feat(lsp): lsp keymaps can now be configured with lsp-config.opts.servers['*'].keys like for lsp servers

This commit is contained in:
Folke Lemaitre
2025-10-25 16:57:16 +02:00
parent 3964433062
commit cd8c4977a0
7 changed files with 160 additions and 139 deletions

View File

@@ -289,15 +289,16 @@ return {
{ {
"neovim/nvim-lspconfig", "neovim/nvim-lspconfig",
opts = function() opts = {
local Keys = require("lazyvim.plugins.lsp.keymaps").get() servers = {
-- stylua: ignore -- stylua: ignore
vim.list_extend(Keys, { ["*"] = {
{ "gd", "<cmd>FzfLua lsp_definitions jump1=true ignore_current_line=true<cr>", desc = "Goto Definition", has = "definition" }, { "gd", "<cmd>FzfLua lsp_definitions jump1=true ignore_current_line=true<cr>", desc = "Goto Definition", has = "definition" },
{ "gr", "<cmd>FzfLua lsp_references jump1=true ignore_current_line=true<cr>", desc = "References", nowait = true }, { "gr", "<cmd>FzfLua lsp_references jump1=true ignore_current_line=true<cr>", desc = "References", nowait = true },
{ "gI", "<cmd>FzfLua lsp_implementations jump1=true ignore_current_line=true<cr>", desc = "Goto Implementation" }, { "gI", "<cmd>FzfLua lsp_implementations jump1=true ignore_current_line=true<cr>", desc = "Goto Implementation" },
{ "gy", "<cmd>FzfLua lsp_typedefs jump1=true ignore_current_line=true<cr>", desc = "Goto T[y]pe Definition" }, { "gy", "<cmd>FzfLua lsp_typedefs jump1=true ignore_current_line=true<cr>", desc = "Goto T[y]pe Definition" },
}) },
end, },
},
}, },
} }

View File

