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..43098c7a8 100644 --- a/README.md +++ b/README.md @@ -5,33 +5,32 @@ - [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 registers a hook with [`lspconfig`](https://github.com/neovim/nvim-lspconfig) that enhances the `PATH` environment, +allowing neovim's LSP client to locate the installed server executable1. 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 - some servers don't provide an executable, in which case the full command to spawn the server is provided instead + ## Screenshots | | | | @@ -83,6 +82,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 +102,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 +261,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 +277,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..811ce4cd9 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,12 @@ 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 {} +< + To view the UI for nvim-lsp-installer, run: > :LspInstallInfo @@ -79,30 +84,6 @@ buffer, simply just run: > 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! - - ============================================================================== COMMANDS *nvim-lsp-installer-commands* @@ -156,20 +137,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 +153,6 @@ Example: > } } }) - - lsp_installer.on_server_ready(function (server) - server:setup {} - end) < *nvim-lsp-installer-default-settings* @@ -200,12 +171,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 +213,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 +224,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 +327,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 +338,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,7 +347,7 @@ 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. diff --git a/lua/nvim-lsp-installer.lua b/lua/nvim-lsp-installer.lua index 62b20a1e2..f044fee58 100644 --- a/lua/nvim-lsp-installer.lua +++ b/lua/nvim-lsp-installer.lua @@ -14,6 +14,12 @@ local M = {} M.settings = settings.set +---@param config table +function M.setup(config) + settings.set(config) + 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/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..144db1a72 100644 --- a/tests/minimal_debug_init.lua +++ b/tests/minimal_debug_init.lua @@ -22,7 +22,7 @@ local function load_plugins() { "wbthomason/packer.nvim", "neovim/nvim-lspconfig", - "williamboman/nvim-lsp-installer", + { "williamboman/nvim-lsp-installer", branch = "feat/on_setup_hook" }, }, config = { package_root = package_root, @@ -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