diff --git a/modules/extra/deprecations.nix b/modules/extra/deprecations.nix index 0a5733ab..fe6f08a5 100644 --- a/modules/extra/deprecations.nix +++ b/modules/extra/deprecations.nix @@ -20,6 +20,10 @@ # 2025-02-07 scrollOffset = "scrolloff"; }; + + lspOptRemovalMsg = '' + `vim.languages..lsp.opts` are now moved to `vim.lsp.servers..init_options` + ''; in { imports = concatLists [ [ @@ -120,6 +124,9 @@ in { in 'vim.clipboard.registers'. Please see the documentation for the new module for more details, or open an issue if you are confused. '') + + # 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 2db178e9..299731f6 100644 --- a/modules/plugins/languages/clang.nix +++ b/modules/plugins/languages/clang.nix @@ -5,47 +5,143 @@ ... }: 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"; + defaultServers = ["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}"} - } + cmd = [(getExe pkgs.ccls)]; + filetypes = ["c" "cpp" "objc" "objcpp" "cuda"]; + offset_encoding = "utf-32"; + root_markers = ["compile_commands.json" ".ccls" ".git"]; + workspace_required = true; + on_attach = mkLuaInline '' + function(client, bufnr) + default_on_attach(client, bufnr) + + local function switch_source_header(bufnr) + local method_name = "textDocument/switchSourceHeader" + 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, + "LspCclsSwitchSourceHeader", + function(arg) + switch_source_header(client, 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}"} - } + cmd = ["${pkgs.clang-tools}/bin/clangd"]; + filetypes = ["c" "cpp" "objc" "objcpp" "cuda" "proto"]; + root_markers = [ + ".clangd" + ".clang-tidy" + ".clang-format" + "compile_commands.json" + "compile_flags.txt" + "configure.ac" + ".git" + ]; + 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", })[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 bufnr = vim.api.nvim_get_current_buf() + local clangd_client = vim.lsp.get_clients({ bufnr = bufnr, name = "clangd" })[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 ''; }; }; @@ -100,23 +196,10 @@ in { lsp = { enable = mkEnableOption "clang LSP support" // {default = config.vim.lsp.enable;}; - server = mkOption { + servers = mkOption { description = "The clang LSP server to use"; - type = 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; + type = listOf (enum (attrNames servers)); + default = defaultServers; }; }; @@ -150,9 +233,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}; + }) + cfg.lsp.servers; }) (mkIf cfg.dap.enable {