Skip to content

Consolidate server state onto a single server-context struct#5

Open
nathanweir wants to merge 1 commit into
mainfrom
refactor/server-context
Open

Consolidate server state onto a single server-context struct#5
nathanweir wants to merge 1 commit into
mainfrom
refactor/server-context

Conversation

@nathanweir
Copy link
Copy Markdown
Owner

Summary

CLEF's mutable server state used to live in defparameters scattered across three packages:

  • clef-lsp/server*documents*, *workspace-root*, *handlers*, *client-capabilities*, *initialized*, *output-stream*
  • clef-symbols*lexical-scopes-by-file*, *symbol-refs-by-file*, *workspace-symbol-index*, *document-line-offsets*, *global-scope*
  • clef-lsp/lifecycle*loaded-systems*, *file-to-system*, *asd-files*

Resetting between test runs meant juggling every one of them by hand, and any new handler that wanted to touch state from a different subsystem had to know which package owned which global. The boundaries between "shared server state" and "this module's private cache" were invisible at the call site.

This PR folds all of that persistent state onto a single server-context struct held in a new clef-context package, bound to clef-context:*server*. Short symbol-macro aliases (ctx:documents, ctx:workspace-root, ctx:handlers, ctx:lexical-scopes, ctx:loaded-systems, ...) expand to struct-accessor reads on *server*, so call sites read and write them as if they were ordinary variables — including with setf:

(gethash uri ctx:documents)          ; reads (server-context-documents *server*)
(setf ctx:workspace-root root)       ; setf works through the struct accessor

Shutdown and test setup now replace *server* with a fresh context in one call via ctx:reset-context, which atomically wipes every field instead of resetting them one by one (and forgetting a few).

What stayed behind

Transient state used only while walking a parse tree — *current-scope* and *current-package* in clef-symbols — remains as dynamic specials in that package. They're meaningful only inside a single build-file-symbol-map call and don't belong on the shared context. Everything persistent moved; everything per-call stayed.

Drive-by cleanups

While migrating handlers I also moved the inline (setf (gethash ... clef-lsp/server:*handlers*) ...) registrations that were sitting at the bottom of highlight.lisp, signature-help.lisp, and workspace/symbol.lisp into register-handlers alongside the rest of the endpoints. There was no reason for those three to be registered out-of-band.

Files touched

  • New: src/context.lisp — the server-context struct, *server*, reset-context, and the symbol-macro aliases
  • New package in src/packages.lisp: clef-context, with all field accessors and short aliases exported. Existing packages now use (:local-nicknames (:ctx :clef-context)) and drop their state exports.
  • clef.asd — loads context right after log, before anything that touches server state
  • src/lsp/server.lisp — all globals removed; reset just calls ctx:reset-context
  • src/symbols/init.lisp — persistent state removed; walker specials kept
  • src/lsp/lifecycle/initialize.lisp / initialized.lisp — ASDF state and initialized flag now on context
  • Every handler file under src/lsp/document/ and src/lsp/workspace/ migrated to the new accessors
  • test/document-tests.lisp / test/lifecycle-tests.lisp — assertions reach through clef-context: now
  • CLAUDE.md — architecture section rewritten to document the context pattern and state it as the rule for new fields

Diff size: 20 files changed, +377 / −211.

Test plan

  • just test — all 63 tests pass on the refactor branch
  • Smoke test the running LSP in Helix/Zed against a real CLEF checkout (didOpen, didChange, hover, completion, go-to-definition, references, rename, diagnostics)
  • Verify shutdowninitialize cycle leaves no stale state (open a file, shutdown, re-initialize, confirm the old document/scope trees are gone)
  • Confirm .asd file reload on didSave still repopulates ctx:loaded-systems and ctx:file-to-system

🤖 Generated with Claude Code

Previously CLEF's mutable state was scattered across defparameters in
three packages: clef-lsp/server (*documents*, *workspace-root*,
*handlers*, *client-capabilities*, *initialized*), clef-symbols
(*lexical-scopes-by-file*, *symbol-refs-by-file*, *workspace-symbol-index*,
*document-line-offsets*, *global-scope*), and clef-lsp/lifecycle
(*loaded-systems*, *file-to-system*, *asd-files*). Resetting between
tests meant juggling every one of them, and new handlers reaching across
modules had to know which package owned each global.

All of that persistent state now lives on a single SERVER-CONTEXT struct
in a new clef-context package, bound to clef-context:*server*. Short
symbol-macro aliases (ctx:documents, ctx:workspace-root, ctx:handlers,
ctx:lexical-scopes, ctx:loaded-systems, ...) expand to struct-accessor
reads on *server* so call sites read and write them as ordinary places,
including with setf. Shutdown and test setup now replace *server* with a
fresh context in one call via ctx:reset-context.

The transient walker state in clef-symbols (*current-scope*,
*current-package*) stays as dynamic specials -- it only matters during
one build-file-symbol-map call and doesn't belong on the shared context.

Handler registration for textDocument/documentHighlight,
textDocument/signatureHelp, and workspace/symbol has moved out of
inline sethandler calls at the bottom of each handler file and into
register-handlers, matching the rest of the endpoints.

All 63 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

;; Reprocess the symbol-map. This is terribly jank and inefficient to do on every single change; needs debounced at the very least
(slog :debug "[textDocument/didChange] Rebuilding symbol map for document: ~A..." document-uri)
(let ((start-time (get-internal-real-time)))
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test comment

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