diff options
Diffstat (limited to 'lua/lvim/lsp')
-rw-r--r-- | lua/lvim/lsp/config.lua | 29 | ||||
-rw-r--r-- | lua/lvim/lsp/handlers.lua | 70 | ||||
-rw-r--r-- | lua/lvim/lsp/init.lua | 14 | ||||
-rw-r--r-- | lua/lvim/lsp/manager.lua | 67 | ||||
-rw-r--r-- | lua/lvim/lsp/null-ls/code_actions.lua | 81 | ||||
-rw-r--r-- | lua/lvim/lsp/null-ls/formatters.lua | 35 | ||||
-rw-r--r-- | lua/lvim/lsp/null-ls/init.lua | 2 | ||||
-rw-r--r-- | lua/lvim/lsp/null-ls/linters.lua | 30 | ||||
-rw-r--r-- | lua/lvim/lsp/null-ls/services.lua | 14 | ||||
-rw-r--r-- | lua/lvim/lsp/templates.lua | 3 | ||||
-rw-r--r-- | lua/lvim/lsp/utils.lua | 11 |
11 files changed, 235 insertions, 121 deletions
diff --git a/lua/lvim/lsp/config.lua b/lua/lvim/lsp/config.lua index ce7ed891..1fbaf3a9 100644 --- a/lua/lvim/lsp/config.lua +++ b/lua/lvim/lsp/config.lua @@ -4,16 +4,31 @@ return { signs = { active = true, values = { - { name = "LspDiagnosticsSignError", text = "ï™™" }, - { name = "LspDiagnosticsSignWarning", text = "" }, - { name = "LspDiagnosticsSignHint", text = "ï µ" }, - { name = "LspDiagnosticsSignInformation", text = "" }, + { name = "DiagnosticSignError", text = "ï™™" }, + { name = "DiagnosticSignWarn", text = "" }, + { name = "DiagnosticSignHint", text = "ï µ" }, + { name = "DiagnosticSignInfo", text = "" }, }, }, virtual_text = true, update_in_insert = false, underline = true, severity_sort = true, + float = { + focusable = false, + style = "minimal", + border = "rounded", + source = "always", + header = "", + prefix = "", + format = function(d) + local t = vim.deepcopy(d) + if d.code then + t.message = string.format("%s [%s]", t.message, t.code):gsub("1. ", "") + end + return t.message + end, + }, }, document_highlight = true, code_lens_refresh = true, @@ -40,10 +55,13 @@ return { }, null_ls = { setup = {}, + config = {}, }, override = { "angularls", "ansiblels", + "ccls", + "csharp_ls", "denols", "ember", "emmet_ls", @@ -54,10 +72,13 @@ return { "ltex", "phpactor", "pylsp", + "quick_lint_js", "rome", "sorbet", "sqlls", "sqls", + "solang", + "spectral", "stylelint_lsp", "tailwindcss", "tflint", diff --git a/lua/lvim/lsp/handlers.lua b/lua/lvim/lsp/handlers.lua index 27ce8589..45f73e91 100644 --- a/lua/lvim/lsp/handlers.lua +++ b/lua/lvim/lsp/handlers.lua @@ -9,42 +9,10 @@ function M.setup() underline = lvim.lsp.diagnostics.underline, update_in_insert = lvim.lsp.diagnostics.update_in_insert, severity_sort = lvim.lsp.diagnostics.severity_sort, + float = lvim.lsp.diagnostics.float, } - if vim.fn.has "nvim-0.5.1" > 0 then - vim.lsp.handlers["textDocument/publishDiagnostics"] = function(_, result, ctx, _) - local uri = result.uri - local bufnr = vim.uri_to_bufnr(uri) - if not bufnr then - return - end - - local diagnostics = result.diagnostics - local ok, vim_diag = pcall(require, "vim.diagnostic") - if ok then - -- FIX: why can't we just use vim.diagnostic.get(buf_id)? - config.signs = true - for i, diagnostic in ipairs(diagnostics) do - local rng = diagnostic.range - diagnostics[i].lnum = rng["start"].line - diagnostics[i].end_lnum = rng["end"].line - diagnostics[i].col = rng["start"].character - diagnostics[i].end_col = rng["end"].character - end - local namespace = vim.lsp.diagnostic.get_namespace(ctx.client_id) - - vim_diag.set(namespace, bufnr, diagnostics, config) - if not vim.api.nvim_buf_is_loaded(bufnr) then - return - end - vim_diag.show(namespace, bufnr, diagnostics, config) - else - vim.lsp.diagnostic.save(diagnostics, bufnr, ctx.client_id) - if not vim.api.nvim_buf_is_loaded(bufnr) then - return - end - vim.lsp.diagnostic.display(diagnostics, bufnr, ctx.client_id, config) - end - end + if vim.fn.has "nvim-0.6" == 1 then + vim.diagnostic.config(config) else vim.lsp.handlers["textDocument/publishDiagnostics"] = function(_, _, params, client_id, _) local uri = params.uri @@ -60,27 +28,29 @@ function M.setup() end vim.lsp.diagnostic.display(diagnostics, bufnr, client_id, config) end - end - - vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { - border = lvim.lsp.popup_border, - }) - vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, { - border = lvim.lsp.popup_border, - }) -end + vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { + border = lvim.lsp.popup_border, + }) -local function split_by_chunk(text, chunkSize) - local s = {} - for i = 1, #text, chunkSize do - s[#s + 1] = text:sub(i, i + chunkSize - 1) + vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, { + border = lvim.lsp.popup_border, + }) end - return s end function M.show_line_diagnostics() - -- TODO: replace all this with vim.diagnostic.show_position_diagnostics() + if vim.fn.has "nvim-0.6" == 1 then + return vim.diagnostic.open_float(0, { scope = "line" }) + end + + local function split_by_chunk(text, chunkSize) + local s = {} + for i = 1, #text, chunkSize do + s[#s + 1] = text:sub(i, i + chunkSize - 1) + end + return s + end local diagnostics = vim.lsp.diagnostic.get_line_diagnostics() local severity_highlight = { "LspDiagnosticsFloatingError", diff --git a/lua/lvim/lsp/init.lua b/lua/lvim/lsp/init.lua index d00f75c6..68a64d6c 100644 --- a/lua/lvim/lsp/init.lua +++ b/lua/lvim/lsp/init.lua @@ -137,10 +137,10 @@ function M.get_common_opts() end local LSP_DEPRECATED_SIGN_MAP = { - ["LspDiagnosticsSignError"] = "DiagnosticSignError", - ["LspDiagnosticsSignWarning"] = "DiagnosticSignWarn", - ["LspDiagnosticsSignHint"] = "DiagnosticSignHint", - ["LspDiagnosticsSignInformation"] = "DiagnosticSignInfo", + ["DiagnosticSignError"] = "LspDiagnosticsSignError", + ["DiagnosticSignWarn"] = "LspDiagnosticsSignWarning", + ["DiagnosticSignHint"] = "LspDiagnosticsSignHint", + ["DiagnosticSignInfo"] = "LspDiagnosticsSignInformation", } function M.setup() @@ -151,11 +151,11 @@ function M.setup() return end - local is_neovim_nightly = vim.fn.has "nvim-0.5.1" > 0 + local is_neovim_5 = vim.fn.has "nvim-0.6" ~= 1 for _, sign in ipairs(lvim.lsp.diagnostics.signs.values) do local lsp_sign_name = LSP_DEPRECATED_SIGN_MAP[sign.name] - if is_neovim_nightly and lsp_sign_name then + if is_neovim_5 and lsp_sign_name then vim.fn.sign_define(lsp_sign_name, { texthl = lsp_sign_name, text = sign.text, numhl = lsp_sign_name }) end vim.fn.sign_define(sign.name, { texthl = sign.name, text = sign.text, numhl = sign.name }) @@ -171,7 +171,7 @@ function M.setup() require("lvim.lsp.null-ls").setup() - require("lvim.utils").toggle_autoformat() + require("lvim.core.autocmds").configure_format_on_save() end return M diff --git a/lua/lvim/lsp/manager.lua b/lua/lvim/lsp/manager.lua index dbb7b87f..7a35f1ff 100644 --- a/lua/lvim/lsp/manager.lua +++ b/lua/lvim/lsp/manager.lua @@ -45,48 +45,65 @@ local function buf_try_add(server_name, bufnr) require("lspconfig")[server_name].manager.try_add_wrapper(bufnr) end +-- check if the manager autocomd has already been configured since some servers can take a while to initialize +-- this helps guarding against a data-race condition where a server can get configured twice +-- which seems to occur only when attaching to single-files +local function client_is_configured(server_name, ft) + ft = ft or vim.bo.filetype + local active_autocmds = vim.split(vim.fn.execute("autocmd FileType " .. ft), "\n") + for _, result in ipairs(active_autocmds) do + if result:match(server_name) then + return true + end + end + return false +end + ---Setup a language server by providing a name ---@param server_name string name of the language server ---@param user_config table [optional] when available it will take predence over any default configurations function M.setup(server_name, user_config) vim.validate { name = { server_name, "string" } } - if lvim_lsp_utils.is_client_active(server_name) then + if lvim_lsp_utils.is_client_active(server_name) or client_is_configured(server_name) then + Log:debug(string.format("[%q] is already configured. Ignoring repeated setup call.", server_name)) return end - local servers = require "nvim-lsp-installer.servers" local config = resolve_config(server_name, user_config) + + local servers = require "nvim-lsp-installer.servers" local server_available, requested_server = servers.get_server(server_name) - if server_available then - local install_notification = false - - if not requested_server:is_installed() then - if lvim.lsp.automatic_servers_installation then - Log:debug "Automatic server installation detected" - requested_server:install() - install_notification = true - else - Log:debug(requested_server.name .. " is not managed by the automatic installer") - end - end + local is_overridden = vim.tbl_contains(lvim.lsp.override, server_name) - requested_server:on_ready(function() - if install_notification then - vim.notify(string.format("Installation complete for [%s] server", requested_server.name), vim.log.levels.INFO) - end - install_notification = false - requested_server:setup(config) - end) - else - -- since it may not be installed, don't attempt to configure the LSP unless there is a custom provider - local has_custom_provider, _ = pcall(require, "lvim/lsp/providers/" .. server_name) - if has_custom_provider then + if not server_available or is_overridden then + pcall(function() require("lspconfig")[server_name].setup(config) buf_try_add(server_name) + end) + return + end + + local install_notification = false + + if not requested_server:is_installed() then + if lvim.lsp.automatic_servers_installation then + Log:debug "Automatic server installation detected" + requested_server:install() + install_notification = true + else + Log:debug(requested_server.name .. " is not managed by the automatic installer") end end + + requested_server:on_ready(function() + if install_notification then + vim.notify(string.format("Installation complete for [%s] server", requested_server.name), vim.log.levels.INFO) + end + install_notification = false + requested_server:setup(config) + end) end return M diff --git a/lua/lvim/lsp/null-ls/code_actions.lua b/lua/lvim/lsp/null-ls/code_actions.lua new file mode 100644 index 00000000..ff59fabf --- /dev/null +++ b/lua/lvim/lsp/null-ls/code_actions.lua @@ -0,0 +1,81 @@ +local M = {} + +local null_ls = require "null-ls" +local services = require "lvim.lsp.null-ls.services" +local Log = require "lvim.core.log" + +local METHOD = null_ls.methods.CODE_ACTION + +local is_registered = function(name) + local query = { + name = name, + method = METHOD, + } + return require("null-ls.sources").is_registered(query) +end + +function M.list_registered_providers(filetype) + local registered_providers = services.list_registered_providers_names(filetype) + return registered_providers[METHOD] or {} +end + +function M.list_available(filetype) + local availables = require("null-ls.sources").get_available(filetype, METHOD) + local actors = vim.tbl_map(function(src) + return src.name + end, availables) + table.sort(actors) + return actors +end + +function M.list_configured(actions_configs) + local actors, errors = {}, {} + + for _, config in ipairs(actions_configs) do + vim.validate { + ["config.name"] = { config.name, "string" }, + } + + local name = config.name:gsub("-", "_") + local actor = null_ls.builtins.code_actions[name] + + if not actor then + Log:error("Not a valid code_actions: " .. config.name) + errors[name] = {} -- Add data here when necessary + elseif is_registered(config.name) then + Log:trace "Skipping registering the source more than once" + else + local command + if actor._opts.command then + command = services.find_command(actor._opts.command) + end + if not command and actor._opts.command ~= nil then + Log:warn("Not found: " .. actor._opts.command) + errors[name] = {} -- Add data here when necessary + else + Log:debug("Using code_actions: " .. (command or config.name)) + table.insert( + actors, + actor.with { + command = command, -- could be nil + extra_args = config.args, + filetypes = config.filetypes, + } + ) + end + end + end + + return { supported = actors, unsupported = errors } +end + +function M.setup(actions_configs) + if vim.tbl_isempty(actions_configs) then + return + end + + local actions = M.list_configured(actions_configs) + null_ls.register { sources = actions.supported } +end + +return M diff --git a/lua/lvim/lsp/null-ls/formatters.lua b/lua/lvim/lsp/null-ls/formatters.lua index 20939039..b2e191c5 100644 --- a/lua/lvim/lsp/null-ls/formatters.lua +++ b/lua/lvim/lsp/null-ls/formatters.lua @@ -4,6 +4,14 @@ local null_ls = require "null-ls" local services = require "lvim.lsp.null-ls.services" local Log = require "lvim.core.log" +local is_registered = function(name) + local query = { + name = name, + method = require("null-ls").methods.FORMATTING, + } + return require("null-ls.sources").is_registered(query) +end + function M.list_registered_providers(filetype) local null_ls_methods = require "null-ls.methods" local formatter_method = null_ls_methods.internal["FORMATTING"] @@ -30,24 +38,29 @@ function M.list_configured(formatter_configs) local formatters, errors = {}, {} for _, fmt_config in ipairs(formatter_configs) do - local formatter_name = fmt_config.exe:gsub("-", "_") - local formatter = null_ls.builtins.formatting[formatter_name] + local name = fmt_config.exe:gsub("-", "_") + local formatter = null_ls.builtins.formatting[name] if not formatter then Log:error("Not a valid formatter: " .. fmt_config.exe) - errors[fmt_config.exe] = {} -- Add data here when necessary + errors[name] = {} -- Add data here when necessary + elseif is_registered(fmt_config.exe) then + Log:trace "Skipping registering the source more than once" else local formatter_cmd = services.find_command(formatter._opts.command) if not formatter_cmd then Log:warn("Not found: " .. formatter._opts.command) - errors[fmt_config.exe] = {} -- Add data here when necessary + errors[name] = {} -- Add data here when necessary else Log:debug("Using formatter: " .. formatter_cmd) - formatters[fmt_config.exe] = formatter.with { - command = formatter_cmd, - extra_args = fmt_config.args, - filetypes = fmt_config.filetypes, - } + table.insert( + formatters, + formatter.with { + command = formatter_cmd, + extra_args = fmt_config.args, + filetypes = fmt_config.filetypes, + } + ) end end end @@ -60,8 +73,8 @@ function M.setup(formatter_configs) return end - local formatters_by_ft = M.list_configured(formatter_configs) - null_ls.register { sources = formatters_by_ft.supported } + local formatters = M.list_configured(formatter_configs) + null_ls.register { sources = formatters.supported } end return M diff --git a/lua/lvim/lsp/null-ls/init.lua b/lua/lvim/lsp/null-ls/init.lua index 5e8c6b11..f5e820e8 100644 --- a/lua/lvim/lsp/null-ls/init.lua +++ b/lua/lvim/lsp/null-ls/init.lua @@ -9,7 +9,7 @@ function M:setup() return end - null_ls.config() + null_ls.config(lvim.lsp.null_ls.config) local default_opts = require("lvim.lsp").get_common_opts() if vim.tbl_isempty(lvim.lsp.null_ls.setup or {}) then diff --git a/lua/lvim/lsp/null-ls/linters.lua b/lua/lvim/lsp/null-ls/linters.lua index ced4bf34..6a793d26 100644 --- a/lua/lvim/lsp/null-ls/linters.lua +++ b/lua/lvim/lsp/null-ls/linters.lua @@ -4,6 +4,14 @@ local null_ls = require "null-ls" local services = require "lvim.lsp.null-ls.services" local Log = require "lvim.core.log" +local is_registered = function(name) + local query = { + name = name, + method = require("null-ls").methods.DIAGNOSTICS, + } + return require("null-ls.sources").is_registered(query) +end + function M.list_registered_providers(filetype) local null_ls_methods = require "null-ls.methods" local linter_method = null_ls_methods.internal["DIAGNOSTICS"] @@ -21,6 +29,7 @@ function M.list_available(filetype) table.insert(linters, provider.name) end end + table.sort(linters) return linters end @@ -29,24 +38,29 @@ function M.list_configured(linter_configs) local linters, errors = {}, {} for _, lnt_config in pairs(linter_configs) do - local linter_name = lnt_config.exe:gsub("-", "_") - local linter = null_ls.builtins.diagnostics[linter_name] + local name = lnt_config.exe:gsub("-", "_") + local linter = null_ls.builtins.diagnostics[name] if not linter then Log:error("Not a valid linter: " .. lnt_config.exe) errors[lnt_config.exe] = {} -- Add data here when necessary + elseif is_registered(lnt_config.exe) then + Log:trace "Skipping registering the source more than once" else local linter_cmd = services.find_command(linter._opts.command) if not linter_cmd then Log:warn("Not found: " .. linter._opts.command) - errors[lnt_config.exe] = {} -- Add data here when necessary + errors[name] = {} -- Add data here when necessary else Log:debug("Using linter: " .. linter_cmd) - linters[lnt_config.exe] = linter.with { - command = linter_cmd, - extra_args = lnt_config.args, - filetypes = lnt_config.filetypes, - } + table.insert( + linters, + linter.with { + command = linter_cmd, + extra_args = lnt_config.args, + filetypes = lnt_config.filetypes, + } + ) end end end diff --git a/lua/lvim/lsp/null-ls/services.lua b/lua/lvim/lsp/null-ls/services.lua index 9cb29f49..9151cc39 100644 --- a/lua/lvim/lsp/null-ls/services.lua +++ b/lua/lvim/lsp/null-ls/services.lua @@ -46,15 +46,13 @@ function M.find_command(command) end function M.list_registered_providers_names(filetype) - local u = require "null-ls.utils" - local c = require "null-ls.config" + local s = require "null-ls.sources" + local available_sources = s.get_available(filetype) local registered = {} - for method, source in pairs(c.get()._methods) do - for name, filetypes in pairs(source) do - if u.filetype_matches(filetypes, filetype) then - registered[method] = registered[method] or {} - table.insert(registered[method], name) - end + for _, source in ipairs(available_sources) do + for method in pairs(source.methods) do + registered[method] = registered[method] or {} + table.insert(registered[method], source.name) end end return registered diff --git a/lua/lvim/lsp/templates.lua b/lua/lvim/lsp/templates.lua index 33c75a6e..3478f4fb 100644 --- a/lua/lvim/lsp/templates.lua +++ b/lua/lvim/lsp/templates.lua @@ -19,8 +19,7 @@ end ---@param server_name string name of a valid language server, e.g. pyright, gopls, tsserver, etc. ---@param dir string the full path to the desired directory function M.generate_ftplugin(server_name, dir) - local has_custom_provider, _ = pcall(require, "lvim/lsp/providers/" .. server_name) - if vim.tbl_contains(lvim.lsp.override, server_name) and not has_custom_provider then + if vim.tbl_contains(lvim.lsp.override, server_name) then return end diff --git a/lua/lvim/lsp/utils.lua b/lua/lvim/lsp/utils.lua index 7cc8f54f..df3846ce 100644 --- a/lua/lvim/lsp/utils.lua +++ b/lua/lvim/lsp/utils.lua @@ -22,22 +22,23 @@ function M.get_active_clients_by_ft(filetype) 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 ipairs(buf_clients) do + for _, buf_client in pairs(buf_clients) do if buf_client.name ~= "null-ls" then - client_id = buf_client.id + client = buf_client break end end + else + client = vim.lsp.get_client_by_id(tonumber(client_id)) end - if not client_id then + if not client then error "Unable to determine client_id" return end - local client = vim.lsp.get_client_by_id(tonumber(client_id)) - local enabled_caps = {} for capability, status in pairs(client.resolved_capabilities) do if status == true then |