You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add lazy-loading and LRU cache for context-based lookups (#62)
"Headless" LSP clients such as Claude Code don't actually open files
when doing lookups or changing them. This means that some of our tricks
for looking things up via the context of the files breaks.
This PR adds lazy disk-fallback to the document store so LSP clients
that don't drive the full `didOpen`/`didChange`/`didClose` lifecycle
(e.g. Claude Code) can still query definitions, hovers, references, and
other text-dependent handlers. Use-injected lookups in particular:
`lookupThroughUse`, `lookupThroughUseOf`. Alias merging also previously
returned `nil` for any file the editor hadn't explicitly opened.
The goal of these changes is enablement work for a proper Claude Code
plugin: #59.
## What changed
- **`DocumentStore.GetOrLoad(uri)`**: lazy disk read for any `file://`
URI that isn't already in the store. Disk-loaded entries are marked
`transient: true` and tracked in a Least Recently Used (LRU) cache.
Non-`file://` URIs (e.g. `untitled:`) return `(false)` without touching
disk to avoid the `uriToPath` panic.
- **LRU eviction**: transient entries are capped (default 50).
Editor-owned buffers (`Set` via `didOpen`) are never counted, never
reordered, never evicted. `Set` cleanly promotes a transient entry to
editor-owned, dropping its LRU bookkeeping.
- **Cap is configurable**: new `maxTransientDocuments` initialization
option (default 50, accepts integer or JSON number, clamps negatives to
0). Documented in `README.md`.
- **Handler migration**: 18 read-only handlers (`Definition`, `Hover`,
`References`, `Completion`, `CodeAction`, `Declaration`,
`DocumentHighlight`, `DocumentSymbol`, `FoldingRanges`,
`Implementation`, `PrepareRename`, `Rename`, `SignatureHelp`,
`TypeDefinition`, `PrepareCallHierarchy`, …) now call `GetOrLoad`.
`Formatting` deliberately keeps `Get` — it only makes sense for
in-memory editor buffers.
## Correctness notes
- File I/O happens outside the write lock; a re-check inside the lock
returns the existing entry if a concurrent `Set` or `GetOrLoad`
populated the URI first.
- `bumpLRU` is a no-op if the URI was promoted to editor-owned between
the RLock and Lock, so the fast-path race is safe.
- `evictTransientLocked` closes the cached tree-sitter tree before
deletion - no leak.
- Race-clean under `go test -race`.
Copy file name to clipboardExpand all lines: README.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -499,6 +499,7 @@ Dexter reads `initializationOptions` from your editor configuration:
499
499
-**`followDelegates`** (boolean, default: `true`): follow `defdelegate` targets on lookup.
500
500
-**`stdlibPath`** (string): override the Elixir stdlib directory to index. Defaults to auto-detection; use this if your install is non-standard.
501
501
-**`debug`** (boolean, default: `false`): enable verbose logging to stderr. Logs timing and resolution details for every definition, hover, references, and rename request. Can also be enabled via the `DEXTER_DEBUG=true` environment variable.
502
+
-**`maxTransientDocuments`** (integer, default: `50`): cap on how many lazily-loaded buffers the server retains in memory. When an LSP client (e.g. Claude Code) queries a file it never opened via `didOpen`, dexter reads it from disk and caches it. Editor-owned buffers are unaffected; only disk-loaded entries are subject to LRU eviction. Set to `0` to disable transient caching.
0 commit comments