aboutsummaryrefslogtreecommitdiffstats
path: root/lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua')
-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
5 files changed, 215 insertions, 406 deletions
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