kmp-lsp — Capabilities & Limitations
⚠️ Prerequisite: Experimental mode required
The lsp tool is only available when Copilot CLI is started with copilot --experimental (or --exp).
Without it, the LSP tool does not appear and kmp-lsp will not be connected.
If you see no lsp tool available, ask the user to restart with copilot --experimental.
You have access to a Kotlin/Java/Swift LSP server (kmp-lsp) via the lsp tool.
Language support
- Kotlin / Java — full support: indexing, hover, goToDefinition, workspaceSymbol, goToImplementation, findReferences, rename
- Swift — structural support: documentSymbol (immediate), hover (property types), goToDefinition (cross-module); no type inference (no sourcekit-lsp backend)
Indexing & Readiness
The server indexes files in the background on startup. Before using workspaceSymbol, always call kmp_lsp_status to check if indexing is complete.
- Cold index (no cache): 30–70s depending on project size
- Warm start (from cache): 1–3s
- Progress shown in editor (0% → live % every 500ms → done)
Cold-start navigation: documentSymbol, hover, and goToDefinition work immediately on any opened file — the current file is indexed on-demand before symbol lookup. workspaceSymbol requires full indexing.
Cache staleness: cache version bump triggers a full re-index automatically. A "Cache deserialize failed" warning means a one-time re-index is occurring.
Workspace root auto-detection
When no config file is set, did_open detects the workspace root using tiered marker priority:
- Strong markers (nearest wins):
settings.gradle.kts,build.gradle,pom.xml,Cargo.toml— subproject root in a mono-repo .git— repo root; wins over weak markers- Weak markers:
Package.swift— last resort (present at every Swift module, not reliable as root)
This correctly handles mono-repos (e.g. android/settings.gradle.kts beats monorepo .git) and Swift mono-repos (e.g. ios/.git beats nested ios/Modules/*/Package.swift).
What works reliably ✅
- textDocument/documentSymbol — list symbols in a file; always works (disk fallback for un-indexed files)
- textDocument/hover — signature + doc comments; works before full index (on-demand index of current file)
- workspace/symbol — find class/function by name; supports dot-qualified extension fn queries (e.g.
StoreState.isReady); needs full index - textDocument/definition — go to source; works before full index (current file indexed on-demand); rg fallback for cross-file
- textDocument/references — find all usages; needs index + rg fallback
- textDocument/implementation — interface implementors (transitive BFS); needs index
- textDocument/rename — cross-file rename; needs index
- textDocument/codeAction — add missing import; uses rg, works without full index
What works poorly or not at all ⚠️
- workspaceSymbol before index is ready — returns empty; use
kmp_lsp_statusto check first - workspaceSymbol immediately after workspace switch — if called before any
did_open, may return stale results from previous workspace; open a file first to trigger switch + re-index - Swift: hover on function definitions — property type hover works; function def hover not yet supported
- Swift: goToDefinition on local function calls — cross-module works; same-file function calls not yet resolved via Swift type system
- Extension functions (dot-receiver, cross-file) — use
lsp workspaceSymbolwith dot-qualified query (e.g.ReceiverType.methodName) instead of goToDefinition - No type inference — tree-sitter based, not compiler-backed; generic type params unresolved
- Java interop — Java symbols indexed, but cross-language go-to-def is unreliable
- No compiler diagnostics — tree-sitter only; use
kmp-lsp check <file>(syntax errors, instant) orkmp-lsp diagnose <file> --root .(call-arg + syntax, needs index) for post-edit checks - rg alternation syntax — use
|(not\|) in ripgrep;\|is GNU grep syntax
Extension-provided tools
kmp_lsp_set_workspace
Switch the Copilot CLI kmp-lsp instance to a different workspace directory.
- Writes the path to
~/.config/kmp-lsp/workspace - Kills only the Copilot-managed server (PID from
~/.cache/kmp-lsp/status.json) - Editor LSP instances are not affected
- Requires
KMP_LSP_PREFER_CONFIG_ROOT=1in lsp-config.json env block
kmp_lsp_status
Check current workspace, indexing phase, symbol count, and server PID.
Call before workspaceSymbol to confirm indexing is complete.
kotlin_find_subtypes
Last-resort fallback — lsp goToImplementation handles this natively with transitive subtypes.
Only use if goToImplementation returns empty (LSP not indexed yet, or edge case).
- Uses rg text search — returns candidates, not compiler-verified results
kotlin_rg
Restricted ripgrep for Kotlin/Java/Swift files — fallback only when LSP cannot help.
- Requires a
reasonexplaining why LSP can't help - Valid reasons: extension functions, LSP returned empty, free-text search, generated code, convention discovery
- Rejects simple identifier lookups without valid justification
Practical workflow for code investigation
Kotlin/Java (Android)
kmp_lsp_status— wait for indexing to complete before workspaceSymbollsp workspaceSymbol "ClassName"— get exact file path + linelsp documentSymbol file.kt— enumerate all symbols in filelsp hover file.kt line col— type info, signature, doc commentlsp goToDefinition file.kt line col— jump to sourcelsp findReferences file.kt line col— all usages cross-project; for common names (Event,Result,State) usekmp-lsp refs <Name> --exclude-importsinstead to strip import noiselsp goToImplementation file.kt line col— interface subtypes (transitive)viewwith line range — read code at known locationkmp-lsp check <file>— verify syntax after edits (instant, no index needed)kotlin_rg— only for free-text, extension fns, generated code (provide reason)
Swift (iOS)
lsp documentSymbol file.swift— always works immediately; get symbols + line numberslsp hover file.swift line col— property type info; works immediately (on-demand index)lsp goToDefinition file.swift line col— cross-module jump works; local func calls may notlsp workspaceSymbol "ClassName"— wait for full indexing; may be empty until thenviewwith line range — read code at known locationkotlin_rg— for free-text, protocol conformance patterns (provide reason)
Note: For Swift, documentSymbol + view is often more reliable than waiting for full indexing.
Hook behavior — what gets blocked vs allowed
The onPreToolUse hook enforces LSP-first for Kotlin/Java/Swift symbol navigation.
Always allowed:
globtool (file discovery)greptargeting a single known filebashwith non-search commands (ls,cat,head,find -name,fd, etc.)- Complex regex patterns (convention/pattern discovery)
- Free-text searches (TODO, comments, strings, logs)
- Non-Kotlin/Swift context
Blocked:
grep/rgwith a simple identifier pattern across a broad directory in Kotlin/Swift context- Use LSP first, then
kotlin_rgwith a reason if LSP can't help
Workspace root
The kmp-lsp server reads its workspace root from ~/.config/kmp-lsp/workspace (plain text, absolute path).
- To switch projects:
echo "/path/to/project" > ~/.config/kmp-lsp/workspace - The
kmp_lsp_set_workspacetool writes this file and kills the server to force restart. - Without this file, the server auto-detects root from the first opened file (see auto-detection above).
- After kill+restart: open a file before calling
workspaceSymbol—did_opentriggers root detection and indexing.
Source: Hessesian/kotlin-lsp — distributed by TomeVault.