diff options
| author | Ashkan Kiani <ashkan.k.kiani@gmail.com> | 2019-11-13 18:08:33 -0800 |
|---|---|---|
| committer | Ashkan Kiani <ashkan.k.kiani@gmail.com> | 2019-11-13 18:08:33 -0800 |
| commit | 997366c95e4ad81d1b79543d1cb787f446c021a5 (patch) | |
| tree | 309b3dddb57c5d60f22dbb0bb28928e088021ce4 /lua | |
| parent | Remove print statement. (diff) | |
| download | nvim-lspconfig-997366c95e4ad81d1b79543d1cb787f446c021a5.tar nvim-lspconfig-997366c95e4ad81d1b79543d1cb787f446c021a5.tar.gz nvim-lspconfig-997366c95e4ad81d1b79543d1cb787f446c021a5.tar.bz2 nvim-lspconfig-997366c95e4ad81d1b79543d1cb787f446c021a5.tar.lz nvim-lspconfig-997366c95e4ad81d1b79543d1cb787f446c021a5.tar.xz nvim-lspconfig-997366c95e4ad81d1b79543d1cb787f446c021a5.tar.zst nvim-lspconfig-997366c95e4ad81d1b79543d1cb787f446c021a5.zip | |
Add gopls, path utils, and root_pattern pattern.
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/common_lsp.lua | 3 | ||||
| -rw-r--r-- | lua/common_lsp/gopls.lua | 176 | ||||
| -rw-r--r-- | lua/common_lsp/util.lua | 167 |
3 files changed, 345 insertions, 1 deletions
diff --git a/lua/common_lsp.lua b/lua/common_lsp.lua index d412bb15..2ff03e59 100644 --- a/lua/common_lsp.lua +++ b/lua/common_lsp.lua @@ -1,7 +1,8 @@ local M = { texlab = require 'common_lsp/texlab'; + gopls = require 'common_lsp/gopls'; + util = require 'common_lsp/util'; } - return M -- vim:et ts=2 sw=2 diff --git a/lua/common_lsp/gopls.lua b/lua/common_lsp/gopls.lua new file mode 100644 index 00000000..c43f4c38 --- /dev/null +++ b/lua/common_lsp/gopls.lua @@ -0,0 +1,176 @@ +local util = require 'common_lsp/util' +local api, validate, lsp = vim.api, vim.validate, 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}; + } + + if config.filetype then + local filetypes + if type(config.filetype) == 'string' then + filetypes = { config.filetype } + else + filetypes = config.filetype + end + api.nvim_command(string.format( + "autocmd FileType %s lua require'common_lsp/%s'.manager.try_add()" + , table.concat(filetypes, ',') + , M.name + )) + else + api.nvim_command(string.format( + "autocmd BufReadPost * lua require'common_lsp/%s'.manager.try_add()" + , M.name + )) + end + + local get_root_dir = 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 + +-- 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; + }; +} + +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/util.lua b/lua/common_lsp/util.lua index 6da2db1c..b821854a 100644 --- a/lua/common_lsp/util.lua +++ b/lua/common_lsp/util.lua @@ -1,5 +1,7 @@ local validate = vim.validate local api = vim.api +local lsp = vim.lsp +local uv = vim.loop local M = {} @@ -87,6 +89,171 @@ function M.create_module_commands(module_name, commands) end end +-- Some path utilities +M.path = (function() + local function exists(filename) + local stat = uv.fs_stat(filename) + return stat and stat.type or false + end + + local function is_dir(filename) + return exists(filename) == 'directory' + end + + local function is_file(filename) + return exists(filename) == 'file' + end + + local is_windows = uv.os_uname().sysname == "Windows" + local path_sep = is_windows and "\\" or "/" + + local is_fs_root + if is_windows then + is_fs_root = function(path) + return path:match("^%a:\\\\$") + end + else + is_fs_root = function(path) + return path == "/" + end + end + + local dirname + do + local strip_dir_pat = path_sep.."([^"..path_sep.."]+)$" + local strip_sep_pat = path_sep.."$" + dirname = function(path) + local result = path:gsub(strip_sep_pat, ""):gsub(strip_dir_pat, "") + if #result == 0 then + return "/" + end + return result + end + end + + local function path_join(...) + return table.concat(vim.tbl_flatten {...}, path_sep) + end + + -- Traverse the path calling cb along the way. + local function traverse_parents(path, cb) + path = uv.fs_realpath(path) + local dir = path + -- Just in case our algo is buggy, don't infinite loop. + for _ = 1, 100 do + dir = dirname(dir) + -- If we can't ascend further, then stop looking. + if cb(dir, path) then + return dir, path + end + if is_fs_root(dir) then + break + end + end + end + + -- Iterate the path until we find the rootdir. + local function iterate_parents(path) + path = uv.fs_realpath(path) + local function it(s, v) + if is_fs_root(v) then return end + return dirname(v), path + end + return it, path, path + end + + return { + is_dir = is_dir; + is_file = is_file; + exists = exists; + sep = path_sep; + dirname = dirname; + join = path_join; + traverse_parents = traverse_parents; + iterate_parents = iterate_parents; + } +end)() + + +-- Returns a function(root_dir), which, when called with a root_dir it hasn't +-- seen before, will call make_config(root_dir) and start a new client. +function M.server_per_root_dir_manager(make_config) + local clients = {} + local manager = {} + + function manager.add(root_dir) + if not root_dir then return end + + -- Check if we have a client alredy or start and store it. + local client_id = clients[root_dir] + if not client_id then + local new_config = make_config(root_dir) + new_config.root_dir = root_dir + new_config.on_exit = M.add_hook_before(new_config.on_exit, function() + clients[root_dir] = nil + end) + client_id = lsp.start_client(new_config) + clients[root_dir] = client_id + end + return client_id + end + + function manager.clients() + local res = {} + for _, id in pairs(clients) do + local client = lsp.get_client_by_id(id) + if client then + table.insert(res, client) + end + end + return res + end + + return manager +end + +function M.search_ancestors(startpath, fn) + validate { fn = {fn, 'f'} } + if fn(startpath) then return startpath end + for path in M.path.iterate_parents(startpath) do + if fn(path) then return path end + end +end + +function M.root_pattern(...) + local patterns = vim.tbl_flatten {...} + local function matcher(path) + for _, pattern in ipairs(patterns) do + if M.path.exists(M.path.join(path, pattern)) then + return path + end + end + end + return function(startpath) + return M.search_ancestors(startpath, matcher) + end +end +function M.find_git_ancestor(startpath) + return M.search_ancestors(startpath, function(path) + if M.path.is_dir(M.path.join(path, ".git")) then + return path + end + end) +end +function M.find_node_modules_ancestor(startpath) + return M.search_ancestors(startpath, function(path) + if M.path.is_dir(M.path.join(path, "node_modules")) then + return path + end + end) +end +function M.find_package_json_ancestor(startpath) + return M.search_ancestors(startpath, function(path) + if M.path.is_file(M.path.join(path, "package.json")) then + return path + end + end) +end return M -- vim:et ts=2 sw=2 |
