diff --git a/Makefile b/Makefile index 4a7fc81c6..cd43a116d 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ NVIM_HEADLESS:=nvim --headless --noplugin -u tests/minimal_init.vim dependencies: git clone --depth 1 https://github.com/nvim-lua/plenary.nvim dependencies/pack/vendor/start/plenary.nvim + git clone --depth 1 https://github.com/neovim/nvim-lspconfig dependencies/pack/vendor/start/nvim-lspconfig .PHONY: clean_dependencies clean_dependencies: diff --git a/README.md b/README.md index 82454362f..e8d54d453 100644 --- a/README.md +++ b/README.md @@ -5,33 +5,34 @@ - [About](#about) - [Screenshots](#screenshots) - [Installation](#installation) - - [Packer](#packer) - - [vim-plug](#vim-plug) - [Usage](#usage) - - [Commands](#commands) - [Setup](#setup) + - [Commands](#commands) - [Configuration](#configuration) - [Available LSPs](#available-lsps) - [Custom servers](#custom-servers) - [Logo](#logo) -- [Roadmap](#roadmap) - [Default configuration](#default-configuration) ## About -Neovim plugin that allows you to seamlessly install LSP servers locally (inside `:echo stdpath("data")`). +Neovim plugin that allows you to manage LSP servers (servers are installed inside `:echo stdpath("data")` by default). +It works in tandem with [`lspconfig`](https://github.com/neovim/nvim-lspconfig)1 by registering a hook that +enhances the `PATH` environment variable, allowing neovim's LSP client to locate the installed server executable.2 On top of just providing commands for installing & uninstalling LSP servers, it: - provides a graphical UI -- is optimized for blazing fast startup times -- provides the ability to check for new server versions +- provides the ability to check for, and upgrade to, new server versions through a single interface - supports installing custom versions of LSP servers (for example `:LspInstall rust_analyzer@nightly`) - relaxes the minimum requirements by attempting multiple different utilities (for example, only one of `wget`, `curl`, or `Invoke-WebRequest` is required for HTTP requests) -- allows you to install and setup servers without having to restart neovim - hosts [a suite of system tests](https://github.com/williamboman/nvim-lspconfig-test) for all supported servers - has full support for Windows +1 - while lspconfig is the main target, this plugin may also be used for other use cases +
+2 - some servers don't provide an executable, in which case the full command to spawn the server is provided instead + ## Screenshots | | | | @@ -83,6 +84,17 @@ Plug 'williamboman/nvim-lsp-installer' ## Usage +### Setup + +In order for nvim-lsp-installer to register the necessary hooks at the right moment, **make sure you call the `.setup()` +function before you set up any servers with `lspconfig`**! + +```lua +require("nvim-lsp-installer").setup {} +``` + +Refer to the [Configuration](#configuration) section for information about which settings are available. + ### Commands - `:LspInstallInfo` - opens a graphical overview of your language servers @@ -92,54 +104,16 @@ Plug 'williamboman/nvim-lsp-installer' - `:LspInstallLog` - opens the log file in a new tab window - `:LspPrintInstalled` - prints all installed language servers -### Setup - -The recommended way of setting up your installed servers is to do it through nvim-lsp-installer. -By doing so, nvim-lsp-installer will make sure to inject any necessary properties before calling lspconfig's setup -function for you. You may find a minimal example below. To see how you can override the default settings for a server, -refer to the [Wiki][overriding-default-settings]. - -Make sure you don't also set up your servers directly via lspconfig (e.g. `require("lspconfig").clangd.setup {}`), as -this will cause servers to be set up twice! - -[overriding-default-settings]: https://github.com/williamboman/nvim-lsp-installer/wiki/Advanced-Configuration#overriding-the-default-lsp-server-options - -```lua -local lsp_installer = require("nvim-lsp-installer") - --- Register a handler that will be called for each installed server when it's ready (i.e. when installation is finished --- or if the server is already installed). -lsp_installer.on_server_ready(function(server) - local opts = {} - - -- (optional) Customize the options passed to the server - -- if server.name == "tsserver" then - -- opts.root_dir = function() ... end - -- end - - -- This setup() function will take the provided server configuration and decorate it with the necessary properties - -- before passing it onwards to lspconfig. - -- Refer to https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md - server:setup(opts) -end) -``` - -For more advanced use cases you may also interact with more APIs nvim-lsp-installer has to offer, refer to `:help nvim-lsp-installer` for more docs. - ### Configuration -You can configure certain behavior of nvim-lsp-installer by calling the `.settings()` function. - -_Make sure to provide your settings before any other interactions with nvim-lsp-installer!_ +You may optionally configure certain behavior of nvim-lsp-installer when calling the `.setup()` function. Refer to the [default configuration](#default-configuration) for all available settings. Example: ```lua -local lsp_installer = require("nvim-lsp-installer") - -lsp_installer.settings({ +require("nvim-lsp-installer").setup({ ui = { icons = { server_installed = "✓", @@ -289,10 +263,6 @@ You can create your own installers by using the same APIs nvim-lsp-installer its Illustrations in the logo are derived from [@Kaligule](https://schauderbasis.de/)'s "Robots" collection. -## Roadmap - -- Command (and corresponding Lua API) to update outdated servers (e.g., `:LspUpdateAll`) - ## Default configuration ```lua @@ -309,12 +279,16 @@ local DEFAULT_SETTINGS = { keymaps = { -- Keymap to expand a server in the UI toggle_server_expand = "", - -- Keymap to install a server + -- Keymap to install the server under the current cursor position install_server = "i", - -- Keymap to reinstall/update a server + -- Keymap to reinstall/update the server under the current cursor position update_server = "u", + -- Keymap to check for new version for the server under the current cursor position + check_server_version = "c", -- Keymap to update all installed servers update_all_servers = "U", + -- Keymap to check which installed servers are outdated + check_outdated_servers = "C", -- Keymap to uninstall a server uninstall_server = "X", }, diff --git a/doc/nvim-lsp-installer.txt b/doc/nvim-lsp-installer.txt index 863ad2c93..9f755a761 100644 --- a/doc/nvim-lsp-installer.txt +++ b/doc/nvim-lsp-installer.txt @@ -20,7 +20,6 @@ it: - relaxes the minimum requirements by attempting multiple different utilities (for example, only one of `wget`, `curl`, or `Invoke-WebRequest` is required for HTTP requests) -- allows you to install and setup servers without having to restart neovim - hosts a suite of system tests for all supported servers - has full support for Windows @@ -49,6 +48,25 @@ https://github.com/williamboman/nvim-lsp-installer/blob/main/CUSTOM_SERVERS.md. ============================================================================== QUICK START *nvim-lsp-installer-quickstart* +The only thing needed to get started with nvim-lsp-installer is to make sure +to call the `setup()` function _before_ you set up any servers: > + + require("nvim-lsp-installer").setup {} +< + +Next, in your initialization files |init.lua|, setup the servers you want to use. +Refer to |lspconfig| for more information! For example: > + + local lspconfig = require("lspconfig") + + local function on_attach(client, bufnr) + -- set up buffer keymaps, etc. + end + + lspconfig.sumneko_lua.setup { on_attach = on_attach } + lspconfig.tsserver.setup { on_attach = on_attach } +< + To view the UI for nvim-lsp-installer, run: > :LspInstallInfo @@ -76,32 +94,7 @@ buffer, simply just run: > :LspInstall < -Please refer to each server's own release page to find which versions are -available. - -Then, somewhere in your initialization script (see `:h init.lua`): > - - -- Register a handler that will be called for each installed server when it's ready (i.e. when installation is finished - -- or if the server is already installed). - lsp_installer.on_server_ready(function(server) - local opts = {} - - -- (optional) Customize the options passed to the server - -- if server.name == "tsserver" then - -- opts.root_dir = function() ... end - -- end - - -- This setup() function will take the provided server configuration and decorate it with the necessary properties - -- before passing it onwards to lspconfig. - -- Refer to https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md - server:setup(opts) - end) -< - -Make sure you don't also set up your servers directly via lspconfig (e.g. -`require("lspconfig").clangd.setup {}`), as this will cause servers to be set -up twice! - +Please refer to each server's own release page to find which versions are available. ============================================================================== COMMANDS *nvim-lsp-installer-commands* @@ -156,20 +149,14 @@ Prints all installed language servers. ============================================================================== SETTINGS *nvim-lsp-installer-settings* -You can configure certain behavior of nvim-lsp-installer by calling the -`.settings()` function. - -Make sure to provide your settings before any other interactions with -nvim-lsp-installer! +You can configure certain behavior of nvim-lsp-installer when calling the +`.setup()` function. Refer to the |nvim-lsp-installer-default-settings| for all available settings. Example: > - local lsp_installer = require("nvim-lsp-installer") - - -- Provide settings first! - lsp_installer.settings({ + require("nvim-lsp-installer").setup({ ui = { icons = { server_installed = "✓", @@ -178,10 +165,6 @@ Example: > } } }) - - lsp_installer.on_server_ready(function (server) - server:setup {} - end) < *nvim-lsp-installer-default-settings* @@ -200,12 +183,16 @@ The following settings are applied by default. > keymaps = { -- Keymap to expand a server in the UI toggle_server_expand = "", - -- Keymap to install a server + -- Keymap to install the server under the current cursor position install_server = "i", - -- Keymap to reinstall/update a server + -- Keymap to reinstall/update the server under the current cursor position update_server = "u", + -- Keymap to check for new version for the server under the current cursor position + check_server_version = "c", -- Keymap to update all installed servers update_all_servers = "U", + -- Keymap to check which installed servers are outdated + check_outdated_servers = "C", -- Keymap to uninstall a server uninstall_server = "X", }, @@ -238,7 +225,9 @@ DEBUGGING *nvim-lsp-installer-debugging* To help with debugging issues with installing/uninstall servers, please make sure to set nvim-lsp-installer's log level to DEBUG or TRACE, like so: > - :lua require("nvim-lsp-installer").settings({ log_level = vim.log.levels.DEBUG }) + require("nvim-lsp-installer").setup { + log_level = vim.log.levels.DEBUG + } < You may find the logs by entering the command `:LspInstallLog`. Providing the @@ -247,6 +236,11 @@ contents of this file when reporting an issue will help tremendously. ============================================================================== Lua module: nvim-lsp-installer + *nvim-lsp-installer.setup()* +setup({config}) + Sets up nvim-lsp-installer with the provided {config} (see + |nvim-lsp-installer-settings|). + *nvim-lsp-installer.install()* install({server_name}) Installs the provided {server_name}. If {server_name} is already @@ -345,7 +339,7 @@ class: Server servers. Methods: ~ - - setup({opts}) + - setup({opts}) *DEPRECATED - setup servers directly via lspconfig instead* Sets up the language server and attaches all open buffers. See: @@ -356,7 +350,7 @@ class: Server {opts} (table) The lspconfig server configuration. See https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md - - setup_lsp({opts}) + - setup_lsp({opts}) *DEPRECATED - setup servers directly via lspconfig instead* Sets up the language server via lspconfig. This function has the same signature as the setup function in nvim-lspconfig. @@ -365,13 +359,16 @@ class: Server {opts} (table) The lspconfig server configuration. See https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md - - attach_buffers() + - attach_buffers() *DEPRECATED - setup servers directly via lspconfig instead* Attaches this server to all current open buffers with a 'filetype' that matches the server's configured filetypes. - get_default_options() Returns a deep copy of the default options provided to lspconfig in the setup({opts}) method. + Note: These only include the options provided by + nvim-lsp-installer, and not the default options provided + by lspconfig. - on_ready({handler}) Registers the provided {handler} to be called when the diff --git a/lua/nvim-lsp-installer.lua b/lua/nvim-lsp-installer.lua index 62b20a1e2..a910440e8 100644 --- a/lua/nvim-lsp-installer.lua +++ b/lua/nvim-lsp-installer.lua @@ -14,6 +14,15 @@ local M = {} M.settings = settings.set +---@param config table +function M.setup(config) + if config then + settings.set(config) + end + settings.uses_new_setup = true + require("nvim-lsp-installer.middleware").register_lspconfig_hook() +end + M.info_window = { ---Opens the status window. open = function() diff --git a/lua/nvim-lsp-installer/middleware.lua b/lua/nvim-lsp-installer/middleware.lua new file mode 100644 index 000000000..3e01b976a --- /dev/null +++ b/lua/nvim-lsp-installer/middleware.lua @@ -0,0 +1,32 @@ +local util = require "lspconfig.util" +local servers = require "nvim-lsp-installer.servers" + +local M = {} + +---@param t1 table +---@param t2 table +local function merge_in_place(t1, t2) + for k, v in pairs(t2) do + if type(v) == "table" then + if type(t1[k]) == "table" and not vim.tbl_islist(t1[k]) then + merge_in_place(t1[k], v) + else + t1[k] = v + end + else + t1[k] = v + end + end + return t1 +end + +function M.register_lspconfig_hook() + util.on_setup = util.add_hook_before(util.on_setup, function(config) + local ok, server = servers.get_server(config.name) + if ok then + merge_in_place(config, server._default_options) + end + end) +end + +return M diff --git a/lua/nvim-lsp-installer/server.lua b/lua/nvim-lsp-installer/server.lua index de6481a4b..66d9655f1 100644 --- a/lua/nvim-lsp-installer/server.lua +++ b/lua/nvim-lsp-installer/server.lua @@ -71,6 +71,10 @@ end ---Sets up the language server and attaches all open buffers. ---@param opts table @The lspconfig server configuration. function M.Server:setup(opts) + assert( + not settings.uses_new_setup, + "Please set up servers directly via lspconfig instead of going through nvim-lsp-installer (this method is now deprecated)! Refer to :h nvim-lsp-installer-quickstart for more information." + ) self:setup_lsp(opts) if not (opts.autostart == false) then self:attach_buffers() diff --git a/lua/nvim-lsp-installer/servers/init.lua b/lua/nvim-lsp-installer/servers/init.lua index efca1dd52..9af4c1bb8 100644 --- a/lua/nvim-lsp-installer/servers/init.lua +++ b/lua/nvim-lsp-installer/servers/init.lua @@ -2,6 +2,7 @@ local Data = require "nvim-lsp-installer.data" local path = require "nvim-lsp-installer.path" local fs = require "nvim-lsp-installer.fs" local settings = require "nvim-lsp-installer.settings" +local log = require "nvim-lsp-installer.log" local M = {} @@ -180,15 +181,22 @@ end ---@param server_name string ---@return string local function get_server_install_dir(server_name) - return INSTALL_DIRS[server_name] or server_name + log.fmt_trace("Getting server installation dirname. uses_new_setup=%s", settings.uses_new_setup) + if settings.uses_new_setup then + return server_name + else + return INSTALL_DIRS[server_name] or server_name + end end function M.get_server_install_path(dirname) + log.trace("Getting server installation path", settings.current.install_root_dir, dirname) return path.concat { settings.current.install_root_dir, dirname } end ---@param server_name string function M.is_server_installed(server_name) + log.trace("Checking if server is installed", server_name) local scanned_server_dirs = scan_server_roots() local dirname = get_server_install_dir(server_name) return scanned_server_dirs[dirname] or false @@ -213,6 +221,7 @@ function M.get_server(server_name) local ok, server_factory = pcall(require, ("nvim-lsp-installer.servers.%s"):format(server_name)) if ok then + log.trace("Initializing core server", server_name) INITIALIZED_SERVERS[server_name] = server_factory( server_name, M.get_server_install_path(get_server_install_dir(server_name)) diff --git a/lua/nvim-lsp-installer/settings.lua b/lua/nvim-lsp-installer/settings.lua index ad875b728..87499354a 100644 --- a/lua/nvim-lsp-installer/settings.lua +++ b/lua/nvim-lsp-installer/settings.lua @@ -59,4 +59,8 @@ function M.set(opts) M.current = vim.tbl_deep_extend("force", M.current, opts) end +-- Whether the new .setup() function has been called. +-- This will temporarily be used as a flag to toggle certain behavior. +M.uses_new_setup = false + return M diff --git a/tests/middleware_spec.lua b/tests/middleware_spec.lua new file mode 100644 index 000000000..5c629ee6d --- /dev/null +++ b/tests/middleware_spec.lua @@ -0,0 +1,39 @@ +local util = require "lspconfig.util" +local servers = require "nvim-lsp-installer.servers" +local middleware = require "nvim-lsp-installer.middleware" + +describe("middleware", function() + it("should register on_setup hook with lspconfig", function() + -- 1. setup dummy server + local default_options = { + cmd = { "dummy-lsp" }, + cmd_env = { PATH = "/keep/my/path/out/your/f/mouth" }, + } + local server = ServerGenerator { + name = "dummy", + default_options = default_options, + } + servers.register(server) + + -- 2. register hook + middleware.register_lspconfig_hook() + + -- 3. call lspconfig hook + local config = { + name = "dummy", + cmd = { "should", "be", "overwritten" }, + custom = "setting", + cmd_env = { SOME_DEFAULT_ENV = "important" }, + } + util.on_setup(config) + assert.are.same({ + cmd = { "dummy-lsp" }, + name = "dummy", + custom = "setting", + cmd_env = { + PATH = "/keep/my/path/out/your/f/mouth", + SOME_DEFAULT_ENV = "important", + }, + }, config) + end) +end) diff --git a/tests/minimal_debug_init.lua b/tests/minimal_debug_init.lua index cd42ed246..f090b3535 100644 --- a/tests/minimal_debug_init.lua +++ b/tests/minimal_debug_init.lua @@ -32,25 +32,21 @@ local function load_plugins() end function _G.load_config() - -- ================================================== - -- ======= MODIFY YOUR CONFIG HERE, IF NEEDED ======= - -- ================================================== - local lsp_installer = require "nvim-lsp-installer" + local lspconfig = require "lspconfig" local function on_attach(client, bufnr) vim.api.nvim_buf_set_option(bufnr, "omnifunc", "v:lua.vim.lsp.omnifunc") end - require("nvim-lsp-installer").settings { + require("nvim-lsp-installer").setup { log = vim.log.levels.DEBUG, } - lsp_installer.on_server_ready(function(server) - server:setup { - on_attach = on_attach, - } - end) -- ================================================== + -- ========= SETUP RELEVANT SERVER(S) HERE! ========= + -- ================================================== + -- + -- lspconfig.sumneko_lua.setup { on_attach = on_attach } end if vim.fn.isdirectory(install_path) == 0 then