local M = {} local tbl = require "lvim.utils.table" local Log = require "lvim.core.log" function M.is_client_active(name) local clients = vim.lsp.get_active_clients() return tbl.find_first(clients, function(client) return client.name == name end) end function M.get_active_clients_by_ft(filetype) local matches = {} local clients = vim.lsp.get_active_clients() for _, client in pairs(clients) do local supported_filetypes = client.config.filetypes or {} if client.name ~= "null-ls" and vim.tbl_contains(supported_filetypes, filetype) then table.insert(matches, client) end end return matches end function M.get_client_capabilities(client_id) local client if not client_id then local buf_clients = vim.lsp.buf_get_clients() for _, buf_client in pairs(buf_clients) do if buf_client.name ~= "null-ls" then client = buf_client break end end else client = vim.lsp.get_client_by_id(tonumber(client_id)) end if not client then error "Unable to determine client_id" return end local enabled_caps = {} for capability, status in pairs(client.server_capabilities or client.resolved_capabilities) do if status == true then table.insert(enabled_caps, capability) end end return enabled_caps end ---Get supported filetypes per server ---@param server_name string can be any server supported by nvim-lsp-installer ---@return string[] supported filestypes as a list of strings function M.get_supported_filetypes(server_name) local status_ok, config = pcall(require, ("lspconfig.server_configurations.%s"):format(server_name)) if not status_ok then return {} end return config.default_config.filetypes or {} end ---Get supported servers per filetype ---@param filter { filetype: string | string[] }?: (optional) Used to filter the list of server names. ---@return string[] list of names of supported servers function M.get_supported_servers(filter) local _, supported_servers = pcall(function() return require("mason-lspconfig").get_available_servers(filter) end) return supported_servers or {} end ---Get all supported filetypes by nvim-lsp-installer ---@return string[] supported filestypes as a list of strings function M.get_all_supported_filetypes() local status_ok, filetype_server_map = pcall(require, "mason-lspconfig.mappings.filetype") if not status_ok then return {} end return vim.tbl_keys(filetype_server_map or {}) end function M.setup_document_highlight(client, bufnr) if lvim.builtin.illuminate.active then Log:debug "skipping setup for document_highlight, illuminate already active" return end local status_ok, highlight_supported = pcall(function() return client.supports_method "textDocument/documentHighlight" end) if not status_ok or not highlight_supported then return end local group = "lsp_document_highlight" local hl_events = { "CursorHold", "CursorHoldI" } local ok, hl_autocmds = pcall(vim.api.nvim_get_autocmds, { group = group, buffer = bufnr, event = hl_events, }) if ok and #hl_autocmds > 0 then return end vim.api.nvim_create_augroup(group, { clear = false }) vim.api.nvim_create_autocmd(hl_events, { group = group, buffer = bufnr, callback = vim.lsp.buf.document_highlight, }) vim.api.nvim_create_autocmd("CursorMoved", { group = group, buffer = bufnr, callback = vim.lsp.buf.clear_references, }) end function M.setup_document_symbols(client, bufnr) vim.g.navic_silence = false -- can be set to true to supress error local symbols_supported = client.supports_method "textDocument/documentSymbol" if not symbols_supported then Log:debug("skipping setup for document_symbols, method not supported by " .. client.name) return end local status_ok, navic = pcall(require, "nvim-navic") if status_ok then navic.attach(client, bufnr) end end function M.setup_codelens_refresh(client, bufnr) local status_ok, codelens_supported = pcall(function() return client.supports_method "textDocument/codeLens" end) if not status_ok or not codelens_supported then return end local group = "lsp_code_lens_refresh" local cl_events = { "BufEnter", "InsertLeave" } local ok, cl_autocmds = pcall(vim.api.nvim_get_autocmds, { group = group, buffer = bufnr, event = cl_events, }) if ok and #cl_autocmds > 0 then return end vim.api.nvim_create_augroup(group, { clear = false }) vim.api.nvim_create_autocmd(cl_events, { group = group, buffer = bufnr, callback = vim.lsp.codelens.refresh, }) end ---filter passed to vim.lsp.buf.format ---always selects null-ls if it's available and caches the value per buffer ---@param client table client attached to a buffer ---@return boolean if client matches function M.format_filter(client) local filetype = vim.bo.filetype local n = require "null-ls" local s = require "null-ls.sources" local method = n.methods.FORMATTING local avalable_formatters = s.get_available(filetype, method) if #avalable_formatters > 0 then return client.name == "null-ls" elseif client.supports_method "textDocument/formatting" then return true else return false end end ---Provide vim.lsp.buf.format for nvim <0.8 ---@param opts table function M.format(opts) opts = opts or {} opts.filter = opts.filter or M.format_filter if vim.lsp.buf.format then return vim.lsp.buf.format(opts) end local bufnr = opts.bufnr or vim.api.nvim_get_current_buf() ---@type table|nil local clients = vim.lsp.get_active_clients { id = opts.id, bufnr = bufnr, name = opts.name, } if opts.filter then clients = vim.tbl_filter(opts.filter, clients) end clients = vim.tbl_filter(function(client) return client.supports_method "textDocument/formatting" end, clients) if #clients == 0 then vim.notify_once "[LSP] Format request failed, no matching language servers." end local timeout_ms = opts.timeout_ms or 1000 for _, client in pairs(clients) do local params = vim.lsp.util.make_formatting_params(opts.formatting_options) local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, bufnr) if result and result.result then vim.lsp.util.apply_text_edits(result.result, bufnr, client.offset_encoding) elseif err then vim.notify(string.format("[LSP][%s] %s", client.name, err), vim.log.levels.WARN) end end end return M