diff options
Diffstat (limited to 'lua/lsp')
| -rw-r--r-- | lua/lsp/config.lua | 45 | ||||
| -rw-r--r-- | lua/lsp/handlers.lua | 178 | ||||
| -rw-r--r-- | lua/lsp/init.lua | 177 | ||||
| -rw-r--r-- | lua/lsp/manager.lua | 86 | ||||
| -rw-r--r-- | lua/lsp/null-ls/formatters.lua | 52 | ||||
| -rw-r--r-- | lua/lsp/null-ls/init.lua | 60 | ||||
| -rw-r--r-- | lua/lsp/null-ls/linters.lua | 52 | ||||
| -rw-r--r-- | lua/lsp/null-ls/services.lua | 20 | ||||
| -rw-r--r-- | lua/lsp/peek.lua | 18 | ||||
| -rw-r--r-- | lua/lsp/providers/jsonls.lua | 197 | ||||
| -rw-r--r-- | lua/lsp/providers/sumneko_lua.lua | 19 | ||||
| -rw-r--r-- | lua/lsp/providers/vuels.lua | 26 | ||||
| -rw-r--r-- | lua/lsp/providers/yamlls.lua | 30 | ||||
| -rw-r--r-- | lua/lsp/templates.lua | 98 | ||||
| -rw-r--r-- | lua/lsp/utils.lua | 58 | 
15 files changed, 883 insertions, 233 deletions
diff --git a/lua/lsp/config.lua b/lua/lsp/config.lua new file mode 100644 index 00000000..f13d9659 --- /dev/null +++ b/lua/lsp/config.lua @@ -0,0 +1,45 @@ +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 = true, +    update_in_insert = false, +    underline = true, +    severity_sort = true, +  }, +  override = {}, +  document_highlight = true, +  code_lens_refresh = true, +  popup_border = "single", +  on_attach_callback = nil, +  on_init_callback = nil, +  automatic_servers_installation = true, +  buffer_mappings = { +    normal_mode = { +      ["K"] = { "<cmd>lua vim.lsp.buf.hover()<CR>", "Show hover" }, +      ["gd"] = { "<cmd>lua vim.lsp.buf.definition()<CR>", "Goto Definition" }, +      ["gD"] = { "<cmd>lua vim.lsp.buf.declaration()<CR>", "Goto declaration" }, +      ["gr"] = { "<cmd>lua vim.lsp.buf.references()<CR>", "Goto references" }, +      ["gI"] = { "<cmd>lua vim.lsp.buf.implementation()<CR>", "Goto Implementation" }, +      ["gs"] = { "<cmd>lua vim.lsp.buf.signature_help()<CR>", "show signature help" }, +      ["gp"] = { "<cmd>lua require'lsp.peek'.Peek('definition')<CR>", "Peek definition" }, +      ["gl"] = { +        "<cmd>lua require'lsp.handlers'.show_line_diagnostics()<CR>", +        "Show line diagnostics", +      }, +    }, +    insert_mode = {}, +    visual_mode = {}, +  }, +  null_ls = { +    setup = {}, +  }, +} diff --git a/lua/lsp/handlers.lua b/lua/lsp/handlers.lua index 2322e76a..ffb7564a 100644 --- a/lua/lsp/handlers.lua +++ b/lua/lsp/handlers.lua @@ -3,52 +3,73 @@  local M = {}  function M.setup() -  vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { +  local config = { -- your config      virtual_text = lvim.lsp.diagnostics.virtual_text, -    signs = lvim.lsp.diagnostics.signs.active, -    underline = lvim.lsp.document_highlight, -  }) - -  vim.lsp.handlers["textDocument/publishDiagnostics"] = function(_, _, params, client_id, _) -    local config = { -- your config -      virtual_text = lvim.lsp.diagnostics.virtual_text, -      signs = lvim.lsp.diagnostics.signs, -      underline = lvim.lsp.diagnostics.underline, -      update_in_insert = lvim.lsp.diagnostics.update_in_insert, -      severity_sort = lvim.lsp.diagnostics.severity_sort, -    } -    local uri = params.uri -    local bufnr = vim.uri_to_bufnr(uri) +    signs = lvim.lsp.diagnostics.signs, +    underline = lvim.lsp.diagnostics.underline, +    update_in_insert = lvim.lsp.diagnostics.update_in_insert, +    severity_sort = lvim.lsp.diagnostics.severity_sort, +  } +  if vim.fn.has "nvim-0.5.1" > 0 then +    vim.lsp.handlers["textDocument/publishDiagnostics"] = function(_, result, ctx, _) +      local uri = result.uri +      local bufnr = vim.uri_to_bufnr(uri) +      if not bufnr then +        return +      end -    if not bufnr then -      return -    end +      local diagnostics = result.diagnostics +      local ok, vim_diag = pcall(require, "vim.diagnostic") +      if ok then +        -- FIX: why can't we just use vim.diagnostic.get(buf_id)? +        config.signs = true +        for i, diagnostic in ipairs(diagnostics) do +          local rng = diagnostic.range +          diagnostics[i].lnum = rng["start"].line +          diagnostics[i].end_lnum = rng["end"].line +          diagnostics[i].col = rng["start"].character +          diagnostics[i].end_col = rng["end"].character +        end +        local namespace = vim.lsp.diagnostic.get_namespace(ctx.client_id) -    local diagnostics = params.diagnostics +        vim_diag.set(namespace, bufnr, diagnostics, config) +        if not vim.api.nvim_buf_is_loaded(bufnr) then +          return +        end -    for i, v in ipairs(diagnostics) do -      local source = v.source -      if source then -        if string.find(source, "/") then -          source = string.sub(v.source, string.find(v.source, "([%w-_]+)$")) +        local sign_names = { +          "DiagnosticSignError", +          "DiagnosticSignWarn", +          "DiagnosticSignInfo", +          "DiagnosticSignHint", +        } +        for i, sign in ipairs(lvim.lsp.diagnostics.signs.values) do +          vim.fn.sign_define(sign_names[i], { texthl = sign_names[i], text = sign.text, numhl = "" })          end -        diagnostics[i].message = string.format("%s: %s", source, v.message) +        vim_diag.show(namespace, bufnr, diagnostics, config)        else -        diagnostics[i].message = string.format("%s", v.message) -      end - -      if vim.tbl_contains(vim.tbl_keys(v), "code") then -        diagnostics[i].message = diagnostics[i].message .. string.format(" [%s]", v.code) +        vim.lsp.diagnostic.save(diagnostics, bufnr, ctx.client_id) +        if not vim.api.nvim_buf_is_loaded(bufnr) then +          return +        end +        vim.lsp.diagnostic.display(diagnostics, bufnr, ctx.client_id, config)        end      end +  else +    vim.lsp.handlers["textDocument/publishDiagnostics"] = function(_, _, params, client_id, _) +      local uri = params.uri +      local bufnr = vim.uri_to_bufnr(uri) +      if not bufnr then +        return +      end -    vim.lsp.diagnostic.save(diagnostics, bufnr, client_id) - -    if not vim.api.nvim_buf_is_loaded(bufnr) then -      return +      local diagnostics = params.diagnostics +      vim.lsp.diagnostic.save(diagnostics, bufnr, client_id) +      if not vim.api.nvim_buf_is_loaded(bufnr) then +        return +      end +      vim.lsp.diagnostic.display(diagnostics, bufnr, client_id, config)      end - -    vim.lsp.diagnostic.display(diagnostics, bufnr, client_id, config)    end    vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { @@ -60,4 +81,89 @@ function M.setup()    })  end +local function split_by_chunk(text, chunkSize) +  local s = {} +  for i = 1, #text, chunkSize do +    s[#s + 1] = text:sub(i, i + chunkSize - 1) +  end +  return s +end + +function M.show_line_diagnostics() +  -- TODO: replace all this with vim.diagnostic.show_position_diagnostics() +  local diagnostics = vim.lsp.diagnostic.get_line_diagnostics() +  local severity_highlight = { +    "LspDiagnosticsFloatingError", +    "LspDiagnosticsFloatingWarning", +    "LspDiagnosticsFloatingInformation", +    "LspDiagnosticsFloatingHint", +  } +  local ok, vim_diag = pcall(require, "vim.diagnostic") +  if ok then +    local buf_id = vim.api.nvim_win_get_buf(0) +    local win_id = vim.api.nvim_get_current_win() +    local cursor_position = vim.api.nvim_win_get_cursor(win_id) +    severity_highlight = { +      "DiagnosticFloatingError", +      "DiagnosticFloatingWarn", +      "DiagnosticFloatingInfo", +      "DiagnosticFloatingHint", +    } +    diagnostics = vim_diag.get(buf_id, { lnum = cursor_position[1] - 1 }) +  end +  local lines = {} +  local max_width = vim.fn.winwidth(0) - 5 +  local height = #diagnostics +  local width = 0 +  local opts = {} +  local close_events = { "CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre" } +  if height == 0 then +    return +  end +  local bufnr = vim.api.nvim_create_buf(false, true) +  local diag_message +  table.sort(diagnostics, function(a, b) +    return a.severity < b.severity +  end) +  for i, diagnostic in ipairs(diagnostics) do +    local source = diagnostic.source +    diag_message = diagnostic.message:gsub("[\n\r]", " ") +    if source then +      if string.find(source, "/") then +        source = string.sub(diagnostic.source, string.find(diagnostic.source, "([%w-_]+)$")) +      end +      diag_message = string.format("%d. %s: %s", i, source, diag_message) +    else +      diag_message = string.format("%d. %s", i, diag_message) +    end +    if diagnostic.code then +      diag_message = string.format("%s [%s]", diag_message, diagnostic.code) +    end +    local msgs = split_by_chunk(diag_message, max_width) +    for _, diag in ipairs(msgs) do +      table.insert(lines, { message = diag, severity = diagnostic.severity }) +      width = math.max(diag:len(), width) +    end +  end +  height = #lines +  opts = vim.lsp.util.make_floating_popup_options(width, height, opts) +  opts["style"] = "minimal" +  opts["border"] = "rounded" +  opts["focusable"] = true + +  vim.api.nvim_buf_set_option(bufnr, "bufhidden", "wipe") +  local winnr = vim.api.nvim_open_win(bufnr, false, opts) +  vim.api.nvim_win_set_option(winnr, "winblend", 0) +  vim.api.nvim_buf_set_var(bufnr, "lsp_floating_window", winnr) +  for i, diag in ipairs(lines) do +    vim.api.nvim_buf_set_lines(bufnr, i - 1, i - 1, 0, { diag.message }) +    vim.api.nvim_buf_add_highlight(bufnr, -1, severity_highlight[diag.severity], i - 1, 0, diag.message:len()) +  end + +  vim.api.nvim_command( +    "autocmd QuitPre <buffer> ++nested ++once lua pcall(vim.api.nvim_win_close, " .. winnr .. ", true)" +  ) +  vim.lsp.util.close_preview_autocmd(close_events, winnr) +end +  return M diff --git a/lua/lsp/init.lua b/lua/lsp/init.lua index 9c948803..88111005 100644 --- a/lua/lsp/init.lua +++ b/lua/lsp/init.lua @@ -1,15 +1,6 @@  local M = {}  local Log = require "core.log" - -function M.config() -  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() -end +local utils = require "utils"  local function lsp_highlight_document(client)    if lvim.lsp.document_highlight == false then @@ -19,9 +10,6 @@ local function lsp_highlight_document(client)    if client.resolved_capabilities.document_highlight then      vim.api.nvim_exec(        [[ -      hi LspReferenceRead cterm=bold ctermbg=red guibg=#464646 -      hi LspReferenceText cterm=bold ctermbg=red guibg=#464646 -      hi LspReferenceWrite cterm=bold ctermbg=red guibg=#464646        augroup lsp_document_highlight          autocmd! * <buffer>          autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() @@ -33,26 +21,49 @@ local function lsp_highlight_document(client)    end  end -local function add_lsp_buffer_keybindings(bufnr) -  local status_ok, wk = pcall(require, "which-key") -  if not status_ok then +local function lsp_code_lens_refresh(client) +  if lvim.lsp.code_lens_refresh == false then      return    end -  local keys = { -    ["K"] = { "<cmd>lua vim.lsp.buf.hover()<CR>", "Show hover" }, -    ["gd"] = { "<cmd>lua vim.lsp.buf.definition()<CR>", "Goto Definition" }, -    ["gD"] = { "<cmd>lua vim.lsp.buf.declaration()<CR>", "Goto declaration" }, -    ["gr"] = { "<cmd>lua vim.lsp.buf.references()<CR>", "Goto references" }, -    ["gI"] = { "<cmd>lua vim.lsp.buf.implementation()<CR>", "Goto Implementation" }, -    ["gs"] = { "<cmd>lua vim.lsp.buf.signature_help()<CR>", "show signature help" }, -    ["gp"] = { "<cmd>lua require'lsp.peek'.Peek('definition')<CR>", "Peek definition" }, -    ["gl"] = { -      "<cmd>lua vim.lsp.diagnostic.show_line_diagnostics({ show_header = false, border = 'single' })<CR>", -      "Show line diagnostics", -    }, +  if client.resolved_capabilities.code_lens then +    vim.api.nvim_exec( +      [[ +      augroup lsp_code_lens_refresh +        autocmd! * <buffer> +        autocmd InsertLeave <buffer> lua vim.lsp.codelens.refresh() +        autocmd InsertLeave <buffer> lua vim.lsp.codelens.display() +      augroup END +    ]], +      false +    ) +  end +end + +local function add_lsp_buffer_keybindings(bufnr) +  local mappings = { +    normal_mode = "n", +    insert_mode = "i", +    visual_mode = "v",    } -  wk.register(keys, { mode = "n", buffer = bufnr }) + +  if lvim.builtin.which_key.active then +    -- Remap using which_key +    local status_ok, wk = pcall(require, "which-key") +    if not status_ok then +      return +    end +    for mode_name, mode_char in pairs(mappings) do +      wk.register(lvim.lsp.buffer_mappings[mode_name], { mode = mode_char, buffer = bufnr }) +    end +  else +    -- Remap using nvim api +    for mode_name, mode_char in pairs(mappings) do +      for key, remap in pairs(lvim.lsp.buffer_mappings[mode_name]) do +        vim.api.nvim_buf_set_keymap(bufnr, mode_char, key, remap[1], { noremap = true, silent = true }) +      end +    end +  end  end  function M.common_capabilities() @@ -65,35 +76,30 @@ function M.common_capabilities()        "additionalTextEdits",      },    } -  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" +  local status_ok, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp") +  if status_ok then +    capabilities = cmp_nvim_lsp.update_capabilities(capabilities)    end -  client = vim.lsp.get_client_by_id(tonumber(client_id)) - -  local enabled_caps = {} +  return capabilities +end -  for k, v in pairs(client.resolved_capabilities) do -    if v == true then -      table.insert(enabled_caps, k) +local function select_default_formater(client) +  local client_formatting = client.resolved_capabilities.document_formatting +    or client.resolved_capabilities.document_range_formatting +  if client.name == "null-ls" or not client_formatting then +    return +  end +  Log:debug("Checking for formatter overriding for " .. client.name) +  local client_filetypes = client.config.filetypes or {} +  for _, filetype in ipairs(client_filetypes) do +    if lvim.lang[filetype] and #vim.tbl_keys(lvim.lang[filetype].formatters) > 0 then +      Log:debug("Formatter overriding detected. Disabling formatting capabilities for " .. client.name) +      client.resolved_capabilities.document_formatting = false +      client.resolved_capabilities.document_range_formatting = false      end    end - -  return enabled_caps  end  function M.common_on_init(client, bufnr) @@ -102,55 +108,58 @@ function M.common_on_init(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 +  select_default_formater(client)  end  function M.common_on_attach(client, bufnr)    if lvim.lsp.on_attach_callback then      lvim.lsp.on_attach_callback(client, bufnr) -    Log:debug "Called lsp.on_init_callback" +    Log:debug "Called lsp.on_attach_callback"    end    lsp_highlight_document(client) +  lsp_code_lens_refresh(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_utils.is_client_active(lsp.provider) then -    return +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 -  local overrides = lvim.lsp.override -  if type(overrides) == "table" then -    if vim.tbl_contains(overrides, lang) then -      return -    end +function M.get_common_opts() +  return { +    on_attach = M.common_on_attach, +    on_init = M.common_on_init, +    capabilities = M.common_capabilities(), +  } +end + +function M.setup() +  Log:debug "Setting up LSP support" + +  local lsp_status_ok, _ = pcall(require, "lspconfig") +  if not lsp_status_ok then +    return    end -  if lsp.provider ~= nil and lsp.provider ~= "" then -    local lspconfig = require "lspconfig" +  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 -    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 +  require("lsp.handlers").setup() -    lspconfig[lsp.provider].setup(lsp.setup) +  if not utils.is_directory(lvim.lsp.templates_dir) then +    require("lsp.templates").generate_templates()    end + +  bootstrap_nlsp { config_home = utils.join_paths(get_config_dir(), "lsp-settings") } + +  require("lsp.null-ls").setup() + +  require("utils").toggle_autoformat()  end  return M diff --git a/lua/lsp/manager.lua b/lua/lsp/manager.lua new file mode 100644 index 00000000..9cb81910 --- /dev/null +++ b/lua/lsp/manager.lua @@ -0,0 +1,86 @@ +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 = {}, +        lsp = {}, +      } +    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 true +    end +  end +end + +---Resolve the configuration for a server based on both common and user configuration +---@param name string +---@param user_config table [optional] +---@return table +local function resolve_config(name, user_config) +  local 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/" .. name) +  if status_ok then +    Log:debug("Using custom configuration for requested server: " .. name) +    config = vim.tbl_deep_extend("force", config, custom_config) +  end + +  if user_config then +    config = vim.tbl_deep_extend("force", config, user_config) +  end + +  return config +end + +---Setup a language server by providing a name +---@param server_name string name of the language server +---@param user_config table [optional] when available it will take predence over any default configurations +function M.setup(server_name, user_config) +  vim.validate { name = { server_name, "string" } } + +  if lsp_utils.is_client_active(server_name) or is_overridden(server_name) then +    return +  end + +  local config = resolve_config(server_name, user_config) +  local server_available, requested_server = require("nvim-lsp-installer.servers").get_server(server_name) + +  local function ensure_installed(server) +    if server:is_installed() then +      return true +    end +    if not lvim.lsp.automatic_servers_installation then +      Log:debug(server.name .. " is not managed by the automatic installer") +      return false +    end +    Log:debug(string.format("Installing [%s]", server.name)) +    server:install() +    vim.schedule(function() +      vim.cmd [[LspStart]] +    end) +  end + +  if server_available and ensure_installed(requested_server) then +    requested_server:setup(config) +  else +    require("lspconfig")[server_name].setup(config) +  end +end + +return M diff --git a/lua/lsp/null-ls/formatters.lua b/lua/lsp/null-ls/formatters.lua index 26be00da..4728b908 100644 --- a/lua/lsp/null-ls/formatters.lua +++ b/lua/lsp/null-ls/formatters.lua @@ -1,36 +1,23 @@  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)    local formatters = {} +  local tbl = require "utils.table"    for _, provider in pairs(null_ls.builtins.formatting) do -    -- TODO: Add support for wildcard filetypes -    if vim.tbl_contains(provider.filetypes or {}, filetype) then +    if tbl.contains(provider.filetypes or {}, function(ft) +      return ft == "*" or ft == filetype +    end) then        table.insert(formatters, provider.name)      end    end @@ -42,19 +29,24 @@ function M.list_configured(formatter_configs)    local formatters, errors = {}, {}    for _, fmt_config in ipairs(formatter_configs) do -    local formatter = null_ls.builtins.formatting[fmt_config.exe] +    local formatter_name = fmt_config.exe:gsub("-", "_") +    local formatter = null_ls.builtins.formatting[formatter_name]      if not formatter then -      Log:error("Not a valid formatter:", fmt_config.exe) +      Log:error("Not a valid formatter: " .. fmt_config.exe)        errors[fmt_config.exe] = {} -- Add data here when necessary      else        local formatter_cmd = services.find_command(formatter._opts.command)        if not formatter_cmd then -        Log:warn("Not found:", formatter._opts.command) +        Log:warn("Not found: " .. formatter._opts.command)          errors[fmt_config.exe] = {} -- Add data here when necessary        else -        Log:debug("Using formatter:", formatter_cmd) -        formatters[fmt_config.exe] = formatter.with { command = formatter_cmd, extra_args = fmt_config.args } +        Log:debug("Using formatter: " .. formatter_cmd) +        formatters[fmt_config.exe] = formatter.with { +          command = formatter_cmd, +          extra_args = fmt_config.args, +          filetypes = fmt_config.filetypes, +        }        end      end    end @@ -62,13 +54,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) +  if vim.tbl_isempty(formatter_configs) then      return    end -  formatters_by_ft[filetype] = M.list_configured(lvim.lang[filetype].formatters) -  null_ls.register { sources = formatters_by_ft[filetype].supported } +  local formatters_by_ft = M.list_configured(formatter_configs) +  null_ls.register { sources = formatters_by_ft.supported }  end  return M diff --git a/lua/lsp/null-ls/init.lua b/lua/lsp/null-ls/init.lua index ce4c07d9..0d030c22 100644 --- a/lua/lsp/null-ls/init.lua +++ b/lua/lsp/null-ls/init.lua @@ -1,44 +1,32 @@  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(lvim.lsp.null_ls.setup) +  for filetype, config in pairs(lvim.lang) do +    if not vim.tbl_isempty(config.formatters) then +      vim.tbl_map(function(c) +        c.filetypes = { filetype } +      end, config.formatters) +      formatters.setup(config.formatters) +    end +    if not vim.tbl_isempty(config.linters) then +      vim.tbl_map(function(c) +        c.filetypes = { filetype } +      end, config.formatters) +      linters.setup(config.linters) +    end +  end  end  return M diff --git a/lua/lsp/null-ls/linters.lua b/lua/lsp/null-ls/linters.lua index bc191d7e..549c6cdd 100644 --- a/lua/lsp/null-ls/linters.lua +++ b/lua/lsp/null-ls/linters.lua @@ -1,36 +1,23 @@  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)    local linters = {} +  local tbl = require "utils.table"    for _, provider in pairs(null_ls.builtins.diagnostics) do -    -- TODO: Add support for wildcard filetypes -    if vim.tbl_contains(provider.filetypes or {}, filetype) then +    if tbl.contains(provider.filetypes or {}, function(ft) +      return ft == "*" or ft == filetype +    end) then        table.insert(linters, provider.name)      end    end @@ -42,19 +29,24 @@ function M.list_configured(linter_configs)    local linters, errors = {}, {}    for _, lnt_config in pairs(linter_configs) do -    local linter = null_ls.builtins.diagnostics[lnt_config.exe] +    local linter_name = lnt_config.exe:gsub("-", "_") +    local linter = null_ls.builtins.diagnostics[linter_name]      if not linter then -      Log:error("Not a valid linter:", lnt_config.exe) +      Log:error("Not a valid linter: " .. lnt_config.exe)        errors[lnt_config.exe] = {} -- Add data here when necessary      else        local linter_cmd = services.find_command(linter._opts.command)        if not linter_cmd then -        Log:warn("Not found:", linter._opts.command) +        Log:warn("Not found: " .. linter._opts.command)          errors[lnt_config.exe] = {} -- Add data here when necessary        else -        Log:debug("Using linter:", linter_cmd) -        linters[lnt_config.exe] = linter.with { command = linter_cmd, extra_args = lnt_config.args } +        Log:debug("Using linter: " .. linter_cmd) +        linters[lnt_config.exe] = linter.with { +          command = linter_cmd, +          extra_args = lnt_config.args, +          filetypes = lnt_config.filetypes, +        }        end      end    end @@ -62,13 +54,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) +  if vim.tbl_isempty(linter_configs) then      return    end -  linters_by_ft[filetype] = M.list_configured(lvim.lang[filetype].linters) -  null_ls.register { sources = linters_by_ft[filetype].supported } +  local linters = M.list_configured(linter_configs) +  null_ls.register { sources = linters.supported }  end  return M diff --git a/lua/lsp/null-ls/services.lua b/lua/lsp/null-ls/services.lua index a1e3a06c..ef9e7d22 100644 --- a/lua/lsp/null-ls/services.lua +++ b/lua/lsp/null-ls/services.lua @@ -4,8 +4,8 @@ local function find_root_dir()    local util = require "lspconfig/util"    local lsp_utils = require "lsp.utils" -  local status_ok, ts_client = lsp_utils.is_client_active "typescript" -  if status_ok then +  local ts_client = lsp_utils.is_client_active "typescript" +  if ts_client then      return ts_client.config.root_dir    end    local dirname = vim.fn.expand "%:p:h" @@ -28,6 +28,7 @@ local local_providers = {    prettier_d_slim = { find = from_node_modules },    eslint_d = { find = from_node_modules },    eslint = { find = from_node_modules }, +  stylelint = { find = from_node_modules },  }  function M.find_command(command) @@ -44,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/peek.lua b/lua/lsp/peek.lua index cc8e57a9..cb00488e 100644 --- a/lua/lsp/peek.lua +++ b/lua/lsp/peek.lua @@ -42,6 +42,7 @@ local function create_floating_file(location, opts)    local winnr = vim.api.nvim_open_win(bufnr, false, opts)    vim.api.nvim_win_set_option(winnr, "winblend", 0) +  vim.api.nvim_win_set_cursor(winnr, { range.start.line + 1, range.start.character })    vim.api.nvim_buf_set_var(bufnr, "lsp_floating_window", winnr)    -- Set some autocmds to close the window @@ -53,9 +54,8 @@ local function create_floating_file(location, opts)    return bufnr, winnr  end -local function preview_location_callback(_, method, result) +local function preview_location_callback(result)    if result == nil or vim.tbl_isempty(result) then -    print("peek: No location found: " .. method)      return nil    end @@ -73,6 +73,14 @@ local function preview_location_callback(_, method, result)    end  end +local function preview_location_callback_old_signature(_, _, result) +  return preview_location_callback(result) +end + +local function preview_location_callback_new_signature(_, result) +  return preview_location_callback(result) +end +  function M.open_file()    -- Get the file currently open in the floating window    local filepath = vim.fn.expand "%:." @@ -128,7 +136,11 @@ function M.Peek(what)    else      -- Make a new request and then create the new window in the callback      local params = vim.lsp.util.make_position_params() -    local success, _ = pcall(vim.lsp.buf_request, 0, "textDocument/" .. what, params, preview_location_callback) +    local preview_callback = preview_location_callback_old_signature +    if vim.fn.has "nvim-0.5.1" > 0 then +      preview_callback = preview_location_callback_new_signature +    end +    local success, _ = pcall(vim.lsp.buf_request, 0, "textDocument/" .. what, params, preview_callback)      if not success then        print(          'peek: Error calling LSP method "textDocument/' .. what .. '". The current language lsp might not support it.' diff --git a/lua/lsp/providers/jsonls.lua b/lua/lsp/providers/jsonls.lua new file mode 100644 index 00000000..1fffa686 --- /dev/null +++ b/lua/lsp/providers/jsonls.lua @@ -0,0 +1,197 @@ +local default_schemas = nil +local status_ok, jsonls_settings = pcall(require, "nlspsettings.jsonls") +if status_ok then +  default_schemas = jsonls_settings.get_default_schemas() +end + +local schemas = { +  { +    description = "TypeScript compiler configuration file", +    fileMatch = { +      "tsconfig.json", +      "tsconfig.*.json", +    }, +    url = "https://json.schemastore.org/tsconfig.json", +  }, +  { +    description = "Lerna config", +    fileMatch = { "lerna.json" }, +    url = "https://json.schemastore.org/lerna.json", +  }, +  { +    description = "Babel configuration", +    fileMatch = { +      ".babelrc.json", +      ".babelrc", +      "babel.config.json", +    }, +    url = "https://json.schemastore.org/babelrc.json", +  }, +  { +    description = "ESLint config", +    fileMatch = { +      ".eslintrc.json", +      ".eslintrc", +    }, +    url = "https://json.schemastore.org/eslintrc.json", +  }, +  { +    description = "Bucklescript config", +    fileMatch = { "bsconfig.json" }, +    url = "https://raw.githubusercontent.com/rescript-lang/rescript-compiler/8.2.0/docs/docson/build-schema.json", +  }, +  { +    description = "Prettier config", +    fileMatch = { +      ".prettierrc", +      ".prettierrc.json", +      "prettier.config.json", +    }, +    url = "https://json.schemastore.org/prettierrc", +  }, +  { +    description = "Vercel Now config", +    fileMatch = { "now.json" }, +    url = "https://json.schemastore.org/now", +  }, +  { +    description = "Stylelint config", +    fileMatch = { +      ".stylelintrc", +      ".stylelintrc.json", +      "stylelint.config.json", +    }, +    url = "https://json.schemastore.org/stylelintrc", +  }, +  { +    description = "A JSON schema for the ASP.NET LaunchSettings.json files", +    fileMatch = { "launchsettings.json" }, +    url = "https://json.schemastore.org/launchsettings.json", +  }, +  { +    description = "Schema for CMake Presets", +    fileMatch = { +      "CMakePresets.json", +      "CMakeUserPresets.json", +    }, +    url = "https://raw.githubusercontent.com/Kitware/CMake/master/Help/manual/presets/schema.json", +  }, +  { +    description = "Configuration file as an alternative for configuring your repository in the settings page.", +    fileMatch = { +      ".codeclimate.json", +    }, +    url = "https://json.schemastore.org/codeclimate.json", +  }, +  { +    description = "LLVM compilation database", +    fileMatch = { +      "compile_commands.json", +    }, +    url = "https://json.schemastore.org/compile-commands.json", +  }, +  { +    description = "Config file for Command Task Runner", +    fileMatch = { +      "commands.json", +    }, +    url = "https://json.schemastore.org/commands.json", +  }, +  { +    description = "AWS CloudFormation provides a common language for you to describe and provision all the infrastructure resources in your cloud environment.", +    fileMatch = { +      "*.cf.json", +      "cloudformation.json", +    }, +    url = "https://raw.githubusercontent.com/awslabs/goformation/v5.2.9/schema/cloudformation.schema.json", +  }, +  { +    description = "The AWS Serverless Application Model (AWS SAM, previously known as Project Flourish) extends AWS CloudFormation to provide a simplified way of defining the Amazon API Gateway APIs, AWS Lambda functions, and Amazon DynamoDB tables needed by your serverless application.", +    fileMatch = { +      "serverless.template", +      "*.sam.json", +      "sam.json", +    }, +    url = "https://raw.githubusercontent.com/awslabs/goformation/v5.2.9/schema/sam.schema.json", +  }, +  { +    description = "Json schema for properties json file for a GitHub Workflow template", +    fileMatch = { +      ".github/workflow-templates/**.properties.json", +    }, +    url = "https://json.schemastore.org/github-workflow-template-properties.json", +  }, +  { +    description = "golangci-lint configuration file", +    fileMatch = { +      ".golangci.toml", +      ".golangci.json", +    }, +    url = "https://json.schemastore.org/golangci-lint.json", +  }, +  { +    description = "JSON schema for the JSON Feed format", +    fileMatch = { +      "feed.json", +    }, +    url = "https://json.schemastore.org/feed.json", +    versions = { +      ["1"] = "https://json.schemastore.org/feed-1.json", +      ["1.1"] = "https://json.schemastore.org/feed.json", +    }, +  }, +  { +    description = "Packer template JSON configuration", +    fileMatch = { +      "packer.json", +    }, +    url = "https://json.schemastore.org/packer.json", +  }, +  { +    description = "NPM configuration file", +    fileMatch = { +      "package.json", +    }, +    url = "https://json.schemastore.org/package.json", +  }, +  { +    description = "JSON schema for Visual Studio component configuration files", +    fileMatch = { +      "*.vsconfig", +    }, +    url = "https://json.schemastore.org/vsconfig.json", +  }, +  { +    description = "Resume json", +    fileMatch = { "resume.json" }, +    url = "https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json", +  }, +} + +local function extend(tab1, tab2) +  for _, value in ipairs(tab2) do +    table.insert(tab1, value) +  end +  return tab1 +end + +local extended_schemas = extend(schemas, default_schemas) + +local opts = { +  settings = { +    json = { +      schemas = extended_schemas, +    }, +  }, +  setup = { +    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/providers/yamlls.lua b/lua/lsp/providers/yamlls.lua new file mode 100644 index 00000000..156a35b0 --- /dev/null +++ b/lua/lsp/providers/yamlls.lua @@ -0,0 +1,30 @@ +local opts = { +  settings = { +    yaml = { +      hover = true, +      completion = true, +      validate = true, +      schemaStore = { +        enable = true, +        url = "https://www.schemastore.org/api/json/catalog.json", +      }, +      schemas = { +        kubernetes = { +          "daemon.{yml,yaml}", +          "manager.{yml,yaml}", +          "restapi.{yml,yaml}", +          "role.{yml,yaml}", +          "role_binding.{yml,yaml}", +          "*onfigma*.{yml,yaml}", +          "*ngres*.{yml,yaml}", +          "*ecre*.{yml,yaml}", +          "*eployment*.{yml,yaml}", +          "*ervic*.{yml,yaml}", +          "kubectl-edit*.yaml", +        }, +      }, +    }, +  }, +} + +return opts diff --git a/lua/lsp/templates.lua b/lua/lsp/templates.lua new file mode 100644 index 00000000..fbbc37f6 --- /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" 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..59003406 100644 --- a/lua/lsp/utils.lua +++ b/lua/lsp/utils.lua @@ -1,28 +1,62 @@  local M = {} +local tbl = require "utils.table" +  function M.is_client_active(name)    local clients = vim.lsp.get_active_clients() +  return tbl.find_first(clients, function(client) +    return client.name == name +  end) +end + +function M.get_active_clients_by_ft(filetype) +  local matches = {} +  local clients = vim.lsp.get_active_clients()    for _, client in pairs(clients) do -    if client.name == name then -      return true, 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 false +  return matches  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 +function M.get_client_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 clients = vim.lsp.get_active_clients() -  for _, client in pairs(clients) do -    if client.name == lvim.lang[filetype].lsp.provider then -      return client +  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  | 
