├── .gitignore ├── plugin └── nvim-lspinstall.vim ├── lua └── nvim-lspinstall │ ├── init.lua │ ├── langs │ ├── pyright.lua │ ├── svelte.lua │ ├── ocamlls.lua │ ├── yamlls.lua │ ├── dockerls.lua │ ├── purescriptls.lua │ ├── html.lua │ ├── tsserver.lua │ ├── jsonls.lua │ ├── sqlls.lua │ ├── vimls.lua │ ├── clangd.lua │ ├── intelephense.lua │ ├── elmls.lua │ ├── omnisharp.lua │ ├── vuels.lua │ └── sumneko_lua.lua │ ├── configs.lua │ └── util.lua ├── README.md └── doc └── nvim-lspinstall.txt /.gitignore: -------------------------------------------------------------------------------- 1 | NetrwTreeListing 2 | -------------------------------------------------------------------------------- /plugin/nvim-lspinstall.vim: -------------------------------------------------------------------------------- 1 | fun! LspInstall(lang) 2 | lua for k in pairs(package.loaded) do if k:match("^nvim-lspinstall") then package.loaded[k] = nil end end 3 | let g:Lsp_Install_Lang = a:lang 4 | lua require('nvim-lspinstall').installLang() 5 | endfun 6 | 7 | command! -nargs=1 -complete=custom,ListLsps InstallLanguageServer :call LspInstall('') 8 | command! -nargs=1 -complete=custom,ListLsps InstallLS :call LspInstall('') 9 | command! -nargs=1 -complete=custom,ListLsps LspInstall :call LspInstall('') 10 | 11 | function! ListLsps(arg, line, pos) abort 12 | let l:list = luaeval('require("nvim-lspinstall").get_lsps()') 13 | return join(list, "\n") 14 | endfunction 15 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/init.lua: -------------------------------------------------------------------------------- 1 | local vim = vim 2 | 3 | local linux_cmds = { 4 | 'bashls', 5 | 'cmake', 6 | 'cssls', 7 | 'docker', 8 | 'elmls', 9 | 'html', 10 | 'intelephense', 11 | 'jsonls', 12 | 'purescriptls', 13 | 'pyls', 14 | 'rust_analyzer', 15 | 'sumneko_lua', 16 | 'svelte', 17 | 'tsserver', 18 | 'vuels', 19 | 'yamlls', 20 | 'sumneko_lua', 21 | 'svelte', 22 | } 23 | 24 | local configs = require('nvim-lspinstall/configs') 25 | local installLang = function() 26 | local lang = vim.g["Lsp_Install_Lang"] 27 | if configs[lang] == nil then 28 | pcall(require('nvim-lspinstall/langs/'..lang)) 29 | end 30 | local config = configs[lang] 31 | if not config then 32 | return print("Invalid server name:", lang) 33 | end 34 | if not config.install then 35 | return print(lang, "can't be automatically installed (yet)") 36 | end 37 | if config.install_info().is_installed then 38 | return print(lang, "is already installed") 39 | end 40 | config.install() 41 | end 42 | 43 | local get_lsps = function() 44 | return vim.tbl_values(linux_cmds) 45 | end 46 | 47 | return { 48 | installLang = installLang, 49 | get_lsps = get_lsps 50 | } 51 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/pyright.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "pyright" 5 | local bin_name = "pyright-langserver" 6 | if vim.fn.has('win32') == 1 then 7 | bin_name = bin_name..".cmd" 8 | end 9 | 10 | local installer = util.npm_installer { 11 | server_name = server_name; 12 | packages = {server_name}; 13 | binaries = {bin_name}; 14 | } 15 | 16 | configs[server_name] = { 17 | default_config = { 18 | cmd = {bin_name, "--stdio"}; 19 | filetypes = {"python"}; 20 | root_dir = util.root_pattern(".git", "setup.py", "setup.cfg", "pyproject.toml", "requirements.txt"); 21 | settings = { 22 | python = { 23 | analysis = { 24 | autoSearchPaths = true; 25 | useLibraryCodeForTypes = true; 26 | }; 27 | }; 28 | }; 29 | }; 30 | docs = { 31 | description = [[ 32 | https://github.com/microsoft/pyright 33 | 34 | `pyright`, a static type checker and language server for python 35 | ]]; 36 | }; 37 | } 38 | 39 | configs[server_name].install = installer.install 40 | configs[server_name].install_info = installer.info 41 | -- vim:et ts=2 sw=2 42 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/svelte.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = 'svelte' 5 | local bin_name = 'svelteserver' 6 | 7 | local installer = util.npm_installer { 8 | server_name = server_name; 9 | packages = { "svelte-language-server" }; 10 | binaries = { bin_name }; 11 | } 12 | 13 | configs[server_name] = { 14 | default_config = { 15 | cmd = {bin_name, '--stdio'}; 16 | filetypes = {'svelte'}; 17 | root_dir = util.root_pattern("package.json", ".git"); 18 | }; 19 | on_new_config = function(new_config) 20 | local install_info = installer.info() 21 | if install_info.is_installed then 22 | if type(new_config.cmd) == 'table' then 23 | new_config.cmd[1] = install_info.binaries[bin_name] 24 | else 25 | new_config.cmd = {install_info.binaries[bin_name]} 26 | end 27 | end 28 | end; 29 | docs = { 30 | description = [[ 31 | https://github.com/sveltejs/language-tools/tree/master/packages/language-server 32 | 33 | `svelte-language-server` can be installed via `:LspInstall svelte` or by yourself with `npm`: 34 | ```sh 35 | npm install -g svelte-language-server 36 | ``` 37 | ]]; 38 | default_config = { 39 | root_dir = [[root_pattern("package.json", ".git")]]; 40 | }; 41 | } 42 | } 43 | 44 | configs[server_name].install = installer.install 45 | configs[server_name].install_info = installer.info 46 | -- vim:et ts=2 sw=2 47 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/ocamlls.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "ocamlls" 5 | local bin_name = "ocaml-language-server" 6 | 7 | local installer = util.npm_installer { 8 | server_name = server_name; 9 | packages = { "ocaml-language-server" }; 10 | binaries = { bin_name }; 11 | } 12 | 13 | configs[server_name] = { 14 | default_config = { 15 | cmd = { bin_name, "--stdio" }; 16 | filetypes = { "ocaml", "reason" }; 17 | root_dir = util.root_pattern(".merlin", "package.json"); 18 | }; 19 | on_new_config = function(new_config) 20 | local install_info = installer.info() 21 | if install_info.is_installed then 22 | if type(new_config.cmd) == 'table' then 23 | new_config.cmd[1] = install_info.binaries[bin_name] 24 | else 25 | new_config.cmd = {install_info.binaries[bin_name]} 26 | end 27 | end 28 | end; 29 | docs = { 30 | description = [[ 31 | https://github.com/ocaml-lsp/ocaml-language-server 32 | 33 | `ocaml-language-server` can be installed via `:LspInstall ocamlls` or by yourself with `npm` 34 | ```sh 35 | npm install -g ocaml-langauge-server 36 | ``` 37 | ]]; 38 | default_config = { 39 | root_dir = [[root_pattern(".merlin", "package.json")]]; 40 | }; 41 | }; 42 | }; 43 | configs[server_name].install = installer.install 44 | configs[server_name].install_info = installer.info 45 | -- vim:et ts=2 sw=2 46 | 47 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/yamlls.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "yamlls" 5 | local bin_name = "yaml-language-server" 6 | 7 | local installer = util.npm_installer { 8 | server_name = server_name; 9 | packages = {bin_name}; 10 | binaries = {bin_name}; 11 | } 12 | 13 | configs[server_name] = { 14 | default_config = { 15 | cmd = {bin_name, "--stdio"}; 16 | filetypes = {"yaml"}; 17 | root_dir = util.root_pattern(".git", vim.fn.getcwd()); 18 | }; 19 | on_new_config = function(new_config) 20 | local install_info = installer.info() 21 | if install_info.is_installed then 22 | if type(new_config.cmd) == 'table' then 23 | new_config.cmd[1] = install_info.binaries[bin_name] 24 | else 25 | new_config.cmd = {install_info.binaries[bin_name]} 26 | end 27 | end 28 | end; 29 | docs = { 30 | package_json = "https://raw.githubusercontent.com/redhat-developer/vscode-yaml/master/package.json"; 31 | description = [[ 32 | https://github.com/redhat-developer/yaml-language-server 33 | 34 | `yaml-language-server` can be installed via `:LspInstall yamlls` or by yourself with `npm`: 35 | ```sh 36 | npm install -g yaml-language-server 37 | ``` 38 | ]]; 39 | default_config = { 40 | root_dir = [[root_pattern(".git", vim.fn.getcwd())]]; 41 | }; 42 | }; 43 | } 44 | 45 | configs[server_name].install = installer.install 46 | configs[server_name].install_info = installer.info 47 | -- vim:et ts=2 sw=2 48 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/dockerls.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "dockerls" 5 | local bin_name = "docker-langserver" 6 | 7 | local installer = util.npm_installer { 8 | server_name = server_name; 9 | packages = { "dockerfile-language-server-nodejs" }; 10 | binaries = {bin_name}; 11 | } 12 | 13 | configs[server_name] = { 14 | default_config = { 15 | cmd = {bin_name, "--stdio"}; 16 | filetypes = {"Dockerfile", "dockerfile"}; 17 | root_dir = util.root_pattern("Dockerfile"); 18 | }; 19 | on_new_config = function(new_config) 20 | local install_info = installer.info() 21 | if install_info.is_installed then 22 | if type(new_config.cmd) == 'table' then 23 | -- Try to preserve any additional args from upstream changes. 24 | new_config.cmd[1] = install_info.binaries[bin_name] 25 | else 26 | new_config.cmd = {install_info.binaries[bin_name]} 27 | end 28 | end 29 | end; 30 | docs = { 31 | description = [[ 32 | https://github.com/rcjsuen/dockerfile-language-server-nodejs 33 | 34 | `docker-langserver` can be installed via `:LspInstall dockerls` or by yourself with `npm`: 35 | ```sh 36 | npm install -g dockerfile-language-server-nodejs 37 | ``` 38 | ]]; 39 | default_config = { 40 | root_dir = [[root_pattern("Dockerfile")]]; 41 | }; 42 | }; 43 | }; 44 | 45 | configs[server_name].install = installer.install 46 | configs[server_name].install_info = installer.info 47 | -- vim:et ts=2 sw=2 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nvim-lspinstall 2 | ## What is it? 3 | Recently, neovim removed the `:LspInstall` function. It made it really easy to install language servers for its built in lsp, so I'm making a replacement! 4 | ## Installation 5 | You can install nvim-lspinstall with any neovim package manager. 6 | ```vim 7 | Plug 'anott03/nvim-lspinstall' 8 | ``` 9 | ## Ultimate Language List 10 | These are the langauges we plan/hope support. It's the same list that was on the [lspconfig github page](https://github.com/neovim/nvim-lspconfig). 11 | - [ ] als 12 | - [ ] angularls 13 | - [ ] bashls 14 | - [ ] ccls 15 | - [X] clangd 16 | - [ ] clojure_lsp 17 | - [ ] cmake 18 | - [ ] codeqlls 19 | - [ ] cssls 20 | - [ ] dartls 21 | - [ ] denols 22 | - [ ] dhall_lsp_server 23 | - [ ] diagnosticls 24 | - [X] dockerls 25 | - [ ] efm 26 | - [ ] elixirls 27 | - [X] elmls 28 | - [ ] flow 29 | - [ ] fortls 30 | - [ ] gdscript 31 | - [ ] ghcide 32 | - [ ] gopls 33 | - [ ] groovyls 34 | - [ ] hie 35 | - [ ] hls 36 | - [X] html 37 | - [X] intelephense 38 | - [ ] jdtls 39 | - [ ] jedi_language_server 40 | - [X] jsonls 41 | - [ ] julials 42 | - [ ] kotlin_language_server 43 | - [ ] leanls 44 | - [ ] metals 45 | - [ ] nimls 46 | - [X] ocamlls 47 | - [ ] ocamllsp 48 | - [X] omnisharp 49 | - [ ] perlls 50 | - [X] purescriptls 51 | - [ ] pyls 52 | - [ ] pyls_ms 53 | - [X] pyright (not working) 54 | - [ ] r_language_server 55 | - [ ] rls 56 | - [ ] rnix 57 | - [ ] rome 58 | - [ ] rust_analyzer 59 | - [ ] scry 60 | - [ ] solargraph 61 | - [ ] sorbet 62 | - [ ] sourcekit 63 | - [X] sqlls 64 | - [X] sumneko_lua 65 | - [X] svelte 66 | - [ ] terraformls 67 | - [ ] texlab 68 | - [X] tsserver 69 | - [X] vimls 70 | - [X] vuels 71 | - [X] yamlls 72 | - [ ] zls 73 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/purescriptls.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "purescriptls" 5 | local bin_name = "purescript-language-server" 6 | if vim.fn.has('win32') == 1 then 7 | bin_name = bin_name..'.cmd' 8 | end 9 | 10 | local installer = util.npm_installer { 11 | server_name = server_name; 12 | packages = { "purescript", "purescript-language-server" }; 13 | binaries = {bin_name, "purs"}; 14 | } 15 | 16 | configs[server_name] = { 17 | default_config = { 18 | cmd = {bin_name, "--stdio"}; 19 | filetypes = {"purescript"}; 20 | root_dir = util.root_pattern("spago.dhall", "bower.json"); 21 | }; 22 | on_new_config = function(new_config) 23 | local install_info = installer.info() 24 | if install_info.is_installed then 25 | if type(new_config.cmd) == 'table' then 26 | new_config.cmd[1] = install_info.binaries[bin_name] 27 | else 28 | new_config.cmd = {install_info.binaries[bin_name]} 29 | end 30 | end 31 | end; 32 | docs = { 33 | package_json = "https://raw.githubusercontent.com/nwolverson/vscode-ide-purescript/master/package.json"; 34 | description = [[ 35 | https://github.com/nwolverson/purescript-language-server 36 | `purescript-language-server` can be installed via `:LspInstall purescriptls` or by yourself with `npm` 37 | ```sh 38 | npm install -g purescript-language-server 39 | ``` 40 | ]]; 41 | default_config = { 42 | root_dir = [[root_pattern("spago.dhall, bower.json")]]; 43 | }; 44 | }; 45 | }; 46 | configs[server_name].install = installer.install 47 | configs[server_name].install_info = installer.info 48 | 49 | -- vim:et ts=2 sw=2 50 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/html.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "html" 5 | local bin_name = "html-languageserver" 6 | 7 | local installer = util.npm_installer { 8 | server_name = server_name; 9 | packages = { "vscode-html-languageserver-bin" }; 10 | binaries = {bin_name}; 11 | } 12 | 13 | local root_pattern = util.root_pattern("package.json") 14 | 15 | configs[server_name] = { 16 | default_config = { 17 | cmd = {bin_name, "--stdio"}; 18 | filetypes = {"html"}; 19 | root_dir = function(fname) 20 | return root_pattern(fname) or vim.loop.os_homedir() 21 | end; 22 | settings = {}; 23 | init_options = { 24 | embeddedLanguages = { css= true, javascript= true }, 25 | configurationSection = { 'html', 'css', 'javascript' }, 26 | } 27 | 28 | }; 29 | on_new_config = function(new_config) 30 | local install_info = installer.info() 31 | if install_info.is_installed then 32 | if type(new_config.cmd) == 'table' then 33 | -- Try to preserve any additional args from upstream changes. 34 | new_config.cmd[1] = install_info.binaries[bin_name] 35 | else 36 | new_config.cmd = {install_info.binaries[bin_name]} 37 | end 38 | end 39 | end; 40 | docs = { 41 | description = [[ 42 | https://github.com/vscode-langservers/vscode-html-languageserver-bin 43 | 44 | `html-languageserver` can be installed via `:LspInstall html` or by yourself with `npm`: 45 | ```sh 46 | npm install -g vscode-html-languageserver-bin 47 | ``` 48 | ]]; 49 | }; 50 | } 51 | 52 | configs[server_name].install = installer.install 53 | configs[server_name].install_info = installer.info 54 | -- vim:et ts=2 sw=2 55 | 56 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/tsserver.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "tsserver" 5 | local bin_name = "typescript-language-server" 6 | if vim.fn.has('win32') == 1 then 7 | bin_name = bin_name..".cmd" 8 | end 9 | 10 | local installer = util.npm_installer { 11 | server_name = server_name; 12 | packages = { "typescript-language-server" }; 13 | binaries = {bin_name}; 14 | } 15 | 16 | configs[server_name] = { 17 | default_config = { 18 | cmd = {bin_name, "--stdio"}; 19 | filetypes = {"javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx"}; 20 | root_dir = util.root_pattern("package.json", "tsconfig.json", "jsconfig.json", ".git"); 21 | }; 22 | on_new_config = function(new_config) 23 | local install_info = installer.info() 24 | if install_info.is_installed then 25 | if type(new_config.cmd) == 'table' then 26 | -- Try to preserve any additional args from upstream changes. 27 | new_config.cmd[1] = install_info.binaries[bin_name] 28 | else 29 | new_config.cmd = {install_info.binaries[bin_name]} 30 | end 31 | end 32 | end; 33 | docs = { 34 | description = [[ 35 | https://github.com/theia-ide/typescript-language-server 36 | 37 | `typescript-language-server` can be installed via `:LspInstall tsserver` or by yourself with `npm`: 38 | ```sh 39 | npm install -g typescript-language-server 40 | ``` 41 | ]]; 42 | default_config = { 43 | root_dir = [[root_pattern("package.json", "tsconfig.json", "jsconfig.json", ".git")]]; 44 | }; 45 | }; 46 | } 47 | 48 | configs[server_name].install = installer.install 49 | configs[server_name].install_info = installer.info 50 | -- vim:et ts=2 sw=2 51 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/jsonls.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "jsonls" 5 | local bin_name = "vscode-json-languageserver" 6 | 7 | local installer = util.npm_installer { 8 | server_name = server_name; 9 | packages = {bin_name}; 10 | binaries = {bin_name}; 11 | } 12 | 13 | configs[server_name] = { 14 | default_config = { 15 | cmd = {bin_name, "--stdio"}; 16 | filetypes = {"json"}; 17 | root_dir = util.root_pattern(".git", vim.fn.getcwd()); 18 | }; 19 | on_new_config = function(new_config) 20 | local install_info = installer.info() 21 | if install_info.is_installed then 22 | if type(new_config.cmd) == 'table' then 23 | -- Try to preserve any additional args from upstream changes. 24 | new_config.cmd[1] = install_info.binaries[bin_name] 25 | else 26 | new_config.cmd = {install_info.binaries[bin_name]} 27 | end 28 | end 29 | end; 30 | docs = { 31 | -- this language server config is in VSCode built-in package.json 32 | package_json = "https://raw.githubusercontent.com/microsoft/vscode/master/extensions/json-language-features/package.json"; 33 | description = [[ 34 | https://github.com/vscode-langservers/vscode-json-languageserver 35 | 36 | vscode-json-languageserver, a language server for JSON and JSON schema 37 | 38 | `vscode-json-languageserver` can be installed via `:LspInstall jsonls` or by yourself with `npm`: 39 | ```sh 40 | npm install -g vscode-json-languageserver 41 | ``` 42 | ]]; 43 | default_config = { 44 | root_dir = [[root_pattern(".git", vim.fn.getcwd())]]; 45 | }; 46 | }; 47 | } 48 | 49 | configs[server_name].install = installer.install 50 | configs[server_name].install_info = installer.info 51 | -- vim:et ts=2 sw=2 52 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/sqlls.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "sqlls" 5 | local bin_name = "sql-language-server" 6 | 7 | local installer = util.npm_installer { 8 | server_name = server_name; 9 | packages = { "sql-language-server" }; 10 | binaries = {bin_name}; 11 | } 12 | 13 | local root_pattern = util.root_pattern(".sqllsrc.json") 14 | 15 | configs[server_name] = { 16 | default_config = { 17 | filetypes = {"sql", "mysql"}; 18 | root_dir = function(fname) 19 | return root_pattern(fname) or vim.loop.os_homedir() 20 | end; 21 | settings = {}; 22 | }; 23 | on_new_config = function(config) 24 | local install_info = installer.info(); 25 | local P = util.path.join 26 | if install_info.is_installed then 27 | local bin_ex = P{install_info.bin_dir, bin_name} 28 | config.cmd = {bin_ex, "up", "--method", "stdio"} 29 | end 30 | end; 31 | docs = { 32 | description = [[ 33 | https://github.com/joe-re/sql-language-server 34 | 35 | `cmd` value is **not set** by default. An installer is provided via the `:LspInstall` command that uses the *nvm_lsp node_modules* directory to find the sql-language-server executable. The `cmd` value can be overriden in the `setup` table; 36 | 37 | ```lua 38 | require'lspconfig'.sqlls.setup{ 39 | cmd = {"path/to/command", "up", "--method", "stdio"}; 40 | ... 41 | } 42 | ``` 43 | 44 | This LSP can be installed via `:LspInstall sqlls` or with `npm`. If using LspInstall, run `:LspInstallInfo sqlls` to view installation paths. Find further instructions on manual installation of the sql-language-server at [joe-re/sql-language-server](https://github.com/joe-re/sql-language-server). 45 |
46 | ]]; 47 | }; 48 | } 49 | 50 | configs[server_name].install = installer.install 51 | configs[server_name].install_info = installer.info 52 | -- vim:et ts=2 sw=2 53 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/vimls.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "vimls" 5 | local bin_name = "vim-language-server" 6 | if vim.fn.has('win32') == 1 then 7 | bin_name = bin_name..".cmd" 8 | end 9 | 10 | local installer = util.npm_installer { 11 | server_name = server_name, 12 | packages = {"vim-language-server"}, 13 | binaries = {bin_name} 14 | } 15 | 16 | configs[server_name] = { 17 | default_config = { 18 | cmd = {bin_name, "--stdio"}, 19 | filetypes = {"vim"}, 20 | root_dir = function(fname) 21 | return util.find_git_ancestor(fname) or vim.loop.os_homedir() 22 | end, 23 | init_options = { 24 | iskeyword = "@,48-57,_,192-255,-#", 25 | vimruntime = "", 26 | runtimepath = "", 27 | diagnostic = {enable = true}, 28 | indexes = { 29 | runtimepath = true, 30 | gap = 100, 31 | count = 3, 32 | projectRootPatterns = {"runtime", "nvim", ".git", "autoload", "plugin"} 33 | }, 34 | suggest = {fromVimruntime = true, fromRuntimepath = true} 35 | }, 36 | on_new_config = function(new_config) 37 | local install_info = installer.info() 38 | if install_info.is_installed then 39 | if type(new_config.cmd) == "table" then 40 | -- Try to preserve any additional args from upstream changes. 41 | new_config.cmd[1] = install_info.binaries[bin_name] 42 | else 43 | new_config.cmd = {install_info.binaries[bin_name]} 44 | end 45 | end 46 | end, 47 | docs = { 48 | description = [[ 49 | https://github.com/iamcco/vim-language-server 50 | 51 | If you don't want to use Nvim to install it, then you can use: 52 | ```sh 53 | npm install -g vim-language-server 54 | ``` 55 | ]] 56 | } 57 | } 58 | } 59 | 60 | configs[server_name].install = installer.install 61 | configs[server_name].install_info = installer.info 62 | 63 | -- vim:et ts=2 sw=2 64 | 65 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/clangd.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | -- https://clangd.llvm.org/extensions.html#switch-between-sourceheader 5 | local function switch_source_header(bufnr) 6 | bufnr = util.validate_bufnr(bufnr) 7 | local params = { uri = vim.uri_from_bufnr(bufnr) } 8 | vim.lsp.buf_request(bufnr, 'textDocument/switchSourceHeader', params, function(err, _, result) 9 | if err then error(tostring(err)) end 10 | if not result then print ("Corresponding file can’t be determined") return end 11 | vim.api.nvim_command('edit '..vim.uri_to_fname(result)) 12 | end) 13 | end 14 | 15 | local root_pattern = util.root_pattern("compile_commands.json", "compile_flags.txt", ".git") 16 | configs.clangd = { 17 | default_config = util.utf8_config { 18 | cmd = {"clangd", "--background-index"}; 19 | filetypes = {"c", "cpp", "objc", "objcpp"}; 20 | root_dir = function(fname) 21 | local filename = util.path.is_absolute(fname) and fname 22 | or util.path.join(vim.loop.cwd(), fname) 23 | return root_pattern(filename) or util.path.dirname(filename) 24 | end; 25 | capabilities = { 26 | textDocument = { 27 | completion = { 28 | editsNearCursor = true 29 | } 30 | } 31 | }, 32 | }; 33 | commands = { 34 | ClangdSwitchSourceHeader = { 35 | function() 36 | switch_source_header(0) 37 | end; 38 | description = "Switch between source/header"; 39 | }; 40 | }; 41 | docs = { 42 | description = [[ 43 | https://clang.llvm.org/extra/clangd/Installation.html 44 | 45 | **NOTE:** Clang >= 9 is recommended! See [this issue for more](https://github.com/neovim/nvim-lsp/issues/23). 46 | 47 | clangd relies on a [JSON compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html) specified 48 | as compile_commands.json or, for simpler projects, a compile_flags.txt. 49 | For details on how to automatically generate one using CMake look [here](https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html). 50 | ]]; 51 | default_config = { 52 | root_dir = [[root_pattern("compile_commands.json", "compile_flags.txt", ".git") or dirname]]; 53 | on_init = [[function to handle changing offsetEncoding]]; 54 | capabilities = [[default capabilities, with offsetEncoding utf-8]]; 55 | }; 56 | }; 57 | } 58 | 59 | configs.clangd.switch_source_header = switch_source_header 60 | -- vim:et ts=2 sw=2 61 | 62 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/intelephense.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "intelephense" 5 | local bin_name = "intelephense" 6 | 7 | local installer = util.npm_installer { 8 | server_name = server_name; 9 | packages = { "intelephense" }; 10 | binaries = {bin_name}; 11 | } 12 | 13 | configs[server_name] = { 14 | default_config = { 15 | cmd = {bin_name, "--stdio"}; 16 | filetypes = {"php"}; 17 | root_dir = function (pattern) 18 | local cwd = vim.loop.cwd(); 19 | local root = util.root_pattern("composer.json", ".git")(pattern); 20 | 21 | -- prefer cwd if root is a descendant 22 | return util.path.is_descendant(cwd, root) and cwd or root; 23 | end; 24 | }; 25 | on_new_config = function(new_config) 26 | local install_info = installer.info() 27 | if install_info.is_installed then 28 | if type(new_config.cmd) == 'table' then 29 | -- Try to preserve any additional args from upstream changes. 30 | new_config.cmd[1] = install_info.binaries[bin_name] 31 | else 32 | new_config.cmd = {install_info.binaries[bin_name]} 33 | end 34 | end 35 | end; 36 | docs = { 37 | description = [[ 38 | https://intelephense.com/ 39 | 40 | `intelephense` can be installed via `:LspInstall intelephense` or by yourself with `npm`: 41 | ```sh 42 | npm install -g intelephense 43 | ``` 44 | ]]; 45 | default_config = { 46 | root_dir = [[root_pattern("composer.json", ".git")]]; 47 | init_options = [[{ 48 | storagePath = Optional absolute path to storage dir. Defaults to os.tmpdir(). 49 | globalStoragePath = Optional absolute path to a global storage dir. Defaults to os.homedir(). 50 | licenceKey = Optional licence key or absolute path to a text file containing the licence key. 51 | clearCache = Optional flag to clear server state. State can also be cleared by deleting {storagePath}/intelephense 52 | -- See https://github.com/bmewburn/intelephense-docs#initialisation-options 53 | }]]; 54 | settings = [[{ 55 | intelephense = { 56 | files = { 57 | maxSize = 1000000; 58 | }; 59 | }; 60 | -- See https://github.com/bmewburn/intelephense-docs#configuration-options 61 | }]]; 62 | }; 63 | }; 64 | } 65 | 66 | configs[server_name].install = installer.install 67 | configs[server_name].install_info = installer.info 68 | -- vim:et ts=2 sw=2 69 | 70 | -------------------------------------------------------------------------------- /doc/nvim-lspinstall.txt: -------------------------------------------------------------------------------- 1 | *hop.txt* For Neovim version 0.5 Last change: 2021 March 18 2 | 3 | ============================================================================== 4 | CONTENTS *lspinstall-contents* 5 | 6 | Introduction ······································· |lspinstall-introduction| 7 | Requirements ······································· |lspinstall-requirements| 8 | Usage ····················································· |lspinstall-usage| 9 | Commands ············································· |lspinstall-commands| 10 | 11 | ============================================================================== 12 | INTRODUCTION *lspinstall* *lspinstall-introduction* 13 | 14 | Nvim-lspinstall is a replacement for the now deprecated `:LspInstall` editor 15 | command that was previously included in |nvim-lspconfig|. This plugin adds 16 | back that functionality so that people who want to use Neovim 0.5's builtin LSP 17 | do not have to install all of their language servers by hand, though does not 18 | yet include all of the language servers that |nvim-lspconfig| had supported. 19 | 20 | ============================================================================== 21 | REQUIREMENTS *lspinstall-requirements* 22 | 23 | Lspinstall works only with Neovim 0.5, as that is the only version with the 24 | builtin LSP client. That is the only requirement for the plugin itself, though 25 | some language servers may have their own individual requirements. 26 | 27 | We also recommend that you use the |nvim-lspconfig| plugin which makes it easy 28 | to set up language servers once you have them installed. 29 | 30 | ============================================================================== 31 | USAGE *lspinstall-usage* 32 | 33 | *lspinstall-commands* 34 | Commands~ 35 | 36 | `:LspInstall` *:LspInstall* 37 | This is the command that allows you to install language servers. For 38 | example, to install the sumneko_lua language server, you would run 39 | `:LspInstall sumneko_lua` 40 | 41 | A full list of available language servers can be found on our README on 42 | github, and details on how to set up language servers after they have been 43 | installed can be found on the |nvim-lspinstall| github page. 44 | 45 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/elmls.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | local lsp = vim.lsp 4 | local api = vim.api 5 | 6 | local server_name = "elmls" 7 | local bin_name = "elm-language-server" 8 | 9 | local installer = util.npm_installer { 10 | server_name = server_name; 11 | packages = { "elm", "elm-test", "elm-format", "@elm-tooling/elm-language-server" }; 12 | binaries = {bin_name, "elm", "elm-format", "elm-test"}; 13 | } 14 | 15 | local default_capabilities = lsp.protocol.make_client_capabilities() 16 | default_capabilities.offsetEncoding = {"utf-8", "utf-16"} 17 | local elm_root_pattern = util.root_pattern("elm.json") 18 | 19 | configs[server_name] = { 20 | default_config = { 21 | cmd = {bin_name}; 22 | -- 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. 23 | filetypes = {"elm"}; 24 | root_dir = function(fname) 25 | local filetype = api.nvim_buf_get_option(0, 'filetype') 26 | if filetype == 'elm' or (filetype == 'json' and fname:match("elm%.json$")) then 27 | return elm_root_pattern(fname) 28 | end 29 | end; 30 | init_options = { 31 | elmPath = "elm", 32 | elmFormatPath = "elm-format", 33 | elmTestPath = "elm-test", 34 | elmAnalyseTrigger = "change", 35 | }; 36 | }; 37 | on_new_config = function(new_config) 38 | local install_info = installer.info() 39 | if install_info.is_installed then 40 | if type(new_config.cmd) == 'table' then 41 | -- Try to preserve any additional args from upstream changes. 42 | new_config.cmd[1] = install_info.binaries[bin_name] 43 | else 44 | new_config.cmd = {install_info.binaries[bin_name]} 45 | end 46 | new_config.init_options = util.tbl_deep_extend('force', new_config.init_options, { 47 | elmPath = install_info.binaries["elm"]; 48 | elmFormatPath = install_info.binaries["elm-format"]; 49 | elmTestPath = install_info.binaries["elm-test"]; 50 | }) 51 | end 52 | end; 53 | docs = { 54 | package_json = "https://raw.githubusercontent.com/elm-tooling/elm-language-client-vscode/master/package.json"; 55 | description = [[ 56 | https://github.com/elm-tooling/elm-language-server#installation 57 | 58 | If you don't want to use Nvim to install it, then you can use: 59 | ```sh 60 | npm install -g elm elm-test elm-format @elm-tooling/elm-language-server 61 | ``` 62 | ]]; 63 | default_config = { 64 | root_dir = [[root_pattern("elm.json")]]; 65 | }; 66 | }; 67 | } 68 | 69 | configs[server_name].install = installer.install 70 | configs[server_name].install_info = installer.info 71 | -- vim:et ts=2 sw=2 72 | 73 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/omnisharp.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | local server_name = 'omnisharp' 4 | local bin_name = 'run' 5 | 6 | local function make_installer() 7 | local install_dir = util.path.join{util.base_install_dir, server_name} 8 | local pid = vim.fn.getpid() 9 | local url = 'linux-x64' 10 | 11 | if vim.fn.has('win32') == 1 then 12 | url = 'win-x64' 13 | bin_name = 'Omnisharp.exe' 14 | elseif vim.fn.has('mac') == 1 then 15 | url = 'osx' 16 | end 17 | local bin_path = util.path.join{install_dir, bin_name} 18 | 19 | local download_target = util.path.join{install_dir, string.format("omnisharp-%s.zip", url)} 20 | local extract_cmd = string.format("unzip '%s' -d '%s'", download_target, install_dir) 21 | local download_cmd = string.format('curl -fLo "%s" --create-dirs "https://github.com/OmniSharp/omnisharp-roslyn/releases/latest/download/omnisharp-%s.zip"', download_target, url) 22 | local make_executable_cmd = string.format("chmod u+x '%s'", bin_path) 23 | 24 | local X = {} 25 | function X.install() 26 | local install_info = X.info() 27 | if install_info.is_installed then 28 | print(server_name, "is already installed") 29 | return 30 | end 31 | if not (util.has_bins("curl")) then 32 | error('Need "curl" to install this.') 33 | return 34 | end 35 | vim.fn.mkdir(install_dir, 'p') 36 | vim.fn.system(download_cmd) 37 | vim.fn.system(extract_cmd) 38 | vim.fn.system(make_executable_cmd) 39 | end 40 | function X.info() 41 | return { 42 | is_installed = util.path.exists(bin_path); 43 | install_dir = install_dir; 44 | cmd = { bin_path, "--languageserver" , "--hostPID", tostring(pid)}; 45 | } 46 | end 47 | function X.configure(config) 48 | local install_info = X.info() 49 | if install_info.is_installed then 50 | config.cmd = install_info.cmd 51 | end 52 | end 53 | return X 54 | end 55 | 56 | local installer = make_installer() 57 | 58 | configs[server_name] = { 59 | default_config = { 60 | cmd = installer.info().cmd; 61 | filetypes = {"cs", "vb"}; 62 | root_dir = util.root_pattern("*.csproj", "*.sln"); 63 | on_new_config = function(config) 64 | installer.configure(config) 65 | end; 66 | init_options = { 67 | }; 68 | }; 69 | -- on_new_config = function(new_config) end; 70 | -- on_attach = function(client, bufnr) end; 71 | docs = { 72 | description = [[ 73 | https://github.com/omnisharp/omnisharp-roslyn 74 | OmniSharp server based on Roslyn workspaces 75 | ]]; 76 | default_config = { 77 | root_dir = [[root_pattern(".csproj", ".sln")]]; 78 | }; 79 | }; 80 | } 81 | 82 | configs[server_name].install = installer.install 83 | configs[server_name].install_info = installer.info 84 | -- vim:et ts=2 sw=2 85 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/vuels.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local server_name = "vuels" 5 | local bin_name = "vls" 6 | 7 | local installer = util.npm_installer { 8 | server_name = server_name; 9 | packages = { "vls" }; 10 | binaries = {bin_name}; 11 | } 12 | 13 | configs[server_name] = { 14 | default_config = { 15 | cmd = {bin_name}; 16 | filetypes = {"vue"}; 17 | root_dir = util.root_pattern("package.json", "vue.config.js"); 18 | init_options = { 19 | config = { 20 | vetur = { 21 | useWorkspaceDependencies = false; 22 | validation = { 23 | template = true; 24 | style = true; 25 | script = true; 26 | }; 27 | completion = { 28 | autoImport = false; 29 | useScaffoldSnippets = false; 30 | tagCasing = "kebab"; 31 | }; 32 | format = { 33 | defaultFormatter = { 34 | js = "none"; 35 | ts = "none"; 36 | }; 37 | defaultFormatterOptions = {}; 38 | scriptInitialIndent = false; 39 | styleInitialIndent = false; 40 | } 41 | }; 42 | css = {}; 43 | html = { 44 | suggest = {}; 45 | }; 46 | javascript = { 47 | format = {}; 48 | }; 49 | typescript = { 50 | format = {}; 51 | }; 52 | emmet = {}; 53 | stylusSupremacy = {}; 54 | }; 55 | }; 56 | }; 57 | on_new_config = function(new_config) 58 | local install_info = installer.info() 59 | if install_info.is_installed then 60 | if type(new_config.cmd) == 'table' then 61 | -- Try to preserve any additional args from upstream changes. 62 | new_config.cmd[1] = install_info.binaries[bin_name] 63 | else 64 | new_config.cmd = {install_info.binaries[bin_name]} 65 | end 66 | end 67 | end; 68 | docs = { 69 | package_json = "https://raw.githubusercontent.com/vuejs/vetur/master/package.json"; 70 | description = [[ 71 | https://github.com/vuejs/vetur/tree/master/server 72 | 73 | Vue language server(vls) 74 | `vue-language-server` can be installed via `:LspInstall vuels` or by yourself with `npm`: 75 | ```sh 76 | npm install -g vls 77 | ``` 78 | ]]; 79 | default_config = { 80 | root_dir = [[root_pattern("package.json", "vue.config.js")]]; 81 | init_options = { 82 | config = { 83 | vetur = { 84 | useWorkspaceDependencies = false; 85 | validation = { 86 | template = true; 87 | style = true; 88 | script = true; 89 | }; 90 | completion = { 91 | autoImport = false; 92 | useScaffoldSnippets = false; 93 | tagCasing = "kebab"; 94 | }; 95 | format = { 96 | defaultFormatter = { 97 | js = "none"; 98 | ts = "none"; 99 | }; 100 | defaultFormatterOptions = {}; 101 | scriptInitialIndent = false; 102 | styleInitialIndent = false; 103 | } 104 | }; 105 | css = {}; 106 | html = { 107 | suggest = {}; 108 | }; 109 | javascript = { 110 | format = {}; 111 | }; 112 | typescript = { 113 | format = {}; 114 | }; 115 | emmet = {}; 116 | stylusSupremacy = {}; 117 | }; 118 | }; 119 | }; 120 | }; 121 | } 122 | 123 | configs[server_name].install = installer.install 124 | configs[server_name].install_info = installer.info 125 | -- vim:et ts=2 sw=2 126 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/langs/sumneko_lua.lua: -------------------------------------------------------------------------------- 1 | local configs = require 'nvim-lspinstall/configs' 2 | local util = require 'nvim-lspinstall/util' 3 | 4 | local name = "sumneko_lua" 5 | local bin_name = "lua-language-server" 6 | 7 | local function make_installer() 8 | local P = util.path.join 9 | local install_dir = P{util.base_install_dir, name} 10 | local git_dir = P{install_dir, bin_name} 11 | local os, bin, ninja_zip, build_file 12 | 13 | if vim.fn.has('osx') == 1 then 14 | os = 'macOS' 15 | bin = P{git_dir, "bin", "macOS", bin_name} 16 | ninja_zip = "ninja-mac.zip" 17 | build_file = "macos.ninja" 18 | elseif vim.fn.has('unix') == 1 then 19 | os = 'Linux' 20 | bin = P{git_dir, "bin", "Linux", bin_name} 21 | ninja_zip = "ninja-linux.zip" 22 | build_file = "linux.ninja" 23 | end 24 | local main_file = P{git_dir, "main.lua"} 25 | local cmd = {bin, '-E', main_file} 26 | 27 | local X = {} 28 | function X.install() 29 | if os == nil then 30 | error("This installer supports Linux and macOS only") 31 | return 32 | end 33 | local install_info = X.info() 34 | if install_info.is_installed then 35 | print(name, "is already installed") 36 | return 37 | end 38 | if not (util.has_bins("ninja") or util.has_bins("curl")) then 39 | error('Need either "ninja" or "curl" (to download ninja) to install this.') 40 | return 41 | end 42 | if not util.has_bins("sh", "chmod", "unzip", "clang") then 43 | error('Need the binaries "sh", "chmod", "unzip", "clang" to install this') 44 | return 45 | end 46 | local script = [=[ 47 | set -e 48 | bin_name=]=]..bin_name..'\n'..[=[ 49 | ninja_zip=]=]..ninja_zip..'\n'..[=[ 50 | build_file=]=]..build_file..'\n'..[=[ 51 | 52 | # Install ninja if not available. 53 | which ninja >/dev/null || { 54 | test -x ninja || { 55 | curl -LO https://github.com/ninja-build/ninja/releases/download/v1.9.0/$ninja_zip 56 | unzip $ninja_zip 57 | chmod +x ninja 58 | } 59 | export PATH="$PWD:$PATH" 60 | } 61 | 62 | # clone project 63 | git clone https://github.com/sumneko/$bin_name 64 | cd $bin_name 65 | git submodule update --init --recursive 66 | 67 | # build 68 | cd 3rd/luamake 69 | ninja -f ninja/$build_file 70 | cd ../.. 71 | ./3rd/luamake/luamake rebuild 72 | ]=] 73 | vim.fn.mkdir(install_info.install_dir, "p") 74 | util.sh(script, install_info.install_dir) 75 | end 76 | function X.info() 77 | return { 78 | is_installed = util.has_bins(bin); 79 | install_dir = install_dir; 80 | cmd = cmd; 81 | } 82 | end 83 | function X.configure(config) 84 | local install_info = X.info() 85 | if install_info.is_installed then 86 | config.cmd = cmd 87 | end 88 | end 89 | return X 90 | end 91 | 92 | local installer = make_installer() 93 | 94 | configs[name] = { 95 | default_config = { 96 | filetypes = {'lua'}; 97 | root_dir = function(fname) 98 | return util.find_git_ancestor(fname) or util.path.dirname(fname) 99 | end; 100 | log_level = vim.lsp.protocol.MessageType.Warning; 101 | }; 102 | on_new_config = function(config) 103 | installer.configure(config) 104 | end; 105 | docs = { 106 | package_json = "https://raw.githubusercontent.com/sumneko/vscode-lua/master/package.json"; 107 | description = [[ 108 | https://github.com/sumneko/lua-language-server 109 | 110 | Lua language server. **By default, this doesn't have a `cmd` set.** This is 111 | because it doesn't provide a global binary. We provide an installer for Linux 112 | and macOS using `:LspInstall`. If you wish to install it yourself, [here is a 113 | guide](https://github.com/sumneko/lua-language-server/wiki/Build-and-Run-(Standalone)). 114 | So you should set `cmd` yourself like this. 115 | 116 | ```lua 117 | require'lspconfig'.sumneko_lua.setup{ 118 | cmd = {"path", "to", "cmd"}; 119 | ... 120 | } 121 | ``` 122 | 123 | If you install via our installer, if you execute `:LspInstallInfo sumneko_lua`, you can know `cmd` value. 124 | 125 | The settings of the language server can also be overridden. This is especially useful 126 | when developing on the lua components of neovim. Some recommended settings are as follows: 127 | 128 | ```lua 129 | require'lspconfig'.sumneko_lua.setup { 130 | settings = { 131 | Lua = { 132 | runtime = { 133 | -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim) 134 | version = 'LuaJIT', 135 | -- Setup your lua path 136 | path = vim.split(package.path, ';'), 137 | }, 138 | diagnostics = { 139 | -- Get the language server to recognize the `vim` global 140 | globals = {'vim'}, 141 | }, 142 | workspace = { 143 | -- Make the server aware of Neovim runtime files 144 | library = { 145 | [vim.fn.expand('$VIMRUNTIME/lua')] = true, 146 | [vim.fn.expand('$VIMRUNTIME/lua/vim/lsp')] = true, 147 | }, 148 | }, 149 | }, 150 | }, 151 | } 152 | ``` 153 | ]]; 154 | default_config = { 155 | root_dir = [[root_pattern(".git") or bufdir]]; 156 | }; 157 | }; 158 | } 159 | 160 | configs[name].install = installer.install 161 | configs[name].install_info = installer.info 162 | -- vim:et ts=2 163 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/configs.lua: -------------------------------------------------------------------------------- 1 | local log = require 'vim.lsp.log' 2 | local util = require 'nvim-lspinstall/util' 3 | local api, validate, lsp = vim.api, vim.validate, vim.lsp 4 | local tbl_extend = vim.tbl_extend 5 | 6 | local configs = {} 7 | 8 | function configs.__newindex(t, config_name, config_def) 9 | validate { 10 | name = {config_name, 's'}; 11 | default_config = {config_def.default_config, 't'}; 12 | on_new_config = {config_def.on_new_config, 'f', true}; 13 | on_attach = {config_def.on_attach, 'f', true}; 14 | commands = {config_def.commands, 't', true}; 15 | } 16 | if config_def.commands then 17 | for k, v in pairs(config_def.commands) do 18 | validate { 19 | ['command.name'] = {k, 's'}; 20 | ['command.fn'] = {v[1], 'f'}; 21 | } 22 | end 23 | else 24 | config_def.commands = {} 25 | end 26 | 27 | local M = {} 28 | 29 | local default_config = tbl_extend("keep", config_def.default_config, util.default_config) 30 | 31 | -- Force this part. 32 | default_config.name = config_name 33 | 34 | -- The config here is the one which will be instantiated for the new server, 35 | -- which is why this is a function, so that it can refer to the settings 36 | -- object on the server. 37 | local function add_handlers(config) 38 | assert(not config.callbacks, "lsp.callbacks has been obsoleted. See here for more: https://github.com/neovim/neovim/pull/12655") 39 | config.handlers["window/logMessage"] = function(err, method, params, client_id) 40 | if params and params.type <= config.log_level then 41 | -- TODO(ashkan) remove this after things have settled. 42 | assert(lsp.handlers["window/logMessage"], "Handler for window/logMessage notification is not defined") 43 | lsp.handlers["window/logMessage"](err, method, params, client_id) 44 | end 45 | end 46 | 47 | config.handlers["window/showMessage"] = function(err, method, params, client_id) 48 | if params and params.type <= config.message_level then 49 | -- TODO(ashkan) remove this after things have settled. 50 | assert(lsp.handlers["window/showMessage"], "Handler for window/showMessage notification is not defined") 51 | lsp.handlers["window/showMessage"](err, method, params, client_id) 52 | end 53 | end 54 | 55 | -- pyright and jdtls ignore dynamicRegistration settings and sent client/registerCapability handler which are unhandled 56 | config.handlers['client/registerCapability'] = function(_, _, _, _) 57 | log.warn(string.format( [[ 58 | The language server %s incorrectly triggers a registerCapability handler 59 | despite dynamicRegistration set to false. Please report upstream. 60 | ]] , config.name)) 61 | return { 62 | result = nil; 63 | error = nil; 64 | } 65 | end 66 | 67 | config.handlers["workspace/configuration"] = function(err, method, params, client_id) 68 | if err then error(tostring(err)) end 69 | if not params.items then 70 | return {} 71 | end 72 | 73 | local result = {} 74 | for _, item in ipairs(params.items) do 75 | if item.section then 76 | local value = util.lookup_section(config.settings, item.section) or vim.NIL 77 | -- For empty sections with no explicit '' key, return settings as is 78 | if value == vim.NIL and item.section == '' then 79 | value = config.settings or vim.NIL 80 | end 81 | table.insert(result, value) 82 | end 83 | end 84 | return result 85 | end 86 | end 87 | 88 | function M.setup(config) 89 | validate { 90 | root_dir = {config.root_dir, 'f', default_config.root_dir ~= nil}; 91 | filetypes = {config.filetype, 't', true}; 92 | on_new_config = {config.on_new_config, 'f', true}; 93 | on_attach = {config.on_attach, 'f', true}; 94 | commands = {config.commands, 't', true}; 95 | } 96 | if config.commands then 97 | for k, v in pairs(config.commands) do 98 | validate { 99 | ['command.name'] = {k, 's'}; 100 | ['command.fn'] = {v[1], 'f'}; 101 | } 102 | end 103 | end 104 | 105 | config = tbl_extend("keep", config, default_config) 106 | 107 | local trigger 108 | if config.filetypes then 109 | trigger = "FileType "..table.concat(config.filetypes, ',') 110 | else 111 | trigger = "BufReadPost *" 112 | end 113 | api.nvim_command(string.format( 114 | "autocmd %s lua require'lspconfig'[%q].manager.try_add()" 115 | , trigger 116 | , config.name 117 | )) 118 | 119 | local get_root_dir = config.root_dir 120 | 121 | -- In the case of a reload, close existing things. 122 | if M.manager then 123 | for _, client in ipairs(M.manager.clients()) do 124 | client.stop(true) 125 | end 126 | M.manager = nil 127 | end 128 | 129 | local make_config = function(_root_dir) 130 | local new_config = util.tbl_deep_extend("keep", vim.empty_dict(), config) 131 | new_config = util.tbl_deep_extend('keep', new_config, default_config) 132 | new_config.capabilities = new_config.capabilities or lsp.protocol.make_client_capabilities() 133 | new_config.capabilities = util.tbl_deep_extend('keep', new_config.capabilities, { 134 | workspace = { 135 | configuration = true; 136 | } 137 | }) 138 | 139 | add_handlers(new_config) 140 | if config_def.on_new_config then 141 | pcall(config_def.on_new_config, new_config, _root_dir) 142 | end 143 | if config.on_new_config then 144 | pcall(config.on_new_config, new_config, _root_dir) 145 | end 146 | 147 | new_config.on_init = util.add_hook_after(new_config.on_init, function(client, _result) 148 | function client.workspace_did_change_configuration(settings) 149 | if not settings then return end 150 | if vim.tbl_isempty(settings) then 151 | settings = {[vim.type_idx]=vim.types.dictionary} 152 | end 153 | return client.notify('workspace/didChangeConfiguration', { 154 | settings = settings; 155 | }) 156 | end 157 | if not vim.tbl_isempty(new_config.settings) then 158 | client.workspace_did_change_configuration(new_config.settings) 159 | end 160 | end) 161 | 162 | -- Save the old _on_attach so that we can reference it via the BufEnter. 163 | new_config._on_attach = new_config.on_attach 164 | new_config.on_attach = vim.schedule_wrap(function(client, bufnr) 165 | if bufnr == api.nvim_get_current_buf() then 166 | M._setup_buffer(client.id) 167 | else 168 | api.nvim_command(string.format( 169 | "autocmd BufEnter ++once lua require'lspconfig'[%q]._setup_buffer(%d)" 170 | , bufnr 171 | , config_name 172 | , client.id 173 | )) 174 | end 175 | end) 176 | 177 | new_config.root_dir = _root_dir 178 | return new_config 179 | end 180 | 181 | local manager = util.server_per_root_dir_manager(function(_root_dir) 182 | return make_config(_root_dir) 183 | end) 184 | 185 | function manager.try_add() 186 | if vim.bo.buftype == 'nofile' then 187 | return 188 | end 189 | local root_dir = get_root_dir(api.nvim_buf_get_name(0), api.nvim_get_current_buf()) 190 | local id = manager.add(root_dir) 191 | if id then 192 | lsp.buf_attach_client(0, id) 193 | end 194 | end 195 | 196 | M.manager = manager 197 | M.make_config = make_config 198 | end 199 | 200 | function M._setup_buffer(client_id) 201 | local client = lsp.get_client_by_id(client_id) 202 | if client.config._on_attach then 203 | client.config._on_attach(client) 204 | end 205 | if client.config.commands and not vim.tbl_isempty(client.config.commands) then 206 | M.commands = util.tbl_deep_extend("force", M.commands, client.config.commands) 207 | end 208 | if not M.commands_created and not vim.tbl_isempty(M.commands) then 209 | -- Create the module commands 210 | util.create_module_commands(config_name, M.commands) 211 | M.commands_created = true 212 | end 213 | end 214 | 215 | M.commands_created = false 216 | M.commands = config_def.commands 217 | M.name = config_name 218 | M.document_config = config_def 219 | 220 | rawset(t, config_name, M) 221 | 222 | return M 223 | end 224 | 225 | return setmetatable({}, configs) 226 | -- vim:et ts=2 sw=2 227 | -------------------------------------------------------------------------------- /lua/nvim-lspinstall/util.lua: -------------------------------------------------------------------------------- 1 | local vim = vim 2 | local validate = vim.validate 3 | local api = vim.api 4 | local lsp = vim.lsp 5 | local uv = vim.loop 6 | local fn = vim.fn 7 | 8 | local M = {} 9 | 10 | M.default_config = { 11 | log_level = lsp.protocol.MessageType.Warning; 12 | message_level = lsp.protocol.MessageType.Warning; 13 | settings = vim.empty_dict(); 14 | init_options = vim.empty_dict(); 15 | handlers = {}; 16 | } 17 | 18 | function M.validate_bufnr(bufnr) 19 | validate { 20 | bufnr = { bufnr, 'n' } 21 | } 22 | return bufnr == 0 and api.nvim_get_current_buf() or bufnr 23 | end 24 | 25 | function M.add_hook_before(func, new_fn) 26 | if func then 27 | return function(...) 28 | -- TODO which result? 29 | new_fn(...) 30 | return func(...) 31 | end 32 | else 33 | return new_fn 34 | end 35 | end 36 | 37 | function M.add_hook_after(func, new_fn) 38 | if func then 39 | return function(...) 40 | -- TODO which result? 41 | func(...) 42 | return new_fn(...) 43 | end 44 | else 45 | return new_fn 46 | end 47 | end 48 | 49 | function M.tbl_deep_extend(behavior, ...) 50 | if (behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force') then 51 | error('invalid "behavior": '..tostring(behavior)) 52 | end 53 | 54 | if select('#', ...) < 2 then 55 | error('wrong number of arguments (given '..tostring(1 + select('#', ...))..', expected at least 3)') 56 | end 57 | 58 | local ret = {} 59 | if vim._empty_dict_mt ~= nil and getmetatable(select(1, ...)) == vim._empty_dict_mt then 60 | ret = vim.empty_dict() 61 | end 62 | 63 | for i = 1, select('#', ...) do 64 | local tbl = select(i, ...) 65 | vim.validate{["after the second argument"] = {tbl,'t'}} 66 | if tbl then 67 | for k, v in pairs(tbl) do 68 | if type(v) == 'table' and not vim.tbl_islist(v) then 69 | ret[k] = M.tbl_deep_extend(behavior, ret[k] or vim.empty_dict(), v) 70 | elseif behavior ~= 'force' and ret[k] ~= nil then 71 | if behavior == 'error' then 72 | error('key found in more than one map: '..k) 73 | end -- Else behavior is "keep". 74 | else 75 | ret[k] = v 76 | end 77 | end 78 | end 79 | end 80 | return ret 81 | end 82 | 83 | function M.nvim_multiline_command(command) 84 | validate { command = { command, 's' } } 85 | for line in vim.gsplit(command, "\n", true) do 86 | api.nvim_command(line) 87 | end 88 | end 89 | 90 | function M.lookup_section(settings, section) 91 | for part in vim.gsplit(section, '.', true) do 92 | settings = settings[part] 93 | if not settings then 94 | return 95 | end 96 | end 97 | return settings 98 | end 99 | 100 | function M.create_module_commands(module_name, commands) 101 | for command_name, def in pairs(commands) do 102 | local parts = {"command!"} 103 | -- Insert attributes. 104 | for k, v in pairs(def) do 105 | if type(k) == 'string' and type(v) == 'boolean' and v then 106 | table.insert(parts, "-"..k) 107 | elseif type(k) == 'number' and type(v) == 'string' and v:match("^%-") then 108 | table.insert(parts, v) 109 | end 110 | end 111 | table.insert(parts, command_name) 112 | -- The command definition. 113 | table.insert(parts, 114 | string.format("lua require'lspconfig'[%q].commands[%q][1]()", module_name, command_name)) 115 | api.nvim_command(table.concat(parts, " ")) 116 | end 117 | end 118 | 119 | function M.has_bins(...) 120 | for i = 1, select("#", ...) do 121 | if 0 == fn.executable((select(i, ...))) then 122 | return false 123 | end 124 | end 125 | return true 126 | end 127 | 128 | -- Some path utilities 129 | M.path = (function() 130 | local function exists(filename) 131 | local stat = uv.fs_stat(filename) 132 | return stat and stat.type or false 133 | end 134 | 135 | local function is_dir(filename) 136 | return exists(filename) == 'directory' 137 | end 138 | 139 | local function is_file(filename) 140 | return exists(filename) == 'file' 141 | end 142 | 143 | local is_windows = uv.os_uname().version:match("Windows") 144 | local path_sep = is_windows and "\\" or "/" 145 | 146 | local is_fs_root 147 | if is_windows then 148 | is_fs_root = function(path) 149 | return path:match("^%a:$") 150 | end 151 | else 152 | is_fs_root = function(path) 153 | return path == "/" 154 | end 155 | end 156 | 157 | local function is_absolute(filename) 158 | if is_windows then 159 | return filename:match("^%a:") or filename:match("^\\\\") 160 | else 161 | return filename:match("^/") 162 | end 163 | end 164 | 165 | local dirname 166 | do 167 | local strip_dir_pat = path_sep.."([^"..path_sep.."]+)$" 168 | local strip_sep_pat = path_sep.."$" 169 | dirname = function(path) 170 | if not path then return end 171 | local result = path:gsub(strip_sep_pat, ""):gsub(strip_dir_pat, "") 172 | if #result == 0 then 173 | return "/" 174 | end 175 | return result 176 | end 177 | end 178 | 179 | local function path_join(...) 180 | local result = 181 | table.concat( 182 | vim.tbl_flatten {...}, path_sep):gsub(path_sep.."+", path_sep) 183 | return result 184 | end 185 | 186 | -- Traverse the path calling cb along the way. 187 | local function traverse_parents(path, cb) 188 | path = uv.fs_realpath(path) 189 | local dir = path 190 | -- Just in case our algo is buggy, don't infinite loop. 191 | for _ = 1, 100 do 192 | dir = dirname(dir) 193 | if not dir then return end 194 | -- If we can't ascend further, then stop looking. 195 | if cb(dir, path) then 196 | return dir, path 197 | end 198 | if is_fs_root(dir) then 199 | break 200 | end 201 | end 202 | end 203 | 204 | -- Iterate the path until we find the rootdir. 205 | local function iterate_parents(path) 206 | path = uv.fs_realpath(path) 207 | local function it(s, v) 208 | if not v then return end 209 | if is_fs_root(v) then return end 210 | return dirname(v), path 211 | end 212 | return it, path, path 213 | end 214 | 215 | local function is_descendant(root, path) 216 | if (not path) then 217 | return false; 218 | end 219 | 220 | local function cb(dir, _) 221 | return dir == root; 222 | end 223 | 224 | local dir, _ = traverse_parents(path, cb); 225 | 226 | return dir == root; 227 | end 228 | 229 | return { 230 | is_dir = is_dir; 231 | is_file = is_file; 232 | is_absolute = is_absolute; 233 | exists = exists; 234 | sep = path_sep; 235 | dirname = dirname; 236 | join = path_join; 237 | traverse_parents = traverse_parents; 238 | iterate_parents = iterate_parents; 239 | is_descendant = is_descendant; 240 | } 241 | end)() 242 | 243 | 244 | -- Returns a function(root_dir), which, when called with a root_dir it hasn't 245 | -- seen before, will call make_config(root_dir) and start a new client. 246 | function M.server_per_root_dir_manager(_make_config) 247 | local clients = {} 248 | local manager = {} 249 | 250 | function manager.add(root_dir) 251 | if not root_dir then return end 252 | if not M.path.is_dir(root_dir) then return end 253 | 254 | -- Check if we have a client alredy or start and store it. 255 | local client_id = clients[root_dir] 256 | if not client_id then 257 | local new_config = _make_config(root_dir) 258 | new_config.on_exit = M.add_hook_before(new_config.on_exit, function() 259 | clients[root_dir] = nil 260 | end) 261 | client_id = lsp.start_client(new_config) 262 | clients[root_dir] = client_id 263 | end 264 | return client_id 265 | end 266 | 267 | function manager.clients() 268 | local res = {} 269 | for _, id in pairs(clients) do 270 | local client = lsp.get_client_by_id(id) 271 | if client then 272 | table.insert(res, client) 273 | end 274 | end 275 | return res 276 | end 277 | 278 | return manager 279 | end 280 | 281 | function M.search_ancestors(startpath, func) 282 | validate { func = {func, 'f'} } 283 | if func(startpath) then return startpath end 284 | for path in M.path.iterate_parents(startpath) do 285 | if func(path) then return path end 286 | end 287 | end 288 | 289 | function M.root_pattern(...) 290 | local patterns = vim.tbl_flatten {...} 291 | local function matcher(path) 292 | for _, pattern in ipairs(patterns) do 293 | if M.path.exists(vim.fn.glob(M.path.join(path, pattern))) then 294 | return path 295 | end 296 | end 297 | end 298 | return function(startpath) 299 | return M.search_ancestors(startpath, matcher) 300 | end 301 | end 302 | function M.find_git_ancestor(startpath) 303 | return M.search_ancestors(startpath, function(path) 304 | if M.path.is_dir(M.path.join(path, ".git")) then 305 | return path 306 | end 307 | end) 308 | end 309 | function M.find_node_modules_ancestor(startpath) 310 | return M.search_ancestors(startpath, function(path) 311 | if M.path.is_dir(M.path.join(path, "node_modules")) then 312 | return path 313 | end 314 | end) 315 | end 316 | function M.find_package_json_ancestor(startpath) 317 | return M.search_ancestors(startpath, function(path) 318 | if M.path.is_file(M.path.join(path, "package.json")) then 319 | return path 320 | end 321 | end) 322 | end 323 | 324 | local function validate_string_list(t) 325 | for _, v in ipairs(t) do 326 | if type(v) ~= 'string' then 327 | return false 328 | end 329 | end 330 | return true 331 | end 332 | 333 | local function map_list(t, func) 334 | local res = {} 335 | for i, v in ipairs(t) do table.insert(res, func(v, i)) end 336 | return res 337 | end 338 | 339 | local function zip_lists_to_map(a, b) 340 | assert(#a == #b) 341 | local res = {} 342 | for i = 1, #a do res[a[i]] = b[i] end 343 | return res 344 | end 345 | 346 | local base_install_dir = M.path.join(fn.stdpath("cache"), "lspconfig") 347 | M.base_install_dir = base_install_dir 348 | function M.npm_installer(config) 349 | validate { 350 | server_name = {config.server_name, 's'}; 351 | packages = {config.packages, validate_string_list, 'List of npm package names'}; 352 | binaries = {config.binaries, validate_string_list, 'List of binary names'}; 353 | post_install_script = {config.post_install_script, 's', true}; 354 | } 355 | 356 | local install_dir = M.path.join(base_install_dir, config.server_name) 357 | local bin_dir = M.path.join(install_dir, "node_modules", ".bin") 358 | local function bin_path(name) 359 | return M.path.join(bin_dir, name) 360 | end 361 | 362 | local binary_paths = map_list(config.binaries, bin_path) 363 | 364 | local function get_install_info() 365 | return { 366 | bin_dir = bin_dir; 367 | install_dir = install_dir; 368 | binaries = zip_lists_to_map(config.binaries, binary_paths); 369 | is_installed = M.has_bins(unpack(binary_paths)); 370 | } 371 | end 372 | 373 | local function install() 374 | -- TODO(ashkan) need all binaries or just the first? 375 | if M.has_bins(unpack(config.binaries)) then 376 | return print(config.server_name, "is already installed (not by Nvim)") 377 | end 378 | if not M.has_bins("sh", "npm", "mkdir") then 379 | api.nvim_err_writeln('Installation requires "sh", "npm", "mkdir"') 380 | return 381 | end 382 | if get_install_info().is_installed then 383 | return print(config.server_name, "is already installed") 384 | end 385 | local install_params = { 386 | packages = table.concat(config.packages, ' '); 387 | install_dir = install_dir; 388 | post_install_script = config.post_install_script or ''; 389 | } 390 | local cmd = io.popen("sh", "w") 391 | local install_script = ([[ 392 | set -e 393 | mkdir -p "{{install_dir}}" 394 | cd "{{install_dir}}" 395 | [ ! -f package.json ] && npm init -y 396 | npm install {{packages}} --no-package-lock --no-save --production 397 | {{post_install_script}} 398 | ]]):gsub("{{(%S+)}}", install_params) 399 | cmd:write(install_script) 400 | cmd:close() 401 | if not get_install_info().is_installed then 402 | api.nvim_err_writeln('Installation of ' .. config.server_name .. ' failed') 403 | end 404 | end 405 | 406 | return { 407 | install = install; 408 | info = get_install_info; 409 | } 410 | end 411 | 412 | function M.sh(script, cwd) 413 | assert(cwd and M.path.is_dir(cwd), "sh: Invalid directory") 414 | -- switching to insert mode makes the buffer scroll as new output is added 415 | -- and makes it easy and intuitive to cancel the operation with Ctrl-C 416 | api.nvim_command("10new | startinsert") 417 | local bufnr = api.nvim_get_current_buf() 418 | local function on_exit(job_id, code, event_type) 419 | if code == 0 then 420 | api.nvim_command("silent bwipeout! "..bufnr) 421 | end 422 | end 423 | fn.termopen({"sh", "-c", script}, {cwd = cwd, on_exit = on_exit}) 424 | end 425 | 426 | function M.format_vspackage_url(extension_name) 427 | local org, package = unpack(vim.split(extension_name, ".", true)) 428 | assert(org and package) 429 | return string.format("https://marketplace.visualstudio.com/_apis/public/gallery/publishers/%s/vsextensions/%s/latest/vspackage", org, package) 430 | end 431 | 432 | 433 | function M.utf8_config(config) 434 | config.capabilities = config.capabilities or lsp.protocol.make_client_capabilities() 435 | config.capabilities.offsetEncoding = {"utf-8", "utf-16"} 436 | function config.on_init(client, result) 437 | if result.offsetEncoding then 438 | client.offset_encoding = result.offsetEncoding 439 | end 440 | end 441 | return config 442 | end 443 | 444 | return M 445 | --------------------------------------------------------------------------------