diff options
| author | kylo252 <[email protected]> | 2021-10-10 21:07:41 +0200 | 
|---|---|---|
| committer | GitHub <[email protected]> | 2021-10-10 21:07:41 +0200 | 
| commit | 52b74557415eb757ad4b7481b0aec8a3f98dd58d (patch) | |
| tree | 9a05ec71a46c99fbdf8df0043be652b528c7c04e /lua/lvim/lsp | |
| parent | e2c85df440564a62fd804555747b1652a6844a5e (diff) | |
feat: add an independent lvim namespace (#1699)
Diffstat (limited to 'lua/lvim/lsp')
| -rw-r--r-- | lua/lvim/lsp/config.lua | 45 | ||||
| -rw-r--r-- | lua/lvim/lsp/handlers.lua | 169 | ||||
| -rw-r--r-- | lua/lvim/lsp/init.lua | 165 | ||||
| -rw-r--r-- | lua/lvim/lsp/manager.lua | 86 | ||||
| -rw-r--r-- | lua/lvim/lsp/null-ls/formatters.lua | 66 | ||||
| -rw-r--r-- | lua/lvim/lsp/null-ls/init.lua | 32 | ||||
| -rw-r--r-- | lua/lvim/lsp/null-ls/linters.lua | 66 | ||||
| -rw-r--r-- | lua/lvim/lsp/null-ls/services.lua | 63 | ||||
| -rw-r--r-- | lua/lvim/lsp/peek.lua | 152 | ||||
| -rw-r--r-- | lua/lvim/lsp/providers/jsonls.lua | 197 | ||||
| -rw-r--r-- | lua/lvim/lsp/providers/sumneko_lua.lua | 19 | ||||
| -rw-r--r-- | lua/lvim/lsp/providers/vuels.lua | 26 | ||||
| -rw-r--r-- | lua/lvim/lsp/providers/yamlls.lua | 30 | ||||
| -rw-r--r-- | lua/lvim/lsp/templates.lua | 98 | ||||
| -rw-r--r-- | lua/lvim/lsp/utils.lua | 62 | 
15 files changed, 1276 insertions, 0 deletions
| diff --git a/lua/lvim/lsp/config.lua b/lua/lvim/lsp/config.lua new file mode 100644 index 00000000..30336cc2 --- /dev/null +++ b/lua/lvim/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'lvim.lsp.peek'.Peek('definition')<CR>", "Peek definition" }, +      ["gl"] = { +        "<cmd>lua require'lvim.lsp.handlers'.show_line_diagnostics()<CR>", +        "Show line diagnostics", +      }, +    }, +    insert_mode = {}, +    visual_mode = {}, +  }, +  null_ls = { +    setup = {}, +  }, +} diff --git a/lua/lvim/lsp/handlers.lua b/lua/lvim/lsp/handlers.lua new file mode 100644 index 00000000..ffb7564a --- /dev/null +++ b/lua/lvim/lsp/handlers.lua @@ -0,0 +1,169 @@ +-- Set Default Prefix. +-- Note: You can set a prefix per lsp server in the lv-globals.lua file +local M = {} + +function M.setup() +  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, +  } +  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 + +      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) + +        vim_diag.set(namespace, bufnr, diagnostics, config) +        if not vim.api.nvim_buf_is_loaded(bufnr) then +          return +        end + +        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 +        vim_diag.show(namespace, bufnr, diagnostics, config) +      else +        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 + +      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 +  end + +  vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { +    border = lvim.lsp.popup_border, +  }) + +  vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(vim.lsp.handlers.signature_help, { +    border = lvim.lsp.popup_border, +  }) +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/lvim/lsp/init.lua b/lua/lvim/lsp/init.lua new file mode 100644 index 00000000..c8d583a9 --- /dev/null +++ b/lua/lvim/lsp/init.lua @@ -0,0 +1,165 @@ +local M = {} +local Log = require "lvim.core.log" +local utils = require "lvim.utils" + +local function lsp_highlight_document(client) +  if lvim.lsp.document_highlight == false then +    return -- we don't need further +  end +  -- Set autocommands conditional on server_capabilities +  if client.resolved_capabilities.document_highlight then +    vim.api.nvim_exec( +      [[ +      augroup lsp_document_highlight +        autocmd! * <buffer> +        autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() +        autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() +      augroup END +    ]], +      false +    ) +  end +end + +local function lsp_code_lens_refresh(client) +  if lvim.lsp.code_lens_refresh == false then +    return +  end + +  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", +  } + +  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() +  local capabilities = vim.lsp.protocol.make_client_capabilities() +  capabilities.textDocument.completion.completionItem.snippetSupport = true +  capabilities.textDocument.completion.completionItem.resolveSupport = { +    properties = { +      "documentation", +      "detail", +      "additionalTextEdits", +    }, +  } + +  local status_ok, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp") +  if status_ok then +    capabilities = cmp_nvim_lsp.update_capabilities(capabilities) +  end + +  return capabilities +end + +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 +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 +  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_attach_callback" +  end +  lsp_highlight_document(client) +  lsp_code_lens_refresh(client) +  add_lsp_buffer_keybindings(bufnr) +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 + +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 + +  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("lvim.lsp.handlers").setup() + +  if not utils.is_directory(lvim.lsp.templates_dir) then +    require("lvim.lsp.templates").generate_templates() +  end + +  bootstrap_nlsp { config_home = utils.join_paths(get_config_dir(), "lsp-settings") } + +  require("lvim.lsp.null-ls").setup() + +  require("lvim.utils").toggle_autoformat() +end + +return M diff --git a/lua/lvim/lsp/manager.lua b/lua/lvim/lsp/manager.lua new file mode 100644 index 00000000..678a08af --- /dev/null +++ b/lua/lvim/lsp/manager.lua @@ -0,0 +1,86 @@ +local M = {} + +local Log = require "lvim.core.log" +local lsp_utils = require "lvim.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("lvim.lsp").common_on_attach, +    on_init = require("lvim.lsp").common_on_init, +    capabilities = require("lvim.lsp").common_capabilities(), +  } + +  local status_ok, custom_config = pcall(require, "lvim.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/lvim/lsp/null-ls/formatters.lua b/lua/lvim/lsp/null-ls/formatters.lua new file mode 100644 index 00000000..663e2586 --- /dev/null +++ b/lua/lvim/lsp/null-ls/formatters.lua @@ -0,0 +1,66 @@ +local M = {} + +local null_ls = require "null-ls" +local services = require "lvim.lsp.null-ls.services" +local Log = require "lvim.core.log" + +function M.list_supported_names(filetype) +  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 "lvim.utils.table" +  for _, provider in pairs(null_ls.builtins.formatting) do +    if tbl.contains(provider.filetypes or {}, function(ft) +      return ft == "*" or ft == filetype +    end) then +      table.insert(formatters, provider.name) +    end +  end + +  return formatters +end + +function M.list_configured(formatter_configs) +  local formatters, errors = {}, {} + +  for _, fmt_config in ipairs(formatter_configs) do +    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) +      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) +        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, +          filetypes = fmt_config.filetypes, +        } +      end +    end +  end + +  return { supported = formatters, unsupported = errors } +end + +function M.setup(formatter_configs) +  if vim.tbl_isempty(formatter_configs) then +    return +  end + +  local formatters_by_ft = M.list_configured(formatter_configs) +  null_ls.register { sources = formatters_by_ft.supported } +end + +return M diff --git a/lua/lvim/lsp/null-ls/init.lua b/lua/lvim/lsp/null-ls/init.lua new file mode 100644 index 00000000..f2d3216d --- /dev/null +++ b/lua/lvim/lsp/null-ls/init.lua @@ -0,0 +1,32 @@ +local M = {} + +local Log = require "lvim.core.log" +local formatters = require "lvim.lsp.null-ls.formatters" +local linters = require "lvim.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 + +  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/lvim/lsp/null-ls/linters.lua b/lua/lvim/lsp/null-ls/linters.lua new file mode 100644 index 00000000..9ea2d55b --- /dev/null +++ b/lua/lvim/lsp/null-ls/linters.lua @@ -0,0 +1,66 @@ +local M = {} + +local null_ls = require "null-ls" +local services = require "lvim.lsp.null-ls.services" +local Log = require "lvim.core.log" + +function M.list_supported_names(filetype) +  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 "lvim.utils.table" +  for _, provider in pairs(null_ls.builtins.diagnostics) do +    if tbl.contains(provider.filetypes or {}, function(ft) +      return ft == "*" or ft == filetype +    end) then +      table.insert(linters, provider.name) +    end +  end + +  return linters +end + +function M.list_configured(linter_configs) +  local linters, errors = {}, {} + +  for _, lnt_config in pairs(linter_configs) do +    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) +      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) +        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, +          filetypes = lnt_config.filetypes, +        } +      end +    end +  end + +  return { supported = linters, unsupported = errors } +end + +function M.setup(linter_configs) +  if vim.tbl_isempty(linter_configs) then +    return +  end + +  local linters = M.list_configured(linter_configs) +  null_ls.register { sources = linters.supported } +end + +return M diff --git a/lua/lvim/lsp/null-ls/services.lua b/lua/lvim/lsp/null-ls/services.lua new file mode 100644 index 00000000..9cb29f49 --- /dev/null +++ b/lua/lvim/lsp/null-ls/services.lua @@ -0,0 +1,63 @@ +local M = {} + +local function find_root_dir() +  local util = require "lspconfig/util" +  local lsp_utils = require "lvim.lsp.utils" + +  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" +  return util.root_pattern "package.json"(dirname) +end + +local function from_node_modules(command) +  local root_dir = find_root_dir() + +  if not root_dir then +    return nil +  end + +  return root_dir .. "/node_modules/.bin/" .. command +end + +local local_providers = { +  prettier = { find = from_node_modules }, +  prettierd = { find = from_node_modules }, +  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) +  if local_providers[command] then +    local local_command = local_providers[command].find(command) +    if local_command and vim.fn.executable(local_command) == 1 then +      return local_command +    end +  end + +  if vim.fn.executable(command) == 1 then +    return command +  end +  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/lvim/lsp/peek.lua b/lua/lvim/lsp/peek.lua new file mode 100644 index 00000000..08345aff --- /dev/null +++ b/lua/lvim/lsp/peek.lua @@ -0,0 +1,152 @@ +local M = { +  floating_buf = nil, +  floating_win = nil, +  prev_result = nil, +} + +local function create_floating_file(location, opts) +  vim.validate { +    location = { location, "t" }, +    opts = { opts, "t", true }, +  } + +  -- Set some defaults +  opts = opts or {} +  local close_events = opts.close_events or { "CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre" } + +  -- location may be LocationLink or Location +  local uri = location.targetUri or location.uri +  if uri == nil then +    return +  end +  local bufnr = vim.uri_to_bufnr(uri) +  if not vim.api.nvim_buf_is_loaded(bufnr) then +    vim.fn.bufload(bufnr) +  end + +  local range = location.targetRange or location.range + +  local contents = vim.api.nvim_buf_get_lines( +    bufnr, +    range.start.line, +    math.min(range["end"].line + 1 + (opts.context or 10), range.start.line + (opts.max_height or 15)), -- Don't let the window be more that 15 lines long(height) +    false +  ) +  local width, height = vim.lsp.util._make_floating_popup_size(contents, opts) +  opts = vim.lsp.util.make_floating_popup_options(width, height, opts) +  -- Don't make it minimal as it is meant to be fully featured +  opts["style"] = nil + +  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_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 +  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) + +  return bufnr, winnr +end + +local function preview_location_callback(result) +  if result == nil or vim.tbl_isempty(result) then +    return nil +  end + +  local opts = { +    border = "rounded", +    context = 10, +  } + +  if vim.tbl_islist(result) then +    M.prev_result = result[1] +    M.floating_buf, M.floating_win = create_floating_file(result[1], opts) +  else +    M.prev_result = result +    M.floating_buf, M.floating_win = create_floating_file(result, opts) +  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 "%:." + +  if not filepath then +    print "peek: Unable to open the file!" +    return +  end + +  -- Close the floating window +  pcall(vim.api.nvim_win_close, M.floating_win, true) + +  -- Edit the file +  vim.cmd("edit " .. filepath) + +  local winnr = vim.api.nvim_get_current_win() + +  -- Set the cursor at the right position +  M.set_cursor_to_prev_pos(winnr) +end + +function M.set_cursor_to_prev_pos(winnr) +  -- Get position of the thing to peek at +  local location = M.prev_result +  local range = location.targetRange or location.range +  local cursor_pos = { range.start.line + 1, range.start.character } + +  -- Set the winnr to the floating window if none was passed in +  winnr = winnr or M.floating_win +  -- Set the cursor at the correct position in the floating window +  vim.api.nvim_win_set_cursor(winnr, cursor_pos) +end + +function M.Peek(what) +  -- If a window already exists, focus it at the right position! +  if vim.tbl_contains(vim.api.nvim_list_wins(), M.floating_win) then +    local success_1, _ = pcall(vim.api.nvim_set_current_win, M.floating_win) +    if not success_1 then +      print "peek: You cannot edit the current file in a preview!" +      return +    end + +    -- Set the cursor at the correct position in the floating window +    M.set_cursor_to_prev_pos() + +    vim.api.nvim_buf_set_keymap( +      M.floating_buf, +      "n", +      "<CR>", +      ":lua require('lvim.lsp.peek').open_file()<CR>", +      { noremap = true, silent = true } +    ) +  else +    -- Make a new request and then create the new window in the callback +    local params = vim.lsp.util.make_position_params() +    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.' +      ) +    end +  end +end + +return M diff --git a/lua/lvim/lsp/providers/jsonls.lua b/lua/lvim/lsp/providers/jsonls.lua new file mode 100644 index 00000000..1fffa686 --- /dev/null +++ b/lua/lvim/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/lvim/lsp/providers/sumneko_lua.lua b/lua/lvim/lsp/providers/sumneko_lua.lua new file mode 100644 index 00000000..6585c8c7 --- /dev/null +++ b/lua/lvim/lsp/providers/sumneko_lua.lua @@ -0,0 +1,19 @@ +local opts = { +  settings = { +    Lua = { +      diagnostics = { +        globals = { "vim", "lvim" }, +      }, +      workspace = { +        library = { +          [require("lvim.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/lvim/lsp/providers/vuels.lua b/lua/lvim/lsp/providers/vuels.lua new file mode 100644 index 00000000..326363fd --- /dev/null +++ b/lua/lvim/lsp/providers/vuels.lua @@ -0,0 +1,26 @@ +local opts = { +  setup = { +    root_dir = function(fname) +      local util = require "lvim.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/lvim/lsp/providers/yamlls.lua b/lua/lvim/lsp/providers/yamlls.lua new file mode 100644 index 00000000..156a35b0 --- /dev/null +++ b/lua/lvim/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/lvim/lsp/templates.lua b/lua/lvim/lsp/templates.lua new file mode 100644 index 00000000..e0741b1b --- /dev/null +++ b/lua/lvim/lsp/templates.lua @@ -0,0 +1,98 @@ +local M = {} + +local Log = require "lvim.core.log" +local utils = require "lvim.utils" +local get_supported_filetypes = require("lvim.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("lvim.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/lvim/lsp/utils.lua b/lua/lvim/lsp/utils.lua new file mode 100644 index 00000000..a34fbf44 --- /dev/null +++ b/lua/lvim/lsp/utils.lua @@ -0,0 +1,62 @@ +local M = {} + +local tbl = require "lvim.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 +    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_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 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 +end + +return M | 
