aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAshkan Kiani <ashkan.k.kiani@gmail.com>2019-11-14 00:04:56 -0800
committerAshkan Kiani <ashkan.k.kiani@gmail.com>2019-11-14 00:04:56 -0800
commit7fbe851feb26529434b64d8fb3a30f5bb01f180c (patch)
treeb8d81bff4b77b93eeadffa2afa57b59f67f06a7f
parentAdd note about gopls (diff)
downloadnvim-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.md196
-rw-r--r--README_preamble.md167
-rw-r--r--lua/common_lsp.lua13
-rw-r--r--lua/common_lsp/gopls.lua192
-rw-r--r--lua/common_lsp/skeleton.lua241
-rw-r--r--lua/common_lsp/texlab.lua170
-rw-r--r--lua/common_lsp/util.lua5
-rw-r--r--scripts/docgen.lua106
8 files changed, 593 insertions, 497 deletions
diff --git a/README.md b/README.md
index 8fac2e53..40b8b4c8 100644
--- a/README.md
+++ b/README.md
@@ -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