Skip to content

feat(lsp): wire sourcekit-lsp completion into the editor (#1) - #21

Open
Yan123-tech wants to merge 2 commits into
mainfrom
feature/lsp-completion
Open

Yan123-tech wants to merge 2 commits into
mainfrom
feature/lsp-completion

Conversation

@Yan123-tech

@Yan123-tech Yan123-tech commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Closes #1 — sourcekit-lsp completion is wired into the editor.

Batch 1 — transport coverage (commit dbfbfbf)

  • Pure, testable Content-Length frame splitter and response routing.
  • An error: envelope now resumes the parked request throwing the server's ResponseError
    (previously silently turned into null and surfaced as an opaque decode failure).
  • Server exit (read-pipe EOF) fires setDisconnectionHandler; LSPClient.connect flips to
    not-ready so a dead sourcekit fails fast instead of hanging.
  • 12 tests.

Batch 2 — editor wiring (commit 2bf4358)

  • AppState owns the LSPClient lifecycle: lazily started per language id,
    (re)connected on first request with a 5s cooldown, disconnected on quit
    (AppDelegate).
  • LSPTextViewCoordinator (TextViewCoordinator + TextViewDelegate + CodeSuggestionDelegate)
    bound via CodeEditorView(appState:completionDelegate:) / MainWindowView.
    completionSuggestionsRequested filters on the trigger prefix, fuzzy-matches +
    sortTexts via CompletionMatching, applies the server textEdit as
    replaceCharacters. didChange is debounced 100ms (full-content sync).
    A server crash or a response the decoder cannot parse yields a single inline error
    row
    instead of hanging; requests are ACKed to string ids; a 10s request timeout
    fails parked requests rather than parking forever.
  • ClientCapabilities.default advertises static completion
    (dynamicRegistration: false). With dynamic registration, sourcekit-lsp emits a
    structurally-invalid ~212KB completion response (a textEdit value that is a bare
    {<docstring>}, dangling "markdown" tokens, sortText carrying a docstring —
    verified by probing the toolchain binary). Static capabilities return the same items as
    compact, strictly-valid JSON.
  • JSONRPCTransport.repairMalformedJSON is a defensive fallback for that shape; it only
    rewrites :"<...>"} to :null, so well-formed messages pass untouched (unit-tested).

Verification

  • swift build — clean (only the pre-existing EditorState.swift warning).
  • swift test — 108/108 in 30 suites. Includes LSPClientEndToEndTests (sourcekit-lsp
    starts, opens, returns name/id completions), gated on the toolchain binary and
    local-only — CI cannot build or run sourcekit-lsp (see [MEMORY.md Wire LSPClient into SourceEditor via a TextViewCoordinator #1]).
    sourcekit-lsp is flaky as a child of swift test (stalls/OOMs on the ~200KB
    Array-member response), so the E2E uses a 3-member local struct (~1KB response) and
    retries flaky spawns — not a transport defect.
  • swift-format lint --strict — 0 violations.

What's next

Diagnostics, hover and go-to-definition (#2, #3) reuse the transport and lifecycle this
batch proves.

Poolside Agent and others added 2 commits October 10, 2026 18:25
… death

Extract the Content-Length message splitter into a pure, testable
`parseNextMessage(from:)` and the response-routing decision into a testable
`routeResponse`, then exercise both. Round-trip tests (request -> framed
response) and a server-crash test confirm that:

- messages split across reads are reassembled, and malformed headers resync;
- an `error:` envelope resumes the parked request throwing the server's
  ResponseError (previously it was silently turned into `null` and surfaced
  as an opaque decode failure);
- a server exit fails in-flight requests with `notConnected` instead of
  hanging, and fires a new `setDisconnectionHandler` callback.

LSPClient.connect now registers a disconnection handler that flips the client
to not-ready on a dead sourcekit-lsp, so callers fail fast instead of waiting
forever. Docs in the working tree are updated but intentionally not committed.
Nothing called the transport or LSPClient until now (#1). This connects them to
SourceEditor:

- AppState owns the LSPClient lifecycle (start on launch, (re)connect on first
  request, disconnect on quit) so a dead server recovers instead of wedging the UI.
- LSPTextViewCoordinator is bound in via CodeEditorView's appState param and
  completionDelegate. It filters the trigger prefix, fuzzy-matches + sortTexts via
  CompletionMatching, applies the server textEdit as replaceCharacters, debounces
  didChange to a 100ms full-content sync, and turns a server crash or an
  undecodable response into a single inline error row rather than a hang.
- ClientCapabilities.default advertises static completion. sourcekit-lsp's
  dynamically-registered completion path emits structurally-invalid ~212KB JSON
  for some Array members (bare-string textEdit values, dangling "markdown"
  tokens, sortText carrying a docstring); static capabilities return the same
  items as compact, strictly-valid JSON.
- JSONRPCTransport.repairMalformedJSON is a defensive fallback for that shape; it
  only rewrites `:"<...>"}` -> `:null`, so well-formed messages pass untouched.

Verified: swift build (clean, pre-existing EditorState warning only),
swift test 108/108 (30 suites; the sourcekit case is local-only, gated on the
toolchain binary — CI cannot build/run it, see MEMORY.md #1),
swift-format lint --strict clean.

Refs #1
@Yan123-tech
Yan123-tech marked this pull request as ready for review October 10, 2026 17:55
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.

Wire LSPClient into SourceEditor via a TextViewCoordinator

1 participant