-
-
Notifications
You must be signed in to change notification settings - Fork 624
fix(#2519): Diagnostics Not Updated When Tree Not Visible #2597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
7bc3616
fix(#2519): diagnostics overhaul
iusmac 2baf3da
fix: Properly filter diagnostics from coc
iusmac f30a8f6
Assign diagnostic version per node to reduce overhead
iusmac 7413041
Require renderer once
iusmac e3c90fd
Revert "Require renderer once"
iusmac b8079fb
Rename `buffer_severity_dict` to `BUFFER_SEVERITY`
iusmac e6f343a
Log diagnostics update properly
iusmac a15c8a9
Implement error handling for coc.nvim
iusmac b05f7e3
CI style fixes
iusmac d7bd359
Capture `Keyboard interrupt` when handling coc exceptions
iusmac c37c895
add more doc
alex-courtis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,44 @@ | ||
local utils = require "nvim-tree.utils" | ||
local view = require "nvim-tree.view" | ||
local core = require "nvim-tree.core" | ||
local log = require "nvim-tree.log" | ||
|
||
local M = {} | ||
|
||
local severity_levels = { | ||
---TODO add "$VIMRUNTIME" to "workspace.library" and use the @enum instead of this integer | ||
---@alias lsp.DiagnosticSeverity integer | ||
|
||
---COC severity level strings to LSP severity levels | ||
---@enum COC_SEVERITY_LEVELS | ||
local COC_SEVERITY_LEVELS = { | ||
Error = 1, | ||
Warning = 2, | ||
Information = 3, | ||
Hint = 4, | ||
} | ||
|
||
---@return table | ||
---Absolute Node path to LSP severity level | ||
---@alias NodeSeverities table<string, lsp.DiagnosticSeverity> | ||
|
||
---@class DiagStatus | ||
---@field value lsp.DiagnosticSeverity|nil | ||
---@field cache_version integer | ||
|
||
--- The buffer-severity mappings derived during the last diagnostic list update. | ||
---@type NodeSeverities | ||
local NODE_SEVERITIES = {} | ||
|
||
---The cache version number of the buffer-severity mappings. | ||
---@type integer | ||
local NODE_SEVERITIES_VERSION = 0 | ||
|
||
---@param path string | ||
---@return string | ||
local function uniformize_path(path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice name! |
||
return utils.canonical_path(path:gsub("\\", "/")) | ||
end | ||
|
||
---Marshal severities from LSP. Does nothing when LSP disabled. | ||
---@return NodeSeverities | ||
local function from_nvim_lsp() | ||
local buffer_severity = {} | ||
|
||
|
@@ -25,103 +51,159 @@ local function from_nvim_lsp() | |
for _, diagnostic in ipairs(vim.diagnostic.get(nil, { severity = M.severity })) do | ||
local buf = diagnostic.bufnr | ||
if vim.api.nvim_buf_is_valid(buf) then | ||
local bufname = vim.api.nvim_buf_get_name(buf) | ||
local lowest_severity = buffer_severity[bufname] | ||
if not lowest_severity or diagnostic.severity < lowest_severity then | ||
buffer_severity[bufname] = diagnostic.severity | ||
end | ||
local bufname = uniformize_path(vim.api.nvim_buf_get_name(buf)) | ||
local severity = diagnostic.severity | ||
local highest_severity = buffer_severity[bufname] or severity | ||
buffer_severity[bufname] = math.min(highest_severity, severity) | ||
end | ||
end | ||
end | ||
|
||
return buffer_severity | ||
end | ||
|
||
---@param severity integer | ||
---Severity is within diagnostics.severity.min, diagnostics.severity.max | ||
---@param severity lsp.DiagnosticSeverity | ||
---@param config table | ||
---@return boolean | ||
local function is_severity_in_range(severity, config) | ||
return config.max <= severity and severity <= config.min | ||
end | ||
|
||
---@return table | ||
local function from_coc() | ||
if vim.g.coc_service_initialized ~= 1 then | ||
return {} | ||
---Handle any COC exceptions, preventing any propagation | ||
---@param err string | ||
local function handle_coc_exception(err) | ||
log.line("diagnostics", "handle_coc_exception: %s", vim.inspect(err)) | ||
local notify = true | ||
|
||
-- avoid distractions on interrupts (CTRL-C) | ||
if err:find "Vim:Interrupt" or err:find "Keyboard interrupt" then | ||
notify = false | ||
end | ||
|
||
local diagnostic_list = vim.fn.CocAction "diagnosticList" | ||
if type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then | ||
return {} | ||
if notify then | ||
require("nvim-tree.notify").error("Diagnostics update from coc.nvim failed. " .. vim.inspect(err)) | ||
end | ||
end | ||
|
||
local diagnostics = {} | ||
for _, diagnostic in ipairs(diagnostic_list) do | ||
local bufname = diagnostic.file | ||
local coc_severity = severity_levels[diagnostic.severity] | ||
---COC service initialized | ||
---@return boolean | ||
local function is_using_coc() | ||
return vim.g.coc_service_initialized == 1 | ||
end | ||
|
||
local serverity = diagnostics[bufname] or vim.diagnostic.severity.HINT | ||
diagnostics[bufname] = math.min(coc_severity, serverity) | ||
---Marshal severities from COC. Does nothing when COC service not started. | ||
---@return NodeSeverities | ||
local function from_coc() | ||
if not is_using_coc() then | ||
return {} | ||
end | ||
|
||
local ok, diagnostic_list = xpcall(function() | ||
return vim.fn.CocAction "diagnosticList" | ||
end, handle_coc_exception) | ||
if not ok or type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then | ||
return {} | ||
end | ||
|
||
local buffer_severity = {} | ||
for bufname, severity in pairs(diagnostics) do | ||
if is_severity_in_range(severity, M.severity) then | ||
buffer_severity[bufname] = severity | ||
for _, diagnostic in ipairs(diagnostic_list) do | ||
local bufname = uniformize_path(diagnostic.file) | ||
local coc_severity = COC_SEVERITY_LEVELS[diagnostic.severity] | ||
local highest_severity = buffer_severity[bufname] or coc_severity | ||
if is_severity_in_range(highest_severity, M.severity) then | ||
buffer_severity[bufname] = math.min(highest_severity, coc_severity) | ||
end | ||
end | ||
|
||
return buffer_severity | ||
end | ||
|
||
local function is_using_coc() | ||
return vim.g.coc_service_initialized == 1 | ||
---Maybe retrieve severity level from the cache | ||
---@param node Node | ||
---@return DiagStatus | ||
local function from_cache(node) | ||
local nodepath = uniformize_path(node.absolute_path) | ||
local max_severity = nil | ||
if not node.nodes then | ||
-- direct cache hit for files | ||
max_severity = NODE_SEVERITIES[nodepath] | ||
else | ||
-- dirs should be searched in the list of cached buffer names by prefix | ||
for bufname, severity in pairs(NODE_SEVERITIES) do | ||
local node_contains_buf = vim.startswith(bufname, nodepath .. "/") | ||
if node_contains_buf then | ||
if severity == M.severity.max then | ||
max_severity = severity | ||
break | ||
else | ||
max_severity = math.min(max_severity or severity, severity) | ||
end | ||
end | ||
end | ||
end | ||
return { value = max_severity, cache_version = NODE_SEVERITIES_VERSION } | ||
end | ||
|
||
---Fired on DiagnosticChanged and CocDiagnosticChanged events: | ||
---debounced retrieval, cache update, version increment and draw | ||
function M.update() | ||
if not M.enable or not core.get_explorer() or not view.is_buf_valid(view.get_bufnr()) then | ||
if not M.enable then | ||
return | ||
end | ||
utils.debounce("diagnostics", M.debounce_delay, function() | ||
local profile = log.profile_start "diagnostics update" | ||
log.line("diagnostics", "update") | ||
|
||
local buffer_severity | ||
if is_using_coc() then | ||
buffer_severity = from_coc() | ||
NODE_SEVERITIES = from_coc() | ||
else | ||
buffer_severity = from_nvim_lsp() | ||
end | ||
|
||
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line()) | ||
for _, node in pairs(nodes_by_line) do | ||
node.diag_status = nil | ||
NODE_SEVERITIES = from_nvim_lsp() | ||
end | ||
|
||
for bufname, severity in pairs(buffer_severity) do | ||
local bufpath = utils.canonical_path(bufname) | ||
log.line("diagnostics", " bufpath '%s' severity %d", bufpath, severity) | ||
if 0 < severity and severity < 5 then | ||
for line, node in pairs(nodes_by_line) do | ||
local nodepath = utils.canonical_path(node.absolute_path) | ||
log.line("diagnostics", " %d checking nodepath '%s'", line, nodepath) | ||
|
||
local node_contains_buf = vim.startswith(bufpath:gsub("\\", "/"), nodepath:gsub("\\", "/") .. "/") | ||
if M.show_on_dirs and node_contains_buf and (not node.open or M.show_on_open_dirs) then | ||
log.line("diagnostics", " matched fold node '%s'", node.absolute_path) | ||
node.diag_status = severity | ||
elseif nodepath == bufpath then | ||
log.line("diagnostics", " matched file node '%s'", node.absolute_path) | ||
node.diag_status = severity | ||
end | ||
end | ||
NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1 | ||
if log.enabled "diagnostics" then | ||
for bufname, severity in pairs(NODE_SEVERITIES) do | ||
log.line("diagnostics", "Indexing bufname '%s' with severity %d", bufname, severity) | ||
end | ||
end | ||
log.profile_end(profile) | ||
require("nvim-tree.renderer").draw() | ||
if view.is_buf_valid(view.get_bufnr()) then | ||
require("nvim-tree.renderer").draw() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. / nit please require once at the start of the file |
||
end | ||
end) | ||
end | ||
|
||
---Maybe retrieve diagnostic status for a node. | ||
---Returns cached value when node's version matches. | ||
---@param node Node | ||
---@return DiagStatus|nil | ||
function M.get_diag_status(node) | ||
if not M.enable then | ||
return nil | ||
end | ||
|
||
-- dir but we shouldn't show on dirs at all | ||
if node.nodes ~= nil and not M.show_on_dirs then | ||
return nil | ||
end | ||
|
||
-- here, we do a lazy update of the diagnostic status carried by the node. | ||
-- This is by design, as diagnostics and nodes live in completely separate | ||
-- worlds, and this module is the link between the two | ||
if not node.diag_status or node.diag_status.cache_version < NODE_SEVERITIES_VERSION then | ||
node.diag_status = from_cache(node) | ||
end | ||
|
||
-- file | ||
if not node.nodes then | ||
return node.diag_status | ||
end | ||
|
||
-- dir is closed or we should show on open_dirs | ||
if not node.open or M.show_on_open_dirs then | ||
return node.diag_status | ||
end | ||
return nil | ||
end | ||
|
||
function M.setup(opts) | ||
M.enable = opts.diagnostics.enable | ||
M.debounce_delay = opts.diagnostics.debounce_delay | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That one's a work in progress #2546