163 lines
5.8 KiB
Lua
163 lines
5.8 KiB
Lua
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
|