From cd8c4977a0a8e80750ed6a10992f988f811c6417 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Sat, 25 Oct 2025 16:57:16 +0200 Subject: [PATCH] feat(lsp): lsp keymaps can now be configured with `lsp-config.opts.servers['*'].keys` like for lsp servers --- lua/lazyvim/plugins/extras/editor/fzf.lua | 21 +-- .../plugins/extras/editor/inc-rename.lua | 31 +++-- .../plugins/extras/editor/snacks_picker.lua | 31 +++-- .../plugins/extras/editor/telescope.lua | 23 ++-- lua/lazyvim/plugins/lsp/init.lua | 62 +++++++-- lua/lazyvim/plugins/lsp/keymaps.lua | 129 +++++++----------- lua/lazyvim/util/init.lua | 2 +- 7 files changed, 160 insertions(+), 139 deletions(-) diff --git a/lua/lazyvim/plugins/extras/editor/fzf.lua b/lua/lazyvim/plugins/extras/editor/fzf.lua index 46e99ace..8e17c671 100644 --- a/lua/lazyvim/plugins/extras/editor/fzf.lua +++ b/lua/lazyvim/plugins/extras/editor/fzf.lua @@ -289,15 +289,16 @@ return { { "neovim/nvim-lspconfig", - opts = function() - local Keys = require("lazyvim.plugins.lsp.keymaps").get() - -- stylua: ignore - vim.list_extend(Keys, { - { "gd", "FzfLua lsp_definitions jump1=true ignore_current_line=true", desc = "Goto Definition", has = "definition" }, - { "gr", "FzfLua lsp_references jump1=true ignore_current_line=true", desc = "References", nowait = true }, - { "gI", "FzfLua lsp_implementations jump1=true ignore_current_line=true", desc = "Goto Implementation" }, - { "gy", "FzfLua lsp_typedefs jump1=true ignore_current_line=true", desc = "Goto T[y]pe Definition" }, - }) - end, + opts = { + servers = { + -- stylua: ignore + ["*"] = { + { "gd", "FzfLua lsp_definitions jump1=true ignore_current_line=true", desc = "Goto Definition", has = "definition" }, + { "gr", "FzfLua lsp_references jump1=true ignore_current_line=true", desc = "References", nowait = true }, + { "gI", "FzfLua lsp_implementations jump1=true ignore_current_line=true", desc = "Goto Implementation" }, + { "gy", "FzfLua lsp_typedefs jump1=true ignore_current_line=true", desc = "Goto T[y]pe Definition" }, + }, + }, + }, }, } diff --git a/lua/lazyvim/plugins/extras/editor/inc-rename.lua b/lua/lazyvim/plugins/extras/editor/inc-rename.lua index a7c65243..b01f65f3 100644 --- a/lua/lazyvim/plugins/extras/editor/inc-rename.lua +++ b/lua/lazyvim/plugins/extras/editor/inc-rename.lua @@ -12,19 +12,24 @@ return { -- LSP Keymaps { "neovim/nvim-lspconfig", - opts = function() - local keys = require("lazyvim.plugins.lsp.keymaps").get() - keys[#keys + 1] = { - "cr", - function() - local inc_rename = require("inc_rename") - return ":" .. inc_rename.config.cmd_name .. " " .. vim.fn.expand("") - end, - expr = true, - desc = "Rename (inc-rename.nvim)", - has = "rename", - } - end, + opts = { + servers = { + ["*"] = { + keys = { + { + "cr", + function() + local inc_rename = require("inc_rename") + return ":" .. inc_rename.config.cmd_name .. " " .. vim.fn.expand("") + end, + expr = true, + desc = "Rename (inc-rename.nvim)", + has = "rename", + }, + }, + }, + }, + }, }, --- Noice integration diff --git a/lua/lazyvim/plugins/extras/editor/snacks_picker.lua b/lua/lazyvim/plugins/extras/editor/snacks_picker.lua index 766a9e59..48eea01a 100644 --- a/lua/lazyvim/plugins/extras/editor/snacks_picker.lua +++ b/lua/lazyvim/plugins/extras/editor/snacks_picker.lua @@ -135,20 +135,23 @@ return { }, { "neovim/nvim-lspconfig", - opts = function() - local Keys = require("lazyvim.plugins.lsp.keymaps").get() - -- stylua: ignore - vim.list_extend(Keys, { - { "gd", function() Snacks.picker.lsp_definitions() end, desc = "Goto Definition", has = "definition" }, - { "gr", function() Snacks.picker.lsp_references() end, nowait = true, desc = "References" }, - { "gI", function() Snacks.picker.lsp_implementations() end, desc = "Goto Implementation" }, - { "gy", function() Snacks.picker.lsp_type_definitions() end, desc = "Goto T[y]pe Definition" }, - { "ss", function() Snacks.picker.lsp_symbols({ filter = LazyVim.config.kind_filter }) end, desc = "LSP Symbols", has = "documentSymbol" }, - { "sS", function() Snacks.picker.lsp_workspace_symbols({ filter = LazyVim.config.kind_filter }) end, desc = "LSP Workspace Symbols", has = "workspace/symbols" }, - { "gai", function() Snacks.picker.lsp_incoming_calls() end, desc = "C[a]lls Incoming", has = "callHierarchy/incomingCalls" }, - { "gao", function() Snacks.picker.lsp_outgoing_calls() end, desc = "C[a]lls Outgoing", has = "callHierarchy/outgoingCalls" }, - }) - end, + opts = { + servers = { + ["*"] = { + -- stylua: ignore + keys = { + { "gd", function() Snacks.picker.lsp_definitions() end, desc = "Goto Definition", has = "definition" }, + { "gr", function() Snacks.picker.lsp_references() end, nowait = true, desc = "References" }, + { "gI", function() Snacks.picker.lsp_implementations() end, desc = "Goto Implementation" }, + { "gy", function() Snacks.picker.lsp_type_definitions() end, desc = "Goto T[y]pe Definition" }, + { "ss", function() Snacks.picker.lsp_symbols({ filter = LazyVim.config.kind_filter }) end, desc = "LSP Symbols", has = "documentSymbol" }, + { "sS", function() Snacks.picker.lsp_workspace_symbols({ filter = LazyVim.config.kind_filter }) end, desc = "LSP Workspace Symbols", has = "workspace/symbols" }, + { "gai", function() Snacks.picker.lsp_incoming_calls() end, desc = "C[a]lls Incoming", has = "callHierarchy/incomingCalls" }, + { "gao", function() Snacks.picker.lsp_outgoing_calls() end, desc = "C[a]lls Outgoing", has = "callHierarchy/outgoingCalls" }, + }, + }, + }, + }, }, { "folke/todo-comments.nvim", diff --git a/lua/lazyvim/plugins/extras/editor/telescope.lua b/lua/lazyvim/plugins/extras/editor/telescope.lua index 0f5ee474..f8f3ed6a 100644 --- a/lua/lazyvim/plugins/extras/editor/telescope.lua +++ b/lua/lazyvim/plugins/extras/editor/telescope.lua @@ -284,15 +284,18 @@ return { { "neovim/nvim-lspconfig", - opts = function() - local Keys = require("lazyvim.plugins.lsp.keymaps").get() - -- stylua: ignore - vim.list_extend(Keys, { - { "gd", function() require("telescope.builtin").lsp_definitions({ reuse_win = true }) end, desc = "Goto Definition", has = "definition" }, - { "gr", "Telescope lsp_references", desc = "References", nowait = true }, - { "gI", function() require("telescope.builtin").lsp_implementations({ reuse_win = true }) end, desc = "Goto Implementation" }, - { "gy", function() require("telescope.builtin").lsp_type_definitions({ reuse_win = true }) end, desc = "Goto T[y]pe Definition" }, - }) - end, + opts = { + servers = { + ["*"] = { + -- stylua: ignore + keys = { + { "gd", function() require("telescope.builtin").lsp_definitions({ reuse_win = true }) end, desc = "Goto Definition", has = "definition" }, + { "gr", "Telescope lsp_references", desc = "References", nowait = true }, + { "gI", function() require("telescope.builtin").lsp_implementations({ reuse_win = true }) end, desc = "Goto Implementation" }, + { "gy", function() require("telescope.builtin").lsp_type_definitions({ reuse_win = true }) end, desc = "Goto T[y]pe Definition" }, + }, + }, + }, + }, }, } diff --git a/lua/lazyvim/plugins/lsp/init.lua b/lua/lazyvim/plugins/lsp/init.lua index 9128ba13..eb882c5c 100644 --- a/lua/lazyvim/plugins/lsp/init.lua +++ b/lua/lazyvim/plugins/lsp/init.lua @@ -7,6 +7,7 @@ return { "mason.nvim", { "mason-org/mason-lspconfig.nvim", config = function() end }, }, + opts_extend = { "servers.*.keys" }, opts = function() ---@class PluginLspOpts local ret = { @@ -51,15 +52,6 @@ return { folds = { enabled = true, }, - -- add any global capabilities here - capabilities = { - workspace = { - fileOperations = { - didRename = true, - willRename = true, - }, - }, - }, -- options for vim.lsp.buf.format -- `bufnr` and `filter` is handled by the LazyVim formatter, -- but can be also overridden when specified @@ -68,9 +60,47 @@ return { timeout_ms = nil, }, -- LSP Server Settings - ---@alias lazyvim.lsp.Config vim.lsp.Config|{mason?:boolean, enabled?:boolean} + -- Sets the default configuration for an LSP client (or all clients if the special name "*" is used). + ---@alias lazyvim.lsp.Config vim.lsp.Config|{mason?:boolean, enabled?:boolean, keys?:LazyKeysLspSpec[]} ---@type table servers = { + -- configuration for all lsp servers + ["*"] = { + capabilities = { + workspace = { + fileOperations = { + didRename = true, + willRename = true, + }, + }, + }, + -- stylua: ignore + keys = { + { "cl", function() Snacks.picker.lsp_config() end, desc = "Lsp Info" }, + { "gd", vim.lsp.buf.definition, desc = "Goto Definition", has = "definition" }, + { "gr", vim.lsp.buf.references, desc = "References", nowait = true }, + { "gI", vim.lsp.buf.implementation, desc = "Goto Implementation" }, + { "gy", vim.lsp.buf.type_definition, desc = "Goto T[y]pe Definition" }, + { "gD", vim.lsp.buf.declaration, desc = "Goto Declaration" }, + { "K", function() return vim.lsp.buf.hover() end, desc = "Hover" }, + { "gK", function() return vim.lsp.buf.signature_help() end, desc = "Signature Help", has = "signatureHelp" }, + { "", function() return vim.lsp.buf.signature_help() end, mode = "i", desc = "Signature Help", has = "signatureHelp" }, + { "ca", vim.lsp.buf.code_action, desc = "Code Action", mode = { "n", "x" }, has = "codeAction" }, + { "cc", vim.lsp.codelens.run, desc = "Run Codelens", mode = { "n", "x" }, has = "codeLens" }, + { "cC", vim.lsp.codelens.refresh, desc = "Refresh & Display Codelens", mode = { "n" }, has = "codeLens" }, + { "cR", function() Snacks.rename.rename_file() end, desc = "Rename File", mode ={"n"}, has = { "workspace/didRenameFiles", "workspace/willRenameFiles" } }, + { "cr", vim.lsp.buf.rename, desc = "Rename", has = "rename" }, + { "cA", LazyVim.lsp.action.source, desc = "Source Action", has = "codeAction" }, + { "]]", function() Snacks.words.jump(vim.v.count1) end, has = "documentHighlight", + desc = "Next Reference", cond = function() return Snacks.words.is_enabled() end }, + { "[[", function() Snacks.words.jump(-vim.v.count1) end, has = "documentHighlight", + desc = "Prev Reference", cond = function() return Snacks.words.is_enabled() end }, + { "", function() Snacks.words.jump(vim.v.count1, true) end, has = "documentHighlight", + desc = "Next Reference", cond = function() return Snacks.words.is_enabled() end }, + { "", function() Snacks.words.jump(-vim.v.count1, true) end, has = "documentHighlight", + desc = "Prev Reference", cond = function() return Snacks.words.is_enabled() end }, + }, + }, stylua = { enabled = false }, lua_ls = { -- mason = false, -- set to false if you don't want this server to be installed with mason @@ -179,7 +209,14 @@ return { vim.diagnostic.config(vim.deepcopy(opts.diagnostics)) if opts.capabilities then - vim.lsp.config("*", { capabilities = opts.capabilities }) + LazyVim.deprecate("lsp-config.opts.capabilities", "Use lsp-config.opts.servers['*'].capabilities instead") + opts.servers["*"] = vim.tbl_deep_extend("force", opts.servers["*"] or {}, { + capabilities = opts.capabilities, + }) + end + + if opts.servers["*"] then + vim.lsp.config("*", opts.servers["*"]) end -- get all the servers that are available through mason-lspconfig @@ -191,6 +228,9 @@ return { ---@return boolean? exclude automatic setup local function configure(server) + if server == "*" then + return false + end local sopts = opts.servers[server] sopts = sopts == true and {} or (not sopts) and { enabled = false } or sopts --[[@as lazyvim.lsp.Config]] diff --git a/lua/lazyvim/plugins/lsp/keymaps.lua b/lua/lazyvim/plugins/lsp/keymaps.lua index 63be2902..8fe36647 100644 --- a/lua/lazyvim/plugins/lsp/keymaps.lua +++ b/lua/lazyvim/plugins/lsp/keymaps.lua @@ -1,97 +1,66 @@ local M = {} ---@type LazyKeysLspSpec[]|nil -M._keys = nil +M._keys = {} ---@alias LazyKeysLspSpec LazyKeysSpec|{has?:string|string[], cond?:fun():boolean} ---@alias LazyKeysLsp LazyKeys|{has?:string|string[], cond?:fun():boolean} +---@deprecated ---@return LazyKeysLspSpec[] function M.get() - if M._keys then - return M._keys - end - -- stylua: ignore - M._keys = { - { "cl", function() Snacks.picker.lsp_config() end, desc = "Lsp Info" }, - { "gd", vim.lsp.buf.definition, desc = "Goto Definition", has = "definition" }, - { "gr", vim.lsp.buf.references, desc = "References", nowait = true }, - { "gI", vim.lsp.buf.implementation, desc = "Goto Implementation" }, - { "gy", vim.lsp.buf.type_definition, desc = "Goto T[y]pe Definition" }, - { "gD", vim.lsp.buf.declaration, desc = "Goto Declaration" }, - { "K", function() return vim.lsp.buf.hover() end, desc = "Hover" }, - { "gK", function() return vim.lsp.buf.signature_help() end, desc = "Signature Help", has = "signatureHelp" }, - { "", function() return vim.lsp.buf.signature_help() end, mode = "i", desc = "Signature Help", has = "signatureHelp" }, - { "ca", vim.lsp.buf.code_action, desc = "Code Action", mode = { "n", "x" }, has = "codeAction" }, - { "cc", vim.lsp.codelens.run, desc = "Run Codelens", mode = { "n", "x" }, has = "codeLens" }, - { "cC", vim.lsp.codelens.refresh, desc = "Refresh & Display Codelens", mode = { "n" }, has = "codeLens" }, - { "cR", function() Snacks.rename.rename_file() end, desc = "Rename File", mode ={"n"}, has = { "workspace/didRenameFiles", "workspace/willRenameFiles" } }, - { "cr", vim.lsp.buf.rename, desc = "Rename", has = "rename" }, - { "cA", LazyVim.lsp.action.source, desc = "Source Action", has = "codeAction" }, - { "]]", function() Snacks.words.jump(vim.v.count1) end, has = "documentHighlight", - desc = "Next Reference", cond = function() return Snacks.words.is_enabled() end }, - { "[[", function() Snacks.words.jump(-vim.v.count1) end, has = "documentHighlight", - desc = "Prev Reference", cond = function() return Snacks.words.is_enabled() end }, - { "", function() Snacks.words.jump(vim.v.count1, true) end, has = "documentHighlight", - desc = "Next Reference", cond = function() return Snacks.words.is_enabled() end }, - { "", function() Snacks.words.jump(-vim.v.count1, true) end, has = "documentHighlight", - desc = "Prev Reference", cond = function() return Snacks.words.is_enabled() end }, - } - + LazyVim.warn({ + 'Adding LSP keymaps via `require("lazyvim.plugins.lsp.keymaps").get()` is deprecated.', + "Please set keymaps via the `keys` field in the LSP server config.", + [[ +```lua +{ + "neovim/nvim-lspconfig", + opts = { + servers = { + ['*'] = { + keys = { + { "gd", "lua vim.lsp.buf.definition()", has = "definition"}, + }, + }, + }, + }, +} +```]], + }, { stacktrace = true }) + vim.schedule(function() + if #M._keys > 0 then + M.set({}, M._keys) + M._keys = {} + end + end) return M._keys end ----@param method string|string[] -function M.has(buffer, method) - if type(method) == "table" then - for _, m in ipairs(method) do - if M.has(buffer, m) then - return true +---@param filter vim.lsp.get_clients.Filter +---@param spec LazyKeysLspSpec[] +function M.set(filter, spec) + local Keys = require("lazy.core.handler.keys") + for _, keys in pairs(Keys.resolve(spec)) do + ---@cast keys LazyKeysLsp + if keys.cond == nil or keys.cond() then + local filters = {} ---@type vim.lsp.get_clients.Filter[] + if keys.has then + local methods = type(keys.has) == "string" and { keys.has } or keys.has --[[@as string[] ]] + for _, method in ipairs(methods) do + method = method:find("/") and method or ("textDocument/" .. method) + filters[#filters + 1] = vim.tbl_extend("force", vim.deepcopy(filter), { method = method }) + end + else + filters[#filters + 1] = filter end - end - return false - end - method = method:find("/") and method or "textDocument/" .. method - local clients = vim.lsp.get_clients({ bufnr = buffer }) - for _, client in ipairs(clients) do - if client:supports_method(method) then - return true - end - end - return false -end ----@return LazyKeysLsp[] -function M.resolve(buffer) - local Keys = require("lazy.core.handler.keys") - if not Keys.resolve then - return {} - end - local spec = vim.tbl_extend("force", {}, M.get()) - local opts = LazyVim.opts("nvim-lspconfig") - local clients = vim.lsp.get_clients({ bufnr = buffer }) - for _, client in ipairs(clients) do - local maps = opts.servers[client.name] and opts.servers[client.name].keys or {} - vim.list_extend(spec, maps) - end - return Keys.resolve(spec) -end - -function M.on_attach(_, buffer) - local Keys = require("lazy.core.handler.keys") - local keymaps = M.resolve(buffer) - - for _, keys in pairs(keymaps) do - local has = not keys.has or M.has(buffer, keys.has) - local cond = not (keys.cond == false or ((type(keys.cond) == "function") and not keys.cond())) - - if has and cond then - local opts = Keys.opts(keys) - opts.cond = nil - opts.has = nil - opts.silent = opts.silent ~= false - opts.buffer = buffer - vim.keymap.set(keys.mode or "n", keys.lhs, keys.rhs, opts) + for _, f in ipairs(filters) do + local opts = Keys.opts(keys) + ---@cast opts snacks.keymap.set.Opts + opts.lsp = f + Snacks.keymap.set(keys.mode or "n", keys.lhs, keys.rhs, opts) + end end end end diff --git a/lua/lazyvim/util/init.lua b/lua/lazyvim/util/init.lua index 812e6f22..be5f91f2 100644 --- a/lua/lazyvim/util/init.lua +++ b/lua/lazyvim/util/init.lua @@ -221,7 +221,7 @@ function M.safe_keymap_set(mode, lhs, rhs, opts) ---@diagnostic disable-next-line: no-unknown opts.remap = nil end - vim.keymap.set(modes, lhs, rhs, opts) + Snacks.keymap.set(modes, lhs, rhs, opts) end end