diff options
Diffstat (limited to 'runtime/lua/vim')
| -rw-r--r-- | runtime/lua/vim/diagnostic.lua | 232 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp.lua | 82 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/diagnostic.lua | 2 | 
3 files changed, 156 insertions, 160 deletions
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 0bc7a305f0..9b57467a52 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -36,7 +36,51 @@ M.handlers = setmetatable({}, {    end,  }) --- Local functions {{{ +-- Metatable that automatically creates an empty table when assigning to a missing key +local bufnr_and_namespace_cacher_mt = { +  __index = function(t, bufnr) +    if not bufnr or bufnr == 0 then +      bufnr = vim.api.nvim_get_current_buf() +    end + +    rawset(t, bufnr, {}) + +    return rawget(t, bufnr) +  end, + +  __newindex = function(t, bufnr, v) +    if not bufnr or bufnr == 0 then +      bufnr = vim.api.nvim_get_current_buf() +    end + +    rawset(t, bufnr, v) +  end, +} + +local diagnostic_cache = setmetatable({}, { +  __index = function(t, bufnr) +    if not bufnr or bufnr == 0 then +      bufnr = vim.api.nvim_get_current_buf() +    end + +    vim.api.nvim_buf_attach(bufnr, false, { +      on_detach = function() +        rawset(t, bufnr, nil) -- clear cache +      end +    }) + +    rawset(t, bufnr, {}) + +    return rawget(t, bufnr) +  end, +}) + +local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) +local diagnostic_attached_buffers = {} +local diagnostic_disabled = {} +local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt) + +local all_namespaces = {}  ---@private  local function to_severity(severity) @@ -106,8 +150,6 @@ local function reformat_diagnostics(format, diagnostics)    return formatted  end -local all_namespaces = {} -  ---@private  local function enabled_value(option, namespace)    local ns = namespace and M.get_namespace(namespace) or {} @@ -213,36 +255,6 @@ local function get_bufnr(bufnr)    return bufnr  end --- Metatable that automatically creates an empty table when assigning to a missing key -local bufnr_and_namespace_cacher_mt = { -  __index = function(t, bufnr) -    if not bufnr or bufnr == 0 then -      bufnr = vim.api.nvim_get_current_buf() -    end - -    if rawget(t, bufnr) == nil then -      rawset(t, bufnr, {}) -    end - -    return rawget(t, bufnr) -  end, - -  __newindex = function(t, bufnr, v) -    if not bufnr or bufnr == 0 then -      bufnr = vim.api.nvim_get_current_buf() -    end - -    rawset(t, bufnr, v) -  end, -} - -local diagnostic_cleanup = setmetatable({}, bufnr_and_namespace_cacher_mt) -local diagnostic_cache = setmetatable({}, bufnr_and_namespace_cacher_mt) -local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) -local diagnostic_attached_buffers = {} -local diagnostic_disabled = {} -local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt) -  ---@private  local function is_disabled(namespace, bufnr)    local ns = M.get_namespace(namespace) @@ -287,11 +299,6 @@ local function set_diagnostic_cache(namespace, bufnr, diagnostics)  end  ---@private -local function clear_diagnostic_cache(namespace, bufnr) -  diagnostic_cache[bufnr][namespace] = nil -end - ----@private  local function restore_extmarks(bufnr, last)    for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do      local extmarks_current = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true}) @@ -377,6 +384,59 @@ local function clear_scheduled_display(namespace, bufnr)  end  ---@private +local function get_diagnostics(bufnr, opts, clamp) +  opts = opts or {} + +  local namespace = opts.namespace +  local diagnostics = {} +  local buf_line_count = clamp and vim.api.nvim_buf_line_count(bufnr) or math.huge + +  ---@private +  local function add(d) +    if not opts.lnum or d.lnum == opts.lnum then +      if clamp and (d.lnum >= buf_line_count or d.end_lnum >= buf_line_count) then +        d = vim.deepcopy(d) +        d.lnum = math.max(math.min(d.lnum, buf_line_count - 1), 0) +        d.end_lnum = math.max(math.min(d.end_lnum, buf_line_count - 1), 0) +      end +      table.insert(diagnostics, d) +    end +  end + +  if namespace == nil and bufnr == nil then +    for _, t in pairs(diagnostic_cache) do +      for _, v in pairs(t) do +        for _, diagnostic in pairs(v) do +          add(diagnostic) +        end +      end +    end +  elseif namespace == nil then +    for iter_namespace in pairs(diagnostic_cache[bufnr]) do +      for _, diagnostic in pairs(diagnostic_cache[bufnr][iter_namespace]) do +        add(diagnostic) +      end +    end +  elseif bufnr == nil then +    for _, t in pairs(diagnostic_cache) do +      for _, diagnostic in pairs(t[namespace] or {}) do +        add(diagnostic) +      end +    end +  else +    for _, diagnostic in pairs(diagnostic_cache[bufnr][namespace] or {}) do +      add(diagnostic) +    end +  end + +  if opts.severity then +    diagnostics = filter_by_severity(opts.severity, diagnostics) +  end + +  return diagnostics +end + +---@private  local function set_list(loclist, opts)    opts = opts or {}    local open = vim.F.if_nil(opts.open, true) @@ -386,7 +446,7 @@ local function set_list(loclist, opts)    if loclist then      bufnr = vim.api.nvim_win_get_buf(winnr)    end -  local diagnostics = M.get(bufnr, opts) +  local diagnostics = get_diagnostics(bufnr, opts, true)    local items = M.toqflist(diagnostics)    if loclist then      vim.fn.setloclist(winnr, {}, ' ', { title = title, items = items }) @@ -399,27 +459,12 @@ local function set_list(loclist, opts)  end  ---@private ---- To (slightly) improve performance, modifies diagnostics in place. -local function clamp_line_numbers(bufnr, diagnostics) -  local buf_line_count = vim.api.nvim_buf_line_count(bufnr) -  if buf_line_count == 0 then -    return -  end - -  for _, diagnostic in ipairs(diagnostics) do -    diagnostic.lnum = math.max(math.min(diagnostic.lnum, buf_line_count - 1), 0) -    diagnostic.end_lnum = math.max(math.min(diagnostic.end_lnum, buf_line_count - 1), 0) -  end -end - ----@private  local function next_diagnostic(position, search_forward, bufnr, opts, namespace)    position[1] = position[1] - 1    bufnr = get_bufnr(bufnr)    local wrap = vim.F.if_nil(opts.wrap, true)    local line_count = vim.api.nvim_buf_line_count(bufnr) -  local diagnostics = M.get(bufnr, vim.tbl_extend("keep", opts, {namespace = namespace})) -  clamp_line_numbers(bufnr, diagnostics) +  local diagnostics = get_diagnostics(bufnr, vim.tbl_extend("keep", opts, {namespace = namespace}), true)    local line_diagnostics = diagnostic_lines(diagnostics)    for i = 0, line_count do      local offset = i * (search_forward and 1 or -1) @@ -431,13 +476,14 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)        lnum = (lnum + line_count) % line_count      end      if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then +      local line_length = #vim.api.nvim_buf_get_lines(bufnr,  lnum, lnum + 1, true)[1]        local sort_diagnostics, is_next        if search_forward then          sort_diagnostics = function(a, b) return a.col < b.col end -        is_next = function(diagnostic) return diagnostic.col > position[2] end +        is_next = function(d) return math.min(d.col, line_length - 1) > position[2] end        else          sort_diagnostics = function(a, b) return a.col > b.col end -        is_next = function(diagnostic) return diagnostic.col < position[2] end +        is_next = function(d) return math.min(d.col, line_length - 1) < position[2] end        end        table.sort(line_diagnostics[lnum], sort_diagnostics)        if i == 0 then @@ -481,10 +527,6 @@ local function diagnostic_move_pos(opts, pos)    end  end --- }}} - --- Public API {{{ -  --- Configure diagnostic options globally or for a specific diagnostic  --- namespace.  --- @@ -618,19 +660,8 @@ function M.set(namespace, bufnr, diagnostics, opts)    }    if vim.tbl_isempty(diagnostics) then -    clear_diagnostic_cache(namespace, bufnr) +    diagnostic_cache[bufnr][namespace] = nil    else -    if not diagnostic_cleanup[bufnr][namespace] then -      diagnostic_cleanup[bufnr][namespace] = true - -      -- Clean up our data when the buffer unloads. -      vim.api.nvim_buf_attach(bufnr, false, { -        on_detach = function(_, b) -          clear_diagnostic_cache(namespace, b) -          diagnostic_cleanup[b][namespace] = nil -        end -      }) -    end      set_diagnostic_cache(namespace, bufnr, diagnostics)    end @@ -689,49 +720,7 @@ function M.get(bufnr, opts)      opts = { opts, 't', true },    } -  opts = opts or {} - -  local namespace = opts.namespace -  local diagnostics = {} - -  ---@private -  local function add(d) -    if not opts.lnum or d.lnum == opts.lnum then -      table.insert(diagnostics, d) -    end -  end - -  if namespace == nil and bufnr == nil then -    for _, t in pairs(diagnostic_cache) do -      for _, v in pairs(t) do -        for _, diagnostic in pairs(v) do -          add(diagnostic) -        end -      end -    end -  elseif namespace == nil then -    for iter_namespace in pairs(diagnostic_cache[bufnr]) do -      for _, diagnostic in pairs(diagnostic_cache[bufnr][iter_namespace]) do -        add(diagnostic) -      end -    end -  elseif bufnr == nil then -    for _, t in pairs(diagnostic_cache) do -      for _, diagnostic in pairs(t[namespace] or {}) do -        add(diagnostic) -      end -    end -  else -    for _, diagnostic in pairs(diagnostic_cache[bufnr][namespace] or {}) do -      add(diagnostic) -    end -  end - -  if opts.severity then -    diagnostics = filter_by_severity(opts.severity, diagnostics) -  end - -  return diagnostics +  return get_diagnostics(bufnr, opts, false)  end  --- Get the previous diagnostic closest to the cursor position. @@ -1115,7 +1104,7 @@ function M.show(namespace, bufnr, diagnostics, opts)    M.hide(namespace, bufnr) -  diagnostics = diagnostics or M.get(bufnr, {namespace=namespace}) +  diagnostics = diagnostics or get_diagnostics(bufnr, {namespace=namespace}, true)    if not diagnostics or vim.tbl_isempty(diagnostics) then      return @@ -1141,8 +1130,6 @@ function M.show(namespace, bufnr, diagnostics, opts)      end    end -  clamp_line_numbers(bufnr, diagnostics) -    for handler_name, handler in pairs(M.handlers) do      if handler.show and opts[handler_name] then        handler.show(namespace, bufnr, diagnostics, opts) @@ -1213,8 +1200,7 @@ function M.open_float(bufnr, opts)      opts = get_resolved_options({ float = float_opts }, nil, bufnr).float    end -  local diagnostics = M.get(bufnr, opts) -  clamp_line_numbers(bufnr, diagnostics) +  local diagnostics = get_diagnostics(bufnr, opts, true)    if scope == "line" then      diagnostics = vim.tbl_filter(function(d) @@ -1338,7 +1324,7 @@ function M.reset(namespace, bufnr)    for _, iter_bufnr in ipairs(buffers) do      local namespaces = namespace and {namespace} or vim.tbl_keys(diagnostic_cache[iter_bufnr])      for _, iter_namespace in ipairs(namespaces) do -      clear_diagnostic_cache(iter_namespace, iter_bufnr) +      diagnostic_cache[iter_bufnr][iter_namespace] = nil        M.hide(iter_namespace, iter_bufnr)      end    end @@ -1563,6 +1549,4 @@ function M.fromqflist(list)    return diagnostics  end --- }}} -  return M diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 88dcb38dd1..7433e7c04d 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -115,6 +115,13 @@ local format_line_ending = {    ["mac"] = '\r',  } +---@private +---@param bufnr (number) +---@returns (string) +local function buf_get_line_ending(bufnr) +  return format_line_ending[nvim_buf_get_option(bufnr, 'fileformat')] or '\n' +end +  local client_index = 0  ---@private  --- Returns a new, unused client id. @@ -236,7 +243,6 @@ local function validate_client_config(config)      config = { config, 't' };    }    validate { -    root_dir        = { config.root_dir, optional_validator(is_dir), "directory" };      handlers        = { config.handlers, "t", true };      capabilities    = { config.capabilities, "t", true };      cmd_cwd         = { config.cmd_cwd, optional_validator(is_dir), "directory" }; @@ -278,9 +284,10 @@ end  ---@param bufnr (number) Buffer handle, or 0 for current.  ---@returns Buffer text as string.  local function buf_get_full_text(bufnr) -  local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), '\n') +  local line_ending = buf_get_line_ending(bufnr) +  local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending)    if nvim_buf_get_option(bufnr, 'eol') then -    text = text .. '\n' +    text = text .. line_ending    end    return text  end @@ -362,9 +369,9 @@ do      local incremental_changes = function(client)        local cached_buffers = state_by_client[client.id].buffers        local curr_lines = nvim_buf_get_lines(bufnr, 0, -1, true) -      local line_ending = format_line_ending[vim.api.nvim_buf_get_option(0, 'fileformat')] +      local line_ending = buf_get_line_ending(bufnr)        local incremental_change = sync.compute_diff( -        cached_buffers[bufnr], curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending or '\n') +        cached_buffers[bufnr], curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending)        cached_buffers[bufnr] = curr_lines        return incremental_change      end @@ -566,12 +573,10 @@ end  --  --- Starts and initializes a client with the given configuration.  --- ---- Parameters `cmd` and `root_dir` are required. +--- Parameter `cmd` is required.  ---  --- The following parameters describe fields in the {config} table.  --- ----@param root_dir: (string) Directory where the LSP server will base ---- its rootUri on initialization.  ---  ---@param cmd: (required, string or list treated like |jobstart()|) Base command  --- that initiates the LSP client. @@ -587,6 +592,11 @@ end  --- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; }  --- </pre>  --- +---@param workspace_folders (table) List of workspace folders passed to the +--- language server. For backwards compatibility rootUri and rootPath will be +--- derived from the first workspace folder in this list. See `workspaceFolders` in +--- the LSP spec. +---  ---@param capabilities Map overriding the default capabilities defined by  --- |vim.lsp.protocol.make_client_capabilities()|, passed to the language  --- server on initialization. Hint: use make_client_capabilities() and modify @@ -610,10 +620,6 @@ end  --- as `initializationOptions`. See `initialize` in the LSP spec.  ---  ---@param name (string, default=client-id) Name in log messages. --- ----@param workspace_folders (table) List of workspace folders passed to the ---- language server. Defaults to root_dir if not set. See `workspaceFolders` in ---- the LSP spec  ---  ---@param get_language_id function(bufnr, filetype) -> language ID as string.  --- Defaults to the filetype. @@ -663,6 +669,11 @@ end  --- - exit_timeout (number, default 500): Milliseconds to wait for server to  --        exit cleanly after sending the 'shutdown' request before sending kill -15.  --        If set to false, nvim exits immediately after sending the 'shutdown' request to the server. +--- +---@param root_dir string Directory where the LSP +--- server will base its workspaceFolders, rootUri, and rootPath +--- on initialization. +---  ---@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be  --- fully initialized. Use `on_init` to do any actions once  --- the client has been initialized. @@ -805,11 +816,24 @@ function lsp.start_client(config)      }      local version = vim.version() -    if config.root_dir and not config.workspace_folders then -      config.workspace_folders = {{ -        uri = vim.uri_from_fname(config.root_dir); -        name = string.format("%s", config.root_dir); -      }}; +    local workspace_folders +    local root_uri +    local root_path +    if config.workspace_folders or config.root_dir then +      if config.root_dir and not config.workspace_folders then +        workspace_folders = {{ +          uri = vim.uri_from_fname(config.root_dir); +          name = string.format("%s", config.root_dir); +        }}; +      else +        workspace_folders = config.workspace_folders +      end +      root_uri = workspace_folders[1].uri +      root_path = vim.uri_to_fname(root_uri) +    else +      workspace_folders = vim.NIL +      root_uri = vim.NIL +      root_path = vim.NIL      end      local initialize_params = { @@ -827,10 +851,15 @@ function lsp.start_client(config)        -- The rootPath of the workspace. Is null if no folder is open.        --        -- @deprecated in favour of rootUri. -      rootPath = config.root_dir; +      rootPath = root_path;        -- The rootUri of the workspace. Is null if no folder is open. If both        -- `rootPath` and `rootUri` are set `rootUri` wins. -      rootUri = config.root_dir and vim.uri_from_fname(config.root_dir); +      rootUri = root_uri; +      -- The workspace folders configured in the client when the server starts. +      -- This property is only available if the client supports workspace folders. +      -- It can be `null` if the client supports workspace folders but none are +      -- configured. +      workspaceFolders = workspace_folders;        -- User provided initialization options.        initializationOptions = config.init_options;        -- The capabilities provided by the client (editor or tool) @@ -838,21 +867,6 @@ function lsp.start_client(config)        -- The initial trace setting. If omitted trace is disabled ("off").        -- trace = "off" | "messages" | "verbose";        trace = valid_traces[config.trace] or 'off'; -      -- The workspace folders configured in the client when the server starts. -      -- This property is only available if the client supports workspace folders. -      -- It can be `null` if the client supports workspace folders but none are -      -- configured. -      -- -      -- Since 3.6.0 -      -- workspaceFolders?: WorkspaceFolder[] | null; -      -- export interface WorkspaceFolder { -      --  -- The associated URI for this workspace folder. -      --  uri -      --  -- The name of the workspace folder. Used to refer to this -      --  -- workspace folder in the user interface. -      --  name -      -- } -      workspaceFolders = config.workspace_folders,      }      if config.before_init then        -- TODO(ashkan) handle errors here. diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 1e6f83c1ba..76a4dc30b7 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -717,5 +717,3 @@ end  -- }}}  return M - --- vim: fdm=marker  | 
