Skip to content

feat(lsp): wire language-server quickfixes into the problems-panel fix workflow#3005

Merged
abose merged 3 commits into
mainfrom
ai
Jul 2, 2026
Merged

feat(lsp): wire language-server quickfixes into the problems-panel fix workflow#3005
abose merged 3 commits into
mainfrom
ai

Conversation

@abose

@abose abose commented Jul 2, 2026

Copy link
Copy Markdown
Member

Diagnostics and fixes are separate LSP channels: fixes must be requested via
textDocument/codeAction with the diagnostics as context. We consumed only
publishDiagnostics, so the servers' quickfixes (vtsls implements them fully)
never reached CodeInspection's existing fix UI (fixable icons, per-problem
fix buttons, Fix All).

Server-agnostic by construction - everything lives on the generic
LanguageClient prototype and the per-client LintingProvider, gated purely on
runtime capabilities, so any future server (PHP etc.) inherits quickfixes
with zero extra wiring:

  • LSPClient: advertise codeAction literal support (quickfix kind; no
    resolveSupport, so conformant servers inline edits - one round trip). New
    requestCodeActions (flush -> codeAction with context.diagnostics, gated on
    the server's codeActionProvider, boolean or object) and resolveCodeAction
    (only for actions missing edit on servers advertising resolveProvider).
    publishDiagnostics hands the RAW diagnostics (range/code/data intact - the
    provider flattens them for CodeInspection) to the quickfix layer.

  • LintingProvider: idle-fetch policy so this is cheap - fetch ~800ms after
    diagnostics settle, rescheduled by newer publishes (zero codeAction
    traffic while typing), only for the active server-synced file, deduped by
    (lastChangeTimestamp, diagnostics signature) so identical re-publishes and
    file switches to unchanged files cost zero requests; an activeEditorChange
    hook catches diagnostics that arrived in a background pane. Strategy is
    runtime-adaptive: one batched whole-file request (vtsls answers it),
    falling back to per-diagnostic requests capped at 20. Admitted actions:
    literal quickfix, not disabled, exactly one TextEdit in exactly this file
    -> converted to CodeInspection's fix contract (replaceText + character
    offsets via indexFromPos) and attached to the matching cached error, then
    CodeInspection.requestRun() re-registers them (staleness stays automatic:
    CodeInspection drops fixes whenever the document changes).

    Identical re-publishes now also KEEP the existing errors array instead of
    replacing it - a fresh array would silently wipe the decorated fixes
    without triggering a re-fetch. Diagnostics additionally carry endPos now,
    so squiggles span the server-reported range instead of one token.

  • CodeInspection: correct the stale fix JSDoc (replace -> replaceText, which
    _isInvalidFix actually requires).

End-to-end test in integration:TypeScript LSP: consol.log(1) -> the 2552
diagnostic appears, the idle fetch surfaces Fix All, clicking it rewrites to
console.log(1). Fix application needs no editor focus, so it runs in an
unfocused test window. Suite green 14/14; verified live (single fix, Fix
All, icons reappearing after typing settles).

abose added 3 commits July 2, 2026 13:46
…x workflow

Diagnostics and fixes are separate LSP channels: fixes must be requested via
textDocument/codeAction with the diagnostics as context. We consumed only
publishDiagnostics, so the servers' quickfixes (vtsls implements them fully)
never reached CodeInspection's existing fix UI (fixable icons, per-problem
fix buttons, Fix All).

Server-agnostic by construction - everything lives on the generic
LanguageClient prototype and the per-client LintingProvider, gated purely on
runtime capabilities, so any future server (PHP etc.) inherits quickfixes
with zero extra wiring:

