diff --git a/README.md b/README.md index 61f1175..3d1599e 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,7 @@ when needed. ### Language servers Language servers are installed automatically to the nvim data directory -(`: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 +(`:echo stdpath('data') .. '/mason'`). The following are some noted requirements for the installations themselves: - **diagnostic-languageserver**: npm @@ -81,11 +77,9 @@ Some servers have additional runtime dependencies: - **bash-language-server**: shellcheck (optional, used for linting) -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`. +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`. ### 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 7b306d9..ecab754 100644 --- a/lua/lsp.lua +++ b/lua/lsp.lua @@ -1,14 +1,14 @@ -local module_name = "lsp" +local package_name = "lsp" local utils = require("utils") -local M = {} +local P = {} -local _filetypes = nil --- local auto_installed_servers = nil +P._filetypes = nil +P._language_servers = nil -local capabilities = {} +P.capabilities = {} -local config = { +P.config = { bashls = {}, clangd = {}, cmake = {}, @@ -23,9 +23,9 @@ local config = { zls = {}, } -for server, _ in pairs(config) do - utils.try_require("lsp." .. server, module_name, function (mod) - config[server] = mod +for server, _ in pairs(P.config) do + utils.try_require("lsp." .. server, package_name, function (mod) + P.config[server] = mod end) end @@ -43,7 +43,7 @@ local function ca_rename() end end -local function setup_diagnostics() +function P._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 @@ local function setup_diagnostics() end end -local function on_attach(client, bufnr) +function P.on_attach(client, bufnr) -- Mappings. -- See `:help vim.lsp.*` for documentation on any of the below functions local opts = { buffer = bufnr, } @@ -93,7 +93,27 @@ local function on_attach(client, bufnr) vim.keymap.set( { "n", "x", }, "lf", - vim.lsp.buf.format, + function () + if vim.bo.filetype ~= "php" then + return vim.lsp.buf.format() + end + + local dls = require("lsp.diagnosticls") + local formatters = dls.lspconfig.init_options.formatFiletypes.php + for _, fmt in ipairs(formatters) do + if fmt == "php_cs_fixer" then + ---@type table + local winview = vim.fn.winsaveview() + vim.cmd.write({ bang = true, }) + vim.lsp.buf.format() + vim.cmd.write({ bang = true, }) + vim.fn.winrestview(winview) + return + end + end + + return vim.lsp.buf.format() + end, opts ) @@ -126,20 +146,20 @@ local function 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 -local function reload_server_buf(name) - local server = config[name] +function P.reload_server_buf(name) + local server = P.config[name] local ft_map = {} for _, ft in ipairs(server.lspconfig.filetypes) do ft_map[ft] = true @@ -160,15 +180,101 @@ local function 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 -local function configure_server(name, server) - local ok, ret = pcall(require, "lspconfig") - if not ok then - utils.err("Missing required plugin lspconfig", module_name) + 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 lspconfig = ret + local ok, lspconfig = pcall(require, "lspconfig") + if not ok then + utils.err("Missing required plugin lspconfig", package_name) + return + end + + -- 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) @@ -176,113 +282,39 @@ local function configure_server(name, server) else server.lspconfig.root_dir = lspconfig.util.find_git_ancestor end - server.lspconfig.capabilities = capabilities + server.lspconfig.capabilities = P.capabilities server.lspconfig.on_attach = function (...) - ok, ret = pcall(on_attach, ...) + local resp + ok, resp = pcall(P.on_attach, ...) if not ok then utils.err( - ("Failed to load on_attach for %s:\n%s"):format(name, ret), - module_name + ("Failed to load on_attach for %s:\n%s"):format(name, resp) ) end end - 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 - ) + if not pcall(lspconfig[name].setup, server.lspconfig) then + utils.err("Unknown LSP server for lspconfig: " .. name, package_name) return end - reload_server_buf(name) + P.reload_server_buf(name) end -local function setup_server(name, server) - local registry = require("mason-registry") - local pkg_name +function P.setup() + P._setup_diagnostics() - if server.mason then - pkg_name = server.mason.name - 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 - -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() + utils.try_require("cmp_nvim_lsp", package_name, function (mod) + P.capabilities = mod.default_capabilities() end) - for name, server in pairs(config) do - if server.enable then - register_server(name, server) - end - end + utils.try_require("mason-lspconfig", package_name, function (mod) + mod.setup_handlers({ + function (name) + P.setup_server(name) + end, + }) + end) end -return M +return P diff --git a/lua/lsp/bashls.lua b/lua/lsp/bashls.lua index ae2b273..0ab6103 100644 --- a/lua/lsp/bashls.lua +++ b/lua/lsp/bashls.lua @@ -1,12 +1,8 @@ return { - enable = true, + enabled = 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 503f928..6116904 100644 --- a/lua/lsp/clangd.lua +++ b/lua/lsp/clangd.lua @@ -1,9 +1,5 @@ return { - enable = true, - mason = { - name = "clangd", - -- version = "", - }, + enabled = true, lspconfig = { filetypes = { "c", diff --git a/lua/lsp/cmake.lua b/lua/lsp/cmake.lua index 750285e..a7eebc8 100644 --- a/lua/lsp/cmake.lua +++ b/lua/lsp/cmake.lua @@ -1,15 +1,11 @@ return { - enable = true, + enabled = 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 ad28787..1f99595 100644 --- a/lua/lsp/diagnosticls.lua +++ b/lua/lsp/diagnosticls.lua @@ -5,14 +5,10 @@ -- https://github.com/iamcco/coc-diagnostic/blob/master/src/config.ts return { - enable = true, + enabled = true, dependencies = { "npm", }, - mason = { - name = "diagnostic-languageserver", - -- version = "", - }, lspconfig = { filetypes = { "python", @@ -114,16 +110,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 b5875e0..94273fb 100644 --- a/lua/lsp/gopls.lua +++ b/lua/lsp/gopls.lua @@ -1,11 +1,7 @@ -- spec: https://rust-analyzer.github.io/manual.html#configuration return { - enable = true, - mason = { - name = "gopls", - -- version = "", - }, + enabled = true, lspconfig = { filetypes = { "go", diff --git a/lua/lsp/intelephense.lua b/lua/lsp/intelephense.lua index 0bcc294..101fc70 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 { - enable = true, + enabled = true, dependencies = { "npm", }, @@ -12,10 +12,6 @@ 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 6d455aa..6a0d56e 100644 --- a/lua/lsp/jedi_language_server.lua +++ b/lua/lsp/jedi_language_server.lua @@ -1,15 +1,11 @@ return { - enable = true, + enabled = 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 2e48518..8102abd 100644 --- a/lua/lsp/lua_ls.lua +++ b/lua/lsp/lua_ls.lua @@ -1,11 +1,7 @@ -- spec: https://luals.github.io/wiki/settings/ return { - enable = true, - mason = { - name = "lua-language-server", - -- version = "", - }, + enabled = true, lspconfig = { filetypes = { "lua", diff --git a/lua/lsp/rust_analyzer.lua b/lua/lsp/rust_analyzer.lua index a47a7a5..d484ddf 100644 --- a/lua/lsp/rust_analyzer.lua +++ b/lua/lsp/rust_analyzer.lua @@ -1,11 +1,7 @@ -- spec: https://rust-analyzer.github.io/manual.html#configuration return { - enable = true, - mason = { - name = "rust-analyzer", - -- version = "", - }, + enabled = true, lspconfig = { filetypes = { "rust", diff --git a/lua/lsp/zls.lua b/lua/lsp/zls.lua index 7e0ee8f..2fed08f 100644 --- a/lua/lsp/zls.lua +++ b/lua/lsp/zls.lua @@ -1,14 +1,8 @@ --- spec: https://github.com/zigtools/zls#configuration-options - return { - enable = true, + enabled = true, dependencies = { "zig", }, - -- mason = { - -- name = "zls", - -- -- version = "", - -- }, lspconfig = { filetypes = { "zig", diff --git a/lua/plugins.lua b/lua/plugins.lua index c39b3d1..2ddf7b4 100644 --- a/lua/plugins.lua +++ b/lua/plugins.lua @@ -8,20 +8,6 @@ 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, @@ -74,6 +60,12 @@ 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, @@ -164,6 +156,10 @@ 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 new file mode 100644 index 0000000..456d174 --- /dev/null +++ b/lua/plugins/mason_lspconfig.lua @@ -0,0 +1,12 @@ +-- 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 new file mode 100644 index 0000000..c0711b9 --- /dev/null +++ b/lua/plugins/mason_update_all.lua @@ -0,0 +1,7 @@ +-- https://github.com/RubixDev/mason-update-all + +local function setup() + require("mason-update-all").setup() +end + +return setup