5.5 KiB
PgTidy LSP Status & Roadmap
This document describes the current state of the PgTidy LSP server (pkg/lsp,
cmd/pgtidy/lsp.go), what it actually provides today, and concrete next steps.
It was written as part of issue #3 ("See what we can provide for LSP").
Note: The V3 milestone is already implemented and shipping (
docs/todo.mdmarks V3 done), so this is an inventory + gap analysis, not a greenfield proposal.
What the server provides today
Verified by hand against the built binary (pgtidy lsp, JSON-RPC 2.0 over stdio,
Content-Length framing) — no external LSP library, all wire types hand-rolled.
Advertised capabilities (initialize → capabilities)
| Capability | Value | Where |
|---|---|---|
textDocumentSync |
{openClose: true, change: 1 (full), willSaveWaitUntil: true} |
serverCaps |
documentFormattingProvider |
true |
handle("initialize") |
documentRangeFormattingProvider |
true |
handle("initialize") |
codeActionProvider |
true |
handle("initialize") |
hoverProvider |
true |
handle("initialize") |
documentSymbolProvider |
true |
handle("initialize") |
Supported methods
| Method | Direction | Behavior |
|---|---|---|
initialize / shutdown / exit / initialized |
req/resp + notif | Lifecycle. exit/shutdown acknowledged with null result. |
textDocument/didOpen |
notif | Stores document text; triggers publishDiagnostics. |
textDocument/didChange |
notif | Stores latest content version; triggers publishDiagnostics. |
textDocument/didClose |
notif | Drops text + fix cache; clears diagnostics with empty list. |
textDocument/formatting |
req/resp | Full-doc format via pkg/format; returns one fullReplace TextEdit. |
textDocument/rangeFormatting |
req/resp | Formats doc, returns minimal edit over the selected line range. |
textDocument/codeAction |
req/resp | Returns quick-fix WorkspaceEdits for fixable diagnostics overlapping the range. |
textDocument/willSaveWaitUntil |
req/resp | Format-on-save: returns the same safety-gated edit as formatting. |
textDocument/hover |
req/resp | Markdown with the rule ID + message of the diagnostic under the cursor; null elsewhere. |
textDocument/documentSymbol |
req/resp | CREATE FUNCTION/PROCEDURE statements (name, kind Function, function/procedure detail, full range + name selection range). |
textDocument/publishDiagnostics |
notif | Sent on every open/change; diagnostic code = RuleID, source = pgtidy; the range covers the offending token, not one character. |
$/cancelRequest |
req | Ignored (per LSP, no response). |
| unknown | req/resp | -32601 method not found (when the request has an id). |
Verified at runtime (e2e smoke test)
initializereturns the capability block above.didOpenonselect * from t;→publishDiagnosticswithCOR001("SELECT * is fragile…", severity 4 = hint, codeCOR001).textDocument/formattingon that input → edit replacing withSELECT *\nFROM t;\n(keyword casing + clause-per-line applied).textDocument/hover→-32601 method not found(not implemented — correct).
What the server does NOT provide (gaps)
Done since the first inventory: real diagnostic highlight range, hover, documentSymbol,
willSaveWaitUntil. Still open:
- No
completion.textDocument/completionis not implemented. Not urgent for a formatter/linter. - No diagnostics debounce/coalescing beyond full-sync. Every
didChangere-runs the full lint engine. Fine for now; matters on large files. initializationOptions/ workspace config.initializeparams are parsed nowhere — no way to pass style overrides or a config path over the protocol.- No
prepareRename,rename,references,foldingRange,documentLink. Low priority;documentSymbolnow provides the symbol info they would build on. - Hover is diagnostics-only. No keyword/type glossary for hover on plain identifiers.
Conventions to keep consistent
- One core, many frontends. The LSP reuses
pkg/diagnostics.Diagnosticandpkg/lintdirectly — no parallel diagnostic model. New LSP features should reuse these, not fork them. - Safety gate is non-negotiable. Both
formattingandrangeFormattingcallformat.SemanticallyEqual(src, out)before returning edits; on failure they return an empty edit (keep original). Any new code path that formats must honor this invariant (invariant #5 inAGENTS.md). - No new external deps. The wire layer is intentionally dependency-free. New protocol types should be added as local structs, not pulled in from an LSP library.
Concrete next steps (recommended, smallest-first)
initializationOptions: accept a config path / style overrides ininitialize.- Debounce
didChangediagnostics. - Hover glossary for keywords/types.
Do NOT: expand the LSP surface into a broad design (workspace features, incremental parsing, custom
textDocument/*extensions). Keep any change scoped and evidence-backed by apkg/lsptest (seeserver_test.gofor the framed-request/response harness).
Verification
go build ./cmd/pgtidysucceeds.go test ./...passes (LSP unit tests inpkg/lsp/server_test.goexerciseinitialize, formatting, range formatting,didClosediagnostics clearing).- Runtime e2e smoke test (framed JSON-RPC over stdio) confirmed
initializecapabilities,COR001diagnostics, and a formatting edit.hover,documentSymbolandwillSaveWaitUntilare covered by tests inpkg/lsp/server_test.go.