From: Samir Benmendil Date: Sun, 16 Feb 2025 17:03:46 +0000 (+0000) Subject: lazyvim: absorb lazyvim.util.lsp X-Git-Url: https://git.rmz.io/dotfiles.git/commitdiff_plain/b99dee25318e48024313b76e075a24afccd0c10b lazyvim: absorb lazyvim.util.lsp Leaving it as is for now, I'm going to have to find more time to read and understand what it does. There's some clever things going on there to have one place to customize the keymaps and apply them to all lsps that support that function. --- diff --git a/nvim/lua/plugins/lsp/init.lua b/nvim/lua/plugins/lsp/init.lua index 351f060..d32e76c 100644 --- a/nvim/lua/plugins/lsp/init.lua +++ b/nvim/lua/plugins/lsp/init.lua @@ -95,21 +95,19 @@ return { end -- setup keymaps - LazyVim.lsp.on_attach(function(client, buffer) + rmz.lsp.on_attach(function(client, buffer) require("plugins.lsp.keymaps").on_attach(client, buffer) end) - LazyVim.lsp.setup() - LazyVim.lsp.on_dynamic_capability(require("plugins.lsp.keymaps").on_attach) + rmz.lsp.setup() + rmz.lsp.on_dynamic_capability(require("plugins.lsp.keymaps").on_attach) -- TODO: should vim.diagnostics be condigured directly? vim.diagnostic.config(vim.deepcopy(opts.diagnostics)) -- get all the servers that are available through mason-lspconfig - local have_mason, mlsp = pcall(require, "mason-lspconfig") - local all_mslp_servers = {} - if have_mason then - all_mslp_servers = vim.tbl_keys(require("mason-lspconfig.mappings.server").lspconfig_to_package) - end + local mlsp = require("mason-lspconfig") + -- TODO: use mason-lspconfig.get_available_servers()? + local all_mslp_servers = vim.tbl_keys(require("mason-lspconfig.mappings.server").lspconfig_to_package) local ensure_installed = {} ---@type string[] for server, server_opts in pairs(opts.servers) do @@ -126,16 +124,14 @@ return { end end - if have_mason then - mlsp.setup({ - ensure_installed = vim.tbl_deep_extend( - "force", - ensure_installed, - LazyVim.opts("mason-lspconfig.nvim").ensure_installed or {} - ), - handlers = { setup }, - }) - end + mlsp.setup({ + ensure_installed = vim.tbl_deep_extend( + "force", + ensure_installed, + LazyVim.opts("mason-lspconfig.nvim").ensure_installed or {} + ), + handlers = { setup }, + }) end }, { "williamboman/mason.nvim", diff --git a/nvim/lua/plugins/lsp/keymaps.lua b/nvim/lua/plugins/lsp/keymaps.lua index 72cfa51..7cb2b49 100644 --- a/nvim/lua/plugins/lsp/keymaps.lua +++ b/nvim/lua/plugins/lsp/keymaps.lua @@ -27,7 +27,7 @@ function M.get() { "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" }, + { "cA", rmz.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", @@ -52,7 +52,7 @@ function M.has(buffer, method) return false end method = method:find("/") and method or "textDocument/" .. method - local clients = LazyVim.lsp.get_clients({ bufnr = buffer }) + local clients = rmz.lsp.get_clients({ bufnr = buffer }) for _, client in ipairs(clients) do if client.supports_method(method) then return true @@ -69,7 +69,7 @@ function M.resolve(buffer) end local spec = vim.tbl_extend("force", {}, M.get()) local opts = LazyVim.opts("nvim-lspconfig") - local clients = LazyVim.lsp.get_clients({ bufnr = buffer }) + local clients = rmz.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) diff --git a/nvim/lua/plugins/picker.lua b/nvim/lua/plugins/picker.lua index 6f69fc2..3878e03 100644 --- a/nvim/lua/plugins/picker.lua +++ b/nvim/lua/plugins/picker.lua @@ -72,8 +72,8 @@ return { -- { "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" }, + { "ss", function() Snacks.picker.lsp_symbols({ filter = rmz.lsp.kind_filter }) end, desc = "LSP Symbols", has = "documentSymbol" }, + { "sS", function() Snacks.picker.lsp_workspace_symbols({ filter = rmz.lsp.kind_filter }) end, desc = "LSP Workspace Symbols", has = "workspace/symbols" }, }) end, }, diff --git a/nvim/lua/plugins/ui.lua b/nvim/lua/plugins/ui.lua index 05ab3df..261312a 100644 --- a/nvim/lua/plugins/ui.lua +++ b/nvim/lua/plugins/ui.lua @@ -330,7 +330,7 @@ return { lazy = true, init = function() vim.g.navic_silence = true - LazyVim.lsp.on_attach(function(client, buffer) + rmz.lsp.on_attach(function(client, buffer) if client.supports_method("textDocument/documentSymbol") then require("nvim-navic").attach(client, buffer) end diff --git a/nvim/lua/rmz/util/init.lua b/nvim/lua/rmz/util/init.lua index 9ef8cb3..7d519f1 100644 --- a/nvim/lua/rmz/util/init.lua +++ b/nvim/lua/rmz/util/init.lua @@ -1,6 +1,7 @@ ---@class rmz.util local M = { ui = require("rmz.util.ui"), + lsp = require("rmz.util.lsp"), } --- Deduplicates a list. diff --git a/nvim/lua/rmz/util/lsp.lua b/nvim/lua/rmz/util/lsp.lua new file mode 100644 index 0000000..7424803 --- /dev/null +++ b/nvim/lua/rmz/util/lsp.lua @@ -0,0 +1,258 @@ +-- Absorbed from LazyVim +-- TODO: review, understand and adapt +---@class rmz.util.lsp +local M = {} + +---@alias lsp.Client.filter {id?: number, bufnr?: number, name?: string, method?: string, filter?:fun(client: lsp.Client):boolean} + +M.kind_filter = { + default = { + "Class", + "Constructor", + "Enum", + "Field", + "Function", + "Interface", + "Method", + "Module", + "Namespace", + "Package", + "Property", + "Struct", + "Trait", + }, + markdown = false, + help = false, + -- you can specify a different filter for each filetype + lua = { + "Class", + "Constructor", + "Enum", + "Field", + "Function", + "Interface", + "Method", + "Module", + "Namespace", + -- "Package", -- remove package since luals uses it for control flow structures + "Property", + "Struct", + "Trait", + }, +} + +---@param opts? lsp.Client.filter +function M.get_clients(opts) + local ret = {} ---@type vim.lsp.Client[] + if vim.lsp.get_clients then + ret = vim.lsp.get_clients(opts) + else + ---@diagnostic disable-next-line: deprecated + ret = vim.lsp.get_active_clients(opts) + if opts and opts.method then + ---@param client vim.lsp.Client + ret = vim.tbl_filter(function(client) + return client.supports_method(opts.method, { bufnr = opts.bufnr }) + end, ret) + end + end + return opts and opts.filter and vim.tbl_filter(opts.filter, ret) or ret +end + +---@param on_attach fun(client:vim.lsp.Client, buffer) +---@param name? string +function M.on_attach(on_attach, name) + return vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local buffer = args.buf ---@type number + local client = vim.lsp.get_client_by_id(args.data.client_id) + if client and (not name or client.name == name) then + return on_attach(client, buffer) + end + end, + }) +end + +---@type table>> +M._supports_method = {} + +function M.setup() + local register_capability = vim.lsp.handlers["client/registerCapability"] + vim.lsp.handlers["client/registerCapability"] = function(err, res, ctx) + ---@diagnostic disable-next-line: no-unknown + local ret = register_capability(err, res, ctx) + local client = vim.lsp.get_client_by_id(ctx.client_id) + if client then + for buffer in pairs(client.attached_buffers) do + vim.api.nvim_exec_autocmds("User", { + pattern = "LspDynamicCapability", + data = { client_id = client.id, buffer = buffer }, + }) + end + end + return ret + end + M.on_attach(M._check_methods) + M.on_dynamic_capability(M._check_methods) +end + +---@param client vim.lsp.Client +function M._check_methods(client, buffer) + -- don't trigger on invalid buffers + if not vim.api.nvim_buf_is_valid(buffer) then + return + end + -- don't trigger on non-listed buffers + if not vim.bo[buffer].buflisted then + return + end + -- don't trigger on nofile buffers + if vim.bo[buffer].buftype == "nofile" then + return + end + for method, clients in pairs(M._supports_method) do + clients[client] = clients[client] or {} + if not clients[client][buffer] then + if client.supports_method and client.supports_method(method, { bufnr = buffer }) then + clients[client][buffer] = true + vim.api.nvim_exec_autocmds("User", { + pattern = "LspSupportsMethod", + data = { client_id = client.id, buffer = buffer, method = method }, + }) + end + end + end +end + +---@param fn fun(client:vim.lsp.Client, buffer):boolean? +---@param opts? {group?: integer} +function M.on_dynamic_capability(fn, opts) + return vim.api.nvim_create_autocmd("User", { + pattern = "LspDynamicCapability", + group = opts and opts.group or nil, + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + local buffer = args.data.buffer ---@type number + if client then + return fn(client, buffer) + end + end, + }) +end + +---@param method string +---@param fn fun(client:vim.lsp.Client, buffer) +function M.on_supports_method(method, fn) + M._supports_method[method] = M._supports_method[method] or setmetatable({}, { __mode = "k" }) + return vim.api.nvim_create_autocmd("User", { + pattern = "LspSupportsMethod", + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + local buffer = args.data.buffer ---@type number + if client and method == args.data.method then + return fn(client, buffer) + end + end, + }) +end + +---@return _.lspconfig.options +function M.get_config(server) + local configs = require("lspconfig.configs") + return rawget(configs, server) +end + +---@return {default_config:lspconfig.Config} +function M.get_raw_config(server) + local ok, ret = pcall(require, "lspconfig.configs." .. server) + if ok then + return ret + end + return require("lspconfig.server_configurations." .. server) +end + +function M.is_enabled(server) + local c = M.get_config(server) + return c and c.enabled ~= false +end + +---@param server string +---@param cond fun( root_dir, config): boolean +function M.disable(server, cond) + local util = require("lspconfig.util") + local def = M.get_config(server) + ---@diagnostic disable-next-line: undefined-field + def.document_config.on_new_config = util.add_hook_before(def.document_config.on_new_config, function(config, root_dir) + if cond(root_dir, config) then + config.enabled = false + end + end) +end + +---@param opts? LazyFormatter| {filter?: (string|lsp.Client.filter)} +function M.formatter(opts) + opts = opts or {} + local filter = opts.filter or {} + filter = type(filter) == "string" and { name = filter } or filter + ---@cast filter lsp.Client.filter + ---@type LazyFormatter + local ret = { + name = "LSP", + primary = true, + priority = 1, + format = function(buf) + M.format(LazyVim.merge({}, filter, { bufnr = buf })) + end, + sources = function(buf) + local clients = M.get_clients(LazyVim.merge({}, filter, { bufnr = buf })) + ---@param client vim.lsp.Client + local ret = vim.tbl_filter(function(client) + return client.supports_method("textDocument/formatting") + or client.supports_method("textDocument/rangeFormatting") + end, clients) + ---@param client vim.lsp.Client + return vim.tbl_map(function(client) + return client.name + end, ret) + end, + } + return LazyVim.merge(ret, opts) --[[@as LazyFormatter]] +end + +---@alias lsp.Client.format {timeout_ms?: number, format_options?: table} | lsp.Client.filter + +---@param opts? lsp.Client.format +function M.format(opts) + opts = vim.tbl_deep_extend( + "force", + {}, + opts or {}, + LazyVim.opts("nvim-lspconfig").format or {}, + LazyVim.opts("conform.nvim").format or {} + ) + local ok, conform = pcall(require, "conform") + -- use conform for formatting with LSP when available, + -- since it has better format diffing + if ok then + opts.formatters = {} + conform.format(opts) + else + vim.lsp.buf.format(opts) + end +end + +M.action = setmetatable({}, { + __index = function(_, action) + return function() + vim.lsp.buf.code_action({ + apply = true, + context = { + only = { action }, + diagnostics = {}, + }, + }) + end + end, +}) + +return M