@@ -12,9 +12,11 @@ return {
-- LSP Keymaps -- LSP Keymaps
{ {
"neovim/nvim-lspconfig", "neovim/nvim-lspconfig",
opts = function() opts = {
local keys = require("lazyvim.plugins.lsp.keymaps").get() servers = {
keys[#keys + 1] = { ["*"] = {
keys = {
{
"<leader>cr", "<leader>cr",
function() function()
local inc_rename = require("inc_rename") local inc_rename = require("inc_rename")
@@ -23,8 +25,11 @@ return {
expr = true, expr = true,
desc = "Rename (inc-rename.nvim)", desc = "Rename (inc-rename.nvim)",
has = "rename", has = "rename",
} },
end, },
},
},
},
}, },
--- Noice integration --- Noice integration

View File

@@ -135,10 +135,11 @@ return {
}, },
{ {
"neovim/nvim-lspconfig", "neovim/nvim-lspconfig",
opts = function() opts = {
local Keys = require("lazyvim.plugins.lsp.keymaps").get() servers = {
["*"] = {
-- stylua: ignore -- stylua: ignore
vim.list_extend(Keys, { keys = {
{ "gd", function() Snacks.picker.lsp_definitions() end, desc = "Goto Definition", has = "definition" }, { "gd", function() Snacks.picker.lsp_definitions() end, desc = "Goto Definition", has = "definition" },
{ "gr", function() Snacks.picker.lsp_references() end, nowait = true, desc = "References" }, { "gr", function() Snacks.picker.lsp_references() end, nowait = true, desc = "References" },
{ "gI", function() Snacks.picker.lsp_implementations() end, desc = "Goto Implementation" }, { "gI", function() Snacks.picker.lsp_implementations() end, desc = "Goto Implementation" },
@@ -147,8 +148,10 @@ return {
{ "<leader>sS", function() Snacks.picker.lsp_workspace_symbols({ filter = LazyVim.config.kind_filter }) end, desc = "LSP Workspace Symbols", has = "workspace/symbols" }, { "<leader>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" }, { "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" }, { "gao", function() Snacks.picker.lsp_outgoing_calls() end, desc = "C[a]lls Outgoing", has = "callHierarchy/outgoingCalls" },
}) },
end, },
},
},
}, },
{ {
"folke/todo-comments.nvim", "folke/todo-comments.nvim",

View File

@@ -284,15 +284,18 @@ return {
{ {
"neovim/nvim-lspconfig", "neovim/nvim-lspconfig",
opts = function() opts = {
local Keys = require("lazyvim.plugins.lsp.keymaps").get() servers = {
["*"] = {
-- stylua: ignore -- stylua: ignore
vim.list_extend(Keys, { keys = {
{ "gd", function() require("telescope.builtin").lsp_definitions({ reuse_win = true }) end, desc = "Goto Definition", has = "definition" }, { "gd", function() require("telescope.builtin").lsp_definitions({ reuse_win = true }) end, desc = "Goto Definition", has = "definition" },
{ "gr", "<cmd>Telescope lsp_references<cr>", desc = "References", nowait = true }, { "gr", "<cmd>Telescope lsp_references<cr>", desc = "References", nowait = true },
{ "gI", function() require("telescope.builtin").lsp_implementations({ reuse_win = true }) end, desc = "Goto Implementation" }, { "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" }, { "gy", function() require("telescope.builtin").lsp_type_definitions({ reuse_win = true }) end, desc = "Goto T[y]pe Definition" },
}) },
end, },
},
},
}, },
} }

View File

@@ -7,6 +7,7 @@ return {
"mason.nvim", "mason.nvim",
{ "mason-org/mason-lspconfig.nvim", config = function() end }, { "mason-org/mason-lspconfig.nvim", config = function() end },
}, },
opts_extend = { "servers.*.keys" },
opts = function() opts = function()
---@class PluginLspOpts ---@class PluginLspOpts
local ret = { local ret = {
@@ -51,15 +52,6 @@ return {
folds = { folds = {
enabled = true, enabled = true,
}, },
-- add any global capabilities here
capabilities = {
workspace = {
fileOperations = {
didRename = true,
willRename = true,
},
},
},
-- options for vim.lsp.buf.format -- options for vim.lsp.buf.format
-- `bufnr` and `filter` is handled by the LazyVim formatter, -- `bufnr` and `filter` is handled by the LazyVim formatter,
-- but can be also overridden when specified -- but can be also overridden when specified
@@ -68,9 +60,47 @@ return {
timeout_ms = nil, timeout_ms = nil,
}, },
-- LSP Server Settings -- 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<string, lazyvim.lsp.Config|boolean> ---@type table<string, lazyvim.lsp.Config|boolean>
servers = { servers = {
-- configuration for all lsp servers
["*"] = {
capabilities = {
workspace = {
fileOperations = {
didRename = true,
willRename = true,
},
},
},
-- stylua: ignore
keys = {
{ "<leader>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" },
{ "<c-k>", function() return vim.lsp.buf.signature_help() end, mode = "i", desc = "Signature Help", has = "signatureHelp" },
{ "<leader>ca", vim.lsp.buf.code_action, desc = "Code Action", mode = { "n", "x" }, has = "codeAction" },
{ "<leader>cc", vim.lsp.codelens.run, desc = "Run Codelens", mode = { "n", "x" }, has = "codeLens" },
{ "<leader>cC", vim.lsp.codelens.refresh, desc = "Refresh & Display Codelens", mode = { "n" }, has = "codeLens" },
{ "<leader>cR", function() Snacks.rename.rename_file() end, desc = "Rename File", mode ={"n"}, has = { "workspace/didRenameFiles", "workspace/willRenameFiles" } },
{ "<leader>cr", vim.lsp.buf.rename, desc = "Rename", has = "rename" },
{ "<leader>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 },
{ "<a-n>", function() Snacks.words.jump(vim.v.count1, true) end, has = "documentHighlight",
desc = "Next Reference", cond = function() return Snacks.words.is_enabled() end },
{ "<a-p>", 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 }, stylua = { enabled = false },
lua_ls = { lua_ls = {
-- mason = false, -- set to false if you don't want this server to be installed with mason -- 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)) vim.diagnostic.config(vim.deepcopy(opts.diagnostics))
if opts.capabilities then 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 end
-- get all the servers that are available through mason-lspconfig -- get all the servers that are available through mason-lspconfig
@@ -191,6 +228,9 @@ return {
---@return boolean? exclude automatic setup ---@return boolean? exclude automatic setup
local function configure(server) local function configure(server)
if server == "*" then
return false
end
local sopts = opts.servers[server] local sopts = opts.servers[server]
sopts = sopts == true and {} or (not sopts) and { enabled = false } or sopts --[[@as lazyvim.lsp.Config]] sopts = sopts == true and {} or (not sopts) and { enabled = false } or sopts --[[@as lazyvim.lsp.Config]]

View File

@@ -1,97 +1,66 @@
local M = {} local M = {}
---@type LazyKeysLspSpec[]|nil ---@type LazyKeysLspSpec[]|nil
M._keys = nil M._keys = {}
---@alias LazyKeysLspSpec LazyKeysSpec|{has?:string|string[], cond?:fun():boolean} ---@alias LazyKeysLspSpec LazyKeysSpec|{has?:string|string[], cond?:fun():boolean}
---@alias LazyKeysLsp LazyKeys|{has?:string|string[], cond?:fun():boolean} ---@alias LazyKeysLsp LazyKeys|{has?:string|string[], cond?:fun():boolean}
---@deprecated
---@return LazyKeysLspSpec[] ---@return LazyKeysLspSpec[]
function M.get() function M.get()
if M._keys then LazyVim.warn({
return M._keys '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", "<cmd>lua vim.lsp.buf.definition()<CR>", has = "definition"},
},
},
},
},
}
```]],
}, { stacktrace = true })
vim.schedule(function()
if #M._keys > 0 then
M.set({}, M._keys)
M._keys = {}
end end
-- stylua: ignore end)
M._keys = {
{ "<leader>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" },
{ "<c-k>", function() return vim.lsp.buf.signature_help() end, mode = "i", desc = "Signature Help", has = "signatureHelp" },
{ "<leader>ca", vim.lsp.buf.code_action, desc = "Code Action", mode = { "n", "x" }, has = "codeAction" },
{ "<leader>cc", vim.lsp.codelens.run, desc = "Run Codelens", mode = { "n", "x" }, has = "codeLens" },
{ "<leader>cC", vim.lsp.codelens.refresh, desc = "Refresh & Display Codelens", mode = { "n" }, has = "codeLens" },
{ "<leader>cR", function() Snacks.rename.rename_file() end, desc = "Rename File", mode ={"n"}, has = { "workspace/didRenameFiles", "workspace/willRenameFiles" } },
{ "<leader>cr", vim.lsp.buf.rename, desc = "Rename", has = "rename" },
{ "<leader>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 },
{ "<a-n>", function() Snacks.words.jump(vim.v.count1, true) end, has = "documentHighlight",
desc = "Next Reference", cond = function() return Snacks.words.is_enabled() end },
{ "<a-p>", function() Snacks.words.jump(-vim.v.count1, true) end, has = "documentHighlight",
desc = "Prev Reference", cond = function() return Snacks.words.is_enabled() end },
}
return M._keys return M._keys
end end
---@param method string|string[] ---@param filter vim.lsp.get_clients.Filter
function M.has(buffer, method) ---@param spec LazyKeysLspSpec[]
if type(method) == "table" then function M.set(filter, spec)
for _, m in ipairs(method) do
if M.has(buffer, m) then
return true
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") local Keys = require("lazy.core.handler.keys")
if not Keys.resolve then for _, keys in pairs(Keys.resolve(spec)) do
return {} ---@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 end
local spec = vim.tbl_extend("force", {}, M.get()) else
local opts = LazyVim.opts("nvim-lspconfig") filters[#filters + 1] = filter
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 end
return Keys.resolve(spec)
end
function M.on_attach(_, buffer) for _, f in ipairs(filters) do
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) local opts = Keys.opts(keys)
opts.cond = nil ---@cast opts snacks.keymap.set.Opts
opts.has = nil opts.lsp = f
opts.silent = opts.silent ~= false Snacks.keymap.set(keys.mode or "n", keys.lhs, keys.rhs, opts)
opts.buffer = buffer end
vim.keymap.set(keys.mode or "n", keys.lhs, keys.rhs, opts)
end end
end end
end end

View File

@@ -221,7 +221,7 @@ function M.safe_keymap_set(mode, lhs, rhs, opts)
---@diagnostic disable-next-line: no-unknown ---@diagnostic disable-next-line: no-unknown
opts.remap = nil opts.remap = nil
end end
vim.keymap.set(modes, lhs, rhs, opts) Snacks.keymap.set(modes, lhs, rhs, opts)
end end
end end