From db9888963a581ba545bf40cd4d3ec393972cfec2 Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Tue, 27 Feb 2024 17:20:51 +0100 Subject: [PATCH] feat(lsp): reimplement automatic installation of language servers --- README.md | 14 +- lua/lsp.lua | 242 +++++++++++++++---------------- lua/lsp/bashls.lua | 6 +- lua/lsp/clangd.lua | 6 +- lua/lsp/cmake.lua | 6 +- lua/lsp/diagnosticls.lua | 12 +- lua/lsp/gopls.lua | 6 +- lua/lsp/intelephense.lua | 6 +- lua/lsp/jedi_language_server.lua | 6 +- lua/lsp/lua_ls.lua | 6 +- lua/lsp/rust_analyzer.lua | 6 +- lua/lsp/zls.lua | 8 +- lua/plugins.lua | 24 +-- lua/plugins/mason_lspconfig.lua | 12 -- lua/plugins/mason_update_all.lua | 7 - 15 files changed, 194 insertions(+), 173 deletions(-) delete mode 100644 lua/plugins/mason_lspconfig.lua delete mode 100644 lua/plugins/mason_update_all.lua diff --git a/README.md b/README.md index 3d1599e..61f1175 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,11 @@ when needed. ### Language servers Language servers are installed automatically to the nvim data directory -(`:echo stdpath('data') .. '/mason'`). The following are some noted requirements +(`:echo stdpath('data') .. '/mason'`) upon entering a buffer of related +filetype. Automatic installation can be turned off, see the end of this section +for instructions. + +The following are some noted requirements for the installations themselves: - **diagnostic-languageserver**: npm @@ -77,9 +81,11 @@ Some servers have additional runtime dependencies: - **bash-language-server**: shellcheck (optional, used for linting) -If you don't need some specific language server, and want to get rid of any -warning messages, you may either remove them from the top of `lua/lsp.lua` -or disable them in `lua/lsp/.lua`. +If you don't need some specific language server, you may either remove them from +the top of `lua/lsp.lua` or disable them in `lua/lsp/.lua`. + +To disable automatic installation of a selected language server, remove or +comment out the mason part of the configuration at `lua/lsp/.lua`. ### Nerd Font It's recommended to use a [Nerd Font](https://www.nerdfonts.com/), diff --git a/lua/lsp.lua b/lua/lsp.lua index ecab754..35a23ba 100644 --- a/lua/lsp.lua +++ b/lua/lsp.lua @@ -1,14 +1,14 @@ -local package_name = "lsp" +local module_name = "lsp" local utils = require("utils") -local P = {} +local M = {} -P._filetypes = nil -P._language_servers = nil +local _filetypes = nil +-- local auto_installed_servers = nil -P.capabilities = {} +local capabilities = {} -P.config = { +local config = { bashls = {}, clangd = {}, cmake = {}, @@ -23,9 +23,9 @@ P.config = { zls = {}, } -for server, _ in pairs(P.config) do - utils.try_require("lsp." .. server, package_name, function (mod) - P.config[server] = mod +for server, _ in pairs(config) do + utils.try_require("lsp." .. server, module_name, function (mod) + config[server] = mod end) end @@ -43,7 +43,7 @@ local function ca_rename() end end -function P._setup_diagnostics() +local function setup_diagnostics() -- https://github.com/neovim/nvim-lspconfig/wiki/UI-Customization#customizing-how-diagnostics-are-displayed vim.diagnostic.config({ underline = true, @@ -73,7 +73,7 @@ function P._setup_diagnostics() end end -function P.on_attach(client, bufnr) +local function on_attach(client, bufnr) -- Mappings. -- See `:help vim.lsp.*` for documentation on any of the below functions local opts = { buffer = bufnr, } @@ -146,20 +146,20 @@ function P.on_attach(client, bufnr) require("lsp-inlayhints").on_attach(client, bufnr, false) - vim.lsp.handlers['textDocument/hover'] = vim.lsp.with( + vim.lsp.handlers["textDocument/hover"] = vim.lsp.with( vim.lsp.handlers.hover, { - border = "single" + border = "single", } ) - vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with( + vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with( vim.lsp.handlers.signature_help, { - border = "single" + border = "single", } ) end -function P.reload_server_buf(name) - local server = P.config[name] +local function reload_server_buf(name) + local server = config[name] local ft_map = {} for _, ft in ipairs(server.lspconfig.filetypes) do ft_map[ft] = true @@ -180,101 +180,15 @@ function P.reload_server_buf(name) end end -function P.filetypes() - if not P._filetypes then - P._filetypes = {} - local unique = {} - for _, server in pairs(P.config) do - for _, ft in ipairs(server.lspconfig.filetypes) do - if not unique[ft] then - table.insert(P._filetypes, ft) - unique[ft] = true - end - end - end - end - return P._filetypes -end - -function P.language_servers() - if not P._language_servers then - P._language_servers = {} - for server, opts in pairs(P.config) do - if opts.enabled ~= true then - goto next_server - end - if opts.dependencies ~= nil then - local not_installed = {} - for _, dep in ipairs(opts.dependencies) do - if not utils.is_installed(dep) then - table.insert(not_installed, dep) - end - end - - if #not_installed > 0 then - utils.warn( - ("Disabling %s " - .. "because the following required package(s) " - .. "are not installed: %s") - :format( - server, - table.concat(not_installed, ", ") - ), - package_name - ) - opts.enabled = false - goto next_server - end - end - - if opts.py_module_deps ~= nil then - local not_installed = {} - for _, mod in ipairs(opts.py_module_deps) do - if not utils.python3_module_is_installed(mod) then - table.insert(not_installed, mod) - end - end - - if #not_installed > 0 then - utils.warn( - ("Disabling %s " - + "because the following required python3 " - + "module(s) are not installed: %s") - :format( - server, - table.concat(not_installed, ", ") - ), - package_name - ) - opts.enabled = false - goto next_server - end - end - - table.insert(P._language_servers, server) - - ::next_server:: - end - end - - return P._language_servers -end - -function P.setup_server(name) - local server = P.config[name] - - if not server or server.enabled ~= true then - return - end - - local ok, lspconfig = pcall(require, "lspconfig") +local function configure_server(name, server) + local ok, ret = pcall(require, "lspconfig") if not ok then - utils.err("Missing required plugin lspconfig", package_name) + utils.err("Missing required plugin lspconfig", module_name) return end + local lspconfig = ret - -- server.lspconfig.root_dir = function () return vim.fn.getcwd() end if server.root_pattern then server.lspconfig.root_dir = lspconfig.util.root_pattern( unpack(server.root_pattern) @@ -282,39 +196,113 @@ function P.setup_server(name) else server.lspconfig.root_dir = lspconfig.util.find_git_ancestor end - server.lspconfig.capabilities = P.capabilities + server.lspconfig.capabilities = capabilities server.lspconfig.on_attach = function (...) - local resp - ok, resp = pcall(P.on_attach, ...) + ok, ret = pcall(on_attach, ...) if not ok then utils.err( - ("Failed to load on_attach for %s:\n%s"):format(name, resp) + ("Failed to load on_attach for %s:\n%s"):format(name, ret), + module_name ) end end - if not pcall(lspconfig[name].setup, server.lspconfig) then - utils.err("Unknown LSP server for lspconfig: " .. name, package_name) + ok, ret = pcall(lspconfig[name].setup, server.lspconfig) + if not ok then + utils.err( + ("Failed to setup LSP server %s with lspconfig: %s"):format( + name, + ret + ), + module_name + ) return end - P.reload_server_buf(name) + reload_server_buf(name) end -function P.setup() - P._setup_diagnostics() +local function setup_server(name, server) + local registry = require("mason-registry") + local pkg_name - utils.try_require("cmp_nvim_lsp", package_name, function (mod) - P.capabilities = mod.default_capabilities() - end) + if server.mason then + pkg_name = server.mason.name + end - utils.try_require("mason-lspconfig", package_name, function (mod) - mod.setup_handlers({ - function (name) - P.setup_server(name) - end, - }) - end) + if (pkg_name and not registry.is_installed(pkg_name)) then + local pkg = registry.get_package(pkg_name) + local handle = pkg:install({ version = server.mason.version, }) + utils.info("Installing " .. pkg_name) + local err + handle:on("stderr", vim.schedule_wrap(function (msg) + err = (err or "") .. msg + end)) + handle:once("closed", vim.schedule_wrap(function () + if err then + utils.err(err, module_name) + end + + if pkg:is_installed() then + utils.info("Installation finished for " .. pkg_name) + configure_server(name, server) + else + utils.err("Installation failed for " .. pkg_name) + server.enable = false + end + end)) + else + if vim.fn.executable(server.lspconfig.cmd[1]) == 1 then + configure_server(name, server) + else + utils.info(name .. " not installed, disabling", module_name) + server.enable = false + end + end end -return P +local function register_server(name, server) + local augroup = vim.api.nvim_create_augroup("LSP-" .. name, {}) + vim.api.nvim_create_autocmd("FileType", { + once = true, + pattern = table.concat(server.lspconfig.filetypes, ","), + callback = vim.schedule_wrap(function () + setup_server(name, server) + vim.api.nvim_del_augroup_by_id(augroup) + end), + group = augroup, + }) +end + +function M.filetypes() + if not _filetypes then + _filetypes = {} + local unique = {} + for _, server in pairs(config) do + for _, ft in ipairs(server.lspconfig.filetypes) do + if not unique[ft] then + table.insert(_filetypes, ft) + unique[ft] = true + end + end + end + end + + return _filetypes +end + +function M.setup() + setup_diagnostics() + + utils.try_require("cmp_nvim_lsp", module_name, function (mod) + capabilities = mod.default_capabilities() + end) + + for name, server in pairs(config) do + if server.enable then + register_server(name, server) + end + end +end + +return M diff --git a/lua/lsp/bashls.lua b/lua/lsp/bashls.lua index 0ab6103..ae2b273 100644 --- a/lua/lsp/bashls.lua +++ b/lua/lsp/bashls.lua @@ -1,8 +1,12 @@ return { - enabled = true, + enable = true, dependencies = { "npm", }, + mason = { + name = "bash-language-server", + -- version = "", + }, lspconfig = { filetypes = { "sh", diff --git a/lua/lsp/clangd.lua b/lua/lsp/clangd.lua index 6116904..503f928 100644 --- a/lua/lsp/clangd.lua +++ b/lua/lsp/clangd.lua @@ -1,5 +1,9 @@ return { - enabled = true, + enable = true, + mason = { + name = "clangd", + -- version = "", + }, lspconfig = { filetypes = { "c", diff --git a/lua/lsp/cmake.lua b/lua/lsp/cmake.lua index a7eebc8..750285e 100644 --- a/lua/lsp/cmake.lua +++ b/lua/lsp/cmake.lua @@ -1,11 +1,15 @@ return { - enabled = true, + enable = true, dependencies = { "python3", }, py_module_deps = { "venv", }, + mason = { + name = "cmake-language-server", + -- version = "", + }, lspconfig = { filetypes = { "cmake", diff --git a/lua/lsp/diagnosticls.lua b/lua/lsp/diagnosticls.lua index 1f99595..ad28787 100644 --- a/lua/lsp/diagnosticls.lua +++ b/lua/lsp/diagnosticls.lua @@ -5,10 +5,14 @@ -- https://github.com/iamcco/coc-diagnostic/blob/master/src/config.ts return { - enabled = true, + enable = true, dependencies = { "npm", }, + mason = { + name = "diagnostic-languageserver", + -- version = "", + }, lspconfig = { filetypes = { "python", @@ -110,16 +114,16 @@ return { sh = { "shfmt", }, bash = { "shfmt", }, zsh = { "shfmt", }, - php = { "php_cs_fixer", }, + -- php = { "php_cs_fixer", }, }, formatters = { autopep8 = { command = "autopep8", args = { "--aggressive", - "-" + "-", }, - rootPatterns = {"Pipfile", "tox.ini", ".git"}, + rootPatterns = { "Pipfile", "tox.ini", ".git", }, isStdout = true, isStderr = false, ignoreExitCode = false, diff --git a/lua/lsp/gopls.lua b/lua/lsp/gopls.lua index 94273fb..b5875e0 100644 --- a/lua/lsp/gopls.lua +++ b/lua/lsp/gopls.lua @@ -1,7 +1,11 @@ -- spec: https://rust-analyzer.github.io/manual.html#configuration return { - enabled = true, + enable = true, + mason = { + name = "gopls", + -- version = "", + }, lspconfig = { filetypes = { "go", diff --git a/lua/lsp/intelephense.lua b/lua/lsp/intelephense.lua index 101fc70..0bcc294 100644 --- a/lua/lsp/intelephense.lua +++ b/lua/lsp/intelephense.lua @@ -3,7 +3,7 @@ -- https://github.com/bmewburn/vscode-intelephense/blob/master/package.json return { - enabled = true, + enable = true, dependencies = { "npm", }, @@ -12,6 +12,10 @@ return { "composer.lock", "vendor", }, + mason = { + name = "intelephense", + -- version = "", + }, lspconfig = { filetypes = { "php", diff --git a/lua/lsp/jedi_language_server.lua b/lua/lsp/jedi_language_server.lua index 6a0d56e..6d455aa 100644 --- a/lua/lsp/jedi_language_server.lua +++ b/lua/lsp/jedi_language_server.lua @@ -1,11 +1,15 @@ return { - enabled = true, + enable = true, dependencies = { "python3", }, py_module_deps = { "venv", }, + mason = { + name = "jedi-language-server", + -- version = "", + }, lspconfig = { filetypes = { "python", diff --git a/lua/lsp/lua_ls.lua b/lua/lsp/lua_ls.lua index 8102abd..2e48518 100644 --- a/lua/lsp/lua_ls.lua +++ b/lua/lsp/lua_ls.lua @@ -1,7 +1,11 @@ -- spec: https://luals.github.io/wiki/settings/ return { - enabled = true, + enable = true, + mason = { + name = "lua-language-server", + -- version = "", + }, lspconfig = { filetypes = { "lua", diff --git a/lua/lsp/rust_analyzer.lua b/lua/lsp/rust_analyzer.lua index d484ddf..a47a7a5 100644 --- a/lua/lsp/rust_analyzer.lua +++ b/lua/lsp/rust_analyzer.lua @@ -1,7 +1,11 @@ -- spec: https://rust-analyzer.github.io/manual.html#configuration return { - enabled = true, + enable = true, + mason = { + name = "rust-analyzer", + -- version = "", + }, lspconfig = { filetypes = { "rust", diff --git a/lua/lsp/zls.lua b/lua/lsp/zls.lua index 2fed08f..7e0ee8f 100644 --- a/lua/lsp/zls.lua +++ b/lua/lsp/zls.lua @@ -1,8 +1,14 @@ +-- spec: https://github.com/zigtools/zls#configuration-options + return { - enabled = true, + enable = true, dependencies = { "zig", }, + -- mason = { + -- name = "zls", + -- -- version = "", + -- }, lspconfig = { filetypes = { "zig", diff --git a/lua/plugins.lua b/lua/plugins.lua index 2ddf7b4..c39b3d1 100644 --- a/lua/plugins.lua +++ b/lua/plugins.lua @@ -8,6 +8,20 @@ local plugins = { name = "moonfly", config = require("plugins.moonfly"), }, + --[[ { + "catppuccin/nvim", + name = "catppuccin", + priority = 100, + lazy = false, + config = require("plugins.catppuccin"), + }, ]] + --[[ { + "navarasu/onedark.nvim", + priority = 1000, + lazy = false, + -- name = "moonfly", + config = require("plugins.onedark"), + }, ]] { "rcarriga/nvim-notify", priority = 900, @@ -60,12 +74,6 @@ local plugins = { lazy = true, event = "VimEnter", }, - { - "williamboman/mason-lspconfig.nvim", - config = require("plugins.mason_lspconfig"), - lazy = true, - event = "VimEnter", - }, { "neovim/nvim-lspconfig", config = require("lsp").setup, @@ -156,10 +164,6 @@ local plugins = { lazy = true, event = "VimEnter", }, - { - "RubixDev/mason-update-all", - config = require("plugins.mason_update_all"), - }, { "famiu/bufdelete.nvim", config = require("plugins.bufdelete"), diff --git a/lua/plugins/mason_lspconfig.lua b/lua/plugins/mason_lspconfig.lua deleted file mode 100644 index 456d174..0000000 --- a/lua/plugins/mason_lspconfig.lua +++ /dev/null @@ -1,12 +0,0 @@ --- https://github.com/williamboman/mason-lspconfig.nvim - -local function setup() - require("mason-lspconfig").setup({ - -- A list of servers to automatically install if they're not already installed. Example: { "rust_analyzer@nightly", "lua_ls" } - -- This setting has no relation with the `automatic_installation` setting. - ---@type string[] - ensure_installed = require("lsp").language_servers(), - }) -end - -return setup diff --git a/lua/plugins/mason_update_all.lua b/lua/plugins/mason_update_all.lua deleted file mode 100644 index c0711b9..0000000 --- a/lua/plugins/mason_update_all.lua +++ /dev/null @@ -1,7 +0,0 @@ --- https://github.com/RubixDev/mason-update-all - -local function setup() - require("mason-update-all").setup() -end - -return setup