diff options
| author | Hirokazu Hata <h.hata.ai.t@gmail.com> | 2020-09-06 17:49:21 +0900 |
|---|---|---|
| committer | Hirokazu Hata <h.hata.ai.t@gmail.com> | 2020-09-06 17:49:21 +0900 |
| commit | ddcd9e6aae50d6397e43e4fc9ba0cf7a82cc79de (patch) | |
| tree | c301c7a765535dcb5387d76cc71e28d845dcce23 /lua/lspconfig | |
| parent | Merge pull request #238 from steelsojka/angular-ls (diff) | |
| download | nvim-lspconfig-ddcd9e6aae50d6397e43e4fc9ba0cf7a82cc79de.tar nvim-lspconfig-ddcd9e6aae50d6397e43e4fc9ba0cf7a82cc79de.tar.gz nvim-lspconfig-ddcd9e6aae50d6397e43e4fc9ba0cf7a82cc79de.tar.bz2 nvim-lspconfig-ddcd9e6aae50d6397e43e4fc9ba0cf7a82cc79de.tar.lz nvim-lspconfig-ddcd9e6aae50d6397e43e4fc9ba0cf7a82cc79de.tar.xz nvim-lspconfig-ddcd9e6aae50d6397e43e4fc9ba0cf7a82cc79de.tar.zst nvim-lspconfig-ddcd9e6aae50d6397e43e4fc9ba0cf7a82cc79de.zip | |
Rename nvim_lsp to lspconfig
Diffstat (limited to 'lua/lspconfig')
57 files changed, 3547 insertions, 0 deletions
diff --git a/lua/lspconfig/als.lua b/lua/lspconfig/als.lua new file mode 100755 index 00000000..c5c4a116 --- /dev/null +++ b/lua/lspconfig/als.lua @@ -0,0 +1,104 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' +local server_name = 'als' +local bin_name = 'ada_language_server' + +if vim.fn.has('win32') == 1 then + bin_name = 'ada_language_server.exe' +end + +local function make_installer() + local install_dir = util.path.join{util.base_install_dir, server_name} + + local url = 'https://dl.bintray.com/reznikmm/ada-language-server/linux-latest.tar.gz' + local download_target = util.path.join{install_dir, "als.tar.gz"} + local extracted_dir = "linux" + local extract_cmd = string.format("tar -xzf '%s' --one-top-level='%s'", download_target, install_dir) + + if vim.fn.has('win32') == 1 then + url = 'https://dl.bintray.com/reznikmm/ada-language-server/win32-latest.zip' + download_target = util.path.join{install_dir, 'win32-latest.zip'} + extracted_dir = 'win32' + extract_cmd = string.format("unzip -o '%s' -d '%s'", download_target, install_dir) + elseif vim.fn.has('mac') == 1 then + url = 'https://dl.bintray.com/reznikmm/ada-language-server/darwin-latest.tar.gz' + download_target = util.path.join{install_dir, 'darwin-latest.tar.gz'} + extracted_dir = 'darwin' + extract_cmd = string.format("tar -xzf '%s' --one-top-level='%s'", download_target, install_dir) + end + + local download_cmd = string.format('curl -fLo "%s" --create-dirs "%s"', download_target, url) + + local bin_path = util.path.join{install_dir, extracted_dir, bin_name} + local X = {} + function X.install() + local install_info = X.info() + if install_info.is_installed then + print(server_name, "is already installed") + return + end + if not (util.has_bins("curl")) then + error('Need "curl" to install this.') + return + end + vim.fn.mkdir(install_dir, 'p') + vim.fn.system(download_cmd) + vim.fn.system(extract_cmd) + end + function X.info() + return { + is_installed = util.path.exists(bin_path); + install_dir = install_dir; + cmd = { bin_path }; + } + end + function X.configure(config) + local install_info = X.info() + if install_info.is_installed then + config.cmd = install_info.cmd + end + end + return X +end + +local installer = make_installer() + +configs[server_name] = { + default_config = { + cmd = {bin_name}; + filetypes = {"ada"}; + -- *.gpr and *.adc would be nice to have here + root_dir = util.root_pattern("Makefile", ".git"); + }; + on_new_config = function(config) + installer.configure(config) + end; + docs = { + package_json = "https://raw.githubusercontent.com/AdaCore/ada_language_server/master/integration/vscode/ada/package.json"; + description = [[ +https://github.com/AdaCore/ada_language_server + +Ada language server. Use `LspInstall als` to install it. + +Can be configured by passing a "settings" object to `als.setup{}`: + +```lua +require('lspconfig').als.setup{ + settings = { + ada = { + projectFile = "project.gpr"; + scenarioVariables = { ... }; + } + } +} +``` +]]; + default_config = { + root_dir = [[util.root_pattern("Makefile", ".git")]]; + }; + }; +}; + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/angularls.lua b/lua/lspconfig/angularls.lua new file mode 100644 index 00000000..f579909f --- /dev/null +++ b/lua/lspconfig/angularls.lua @@ -0,0 +1,72 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = 'angularls' +local bin_name = server_name +local install_loc = util.base_install_dir .. '/' .. server_name +local script_loc = install_loc .. '/node_modules/@angular/language-server/index.js' +local bin_loc = install_loc .. '/node_modules/.bin/angularls' + +local installer = util.npm_installer { + server_name = server_name; + packages = { '@angular/language-server' }; + binaries = { bin_name }; + -- angular-language-service doesn't expose a binary, so we create an execution wrapper. + post_install_script = + 'echo "#! /bin/sh\n' .. 'node ' .. script_loc .. ' \\$*' .. '" > ' .. bin_loc .. '\n' .. + 'chmod +x ' .. bin_loc; +} + +-- Angular requires a node_modules directory to probe for @angular/language-service and typescript +-- in order to use your projects configured versions. +-- This defaults to the vim cwd, but will get overwritten by the resolved root of the file. +local function get_probe_dir(root_dir) + local project_root = util.find_node_modules_ancestor(root_dir) + + return project_root and (project_root .. '/node_modules') or '' +end + +local default_probe_dir = get_probe_dir(vim.fn.getcwd()) + +configs[server_name] = { + default_config = { + cmd = { + bin_loc, + '--stdio', + '--tsProbeLocations', default_probe_dir, + '--ngProbeLocations', default_probe_dir + }; + filetypes = {'typescript', 'html', 'typescriptreact', 'typescript.tsx'}; + -- Check for angular.json or .git first since that is the root of the project. + -- Don't check for tsconfig.json or package.json since there are multiple of these + -- in an angular monorepo setup. + root_dir = util.root_pattern('angular.json', '.git'); + }; + on_new_config = function(new_config, new_root_dir) + local new_probe_dir = get_probe_dir(new_root_dir) + + -- We need to check our probe directories because they may have changed. + new_config.cmd = { + bin_loc, + '--stdio', + '--tsProbeLocations', new_probe_dir, + '--ngProbeLocations', new_probe_dir + } + end; + docs = { + description = [[ +https://github.com/angular/vscode-ng-language-service + +`angular-language-server` can be installed via `:LspInstall angularls` + +If you prefer to install this yourself you can through npm `npm install @angular/language-server`. +Be aware there is no global binary and must be run via `node_modules/@angular/language-server/index.js` + ]]; + default_config = { + root_dir = [[root_pattern("angular.json", ".git")]]; + }; + } +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info diff --git a/lua/lspconfig/bashls.lua b/lua/lspconfig/bashls.lua new file mode 100644 index 00000000..59b21079 --- /dev/null +++ b/lua/lspconfig/bashls.lua @@ -0,0 +1,44 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +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}; +} + +configs[server_name] = { + default_config = { + cmd = {"bash-language-server", "start"}; + filetypes = {"sh"}; + root_dir = util.path.dirname; + }; + 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; + docs = { + description = [[ +https://github.com/mads-hartmann/bash-language-server + +Language server for bash, written using tree sitter in typescript. +]]; + default_config = { + root_dir = "vim's starting directory"; + }; + }; +}; + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/ccls.lua b/lua/lspconfig/ccls.lua new file mode 100644 index 00000000..d167249d --- /dev/null +++ b/lua/lspconfig/ccls.lua @@ -0,0 +1,24 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.ccls = { + default_config = { + cmd = {"ccls"}; + filetypes = {"c", "cpp", "objc", "objcpp"}; + root_dir = util.root_pattern("compile_commands.json", "compile_flags.txt", ".git"); + }; + docs = { + package_json = "https://raw.githubusercontent.com/MaskRay/vscode-ccls/master/package.json"; + description = [[ +https://github.com/MaskRay/ccls/wiki + +ccls 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. +For details on how to automatically generate one using CMake look [here](https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html). +]]; + default_config = { + root_dir = [[root_pattern("compile_commands.json", "compile_flags.txt", ".git")]]; + }; + }; +} +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/clangd.lua b/lua/lspconfig/clangd.lua new file mode 100644 index 00000000..d165edbb --- /dev/null +++ b/lua/lspconfig/clangd.lua @@ -0,0 +1,60 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +-- https://clangd.llvm.org/extensions.html#switch-between-sourceheader +local function switch_source_header(bufnr) + bufnr = util.validate_bufnr(bufnr) + local params = { uri = vim.uri_from_bufnr(bufnr) } + vim.lsp.buf_request(bufnr, 'textDocument/switchSourceHeader', params, function(err, _, result) + if err then error(tostring(err)) end + if not result then print ("Corresponding file can’t be determined") return end + vim.api.nvim_command('edit '..vim.uri_to_fname(result)) + end) +end + +local root_pattern = util.root_pattern("compile_commands.json", "compile_flags.txt", ".git") +configs.clangd = { + default_config = util.utf8_config { + cmd = {"clangd", "--background-index"}; + filetypes = {"c", "cpp", "objc", "objcpp"}; + root_dir = function(fname) + local filename = util.path.is_absolute(fname) and fname + or util.path.join(vim.loop.cwd(), fname) + return root_pattern(filename) or util.path.dirname(filename) + end; + capabilities = { + textDocument = { + completion = { + editsNearCursor = true + } + } + }, + }; + commands = { + ClangdSwitchSourceHeader = { + function() + switch_source_header(0) + end; + description = "Switch between source/header"; + }; + }; + docs = { + description = [[ +https://clang.llvm.org/extra/clangd/Installation.html + +**NOTE:** Clang >= 9 is recommended! See [this issue for more](https://github.com/neovim/nvim-lsp/issues/23). + +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. +For details on how to automatically generate one using CMake look [here](https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html). +]]; + default_config = { + root_dir = [[root_pattern("compile_commands.json", "compile_flags.txt", ".git") or dirname]]; + on_init = [[function to handle changing offsetEncoding]]; + capabilities = [[default capabilities, with offsetEncoding utf-8]]; + }; + }; +} + +configs.clangd.switch_source_header = switch_source_header +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/clojure_lsp.lua b/lua/lspconfig/clojure_lsp.lua new file mode 100644 index 00000000..f1ec6932 --- /dev/null +++ b/lua/lspconfig/clojure_lsp.lua @@ -0,0 +1,22 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.clojure_lsp = { + default_config = { + cmd = {"clojure-lsp"}; + filetypes = {"clojure", "edn"}; + root_dir = util.root_pattern("project.clj", "deps.edn", ".git"); + }; + docs = { + description = [[ +https://github.com/snoe/clojure-lsp + +Clojure Language Server +]]; + default_config = { + root_dir = [[root_pattern("project.clj", "deps.edn", ".git")]]; + }; + }; +} +-- vim:et ts=2 sw=2 + diff --git a/lua/lspconfig/cmake.lua b/lua/lspconfig/cmake.lua new file mode 100755 index 00000000..9dbcf717 --- /dev/null +++ b/lua/lspconfig/cmake.lua @@ -0,0 +1,25 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.cmake = { + default_config = { + cmd = {"cmake-language-server"}; + filetypes = {"cmake"}; + root_dir = util.root_pattern(".git", "compile_commands.json", "build"); + init_options = { + buildDirectory = "build", + } + }; + docs = { + description = [[ +https://github.com/regen100/cmake-language-server + +CMake LSP Implementation +]]; + default_config = { + root_dir = [[root_pattern(".git", "compile_commands.json", "build")]]; + }; + }; +}; + +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/codeqlls.lua b/lua/lspconfig/codeqlls.lua new file mode 100644 index 00000000..ffe08df4 --- /dev/null +++ b/lua/lspconfig/codeqlls.lua @@ -0,0 +1,52 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "codeqlls" + +local root_pattern = util.root_pattern("qlpack.yml") + +configs[server_name] = { + default_config = { + cmd = {"codeql", "execute", "language-server", "--check-errors", "ON_CHANGE", "-q"}; + filetypes = {'ql'}; + root_dir = function(fname) + return root_pattern("qlpack.yml") or util.path.dirname(fname) + end; + log_level = vim.lsp.protocol.MessageType.Warning; + before_init = function(initialize_params, config) + initialize_params['workspaceFolders'] = {{ + name = 'workspace', + uri = initialize_params['rootUri'] + }} + end; + settings = { + search_path = vim.empty_dict() + }; + }; + docs = { + package_json = "https://raw.githubusercontent.com/github/vscode-codeql/main/extensions/ql-vscode/package.json"; + description = [[ +Reference: +https://help.semmle.com/codeql/codeql-cli.html + +Binaries: +https://github.com/github/codeql-cli-binaries + ]]; + default_config = { + settings = { + search_path = [[list containing all search paths, eg: '~/codeql-home/codeql-repo']]; + }; + }; + }; + on_new_config = function(config) + if type(config.settings.search_path) == 'table' and not vim.tbl_isempty(config.settings.search_path) then + local search_path = "--search-path=" + for _, path in ipairs(config.settings.search_path) do + search_path = search_path..vim.fn.expand(path)..":" + end + config.cmd = {"codeql", "execute", "language-server", "--check-errors", "ON_CHANGE", "-q", search_path} + else + config.cmd = {"codeql", "execute", "language-server", "--check-errors", "ON_CHANGE", "-q"} + end + end; +} diff --git a/lua/lspconfig/configs.lua b/lua/lspconfig/configs.lua new file mode 100644 index 00000000..16ef9949 --- /dev/null +++ b/lua/lspconfig/configs.lua @@ -0,0 +1,214 @@ +local util = require 'lspconfig/util' +local api, validate, lsp = vim.api, vim.validate, vim.lsp +local tbl_extend = vim.tbl_extend + +local configs = {} + +function configs.__newindex(t, config_name, config_def) + validate { + name = {config_name, 's'}; + default_config = {config_def.default_config, 't'}; + on_new_config = {config_def.on_new_config, 'f', true}; + on_attach = {config_def.on_attach, 'f', true}; + commands = {config_def.commands, 't', true}; + } + if config_def.commands then + for k, v in pairs(config_def.commands) do + validate { + ['command.name'] = {k, 's'}; + ['command.fn'] = {v[1], 'f'}; + } + end + else + config_def.commands = {} + end + + local M = {} + + local default_config = tbl_extend("keep", config_def.default_config, util.default_config) + + -- Force this part. + default_config.name = config_name + + -- The config here is the one which will be instantiated for the new server, + -- which is why this is a function, so that it can refer to the settings + -- object on the server. + local function add_callbacks(config) + config.callbacks["window/logMessage"] = function(err, method, params, client_id) + if params and params.type <= config.log_level then + -- TODO(ashkan) remove this after things have settled. + assert(lsp.callbacks, "Update to Nvim HEAD. This is an incompatible interface.") + assert(lsp.callbacks["window/logMessage"], "Callback for window/logMessage notification is not defined") + lsp.callbacks["window/logMessage"](err, method, params, client_id) + end + end + + config.callbacks["window/showMessage"] = function(err, method, params, client_id) + if params and params.type <= config.message_level then + -- TODO(ashkan) remove this after things have settled. + assert(lsp.callbacks and lsp.callbacks[method], "Update to Nvim HEAD. This is an incompatible interface.") + assert(lsp.callbacks["window/showMessage"], "Callback for window/showMessage notification is not defined") + lsp.callbacks["window/showMessage"](err, method, params, client_id) + end + end + + config.callbacks["workspace/configuration"] = function(err, method, params, client_id) + if err then error(tostring(err)) end + if not params.items then + return {} + end + + local result = {} + for _, item in ipairs(params.items) do + if item.section then + local value = util.lookup_section(config.settings, item.section) or vim.NIL + -- For empty sections with no explicit '' key, return settings as is + if value == vim.NIL and item.section == '' then + value = config.settings or vim.NIL + end + table.insert(result, value) + end + end + return result + end + end + + function M.setup(config) + validate { + root_dir = {config.root_dir, 'f', default_config.root_dir ~= nil}; + filetypes = {config.filetype, 't', true}; + on_new_config = {config.on_new_config, 'f', true}; + on_attach = {config.on_attach, 'f', true}; + commands = {config.commands, 't', true}; + } + if config.commands then + for k, v in pairs(config.commands) do + validate { + ['command.name'] = {k, 's'}; + ['command.fn'] = {v[1], 'f'}; + } + end + end + + config = tbl_extend("keep", config, default_config) + + local trigger + if config.filetypes then + trigger = "FileType "..table.concat(config.filetypes, ',') + else + trigger = "BufReadPost *" + end + api.nvim_command(string.format( + "autocmd %s lua require'lspconfig'[%q].manager.try_add()" + , trigger + , config.name + )) + + local get_root_dir = config.root_dir + + -- In the case of a reload, close existing things. + if M.manager then + for _, client in ipairs(M.manager.clients()) do + client.stop(true) + end + M.manager = nil + end + + local make_config = function(_root_dir) + local new_config = util.tbl_deep_extend("keep", vim.empty_dict(), config) + new_config = util.tbl_deep_extend('keep', new_config, default_config) + new_config.capabilities = new_config.capabilities or lsp.protocol.make_client_capabilities() + new_config.capabilities = util.tbl_deep_extend('keep', new_config.capabilities, { + workspace = { + configuration = true; + } + }) + + add_callbacks(new_config) + if config_def.on_new_config then + pcall(config_def.on_new_config, new_config, _root_dir) + end + if config.on_new_config then + pcall(config.on_new_config, new_config, _root_dir) + end + + new_config.on_init = util.add_hook_after(new_config.on_init, function(client, _result) + function client.workspace_did_change_configuration(settings) + if not settings then return end + if vim.tbl_isempty(settings) then + settings = {[vim.type_idx]=vim.types.dictionary} + end + return client.notify('workspace/didChangeConfiguration', { + settings = settings; + }) + end + if not vim.tbl_isempty(new_config.settings) then + client.workspace_did_change_configuration(new_config.settings) + end + end) + + -- Save the old _on_attach so that we can reference it via the BufEnter. + new_config._on_attach = new_config.on_attach + new_config.on_attach = vim.schedule_wrap(function(client, bufnr) + if bufnr == api.nvim_get_current_buf() then + M._setup_buffer(client.id) + else + api.nvim_command(string.format( + "autocmd BufEnter <buffer=%d> ++once lua require'lspconfig'[%q]._setup_buffer(%d)" + , bufnr + , config_name + , client.id + )) + end + end) + + new_config.root_dir = _root_dir + return new_config + end + + local manager = util.server_per_root_dir_manager(function(_root_dir) + return make_config(_root_dir) + end) + + function manager.try_add() + if vim.bo.buftype == 'nofile' then + return + end + local root_dir = get_root_dir(api.nvim_buf_get_name(0), api.nvim_get_current_buf()) + local id = manager.add(root_dir) + if id then + lsp.buf_attach_client(0, id) + end + end + + M.manager = manager + M.make_config = make_config + end + + function M._setup_buffer(client_id) + local client = lsp.get_client_by_id(client_id) + if client.config._on_attach then + client.config._on_attach(client) + end + if client.config.commands and not vim.tbl_isempty(client.config.commands) then + M.commands = util.tbl_deep_extend("force", M.commands, client.config.commands) + end + if not M.commands_created and not vim.tbl_isempty(M.commands) then + -- Create the module commands + util.create_module_commands(config_name, M.commands) + M.commands_created = true + end + end + + M.commands_created = false + M.commands = config_def.commands + M.name = config_name + M.document_config = config_def + + rawset(t, config_name, M) + + return M +end + +return setmetatable({}, configs) +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/cssls.lua b/lua/lspconfig/cssls.lua new file mode 100644 index 00000000..7b50950a --- /dev/null +++ b/lua/lspconfig/cssls.lua @@ -0,0 +1,56 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "cssls" +local bin_name = "css-languageserver" + +local installer = util.npm_installer { + server_name = server_name; + packages = { "vscode-css-languageserver-bin" }; + binaries = {bin_name}; +} + +local root_pattern = util.root_pattern("package.json") + +configs[server_name] = { + default_config = { + cmd = {bin_name, "--stdio"}; + filetypes = {"css", "scss", "less"}; + root_dir = function(fname) + return root_pattern(fname) or vim.loop.os_homedir() + end; + settings = { + css = { validate = true }, + scss = { validate = true }, + less = { validate = true } + }; + }; + 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; + docs = { + description = [[ +https://github.com/vscode-langservers/vscode-css-languageserver-bin + +`css-languageserver` can be installed via `:LspInstall cssls` or by yourself with `npm`: +```sh +npm install -g vscode-css-languageserver-bin +``` +]]; + default_config = { + root_dir = [[root_pattern("package.json")]]; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/dartls.lua b/lua/lspconfig/dartls.lua new file mode 100644 index 00000000..1baf05d6 --- /dev/null +++ b/lua/lspconfig/dartls.lua @@ -0,0 +1,55 @@ +local util = require 'lspconfig/util' +local configs = require 'lspconfig/configs' + +local server_name = "dartls" +local bin_name = "dart" + +local find_dart_sdk_root_path = function() + if vim.fn["executable"]("flutter") == 1 then + local flutter_path = vim.fn["resolve"](vim.fn["exepath"]("flutter")) + local flutter_bin = vim.fn["fnamemodify"](flutter_path, ":h") + return flutter_bin.."/cache/dart-sdk/bin/dart" + elseif vim.fn["executable"]("dart") == 1 then + return vim.fn["resolve"](vim.fn["exepath"]("dart")) + else + return '' + end +end + +local analysis_server_snapshot_path = function() + local dart_sdk_root_path = vim.fn["fnamemodify"](find_dart_sdk_root_path(), ":h") + local snapshot = dart_sdk_root_path.."/snapshots/analysis_server.dart.snapshot" + + if vim.fn["has"]("win32") == 1 or vim.fn["has"]("win64") == 1 then + snapshot = snapshot:gsub("/", "\\") + end + + return snapshot +end + +configs[server_name] = { + default_config = { + cmd = {bin_name, analysis_server_snapshot_path(), "--lsp"}; + filetypes = {"dart"}; + root_dir = util.root_pattern("pubspec.yaml"); + init_options = { + onlyAnalyzeProjectsWithOpenFiles = "false", + suggestFromUnimportedLibraries = "true", + closingLabels = "true", + outline = "true", + flutterOutline= "false" + }; + }; + docs = { + package_json = "https://raw.githubusercontent.com/Dart-Code/Dart-Code/master/package.json"; + description = [[ +https://github.com/dart-lang/sdk/tree/master/pkg/analysis_server/tool/lsp_spec + +Language server for dart. +]]; + default_config = { + root_dir = [[root_pattern("pubspec.yaml")]]; + }; + }; +}; +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/diagnosticls.lua b/lua/lspconfig/diagnosticls.lua new file mode 100644 index 00000000..c4f26220 --- /dev/null +++ b/lua/lspconfig/diagnosticls.lua @@ -0,0 +1,45 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "diagnosticls" +local bin_name = "diagnostic-languageserver" + +local installer = util.npm_installer { + server_name = server_name; + packages = { bin_name }; + binaries = { bin_name }; +} + +configs[server_name] = { + default_config = { + cmd = {bin_name, "--stdio"}, + filetypes = {}, + root_dir = util.path.dirname, + }, + 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], "--stdio"} + end + end + end; + docs = { + description = [[ +https://github.com/iamcco/diagnostic-languageserver + +Diagnostic language server integrate with linters. +]]; + default_config = { + filetypes = "Empty by default, override to add filetypes", + root_dir = "Vim's starting directory"; + init_options = "Configuration from https://github.com/iamcco/diagnostic-languageserver#config--document"; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info diff --git a/lua/lspconfig/dockerls.lua b/lua/lspconfig/dockerls.lua new file mode 100644 index 00000000..572e4701 --- /dev/null +++ b/lua/lspconfig/dockerls.lua @@ -0,0 +1,47 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "dockerls" +local bin_name = "docker-langserver" + +local installer = util.npm_installer { + server_name = server_name; + packages = { "dockerfile-language-server-nodejs" }; + binaries = {bin_name}; +} + +configs[server_name] = { + default_config = { + cmd = {bin_name, "--stdio"}; + filetypes = {"Dockerfile", "dockerfile"}; + root_dir = util.root_pattern("Dockerfile"); + }; + 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; + docs = { + description = [[ +https://github.com/rcjsuen/dockerfile-language-server-nodejs + +`docker-langserver` can be installed via `:LspInstall dockerls` or by yourself with `npm`: +```sh +npm install -g dockerfile-language-server-nodejs +``` + ]]; + default_config = { + root_dir = [[root_pattern("Dockerfile")]]; + }; + }; +}; + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/efm.lua b/lua/lspconfig/efm.lua new file mode 100644 index 00000000..d4fc88ac --- /dev/null +++ b/lua/lspconfig/efm.lua @@ -0,0 +1,25 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "efm" +local bin_name = "efm-langserver" + + +configs[server_name] = { + default_config = { + cmd = {bin_name}; + root_dir = util.root_pattern(".git"); + }; + + docs = { + description = [[ +https://github.com/mattn/efm-langserver + +General purpose Language Server that can use specified error message format generated from specified command. +]]; + default_config = { + root_dir = [[root_pattern(".git")]]; + }; + }; +} +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/elixirls.lua b/lua/lspconfig/elixirls.lua new file mode 100644 index 00000000..fcd91a04 --- /dev/null +++ b/lua/lspconfig/elixirls.lua @@ -0,0 +1,110 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "elixirls" +local bin_name = "elixir-ls" +local cmd = "language_server" + +if vim.fn.has('mac') == 1 or vim.fn.has('unix') == 1 then + cmd = cmd..".sh" +elseif vim.fn.has('win32') == 1 or vim.fn.has('win64') == 1 then + cmd = cmd..".bat" +else + error("System is not supported, try to install manually.") + return +end + +local function make_installer() + local P = util.path.join + local install_dir = P{util.base_install_dir, server_name} + local cmd_path = P{install_dir, bin_name, "release", cmd} + + local X = {} + function X.install() + local install_info = X.info() + if install_info.is_installed then + print(server_name, "is already installed.") + return + end + + if not (util.has_bins("elixir") and util.has_bins("erl")) then + error("Need elixir and erl to install this") + return + end + + local script = [=[ + set -e + + # clone project + git clone https://github.com/elixir-lsp/elixir-ls + cd elixir-ls + + # fetch dependencies and compile + mix deps.get && mix compile + + # install executable + mix elixir_ls.release -o release + ]=] + vim.fn.mkdir(install_info.install_dir, "p") + util.sh(script, install_info.install_dir) + end + + function X.info() + return { + is_installed = util.path.exists(cmd_path); + install_dir = install_dir; + cmd = { cmd_path }; + } + end + + function X.configure(config) + local install_info = X.info() + if install_info.is_installed then + config.cmd = install_info.cmd + end + end + return X +end + +local installer = make_installer() + +configs[server_name] = { + default_config = { + cmd = { cmd }; + filetypes = {"elixir", "eelixir"}; + root_dir = function(fname) + return util.root_pattern("mix.exs", ".git")(fname) or vim.loop.os_homedir() + end; + }; + on_new_config = function(config) + installer.configure(config) + end; + docs = { + package_json = "https://raw.githubusercontent.com/JakeBecker/vscode-elixir-ls/master/package.json"; + description = [[ +https://github.com/elixir-lsp/elixir-ls + +`elixir-ls` can be installed via `:LspInstall elixirls` or by yourself by following the instructions [here](https://github.com/elixir-lsp/elixir-ls#building-and-running). + +This language server does not provide a global binary, but must be installed manually. The command `:LspInstaller elixirls` makes an attempt at installing the binary by +Fetching the elixir-ls repository from GitHub, compiling it and then installing it. + +```lua +require'lspconfig'.elixirls.setup{ + -- Unix + cmd = { "path/to/language_server.sh" }; + -- Windows + cmd = { "path/to/language_server.bat" }; + ... +} +``` +]]; + default_config = { + root_dir = [[root_pattern("mix.exs", ".git") or vim.loop.os_homedir()]]; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/elmls.lua b/lua/lspconfig/elmls.lua new file mode 100644 index 00000000..c35824c4 --- /dev/null +++ b/lua/lspconfig/elmls.lua @@ -0,0 +1,72 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' +local lsp = vim.lsp +local api = vim.api + +local server_name = "elmls" +local bin_name = "elm-language-server" + +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") + +configs[server_name] = { + default_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) + 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) + end + end; + init_options = { + elmPath = "elm", + elmFormatPath = "elm-format", + elmTestPath = "elm-test", + elmAnalyseTrigger = "change", + }; + }; + 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 + new_config.init_options = util.tbl_deep_extend('force', new_config.init_options, { + elmPath = install_info.binaries["elm"]; + elmFormatPath = install_info.binaries["elm-format"]; + elmTestPath = install_info.binaries["elm-test"]; + }) + end + end; + docs = { + package_json = "https://raw.githubusercontent.com/elm-tooling/elm-language-client-vscode/master/package.json"; + description = [[ +https://github.com/elm-tooling/elm-language-server#installation + +If you don't want to use Nvim to install it, then you can use: +```sh +npm install -g elm elm-test elm-format @elm-tooling/elm-language-server +``` +]]; + default_config = { + root_dir = [[root_pattern("elm.json")]]; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 + diff --git a/lua/lspconfig/flow.lua b/lua/lspconfig/flow.lua new file mode 100644 index 00000000..5e0abab3 --- /dev/null +++ b/lua/lspconfig/flow.lua @@ -0,0 +1,30 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.flow = { + default_config = { + cmd = {"npx", "--no-install", "flow","lsp"}; + filetypes = {"javascript", "javascriptreact", "javascript.jsx"}; + root_dir = util.root_pattern(".flowconfig"); + }; + docs = { + package_json = "https://raw.githubusercontent.com/flowtype/flow-for-vscode/master/package.json"; + description = [[ +https://flow.org/ +https://github.com/facebook/flow + +See below for how to setup Flow itself. +https://flow.org/en/docs/install/ + +See below for lsp command options. + +```sh +npx flow lsp --help +``` + ]]; + default_config = { + root_dir = [[root_pattern(".flowconfig")]]; + }; + }; +}; +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/fortls.lua b/lua/lspconfig/fortls.lua new file mode 100644 index 00000000..7a66c703 --- /dev/null +++ b/lua/lspconfig/fortls.lua @@ -0,0 +1,25 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.fortls = { + default_config = { + cmd = {"fortls"}; + filetypes = {"fortran"}; + root_dir = util.root_pattern(".fortls"); + settings = { + nthreads = 1, + }; + }; + docs = { + package_json = "https://raw.githubusercontent.com/hansec/vscode-fortran-ls/master/package.json"; + description = [[ +https://github.com/hansec/fortran-language-server + +Fortran Language Server for the Language Server Protocol + ]]; + default_config = { + root_dir = [[root_pattern(".fortls")]]; + }; + }; +}; +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/gdscript.lua b/lua/lspconfig/gdscript.lua new file mode 100644 index 00000000..ecd3288f --- /dev/null +++ b/lua/lspconfig/gdscript.lua @@ -0,0 +1,22 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.gdscript = { + default_config = { + cmd = {"nc", "localhost", "6008"}; + filetypes = {"gd", "gdscript", "gdscript3"}; + root_dir = util.root_pattern("project.godot", ".git"); + }; + docs = { + description = [[ +https://github.com/godotengine/godot + +Language server for GDScript, used by Godot Engine. +]]; + default_config = { + root_dir = util.root_pattern("project.godot", ".git"); + }; + }; +}; + +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/ghcide.lua b/lua/lspconfig/ghcide.lua new file mode 100644 index 00000000..c427d599 --- /dev/null +++ b/lua/lspconfig/ghcide.lua @@ -0,0 +1,24 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.ghcide = { + default_config = { + cmd = { "ghcide", "--lsp" }; + filetypes = { "haskell", "lhaskell" }; + root_dir = util.root_pattern("stack.yaml", "hie-bios", "BUILD.bazel", "cabal.config", "package.yaml"); + }; + + docs = { + package_json = "https://raw.githubusercontent.com/digital-asset/ghcide/master/extension/package.json"; + description = [[ +https://github.com/digital-asset/ghcide + +A library for building Haskell IDE tooling. +]]; + default_config = { + root_dir = [[root_pattern("stack.yaml", "hie-bios", "BUILD.bazel", "cabal.config", "package.yaml")]]; + }; + }; +}; + +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/gopls.lua b/lua/lspconfig/gopls.lua new file mode 100644 index 00000000..08eacc15 --- /dev/null +++ b/lua/lspconfig/gopls.lua @@ -0,0 +1,23 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.gopls = { + default_config = { + cmd = {"gopls"}; + filetypes = {"go", "gomod"}; + root_dir = util.root_pattern("go.mod", ".git"); + }; + -- on_new_config = function(new_config) end; + -- on_attach = function(client, bufnr) end; + docs = { + description = [[ +https://github.com/golang/tools/tree/master/gopls + +Google's lsp server for golang. +]]; + default_config = { + root_dir = [[root_pattern("go.mod", ".git")]]; + }; + }; +} +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/health.lua b/lua/lspconfig/health.lua new file mode 100644 index 00000000..3abab080 --- /dev/null +++ b/lua/lspconfig/health.lua @@ -0,0 +1,26 @@ +local M = {} +function M.check_health() + local configs = require 'lspconfig/configs' + + for _, top_level_config in pairs(configs) do + -- If users execute `:LspInstall` or `:LspInstallInfo`, + -- a config is required but is not added make_config function. + if not (top_level_config.make_config == nil) then + -- the folder needs to exist + local config = top_level_config.make_config(".") + + local status, cmd = pcall(vim.lsp._cmd_parts, config.cmd) + if not status then + vim.fn['health#report_error'](string.format("%s: config.cmd error, %s", config.name, cmd)) + else + if not (vim.fn.executable(cmd) == 1) then + vim.fn['health#report_error'](string.format("%s: The given command %q is not executable.", config.name, cmd)) + else + vim.fn['health#report_info'](string.format("%s: configuration checked.", config.name)) + end + end + end + end +end + +return M diff --git a/lua/lspconfig/hie.lua b/lua/lspconfig/hie.lua new file mode 100644 index 00000000..f57326dc --- /dev/null +++ b/lua/lspconfig/hie.lua @@ -0,0 +1,38 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.hie = { + default_config = { + cmd = {"hie-wrapper", "--lsp"}; + filetypes = {"haskell"}; + root_dir = util.root_pattern("stack.yaml", "package.yaml", ".git"); + }; + + docs = { + package_json = "https://raw.githubusercontent.com/alanz/vscode-hie-server/master/package.json"; + description = [[ +https://github.com/haskell/haskell-ide-engine + +the following init_options are supported (see https://github.com/haskell/haskell-ide-engine#configuration): +```lua +init_options = { + languageServerHaskell = { + hlintOn = bool; + maxNumberOfProblems = number; + diagnosticsDebounceDuration = number; + liquidOn = bool (default false); + completionSnippetsOn = bool (default true); + formatOnImportOn = bool (default true); + formattingProvider = string (default "brittany", alternate "floskell"); + } +} +``` + ]]; + + default_config = { + root_dir = [[root_pattern("stack.yaml", "package.yaml", ".git")]]; + }; + }; +}; + +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/hls.lua b/lua/lspconfig/hls.lua new file mode 100644 index 00000000..9c29b82c --- /dev/null +++ b/lua/lspconfig/hls.lua @@ -0,0 +1,24 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.hls = { + default_config = { + cmd = {"haskell-language-server-wrapper", "--lsp"}; + filetypes = {"haskell", "lhaskell"}; + root_dir = util.root_pattern("*.cabal", "stack.yaml", "cabal.project", "package.yaml", "hie.yaml"); + }; + + docs = { + description = [[ +https://github.com/haskell/haskell-language-server + +Haskell Language Server + ]]; + + default_config = { + root_dir = [[root_pattern("*.cabal", "stack.yaml", "cabal.project", "package.yaml", "hie.yaml")]]; + }; + }; +}; + +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/html.lua b/lua/lspconfig/html.lua new file mode 100644 index 00000000..a9636222 --- /dev/null +++ b/lua/lspconfig/html.lua @@ -0,0 +1,54 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "html" +local bin_name = "html-languageserver" + +local installer = util.npm_installer { + server_name = server_name; + packages = { "vscode-html-languageserver-bin" }; + binaries = {bin_name}; +} + +local root_pattern = util.root_pattern("package.json") + +configs[server_name] = { + default_config = { + cmd = {bin_name, "--stdio"}; + filetypes = {"html"}; + root_dir = function(fname) + return root_pattern(fname) or vim.loop.os_homedir() + end; + settings = {}; + init_options = { + embeddedLanguages = { css= true, javascript= true }, + configurationSection = { 'html', 'css', 'javascript' }, + } + + }; + 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; + docs = { + description = [[ +https://github.com/vscode-langservers/vscode-html-languageserver-bin + +`html-languageserver` can be installed via `:LspInstall html` or by yourself with `npm`: +```sh +npm install -g vscode-html-languageserver-bin +``` +]]; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/intelephense.lua b/lua/lspconfig/intelephense.lua new file mode 100644 index 00000000..7e381349 --- /dev/null +++ b/lua/lspconfig/intelephense.lua @@ -0,0 +1,68 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "intelephense" +local bin_name = "intelephense" + +local installer = util.npm_installer { + server_name = server_name; + packages = { "intelephense" }; + binaries = {bin_name}; +} + +configs[server_name] = { + default_config = { + cmd = {bin_name, "--stdio"}; + filetypes = {"php"}; + root_dir = function (pattern) + local cwd = vim.loop.cwd(); + local root = util.root_pattern("composer.json", ".git")(pattern); + + -- prefer cwd if root is a descendant + return util.path.is_descendant(cwd, root) and cwd or root; + end; + }; + 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; + docs = { + description = [[ +https://intelephense.com/ + +`intelephense` can be installed via `:LspInstall intelephense` or by yourself with `npm`: +```sh +npm install -g intelephense +``` +]]; + default_config = { + root_dir = [[root_pattern("composer.json", ".git")]]; + init_options = [[{ + storagePath = Optional absolute path to storage dir. Defaults to os.tmpdir(). + globalStoragePath = Optional absolute path to a global storage dir. Defaults to os.homedir(). + licenceKey = Optional licence key or absolute path to a text file containing the licence key. + clearCache = Optional flag to clear server state. State can also be cleared by deleting {storagePath}/intelephense + -- See https://github.com/bmewburn/intelephense-docs#initialisation-options + }]]; + settings = [[{ + intelephense = { + files = { + maxSize = 1000000; + }; + }; + -- See https://github.com/bmewburn/intelephense-docs#configuration-options + }]]; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/jdtls.lua b/lua/lspconfig/jdtls.lua new file mode 100644 index 00000000..be9ba6e8 --- /dev/null +++ b/lua/lspconfig/jdtls.lua @@ -0,0 +1,138 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' +local callbacks = require 'vim.lsp.callbacks' +local path = util.path + +local server_name = "jdtls" + +local function make_installer() + local install_dir = path.join { util.base_install_dir, server_name } + local tar_name = "jdt-language-server-latest.tar.gz" + local script = string.format([[ + curl -LO http://download.eclipse.org/jdtls/snapshots/%s > %s + tar xf %s + ]], tar_name, tar_name, tar_name) + local launcher_ls = "ls " .. path.join { install_dir, "plugins", "org.eclipse.equinox.launcher_*.jar" } + + local X = {} + + function X.install() + if not util.has_bins("curl", "tar") then + error('Need the binaries "curl", "tar" to install this.') + return + end + + vim.fn.mkdir(install_dir, "p") + util.sh(script, install_dir) + end + + function X.info() + return { + is_installed = util.path.exists(install_dir, 'features') ~= false; + install_dir = install_dir; + } + end + + function X.get_os_config() + if vim.fn.has("osx") == 1 then + return "config_mac" + elseif vim.fn.has("unix") == 1 then + return "config_linux" + else + return "config_win" + end + end + + function X.get_launcher() + local file = io.popen(launcher_ls) + local results = {} + + for line in file:lines() do + table.insert(results, line) + end + + if #results == 1 then + return results[1] + end + + error("Could not find launcher for jdtls.") + end + + function X.configure(config) + local install_info = X.info() + local launcher_path = X.get_launcher() + + if install_info.is_installed then + config.cmd = vim.list_extend( + vim.list_extend( + { + "java", + "-Declipse.application=org.eclipse.jdt.ls.core.id1", + "-Dosgi.bundles.defaultStartLevel=4", + "-Declipse.product=org.eclipse.jdt.ls.core.product", + "-Dlog.level=ALL", + "-noverify", + "-Xmx1G", + }, + config.init_options.jvm_args), + { + "-jar", launcher_path, + "-configuration", path.join { install_dir, config.init_options.os_config or X.get_os_config() }, + "-data", config.init_options.workspace, + -- TODO: Handle Java versions 8 and under. This may just work... + "--add-modules=ALL-SYSTEM", + "--add-opens", "java.base/java.util=ALL-UNNAMED", + "--add-opens", "java.base/java.lang=ALL-UNNAMED" + }) + end + end + + return X +end + +local installer = make_installer() + +configs[server_name] = { + default_config = { + filetypes = { "java" }; + root_dir = util.root_pattern('.git'); + init_options = { + workspace = path.join { vim.loop.os_homedir(), "workspace" }; + jvm_args = {}; + os_config = nil; + }; + callbacks = { + -- Due to an invalid protocol implementation in the jdtls we have to + -- conform these to be spec compliant. + -- https://github.com/eclipse/eclipse.jdt.ls/issues/376 + ['textDocument/codeAction'] = function(a, b, actions) + for _,action in ipairs(actions) do + -- TODO: (steelsojka) Handle more than one edit? + if action.command == 'java.apply.workspaceEdit' then + action.edit = action.arguments[1] + end + end + + callbacks['textDocument/codeAction'](a, b, actions) + end + }; + }; + on_new_config = function(config) + installer.configure(config) + end; + docs = { + description = [[ +https://projects.eclipse.org/projects/eclipse.jdt.ls + +Language server can be installed with `:LspInstall jdtls` + +Language server for Java. + ]]; + default_config = { + root_dir = [[root_pattern(".git")]]; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info diff --git a/lua/lspconfig/jedi_language_server.lua b/lua/lspconfig/jedi_language_server.lua new file mode 100644 index 00000000..5fd6cbfc --- /dev/null +++ b/lua/lspconfig/jedi_language_server.lua @@ -0,0 +1,22 @@ +local configs = require 'lspconfig/configs' + +configs.jedi_language_server = { + default_config = { + cmd = {"jedi-language-server"}; + filetypes = {"python"}; + root_dir = function(fname) + return vim.fn.getcwd() + end; + }; + docs = { + description = [[ +https://github.com/pappasam/jedi-language-server + +`jedi-language-server`, a language server for Python, built on top of jedi + ]]; + default_config = { + root_dir = "vim's starting directory"; + }; + }; +}; +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/jsonls.lua b/lua/lspconfig/jsonls.lua new file mode 100644 index 00000000..616a0bbe --- /dev/null +++ b/lua/lspconfig/jsonls.lua @@ -0,0 +1,51 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "jsonls" +local bin_name = "vscode-json-languageserver" + +local installer = util.npm_installer { + server_name = server_name; + packages = {bin_name}; + binaries = {bin_name}; +} + +configs[server_name] = { + default_config = { + cmd = {bin_name, "--stdio"}; + filetypes = {"json"}; + root_dir = util.root_pattern(".git", vim.fn.getcwd()); + }; + 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; + docs = { + -- this language server config is in VSCode built-in package.json + package_json = "https://raw.githubusercontent.com/microsoft/vscode/master/extensions/json-language-features/package.json"; + description = [[ +https://github.com/vscode-langservers/vscode-json-languageserver + +vscode-json-languageserver, a language server for JSON and JSON schema + +`vscode-json-languageserver` can be installed via `:LspInstall jsonls` or by yourself with `npm`: +```sh +npm install -g vscode-json-languageserver +``` +]]; + default_config = { + root_dir = [[root_pattern(".git", vim.fn.getcwd())]]; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/julials.lua b/lua/lspconfig/julials.lua new file mode 100644 index 00000000..f74ce7b7 --- /dev/null +++ b/lua/lspconfig/julials.lua @@ -0,0 +1,66 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local environment_directory = util.path.join(util.base_install_dir, "julials") + +configs.julials = { + default_config = { + cmd = { + "julia", "--project=" .. environment_directory, "--startup-file=no", "--history-file=no", "-e", [[ + using Pkg; + Pkg.instantiate() + using LanguageServer; using SymbolServer; + depot_path = get(ENV, "JULIA_DEPOT_PATH", "") + project_path = dirname(something(Base.current_project(pwd()), Base.load_path_expand(LOAD_PATH[2]))) + # Make sure that we only load packages from this environment specifically. + empty!(LOAD_PATH) + push!(LOAD_PATH, "@") + @info "Running language server" env=Base.load_path()[1] pwd() project_path depot_path + server = LanguageServer.LanguageServerInstance(stdin, stdout, project_path, depot_path); + server.runlinter = true; + run(server); + ]] + }; + filetypes = {'julia'}; + root_dir = function(fname) + return util.find_git_ancestor(fname) or vim.loop.os_homedir() + end; + }; + docs = { + package_json = "https://raw.githubusercontent.com/julia-vscode/julia-vscode/master/package.json"; + description = [[ +https://github.com/julia-vscode/julia-vscode +`LanguageServer.jl` can be installed via `:LspInstall julials` or by yourself the `julia` and `Pkg`: +```sh +julia --project=]] .. environment_directory .. [[ -e 'using Pkg; Pkg.add("LanguageServer"); Pkg.add("SymbolServer")' +``` +If you want to install the LanguageServer manually, you will have to ensure that the Julia environment is stored in this location: +```vim +:lua print(require'lspconfig'.util.path.join(require'lspconfig'.util.base_install_dir, "julials")) +``` + ]]; + }; +} + +configs.julials.install = function() + + local script = [[ + julia --project=]] .. environment_directory .. [[ -e 'using Pkg; Pkg.add("LanguageServer"); Pkg.add("SymbolServer")' + ]] + + util.sh(script, vim.loop.os_homedir()) +end + +configs.julials.install_info = function() + local script = [[ + julia --project=]] .. environment_directory .. [[ -e 'using LanguageServer; using SymbolServer' + ]] + + local status = pcall(vim.fn.system, script) + + return { + is_installed = status and vim.v.shell_error == 0; + } +end + +--- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/kotlin_language_server.lua b/lua/lspconfig/kotlin_language_server.lua new file mode 100644 index 00000000..373c782c --- /dev/null +++ b/lua/lspconfig/kotlin_language_server.lua @@ -0,0 +1,52 @@ +--- default config for gradle-projects of the +--- kotlin-language-server: https://github.com/fwcd/kotlin-language-server +--- +--- This server requires vim to be aware of the kotlin-filetype. +--- You could refer for this capability to: +--- https://github.com/udalov/kotlin-vim (recommended) +--- Note that there is no LICENSE specified yet. + +local util = require 'lspconfig/util' +local configs = require 'lspconfig/configs' + +configs.kotlin_language_server = { + default_config = { + filetypes = { "kotlin" }; + root_dir = util.root_pattern("settings.gradle"); + }; + docs = { + package_json = "https://raw.githubusercontent.com/fwcd/vscode-kotlin/master/package.json"; + description = [[ + A kotlin language server which was developed for internal usage and + released afterwards. Maintaining is not done by the original author, + but by fwcd. + + It is builded via gradle and developed on github. + Source and additional description: + https://github.com/fwcd/kotlin-language-server + ]]; + default_config = { + root_dir = [[root_pattern("settings.gradle")]]; + capabilities = [[ + smart code completion, + diagnostics, + hover, + document symbols, + definition lookup, + method signature help, + dependency resolution, + additional plugins from: https://github.com/fwcd + + Snipped of License (refer to source for full License): + + The MIT License (MIT) + + Copyright (c) 2016 George Fraser + Copyright (c) 2018 fwcd + + ]]; + }; + }; +} + +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/leanls.lua b/lua/lspconfig/leanls.lua new file mode 100644 index 00000000..cd6f4644 --- /dev/null +++ b/lua/lspconfig/leanls.lua @@ -0,0 +1,22 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.leanls = { + default_config = { + cmd = {"lean-language-server", "--stdio"}; + filetypes = {"lean"}; + root_dir = util.root_pattern(".git"); + }; + docs = { + package_json = "https://raw.githubusercontent.com/leanprover/vscode-lean/master/package.json"; + description = [[ +https://github.com/leanprover/lean-client-js/tree/master/lean-language-server + +Lean language server. + ]]; + default_config = { + root_dir = [[util.root_pattern(".git")]]; + }; + }; +} +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/metals.lua b/lua/lspconfig/metals.lua new file mode 100644 index 00000000..12e244bb --- /dev/null +++ b/lua/lspconfig/metals.lua @@ -0,0 +1,106 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' +local server_name = "metals" +local bin_name = "metals" + +local function make_installer() + local install_dir = util.path.join{util.base_install_dir, server_name} + local metals_bin = util.path.join{install_dir, bin_name} + local server_version + if (vim.g.metals_server_version) then + server_version = vim.g.metals_server_version + else + server_version = 'latest.release' + end + local X = {} + function X.install() + local install_info = X.info() + if install_info.is_installed then + print(server_name, "is already installed") + return + end + if not (util.has_bins("curl")) then + error('Need "curl" to install this.') + return + end + if not (util.has_bins("java")) then + error('Need "JDK" to install this.') + return + end + + local coursier_exe = nil + if util.has_bins("cs") then + coursier_exe = "cs" + elseif util.has_bins("coursier") then + coursier_exe = "coursier" + end + if not coursier_exe then + coursier_exe = install_dir .. "/coursier" + local download_cmd = string.format("curl -fLo %s --create-dirs https://git.io/coursier-cli", coursier_exe) + local chmod_cmd = string.format("chmod +x %s", coursier_exe) + vim.fn.system(download_cmd) + vim.fn.system(chmod_cmd) + else + os.execute("mkdir -p " .. install_dir) + end + + local install_cmd = string.format("%s bootstrap --java-opt -Xss4m --java-opt -Xms100m org.scalameta:metals_2.12:%s -r bintray:scalacenter/releases -r sonatype:snapshots -o %s -f", coursier_exe, server_version, metals_bin) + vim.fn.system(install_cmd) + end + function X.info() + return { + is_installed = util.path.exists(metals_bin); + install_dir = install_dir; + cmd = { metals_bin }; + } + end + function X.configure(config) + local install_info = X.info() + if install_info.is_installed then + config.cmd = install_info.cmd + end + end + return X +end + +local installer = make_installer() + +configs[server_name] = { + default_config = { + cmd = {bin_name}; + filetypes = {"scala"}; + root_dir = util.root_pattern("build.sbt", "build.sc", "build.gradle", "pom.xml"); + message_level = vim.lsp.protocol.MessageType.Log; + init_options = { + statusBarProvider = "show-message", + isHttpEnabled = true, + compilerOptions = { + snippetAutoIndent = false + } + }; + }; + on_new_config = function(config) + installer.configure(config) + end; + docs = { + package_json = "https://raw.githubusercontent.com/scalameta/metals-vscode/master/package.json"; + description = [[ +https://scalameta.org/metals/ + +To target a specific version on Metals, set the following. +If nothing is set, the latest stable will be used. +```vim +let g:metals_server_version = '0.8.4+106-5f2b9350-SNAPSHOT' +``` + +Scala language server with rich IDE features. +`metals` can be installed via `:LspInstall metals`. +]]; + default_config = { + root_dir = [[util.root_pattern("build.sbt", "build.sc", "build.gradle", "pom.xml")]]; + }; + }; +}; + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info diff --git a/lua/lspconfig/nimls.lua b/lua/lspconfig/nimls.lua new file mode 100644 index 00000000..f71c60f9 --- /dev/null +++ b/lua/lspconfig/nimls.lua @@ -0,0 +1,45 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.nimls = { + default_config = { + cmd = {"nimlsp",}; + filetypes = {'nim'}; + root_dir = function(fname) + return util.find_git_ancestor(fname) or vim.loop.os_homedir() + end; + }; + docs = { + package_json = "https://raw.githubusercontent.com/pragmagic/vscode-nim/master/package.json"; + description = [[ +https://github.com/PMunch/nimlsp +`nimlsp` can be installed via `:LspInstall nimls` or by yourself the `nimble` package manager: +```sh +nimble install nimlsp +``` + ]]; + default_config = { + root_dir = [[root_pattern(".git") or os_homedir]]; + }; + }; +} + +configs.nimls.install = function() + local script = [[ + nimble install nimlsp + ]] + + util.sh(script, vim.loop.os_homedir()) +end + +configs.nimls.install_info = function() + local script = [[ + nimlsp --version + ]] + + local status = pcall(vim.fn.system, script) + + return { + is_installed = status and vim.v.shell_error == 0; + } +end diff --git a/lua/lspconfig/ocamlls.lua b/lua/lspconfig/ocamlls.lua new file mode 100644 index 00000000..05e71992 --- /dev/null +++ b/lua/lspconfig/ocamlls.lua @@ -0,0 +1,45 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "ocamlls" +local bin_name = "ocaml-language-server" + +local installer = util.npm_installer { + server_name = server_name; + packages = { "ocaml-language-server" }; + binaries = { bin_name }; +} + +configs[server_name] = { + default_config = { + cmd = { bin_name, "--stdio" }; + filetypes = { "ocaml", "reason" }; + root_dir = util.root_pattern(".merlin", "package.json"); + }; + on_new_config = function(new_config) + local install_info = installer.info() + if install_info.is_installed then + if type(new_config.cmd) == 'table' then + 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/ocaml-lsp/ocaml-language-server + +`ocaml-language-server` can be installed via `:LspInstall ocamlls` or by yourself with `npm` +```sh +npm install -g ocaml-langauge-server +``` + ]]; + default_config = { + root_dir = [[root_pattern(".merlin", "package.json")]]; + }; + }; +}; +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/ocamllsp.lua b/lua/lspconfig/ocamllsp.lua new file mode 100644 index 00000000..9efc59b4 --- /dev/null +++ b/lua/lspconfig/ocamllsp.lua @@ -0,0 +1,26 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.ocamllsp = { + default_config = { + cmd = {"ocamllsp",}; + filetypes = {'ocaml', 'reason'}; + root_dir = util.root_pattern(".merlin", "package.json", ".git"); + }; + docs = { + description = [[ +https://github.com/ocaml/ocaml-lsp + +`ocaml-lsp` can be installed as described in [installation guide](https://github.com/ocaml/ocaml-lsp#installation). + +To install the lsp server in a particular opam switch: +```sh +opam pin add ocaml-lsp-server https://github.com/ocaml/ocaml-lsp.git +opam install ocaml-lsp-server +``` + ]]; + default_config = { + root_dir = [[root_pattern(".merlin", "package.json")]]; + }; + }; +} diff --git a/lua/lspconfig/omnisharp.lua b/lua/lspconfig/omnisharp.lua new file mode 100644 index 00000000..a608537d --- /dev/null +++ b/lua/lspconfig/omnisharp.lua @@ -0,0 +1,84 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' +local server_name = 'omnisharp' +local bin_name = 'run' + +local function make_installer() + local install_dir = util.path.join{util.base_install_dir, server_name} + local pid = vim.fn.getpid() + local url = 'linux-x64' + + if vim.fn.has('win32') == 1 then + url = 'win-x64' + bin_name = 'Omnisharp.exe' + elseif vim.fn.has('mac') == 1 then + url = 'osx' + end + local bin_path = util.path.join{install_dir, bin_name} + + local download_target = util.path.join{install_dir, string.format("omnisharp-%s.zip", url)} + local extract_cmd = string.format("unzip '%s' -d '%s'", download_target, install_dir) + 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) + local make_executable_cmd = string.format("chmod u+x '%s'", bin_path) + + local X = {} + function X.install() + local install_info = X.info() + if install_info.is_installed then + print(server_name, "is already installed") + return + end + if not (util.has_bins("curl")) then + error('Need "curl" to install this.') + return + end + vim.fn.mkdir(install_dir, 'p') + vim.fn.system(download_cmd) + vim.fn.system(extract_cmd) + vim.fn.system(make_executable_cmd) + end + function X.info() + return { + is_installed = util.path.exists(bin_path); + install_dir = install_dir; + cmd = { bin_path, "--languageserver" , "--hostPID", tostring(pid)}; + } + end + function X.configure(config) + local install_info = X.info() + if install_info.is_installed then + config.cmd = install_info.cmd + end + end + return X +end + +local installer = make_installer() + +configs[server_name] = { + default_config = { + cmd = installer.info().cmd; + filetypes = {"cs", "vb"}; + root_dir = util.root_pattern("*.csproj", "*.sln"); + on_new_config = function(config) + installer.configure(config) + end; + init_options = { + }; + }; + -- on_new_config = function(new_config) end; + -- on_attach = function(client, bufnr) end; + docs = { + description = [[ +https://github.com/omnisharp/omnisharp-roslyn +OmniSharp server based on Roslyn workspaces +]]; + default_config = { + root_dir = [[root_pattern(".csproj", ".sln", ".git")]]; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/purescriptls.lua b/lua/lspconfig/purescriptls.lua new file mode 100644 index 00000000..14465d5b --- /dev/null +++ b/lua/lspconfig/purescriptls.lua @@ -0,0 +1,46 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "purescriptls" +local bin_name = "purescript-language-server" + +local installer = util.npm_installer { + server_name = server_name; + packages = { "purescript", "purescript-language-server" }; + binaries = {bin_name, "purs"}; +} + +configs[server_name] = { + default_config = { + cmd = {"purescript-language-server", "--stdio"}; + filetypes = {"purescript"}; + root_dir = util.root_pattern("spago.dhall", "bower.json"); + }; + on_new_config = function(new_config) + local install_info = installer.info() + if install_info.is_installed then + if type(new_config.cmd) == 'table' then + new_config.cmd[1] = install_info.binaries[bin_name] + else + new_config.cmd = {install_info.binaries[bin_name]} + end + end + end; + docs = { + package_json = "https://raw.githubusercontent.com/nwolverson/vscode-ide-purescript/master/package.json"; + description = [[ +https://github.com/nwolverson/purescript-language-server +`purescript-language-server` can be installed via `:LspInstall purescriptls` or by yourself with `npm` +```sh +npm install -g purescript-language-server +``` +]]; + default_config = { + root_dir = [[root_pattern("spago.dhall, bower.json")]]; + }; + }; +}; +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info + +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/pyls.lua b/lua/lspconfig/pyls.lua new file mode 100644 index 00000000..fef0749d --- /dev/null +++ b/lua/lspconfig/pyls.lua @@ -0,0 +1,23 @@ +local configs = require 'lspconfig/configs' + +configs.pyls = { + default_config = { + cmd = {"pyls"}; + filetypes = {"python"}; + root_dir = function(fname) + return vim.fn.getcwd() + end; + }; + docs = { + package_json = "https://raw.githubusercontent.com/palantir/python-language-server/develop/vscode-client/package.json"; + description = [[ +https://github.com/palantir/python-language-server + +`python-language-server`, a language server for Python. + ]]; + default_config = { + root_dir = "vim's starting directory"; + }; + }; +}; +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/pyls_ms.lua b/lua/lspconfig/pyls_ms.lua new file mode 100644 index 00000000..a1a8b687 --- /dev/null +++ b/lua/lspconfig/pyls_ms.lua @@ -0,0 +1,153 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local name = "pyls_ms" + +local function get_latest_pyls() + local f = io.popen("curl -k --silent 'https://pvsc.blob.core.windows.net/python-language-server-stable?restype=container&comp=list&prefix=Python-Language-Server-osx-x64'") + local l = f:read("*a") + f:close() + local version + for w in string.gmatch (l, "x64%.(.-).nupkg") do + version = w + end + return version +end + +local function make_installer() + local P = util.path.join + local install_dir = P{util.base_install_dir, name} + + local bin = P{install_dir, "Microsoft.Python.LanguageServer.dll"} + local cmd = {"dotnet", "exec", bin} + + local X = {} + function X.install() + local install_info = X.info() + if install_info.is_installed then + print(name, "is already installed") + return + end + if not (util.has_bins("curl")) then + error('Need "curl" to install this.') + return + end + if not (util.has_bins("dotnet")) then + error('Need ".NET Core" to install this.') + return + end + + local system + if vim.fn.has('mac') == 1 then + system = 'osx' + elseif vim.fn.has('unix') == 1 then + system = 'linux' + elseif vim.fn.has('win32') == 1 then + system = 'win' + else + error('Unable to identify host operating system') + end + + local version = get_latest_pyls() + local url = string.format("https://pvsc.azureedge.net/python-language-server-stable/Python-Language-Server-%s-x64.%s.nupkg", string.lower(system), version) + local download_cmd = string.format('curl -fLo %s --create-dirs %s', install_info.install_dir .. "/pyls.nupkg", url) + local install_cmd = '' + + if vim.fn.has('mac') == 1 or vim.fn.has('unix') == 1 then + install_cmd = "unzip " .. install_info.install_dir .. "/pyls.nupkg -d " .. install_info.install_dir + elseif vim.fn.has('win32') == 1 then + install_cmd = "Expand-Archive -Force " .. install_info.install_dir .. "/pyls.nupkg -d " .. install_info.install_dir + end + + vim.fn.system(download_cmd) + vim.fn.system(install_cmd) + end + function X.info() + return { + is_installed = util.path.exists(bin); + install_dir = install_dir; + cmd = cmd; + } + end + function X.configure(config) + local install_info = X.info() + if install_info.is_installed then + config.cmd = cmd + end + end + return X +end + +local installer = make_installer() + +configs[name] = { + + default_config = { + filetypes = {"python"}; + root_dir = function(fname) + return util.find_git_ancestor(fname) or vim.loop.os_homedir() + end; + settings = { + python = { + analysis = { + errors = {}; + info = {}; + disabled = {}; + }; + }; + }; + on_new_config = function(config) + installer.configure(config) + end; + init_options = { + interpreter = { + properties = + { + InterpreterPath = ""; + Version = ""; + }; + }; + displayOptions = {}; + analysisUpdates = true; + asyncStartup = true; + }; + }; + docs = { + description = [[ +https://github.com/Microsoft/python-language-server + +`python-language-server`, a language server for Python. + +Requires [.NET Core](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script) to run. On Linux or macOS: + +```bash +curl -L https://dot.net/v1/dotnet-install.sh | sh +``` + +`python-language-server` can be installed via `:LspInstall pyls_ms` or you can [build](https://github.com/microsoft/python-language-server/blob/master/CONTRIBUTING.md#setup) your own. + +If you want to use your own build, set cmd to point to `Microsoft.Python.languageServer.dll`. + +```lua +cmd = { "dotnet", "exec", "path/to/Microsoft.Python.languageServer.dll" }; +``` + +If the `python` interpreter is not in your PATH environment variable, set the `InterpreterPath` and `Version` properties accordingly. + +```lua +InterpreterPath = "path/to/python", +Version = "3.8" +``` + +This server accepts configuration via the `settings` key. + + ]]; + default_config = { + root_dir = "vim's starting directory"; + }; + }; +}; + +configs[name].install = installer.install +configs[name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/r_language_server.lua b/lua/lspconfig/r_language_server.lua new file mode 100644 index 00000000..d75da11f --- /dev/null +++ b/lua/lspconfig/r_language_server.lua @@ -0,0 +1,30 @@ +local util = require 'lspconfig/util' +local configs = require 'lspconfig/configs' + +configs.r_language_server = { + default_config = { + cmd = {"R", "--slave", "-e", "languageserver::run()"}; + filetypes = {"r", "rmd"}; + root_dir = function(fname) + return util.find_git_ancestor(fname) or vim.loop.os_homedir() + end; + log_level = vim.lsp.protocol.MessageType.Warning; + }; + docs = { + package_json = "https://raw.githubusercontent.com/REditorSupport/vscode-r-lsp/master/package.json"; + description = [[ + [languageserver](https://github.com/REditorSupport/languageserver) is an + implementation of the Microsoft's Language Server Protocol for the R + language. + + It is released on CRAN and can be easily installed by + + ```R + install.packages("languageserver") + ``` + ]]; + default_config = { + root_dir = [[root_pattern(".git") or os_homedir]] + }; + }; +} diff --git a/lua/lspconfig/rls.lua b/lua/lspconfig/rls.lua new file mode 100644 index 00000000..a1b15d9f --- /dev/null +++ b/lua/lspconfig/rls.lua @@ -0,0 +1,31 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.rls = { + default_config = { + cmd = {"rls"}; + filetypes = {"rust"}; + root_dir = util.root_pattern("Cargo.toml"); + }; + docs = { + package_json = "https://raw.githubusercontent.com/rust-lang/rls-vscode/master/package.json"; + description = [[ +https://github.com/rust-lang/rls + +rls, a language server for Rust + +See https://github.com/rust-lang/rls#setup to setup rls itself. +See https://github.com/rust-lang/rls#configuration for rls-specific settings. + +If you want to use rls for a particular build, eg nightly, set cmd as follows: + +```lua +cmd = {"rustup", "run", "nightly", "rls"} +``` + ]]; + default_config = { + root_dir = [[root_pattern("Cargo.toml")]]; + }; + }; +}; +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/rnix.lua b/lua/lspconfig/rnix.lua new file mode 100644 index 00000000..deaeac98 --- /dev/null +++ b/lua/lspconfig/rnix.lua @@ -0,0 +1,81 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local name = "rnix" + +local function make_installer() + local P = util.path.join + local install_dir = P{util.base_install_dir, name} + + local bin = P{install_dir, "bin", "rnix-lsp"} + local cmd = {bin} + + local X = {} + function X.install() + local install_info = X.info() + if install_info.is_installed then + print(name, "is already installed") + return + end + if not (util.has_bins("cargo")) then + error('Need "cargo" to install this.') + return + end + + local install_cmd = "cargo install rnix-lsp --root=" .. install_info.install_dir .. " rnix-lsp" + + vim.fn.system(install_cmd) + end + function X.info() + return { + is_installed = util.path.exists(bin); + install_dir = install_dir; + cmd = cmd; + } + end + function X.configure(config) + local install_info = X.info() + if install_info.is_installed then + config.cmd = cmd + end + end + return X +end + +local installer = make_installer() + +configs[name] = { + + default_config = { + cmd = {"rnix-lsp"}; + filetypes = {"nix"}; + root_dir = function(fname) + return util.find_git_ancestor(fname) or vim.loop.os_homedir() + end; + settings = { + }; + on_new_config = function(config) + installer.configure(config) + end; + init_options = { + }; + }; + docs = { + description = [[ +https://github.com/nix-community/rnix-lsp + +A language server for Nix providing basic completion and formatting via nixpkgs-fmt. + +To install manually, run `cargo install rnix-lsp`. If you are using nix, rnix-lsp is in nixpkgs. + +This server accepts configuration via the `settings` key. + + ]]; + default_config = { + root_dir = "vim's starting directory"; + }; + }; +}; + +configs[name].install = installer.install +configs[name].install_info = installer.info diff --git a/lua/lspconfig/rome.lua b/lua/lspconfig/rome.lua new file mode 100644 index 00000000..a8d813f2 --- /dev/null +++ b/lua/lspconfig/rome.lua @@ -0,0 +1,37 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.rome = { + default_config = { + cmd = {'rome', 'lsp'}, + filetypes = { + 'javascript', + 'javascriptreact', + 'json', + 'typescript', + 'typescript.tsx', + 'typescriptreact' + }, + root_dir = function(fname) + return util.find_package_json_ancestor(fname) or + util.find_node_modules_ancestor(fname) or + util.find_git_ancestor(fname) or + util.path.dirname(fname) + end + }, + docs = { + description = [[ +https://romefrontend.dev + +Language server for the Rome Frontend Toolchain. + +```sh +npm install [-g] rome +``` +]], + default_config = { + root_dir = [[root_pattern('package.json', 'node_modules', '.git') or dirname]] + } + } +} + diff --git a/lua/lspconfig/rust_analyzer.lua b/lua/lspconfig/rust_analyzer.lua new file mode 100644 index 00000000..2d545517 --- /dev/null +++ b/lua/lspconfig/rust_analyzer.lua @@ -0,0 +1,27 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.rust_analyzer = { + default_config = { + cmd = {"rust-analyzer"}; + filetypes = {"rust"}; + root_dir = util.root_pattern("Cargo.toml", "rust-project.json"); + settings = { + ["rust-analyzer"] = {} + } + }; + docs = { + package_json = "https://raw.githubusercontent.com/rust-analyzer/rust-analyzer/master/editors/code/package.json"; + description = [[ +https://github.com/rust-analyzer/rust-analyzer + +rust-analyzer (aka rls 2.0), a language server for Rust + +See [docs](https://github.com/rust-analyzer/rust-analyzer/tree/master/docs/user#settings) for extra settings. + ]]; + default_config = { + root_dir = [[root_pattern("Cargo.toml", "rust-project.json")]]; + }; + }; +}; +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/scry.lua b/lua/lspconfig/scry.lua new file mode 100644 index 00000000..d2b5164e --- /dev/null +++ b/lua/lspconfig/scry.lua @@ -0,0 +1,24 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.scry = { + default_config = { + cmd = {'scry'}, + filetypes = {'crystal'}, + root_dir = function(fname) + return util.root_pattern('shard.yml') or + util.find_git_ancestor(fname) or + util.path.dirname(fname) + end + }, + docs = { + description = [[ +https://github.com/crystal-lang-tools/scry + +Crystal language server. +]], + default_config = { + root_dir = [[root_pattern('shard.yml', '.git') or dirname]] + } + } +} diff --git a/lua/lspconfig/solargraph.lua b/lua/lspconfig/solargraph.lua new file mode 100644 index 00000000..5aa2ec17 --- /dev/null +++ b/lua/lspconfig/solargraph.lua @@ -0,0 +1,32 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local bin_name = "solargraph" +if vim.fn.has('win32') == 1 then + bin_name = bin_name..'.bat' +end +configs.solargraph = { + default_config = { + cmd = {bin_name, "stdio"}; + filetypes = {"ruby"}; + root_dir = util.root_pattern("Gemfile", ".git"); + }; + docs = { + package_json = "https://raw.githubusercontent.com/castwide/vscode-solargraph/master/package.json"; + description = [[ +https://solargraph.org/ + +solargraph, a language server for Ruby + +You can install solargraph via gem install. + +```sh +gem install solargraph +``` + ]]; + default_config = { + root_dir = [[root_pattern("Gemfile", ".git")]]; + }; + }; +}; +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/sourcekit.lua b/lua/lspconfig/sourcekit.lua new file mode 100644 index 00000000..4b80b085 --- /dev/null +++ b/lua/lspconfig/sourcekit.lua @@ -0,0 +1,22 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.sourcekit = { + default_config = { + cmd = {"xcrun", "sourcekit-lsp"}; + filetypes = {"swift", "c", "cpp", "objective-c", "objective-cpp"}; + root_dir = util.root_pattern("Package.swift", ".git") + }; + docs = { + package_json = "https://raw.githubusercontent.com/apple/sourcekit-lsp/master/Editors/vscode/package.json"; + description = [[ +https://github.com/apple/sourcekit-lsp + +Language server for Swift and C/C++/Objective-C. + ]]; + default_config = { + root_dir = [[root_pattern("Package.swift", ".git")]]; + }; + }; +}; +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/sqlls.lua b/lua/lspconfig/sqlls.lua new file mode 100644 index 00000000..aced4e02 --- /dev/null +++ b/lua/lspconfig/sqlls.lua @@ -0,0 +1,52 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "sqlls" +local bin_name = "sql-language-server" + +local installer = util.npm_installer { + server_name = server_name; + packages = { "sql-language-server" }; + binaries = {bin_name}; +} + +local root_pattern = util.root_pattern(".sqllsrc.json") + +configs[server_name] = { + default_config = { + filetypes = {"sql", "mysql"}; + root_dir = function(fname) + return root_pattern(fname) or vim.loop.os_homedir() + end; + settings = {}; + }; + on_new_config = function(config) + local install_info = installer.info(); + local P = util.path.join + if install_info.is_installed then + local bin_ex = P{install_info.bin_dir, bin_name} + config.cmd = {bin_ex, "up", "--method", "stdio"} + end + end; + docs = { + description = [[ +https://github.com/joe-re/sql-language-server + +`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; + +```lua +require'lspconfig'.sqlls.setup{ + cmd = {"path/to/command", "up", "--method", "stdio"}; + ... +} +``` + +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). +<br> + ]]; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/sumneko_lua.lua b/lua/lspconfig/sumneko_lua.lua new file mode 100644 index 00000000..3faca644 --- /dev/null +++ b/lua/lspconfig/sumneko_lua.lua @@ -0,0 +1,133 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local name = "sumneko_lua" +local bin_name = "lua-language-server" + +local function make_installer() + local P = util.path.join + local install_dir = P{util.base_install_dir, name} + local git_dir = P{install_dir, bin_name} + local os, bin, ninja_zip, build_file + + if vim.fn.has('osx') == 1 then + os = 'macOS' + bin = P{git_dir, "bin", "macOS", bin_name} + ninja_zip = "ninja-mac.zip" + build_file = "macos.ninja" + elseif vim.fn.has('unix') == 1 then + os = 'Linux' + bin = P{git_dir, "bin", "Linux", bin_name} + ninja_zip = "ninja-linux.zip" + build_file = "linux.ninja" + end + local main_file = P{git_dir, "main.lua"} + local cmd = {bin, '-E', main_file} + + local X = {} + function X.install() + if os == nil then + error("This installer supports Linux and macOS only") + return + end + local install_info = X.info() + if install_info.is_installed then + print(name, "is already installed") + return + end + if not (util.has_bins("ninja") or util.has_bins("curl")) then + error('Need either "ninja" or "curl" (to download ninja) to install this.') + return + end + if not util.has_bins("sh", "chmod", "unzip", "clang") then + error('Need the binaries "sh", "chmod", "unzip", "clang" to install this') + return + end + local script = [=[ +set -e +bin_name=]=]..bin_name..'\n'..[=[ +ninja_zip=]=]..ninja_zip..'\n'..[=[ +build_file=]=]..build_file..'\n'..[=[ + +# Install ninja if not available. +which ninja >/dev/null || { + test -x ninja || { + curl -LO https://github.com/ninja-build/ninja/releases/download/v1.9.0/$ninja_zip + unzip $ninja_zip + chmod +x ninja + } + export PATH="$PWD:$PATH" +} + +# clone project +git clone https://github.com/sumneko/$bin_name +cd $bin_name +git submodule update --init --recursive + +# build +cd 3rd/luamake +ninja -f ninja/$build_file +cd ../.. +./3rd/luamake/luamake rebuild + ]=] + vim.fn.mkdir(install_info.install_dir, "p") + util.sh(script, install_info.install_dir) + end + function X.info() + return { + is_installed = util.has_bins(bin); + install_dir = install_dir; + cmd = cmd; + } + end + function X.configure(config) + local install_info = X.info() + if install_info.is_installed then + config.cmd = cmd + end + end + return X +end + +local installer = make_installer() + +configs[name] = { + default_config = { + filetypes = {'lua'}; + root_dir = function(fname) + return util.find_git_ancestor(fname) or vim.loop.os_homedir() + end; + log_level = vim.lsp.protocol.MessageType.Warning; + }; + on_new_config = function(config) + installer.configure(config) + end; + docs = { + package_json = "https://raw.githubusercontent.com/sumneko/vscode-lua/master/package.json"; + description = [[ +https://github.com/sumneko/lua-language-server + +Lua language server. **By default, this doesn't have a `cmd` set.** This is +because it doesn't provide a global binary. We provide an installer for Linux +and macOS using `:LspInstall`. If you wish to install it yourself, [here is a +guide](https://github.com/sumneko/lua-language-server/wiki/Build-and-Run-(Standalone)). +So you should set `cmd` yourself like this. + +```lua +require'lspconfig'.sumneko_lua.setup{ + cmd = {"path", "to", "cmd"}; + ... +} +``` + +If you install via our installer, if you execute `:LspInstallInfo sumneko_lua`, you can know `cmd` value. +]]; + default_config = { + root_dir = [[root_pattern(".git") or os_homedir]]; + }; + }; +} + +configs[name].install = installer.install +configs[name].install_info = installer.info +-- vim:et ts=2 diff --git a/lua/lspconfig/terraformls.lua b/lua/lspconfig/terraformls.lua new file mode 100644 index 00000000..7d551e5c --- /dev/null +++ b/lua/lspconfig/terraformls.lua @@ -0,0 +1,23 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +configs.terraformls = { + default_config = { + cmd = {"terraform-ls"}; + filetypes = {"terraform"}; + root_dir = util.root_pattern(".terraform", ".git"); + }; + docs = { + package_json = "https://raw.githubusercontent.com/hashicorp/vscode-terraform/master/package.json"; + description = [[ +https://github.com/hashicorp/terraform-ls + +Terraform language server +Download a released binary from https://github.com/hashicorp/terraform-ls/releases. +]]; + default_config = { + root_dir = [[root_pattern(".terraform", ".git")]]; + }; + }; +} +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/texlab.lua b/lua/lspconfig/texlab.lua new file mode 100644 index 00000000..b6e08927 --- /dev/null +++ b/lua/lspconfig/texlab.lua @@ -0,0 +1,85 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' +local lsp = vim.lsp + +local texlab_build_status = vim.tbl_add_reverse_lookup { + Success = 0; + Error = 1; + Failure = 2; + Cancelled = 3; +} + +local function buf_build(bufnr) + bufnr = util.validate_bufnr(bufnr) + local params = { textDocument = { uri = vim.uri_from_bufnr(bufnr) } } + lsp.buf_request(bufnr, 'textDocument/build', params, + function(err, _, result, _) + if err then error(tostring(err)) end + print("Build "..texlab_build_status[result.status]) + end) +end + +-- bufnr isn't actually required here, but we need a valid buffer in order to +-- be able to find the client for buf_request. +-- TODO find a client by looking through buffers for a valid client? +-- local function build_cancel_all(bufnr) +-- bufnr = util.validate_bufnr(bufnr) +-- local params = { token = "texlab-build-*" } +-- lsp.buf_request(bufnr, 'window/progress/cancel', params, function(err, method, result, client_id) +-- if err then error(tostring(err)) end +-- print("Cancel result", vim.inspect(result)) +-- end) +-- end + +configs.texlab = { + default_config = { + cmd = {"texlab"}; + filetypes = {"tex", "bib"}; + root_dir = vim.loop.os_homedir; + settings = { + latex = { + build = { + args = {"-pdf", "-interaction=nonstopmode", "-synctex=1"}; + executable = "latexmk"; + onSave = false; + }; + forwardSearch = { + args = {}; + executable = nil; + onSave = false; + }; + lint = { + onChange = false; + }; + }; + bibtex = { + formatting = { + lineLength = 120 + }; + }; + }; + }; + commands = { + TexlabBuild = { + function() + buf_build(0) + end; + description = "Build the current buffer"; + }; + }; + docs = { + description = [[ +https://texlab.netlify.com/ + +A completion engine built from scratch for (La)TeX. + +See https://texlab.netlify.com/docs/reference/configuration for configuration options. +]]; + default_config = { + root_dir = "vim's starting directory"; + }; + }; +} + +configs.texlab.buf_build = buf_build +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/tsserver.lua b/lua/lspconfig/tsserver.lua new file mode 100644 index 00000000..4afc6077 --- /dev/null +++ b/lua/lspconfig/tsserver.lua @@ -0,0 +1,50 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "tsserver" +local bin_name = "typescript-language-server" +if vim.fn.has('win32') == 1 then + bin_name = bin_name..".cmd" +end + +local installer = util.npm_installer { + server_name = server_name; + packages = { "typescript-language-server" }; + binaries = {bin_name}; +} + +configs[server_name] = { + default_config = { + cmd = {bin_name, "--stdio"}; + filetypes = {"javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx"}; + root_dir = util.root_pattern("package.json", "tsconfig.json", ".git"); + }; + 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; + docs = { + description = [[ +https://github.com/theia-ide/typescript-language-server + +`typescript-language-server` can be installed via `:LspInstall tsserver` or by yourself with `npm`: +```sh +npm install -g typescript-language-server +``` +]]; + default_config = { + root_dir = [[root_pattern("package.json", "tsconfig.json", ".git")]]; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/util.lua b/lua/lspconfig/util.lua new file mode 100644 index 00000000..14eee811 --- /dev/null +++ b/lua/lspconfig/util.lua @@ -0,0 +1,445 @@ +local vim = vim +local validate = vim.validate +local api = vim.api +local lsp = vim.lsp +local uv = vim.loop +local fn = vim.fn + +local M = {} + +M.default_config = { + log_level = lsp.protocol.MessageType.Warning; + message_level = lsp.protocol.MessageType.Warning; + settings = vim.empty_dict(); + init_options = vim.empty_dict(); + callbacks = {}; +} + +function M.validate_bufnr(bufnr) + validate { + bufnr = { bufnr, 'n' } + } + return bufnr == 0 and api.nvim_get_current_buf() or bufnr +end + +function M.add_hook_before(func, new_fn) + if func then + return function(...) + -- TODO which result? + new_fn(...) + return func(...) + end + else + return new_fn + end +end + +function M.add_hook_after(func, new_fn) + if func then + return function(...) + -- TODO which result? + func(...) + return new_fn(...) + end + else + return new_fn + end +end + +function M.tbl_deep_extend(behavior, ...) + if (behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force') then + error('invalid "behavior": '..tostring(behavior)) + end + + if select('#', ...) < 2 then + error('wrong number of arguments (given '..tostring(1 + select('#', ...))..', expected at least 3)') + end + + local ret = {} + if vim._empty_dict_mt ~= nil and getmetatable(select(1, ...)) == vim._empty_dict_mt then + ret = vim.empty_dict() + end + + for i = 1, select('#', ...) do + local tbl = select(i, ...) + vim.validate{["after the second argument"] = {tbl,'t'}} + if tbl then + for k, v in pairs(tbl) do + if type(v) == 'table' and not vim.tbl_islist(v) then + ret[k] = M.tbl_deep_extend(behavior, ret[k] or vim.empty_dict(), v) + elseif behavior ~= 'force' and ret[k] ~= nil then + if behavior == 'error' then + error('key found in more than one map: '..k) + end -- Else behavior is "keep". + else + ret[k] = v + end + end + end + end + return ret +end + +function M.nvim_multiline_command(command) + validate { command = { command, 's' } } + for line in vim.gsplit(command, "\n", true) do + api.nvim_command(line) + end +end + +function M.lookup_section(settings, section) + for part in vim.gsplit(section, '.', true) do + settings = settings[part] + if not settings then + return + end + end + return settings +end + +function M.create_module_commands(module_name, commands) + for command_name, def in pairs(commands) do + local parts = {"command!"} + -- Insert attributes. + for k, v in pairs(def) do + if type(k) == 'string' and type(v) == 'boolean' and v then + table.insert(parts, "-"..k) + elseif type(k) == 'number' and type(v) == 'string' and v:match("^%-") then + table.insert(parts, v) + end + end + table.insert(parts, command_name) + -- The command definition. + table.insert(parts, + string.format("lua require'lspconfig'[%q].commands[%q][1](<f-args>)", module_name, command_name)) + api.nvim_command(table.concat(parts, " ")) + end +end + +function M.has_bins(...) + for i = 1, select("#", ...) do + if 0 == fn.executable((select(i, ...))) then + return false + end + end + return true +end + +-- Some path utilities +M.path = (function() + local function exists(filename) + local stat = uv.fs_stat(filename) + return stat and stat.type or false + end + + local function is_dir(filename) + return exists(filename) == 'directory' + end + + local function is_file(filename) + return exists(filename) == 'file' + end + + local is_windows = uv.os_uname().version:match("Windows") + local path_sep = is_windows and "\\" or "/" + + local is_fs_root + if is_windows then + is_fs_root = function(path) + return path:match("^%a:$") + end + else + is_fs_root = function(path) + return path == "/" + end + end + + local function is_absolute(filename) + if is_windows then + return filename:match("^%a:") or filename:match("^\\\\") + else + return filename:match("^/") + end + end + + local dirname + do + local strip_dir_pat = path_sep.."([^"..path_sep.."]+)$" + local strip_sep_pat = path_sep.."$" + dirname = function(path) + if not path then return end + local result = path:gsub(strip_sep_pat, ""):gsub(strip_dir_pat, "") + if #result == 0 then + return "/" + end + return result + end + end + + local function path_join(...) + local result = + table.concat( + vim.tbl_flatten {...}, path_sep):gsub(path_sep.."+", path_sep) + return result + end + + -- Traverse the path calling cb along the way. + local function traverse_parents(path, cb) + path = uv.fs_realpath(path) + local dir = path + -- Just in case our algo is buggy, don't infinite loop. + for _ = 1, 100 do + dir = dirname(dir) + if not dir then return end + -- If we can't ascend further, then stop looking. + if cb(dir, path) then + return dir, path + end + if is_fs_root(dir) then + break + end + end + end + + -- Iterate the path until we find the rootdir. + local function iterate_parents(path) + path = uv.fs_realpath(path) + local function it(s, v) + if not v then return end + if is_fs_root(v) then return end + return dirname(v), path + end + return it, path, path + end + + local function is_descendant(root, path) + if (not path) then + return false; + end + + local function cb(dir, _) + return dir == root; + end + + local dir, _ = traverse_parents(path, cb); + + return dir == root; + end + + return { + is_dir = is_dir; + is_file = is_file; + is_absolute = is_absolute; + exists = exists; + sep = path_sep; + dirname = dirname; + join = path_join; + traverse_parents = traverse_parents; + iterate_parents = iterate_parents; + is_descendant = is_descendant; + } +end)() + + +-- Returns a function(root_dir), which, when called with a root_dir it hasn't +-- seen before, will call make_config(root_dir) and start a new client. +function M.server_per_root_dir_manager(_make_config) + local clients = {} + local manager = {} + + function manager.add(root_dir) + if not root_dir then return end + if not M.path.is_dir(root_dir) then return end + + -- Check if we have a client alredy or start and store it. + local client_id = clients[root_dir] + if not client_id then + local new_config = _make_config(root_dir) + new_config.on_exit = M.add_hook_before(new_config.on_exit, function() + clients[root_dir] = nil + end) + client_id = lsp.start_client(new_config) + clients[root_dir] = client_id + end + return client_id + end + + function manager.clients() + local res = {} + for _, id in pairs(clients) do + local client = lsp.get_client_by_id(id) + if client then + table.insert(res, client) + end + end + return res + end + + return manager +end + +function M.search_ancestors(startpath, func) + validate { func = {func, 'f'} } + if func(startpath) then return startpath end + for path in M.path.iterate_parents(startpath) do + if func(path) then return path end + end +end + +function M.root_pattern(...) + local patterns = vim.tbl_flatten {...} + local function matcher(path) + for _, pattern in ipairs(patterns) do + if M.path.exists(vim.fn.glob(M.path.join(path, pattern))) then + return path + end + end + end + return function(startpath) + return M.search_ancestors(startpath, matcher) + end +end +function M.find_git_ancestor(startpath) + return M.search_ancestors(startpath, function(path) + if M.path.is_dir(M.path.join(path, ".git")) then + return path + end + end) +end +function M.find_node_modules_ancestor(startpath) + return M.search_ancestors(startpath, function(path) + if M.path.is_dir(M.path.join(path, "node_modules")) then + return path + end + end) +end +function M.find_package_json_ancestor(startpath) + return M.search_ancestors(startpath, function(path) + if M.path.is_file(M.path.join(path, "package.json")) then + return path + end + 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, func) + local res = {} + for i, v in ipairs(t) do table.insert(res, func(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"), "lspconfig") +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 Nvim)") + 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 -e + mkdir -p "{{install_dir}}" + cd "{{install_dir}}" + [ ! -f package.json ] && npm init -y + npm install {{packages}} --no-package-lock --no-save --production + {{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.sh(script, cwd) + assert(cwd and M.path.is_dir(cwd), "sh: Invalid directory") + -- switching to insert mode makes the buffer scroll as new output is added + -- and makes it easy and intuitive to cancel the operation with Ctrl-C + api.nvim_command("10new | startinsert") + local bufnr = api.nvim_get_current_buf() + local function on_exit(job_id, code, event_type) + if code == 0 then + api.nvim_command("silent bwipeout! "..bufnr) + end + end + fn.termopen({"sh", "-c", script}, {cwd = cwd, on_exit = on_exit}) +end + +function M.format_vspackage_url(extension_name) + local org, package = unpack(vim.split(extension_name, ".", true)) + assert(org and package) + return string.format("https://marketplace.visualstudio.com/_apis/public/gallery/publishers/%s/vsextensions/%s/latest/vspackage", org, package) +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/lua/lspconfig/vimls.lua b/lua/lspconfig/vimls.lua new file mode 100644 index 00000000..15e5fed1 --- /dev/null +++ b/lua/lspconfig/vimls.lua @@ -0,0 +1,63 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "vimls" +local bin_name = "vim-language-server" +if vim.fn.has('win32') == 1 then + bin_name = bin_name..".cmd" +end + +local installer = util.npm_installer { + server_name = server_name, + packages = {"vim-language-server"}, + binaries = {bin_name} +} + +configs[server_name] = { + default_config = { + cmd = {bin_name, "--stdio"}, + filetypes = {"vim"}, + root_dir = function(fname) + return util.find_git_ancestor(fname) or vim.loop.os_homedir() + end, + init_options = { + iskeyword = "@,48-57,_,192-255,-#", + vimruntime = "", + runtimepath = "", + diagnostic = {enable = true}, + indexes = { + runtimepath = true, + gap = 100, + count = 3, + projectRootPatterns = {"runtime", "nvim", ".git", "autoload", "plugin"} + }, + suggest = {fromVimruntime = true, fromRuntimepath = true} + }, + 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, + docs = { + description = [[ +https://github.com/iamcco/vim-language-server + +If you don't want to use Nvim to install it, then you can use: +```sh +npm install -g vim-language-server +``` +]] + } + } +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info + +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/vuels.lua b/lua/lspconfig/vuels.lua new file mode 100644 index 00000000..4a8774bd --- /dev/null +++ b/lua/lspconfig/vuels.lua @@ -0,0 +1,125 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "vuels" +local bin_name = "vls" + +local installer = util.npm_installer { + server_name = server_name; + packages = { "vls" }; + binaries = {bin_name}; +} + +configs[server_name] = { + default_config = { + cmd = {bin_name}; + filetypes = {"vue"}; + root_dir = util.root_pattern("package.json", "vue.config.js"); + init_options = { + config = { + vetur = { + useWorkspaceDependencies = false; + validation = { + template = true; + style = true; + script = true; + }; + completion = { + autoImport = false; + useScaffoldSnippets = false; + tagCasing = "kebab"; + }; + format = { + defaultFormatter = { + js = "none"; + ts = "none"; + }; + defaultFormatterOptions = {}; + scriptInitialIndent = false; + styleInitialIndent = false; + } + }; + css = {}; + html = { + suggest = {}; + }; + javascript = { + format = {}; + }; + typescript = { + format = {}; + }; + emmet = {}; + stylusSupremacy = {}; + }; + }; + }; + 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; + docs = { + package_json = "https://raw.githubusercontent.com/vuejs/vetur/master/package.json"; + description = [[ +https://github.com/vuejs/vetur/tree/master/server + +Vue language server(vls) +`vue-language-server` can be installed via `:LspInstall vuels` or by yourself with `npm`: +```sh +npm install -g vls +``` +]]; + default_config = { + root_dir = [[root_pattern("package.json", "vue.config.js")]]; + init_options = { + config = { + vetur = { + useWorkspaceDependencies = false; + validation = { + template = true; + style = true; + script = true; + }; + completion = { + autoImport = false; + useScaffoldSnippets = false; + tagCasing = "kebab"; + }; + format = { + defaultFormatter = { + js = "none"; + ts = "none"; + }; + defaultFormatterOptions = {}; + scriptInitialIndent = false; + styleInitialIndent = false; + } + }; + css = {}; + html = { + suggest = {}; + }; + javascript = { + format = {}; + }; + typescript = { + format = {}; + }; + emmet = {}; + stylusSupremacy = {}; + }; + }; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 diff --git a/lua/lspconfig/yamlls.lua b/lua/lspconfig/yamlls.lua new file mode 100644 index 00000000..787e8df3 --- /dev/null +++ b/lua/lspconfig/yamlls.lua @@ -0,0 +1,47 @@ +local configs = require 'lspconfig/configs' +local util = require 'lspconfig/util' + +local server_name = "yamlls" +local bin_name = "yaml-language-server" + +local installer = util.npm_installer { + server_name = server_name; + packages = {bin_name}; + binaries = {bin_name}; +} + +configs[server_name] = { + default_config = { + cmd = {bin_name, "--stdio"}; + filetypes = {"yaml"}; + root_dir = util.root_pattern(".git", vim.fn.getcwd()); + }; + on_new_config = function(new_config) + local install_info = installer.info() + if install_info.is_installed then + if type(new_config.cmd) == 'table' then + new_config.cmd[1] = install_info.binaries[bin_name] + else + new_config.cmd = {install_info.binaries[bin_name]} + end + end + end; + docs = { + package_json = "https://raw.githubusercontent.com/redhat-developer/vscode-yaml/master/package.json"; + description = [[ +https://github.com/redhat-developer/yaml-language-server + +`yaml-language-server` can be installed via `:LspInstall yamlls` or by yourself with `npm`: +```sh +npm install -g yaml-language-server +``` +]]; + default_config = { + root_dir = [[root_pattern(".git", vim.fn.getcwd())]]; + }; + }; +} + +configs[server_name].install = installer.install +configs[server_name].install_info = installer.info +-- vim:et ts=2 sw=2 |
