diff --git a/builtin/runtime/lsp.lua b/builtin/runtime/lsp.lua index e994a72..32bdeb1 100644 --- a/builtin/runtime/lsp.lua +++ b/builtin/runtime/lsp.lua @@ -84,6 +84,65 @@ pmacs.lsp.config.go = pmacs.lsp.config.go or { settings = { gopls = {} }, } +-- TypeScript / JavaScript via typescript-language-server (the +-- tsserver wrapper). One binary serves the whole family; like +-- `c`/`cpp` these are separate config entries purely so the +-- `language_id` sent in `didOpen` is accurate — tsserver keys +-- diagnostics and some code actions off it (typescript / +-- typescriptreact / javascript / javascriptreact). `--stdio` is the +-- LSP transport. The project model comes from `tsconfig.json` / +-- `jsconfig.json` (no `workspace/configuration` pull). Users +-- override from init.lua before a TS/JS file opens; swapping to +-- `vtsls` is just `command = "vtsls"`. +for _, lid in ipairs({ "typescript", "typescriptreact", "javascript", "javascriptreact" }) do + pmacs.lsp.config[lid] = pmacs.lsp.config[lid] or { + command = "typescript-language-server", + args = { "--stdio" }, + } +end + +-- Lua via lua-language-server (sumneko). Speaks LSP over stdio with +-- no transport flag. It pulls configuration via +-- `workspace/configuration` under the `Lua` section; an empty table +-- is "use defaults" — present, not null (same rationale as gopls). +-- `.lua` is tree-sitter-grammar-backed, so `pmacs.parse` already +-- resolves the `lua` language and this is the config that attaches. +-- Users populate `settings.Lua` (runtime.version, workspace.library, +-- diagnostics.globals = { "pmacs" }, …) from init.lua. +pmacs.lsp.config.lua = pmacs.lsp.config.lua or { + command = "lua-language-server", + args = {}, + settings = { Lua = {} }, +} + +-- Bash / shell via bash-language-server. `start` is its +-- LSP-over-stdio subcommand. No project config; it shells out to +-- `shellcheck` (diagnostics) and `shfmt` (formatting) when those are +-- on PATH. Users override from init.lua before a shell file opens. +pmacs.lsp.config.bash = pmacs.lsp.config.bash or { + command = "bash-language-server", + args = { "start" }, +} + +-- TOML via taplo. `taplo lsp stdio` serves LSP over stdio. taplo +-- pulls configuration via `workspace/configuration` under the +-- `taplo` section (empty ⇒ defaults, present not null); a project +-- `.taplo.toml` still wins. Users populate it from init.lua. +pmacs.lsp.config.toml = pmacs.lsp.config.toml or { + command = "taplo", + args = { "lsp", "stdio" }, + settings = { taplo = {} }, +} + +-- Zig via zls. `zls` with no args serves LSP over stdio; it reads +-- `zls.json` / `build.zig` for the project model (no +-- `workspace/configuration` pull). Users override from init.lua +-- before a `.zig` / `.zon` file opens. +pmacs.lsp.config.zig = pmacs.lsp.config.zig or { + command = "zls", + args = {}, +} + -- LSP-side extension → language map, deliberately independent of the -- tree-sitter detection in `pmacs.parse` (which is grammar-gated: -- Python has an LSP server but no bundled grammar). Consulted only @@ -103,6 +162,28 @@ for _, ext in ipairs({ "cpp", "cc", "cxx", "hpp", "hh", "hxx", "ipp", "inl", "cp end -- Go. pmacs.lsp.filetypes.go = pmacs.lsp.filetypes.go or "go" +-- Tier 1 single-binary servers. TypeScript / JavaScript distinguish +-- the JSX variants so the server enables the JSX parser. +for _, ext in ipairs({ "ts", "mts", "cts" }) do + pmacs.lsp.filetypes[ext] = pmacs.lsp.filetypes[ext] or "typescript" +end +pmacs.lsp.filetypes.tsx = pmacs.lsp.filetypes.tsx or "typescriptreact" +for _, ext in ipairs({ "js", "mjs", "cjs" }) do + pmacs.lsp.filetypes[ext] = pmacs.lsp.filetypes[ext] or "javascript" +end +pmacs.lsp.filetypes.jsx = pmacs.lsp.filetypes.jsx or "javascriptreact" +-- Lua (lua-language-server). pmacs bundles a Lua grammar, so +-- `language_for_path` resolves `.lua` first; this entry is the LSP +-- fallback and keeps the language id stable if that ever changes. +pmacs.lsp.filetypes.lua = pmacs.lsp.filetypes.lua or "lua" +-- Bash / shell (bash-language-server). +pmacs.lsp.filetypes.sh = pmacs.lsp.filetypes.sh or "bash" +pmacs.lsp.filetypes.bash = pmacs.lsp.filetypes.bash or "bash" +-- TOML (taplo). +pmacs.lsp.filetypes.toml = pmacs.lsp.filetypes.toml or "toml" +-- Zig (zls). `.zon` is Zig Object Notation, handled by the same server. +pmacs.lsp.filetypes.zig = pmacs.lsp.filetypes.zig or "zig" +pmacs.lsp.filetypes.zon = pmacs.lsp.filetypes.zon or "zig" -- Per-buffer attachment record: { language, server, uri, version }. -- Keyed by `tostring(BufferIdLua)` because BufferIdLua hands out fresh diff --git a/tests/m4_acceptance.rs b/tests/m4_acceptance.rs index 708ee59..6d9d40e 100644 --- a/tests/m4_acceptance.rs +++ b/tests/m4_acceptance.rs @@ -4364,6 +4364,92 @@ fn m4_24_workspace_did_change_watched_files() { ); } +/// Tier 1 single-binary language servers ship pre-configured in the +/// default bundle. Binary-independent: we don't spawn anything, just +/// assert the `pmacs.lsp.config` tables and the `pmacs.lsp.filetypes` +/// extension→language map resolve to the documented values, so a user +/// who installs `typescript-language-server` / `lua-language-server` / +/// `bash-language-server` / `taplo` / `zls` gets attachment with no +/// init.lua. +#[test] +fn m4_25_tier1_language_server_configs_and_filetypes() { + use pmacs::editor::EditorState; + + let s = EditorState::new(); + let probe: mlua::Table = s + .lua_host + .lua() + .load( + r#" + local out = {} + local c = pmacs.lsp.config + local ft = pmacs.lsp.filetypes + + -- TypeScript / JavaScript family: one binary, four language + -- ids so didOpen reports the right one. --stdio transport. + local tsjs_ok = true + for _, lid in ipairs({ "typescript", "typescriptreact", + "javascript", "javascriptreact" }) do + local e = c[lid] + if not (e and e.command == "typescript-language-server" + and e.args and e.args[1] == "--stdio") then + tsjs_ok = false + end + end + out.tsjs = tsjs_ok + + -- Lua: lua-language-server, no transport flag, settings.Lua + -- present-not-null for the workspace/configuration pull. + out.lua = c.lua ~= nil + and c.lua.command == "lua-language-server" + and type(c.lua.settings) == "table" + and type(c.lua.settings.Lua) == "table" + + -- Bash: bash-language-server start. + out.bash = c.bash ~= nil + and c.bash.command == "bash-language-server" + and c.bash.args and c.bash.args[1] == "start" + + -- TOML: taplo lsp stdio, settings.taplo present-not-null. + out.toml = c.toml ~= nil + and c.toml.command == "taplo" + and c.toml.args and c.toml.args[1] == "lsp" + and c.toml.args[2] == "stdio" + and type(c.toml.settings) == "table" + and type(c.toml.settings.taplo) == "table" + + -- Zig: zls, no args. + out.zig = c.zig ~= nil and c.zig.command == "zls" + + -- Extension → language map. + out.ft = ft.ts == "typescript" + and ft.mts == "typescript" + and ft.cts == "typescript" + and ft.tsx == "typescriptreact" + and ft.js == "javascript" + and ft.mjs == "javascript" + and ft.cjs == "javascript" + and ft.jsx == "javascriptreact" + and ft.sh == "bash" + and ft.bash == "bash" + and ft.toml == "toml" + and ft.zig == "zig" + and ft.zon == "zig" + and ft.lua == "lua" + + return out + "#, + ) + .eval() + .expect("probe tier1 config + filetypes"); + assert!(probe.get::("tsjs").unwrap(), "ts/js family config"); + assert!(probe.get::("lua").unwrap(), "lua config"); + assert!(probe.get::("bash").unwrap(), "bash config"); + assert!(probe.get::("toml").unwrap(), "toml config"); + assert!(probe.get::("zig").unwrap(), "zig config"); + assert!(probe.get::("ft").unwrap(), "filetype map"); +} + /// Default LSP bundle (`builtin/runtime/lsp.lua`) is wired in: the /// hooks are defined, the namespace tables exist, the user-facing /// commands are registered with the command registry, and the default