Add lifecycle-aware NullFormatter placeholder for formatter tool extensions#63
Conversation
|
Core fix looks good. A few non-blocking concerns worth addressing before merge: the re-registration coverage claimed in the CHANGELOG/JSDoc doesn't fully hold for extension-driven restarts, the failure path can leave the extension with no placeholder, and the new shared-mock additions are unused by the actual test suite. |
- Re-register placeholder at the start of each runServer() cycle to cover extension-driven restarts (where the old state listener is disposed before restartServer stops the previous client) and failure paths (restartServer throws or returns no client). - Replace inline makeMockClient with shared LanguageClient mock from _languageclient_mock.ts so test and production getter semantics stay in sync. - Rename Test 2 to reflect it exercises crash/recovery, not the extension-driven restart path. - Add Test 7 (second runServer call) to exercise the dispose-then- reattach path for extension-driven restarts. - Add Test 8 to verify placeholder stays registered when restartServer returns no client. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Solid fix that moves the formatter placeholder lifecycle into the shared package. One thing worth a look: the eager |
- Guard nullFormatter.register() at the top of runServer() with 'if (!ctx.lsClient || ctx.lsClient.state !== State.Running)' to prevent the transient duplicate-formatter symptom (#752) when restarting a healthy Running server. - Add explicit register() in the result.client branch when the new client is not yet Running, covering the case where the guard skipped registration because the *old* client was still Running. - Add explicit register() in the catch block and the no-client else branch to cover failure paths. - Update isFormatter JSDoc in types.ts to document both the state-listener path (crash recovery) and the eager register path (extension-driven restarts). - Update Test 7 to model the first client being Running when second runServer is called, verifying the guard prevents transient duplicate and placeholder is re-registered only after restartServer returns. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
GitHub cannot anchor PR review comments to unchanged lines in the diff. Falling back to a general PR comment for typescript/src/activation.ts:L188. 📍 typescript/src/activation.ts:184 [verified] |
|
Solid fix overall, but one blocking issue: the failure-path |
…ailure Apply the same state !== Running guard to the catch block's nullFormatter.register() call, preventing the duplicate-formatter symptom (#752) when a restart fails while a healthy client is still running. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed in 4a2bfc2. The catch block's |
|
GitHub cannot anchor PR review comments to unchanged lines in the diff. Falling back to a general PR comment for typescript/src/activation.ts:L186. The empty-interpreter branch ( |
|
Solid fix with good test coverage. Two things before merge: remove the inline |
… #752 ref - Add nullFormatter.register() in the empty-interpreter branch so a Running formatter that loses its interpreter stays visible in the formatter picker. - Remove inline #752 issue reference from code comments (kept in CHANGELOG/PR only). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed in 76e1bad:
|
|
Solid, well-tested fix for the duplicate-formatter regression. A couple of non-blocking notes below on the empty-interpreter exit path and test fidelity; safe to merge once you've had a look. |
Formatter extensions (black, autopep8, isort) register a placeholder
DocumentFormattingEditProviderat activation so the extension appears in the picker before the LSP starts — but never dispose it once the LSP reachesRunning. VS Code sees two providers with the same selector and shows the extension twice in the formatter picker (vscode-black-formatter#752).The fix moves the full placeholder lifecycle into this shared package so every formatter extension gets it for free.
New:
ToolConfig.isFormatterOptional boolean flag. When
true,createToolContextautomatically manages the placeholder lifetime. Linter-only tools (pylint, flake8, mypy, …) leave it unset — zero behavioral change.New:
NullFormatterclass (typescript/src/nullFormatter.ts)Lifecycle-aware wrapper around
registerDocumentFormattingEditProvider. Exported as public API for consumers that want manual control.createToolContextwiring (typescript/src/activation.ts)When
isFormatter: true:nullFormatter.register()eagerly at context creation (extension visible in picker from activation)restartServer()resolves: if client is alreadyState.Running(initial transition is missed by theonDidChangeStatelistener — same pattern as the existing busy-status reset), disposes the placeholder immediatelyonDidChangeStatelistener:Running→unregister(),Stopped/Starting→register()(covers restart cycles)dispose()callsnullFormatter?.dispose()Tests
Six new tests in
activation.test.tscover: activation-time registration, disposal onState.Runningvia listener, fullStopped → Starting → Runningrestart cycle, the already-Running synchronous guard, andisFormatter: false/unset no-ops. Mock client extended withstategetter andsimulateStateChange().Version bump
0.6.1→0.7.0(new public API:ToolConfig.isFormatter,NullFormatterexport).