Merge pull request #14 from levineuwirth/lsp-clangd-gopls
T M4.5: default C/C++ (clangd) + Go (gopls) language entries
This commit is contained in:
commit
deb9935ee0
|
|
@ -47,6 +47,33 @@ pmacs.lsp.config.python = pmacs.lsp.config.python or {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- C / C++ via clangd. One clangd binary serves both; `config.c` and
|
||||||
|
-- `config.cpp` are separate entries only so the `language_id` sent in
|
||||||
|
-- `didOpen` is accurate (clangd respects it). clangd takes its
|
||||||
|
-- project model from `compile_commands.json` / `compile_flags.txt`
|
||||||
|
-- at the project root, not `workspace/configuration`, so no
|
||||||
|
-- `settings` here; `--background-index` enables cross-file features.
|
||||||
|
-- Users override from init.lua before a C/C++ file opens.
|
||||||
|
pmacs.lsp.config.c = pmacs.lsp.config.c or {
|
||||||
|
command = "clangd",
|
||||||
|
args = { "--background-index" },
|
||||||
|
}
|
||||||
|
pmacs.lsp.config.cpp = pmacs.lsp.config.cpp or {
|
||||||
|
command = "clangd",
|
||||||
|
args = { "--background-index" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Go via gopls. `gopls` with no args serves LSP over stdio. gopls
|
||||||
|
-- pulls its configuration via `workspace/configuration` (now
|
||||||
|
-- answered, #13) under the `gopls` section; an empty section means
|
||||||
|
-- "use defaults" — present, not null, which gopls prefers. Users
|
||||||
|
-- populate it (e.g. staticcheck, analyses) from init.lua.
|
||||||
|
pmacs.lsp.config.go = pmacs.lsp.config.go or {
|
||||||
|
command = "gopls",
|
||||||
|
args = {},
|
||||||
|
settings = { gopls = {} },
|
||||||
|
}
|
||||||
|
|
||||||
-- LSP-side extension → language map, deliberately independent of the
|
-- LSP-side extension → language map, deliberately independent of the
|
||||||
-- tree-sitter detection in `pmacs.parse` (which is grammar-gated:
|
-- tree-sitter detection in `pmacs.parse` (which is grammar-gated:
|
||||||
-- Python has an LSP server but no bundled grammar). Consulted only
|
-- Python has an LSP server but no bundled grammar). Consulted only
|
||||||
|
|
@ -56,6 +83,16 @@ pmacs.lsp.config.python = pmacs.lsp.config.python or {
|
||||||
pmacs.lsp.filetypes = pmacs.lsp.filetypes or {}
|
pmacs.lsp.filetypes = pmacs.lsp.filetypes or {}
|
||||||
pmacs.lsp.filetypes.py = pmacs.lsp.filetypes.py or "python"
|
pmacs.lsp.filetypes.py = pmacs.lsp.filetypes.py or "python"
|
||||||
pmacs.lsp.filetypes.pyi = pmacs.lsp.filetypes.pyi or "python"
|
pmacs.lsp.filetypes.pyi = pmacs.lsp.filetypes.pyi or "python"
|
||||||
|
-- C. `.h` is ambiguous C/C++; default it to C (clangd copes either
|
||||||
|
-- way, and users can remap `pmacs.lsp.filetypes.h = "cpp"`).
|
||||||
|
pmacs.lsp.filetypes.c = pmacs.lsp.filetypes.c or "c"
|
||||||
|
pmacs.lsp.filetypes.h = pmacs.lsp.filetypes.h or "c"
|
||||||
|
-- C++.
|
||||||
|
for _, ext in ipairs({ "cpp", "cc", "cxx", "hpp", "hh", "hxx", "ipp", "inl", "cppm" }) do
|
||||||
|
pmacs.lsp.filetypes[ext] = pmacs.lsp.filetypes[ext] or "cpp"
|
||||||
|
end
|
||||||
|
-- Go.
|
||||||
|
pmacs.lsp.filetypes.go = pmacs.lsp.filetypes.go or "go"
|
||||||
|
|
||||||
-- Per-buffer attachment record: { language, server, uri, version }.
|
-- Per-buffer attachment record: { language, server, uri, version }.
|
||||||
-- Keyed by `tostring(BufferIdLua)` because BufferIdLua hands out fresh
|
-- Keyed by `tostring(BufferIdLua)` because BufferIdLua hands out fresh
|
||||||
|
|
|
||||||
|
|
@ -1190,6 +1190,70 @@ fn m4_5_basedpyright_initializes_and_negotiates_encoding() {
|
||||||
let _ = mgr.borrow_mut().stop(sid);
|
let _ = mgr.borrow_mut().stop(sid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shared body for the PATH-gated real-server smoke tests: spawn,
|
||||||
|
/// reach `Initialized`, and assert the server negotiated a
|
||||||
|
/// `positionEncoding` pmacs can actually encode (absent ⇒ pmacs
|
||||||
|
/// defaults to UTF-16, also fine; a third encoding must never slip
|
||||||
|
/// through). Mirrors the basedpyright test for clangd / gopls —
|
||||||
|
/// strict-by-default servers that exercise the Option B path against
|
||||||
|
/// real implementations, not just the fake.
|
||||||
|
fn assert_lsp_initializes_and_negotiates(
|
||||||
|
label: &str,
|
||||||
|
language_id: &str,
|
||||||
|
command: &str,
|
||||||
|
args: &[&str],
|
||||||
|
) {
|
||||||
|
let (sup, mgr) = make_lsp_test_manager();
|
||||||
|
let mut spec = LspServerSpec::new(label, language_id, command);
|
||||||
|
spec.args = args.iter().map(|s| (*s).to_string()).collect();
|
||||||
|
spec.restart = LspRestartPolicy::Never;
|
||||||
|
let sid = mgr.borrow_mut().spawn(spec).expect("spawn server");
|
||||||
|
let evs = drain_lsp_until(&sup, &mgr, sid, Duration::from_secs(20), |evs| {
|
||||||
|
evs.iter()
|
||||||
|
.any(|e| matches!(e.kind, LspEventKind::Initialized { .. }))
|
||||||
|
});
|
||||||
|
let caps = evs
|
||||||
|
.iter()
|
||||||
|
.find_map(|e| match &e.kind {
|
||||||
|
LspEventKind::Initialized { capabilities } => Some(capabilities.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.expect("must observe Initialized event");
|
||||||
|
assert!(caps.is_object(), "capabilities should be a JSON object");
|
||||||
|
let enc = caps.get("positionEncoding").and_then(|v| v.as_str());
|
||||||
|
assert!(
|
||||||
|
matches!(enc, None | Some("utf-8" | "utf-16")),
|
||||||
|
"{label} negotiated an encoding pmacs cannot handle: {enc:?}"
|
||||||
|
);
|
||||||
|
let state = mgr.borrow().state(sid).cloned();
|
||||||
|
assert!(matches!(state, Some(LspClientState::Initialized { .. })));
|
||||||
|
let _ = mgr.borrow_mut().stop(sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// C/C++ via clangd (PATH-gated). clangd defaults UTF-16 and also
|
||||||
|
/// supports its own `offsetEncoding` extension; either way the
|
||||||
|
/// negotiated encoding must be one pmacs encodes.
|
||||||
|
#[test]
|
||||||
|
fn m4_5_clangd_initializes_and_negotiates_encoding() {
|
||||||
|
let Ok(_) = which_binary("clangd") else {
|
||||||
|
eprintln!("clangd not on PATH; skipping");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
assert_lsp_initializes_and_negotiates("clangd", "cpp", "clangd", &["--background-index"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Go via gopls (PATH-gated). gopls implements LSP 3.17
|
||||||
|
/// position-encoding, defaults UTF-16, and pulls config via
|
||||||
|
/// `workspace/configuration` — exercises the full stack end to end.
|
||||||
|
#[test]
|
||||||
|
fn m4_5_gopls_initializes_and_negotiates_encoding() {
|
||||||
|
let Ok(_) = which_binary("gopls") else {
|
||||||
|
eprintln!("gopls not on PATH; skipping");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
assert_lsp_initializes_and_negotiates("gopls", "go", "gopls", &[]);
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper: scan PATH for a binary by name. Returns the absolute
|
/// Helper: scan PATH for a binary by name. Returns the absolute
|
||||||
/// path if found.
|
/// path if found.
|
||||||
fn which_binary(name: &str) -> std::io::Result<std::path::PathBuf> {
|
fn which_binary(name: &str) -> std::io::Result<std::path::PathBuf> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue