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
|
||||
-- tree-sitter detection in `pmacs.parse` (which is grammar-gated:
|
||||
-- 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.py = pmacs.lsp.filetypes.py 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 }.
|
||||
-- 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);
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// path if found.
|
||||
fn which_binary(name: &str) -> std::io::Result<std::path::PathBuf> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue