diff --git a/lua/snacks/util/init.lua b/lua/snacks/util/init.lua index 0b817a29..5fe05444 100644 --- a/lua/snacks/util/init.lua +++ b/lua/snacks/util/init.lua @@ -1,5 +1,15 @@ ---@class snacks.util -local M = {} +---@field spawn snacks.spawn +---@field lsp snacks.lsp +local M = setmetatable({}, { + ---@param M snacks.util + __index = function(M, k) + if vim.tbl_contains({ "spawn", "lsp" }, k) then + M[k] = require("snacks.util." .. k) + end + return rawget(M, k) + end, +}) M.meta = { desc = "Utility functions for Snacks _(library)_", diff --git a/lua/snacks/util/lsp.lua b/lua/snacks/util/lsp.lua new file mode 100644 index 00000000..d4a820ad --- /dev/null +++ b/lua/snacks/util/lsp.lua @@ -0,0 +1,84 @@ +---@class snacks.lsp +local M = {} + +---@alias snacks.lsp.handler.cb fun(buf: number, client: vim.lsp.Client):any? + +---@class snacks.lsp.Handler +---@field filter vim.lsp.get_clients.Filter +---@field cb snacks.lsp.handler.cb +---@field done table + +local _handlers = {} ---@type snacks.lsp.Handler[] + +local did_setup = false + +---@param filter vim.lsp.get_clients.Filter +local function _handle(filter) + ---@param h snacks.lsp.Handler + local handlers = vim.tbl_filter(function(h) + ---@diagnostic disable-next-line: no-unknown + for k, v in pairs(filter) do + if h.filter[k] ~= nil and h.filter[k] ~= v then + return false + end + end + return true + end, _handlers) + + if #handlers == 0 then + return + end + + for _, state in ipairs(handlers) do + local f = vim.deepcopy(state.filter) + f = vim.tbl_extend("force", f, filter) + local clients = vim.lsp.get_clients(f) + for _, client in ipairs(clients) do + for buf in pairs(client.attached_buffers) do + if not state.done[buf] then + state.done[buf] = true + local ok, err = pcall(state.cb, buf, client) + if not ok then + vim.schedule(function() + Snacks.notify.error(("Error in handler:\n%s\n```lua\n%s\n```"):format(err, vim.inspect(state.filter))) + end) + end + end + end + end + end +end + +local function setup() + if did_setup then + return + end + did_setup = true + local register_capability = vim.lsp.handlers["client/registerCapability"] + vim.lsp.handlers["client/registerCapability"] = function(err, res, ctx) + ---@cast res lsp.RegistrationParams + local ret = register_capability(err, res, ctx) ---@type any + vim.schedule(function() + for _, m in ipairs(res.registrations or {}) do + _handle({ method = m.method, id = ctx.client_id }) + end + end) + return ret + end + vim.api.nvim_create_autocmd("LspAttach", { + group = vim.api.nvim_create_augroup("snacks.lsp.on_attach", { clear = true }), + callback = function(ev) + _handle({ id = ev.data.client_id, buffer = ev.buf }) + end, + }) +end + +---@param filter vim.lsp.get_clients.Filter +---@param cb snacks.lsp.handler.cb +function M.on(filter, cb) + setup() + table.insert(_handlers, { filter = filter, cb = cb, done = {} }) + _handle(filter) +end + +return M