diff options
| author | Ashkan Kiani <ashkan.k.kiani@gmail.com> | 2019-11-15 17:26:22 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-11-15 17:26:22 -0800 |
| commit | 5686a90890105e6271307e86b472f729af1cc4f8 (patch) | |
| tree | 7d691ac0e7ea2feb58c5862dd52a1c8bfbef6fb4 | |
| parent | [docgen] Update README.md (diff) | |
| download | nvim-lspconfig-5686a90890105e6271307e86b472f729af1cc4f8.tar nvim-lspconfig-5686a90890105e6271307e86b472f729af1cc4f8.tar.gz nvim-lspconfig-5686a90890105e6271307e86b472f729af1cc4f8.tar.bz2 nvim-lspconfig-5686a90890105e6271307e86b472f729af1cc4f8.tar.lz nvim-lspconfig-5686a90890105e6271307e86b472f729af1cc4f8.tar.xz nvim-lspconfig-5686a90890105e6271307e86b472f729af1cc4f8.tar.zst nvim-lspconfig-5686a90890105e6271307e86b472f729af1cc4f8.zip | |
Redo installation. (#17)
* Redo installation.
Servers which want to be auto installed should specify
skeleton[name].install()
and it will be automatically added to the list of installable servers.
- Add :LspInstall and :LspInstallInfo
- Auto generate docs for servers with .install() available.
- Add util.npm_installer
- Refactor utf8 capabilities common config into a single function
- Add contribution notes.
- Also expose util.base_install_dir for other installers potentially
- Fix tsserver's arguments and add javascript filetypes
| -rw-r--r-- | CONTRIBUTING.md | 15 | ||||
| -rw-r--r-- | README.md | 76 | ||||
| -rw-r--r-- | README_preamble.md | 36 | ||||
| -rw-r--r-- | autoload/nvim_lsp.vim | 9 | ||||
| -rw-r--r-- | lua/nvim_lsp.lua | 68 | ||||
| -rw-r--r-- | lua/nvim_lsp/bash.lua | 28 | ||||
| -rw-r--r-- | lua/nvim_lsp/bashls.lua | 49 | ||||
| -rw-r--r-- | lua/nvim_lsp/clangd.lua | 11 | ||||
| -rw-r--r-- | lua/nvim_lsp/elmls.lua | 112 | ||||
| -rw-r--r-- | lua/nvim_lsp/tsserver.lua | 97 | ||||
| -rw-r--r-- | lua/nvim_lsp/util.lua | 102 | ||||
| -rw-r--r-- | plugin/nvim_lsp.vim | 11 | ||||
| -rw-r--r-- | scripts/docgen.lua | 14 |
13 files changed, 371 insertions, 257 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d46017f7..1495e8af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,3 +84,18 @@ After you create a skeleton, you have to `require 'nvim_lsp/SERVER_NAME'` in `lua/nvim_lsp.lua`, and that's it. Generate docs and you're done. + +# Supporting installation + +If a skeleton has the functions `.install()` and `.install_info()` available, then +it will be picked up by `LspInstall` and `LspInstallInfo`. + +`function install()` is the signature and it is expected that it will create +any data in `util.base_install_dir/{server_name}`. + +`function install_info()` should return a table with at least `is_installed` +which indicates the current status of installation (if it is installed by us). +It can contain any other additional data that the user may find useful. + +The helper function `util.npm_installer` can be used for lsps which are installed +with `npm`. See `elmls.lua` or `tsserver.lua` or `bashls.lua` for example usage. @@ -31,6 +31,7 @@ them are good references. ## Progress Implemented: +- [bashls](https://github.com/neovim/nvim-lsp#bashls) - [clangd](https://github.com/neovim/nvim-lsp#clangd) - [elmls](https://github.com/neovim/nvim-lsp#elmls) - [gopls](https://github.com/neovim/nvim-lsp#gopls) (has some errors) @@ -50,7 +51,33 @@ In progress: `Plug 'neovim/nvim-lsp'` -## Use +## Usage + +Servers configurations can be set up with a "setup function." These are +functions to set up servers more easily with some server specific defaults and +more server specific things like commands or different diagnostics. + +The "setup functions" are `call nvim_lsp#setup({name}, {config})` from vim and +`nvim_lsp[name].setup(config)` from Lua. + +Servers may define extra functions on the `nvim_lsp.SERVER` table, e.g. +`nvim_lsp.texlab.buf_build({bufnr})`. + +### Auto Installation + +Many servers can be automatically installed with the `:LspInstall` +command. Detailed installation info can be found +with the `:LspInstallInfo` command, which optionally accepts a specific server name. + +For example: +```vim +LspInstall elmls +silent LspInstall elmls " useful if you want to autoinstall in init.vim +LspInstallInfo +LspInstallInfo elmls +``` + +### Example From vim: ```vim @@ -82,12 +109,7 @@ nvim_lsp.gopls.setup { require 'nvim_lsp'.texlab.buf_build(0) ``` -These are functions to set up servers more easily with some server specific -defaults and more server specific things like commands or different -diagnostics. - -Servers may define extra functions on the `nvim_lsp.SERVER` table, e.g. -`nvim_lsp.texlab.buf_build({bufnr})`. +### Setup function details The main setup signature will be: @@ -153,14 +175,16 @@ nvim_lsp.SERVER.setup({config}) ``` # LSP Implementations -## bash +## bashls For install instruction visit: https://github.com/mads-hartmann/bash-language-server#installation +Can be installed in neovim with `:LspInstall bashls` + ```lua -nvim_lsp.bash.setup({config}) -nvim_lsp#setup("bash", {config}) +nvim_lsp.bashls.setup({config}) +nvim_lsp#setup("bashls", {config}) Default Values: cmd = { "bash-language-server", "start" } @@ -176,6 +200,7 @@ https://clang.llvm.org/extra/clangd/Installation.html clangd relies on a [JSON compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html) specified as compile_commands.json or, for simpler projects, a compile_flags.txt. + ```lua nvim_lsp.clangd.setup({config}) nvim_lsp#setup("clangd", {config}) @@ -193,29 +218,17 @@ nvim_lsp#setup("clangd", {config}) https://github.com/elm-tooling/elm-language-server#installation -You can install elmls automatically to the path at - `stdpath("cache")/nvim_lsp/elmls` -by using the function `nvim_lsp.elmls.install()` or the command `:ElmlsInstall`. - -This will only install if it can't find `elm-language-server` and if it hasn't -been installed before by neovim. - -You can see installation info via `:ElmlsInstallInfo` or via -`nvim_lsp.elmls.get_install_info()`. This will let you know if it is installed. - If you don't want to use neovim to install it, then you can use: ```sh npm install -g elm elm-test elm-format @elm-tooling/elm-language-server ``` +Can be installed in neovim with `:LspInstall elmls` + ```lua nvim_lsp.elmls.setup({config}) nvim_lsp#setup("elmls", {config}) - Commands: - - ElmlsInstall: Install elmls and its dependencies to stdpath("cache")/nvim_lsp/elmls - - ElmlsInstallInfo: Print installation info for `elmls` - Default Values: capabilities = default capabilities, with offsetEncoding utf-8 cmd = { "elm-language-server" } @@ -237,6 +250,7 @@ https://github.com/golang/tools/tree/master/gopls Google's lsp server for golang. + ```lua nvim_lsp.gopls.setup({config}) nvim_lsp#setup("gopls", {config}) @@ -290,6 +304,7 @@ settings = { }; ``` + ```lua nvim_lsp.pyls.setup({config}) nvim_lsp#setup("pyls", {config}) @@ -309,6 +324,7 @@ A completion engine built from scratch for (La)TeX. See https://texlab.netlify.com/docs/reference/configuration for configuration options. + ```lua nvim_lsp.texlab.setup({config}) nvim_lsp#setup("texlab", {config}) @@ -347,23 +363,21 @@ nvim_lsp#setup("texlab", {config}) https://github.com/theia-ide/typescript-language-server -typescript-language-server can be installed via `:TsServerInstall` or by yourself with `npm`: +`typescript-language-server` can be installed via `:LspInstall tsserver` or by yourself with `npm`: ```sh npm install -g typescript-language-server ``` +Can be installed in neovim with `:LspInstall tsserver` + ```lua nvim_lsp.tsserver.setup({config}) nvim_lsp#setup("tsserver", {config}) - Commands: - - TsServerInstall: Install typescript-language-server and its dependencies to stdpath("cache")/nvim_lsp/typescript-language-server - - TsServerInstallInfo: Print installation infor for `typescript-language-server` - Default Values: capabilities = default capabilities, with offsetEncoding utf-8 - cmd = { "typescript-language-server" } - filetypes = { "typescript", "typescriptreact", "typescript.tsx" } + cmd = { "typescript-language-server", "--stdio" } + filetypes = { "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx" } log_level = 2 on_init = function to handle changing offsetEncoding root_dir = root_pattern("package.json") diff --git a/README_preamble.md b/README_preamble.md index f19a73ba..8c994acb 100644 --- a/README_preamble.md +++ b/README_preamble.md @@ -31,6 +31,7 @@ them are good references. ## Progress Implemented: +- [bashls](https://github.com/neovim/nvim-lsp#bashls) - [clangd](https://github.com/neovim/nvim-lsp#clangd) - [elmls](https://github.com/neovim/nvim-lsp#elmls) - [gopls](https://github.com/neovim/nvim-lsp#gopls) (has some errors) @@ -50,7 +51,33 @@ In progress: `Plug 'neovim/nvim-lsp'` -## Use +## Usage + +Servers configurations can be set up with a "setup function." These are +functions to set up servers more easily with some server specific defaults and +more server specific things like commands or different diagnostics. + +The "setup functions" are `call nvim_lsp#setup({name}, {config})` from vim and +`nvim_lsp[name].setup(config)` from Lua. + +Servers may define extra functions on the `nvim_lsp.SERVER` table, e.g. +`nvim_lsp.texlab.buf_build({bufnr})`. + +### Auto Installation + +Many servers can be automatically installed with the `:LspInstall` +command. Detailed installation info can be found +with the `:LspInstallInfo` command, which optionally accepts a specific server name. + +For example: +```vim +LspInstall elmls +silent LspInstall elmls " useful if you want to autoinstall in init.vim +LspInstallInfo +LspInstallInfo elmls +``` + +### Example From vim: ```vim @@ -82,12 +109,7 @@ nvim_lsp.gopls.setup { require 'nvim_lsp'.texlab.buf_build(0) ``` -These are functions to set up servers more easily with some server specific -defaults and more server specific things like commands or different -diagnostics. - -Servers may define extra functions on the `nvim_lsp.SERVER` table, e.g. -`nvim_lsp.texlab.buf_build({bufnr})`. +### Setup function details The main setup signature will be: diff --git a/autoload/nvim_lsp.vim b/autoload/nvim_lsp.vim index 61bea51b..ec36fe2d 100644 --- a/autoload/nvim_lsp.vim +++ b/autoload/nvim_lsp.vim @@ -1,12 +1,3 @@ function! nvim_lsp#setup(name, config) return luaeval("require'nvim_lsp'[_A[1]].setup(_A[2])", [a:name, a:config]) endfunction - -" function! nvim_lsp#texlab(config) -" call nvim_lsp#setup("texlab", a:config) -" endfunction - -" function! nvim_lsp#gopls(config) -" call nvim_lsp#setup("gopls", a:config) -" endfunction - diff --git a/lua/nvim_lsp.lua b/lua/nvim_lsp.lua index 550d56dd..629b6b0a 100644 --- a/lua/nvim_lsp.lua +++ b/lua/nvim_lsp.lua @@ -1,5 +1,5 @@ local skeleton = require 'nvim_lsp/skeleton' -require 'nvim_lsp/bash' +require 'nvim_lsp/bashls' require 'nvim_lsp/clangd' require 'nvim_lsp/elmls' require 'nvim_lsp/gopls' @@ -11,6 +11,72 @@ local M = { util = require 'nvim_lsp/util'; } +function M.available_servers() + return vim.tbl_keys(skeleton) +end + +function M.installable_servers() + local res = {} + for k, v in pairs(skeleton) do + if v.install then table.insert(res, k) end + end + return res +end + +M._root = {} +-- Called from plugin/nvim_lsp.vim because it requires knowing that the last +-- script in scriptnames to be executed is nvim_lsp. +function M._root._setup() + local snr = tonumber(table.remove(vim.split(vim.fn.execute("scriptnames"), '\n')):match("^%s*(%d+)")) + local function sid(s) + return string.format("<SNR>%d_%s", snr, s) + end + + M._root.commands = { + LspInstall = { + function(name) + local template = skeleton[name] + if not template then + return print("Invalid server name:", name) + end + if not template.install then + return print(name, "can't be automatically installed (yet)") + end + if template.install_info().is_installed then + return print(name, "is already installed") + end + template.install() + end; + "-nargs=1"; + "-complete=custom,"..sid("complete_installable_server_names"); + description = '`:LspInstall {name}` installs a server under stdpath("cache")/nvim_lsp/{name}'; + }; + LspInstallInfo = { + function(name) + if name == nil then + local res = {} + for k, v in pairs(skeleton) do + if v.install_info then + res[k] = v.install_info() + end + end + return print(vim.inspect(res)) + end + local template = skeleton[name] + if not template then + return print("Invalid server name:", name) + end + return print(vim.inspect(template.install_info())) + end; + "-nargs=?"; + "-complete=custom,"..sid("complete_installable_server_names"); + description = 'Print installation info for {name} if one is specified, or all installable servers.'; + }; + }; + + M.util.create_module_commands("_root", M._root.commands) +end + local mt = {} function mt:__index(k) return skeleton[k] diff --git a/lua/nvim_lsp/bash.lua b/lua/nvim_lsp/bash.lua deleted file mode 100644 index a28ab2aa..00000000 --- a/lua/nvim_lsp/bash.lua +++ /dev/null @@ -1,28 +0,0 @@ -local skeleton = require 'nvim_lsp/skeleton' -local util = require 'nvim_lsp/util' -local lsp = vim.lsp - -local cwd = vim.loop.cwd() - -skeleton.bash = { - default_config = { - cmd = {"bash-language-server", "start"}; - filetypes = {"sh"}; - root_dir = function() return cwd end; - log_level = lsp.protocol.MessageType.Warning; - settings = {}; - }; - -- on_new_config = function(new_config) end; - -- on_attach = function(client, bufnr) end; - docs = { - description = [[ -For install instruction visit: -https://github.com/mads-hartmann/bash-language-server#installation -]]; - default_config = { - root_dir = "vim's starting directory"; - }; - }; -}; - --- vim:et ts=2 sw=2 diff --git a/lua/nvim_lsp/bashls.lua b/lua/nvim_lsp/bashls.lua new file mode 100644 index 00000000..b2ae5187 --- /dev/null +++ b/lua/nvim_lsp/bashls.lua @@ -0,0 +1,49 @@ +local skeleton = require 'nvim_lsp/skeleton' +local util = require 'nvim_lsp/util' +local lsp = vim.lsp + +local cwd = vim.loop.cwd() + +local server_name = "bashls" +local bin_name = "bash-language-server" + +local installer = util.npm_installer { + server_name = server_name; + packages = { "bash-language-server" }; + binaries = {bin_name}; +} + +skeleton[server_name] = { + default_config = { + cmd = {"bash-language-server", "start"}; + filetypes = {"sh"}; + root_dir = function() return cwd end; + log_level = lsp.protocol.MessageType.Warning; + settings = {}; + }; + on_new_config = function(new_config) + local install_info = installer.info() + if install_info.is_installed then + if type(new_config.cmd) == 'table' then + -- Try to preserve any additional args from upstream changes. + new_config.cmd[1] = install_info.binaries[bin_name] + else + new_config.cmd = {install_info.binaries[bin_name]} + end + end + end; + -- on_attach = function(client, bufnr) end; + docs = { + description = [[ +For install instruction visit: +https://github.com/mads-hartmann/bash-language-server#installation +]]; + default_config = { + root_dir = "vim's starting directory"; + }; + }; +}; + +skeleton[server_name].install = installer.install +skeleton[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/nvim_lsp/clangd.lua b/lua/nvim_lsp/clangd.lua index 8abe8ea2..6ba349e5 100644 --- a/lua/nvim_lsp/clangd.lua +++ b/lua/nvim_lsp/clangd.lua @@ -2,22 +2,13 @@ local skeleton = require 'nvim_lsp/skeleton' local util = require 'nvim_lsp/util' local lsp = vim.lsp -local default_capabilities = lsp.protocol.make_client_capabilities() -default_capabilities.offsetEncoding = {"utf-8", "utf-16"} - skeleton.clangd = { - default_config = { + default_config = util.utf8_config { cmd = {"clangd", "--background-index"}; filetypes = {"c", "cpp", "objc", "objcpp"}; root_dir = util.root_pattern("compile_commands.json", "compile_flags.txt", ".git"); log_level = lsp.protocol.MessageType.Warning; settings = {}; - capabilities = default_capabilities; - on_init = function(client, result) - if result.offsetEncoding then - client.offset_encoding = result.offsetEncoding - end - end }; -- commands = {}; -- on_new_config = function(new_config) end; diff --git a/lua/nvim_lsp/elmls.lua b/lua/nvim_lsp/elmls.lua index 08a28c45..93dce340 100644 --- a/lua/nvim_lsp/elmls.lua +++ b/lua/nvim_lsp/elmls.lua @@ -1,59 +1,27 @@ local skeleton = require 'nvim_lsp/skeleton' local util = require 'nvim_lsp/util' local lsp = vim.lsp -local fn = vim.fn local api = vim.api -local need_bins = util.need_bins - -local default_capabilities = lsp.protocol.make_client_capabilities() -default_capabilities.offsetEncoding = {"utf-8", "utf-16"} +local server_name = "elmls" local bin_name = "elm-language-server" -local install_dir = util.path.join(fn.stdpath("cache"), "nvim_lsp", "elmls") -local function get_install_info() - local bin_dir = util.path.join(install_dir, "node_modules", ".bin") - local bins = { bin_dir = bin_dir; install_dir = install_dir; } - bins.elmls = util.path.join(bin_dir, bin_name) - bins.elm = util.path.join(bin_dir, "elm") - bins.elm_format = util.path.join(bin_dir, "elm-format") - bins.elm_test = util.path.join(bin_dir, "elm-test") - bins.is_installed = need_bins(bins.elmls, bins.elm, bins.elm_format, bins.elm_test) - return bins -end - -local global_commands = { - ElmlsInstall = { - function() - if get_install_info().is_installed then - return print("ElmLS is already installed") - end - skeleton.elmls.install() - end; - description = 'Install elmls and its dependencies to stdpath("cache")/nvim_lsp/elmls'; - }; - ElmlsInstallInfo = { - function() - local install_info = get_install_info() - if not install_info.is_installed then - return print("ElmLS is not installed") - end - print(vim.inspect(install_info)) - end; - description = 'Print installation info for `elmls`'; - }; -}; - -util.create_module_commands("elmls", global_commands) +local installer = util.npm_installer { + server_name = server_name; + packages = { "elm", "elm-test", "elm-format", "@elm-tooling/elm-language-server" }; + binaries = {bin_name, "elm", "elm-format", "elm-test"}; +} +local default_capabilities = lsp.protocol.make_client_capabilities() +default_capabilities.offsetEncoding = {"utf-8", "utf-16"} local elm_root_pattern = util.root_pattern("elm.json") -skeleton.elmls = { - default_config = { +skeleton[server_name] = { + default_config = util.utf8_config { cmd = {bin_name}; -- TODO(ashkan) if we comment this out, it will allow elmls to operate on elm.json. It seems like it could do that, but no other editor allows it right now. filetypes = {"elm"}; - root_dir = function(fname, bufnr) + root_dir = function(fname) local filetype = api.nvim_buf_get_option(0, 'filetype') if filetype == 'elm' or (filetype == 'json' and fname:match("elm%.json$")) then return elm_root_pattern(fname) @@ -67,40 +35,27 @@ skeleton.elmls = { elmTestPath = "elm-test", elmAnalyseTrigger = "change", }; - capabilities = default_capabilities; - on_init = function(client, result) - if result.offsetEncoding then - client.offset_encoding = result.offsetEncoding - end - end }; - commands = global_commands; on_new_config = function(new_config) - local install_info = get_install_info() + local install_info = installer.info() if install_info.is_installed then - new_config.cmd = {install_info.elmls} + if type(new_config.cmd) == 'table' then + -- Try to preserve any additional args from upstream changes. + new_config.cmd[1] = install_info.binaries[bin_name] + else + new_config.cmd = {install_info.binaries[bin_name]} + end util.tbl_deep_extend(new_config.init_options, { - elmPath = install_info.elm; - elmFormatPath = install_info.elm_format; - elmTestPath = install_info.elm_test; + elmPath = install_info.binaries["elm"]; + elmFormatPath = install_info.binaries["elm-format"]; + elmTestPath = install_info.binaries["elm-test"]; }) end - print(vim.inspect(new_config)) end; docs = { description = [[ https://github.com/elm-tooling/elm-language-server#installation -You can install elmls automatically to the path at - `stdpath("cache")/nvim_lsp/elmls` -by using the function `nvim_lsp.elmls.install()` or the command `:ElmlsInstall`. - -This will only install if it can't find `elm-language-server` and if it hasn't -been installed before by neovim. - -You can see installation info via `:ElmlsInstallInfo` or via -`nvim_lsp.elmls.get_install_info()`. This will let you know if it is installed. - If you don't want to use neovim to install it, then you can use: ```sh npm install -g elm elm-test elm-format @elm-tooling/elm-language-server @@ -114,28 +69,7 @@ npm install -g elm elm-test elm-format @elm-tooling/elm-language-server }; } -function skeleton.elmls.install() - if not need_bins(bin_name) then return end - if not need_bins("sh", "npm", "mkdir", "cd") then - vim.api.nvim_err_writeln('Installation requires "sh", "npm", "mkdir", "cd"') - return - end - local install_info = get_install_info() - if install_info.is_installed then - return - end - local cmd = io.popen("sh", "w") - local install_script = ([[ -set -eo pipefail -mkdir -p "{{install_dir}}" -cd "{{install_dir}}" -npm install elm elm-test elm-format @elm-tooling/elm-language-server -]]):gsub("{{(%S+)}}", install_info) - print(install_script) - cmd:write(install_script) - cmd:close() -end - -skeleton.elmls.get_install_info = get_install_info +skeleton[server_name].install = installer.install +skeleton[server_name].install_info = installer.info -- vim:et ts=2 sw=2 diff --git a/lua/nvim_lsp/tsserver.lua b/lua/nvim_lsp/tsserver.lua index e9f5031f..32c289da 100644 --- a/lua/nvim_lsp/tsserver.lua +++ b/lua/nvim_lsp/tsserver.lua @@ -1,76 +1,40 @@ local skeleton = require 'nvim_lsp/skeleton' local util = require 'nvim_lsp/util' local lsp = vim.lsp -local fn = vim.fn -local api = vim.api -local need_bins = util.need_bins - -local default_capabilities = lsp.protocol.make_client_capabilities() -default_capabilities.offsetEncoding = {"utf-8", "utf-16"} +local server_name = "tsserver" local bin_name = "typescript-language-server" -local install_dir = util.path.join(fn.stdpath("cache"), "nvim_lsp", "typescript-language-server") - -local function get_install_info() - local bin_dir = util.path.join(install_dir, "node_modules", ".bin") - local bins = { bin_dir = bin_dir; install_dir = install_dir; } - bins.tsserver = util.path.join(bin_dir, bin_name) - bins.is_installed = need_bins(bins.tsserver) - return bins -end - -local global_commands = { - TsServerInstall = { - function() - if get_install_info().is_installed then - return print('typescript-language-server is already installed') - end - skeleton.tsserver.install() - end; - description = 'Install typescript-language-server and its dependencies to stdpath("cache")/nvim_lsp/typescript-language-server'; - }; - TsServerInstallInfo = { - function() - local install_info = get_install_info() - if not install_info.is_installed then - return print('typescript-language-server is not installed') - end - print(vim.inspect(install_info)) - end; - description = 'Print installation infor for `typescript-language-server`' - } -}; - -util.create_module_commands("typescript-language-server", global_commands) - +local installer = util.npm_installer { + server_name = server_name; + packages = { "typescript-language-server" }; + binaries = {bin_name}; +} -skeleton.tsserver = { - default_config = { - cmd = {bin_name}; - filetypes = {"typescript", "typescriptreact", "typescript.tsx"}; +skeleton[server_name] = { + default_config = util.utf8_config { + cmd = {bin_name, "--stdio"}; + filetypes = {"javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx"}; root_dir = util.root_pattern("package.json"); log_level = lsp.protocol.MessageType.Warning; settings = {}; - capabilities = default_capabilities; - on_init = function(client, result) - if result.offsetEncoding then - client.offset_encoding = result.offsetEncoding - end - end }; - commands = global_commands; on_new_config = function(new_config) - local install_info = get_install_info() + local install_info = installer.info() if install_info.is_installed then - new_config.cmd = {install_info.tsserver} + if type(new_config.cmd) == 'table' then + -- Try to preserve any additional args from upstream changes. + new_config.cmd[1] = install_info.binaries[bin_name] + else + new_config.cmd = {install_info.binaries[bin_name]} + end end end; docs = { description = [[ https://github.com/theia-ide/typescript-language-server -typescript-language-server can be installed via `:TsServerInstall` or by yourself with `npm`: +`typescript-language-server` can be installed via `:LspInstall tsserver` or by yourself with `npm`: ```sh npm install -g typescript-language-server ``` @@ -83,27 +47,6 @@ npm install -g typescript-language-server }; } -function skeleton.tsserver.install() - if not need_bins(bin_name) then return end - if not need_bins("sh", "npm", "mkdir", "cd") then - vim.api.nvim_err_writeln('Installation requires "sh", "npm", "mkdir", "cd"') - return - end - local install_info = get_install_info() - if install_info.is_installed then - return - end - local cmd = io.popen("sh", "w") - local install_script = ([[ -set -eo pipefail -mkdir -p "{{install_dir}}" -cd "{{install_dir}}" -npm install typescript-language-server -]]):gsub("{{(%S+)}}", install_info) - print(install_script) - cmd:write(install_script) - cmd:close() -end - -skeleton.tsserver.get_install_info = get_install_info +skeleton[server_name].install = installer.install +skeleton[server_name].install_info = installer.info -- vim:et ts=2 sw=2 diff --git a/lua/nvim_lsp/util.lua b/lua/nvim_lsp/util.lua index 2b13d227..a9ca5680 100644 --- a/lua/nvim_lsp/util.lua +++ b/lua/nvim_lsp/util.lua @@ -85,12 +85,12 @@ function M.create_module_commands(module_name, commands) table.insert(parts, command_name) -- The command definition. table.insert(parts, - string.format("lua require'nvim_lsp'[%q].commands[%q][1]()", module_name, command_name)) + string.format("lua require'nvim_lsp'[%q].commands[%q][1](<f-args>)", module_name, command_name)) api.nvim_command(table.concat(parts, " ")) end end -function M.need_bins(...) +function M.has_bins(...) for i = 1, select("#", ...) do if 0 == fn.executable((select(i, ...))) then return false @@ -268,5 +268,103 @@ function M.find_package_json_ancestor(startpath) end) end +local function validate_string_list(t) + for _, v in ipairs(t) do + if type(v) ~= 'string' then + return false + end + end + return true +end + +local function map_list(t, fn) + local res = {} + for i, v in ipairs(t) do table.insert(res, fn(v, i)) end + return res +end + +local function zip_lists_to_map(a, b) + assert(#a == #b) + local res = {} + for i = 1, #a do res[a[i]] = b[i] end + return res +end + +local base_install_dir = M.path.join(fn.stdpath("cache"), "nvim_lsp") +M.base_install_dir = base_install_dir +function M.npm_installer(config) + validate { + server_name = {config.server_name, 's'}; + packages = {config.packages, validate_string_list, 'List of npm package names'}; + binaries = {config.binaries, validate_string_list, 'List of binary names'}; + post_install_script = {config.post_install_script, 's', true}; + } + + local install_dir = M.path.join(base_install_dir, config.server_name) + local bin_dir = M.path.join(install_dir, "node_modules", ".bin") + local function bin_path(name) + return M.path.join(bin_dir, name) + end + + local binary_paths = map_list(config.binaries, bin_path) + + local function get_install_info() + return { + bin_dir = bin_dir; + install_dir = install_dir; + binaries = zip_lists_to_map(config.binaries, binary_paths); + is_installed = M.has_bins(unpack(binary_paths)); + } + end + + local function install() + -- TODO(ashkan) need all binaries or just the first? + if M.has_bins(unpack(config.binaries)) then + return print(config.server_name, "is already installed (not by neovim)") + end + if not M.has_bins("sh", "npm", "mkdir") then + api.nvim_err_writeln('Installation requires "sh", "npm", "mkdir"') + return + end + if get_install_info().is_installed then + return print(config.server_name, "is already installed") + end + local install_params = { + packages = table.concat(config.packages, ' '); + install_dir = install_dir; + post_install_script = config.post_install_script or ''; + } + local cmd = io.popen("sh", "w") + local install_script = ([[ + set -eo pipefail + mkdir -p "{{install_dir}}" + cd "{{install_dir}}" + npm install {{packages}} + {{post_install_script}} + ]]):gsub("{{(%S+)}}", install_params) + cmd:write(install_script) + cmd:close() + if not get_install_info().is_installed then + api.nvim_err_writeln('Installation of', config.server_name, 'failed') + end + end + + return { + install = install; + info = get_install_info; + } +end + +function M.utf8_config(config) + config.capabilities = config.capabilities or lsp.protocol.make_client_capabilities() + config.capabilities.offsetEncoding = {"utf-8", "utf-16"} + function config.on_init(client, result) + if result.offsetEncoding then + client.offset_encoding = result.offsetEncoding + end + end + return config +end + return M -- vim:et ts=2 sw=2 diff --git a/plugin/nvim_lsp.vim b/plugin/nvim_lsp.vim new file mode 100644 index 00000000..bffab637 --- /dev/null +++ b/plugin/nvim_lsp.vim @@ -0,0 +1,11 @@ +function! s:complete_server_names(A,L,P) abort + return luaeval("table.concat(require'nvim_lsp'.available_servers(), '\\n')") +endfunction + +function! s:complete_installable_server_names(A,L,P) abort + return luaeval("table.concat(require'nvim_lsp'.installable_servers(), '\\n')") +endfunction + +lua << EOF +require'nvim_lsp'._root._setup() +EOF diff --git a/scripts/docgen.lua b/scripts/docgen.lua index 7acc8bb5..d58c5881 100644 --- a/scripts/docgen.lua +++ b/scripts/docgen.lua @@ -88,9 +88,17 @@ for _, k in ipairs(skeleton_keys) do end params.preamble = "" if tconf.docs then - params.preamble = table.concat(filter( - nilifempty(tconf.docs.description) - ), '\n\n') + local installation_instructions + if v.install then + installation_instructions = string.format("Can be installed in neovim with `:LspInstall %s`", k) + end + local preamble_parts = filter( + nilifempty(tconf.docs.description) + , installation_instructions + ) + -- Insert a newline after the preamble if it exists. + if #preamble_parts > 0 then table.insert(preamble_parts, '') end + params.preamble = table.concat(preamble_parts, '\n') end local section = ([[ |
