local M = {} local function within(path, root) return path == root or path:sub(1, #root + 1) == root .. "/" end local function setup_formatting(state) if state.formatting then return end local ok, conform = pcall(require, "conform") if not ok then vim.notify("Modulus formatting requires conform.nvim", vim.log.levels.WARN) return end state.formatting = true local function project(buf) local ft = vim.bo[buf].filetype if ft ~= "c" and ft ~= "cpp" and ft ~= "python" then return nil end return state.project(vim.api.nvim_buf_get_name(buf)) end local fallback = conform.formatters_by_ft["_"] for _, ft in ipairs({ "c", "cpp", "python", "*" }) do local original = conform.formatters_by_ft[ft] if ft ~= "*" and (not original or (type(original) == "table" and vim.tbl_isempty(original))) then original = fallback end conform.formatters_by_ft[ft] = function(buf) if project(buf) then if ft == "*" then return {} end return { "modulus", lsp_format = "never", timeout_ms = 10000 } end if type(original) == "function" then return original(buf) or {} end return original or {} end end conform.formatters.modulus = { command = "python3", env = { MODULUS_DIRECT = "0" }, args = function(_, context) local root = assert(project(context.buf)) local args = vim.uv.fs_stat(root .. "/modulus/cli.py") and { root .. "/tools/format.py" } or { root .. "/modulus.py", "format" } vim.list_extend(args, { "--stdin-filename", context.filename }) return args end, cwd = function(_, context) return project(context.buf) end, stdin = true, } -- LazyVim already owns save/manual formatting and its global/buffer toggles. local has_save, save_hooks = pcall(vim.api.nvim_get_autocmds, { event = "BufWritePre", group = "Conform" }) if not rawget(_G, "LazyVim") and (not has_save or #save_hooks == 0) then vim.api.nvim_create_autocmd("BufWritePre", { group = vim.api.nvim_create_augroup("ModulusFormat", { clear = true }), callback = function(event) if project(event.buf) and vim.g.autoformat ~= false and vim.b[event.buf].autoformat ~= false and not vim.g.disable_autoformat and not vim.b[event.buf].disable_autoformat then conform.format({ bufnr = event.buf, lsp_format = "never", timeout_ms = 10000 }) end end, }) end 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", "conform.nvim" } }) 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 setup_formatting(state) 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