Lazy start Quarto LSP to reduce memory usage in non-Quarto workspaces#1059
Open
jmcphers wants to merge 2 commits into
Open
Lazy start Quarto LSP to reduce memory usage in non-Quarto workspaces#1059jmcphers wants to merge 2 commits into
jmcphers wants to merge 2 commits into
Conversation
Contributor
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
juliasilge
approved these changes
Jul 17, 2026
juliasilge
left a comment
Collaborator
There was a problem hiding this comment.
This is working well for me! 🙌
- Can you add an entry to CHANGELOG? I think might be a good change to make visible for folks, or for us to be able to easily track down later.
- I have one question about Zotero, and whether we should await the library config.
| // sync and re-syncs after any config change made while the server was down). | ||
| // We deliberately do NOT start the server just to push config; if it isn't | ||
| // running the config will be picked up the next time it starts. | ||
| context.subscriptions.push(lsp.onReady(() => { void pushLibraryConfig(); })); |
Collaborator
There was a problem hiding this comment.
Could this race with the first Zotero RPC after lazy startup? pushLibraryConfig() is fire-and-forget and uses the same lazy lspRequest, so a user Zotero request that triggered startup may reach the server before setLibraryConfig. Since the backend returns empty results when no Zotero source is configured, maybe Zotero RPCs should await an initial config-sync promise.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Quarto language server is a separate Node process that consumes 50-100mb of RAM in every Positron session.
Until now it was launched the moment the extension activated, which meant every VS Code window paid that memory cost, including windows that never touch a Quarto document. The most common example is an R project (or any other workspace) that happens to have the Quarto extension installed but contains no
.qmd/.rmdfiles at all. Those sessions were carrying ~98 MB of steady-state overhead for a server they would never use.The goal of this change is to reduce steady-state memory usage, and especially to bring it to zero for workspaces that have no Quarto content.
What changed
The language server is now started lazily, on first actual need, rather than eagerly at activation. Concretely, the extension still creates the language client during activation (so everything is wired up and ready), but it defers spawning the server process until one of the following happens:
If none of these ever occur, such as the "R project with no Quarto files" case, the server is never spawned and the ~98 MB is never allocated. For real Quarto projects, behavior is effectively unchanged; the server comes up as soon as it's needed.
How it's wired up
Activation now returns a small handle instead of the raw language client. The handle exposes a lazy request transport, an
onReadyhook for work that should run "if and when" the server starts (used for pushing the color theme and Zotero configuration), an idempotentensureStarted, and a way to ask for the client only if it's already running.This let a couple of consumers get simpler and better-behaved:
sleepapproach with a clean readiness callback.deactivatewas updated to be a no-op when the server was never started.Tests
Because the server no longer starts eagerly, the workspace-symbol tests could race against startup and indexing. Added a shared
waitForWorkspaceSymbolhelper (built on a generalwaitForConditionpoller) and asuiteSetupin the affected suites that waits for the server to be running and to have indexed the workspace before asserting.