Skip to content

Support loading libraries into an already-running language server session - #63

Open
SVAGEN26 wants to merge 6 commits into
OpenModelica:mainfrom
SVAGEN26:svagen/runtime-library-loading
Open

Support loading libraries into an already-running language server session#63
SVAGEN26 wants to merge 6 commits into
OpenModelica:mainfrom
SVAGEN26:svagen/runtime-library-loading

Conversation

@SVAGEN26

@SVAGEN26 SVAGEN26 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

This is a Claude Code agent acting on behalf of @JKRT.

Summary

Closes #62.

Previously the only way for the server to pick up a new library was a full restart: Analyzer.loadLibrary was only ever invoked from ModelicaServer.initialize, so there was no code path to add a library once the server was running. This blocked OMEdit's go-to-definition from cleanly resolving within references into libraries that were not loaded yet (see the workaround discussion in OpenModelica/OpenModelica#15925).

  • ModelicaServer now tracks the resolved paths of libraries/workspaces it has already loaded (#loadedLibraryPaths), so re-announcing the same folder is a no-op instead of loading it (and its documents) a second time.
  • workspace/didChangeWorkspaceFolders is now handled: folders added after startup are loaded into the running analyzer the same way initial workspace folders are. Removed folders are not unloaded yet (out of scope here) — a restart is still needed to fully drop a library.
  • workspace/didChangeConfiguration is now handled: a client can push an updated modelica.libraries list and any new entries get loaded without a restart.
  • Refactored the duplicated "resolve path → load → warn on failure" logic from initialize() into #tryLoadLibrary/#loadConfiguredLibrary so the new runtime paths reuse it instead of re-implementing it.
  • README's "Loading external Modelica libraries" section updated — it previously said a VS Code window reload was required for library changes to take effect; that's no longer accurate for additions.

Verification

Added server/src/test/runtimeLibraryLoad.test.ts, an end-to-end test that spawns the built out/server.js bundle over its real stdio JSON-RPC transport (same transport an editor uses) and:

  1. Initializes the server with only RuntimeLoadLibB loaded (fixtures under server/src/test/fixtures/), where RuntimeLoadLibB.N does extends RuntimeLoadLibA.M.
  2. Confirms textDocument/definition on RuntimeLoadLibA.M returns empty — the reference is unresolved because RuntimeLoadLibA isn't loaded.
  3. Sends workspace/didChangeWorkspaceFolders announcing RuntimeLoadLibA, without restarting the process.
  4. Confirms textDocument/definition now resolves.

This reproduces (and now guards against regressing) the exact gap reported in #62 — I verified the old behavior against this same test harness before making the fix, and it failed at step 4 as expected.

Test plan

  • npm run test:server — 28 passing, including the new end-to-end test.
  • npm run lint — clean.
  • npm run esbuild + manual typecheck (tsc --noEmit) — clean.
  • CI run on this PR.

JKRT and others added 2 commits July 9, 2026 11:23
…sion

Previously the only way to pick up a new library was a full server
restart with the library baked into initializationOptions/workspace
folders, since loadLibrary was only ever called from
ModelicaServer.initialize. This wires up onDidChangeWorkspaceFolders
and onDidChangeConfiguration to load newly announced libraries at
runtime, tracking already-loaded paths so repeated notifications are
no-ops. Unloading a removed workspace folder is out of scope for now
and still requires a restart.

Fixes OpenModelica#62.

Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI's "Build package" step runs the root esbuild.config.js, which
bundles server.ts into ./out/server.js. server/out/server.js is a
different build (server's own esbuild.config.js, used for the
standalone SEA binary) that CI's main test job never produces.

Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread server/src/server.ts Outdated
* Loads libraries added to the workspace after startup, so a client can
* make a new library available (e.g. to resolve an external `within`
* reference) without restarting the server. Folders can only be added;
* there is currently no support for unloading a removed folder.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For long OMEdit sessions this too would be neat to have

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a Claude Code agent acting on behalf of @JKRT.

Done in 48d0937. onDidChangeWorkspaceFolders now handles event.removed: ModelicaProject.removeLibrariesUnder drops every library at or below the removed root (and its documents), Analyzer.unloadLibrary exposes it, and the server forgets the path from #loadedLibraryPaths so the same folder can be re-added later. Covered by unit tests plus an end-to-end add -> resolves -> remove -> stops resolving -> re-add -> resolves cycle.

connection.workspace.onDidChangeWorkspaceFolders throws synchronously
if the client didn't advertise workspace.workspaceFolders. Since
register() runs uncaught inside connection.onInitialize, that turned
into a JSON-RPC error response for the whole initialize request,
silently breaking every LSP feature (hover, definition, symbols) for
that session. Confirmed by probing the built bundle directly with and
without the capability present.

Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
JKRT and others added 3 commits July 9, 2026 12:13
Subscribing via connection.workspace.onDidChangeWorkspaceFolders during
the onInitialize handler happens before the server capabilities are
filled, so the vscode-languageserver library treats the (statically
declared) workspace folder change notification as not-yet-registered and
fires a dynamic client/registerCapability request. The real
vscode-languageclient rejects that redundant registration, and the
resulting unhandled promise rejection crashes the server process right
after initialize. Every subsequent request then goes unanswered, which
is why all four e2e client tests (hover, documentSymbol, declaration)
timed out.

Move the subscription to onInitialized, which runs after capabilities
are filled: the notification is then already auto-registered, so no
dynamic registration is sent and nothing can be rejected. Verified by
driving the built bundle over stdio with a client that rejects the
registration (crashes before the fix, survives after).

Add a regression test that rejects the workspace folder registration
the way vscode-languageclient does and asserts the server process stays
alive and keeps answering requests.

Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The three new end-to-end tests exercise paths the first test did not:
  - loading a library pushed via workspace/didChangeConfiguration
  - re-announcing an already-loaded library (dedup no-op via the
    "already loaded" log) without disturbing resolution
  - announcing a nonexistent folder alongside two real libraries where
    one (RuntimeLoadLibC.P) extends another (RuntimeLoadLibA.M), proving
    the bad entry neither crashes the server nor blocks cross-library
    resolution

Writing them surfaced a real bug: the workspace folder subscription was
set up after `await connection.client.register(didChangeWatchedFiles)`
in onInitialized, so a didChangeWorkspaceFolders notification arriving
before that await resolved was delivered with no handler attached and
silently dropped. The original test only passed because an intermediate
request gave the registration time to complete. Subscribe synchronously
at the top of onInitialized instead (capabilities are already filled, so
no premature dynamic registration is sent).

Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses review feedback on OpenModelica#63: for long-running sessions (e.g. OMEdit)
a user may unload a library, so the server should free it at runtime
instead of only supporting additions.

- ModelicaProject.removeLibrariesUnder(rootPath) drops every library at or
  below a removed root, along with its documents.
- Analyzer.unloadLibrary(uri) exposes that for a workspace/library root.
- onDidChangeWorkspaceFolders now handles event.removed: it unloads the
  affected libraries and forgets their paths from #loadedLibraryPaths so
  the same folder can be re-added later. A removed folder with a non-file
  URI is ignored rather than crashing the handler.

Tests: Analyzer.unloadLibrary unit tests (unload + report once, nested
under a removed root, no-match returns empty) and an end-to-end
add -> resolves -> remove -> stops resolving -> re-add -> resolves cycle.

Co-Authored-By: JKRT <jtinnerholm@gmail.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@AnHeuermann

Copy link
Copy Markdown
Member

@SVAGEN26 add a function to the VS Code client to load a library, makes it easy to manually test this feature.

@JKRT I think this is mostly good to go. Merge when you think it is ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support adding/loading a library into an already-running language server session

3 participants