diff --git a/doc/lspconfig.txt b/doc/lspconfig.txt index d1d9103c5b..166114db9b 100644 --- a/doc/lspconfig.txt +++ b/doc/lspconfig.txt @@ -254,12 +254,12 @@ The global defaults for all servers can be overridden by extending the if params and params.type <= vim.lsp.protocol.MessageType.Log then vim.lsp.handlers["window/logMessage"](err, method, params, client_id) end - end; + end, ["window/showMessage"] = function(err, method, params, client_id) if params and params.type <= vim.lsp.protocol.MessageType.Warning.Error then vim.lsp.handlers["window/showMessage"](err, method, params, client_id) end - end; + end, } } ) @@ -282,34 +282,37 @@ The `configs` module is a singleton where configs are defined. The schema for validating using `vim.validate` is: > configs.SERVER_NAME = { - default_config = {'t'}; - on_new_config = {'f', true}; - on_attach = {'f', true}; - commands = {'t', true}; - docs = {'t', true}; + default_config = {'t'}, + on_new_config = {'f', true}, + on_attach = {'f', true}, + user_commands = {'t', true}, + docs = {'t', true}, } < where the structure of the docs table is as follows: > docs = { - description = {'s', true}; - default_config = {'t', true}; + description = {'s', true}, + default_config = {'t', true}, } < -`commands` is a map of `name:definition` key:value pairs, where `definition` -is a list whose first value is a function implementing the command, and the -rest are either array values which will be formed into flags for the command, -or special keys like `description`. Example: +`user_commands` is a list of tables where each table consists of the fields +`name`, `command`, and `opts`. See |nvim_create_user_command| for more +details. + +Example: > - commands = { - TexlabBuild = { - function() + user_commands = { + { + name = "TexlabBuild" + command = function() buf_build(0) - end; - "-range"; - description = "Build the current buffer"; - }; - }; + end, + opts = { + desc = "Build the current buffer", + } + }, + }, < The `configs.__newindex` metamethod consumes the config definition and returns an object with a `setup()` method, to be invoked by users: @@ -342,13 +345,13 @@ The three steps for adding and enabling a new server configuration are: if not configs.foo_lsp then configs.foo_lsp = { default_config = { - cmd = {'/home/neovim/lua-language-server/run.sh'}; - filetypes = {'lua'}; + cmd = {'/home/neovim/lua-language-server/run.sh'}, + filetypes = {'lua'}, root_dir = function(fname) return lspconfig.util.find_git_ancestor(fname) - end; - settings = {}; - }; + end, + settings = {}, + }, } end diff --git a/lua/lspconfig.lua b/lua/lspconfig.lua index 8404632dfc..1178202eeb 100644 --- a/lua/lspconfig.lua +++ b/lua/lspconfig.lua @@ -4,69 +4,6 @@ local M = { util = require 'lspconfig.util', } -M._root = {} - -function M.available_servers() - return vim.tbl_keys(configs) -end - --- Called from plugin/lspconfig.vim because it requires knowing that the last --- script in scriptnames to be executed is lspconfig. -function M._root._setup() - M._root.commands = { - LspInfo = { - function() - require 'lspconfig.ui.lspinfo'() - end, - '-nargs=0', - description = '`:LspInfo` Displays attached, active, and configured language servers', - }, - LspStart = { - function(server_name) - if server_name then - if configs[server_name] then - configs[server_name].launch() - end - else - local buffer_filetype = vim.bo.filetype - for _, config in pairs(configs) do - for _, filetype_match in ipairs(config.filetypes or {}) do - if buffer_filetype == filetype_match then - config.launch() - end - end - end - end - end, - '-nargs=? -complete=custom,v:lua.lsp_complete_configured_servers', - description = '`:LspStart` Manually launches a language server.', - }, - LspStop = { - function(cmd_args) - for _, client in ipairs(M.util.get_clients_from_cmd_args(cmd_args)) do - client.stop() - end - end, - '-nargs=? -complete=customlist,v:lua.lsp_get_active_client_ids', - description = '`:LspStop` Manually stops the given language client(s).', - }, - LspRestart = { - function(cmd_args) - for _, client in ipairs(M.util.get_clients_from_cmd_args(cmd_args)) do - client.stop() - vim.defer_fn(function() - configs[client.name].launch() - end, 500) - end - end, - '-nargs=? -complete=customlist,v:lua.lsp_get_active_client_ids', - description = '`:LspRestart` Manually restart the given language client(s).', - }, - } - - M.util.create_module_commands('_root', M._root.commands) -end - local mt = {} function mt:__index(k) if configs[k] == nil then diff --git a/lua/lspconfig/configs.lua b/lua/lspconfig/configs.lua index a47ecbaedf..da3ff2d878 100644 --- a/lua/lspconfig/configs.lua +++ b/lua/lspconfig/configs.lua @@ -10,17 +10,17 @@ function configs.__newindex(t, config_name, config_def) 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 }, + user_commands = { config_def.user_commands, 't', true }, } - if config_def.commands then - for k, v in pairs(config_def.commands) do + if config_def.user_commands then + for _, v in pairs(config_def.user_commands) do validate { - ['command.name'] = { k, 's' }, - ['command.fn'] = { v[1], 'f' }, + name = { v.name, 's' }, + command = { v.command, 'f' }, } end else - config_def.commands = {} + config_def.user_commands = {} end local M = {} @@ -37,13 +37,13 @@ function configs.__newindex(t, config_name, config_def) 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 }, + user_commands = { config.user_commands, 't', true }, } - if config.commands then - for k, v in pairs(config.commands) do + if config.user_commands then + for _, v in pairs(config.user_commands) do validate { - ['command.name'] = { k, 's' }, - ['command.fn'] = { v[1], 'f' }, + name = { v.name, 's' }, + command = { v.command, 'f' }, } end end @@ -64,14 +64,14 @@ function configs.__newindex(t, config_name, config_def) event = 'BufReadPost' pattern = '*' end - api.nvim_command( - string.format( - "autocmd %s %s unsilent lua require'lspconfig'[%q].manager.try_add()", - event, - pattern, - config.name - ) - ) + local lsp_group = vim.api.nvim_create_augroup('lspconfig', { clear = false }) + vim.api.nvim_create_autocmd(event, { + pattern = pattern, + callback = function() + M.manager.try_add() + end, + group = lsp_group, + }) end local get_root_dir = config.root_dir @@ -88,13 +88,14 @@ function configs.__newindex(t, config_name, config_def) end if root_dir then - api.nvim_command( - string.format( - "autocmd BufReadPost %s/* unsilent lua require'lspconfig'[%q].manager.try_add_wrapper()", - vim.fn.fnameescape(root_dir), - config.name - ) - ) + local lsp_group = vim.api.nvim_create_augroup('lspconfig', { clear = false }) + vim.api.nvim_create_autocmd('BufReadPost', { + pattern = vim.fn.fnameescape(root_dir) .. '/*', + callback = function() + M.manager.try_add_wrapper() + end, + group = lsp_group, + }) for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do local bufname = api.nvim_buf_get_name(bufnr) if util.bufname_valid(bufname) then @@ -182,15 +183,15 @@ function configs.__newindex(t, config_name, config_def) if bufnr == api.nvim_get_current_buf() then M._setup_buffer(client.id, bufnr) else - api.nvim_command( - string.format( - "autocmd BufEnter ++once lua require'lspconfig'[%q]._setup_buffer(%d,%d)", - bufnr, - config_name, - client.id, - bufnr - ) - ) + local lsp_group = vim.api.nvim_create_augroup('lspconfig', { clear = false }) + vim.api.nvim_create_autocmd('BufEnter', { + callback = function() + M._setup_buffer(client.id, bufnr) + end, + group = lsp_group, + buffer = bufnr, + once = true, + }) end end) @@ -272,18 +273,17 @@ function configs.__newindex(t, config_name, config_def) if client.config._on_attach then client.config._on_attach(client, bufnr) end - if client.config.commands and not vim.tbl_isempty(client.config.commands) then - M.commands = vim.tbl_deep_extend('force', M.commands, client.config.commands) + if client.config.user_commands and not vim.tbl_isempty(client.config._user_commands) then + M.user_commands = vim.tbl_deep_extend('force', M.user_commands, client.config.user_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) + for _, command_info in pairs(M.user_commands or {}) do + vim.api.nvim_create_user_command(command_info.name, command_info.command, command_info.opts or {}) M.commands_created = true end end M.commands_created = false - M.commands = config_def.commands + M.user_commands = config_def.user_commands M.name = config_name M.document_config = config_def diff --git a/lua/lspconfig/server_configurations/clangd.lua b/lua/lspconfig/server_configurations/clangd.lua index 4e0f2d7f2c..64e02e9896 100644 --- a/lua/lspconfig/server_configurations/clangd.lua +++ b/lua/lspconfig/server_configurations/clangd.lua @@ -53,12 +53,15 @@ return { single_file_support = true, capabilities = default_capabilities, }, - commands = { - ClangdSwitchSourceHeader = { - function() + user_commands = { + { + name = 'ClangdSwitchSourceHeader', + command = function() switch_source_header(0) end, - description = 'Switch between source/header', + opts = { + desc = 'Switch between source/header', + }, }, }, docs = { diff --git a/lua/lspconfig/server_configurations/denols.lua b/lua/lspconfig/server_configurations/denols.lua index a7bed35fba..bf69188d48 100644 --- a/lua/lspconfig/server_configurations/denols.lua +++ b/lua/lspconfig/server_configurations/denols.lua @@ -83,12 +83,15 @@ return { ['textDocument/references'] = denols_handler, }, }, - commands = { - DenolsCache = { - function() + user_commands = { + { + name = 'DenolsCache', + command = function() buf_cache(0) end, - description = 'Cache a module and all of its dependencies.', + opts = { + desc = 'Cache a module and all of its dependencies.', + }, }, }, docs = { diff --git a/lua/lspconfig/server_configurations/eslint.lua b/lua/lspconfig/server_configurations/eslint.lua index 925cdfeea4..69599a3b4c 100644 --- a/lua/lspconfig/server_configurations/eslint.lua +++ b/lua/lspconfig/server_configurations/eslint.lua @@ -139,12 +139,15 @@ return { end, }, }, - commands = { - EslintFixAll = { - function() + user_commands = { + { + name = 'EslintFixAll', + command = function() fix_all { sync = true, bufnr = 0 } end, - description = 'Fix all eslint problems for this buffer', + opts = { + desc = 'Fix all eslint problems for this buffer', + }, }, }, docs = { diff --git a/lua/lspconfig/server_configurations/pyright.lua b/lua/lspconfig/server_configurations/pyright.lua index a198477f4c..65b86fef40 100644 --- a/lua/lspconfig/server_configurations/pyright.lua +++ b/lua/lspconfig/server_configurations/pyright.lua @@ -40,10 +40,13 @@ return { }, }, }, - commands = { - PyrightOrganizeImports = { - organize_imports, - description = 'Organize Imports', + user_commands = { + { + name = 'PyrightOrganizeImports', + command = organize_imports, + opts = { + desc = 'Organize Imports', + }, }, }, docs = { diff --git a/lua/lspconfig/server_configurations/rust_analyzer.lua b/lua/lspconfig/server_configurations/rust_analyzer.lua index 6331cf513a..16a8045c84 100644 --- a/lua/lspconfig/server_configurations/rust_analyzer.lua +++ b/lua/lspconfig/server_configurations/rust_analyzer.lua @@ -56,12 +56,15 @@ return { ['rust-analyzer'] = {}, }, }, - commands = { - CargoReload = { - function() + user_commands = { + { + name = 'CargoReload', + command = function() reload_workspace(0) end, - description = 'Reload current cargo workspace', + opts = { + desc = 'Reload current cargo workspace', + }, }, }, docs = { diff --git a/lua/lspconfig/server_configurations/texlab.lua b/lua/lspconfig/server_configurations/texlab.lua index 88bfa20ca8..68366fe5d5 100644 --- a/lua/lspconfig/server_configurations/texlab.lua +++ b/lua/lspconfig/server_configurations/texlab.lua @@ -100,18 +100,24 @@ return { }, }, }, - commands = { - TexlabBuild = { - function() + user_commands = { + { + name = 'TexlabBuild', + command = function() buf_build(0) end, - description = 'Build the current buffer', + opts = { + desc = 'Build the current buffer', + }, }, - TexlabForward = { - function() + { + name = 'TexlabForward', + command = function() buf_search(0) end, - description = 'Forward search from current position', + opts = { + desc = 'Forward search from current position', + }, }, }, docs = { diff --git a/lua/lspconfig/server_configurations/zk.lua b/lua/lspconfig/server_configurations/zk.lua index c2890451f7..4a36c61b93 100644 --- a/lua/lspconfig/server_configurations/zk.lua +++ b/lua/lspconfig/server_configurations/zk.lua @@ -6,18 +6,22 @@ return { filetypes = { 'markdown' }, root_dir = util.root_pattern '.zk', }, - commands = { - ZkIndex = { - function() + user_commands = { + { + name = 'ZkIndex', + command = function() vim.lsp.buf.execute_command { command = 'zk.index', arguments = { vim.api.nvim_buf_get_name(0) }, } end, - description = 'Index', + opts = { + desc = 'Index', + }, }, - ZkNew = { - function(...) + { + name = 'ZkNew', + command = function(...) vim.lsp.buf_request(0, 'workspace/executeCommand', { command = 'zk.new', arguments = { @@ -31,8 +35,9 @@ return { vim.cmd('edit ' .. result.path) end) end, - - description = 'ZkNew', + opts = { + desc = 'ZkNew', + }, }, }, docs = { diff --git a/lua/lspconfig/util.lua b/lua/lspconfig/util.lua index 3febafc0b9..e06dc41a42 100644 --- a/lua/lspconfig/util.lua +++ b/lua/lspconfig/util.lua @@ -3,7 +3,6 @@ local validate = vim.validate local api = vim.api local lsp = vim.lsp local uv = vim.loop -local fn = vim.fn local M = {} @@ -58,41 +57,6 @@ function M.add_hook_after(func, new_fn) end 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]()", 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 - -M.script_path = function() - local str = debug.getinfo(2, 'S').source:sub(2) - return str:match '(.*[/\\])' -end - -- Some path utilities M.path = (function() local is_windows = uv.os_uname().version:match 'Windows' @@ -393,17 +357,6 @@ function M.get_other_matching_providers(filetype) return other_matching_configs end -function M.get_clients_from_cmd_args(arg) - local result = {} - for id in (arg or ''):gmatch '(%d+)' do - result[id] = vim.lsp.get_client_by_id(tonumber(id)) - end - if vim.tbl_isempty(result) then - return M.get_managed_clients() - end - return vim.tbl_values(result) -end - function M.get_active_client_by_name(bufnr, servername) for _, client in pairs(vim.lsp.buf_get_clients(bufnr)) do if client.name == servername then diff --git a/plugin/lspconfig.lua b/plugin/lspconfig.lua new file mode 100644 index 0000000000..c15f3a7f35 --- /dev/null +++ b/plugin/lspconfig.lua @@ -0,0 +1,73 @@ +local util = require 'lspconfig.util' +local configs = require 'lspconfig.configs' + +local lsp_get_active_client_ids = function() + return vim.tbl_map(function(client) + return ('%d (%s)'):format(client.id, client.name) + end, require('lspconfig.util').get_managed_clients()) +end + +local get_clients_from_cmd_args = function(arg) + local result = {} + for id in (arg or ''):gmatch '(%d+)' do + result[id] = vim.lsp.get_client_by_id(tonumber(id)) + end + if vim.tbl_isempty(result) then + return util.get_managed_clients() + end + return vim.tbl_values(result) +end + +-- Called from plugin/lspconfig.vim because it requires knowing that the last +-- script in scriptnames to be executed is lspconfig. +vim.api.nvim_create_user_command('LspInfo', function() + require 'lspconfig.ui.lspinfo'() +end, { + desc = 'Displays attached, active, and configured language servers', +}) + +vim.api.nvim_create_user_command('LspStart', function(info) + local server_name = info.fargs[1] + if server_name then + if configs[server_name] then + configs[server_name].launch() + end + else + local buffer_filetype = vim.bo.filetype + for _, config in pairs(configs) do + for _, filetype_match in ipairs(config.filetypes or {}) do + if buffer_filetype == filetype_match then + config.launch() + end + end + end + end +end, { + desc = 'Manually launches a language server', + nargs = '?', + complete = function() + return vim.tbl_keys(configs) + end, +}) +vim.api.nvim_create_user_command('LspRestart', function(info) + for _, client in ipairs(get_clients_from_cmd_args(info.args)) do + client.stop() + vim.defer_fn(function() + configs[client.name].launch() + end, 500) + end +end, { + desc = 'Manually restart the given language client(s)', + nargs = '?', + complete = lsp_get_active_client_ids, +}) + +vim.api.nvim_create_user_command('LspStop', function(info) + for _, client in ipairs(get_clients_from_cmd_args(info.args)) do + client.stop() + end +end, { + desc = 'Manually stops the given language client(s)', + nargs = '?', + complete = lsp_get_active_client_ids, +}) diff --git a/plugin/lspconfig.vim b/plugin/lspconfig.vim deleted file mode 100644 index 5c52d4e199..0000000000 --- a/plugin/lspconfig.vim +++ /dev/null @@ -1,16 +0,0 @@ -if exists('g:lspconfig') - finish -endif -let g:lspconfig = 1 - -lua << EOF -lsp_complete_configured_servers = function() - return table.concat(require'lspconfig'.available_servers(), '\n') -end -lsp_get_active_client_ids = function() - return vim.tbl_map(function(client) - return ("%d (%s)"):format(client.id, client.name) - end, require'lspconfig.util'.get_managed_clients()) -end -require'lspconfig'._root._setup() -EOF diff --git a/scripts/docgen.lua b/scripts/docgen.lua index 375c5bd47a..45b4c72b9f 100644 --- a/scripts/docgen.lua +++ b/scripts/docgen.lua @@ -74,7 +74,7 @@ local lsp_section_template = [[ ```lua require'lspconfig'.{{template_name}}.setup{} ``` -{{commands}} +{{user_commands}} **Default values:** {{default_values}} @@ -100,18 +100,18 @@ local function make_lsp_sections() local params = { template_name = template_name, preamble = '', - commands = '', + user_commands = '', default_values = '', } - params.commands = make_section(0, '\n\n', { + params.user_commands = make_section(0, '\n\n', { function() - if not template_def.commands or #vim.tbl_keys(template_def.commands) == 0 then + if not template_def.user_commands or #vim.tbl_keys(template_def.user_commands) == 0 then return end return '**Commands:**\n' .. make_section(0, '\n', { - sorted_map_table(template_def.commands, function(name, def) + sorted_map_table(template_def.user_commands, function(name, def) if def.description then return string.format('- %s: %s', name, def.description) end diff --git a/test/minimal_init.lua b/test/minimal_init.lua index 6b579ee241..ceabef79d6 100644 --- a/test/minimal_init.lua +++ b/test/minimal_init.lua @@ -29,7 +29,7 @@ local function load_plugins() } end -_G.load_config = function() +local load_config = function() vim.lsp.set_log_level 'trace' if vim.fn.has 'nvim-0.5.1' == 1 then require('vim.lsp.log').set_format_func(vim.inspect) @@ -87,9 +87,13 @@ if vim.fn.isdirectory(install_path) == 0 then vim.fn.system { 'git', 'clone', 'https://github.com/wbthomason/packer.nvim', install_path } load_plugins() require('packer').sync() - vim.cmd [[autocmd User PackerComplete ++once lua load_config()]] + local packer_group = vim.api.nvim_create_augroup('Packer', { clear = true }) + vim.api.nvim_create_autocmd( + 'User', + { pattern = 'PackerComplete', callback = load_config, group = packer_group, once = true } + ) else load_plugins() require('packer').sync() - _G.load_config() + load_config() end