From e621af1410a2c801d77d118b58d528b502676d56 Mon Sep 17 00:00:00 2001 From: Ching Pei Yang Date: Sun, 6 Apr 2025 00:39:14 +0200 Subject: [PATCH] language/clang: migrate to vim.lsp.servers --- modules/extra/deprecations.nix | 7 + modules/plugins/languages/clang.nix | 207 +++++++++++++++++++++------- 2 files changed, 166 insertions(+), 48 deletions(-) diff --git a/modules/extra/deprecations.nix b/modules/extra/deprecations.nix index 86497130..12904a3b 100644 --- a/modules/extra/deprecations.nix +++ b/modules/extra/deprecations.nix @@ -20,6 +20,10 @@ # 2025-02-07 scrollOff = "scrolloff"; }; + + lspOptRemovalMsg = '' + `vim.languages..lsp.opts` are now moved to `vim.lsp.servers..init_options` + ''; in { imports = concatLists [ [ @@ -111,6 +115,9 @@ in { under the diagnostics module. Please consider using one of 'vim.diagnostics.config' or 'vim.luaConfigRC' to configure LSP lines for Neovim through its own diagnostics API. '') + + # 2025-04-05 + (mkRemovedOptionModule ["vim" "languages" "clang" "lsp" "opts"] lspOptRemovalMsg) ] # Migrated via batchRenameOptions. Further batch renames must be below this line. diff --git a/modules/plugins/languages/clang.nix b/modules/plugins/languages/clang.nix index bb30cc95..eef80165 100644 --- a/modules/plugins/languages/clang.nix +++ b/modules/plugins/languages/clang.nix @@ -5,47 +5,168 @@ ... }: let inherit (builtins) attrNames; - inherit (lib.lists) isList; - inherit (lib.strings) optionalString; inherit (lib.options) mkEnableOption mkOption; - inherit (lib.types) bool enum package either listOf str nullOr; + inherit (lib.types) bool enum package listOf; + inherit (lib.meta) getExe; inherit (lib.modules) mkIf mkMerge; - inherit (lib.nvim.lua) expToLua; + inherit (lib.generators) mkLuaInline; inherit (lib.nvim.types) mkGrammarOption; + inherit (lib.nvim.attrsets) mapListToAttrs; inherit (lib.nvim.dag) entryAfter; - packageToCmd = package: defaultCmd: - if isList cfg.lsp.package - then expToLua cfg.lsp.package - else ''{ "${cfg.lsp.package}/bin/${defaultCmd}" }''; - cfg = config.vim.languages.clang; - defaultServer = "clangd"; + defaultServer = ["clangd"]; servers = { - ccls = { - package = pkgs.ccls; - lspConfig = '' - lspconfig.ccls.setup{ - capabilities = capabilities; - on_attach=default_on_attach; - cmd = ${packageToCmd cfg.lsp.package "ccls"}; - ${optionalString (cfg.lsp.opts != null) "init_options = ${cfg.lsp.opts}"} - } + ccls.options = { + cmd = [(getExe pkgs.ccls)]; + filetypes = ["c" "cpp" "objc" "objcpp" "cuda"]; + offset_encoding = "utf-32"; + root_dir = mkLuaInline '' + function(bufnr, cb) + local dir = vim.fs.root(bufnr, {"compile_commands.json", ".ccls"}) + or vim.fs.root(bufnr, {".git"}) + if dir then cb(dir) end + end + ''; + on_attach = mkLuaInline '' + function(client, bufnr) + default_on_attach(client, bufnr) + + local function switch_source_header(bufnr) + local method_name = "textDocument/switchSourceHeader" + local client = vim.lsp.get_clients({ + bufnr = bufnr, + name = "ccls", + method = method_name, + })[1] + if not client then + return vim.notify(('method %s is not supported by any servers active on the current buffer'):format(method_name)) + end + local params = vim.lsp.util.make_text_document_params(bufnr) + client:request(method_name, params, function(err, result) + if err then + error(tostring(err)) + end + if not result then + vim.notify('corresponding file cannot be determined') + return + end + vim.cmd.edit(vim.uri_to_fname(result)) + end, bufnr) + end + + vim.api.nvim_buf_create_user_command( + bufnr, + "CclsSwitchSourceHeader", + function(arg) + switch_source_header(0) + end, + {desc = "Switch between source/header"} + ) + end ''; }; - clangd = { - package = pkgs.clang-tools; - lspConfig = '' - local clangd_cap = capabilities - -- use same offsetEncoding as null-ls - clangd_cap.offsetEncoding = {"utf-16"} - lspconfig.clangd.setup{ - capabilities = clangd_cap; - on_attach=default_on_attach; - cmd = ${packageToCmd cfg.lsp.package "clangd"}; - ${optionalString (cfg.lsp.opts != null) "init_options = ${cfg.lsp.opts}"} - } + + clangd.options = { + cmd = ["${pkgs.clang-tools}/bin/clangd"]; + filetypes = ["c" "cpp" "objc" "objcpp" "cuda" "proto"]; + root_dir = mkLuaInline '' + function(bufnr, cb) + local dir = vim.fs.root(bufnr, { + '.clangd', + '.clang-tidy', + '.clang-format', + 'compile_commands.json', + 'compile_flags.txt', + 'configure.ac' -- AutoTools + }) or vim.fs.root(bufnr, {".git"}) + + if dir then cb(dir) end + end + ''; + capabilities = { + textDocument = { + completion = { + editsNearCursor = true; + }; + }; + offsetEncoding = ["utf-8" "utf-16"]; + }; + on_attach = mkLuaInline '' + function(client, bufnr) + default_on_attach(client, bufnr) + + local function switch_source_header(bufnr) + local method_name = "textDocument/switchSourceHeader" + local client = vim.lsp.get_clients({ + bufnr = bufnr, + name = "clangd", + method = method_name, + })[1] + if not client then + return vim.notify(('method %s is not supported by any servers active on the current buffer'):format(method_name)) + end + local params = vim.lsp.util.make_text_document_params(bufnr) + client:request(method_name, params, function(err, result) + if err then + error(tostring(err)) + end + if not result then + vim.notify('corresponding file cannot be determined') + return + end + vim.cmd.edit(vim.uri_to_fname(result)) + end, bufnr) + end + + local function symbol_info() + local clangd_client = vim.lsp.get_clients({ + bufnr = 0, + name = "clangd", + method = "textDocument/symbolInfo", + })[1] + if not clangd_client or not clangd_client.supports_method 'textDocument/symbolInfo' then + return vim.notify('Clangd client not found', vim.log.levels.ERROR) + end + local win = vim.api.nvim_get_current_win() + local params = vim.lsp.util.make_position_params(win, clangd_client.offset_encoding) + clangd_client:request('textDocument/symbolInfo', params, function(err, res) + if err or #res == 0 then + -- Clangd always returns an error, there is not reason to parse it + return + end + local container = string.format('container: %s', res[1].containerName) ---@type string + local name = string.format('name: %s', res[1].name) ---@type string + vim.lsp.util.open_floating_preview({ name, container }, "", { + height = 2, + width = math.max(string.len(name), string.len(container)), + focusable = false, + focus = false, + border = 'single', + title = 'Symbol Info', + }) + end, bufnr) + end + + vim.api.nvim_buf_create_user_command( + bufnr, + "ClangdSwitchSourceHeader", + function(arg) + switch_source_header(0) + end, + {desc = "Switch between source/header"} + ) + + vim.api.nvim_buf_create_user_command( + bufnr, + "ClangdShowSymbolInfo", + function(arg) + symbol_info() + end, + {desc = "Show symbol info"} + ) + end ''; }; }; @@ -102,22 +223,9 @@ in { server = mkOption { description = "The clang LSP server to use"; - type = enum (attrNames servers); + type = listOf (enum (attrNames servers)); default = defaultServer; }; - - package = mkOption { - description = "clang LSP server package, or the command to run as a list of strings"; - example = ''[lib.getExe pkgs.jdt-language-server " - data " " ~/.cache/jdtls/workspace "]''; - type = either package (listOf str); - default = servers.${cfg.lsp.server}.package; - }; - - opts = mkOption { - description = "Options to pass to clang LSP server"; - type = nullOr str; - default = null; - }; }; dap = { @@ -150,9 +258,12 @@ in { }) (mkIf cfg.lsp.enable { - vim.lsp.lspconfig.enable = true; - - vim.lsp.lspconfig.sources.clang-lsp = servers.${cfg.lsp.server}.lspConfig; + vim.lsp.servers = + mapListToAttrs (name: { + inherit name; + value = servers.${name}.options; + }) + cfg.lsp.server; }) (mkIf cfg.dap.enable {