- LSPClient: advertise codeAction literal support (quickfix kind; no
  resolveSupport, so conformant servers inline edits - one round trip). New
  requestCodeActions (flush -> codeAction with context.diagnostics, gated on
  the server's codeActionProvider, boolean or object) and resolveCodeAction
  (only for actions missing `edit` on servers advertising resolveProvider).
  publishDiagnostics hands the RAW diagnostics (range/code/data intact - the
  provider flattens them for CodeInspection) to the quickfix layer.

- LintingProvider: idle-fetch policy so this is cheap - fetch ~800ms after
  diagnostics settle, rescheduled by newer publishes (zero codeAction
  traffic while typing), only for the active server-synced file, deduped by
  (lastChangeTimestamp, diagnostics signature) so identical re-publishes and
  file switches to unchanged files cost zero requests; an activeEditorChange
  hook catches diagnostics that arrived in a background pane. Strategy is
  runtime-adaptive: one batched whole-file request (vtsls answers it),
  falling back to per-diagnostic requests capped at 20. Admitted actions:
  literal quickfix, not disabled, exactly one TextEdit in exactly this file
  -> converted to CodeInspection's fix contract (replaceText + character
  offsets via indexFromPos) and attached to the matching cached error, then
  CodeInspection.requestRun() re-registers them (staleness stays automatic:
  CodeInspection drops fixes whenever the document changes).

  Identical re-publishes now also KEEP the existing errors array instead of
  replacing it - a fresh array would silently wipe the decorated fixes
  without triggering a re-fetch. Diagnostics additionally carry endPos now,
  so squiggles span the server-reported range instead of one token.

- CodeInspection: correct the stale fix JSDoc (replace -> replaceText, which
  _isInvalidFix actually requires).

End-to-end test in integration:TypeScript LSP: consol.log(1) -> the 2552
diagnostic appears, the idle fetch surfaces Fix All, clicking it rewrites to
console.log(1). Fix application needs no editor focus, so it runs in an
unfocused test window. Suite green 14/14; verified live (single fix, Fix
All, icons reappearing after typing settles).
The QuickView popover centers and shrink-wraps each provider's content
(.quick-view-item is a flex column with align-items:center - right for
image/color previews). When another provider widens the popover - e.g. the
problem row with its Fix button that now accompanies quickfixes - the
narrower hover content floated centered in the leftover space (a small
signature box in the middle of a wide popup). align-self:stretch on
.lsp-hover-quickview makes just the LSP hover fill the available width,
left-aligned, while image previews stay centered as designed.
The intermittent CI failure of "should jump to definition" (LegacyInteg:
EditorCommandHandlers) was not cold-start latency: the spec's first
NAVIGATE_JUMPTO_DEFINITION fired while tsserver was still loading the
project, the server answered with no definition, and doJumpToDef's deferred
only resolved inside `if (msgObj && msgObj.range)` - an empty result NEVER
settled it. The command promise then hung forever, and awaitsFor races each
poll invocation against the FULL timeout, so the "retry loop" never retried
once and the spec burned its whole budget on one dead promise (the
SpecRunner.js:141 stack in the CI logs). Manual jumps worked the whole time
- the server was healthy; the spec was pinned.

- DefaultProviders.doJumpToDef: settle (reject) when the server returns no
  definition, and on a failed cross-file FILE_OPEN. Also fixes real users:
  F12 on a symbol with no definition left a permanently pending command.
- src-node/lsp-client: on unexpected server exit, reject all in-flight
  requests immediately - the dead process can never answer them, and they
  previously hung callers until the 2-minute per-request timeout (only
  graceful stops flushed the pending map).
- The spec: the one-time tsserver cold start now happens in the suite's
  beforeAll warm-up (the TypeScript LSP suite's pattern, 90s budget there);
  the spec itself waits max 15s, polling every 500ms with each jump attempt
  time-boxed to 3s - a slow or rejected attempt is a retry, never a pin -
  and a rejected jump no longer aborts awaitsFor via an exception.

LegacyInteg:EditorCommandHandlers Integration green 12/12 twice in a row
(including the focus-sensitive clipboard spec).
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

@abose abose merged commit ab87849 into main Jul 2, 2026
8 of 21 checks passed
@abose abose deleted the ai branch July 2, 2026 09:25
@abose abose restored the ai branch July 2, 2026 10:15
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.

1 participant