Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import modulus_tool
|
||||
|
||||
if __name__ == "__main__":
|
||||
modulus_tool.run("clangd")
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Runs only in the build image, during configure/lsp, never server startup."""
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def header_roots(config):
|
||||
roots = set()
|
||||
for driver in config["query_drivers"]:
|
||||
for language in ("c", "c++"):
|
||||
result = subprocess.run([driver, "-E", "-v", "-x", language, "-"], input="",
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
|
||||
listing = False
|
||||
for line in result.stderr.splitlines():
|
||||
if line == "#include <...> search starts here:":
|
||||
listing = True
|
||||
elif line == "End of search list.":
|
||||
listing = False
|
||||
elif listing:
|
||||
roots.add(pathlib.Path(line.strip()).resolve())
|
||||
version = subprocess.check_output([config["executable"], "--version"], text=True)
|
||||
major = re.search(r"version (\d+)", version).group(1)
|
||||
builtin = pathlib.Path(f"/usr/lib/llvm-{major}/lib/clang/{major}/include")
|
||||
roots.add(builtin)
|
||||
roots.update(pathlib.Path(root) for root in config["header_roots"])
|
||||
for root in roots:
|
||||
if not root.is_dir() or root == pathlib.Path("/"):
|
||||
raise ValueError(f"missing or invalid header directory: {root}")
|
||||
return sorted(str(root) for root in roots if not any(parent in roots for parent in root.parents))
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(json.dumps(header_roots(json.loads(sys.argv[1]))))
|
||||
@@ -9,7 +9,7 @@ RUN apt-get update \
|
||||
bash \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
clangd \
|
||||
clangd-19 \
|
||||
cmake \
|
||||
curl \
|
||||
g++-12 \
|
||||
@@ -31,6 +31,7 @@ RUN apt-get update \
|
||||
pkg-config \
|
||||
python3 \
|
||||
python3-venv \
|
||||
&& ln -s /usr/bin/clangd-19 /usr/local/bin/clangd \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV CC=/usr/bin/gcc-12
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
local M = {}
|
||||
|
||||
local function within(path, root)
|
||||
return path == root or path:sub(1, #root + 1) == root .. "/"
|
||||
end
|
||||
|
||||
function M.setup(root)
|
||||
if vim.fn.has("nvim-0.11") ~= 1 then
|
||||
vim.notify("Modulus container clangd requires Neovim 0.11+", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
root = vim.fs.normalize(root)
|
||||
-- Finish LazyVim's server setup before changing only the command and root routing.
|
||||
local has_lazy, lazy = pcall(require, "lazy")
|
||||
if has_lazy then
|
||||
lazy.load({ plugins = { "nvim-lspconfig" } })
|
||||
end
|
||||
local state = rawget(_G, "modulus_clangd")
|
||||
if not state then
|
||||
state = { roots = {} }
|
||||
_G.modulus_clangd = state
|
||||
local original = vim.lsp.config.clangd or { cmd = { "clangd" }, filetypes = { "c", "cpp", "objc", "objcpp", "cuda" } }
|
||||
if not vim.lsp.config.clangd then
|
||||
vim.lsp.config("clangd", original)
|
||||
end
|
||||
local original_cmd, original_root = original.cmd, original.root_dir
|
||||
local cache = vim.fs.normalize((vim.env.XDG_CACHE_HOME or (vim.env.HOME .. "/.cache")) .. "/modulus/clangd")
|
||||
local function project(path)
|
||||
local best
|
||||
for candidate in pairs(state.roots) do
|
||||
if within(path, candidate) and (not best or #candidate > #best) then
|
||||
best = candidate
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
state.project = project
|
||||
vim.lsp.config("clangd", {
|
||||
cmd = function(dispatchers, config)
|
||||
local selected = config.root_dir and state.roots[config.root_dir]
|
||||
if selected then
|
||||
config._modulus_container = true
|
||||
return vim.lsp.rpc.start(selected, dispatchers, { cwd = config.root_dir })
|
||||
end
|
||||
if type(original_cmd) == "function" then
|
||||
return original_cmd(dispatchers, config)
|
||||
end
|
||||
return vim.lsp.rpc.start(original_cmd or { "clangd" }, dispatchers, { cwd = config.root_dir })
|
||||
end,
|
||||
root_dir = function(buf, on_dir)
|
||||
local name = vim.api.nvim_buf_get_name(buf)
|
||||
if within(name, cache) then
|
||||
local clients = vim.tbl_filter(function(client) return client.config._modulus_container end, vim.lsp.get_clients({ name = "clangd" }))
|
||||
if #clients == 1 then
|
||||
vim.lsp.buf_attach_client(buf, clients[1].id)
|
||||
end
|
||||
return
|
||||
end
|
||||
local selected = project(name)
|
||||
if selected then
|
||||
on_dir(selected)
|
||||
elseif type(original_root) == "function" then
|
||||
original_root(buf, on_dir)
|
||||
elseif type(original_root) == "string" then
|
||||
on_dir(original_root)
|
||||
else
|
||||
on_dir(vim.fs.root(name, { ".clangd", "compile_commands.json", ".git" }) or vim.fs.dirname(name))
|
||||
end
|
||||
end,
|
||||
})
|
||||
vim.api.nvim_create_autocmd("BufReadPost", {
|
||||
group = vim.api.nvim_create_augroup("ModulusClangdHeaders", { clear = true }),
|
||||
callback = function(event)
|
||||
if within(vim.api.nvim_buf_get_name(event.buf), cache) then
|
||||
vim.bo[event.buf].readonly = true
|
||||
vim.bo[event.buf].modifiable = false
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
local command
|
||||
if vim.uv.fs_stat(root .. "/modulus/cli.py") then
|
||||
command = { "python3", root .. "/tools/clangd.py" }
|
||||
else
|
||||
command = { "python3", root .. "/modulus.py", "clangd" }
|
||||
end
|
||||
state.roots[root] = command
|
||||
for _, client in ipairs(vim.lsp.get_clients({ name = "clangd" })) do
|
||||
if not client.config._modulus_container and client.config.root_dir and within(client.config.root_dir, root) then
|
||||
client:stop()
|
||||
end
|
||||
end
|
||||
vim.lsp.enable("clangd")
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user