diff options
| author | kylo252 <[email protected]> | 2021-10-03 16:13:46 +0200 | 
|---|---|---|
| committer | GitHub <[email protected]> | 2021-10-03 16:13:46 +0200 | 
| commit | d01ba08eaec1640ac2d038893525b3ba0af25813 (patch) | |
| tree | 5edf2f5a12cedacb32f0c5d45ec2d999dacb99cd /lua/lsp | |
| parent | 3e1cd1ec235404ae96ed2d0756729cf44ae48f3e (diff) | |
refactor: auto-generate language configuration (#1584)
Refactor the monolithic `lvim.lang` design into a more modular approach.
IMPORTANT: run `:LvimUpdate` in order to generate the new ftplugin template files.
Diffstat (limited to 'lua/lsp')
| -rw-r--r-- | lua/lsp/config.lua | 27 | ||||
| -rw-r--r-- | lua/lsp/init.lua | 102 | ||||
| -rw-r--r-- | lua/lsp/kind.lua | 31 | ||||
| -rw-r--r-- | lua/lsp/manager.lua | 82 | ||||
| -rw-r--r-- | lua/lsp/null-ls/formatters.lua | 30 | ||||
| -rw-r--r-- | lua/lsp/null-ls/init.lua | 54 | ||||
| -rw-r--r-- | lua/lsp/null-ls/linters.lua | 30 | ||||
| -rw-r--r-- | lua/lsp/null-ls/services.lua | 15 | ||||
| -rw-r--r-- | lua/lsp/providers/jsonls.lua | 30 | ||||
| -rw-r--r-- | lua/lsp/providers/sumneko_lua.lua | 19 | ||||
| -rw-r--r-- | lua/lsp/providers/vuels.lua | 26 | ||||
| -rw-r--r-- | lua/lsp/templates.lua | 98 | ||||
| -rw-r--r-- | lua/lsp/utils.lua | 57 | 
13 files changed, 405 insertions, 196 deletions
diff --git a/lua/lsp/config.lua b/lua/lsp/config.lua new file mode 100644 index 00000000..146301c9 --- /dev/null +++ b/lua/lsp/config.lua @@ -0,0 +1,27 @@ +return { +  templates_dir = join_paths(get_runtime_dir(), "site", "after", "ftplugin"), +  diagnostics = { +    signs = { +      active = true, +      values = { +        { name = "LspDiagnosticsSignError", text = "ī" }, +        { name = "LspDiagnosticsSignWarning", text = "īŠ" }, +        { name = "LspDiagnosticsSignHint", text = "ī ĩ" }, +        { name = "LspDiagnosticsSignInformation", text = "ī" }, +      }, +    }, +    virtual_text = { +      prefix = "ī", +      spacing = 0, +    }, +    update_in_insert = false, +    underline = true, +    severity_sort = true, +  }, +  override = {}, +  document_highlight = true, +  popup_border = "single", +  on_attach_callback = nil, +  on_init_callback = nil, +  automatic_servers_installation = true, +} diff --git a/lua/lsp/init.lua b/lua/lsp/init.lua index 386be075..7f20a39d 100644 --- a/lua/lsp/init.lua +++ b/lua/lsp/init.lua @@ -1,5 +1,6 @@  local M = {}  local Log = require "core.log" +local utils = require "utils"  local function lsp_highlight_document(client)    if lvim.lsp.document_highlight == false then @@ -61,48 +62,12 @@ function M.common_capabilities()    return capabilities  end -function M.get_ls_capabilities(client_id) -  local client -  if not client_id then -    local buf_clients = vim.lsp.buf_get_clients() -    for _, buf_client in ipairs(buf_clients) do -      if buf_client.name ~= "null-ls" then -        client_id = buf_client.id -        break -      end -    end -  end -  if not client_id then -    error "Unable to determine client_id" -  end - -  client = vim.lsp.get_client_by_id(tonumber(client_id)) - -  local enabled_caps = {} - -  for k, v in pairs(client.resolved_capabilities) do -    if v == true then -      table.insert(enabled_caps, k) -    end -  end - -  return enabled_caps -end -  function M.common_on_init(client, bufnr)    if lvim.lsp.on_init_callback then      lvim.lsp.on_init_callback(client, bufnr)      Log:debug "Called lsp.on_init_callback"      return    end - -  local formatters = lvim.lang[vim.bo.filetype].formatters -  if not vim.tbl_isempty(formatters) and formatters[1]["exe"] ~= nil and formatters[1].exe ~= "" then -    client.resolved_capabilities.document_formatting = false -    Log:debug( -      string.format("Overriding language server [%s] with format provider [%s]", client.name, formatters[1].exe) -    ) -  end  end  function M.common_on_attach(client, bufnr) @@ -112,63 +77,46 @@ function M.common_on_attach(client, bufnr)    end    lsp_highlight_document(client)    add_lsp_buffer_keybindings(bufnr) -  require("lsp.null-ls").setup(vim.bo.filetype)  end -function M.setup(lang) -  local lsp_utils = require "lsp.utils" -  local lsp = lvim.lang[lang].lsp -  if (lsp.active ~= nil and not lsp.active) or lsp_utils.is_client_active(lsp.provider) then -    return -  end - -  local overrides = lvim.lsp.override -  if type(overrides) == "table" then -    if vim.tbl_contains(overrides, lang) then -      return -    end +local function bootstrap_nlsp(opts) +  opts = opts or {} +  local lsp_settings_status_ok, lsp_settings = pcall(require, "nlspsettings") +  if lsp_settings_status_ok then +    lsp_settings.setup(opts)    end +end -  if lsp.provider ~= nil and lsp.provider ~= "" then -    local lspconfig = require "lspconfig" +function M.get_common_opts() +  return { +    on_attach = M.common_on_attach, +    on_init = M.common_on_init, +    capabilities = M.common_capabilities(), +  } +end -    if not lsp.setup.on_attach then -      lsp.setup.on_attach = M.common_on_attach -    end -    if not lsp.setup.on_init then -      lsp.setup.on_init = M.common_on_init -    end -    if not lsp.setup.capabilities then -      lsp.setup.capabilities = M.common_capabilities() -    end +function M.setup() +  Log:debug "Setting up LSP support" -    lspconfig[lsp.provider].setup(lsp.setup) +  local lsp_status_ok, _ = pcall(require, "lspconfig") +  if not lsp_status_ok then +    return    end -end - -function M.global_setup() -  vim.lsp.protocol.CompletionItemKind = lvim.lsp.completion.item_kind    for _, sign in ipairs(lvim.lsp.diagnostics.signs.values) do      vim.fn.sign_define(sign.name, { texthl = sign.name, text = sign.text, numhl = sign.name })    end -    require("lsp.handlers").setup() -  local null_status_ok, null_ls = pcall(require, "null-ls") -  if null_status_ok then -    null_ls.config() -    require("lspconfig")["null-ls"].setup(lvim.lsp.null_ls.setup) +  if not utils.is_directory(lvim.lsp.templates_dir) then +    require("lsp.templates").generate_templates()    end -  local utils = require "utils" +  bootstrap_nlsp { config_home = utils.join_paths(get_config_dir(), "lsp-settings") } -  local lsp_settings_status_ok, lsp_settings = pcall(require, "nlspsettings") -  if lsp_settings_status_ok then -    lsp_settings.setup { -      config_home = utils.join_paths(get_config_dir(), "lsp-settings"), -    } -  end +  require("lsp.null-ls").setup() + +  require("utils").toggle_autoformat()  end  return M diff --git a/lua/lsp/kind.lua b/lua/lsp/kind.lua deleted file mode 100644 index b78fd318..00000000 --- a/lua/lsp/kind.lua +++ /dev/null @@ -1,31 +0,0 @@ -local M = {} - -M.icons = { -  Class = "ī  ", -  Color = "îĢ ", -  Constant = "ī˛ ", -  Constructor = "īĨ ", -  Enum = "īŠ", -  EnumMember = "ī
 ", -  Event = "ī§ ", -  Field = "î ", -  File = "ī", -  Folder = "ī ", -  Function = "ī ", -  Interface = "ī°Ž ", -  Keyword = "ī  ", -  Method = "î ", -  Module = "ī¨ ", -  Operator = "ī", -  Property = "î¤ ", -  Reference = "ī ", -  Snippet = "ī ", -  Struct = "ī ", -  Text = "īž ", -  TypeParameter = "ī ", -  Unit = "īĨŦ", -  Value = "īĸ ", -  Variable = "īĻ ", -} - -return M diff --git a/lua/lsp/manager.lua b/lua/lsp/manager.lua new file mode 100644 index 00000000..24d462ad --- /dev/null +++ b/lua/lsp/manager.lua @@ -0,0 +1,82 @@ +local M = {} + +local Log = require "core.log" +local lsp_utils = require "lsp.utils" + +function M.init_defaults(languages) +  for _, entry in ipairs(languages) do +    if not lvim.lang[entry] then +      lvim.lang[entry] = { +        formatters = {}, +        linters = {}, +      } +    end +  end +end + +local function is_overridden(server) +  local overrides = lvim.lsp.override +  if type(overrides) == "table" then +    if vim.tbl_contains(overrides, server) then +      return +    end +  end +end + +function M.setup_server(server_name) +  vim.validate { +    name = { server_name, "string" }, +  } + +  if lsp_utils.is_client_active(server_name) or is_overridden(server_name) then +    return +  end + +  local lsp_installer_servers = require "nvim-lsp-installer.servers" +  local server_available, requested_server = lsp_installer_servers.get_server(server_name) +  if server_available then +    if not requested_server:is_installed() then +      Log:debug(string.format("[%s] is not installed", server_name)) +      if lvim.lsp.automatic_servers_installation then +        Log:debug(string.format("Installing [%s]", server_name)) +        requested_server:install() +      else +        return +      end +    end +  end + +  local default_config = { +    on_attach = require("lsp").common_on_attach, +    on_init = require("lsp").common_on_init, +    capabilities = require("lsp").common_capabilities(), +  } + +  local status_ok, custom_config = pcall(require, "lsp/providers/" .. requested_server.name) +  if status_ok then +    local new_config = vim.tbl_deep_extend("force", default_config, custom_config) +    Log:debug("Using custom configuration for requested server: " .. requested_server.name) +    requested_server:setup(new_config) +  else +    Log:debug("Using the default configuration for requested server: " .. requested_server.name) +    requested_server:setup(default_config) +  end +end + +function M.setup(servers) +  local status_ok, _ = pcall(require, "nvim-lsp-installer") +  if not status_ok then +    return +  end + +  --- allow using a single value +  if type(servers) == "string" then +    servers = { servers } +  end + +  for _, server in ipairs(servers) do +    M.setup_server(server) +  end +end + +return M diff --git a/lua/lsp/null-ls/formatters.lua b/lua/lsp/null-ls/formatters.lua index 2c2a4f06..8199aca0 100644 --- a/lua/lsp/null-ls/formatters.lua +++ b/lua/lsp/null-ls/formatters.lua @@ -1,29 +1,14 @@  local M = {} -local formatters_by_ft = {}  local null_ls = require "null-ls"  local services = require "lsp.null-ls.services"  local Log = require "core.log" -local function list_names(formatters, options) -  options = options or {} -  local filter = options.filter or "supported" - -  return vim.tbl_keys(formatters[filter]) -end -  function M.list_supported_names(filetype) -  if not formatters_by_ft[filetype] then -    return {} -  end -  return list_names(formatters_by_ft[filetype], { filter = "supported" }) -end - -function M.list_unsupported_names(filetype) -  if not formatters_by_ft[filetype] then -    return {} -  end -  return list_names(formatters_by_ft[filetype], { filter = "unsupported" }) +  local null_ls_methods = require "null-ls.methods" +  local formatter_method = null_ls_methods.internal["FORMATTING"] +  local registered_providers = services.list_registered_providers_names(filetype) +  return registered_providers[formatter_method] or {}  end  function M.list_available(filetype) @@ -62,12 +47,13 @@ function M.list_configured(formatter_configs)    return { supported = formatters, unsupported = errors }  end -function M.setup(filetype, options) -  if not lvim.lang[filetype] or (formatters_by_ft[filetype] and not options.force_reload) then +function M.setup(formatter_configs, filetype) +  if vim.tbl_isempty(formatter_configs) then      return    end -  formatters_by_ft[filetype] = M.list_configured(lvim.lang[filetype].formatters) +  local formatters_by_ft = {} +  formatters_by_ft[filetype] = M.list_configured(formatter_configs)    null_ls.register { sources = formatters_by_ft[filetype].supported }  end diff --git a/lua/lsp/null-ls/init.lua b/lua/lsp/null-ls/init.lua index ce4c07d9..0540fb48 100644 --- a/lua/lsp/null-ls/init.lua +++ b/lua/lsp/null-ls/init.lua @@ -1,44 +1,26 @@  local M = {} -function M.list_supported_provider_names(filetype) -  local names = {} - -  local formatters = require "lsp.null-ls.formatters" -  local linters = require "lsp.null-ls.linters" - -  vim.list_extend(names, formatters.list_supported_names(filetype)) -  vim.list_extend(names, linters.list_supported_names(filetype)) - -  return names -end - -function M.list_unsupported_provider_names(filetype) -  local names = {} - -  local formatters = require "lsp.null-ls.formatters" -  local linters = require "lsp.null-ls.linters" - -  vim.list_extend(names, formatters.list_unsupported_names(filetype)) -  vim.list_extend(names, linters.list_unsupported_names(filetype)) - -  return names -end - --- TODO: for linters and formatters with spaces and '-' replace with '_' -function M.setup(filetype, options) -  options = options or {} - -  local ok, _ = pcall(require, "null-ls") -  if not ok then -    require("core.log"):error "Missing null-ls dependency" +local Log = require "core.log" +local formatters = require "lsp.null-ls.formatters" +local linters = require "lsp.null-ls.linters" + +function M:setup() +  local status_ok, null_ls = pcall(require, "null-ls") +  if not status_ok then +    Log:error "Missing null-ls dependency"      return    end -  local formatters = require "lsp.null-ls.formatters" -  local linters = require "lsp.null-ls.linters" - -  formatters.setup(filetype, options) -  linters.setup(filetype, options) +  null_ls.config() +  require("lspconfig")["null-ls"].setup {} +  for _, filetype in pairs(lvim.lang) do +    if filetype.formatters then +      formatters.setup(filetype.formatters, filetype) +    end +    if filetype.linters then +      linters.setup(filetype.linters, filetype) +    end +  end  end  return M diff --git a/lua/lsp/null-ls/linters.lua b/lua/lsp/null-ls/linters.lua index d88a8b83..ea45fa87 100644 --- a/lua/lsp/null-ls/linters.lua +++ b/lua/lsp/null-ls/linters.lua @@ -1,29 +1,14 @@  local M = {} -local linters_by_ft = {}  local null_ls = require "null-ls"  local services = require "lsp.null-ls.services"  local Log = require "core.log" -local function list_names(linters, options) -  options = options or {} -  local filter = options.filter or "supported" - -  return vim.tbl_keys(linters[filter]) -end -  function M.list_supported_names(filetype) -  if not linters_by_ft[filetype] then -    return {} -  end -  return list_names(linters_by_ft[filetype], { filter = "supported" }) -end - -function M.list_unsupported_names(filetype) -  if not linters_by_ft[filetype] then -    return {} -  end -  return list_names(linters_by_ft[filetype], { filter = "unsupported" }) +  local null_ls_methods = require "null-ls.methods" +  local linter_method = null_ls_methods.internal["DIAGNOSTICS"] +  local registered_providers = services.list_registered_providers_names(filetype) +  return registered_providers[linter_method] or {}  end  function M.list_available(filetype) @@ -62,12 +47,13 @@ function M.list_configured(linter_configs)    return { supported = linters, unsupported = errors }  end -function M.setup(filetype, options) -  if not lvim.lang[filetype] or (linters_by_ft[filetype] and not options.force_reload) then +function M.setup(linter_configs, filetype) +  if vim.tbl_isempty(linter_configs) then      return    end -  linters_by_ft[filetype] = M.list_configured(lvim.lang[filetype].linters) +  local linters_by_ft = {} +  linters_by_ft[filetype] = M.list_configured(linter_configs)    null_ls.register { sources = linters_by_ft[filetype].supported }  end diff --git a/lua/lsp/null-ls/services.lua b/lua/lsp/null-ls/services.lua index 1e76b40a..c62fc709 100644 --- a/lua/lsp/null-ls/services.lua +++ b/lua/lsp/null-ls/services.lua @@ -45,4 +45,19 @@ function M.find_command(command)    return nil  end +function M.list_registered_providers_names(filetype) +  local u = require "null-ls.utils" +  local c = require "null-ls.config" +  local registered = {} +  for method, source in pairs(c.get()._methods) do +    for name, filetypes in pairs(source) do +      if u.filetype_matches(filetypes, filetype) then +        registered[method] = registered[method] or {} +        table.insert(registered[method], name) +      end +    end +  end +  return registered +end +  return M diff --git a/lua/lsp/providers/jsonls.lua b/lua/lsp/providers/jsonls.lua new file mode 100644 index 00000000..55bc1ea2 --- /dev/null +++ b/lua/lsp/providers/jsonls.lua @@ -0,0 +1,30 @@ +local schemas = nil +local status_ok, jsonls_settings = pcall(require, "nlspsettings.jsonls") +if status_ok then +  schemas = jsonls_settings.get_default_schemas() +end + +local opts = { +  setup = { +    settings = { +      json = { +        schemas = schemas, +        --   = { +        --   { +        --     fileMatch = { "package.json" }, +        --     url = "https://json.schemastore.org/package.json", +        --   }, +        -- }, +      }, +    }, +    commands = { +      Format = { +        function() +          vim.lsp.buf.range_formatting({}, { 0, 0 }, { vim.fn.line "$", 0 }) +        end, +      }, +    }, +  }, +} + +return opts diff --git a/lua/lsp/providers/sumneko_lua.lua b/lua/lsp/providers/sumneko_lua.lua new file mode 100644 index 00000000..4fee1fd1 --- /dev/null +++ b/lua/lsp/providers/sumneko_lua.lua @@ -0,0 +1,19 @@ +local opts = { +  settings = { +    Lua = { +      diagnostics = { +        globals = { "vim", "lvim" }, +      }, +      workspace = { +        library = { +          [require("utils").join_paths(get_runtime_dir(), "lvim", "lua")] = true, +          [vim.fn.expand "$VIMRUNTIME/lua"] = true, +          [vim.fn.expand "$VIMRUNTIME/lua/vim/lsp"] = true, +        }, +        maxPreload = 100000, +        preloadFileSize = 10000, +      }, +    }, +  }, +} +return opts diff --git a/lua/lsp/providers/vuels.lua b/lua/lsp/providers/vuels.lua new file mode 100644 index 00000000..3f44275d --- /dev/null +++ b/lua/lsp/providers/vuels.lua @@ -0,0 +1,26 @@ +local opts = { +  setup = { +    root_dir = function(fname) +      local util = require "lspconfig/util" +      return util.root_pattern "package.json"(fname) or util.root_pattern "vue.config.js"(fname) or vim.fn.getcwd() +    end, +    init_options = { +      config = { +        vetur = { +          completion = { +            autoImport = true, +            tagCasing = "kebab", +            useScaffoldSnippets = true, +          }, +          useWorkspaceDependencies = true, +          validation = { +            script = true, +            style = true, +            template = true, +          }, +        }, +      }, +    }, +  }, +} +return opts diff --git a/lua/lsp/templates.lua b/lua/lsp/templates.lua new file mode 100644 index 00000000..6ded636d --- /dev/null +++ b/lua/lsp/templates.lua @@ -0,0 +1,98 @@ +local M = {} + +local Log = require "core.log" +local utils = require "utils" +local get_supported_filetypes = require("lsp.utils").get_supported_filetypes + +local ftplugin_dir = lvim.lsp.templates_dir + +local join_paths = _G.join_paths + +function M.remove_template_files() +  -- remove any outdated files +  for _, file in ipairs(vim.fn.glob(ftplugin_dir .. "/*.lua", 1, 1)) do +    vim.fn.delete(file) +  end +end + +---Checks if a server is ignored by default because of a conflict +---Only TSServer is enabled by default for the javascript-family +---@param server_name string +function M.is_ignored(server_name, filetypes) +  --TODO: this is easy to be made configurable once stable +  filetypes = filetypes or get_supported_filetypes(server_name) + +  if vim.tbl_contains(filetypes, "javascript") then +    if server_name == "tsserver" or server_name == "tailwindcss" then +      return false +    else +      return true +    end +  end + +  local blacklist = { +    "jedi_language_server", +    "pylsp", +    "sqlls", +    "sqls", +    "angularls", +    "ansiblels", +  } +  return vim.tbl_contains(blacklist, server_name) +end + +---Generates an ftplugin file based on the server_name in the selected directory +---@param server_name string name of a valid language server, e.g. pyright, gopls, tsserver, etc. +---@param dir string the full path to the desired directory +function M.generate_ftplugin(server_name, dir) +  -- we need to go through lspconfig to get the corresponding filetypes currently +  local filetypes = get_supported_filetypes(server_name) or {} +  if not filetypes then +    return +  end + +  if M.is_ignored(server_name, filetypes) then +    return +  end + +  -- print("got associated filetypes: " .. vim.inspect(filetypes)) + +  for _, filetype in ipairs(filetypes) do +    local filename = join_paths(dir, filetype .. ".lua") +    local setup_cmd = string.format([[require("lsp.manager").setup(%q)]], server_name) +    -- print("using setup_cmd: " .. setup_cmd) +    -- overwrite the file completely +    utils.write_file(filename, setup_cmd .. "\n", "a") +  end +end + +---Generates ftplugin files based on a list of server_names +---The files are generated to a runtimepath: "$LUNARVIM_RUNTIME_DIR/site/after/ftplugin/template.lua" +---@param servers_names table list of servers to be enabled. Will add all by default +function M.generate_templates(servers_names) +  servers_names = servers_names or {} + +  Log:debug "Templates installation in progress" + +  M.remove_template_files() + +  if vim.tbl_isempty(servers_names) then +    local available_servers = require("nvim-lsp-installer.servers").get_available_servers() + +    for _, server in pairs(available_servers) do +      table.insert(servers_names, server.name) +    end +  end + +  -- create the directory if it didn't exist +  if not utils.is_directory(lvim.lsp.templates_dir) then +    vim.fn.mkdir(ftplugin_dir, "p") +  end + +  for _, server in ipairs(servers_names) do +    M.generate_ftplugin(server, ftplugin_dir) +  end +  Log:debug "Templates installation is complete" +end + +return M diff --git a/lua/lsp/utils.lua b/lua/lsp/utils.lua index 17b9c3bc..1a5dd79d 100644 --- a/lua/lsp/utils.lua +++ b/lua/lsp/utils.lua @@ -10,19 +10,60 @@ function M.is_client_active(name)    return false  end --- FIXME: this should return a list instead -function M.get_active_client_by_ft(filetype) -  if not lvim.lang[filetype] or not lvim.lang[filetype].lsp then -    return nil -  end +function M.disable_formatting_capability(client) +  -- FIXME: figure out a reasonable way to do this +  client.resolved_capabilities.document_formatting = false +  require("core.log"):debug(string.format("Turning off formatting capability for language server [%s] ", client.name)) +end +function M.get_active_client_by_ft(filetype) +  local matches = {}    local clients = vim.lsp.get_active_clients()    for _, client in pairs(clients) do -    if client.name == lvim.lang[filetype].lsp.provider then -      return client +    local supported_filetypes = client.config.filetypes or {} +    if client.name ~= "null-ls" and vim.tbl_contains(supported_filetypes, filetype) then +      table.insert(matches, client) +    end +  end +  return matches +end + +function M.get_ls_capabilities(client_id) +  if not client_id then +    local buf_clients = vim.lsp.buf_get_clients() +    for _, buf_client in ipairs(buf_clients) do +      if buf_client.name ~= "null-ls" then +        client_id = buf_client.id +        break +      end +    end +  end +  if not client_id then +    error "Unable to determine client_id" +    return +  end + +  local client = vim.lsp.get_client_by_id(tonumber(client_id)) + +  local enabled_caps = {} +  for capability, status in pairs(client.resolved_capabilities) do +    if status == true then +      table.insert(enabled_caps, capability) +    end +  end + +  return enabled_caps +end + +function M.get_supported_filetypes(server_name) +  -- print("got filetypes query request for: " .. server_name) +  local configs = require "lspconfig/configs" +  pcall(require, ("lspconfig/" .. server_name)) +  for _, config in pairs(configs) do +    if config.name == server_name then +      return config.document_config.default_config.filetypes or {}      end    end -  return nil  end  return M  | 
