diff options
| author | Ashkan Kiani <ashkan.k.kiani@gmail.com> | 2019-11-14 00:04:56 -0800 |
|---|---|---|
| committer | Ashkan Kiani <ashkan.k.kiani@gmail.com> | 2019-11-14 00:04:56 -0800 |
| commit | 7fbe851feb26529434b64d8fb3a30f5bb01f180c (patch) | |
| tree | b8d81bff4b77b93eeadffa2afa57b59f67f06a7f | |
| parent | Add note about gopls (diff) | |
| download | nvim-lspconfig-7fbe851feb26529434b64d8fb3a30f5bb01f180c.tar nvim-lspconfig-7fbe851feb26529434b64d8fb3a30f5bb01f180c.tar.gz nvim-lspconfig-7fbe851feb26529434b64d8fb3a30f5bb01f180c.tar.bz2 nvim-lspconfig-7fbe851feb26529434b64d8fb3a30f5bb01f180c.tar.lz nvim-lspconfig-7fbe851feb26529434b64d8fb3a30f5bb01f180c.tar.xz nvim-lspconfig-7fbe851feb26529434b64d8fb3a30f5bb01f180c.tar.zst nvim-lspconfig-7fbe851feb26529434b64d8fb3a30f5bb01f180c.zip | |
Use new skeleton. Add docs generator.
| -rw-r--r-- | README.md | 196 | ||||
| -rw-r--r-- | README_preamble.md | 167 | ||||
| -rw-r--r-- | lua/common_lsp.lua | 13 | ||||
| -rw-r--r-- | lua/common_lsp/gopls.lua | 192 | ||||
| -rw-r--r-- | lua/common_lsp/skeleton.lua | 241 | ||||
| -rw-r--r-- | lua/common_lsp/texlab.lua | 170 | ||||
| -rw-r--r-- | lua/common_lsp/util.lua | 5 | ||||
| -rw-r--r-- | scripts/docgen.lua | 106 |
8 files changed, 593 insertions, 497 deletions
@@ -16,7 +16,7 @@ things as much as you want in addition to the defaults that this provides. There's a lot of language servers in the world, and not enough time. See [`lua/common_lsp/texlab.lua`](https://github.com/norcalli/nvim-common-lsp/blob/master/lua/common_lsp/texlab.lua) and -[`lua/common_lsp/skeleton.lua`](https://github.com/norcalli/nvim-common-lsp/blob/master/lua/common_lsp/skeleton.lua) +[`lua/common_lsp/gopls.lua`](https://github.com/norcalli/nvim-common-lsp/blob/master/lua/common_lsp/gopls.lua) for examples and ask me questions in the [Neovim Gitter](https://gitter.im/neovim/neovim) to help me complete configurations for *all the LSPs!* @@ -26,11 +26,8 @@ implemented from [this excellent list compiled by the coc.nvim contributors](https://github.com/neoclide/coc.nvim/wiki/Language-servers) or [this other excellent list from the emacs lsp-mode contributors](https://github.com/emacs-lsp/lsp-mode#supported-languages) -and create a new file under `lua/common_lsp/SERVER_NAME.lua`. -- For a simple server which should only ever have one instance for the entire -neovim lifetime, I recommend copying `lua/common_lsp/texlab.lua`. -- For servers which should have a different instance for each project root, I -recommend copying `lua/common_lsp/gopls.lua`. +and create a new file under `lua/common_lsp/SERVER_NAME.lua`. I recommend +looking at `lua/common_lsp/texlab.lua` for inspiration. ## Progress @@ -55,15 +52,6 @@ In progress: From vim: ```vim -call common_lsp#texlab({}) -call common_lsp#gopls({}) - -" These are still TODO, but will be done. -call common_lsp#clangd({}) -call common_lsp#ccls({}) -call common_lsp#tsserver({}) - -" Or using a dynamic name. call common_lsp#setup("texlab", {}) call common_lsp#setup("gopls", {}) ``` @@ -90,101 +78,127 @@ common_lsp.gopls.setup { } -- Build the current buffer. -require 'common_lsp'.texlab.buf_build(0) +common_lsp.texlab.buf_build(0) ``` -# LSP Implementations +``` +These are functions to set up servers more easily with some server specific +defaults and more server specific things like commands or different +diagnostics. -## texlab +Servers may define extra functions on the `common_lsp.SERVER` table, e.g. +`common_lsp.texlab.buf_build({bufnr})`. -https://texlab.netlify.com/ +The main setup signature will be: -``` -common_lsp.texlab.setup({config}) -common_lsp#texlab({config}) +common_lsp.SERVER.setup({config}) - A function to set up texlab easier. + {config} is the same as |vim.lsp.start_client()|, but with some + additions and changes: - Additionally, it sets up the following commands: - - `TexlabBuild`: builds the current buffer. + {root_dir} + May be required (depending on the server). + `function(filename, bufnr)` which is called on new candidate buffers to + attach to and returns either a root_dir or nil. - {config} is the same as |vim.lsp.add_filetype_config()|, but with some - additions and changes: + If a root_dir is returned, then this file will also be attached. You + can optionally use {filetype} to help pre-filter by filetype. - {log_level} - controls the level of logs to show from build processes and other - window/logMessage events. By default it is set to - vim.lsp.protocol.MessageType.Warning instead of - vim.lsp.protocol.MessageType.Log. + If a root_dir is returned which is unique from any previously returned + root_dir, a new server will be spawned with that root_dir. - {settings} - The settings specified here https://texlab.netlify.com/docs/reference/configuration. - This is a table, and the keys are case sensitive. - Example: `settings = { latex = { build = { onSave = true } } }` + If nil is returned, the buffer is skipped. - {filetype} - Defaults to {"tex", "bib"} + See |common_lsp.util.search_ancestors()| and the functions which use it: + - |common_lsp.util.root_pattern(patterns...)| finds an ancestor which + - contains one of the files in `patterns...`. This is equivalent + to coc.nvim's "rootPatterns" + - More specific utilities: + - |common_lsp.util.find_git_root()| + - |common_lsp.util.find_node_modules_root()| + - |common_lsp.util.find_package_json_root()| - {cmd} - Defaults to {"texlab"} + {name} + Defaults to the server's name. - {name} - Defaults to "texlab" + {filetypes} + A set of filetypes to filter for consideration by {root_dir}. + Can be left empty. + A server may specify a default value. + + {log_level} + controls the level of logs to show from build processes and other + window/logMessage events. By default it is set to + vim.lsp.protocol.MessageType.Warning instead of + vim.lsp.protocol.MessageType.Log. + + {settings} + This is a table, and the keys are case sensitive. This is for the + window/configuration event responses. + Example: `settings = { keyName = { subKey = 1 } }` + + {on_attach} + `function(client)` will be executed with the current buffer as the + one the {client} is being attaching to. This is different from + |vim.lsp.start_client()|'s on_attach parameter, which passes the {bufnr} as + the second parameter instead. This is useful for running buffer local + commands. + + {on_new_config} + `function(new_config)` will be executed after a new configuration has been + created as a result of {root_dir} returning a unique value. You can use this + as an opportunity to further modify the new_config or use it before it is + sent to |vim.lsp.start_client()|. ``` +# LSP Implementations + +## texlab + +https://texlab.netlify.com/ + +A completion engine built from scratch for (la)tex. + + + +common_lsp.texlab.setup({config}) +common_lsp#setup("texlab", {config}) + +``` + Commands: + - TexlabBuild: Build the current buffer + + Default Values: + cmd = { "texlab" } + filetypes = { "tex", "bib" } + log_level = 2 + root_dir = vim's starting directory + settings = { + latex = { + build = { + args = { "-pdf", "-interaction=nonstopmode", "-synctex=1" }, + executable = "latexmk", + onSave = false + } + } + } +``` ## gopls https://github.com/golang/tools/tree/master/gopls -``` +Google's lsp server for golang. + + + common_lsp.gopls.setup({config}) -common_lsp#gopls({config}) +common_lsp#setup("gopls", {config}) - A function to set up `gopls` easier. - - Additionally, it sets up the following commands: - - SKELETON_SPOOKY_COMMAND: This does something SPOOKY. - - {config} is the same as |vim.lsp.add_filetype_config()|, but with some - additions and changes: - - {root_dir} - REQUIRED function(filename, bufnr) which is called on new candidate - buffers to attach to and returns either a root_dir or nil. - If a root_dir is returned, then this file will also be attached. You can - optionally use {filetype} to help pre-filter by filetype. - If a root_dir is returned which differs from any previously returned - root_dir, a new server will be spawned with that root_dir. - If nil is returned, the buffer is skipped. - - See |common_lsp.util.search_ancestors()| and the functions which use it: - - |common_lsp.util.root_pattern(patterns...)| finds an ancestor which a - descendent which has one of the files in `patterns...`. This is equivalent - to coc.nvim's "rootPatterns" - - More specific utilities: - - |common_lsp.util.find_git_root()| - - |common_lsp.util.find_node_modules_root()| - - |common_lsp.util.find_package_json_root()| - - Defaults to common_lsp.util.root_pattern("go.mod", ".git") - - {name} - Defaults to "gopls" - - {cmd} - Defaults to {"gopls"} - - {filetype} - Defaults to {"go"}. This is optional and only serves to reduce the scope - of files to filter for {root_dir}. - - {log_level} - controls the level of logs to show from build processes and other - window/logMessage events. By default it is set to - vim.lsp.protocol.MessageType.Warning instead of - vim.lsp.protocol.MessageType.Log. - - {settings} - This is a table, and the keys are case sensitive. - Example: `settings = { }` +``` + Default Values: + cmd = { "gopls" } + filetypes = { "go" } + log_level = 2 + root_dir = vim's starting directory + settings = {} ``` diff --git a/README_preamble.md b/README_preamble.md new file mode 100644 index 00000000..dcd318cb --- /dev/null +++ b/README_preamble.md @@ -0,0 +1,167 @@ +# nvim-common-lsp + +WIP Common configurations for Language Servers. + +This repository aims to be a central location to store configurations for +Language Servers which leverages Neovim's built-in LSP client `vim.lsp` for the +client backbone. The `vim.lsp` implementation is made to be customizable and +greatly extensible, but most users just want to get up and going. This +plugin/library is for those people, although it still let's you customize +things as much as you want in addition to the defaults that this provides. + +**NOTE**: Requires current Neovim master as of 2019-11-13 + +**CONTRIBUTIONS ARE WELCOME!** + +There's a lot of language servers in the world, and not enough time. See +[`lua/common_lsp/texlab.lua`](https://github.com/norcalli/nvim-common-lsp/blob/master/lua/common_lsp/texlab.lua) +and +[`lua/common_lsp/skeleton.lua`](https://github.com/norcalli/nvim-common-lsp/blob/master/lua/common_lsp/skeleton.lua) +for examples and ask me questions in the [Neovim +Gitter](https://gitter.im/neovim/neovim) to help me complete configurations for +*all the LSPs!* + +If you don't know where to start, you can pick one that's not in progress or +implemented from [this excellent list compiled by the coc.nvim +contributors](https://github.com/neoclide/coc.nvim/wiki/Language-servers) or +[this other excellent list from the emacs lsp-mode +contributors](https://github.com/emacs-lsp/lsp-mode#supported-languages) +and create a new file under `lua/common_lsp/SERVER_NAME.lua`. +- For a simple server which should only ever have one instance for the entire +neovim lifetime, I recommend copying `lua/common_lsp/texlab.lua`. +- For servers which should have a different instance for each project root, I +recommend copying `lua/common_lsp/gopls.lua`. + +## Progress + +Implemented: +- [gopls](https://github.com/norcalli/nvim-common-lsp#gopls) (has some errors) +- [texlab](https://github.com/norcalli/nvim-common-lsp#texlab) + +Planned servers to implement (by me, but contributions welcome anyway): +- [clangd](https://clang.llvm.org/extra/clangd/Installation.html) +- [ccls](https://github.com/MaskRay/ccls) +- [lua-language-server](https://github.com/sumneko/lua-language-server) +- [rust-analyzer](https://github.com/rust-analyzer/rust-analyzer) + +In progress: +- ... + +## Install + +`Plug 'norcalli/nvim-common-lsp'` + +## Use + +From vim: +```vim +call common_lsp#texlab({}) +call common_lsp#gopls({}) + +" These are still TODO, but will be done. +call common_lsp#clangd({}) +call common_lsp#ccls({}) +call common_lsp#tsserver({}) + +" Or using a dynamic name. +call common_lsp#setup("texlab", {}) +call common_lsp#setup("gopls", {}) +``` + +From Lua: +```lua +require 'common_lsp'.texlab.setup { + name = "texlab_fancy"; + log_level = vim.lsp.protocol.MessageType.Log; + settings = { + latex = { + build = { + onSave = true; + } + } + } +} + +local common_lsp = require 'common_lsp' + +-- Customize how to find the root_dir +common_lsp.gopls.setup { + root_dir = common_lsp.util.root_pattern(".git"); +} + +-- Build the current buffer. +require 'common_lsp'.texlab.buf_build(0) +``` + +``` +These are functions to set up servers more easily with some server specific +defaults and more server specific things like commands or different +diagnostics. + +Servers may define extra functions on the `common_lsp.SERVER` table, e.g. +`common_lsp.texlab.buf_build({bufnr})`. + +The main setup signature will be: + +common_lsp.SERVER.setup({config}) + + {config} is the same as |vim.lsp.start_client()|, but with some + additions and changes: + + {root_dir} + May be required (depending on the server). + `function(filename, bufnr)` which is called on new candidate buffers to + attach to and returns either a root_dir or nil. + + If a root_dir is returned, then this file will also be attached. You + can optionally use {filetype} to help pre-filter by filetype. + + If a root_dir is returned which is unique from any previously returned + root_dir, a new server will be spawned with that root_dir. + + If nil is returned, the buffer is skipped. + + See |common_lsp.util.search_ancestors()| and the functions which use it: + - |common_lsp.util.root_pattern(patterns...)| finds an ancestor which + - contains one of the files in `patterns...`. This is equivalent + to coc.nvim's "rootPatterns" + - More specific utilities: + - |common_lsp.util.find_git_root()| + - |common_lsp.util.find_node_modules_root()| + - |common_lsp.util.find_package_json_root()| + + {name} + Defaults to the server's name. + + {filetypes} + A set of filetypes to filter for consideration by {root_dir}. + Can be left empty. + A server may specify a default value. + + {log_level} + controls the level of logs to show from build processes and other + window/logMessage events. By default it is set to + vim.lsp.protocol.MessageType.Warning instead of + vim.lsp.protocol.MessageType.Log. + + {settings} + This is a table, and the keys are case sensitive. This is for the + window/configuration event responses. + Example: `settings = { keyName = { subKey = 1 } }` + + {on_attach} + `function(client)` will be executed with the current buffer as the + one the {client} is being attaching to. This is different from + |vim.lsp.start_client()|'s on_attach parameter, which passes the {bufnr} as + the second parameter instead. This is useful for running buffer local + commands. + + {on_new_config} + `function(new_config)` will be executed after a new configuration has been + created as a result of {root_dir} returning a unique value. You can use this + as an opportunity to further modify the new_config or use it before it is + sent to |vim.lsp.start_client()|. +``` + +# LSP Implementations + diff --git a/lua/common_lsp.lua b/lua/common_lsp.lua index 2ff03e59..eafd5e2c 100644 --- a/lua/common_lsp.lua +++ b/lua/common_lsp.lua @@ -1,8 +1,15 @@ +local skeleton = require 'common_lsp/skeleton' +require 'common_lsp/gopls' +require 'common_lsp/texlab' + local M = { - texlab = require 'common_lsp/texlab'; - gopls = require 'common_lsp/gopls'; util = require 'common_lsp/util'; } -return M +local mt = {} +function mt:__index(k) + return skeleton[k] +end + +return setmetatable(M, mt) -- vim:et ts=2 sw=2 diff --git a/lua/common_lsp/gopls.lua b/lua/common_lsp/gopls.lua index 96e9df76..306c7f2c 100644 --- a/lua/common_lsp/gopls.lua +++ b/lua/common_lsp/gopls.lua @@ -1,178 +1,26 @@ +local skeleton = require 'common_lsp/skeleton' local util = require 'common_lsp/util' -local api, validate, lsp = vim.api, vim.validate, vim.lsp +local lsp = vim.lsp -local M = {} - -M.name = "gopls" - -local default_config -default_config = { - name = M.name; - cmd = {"gopls"}; - filetype = {"go"}; - root_dir = util.root_pattern("go.mod", ".git"); - log_level = lsp.protocol.MessageType.Warning; - settings = {}; -} - -local function setup_callbacks(config) - config.callbacks = config.callbacks or {} - - config.callbacks["window/logMessage"] = function(err, method, params, client_id) - if params and params.type <= config.log_level then - lsp.builtin_callbacks[method](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 - -- Uncomment this to debug. - -- print(string.format("config[%q] = %s", item.section, inspect(value))) - table.insert(result, value) - end - end - return result - end -end - --- A function to set up `gopls` easier. --- --- Additionally, it sets up the following commands: --- - SKELETON_SPOOKY_COMMAND: This does something SPOOKY. --- --- {config} is the same as |vim.lsp.add_filetype_config()|, but with some --- additions and changes: --- --- {root_dir} --- REQUIRED function(filename, bufnr) which is called on new candidate --- buffers to attach to and returns either a root_dir or nil. --- If a root_dir is returned, then this file will also be attached. You can --- optionally use {filetype} to help pre-filter by filetype. --- If a root_dir is returned which differs from any previously returned --- root_dir, a new server will be spawned with that root_dir. --- If nil is returned, the buffer is skipped. - --- See |common_lsp.util.search_ancestors()| and the functions which use it: --- - |common_lsp.util.root_pattern(patterns...)| finds an ancestor which a --- descendent which has one of the files in `patterns...`. This is equivalent --- to coc.nvim's "rootPatterns" --- - More specific utilities: --- - |common_lsp.util.find_git_root()| --- - |common_lsp.util.find_node_modules_root()| --- - |common_lsp.util.find_package_json_root()| --- --- Defaults to common_lsp.util.root_pattern("go.mod", ".git") --- --- {name} --- Defaults to "gopls" --- --- {cmd} --- Defaults to {"gopls"} --- --- {filetype} --- Defaults to {"go"}. This is optional and only serves to reduce the scope --- of files to filter for {root_dir}. --- --- {log_level} --- controls the level of logs to show from build processes and other --- window/logMessage events. By default it is set to --- vim.lsp.protocol.MessageType.Warning instead of --- vim.lsp.protocol.MessageType.Log. --- --- {settings} --- This is a table, and the keys are case sensitive. --- Example: `settings = { }` -function M.setup(config) - validate { - root_dir = {config.root_dir, 'f'}; - filetype = {config.filetype, 't', true}; - } - - local filetype = config.filetype or default_config.filetype - - if filetype then - local filetypes - if type(filetype) == 'string' then - filetypes = { filetype } - else - filetypes = filetype - end - api.nvim_command(string.format( - "autocmd FileType %s lua require'common_lsp'[%q].manager.try_add()" - , table.concat(filetypes, ',') - , M.name - )) - else - api.nvim_command(string.format( - "autocmd BufReadPost * lua require'common_lsp'[%q].manager.try_add()" - , M.name - )) - end - - local get_root_dir = config.root_dir or default_config.root_dir - - M.manager = util.server_per_root_dir_manager(function(_root_dir) - local new_config = vim.tbl_extend("keep", config, default_config) - -- Deepcopy anything that is >1 level nested. - new_config.settings = vim.deepcopy(new_config.settings) - util.tbl_deep_extend(new_config.settings, default_config.settings) - - new_config.capabilities = new_config.capabilities or lsp.protocol.make_client_capabilities() - util.tbl_deep_extend(new_config.capabilities, { - workspace = { - configuration = true; - } - }) - - setup_callbacks(new_config) - - new_config.on_attach = util.add_hook_after(new_config.on_attach, function(client, bufnr) - if bufnr == api.nvim_get_current_buf() then - M._setup_buffer() - else - api.nvim_command(string.format( - "autocmd BufEnter <buffer=%d> ++once lua require'common_lsp/%s'._setup_buffer()", - M.name, - bufnr)) - end - end) - return new_config - end) - - function M.manager.try_add() - local root_dir = get_root_dir(api.nvim_buf_get_name(0), api.nvim_get_current_buf()) - print(api.nvim_get_current_buf(), root_dir) - local id = M.manager.add(root_dir) - lsp.buf_attach_client(0, id) - end -end +skeleton.gopls = { + default_config = { + cmd = {"gopls"}; + filetypes = {"go"}; + root_dir = util.root_pattern("go.mod", ".git"); + log_level = lsp.protocol.MessageType.Warning; + settings = {}; + }; + -- on_new_config = function(new_config) end; + -- on_attach = function(client, bufnr) end; + docs = { + description = [[ +https://github.com/golang/tools/tree/master/gopls --- Declare any commands here. You can use additional modifiers like "-range" --- which will be added as command options. All of these commands are buffer --- level by default. -M.commands = { - SKELETON_SPOOKY_COMMAND = { - function() - local bufnr = util.validate_bufnr(0) - print("SPOOKY COMMAND STUFF!", bufnr) - end; +Google's lsp server for golang. +]]; + default_config = { + root_dir = "vim's starting directory"; + }; }; } - -function M._setup_buffer() - -- Create the module commands - util.create_module_commands(M.name, M.commands) -end - -return M -- vim:et ts=2 sw=2 - - diff --git a/lua/common_lsp/skeleton.lua b/lua/common_lsp/skeleton.lua index f030030a..574c4bb8 100644 --- a/lua/common_lsp/skeleton.lua +++ b/lua/common_lsp/skeleton.lua @@ -1,133 +1,162 @@ local util = require 'common_lsp/util' local api, validate, lsp = vim.api, vim.validate, vim.lsp -local inspect = vim.inspect +local tbl_extend = vim.tbl_extend -local M = {} +local skeleton = {} -M.name = "SKELETON" -local default_config -default_config = { - name = M.name; - cmd = {"SKELETON"}; - filetype = {"SKELETON"}; - log_level = lsp.protocol.MessageType.Warning; - settings = {}; -} - -local function setup_callbacks(config) - config.callbacks = config.callbacks or {} - - config.callbacks["window/logMessage"] = function(err, method, params, client_id) - if params and params.type <= config.log_level then - lsp.builtin_callbacks[method](err, method, params, client_id) +function skeleton.__newindex(t, template_name, template) + validate { + name = {template_name, 's'}; + default_config = {template.default_config, 't'}; + on_new_config = {template.on_new_config, 'f', true}; + on_attach = {template.on_attach, 'f', true}; + commands = {template.commands, 't', true}; + } + if template.commands then + for k, v in pairs(template.commands) do + validate { + ['command.name'] = {k, 's'}; + ['command.fn'] = {v[1], 'f'}; + } 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 {} + local M = {} + + local default_config = tbl_extend("keep", template.default_config, { + log_level = lsp.protocol.MessageType.Warning; + settings = {}; + callbacks = {}; + }) + + -- Force this part. + default_config.name = template_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 + lsp.builtin_callbacks[method](err, method, params, client_id) + end 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 - -- Uncomment this to debug. - -- print(string.format("config[%q] = %s", item.section, inspect(value))) - table.insert(result, value) + 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 + table.insert(result, value) + end + end + return result end - return result end -end --- A function to set up SKELETON easier. --- --- Additionally, it sets up the following commands: --- - SKELETON_SPOOKY_COMMAND: This does something SPOOKY. --- - SKELETON_OTHER_COMMAND: This does some OTHER thing. --- --- {config} is the same as |vim.lsp.add_filetype_config()|, but with some --- additions and changes: --- --- {name} --- Defaults to "SKELETON" --- --- {cmd} --- Defaults to {"SKELETON"} --- --- {filetype} --- Defaults to {"SKELETON"} --- --- {log_level} --- controls the level of logs to show from build processes and other --- window/logMessage events. By default it is set to --- vim.lsp.protocol.MessageType.Warning instead of --- vim.lsp.protocol.MessageType.Log. --- --- {settings} --- This is a table, and the keys are case sensitive. --- Example: `settings = { }` -function M.setup(config) - config = vim.tbl_extend("keep", config, default_config) + 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}; + } + config = tbl_extend("keep", config, default_config) - util.tbl_deep_extend(config.settings, default_config.settings) + 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'common_lsp'[%q].manager.try_add()" + , trigger + , config.name + )) - config.capabilities = config.capabilities or lsp.protocol.make_client_capabilities() - util.tbl_deep_extend(config.capabilities, { - workspace = { - configuration = true; - } - }) + local get_root_dir = config.root_dir - setup_callbacks(config) + -- 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 manager = util.server_per_root_dir_manager(function(_root_dir) + local new_config = vim.tbl_extend("keep", {}, config) + -- Deepcopy anything that is >1 level nested. + new_config.settings = vim.deepcopy(new_config.settings) + util.tbl_deep_extend(new_config.settings, default_config.settings) - config.on_attach = util.add_hook_after(config.on_attach, function(client, bufnr) - if bufnr == api.nvim_get_current_buf() then - M._setup_buffer() - else - api.nvim_command(string.format( - "autocmd BufEnter <buffer=%d> ++once lua require'common_lsp/%s'._setup_buffer()", - M.name, - bufnr)) + new_config.capabilities = new_config.capabilities or lsp.protocol.make_client_capabilities() + util.tbl_deep_extend(new_config.capabilities, { + workspace = { + configuration = true; + } + }) + + add_callbacks(new_config) + if template.on_new_config then + pcall(template.on_new_config, new_config) + end + if config.on_new_config then + pcall(config.on_new_config, new_config) + 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'common_lsp'[%q]._setup_buffer(%d)" + , template_name + , bufnr + , client.id + )) + end + end) + return new_config + end) + + function manager.try_add() + local root_dir = get_root_dir(api.nvim_buf_get_name(0), api.nvim_get_current_buf()) + print(api.nvim_get_current_buf(), root_dir) + local id = manager.add(root_dir) + lsp.buf_attach_client(0, id) end - end) - lsp.add_filetype_config(config) -end + M.manager = manager + end --- Declare any commands here. You can use additional modifiers like "-range" --- which will be added as command options. All of these commands are buffer --- level by default. -M.commands = { - SKELETON_FORMAT = { - function() - M.buf_SPOOKY_FUNCTION(0) - end; - "-range"; - }; - SKELETON_SPOOKY_COMMAND = { - function() - local bufnr = util.validate_bufnr(0) - print("SPOOKY COMMAND STUFF!", bufnr) - 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 template.commands then + -- Create the module commands + util.create_module_commands(template_name, M.commands) + end + end -function M._setup_buffer() - -- Do other setup here if you want. + M.commands = template.commands + M.name = template_name + M.template_config = template - -- Create the module commands - util.create_module_commands(M.name, M.commands) -end + rawset(t, template_name, M) -function M.buf_SPOOKY_FUNCTION(bufnr) - bufnr = util.validate_bufnr(bufnr) - print("SPOOKY FUNCTION STUFF!", bufnr) + return M end -return M +return setmetatable({}, skeleton) -- vim:et ts=2 sw=2 - diff --git a/lua/common_lsp/texlab.lua b/lua/common_lsp/texlab.lua index edf05c38..d0339e1f 100644 --- a/lua/common_lsp/texlab.lua +++ b/lua/common_lsp/texlab.lua @@ -1,126 +1,8 @@ +local skeleton = require 'common_lsp/skeleton' local util = require 'common_lsp/util' -local api, lsp = vim.api, vim.lsp +local lsp = vim.lsp -local M = {} - -M.name = "texlab" - --- TODO support more of https://github.com/microsoft/vscode-languageserver-node/blob/master/protocol/src/protocol.progress.proposed.md - -local default_config -default_config = { - name = M.name; - cmd = {"texlab"}; - filetype = {"tex", "bib"}; - log_level = lsp.protocol.MessageType.Warning; - settings = { - latex = { - build = { - args = {"-pdf", "-interaction=nonstopmode", "-synctex=1"}; - executable = "latexmk"; - onSave = false; - } - } - } -} - -local function setup_callbacks(config) - config.callbacks = config.callbacks or {} - - config.callbacks["window/logMessage"] = function(err, method, params, client_id) - if params and params.type <= config.log_level then - lsp.builtin_callbacks[method](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 - table.insert(result, value) - end - end - return result - end -end - --- A function to set up texlab easier. --- --- Additionally, it sets up the following commands: --- - TexlabBuild: builds the current buffer. --- --- {config} is the same as |vim.lsp.add_filetype_config()|, but with some --- additions and changes: --- --- {name} --- Defaults to "texlab" --- --- {cmd} --- Defaults to {"texlab"} --- --- {filetype} --- Defaults to {"tex", "bib"} --- --- {log_level} --- controls the level of logs to show from build processes and other --- window/logMessage events. By default it is set to --- vim.lsp.protocol.MessageType.Warning instead of --- vim.lsp.protocol.MessageType.Log. --- --- {settings} --- The settings specified here https://texlab.netlify.com/docs/reference/configuration. --- This is a table, and the keys are case sensitive. --- Example: `settings = { latex = { build = { onSave = true; } } }` -function M.setup(config) - config = vim.tbl_extend("keep", config, default_config) - - util.tbl_deep_extend(config.settings, default_config.settings) - - config.capabilities = config.capabilities or vim.lsp.protocol.make_client_capabilities() - util.tbl_deep_extend(config.capabilities, { - workspace = { - configuration = true; - } - }) - - setup_callbacks(config) - - config.on_attach = util.add_hook_after(config.on_attach, function(client, bufnr) - if bufnr == api.nvim_get_current_buf() then - M._setup_buffer() - else - api.nvim_command(string.format( - "autocmd BufEnter <buffer=%d> ++once lua require'common_lsp/%s'._setup_buffer()", - M.name, - bufnr)) - end - end) - - lsp.add_filetype_config(config) -end - --- Declare any commands here. You can use additional modifiers like "-range" --- which will be added as command options. All of these commands are buffer --- level by default. -M.commands = { - TexlabBuild = { - function() - M.buf_build(0) - end; - }; - -- TexlabCancelAllBuilds = { - -- }; -} - -function M._setup_buffer() - -- Create the module commands - util.create_module_commands(M.name, M.commands) -end +local cwd = vim.loop.cwd() local texlab_build_status = vim.tbl_add_reverse_lookup { Success = 0; @@ -129,7 +11,7 @@ local texlab_build_status = vim.tbl_add_reverse_lookup { Cancelled = 3; } -function M.buf_build(bufnr) +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, @@ -142,7 +24,7 @@ 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? -function M.build_cancel_all(bufnr) +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) @@ -151,5 +33,45 @@ function M.build_cancel_all(bufnr) end) end -return M +skeleton.texlab = { + default_config = { + cmd = {"texlab"}; + filetypes = {"tex", "bib"}; + root_dir = function() return cwd end; + log_level = lsp.protocol.MessageType.Warning; + settings = { + latex = { + build = { + args = {"-pdf", "-interaction=nonstopmode", "-synctex=1"}; + executable = "latexmk"; + onSave = false; + }; + }; + }; + }; + commands = { + TexlabBuild = { + function() + buf_build(0) + end; + description = "Build the current buffer"; + }; + -- TexlabCancelAllBuilds = { + -- }; + }; + -- on_new_config = function(new_config) end; + -- on_attach = function(client, bufnr) end; + docs = { + description = [[ +https://texlab.netlify.com/ + +A completion engine built from scratch for (la)tex. +]]; + default_config = { + root_dir = "vim's starting directory"; + }; + }; +} + +skeleton.texlab.buf_build = buf_build -- vim:et ts=2 sw=2 diff --git a/lua/common_lsp/util.lua b/lua/common_lsp/util.lua index 9aed25fe..6cc6db0f 100644 --- a/lua/common_lsp/util.lua +++ b/lua/common_lsp/util.lua @@ -84,7 +84,7 @@ function M.create_module_commands(module_name, commands) table.insert(parts, command_name) -- The command definition. table.insert(parts, - string.format("lua require'common_lsp/%s'.commands[%q][1]()", module_name, command_name)) + string.format("lua require'common_lsp'[%q].commands[%q][1]()", module_name, command_name)) api.nvim_command(table.concat(parts, " ")) end end @@ -123,6 +123,7 @@ M.path = (function() 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 "/" @@ -142,6 +143,7 @@ M.path = (function() -- 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 @@ -156,6 +158,7 @@ M.path = (function() 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 diff --git a/scripts/docgen.lua b/scripts/docgen.lua new file mode 100644 index 00000000..6cd2b1cb --- /dev/null +++ b/scripts/docgen.lua @@ -0,0 +1,106 @@ +local skeleton = require 'common_lsp/skeleton' +local inspect = vim.inspect + +local function filter(...) + local lines = {} + for i = 1, select("#", ...) do + local v = select(i, ...) + if v then + table.insert(lines, v) + end + end + return lines +end + +local function nilifempty(s) + if #s == 0 then return end + return s +end + +local function dedent(s) + local lines = vim.split(s, '\n', true) + if #lines == 0 then + return "" + end + local indent = #lines[1]:match("^%s*") + for i = 1, #lines do + lines[i] = lines[i]:sub(indent) + end + return table.concat(lines, '\n') +end + +local function indent(n, s) + if n <= 0 then return s end + local lines = vim.split(s, '\n', true) + for i, line in ipairs(lines) do + lines[i] = string.rep(" ", n)..line + end + return table.concat(lines, '\n') +end + +local writer = io.popen("cat README_preamble.md - > README.md common-lsp-docs.md", "w") + +for k, v in pairs(skeleton) do + local tconf = v.template_config + + local params = {} + params.template_name = k + if tconf.commands then + local lines = {"Commands:"} + local cnames = vim.tbl_keys(tconf.commands) + table.sort(cnames) + for _, cname in ipairs(cnames) do + local def = tconf.commands[cname] + if def.description then + table.insert(lines, string.format("- %s: %s", cname, def.description)) + else + table.insert(lines, string.format("- %s", cname)) + end + lines[#lines] = indent(0, lines[#lines]) + end + params.commands = indent(0, table.concat(lines, '\n')) + end + if tconf.default_config then + local lines = {} + lines = {"Default Values:"} + local keys = vim.tbl_keys(tconf.default_config) + table.sort(keys) + for _, dk in ipairs(keys) do + local dv = tconf.default_config[dk] + local description = tconf.docs and tconf.docs.default_config and tconf.docs.default_config[dk] + table.insert(lines, indent(2, string.format("%s = %s", dk, description or inspect(dv)))) + end + params.default_config = indent(0, table.concat(lines, '\n')) + end + do + local body_lines = filter( + params.commands + , params.default_config + ) + params.body = indent(2, table.concat(body_lines, '\n\n')) + end + params.preamble = "" + if tconf.docs then + params.preamble = table.concat(filter( + nilifempty(tconf.docs.description), + "" + ), '\n\n') + end + + local section = ([[ +## {{template_name}} + +{{preamble}} +common_lsp.{{template_name}}.setup({config}) +common_lsp#setup("{{template_name}}", {config}) + +``` +{{body}} +``` +]]):gsub("{{(%S+)}}", params) + + writer:write(section) +end + +writer:close() +-- vim:et ts=2 sw=2 |
