summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/lsp/language.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 17:52:14 +0900
committerAdam Malczewski <[email protected]>2026-06-02 17:52:14 +0900
commit062d01bd2f5c3ab6de7747dc5028e66b81dac6f5 (patch)
tree6097df0d53265f1a5e734aadab75c0334cb8e0e7 /packages/core/src/lsp/language.ts
parentb3aca3efe9e8cda79db6e2c7fa20482880ed16c3 (diff)
downloaddispatch-062d01bd2f5c3ab6de7747dc5028e66b81dac6f5.tar.gz
dispatch-062d01bd2f5c3ab6de7747dc5028e66b81dac6f5.zip
feat(lsp): add config-driven LSP support (Roblox Luau via luau-lsp)
Add Language Server Protocol integration modeled on opencode's, wired for this codebase's plain-TypeScript tool/agent architecture. Core (@dispatch/core): - lsp/client.ts: LSP/JSON-RPC client over stdio (vscode-jsonrpc) with the initialize handshake, didOpen/didChange sync, push + pull diagnostics (textDocument/diagnostic, workspace/diagnostic), and a generic request() passthrough for hover/definition/references/documentSymbol. - lsp/server.ts: resolves dispatch.toml [lsp] entries into spawn specs. Config-driven only — no builtin registry, no auto-download. - lsp/manager.ts: process-wide LspManager owning client lifecycles, keyed by root+serverID, lazy spawn + reuse + graceful shutdown. - lsp/language.ts: extension->languageId map incl. .luau -> "luau". - lsp/diagnostic.ts: error-only <diagnostics> block formatting (1-based). - tools/lsp.ts: on-demand 'lsp' tool (1-based coords -> 0-based wire). - write-file.ts: optional onAfterWrite hook for diagnostics-on-write. - config schema: validate [lsp] block; DispatchConfig.lsp + LspServerConfig. API (@dispatch/api): - AgentManager owns one LspManager; per-working-directory server cache cleared on config reload; diagnostics appended to write_file results; 'lsp' tool gated by new perm_lsp setting; shutdownAll on destroy(). Config: - dispatch.toml: documented, commented [lsp.luau-lsp] Roblox example. Tests: fake-lsp-server fixture + client/manager/server/diagnostic/schema/ tool/write-hook suites, plus an opt-in real-binary luau-lsp smoke test (auto-skipped when luau-lsp is absent). 652 pass; biome + 3 typechecks green.
Diffstat (limited to 'packages/core/src/lsp/language.ts')
-rw-r--r--packages/core/src/lsp/language.ts72
1 files changed, 72 insertions, 0 deletions
diff --git a/packages/core/src/lsp/language.ts b/packages/core/src/lsp/language.ts
new file mode 100644
index 0000000..3e9fe68
--- /dev/null
+++ b/packages/core/src/lsp/language.ts
@@ -0,0 +1,72 @@
+/**
+ * File-extension → LSP `languageId` map.
+ *
+ * The LSP `textDocument/didOpen` notification carries a `languageId` string
+ * that tells the server how to parse the document. This table is a trimmed
+ * port of opencode's `lsp/language.ts`, with one critical addition for this
+ * project: `.luau` → `"luau"`. Roblox Luau sources use the `.luau` extension,
+ * which standard Lua tooling does not recognise — luau-lsp expects the
+ * `"luau"` languageId.
+ *
+ * Extensions are looked up with their leading dot (e.g. `".luau"`). Unknown
+ * extensions fall back to `"plaintext"` at the call site.
+ */
+export const LANGUAGE_EXTENSIONS: Record<string, string> = {
+ // Luau (Roblox) — the reason this module exists. Keep first for visibility.
+ ".luau": "luau",
+ ".lua": "lua",
+ // A pragmatic subset of common languages, mirroring opencode's table so a
+ // user can point an arbitrary LSP server at this codebase and have the
+ // right languageId reported.
+ ".c": "c",
+ ".cpp": "cpp",
+ ".cc": "cpp",
+ ".cxx": "cpp",
+ ".h": "c",
+ ".hpp": "cpp",
+ ".cs": "csharp",
+ ".css": "css",
+ ".dart": "dart",
+ ".go": "go",
+ ".html": "html",
+ ".htm": "html",
+ ".java": "java",
+ ".js": "javascript",
+ ".jsx": "javascriptreact",
+ ".json": "json",
+ ".jsonc": "jsonc",
+ ".kt": "kotlin",
+ ".kts": "kotlin",
+ ".md": "markdown",
+ ".markdown": "markdown",
+ ".php": "php",
+ ".py": "python",
+ ".rb": "ruby",
+ ".rs": "rust",
+ ".scss": "scss",
+ ".sass": "sass",
+ ".sh": "shellscript",
+ ".bash": "shellscript",
+ ".zsh": "shellscript",
+ ".sql": "sql",
+ ".svelte": "svelte",
+ ".swift": "swift",
+ ".toml": "toml",
+ ".ts": "typescript",
+ ".tsx": "typescriptreact",
+ ".mts": "typescript",
+ ".cts": "typescript",
+ ".vue": "vue",
+ ".xml": "xml",
+ ".yaml": "yaml",
+ ".yml": "yaml",
+ ".zig": "zig",
+};
+
+/**
+ * Resolve the LSP `languageId` for a file path's extension, falling back to
+ * `"plaintext"` when the extension is unknown.
+ */
+export function languageIdForExtension(extension: string): string {
+ return LANGUAGE_EXTENSIONS[extension] ?? "plaintext";
+}