mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	feat(lsp): workspace diagnostic support (#34262)
* refactor(lsp): remove underscore prefix from local variables * feat(lsp): workspace diagnostic support
This commit is contained in:
		 Maria José Solano
					Maria José Solano
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							d75ffa5934
						
					
				
				
					commit
					cb4559bc32
				
			| @@ -1855,6 +1855,18 @@ typehierarchy({kind})                            *vim.lsp.buf.typehierarchy()* | |||||||
|     Parameters: ~ |     Parameters: ~ | ||||||
|       • {kind}  (`"subtypes"|"supertypes"`) |       • {kind}  (`"subtypes"|"supertypes"`) | ||||||
|  |  | ||||||
|  | workspace_diagnostics({opts})            *vim.lsp.buf.workspace_diagnostics()* | ||||||
|  |     Request workspace-wide diagnostics. | ||||||
|  |  | ||||||
|  |     Parameters: ~ | ||||||
|  |       • {opts}  (`table?`) A table with the following fields: | ||||||
|  |                 • {client_id}? (`integer`) Only request diagnostics from the | ||||||
|  |                   indicated client. If nil, the request is sent to all | ||||||
|  |                   clients. | ||||||
|  |  | ||||||
|  |     See also: ~ | ||||||
|  |       • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_dagnostics | ||||||
|  |  | ||||||
| workspace_symbol({query}, {opts})             *vim.lsp.buf.workspace_symbol()* | workspace_symbol({query}, {opts})             *vim.lsp.buf.workspace_symbol()* | ||||||
|     Lists all symbols in the current workspace in the quickfix window. |     Lists all symbols in the current workspace in the quickfix window. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -165,6 +165,8 @@ LSP | |||||||
|   non-applicable LSP clients. |   non-applicable LSP clients. | ||||||
| • |vim.lsp.is_enabled()| checks if a LSP config is enabled (without | • |vim.lsp.is_enabled()| checks if a LSP config is enabled (without | ||||||
|   "resolving" it). |   "resolving" it). | ||||||
|  | • Support for `workspace/diagnostic`: |vim.lsp.buf.workspace_diagnostics()| | ||||||
|  |   https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_dagnostics | ||||||
|  |  | ||||||
| LUA | LUA | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1015,6 +1015,21 @@ function M.workspace_symbol(query, opts) | |||||||
|   request_with_opts(ms.workspace_symbol, params, opts) |   request_with_opts(ms.workspace_symbol, params, opts) | ||||||
| end | end | ||||||
|  |  | ||||||
|  | --- @class vim.lsp.WorkspaceDiagnosticsOpts | ||||||
|  | --- @inlinedoc | ||||||
|  | --- | ||||||
|  | --- Only request diagnostics from the indicated client. If nil, the request is sent to all clients. | ||||||
|  | --- @field client_id? integer | ||||||
|  |  | ||||||
|  | --- Request workspace-wide diagnostics. | ||||||
|  | --- @param opts? vim.lsp.WorkspaceDiagnosticsOpts | ||||||
|  | --- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_dagnostics | ||||||
|  | function M.workspace_diagnostics(opts) | ||||||
|  |   vim.validate('opts', opts, 'table', true) | ||||||
|  |  | ||||||
|  |   lsp.diagnostic._workspace_diagnostics(opts or {}) | ||||||
|  | end | ||||||
|  |  | ||||||
| --- Send request to the server to resolve document highlights for the current | --- Send request to the server to resolve document highlights for the current | ||||||
| --- text document position. This request can be triggered by a  key mapping or | --- text document position. This request can be triggered by a  key mapping or | ||||||
| --- by events such as `CursorHold`, e.g.: | --- by events such as `CursorHold`, e.g.: | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| local protocol = require('vim.lsp.protocol') | local lsp = vim.lsp | ||||||
|  | local protocol = lsp.protocol | ||||||
| local ms = protocol.Methods | local ms = protocol.Methods | ||||||
| local util = vim.lsp.util | local util = lsp.util | ||||||
|  |  | ||||||
| local api = vim.api | local api = vim.api | ||||||
|  |  | ||||||
| @@ -9,10 +10,10 @@ local M = {} | |||||||
| local augroup = api.nvim_create_augroup('nvim.lsp.diagnostic', {}) | local augroup = api.nvim_create_augroup('nvim.lsp.diagnostic', {}) | ||||||
|  |  | ||||||
| ---@class (private) vim.lsp.diagnostic.BufState | ---@class (private) vim.lsp.diagnostic.BufState | ||||||
| ---@field enabled boolean Whether diagnostics are enabled for this buffer | ---@field pull_kind 'document'|'workspace'|'disabled' Whether diagnostics are being updated via document pull, workspace pull, or disabled. | ||||||
| ---@field client_result_id table<integer, string?> Latest responded `resultId` | ---@field client_result_id table<integer, string?> Latest responded `resultId` | ||||||
|  |  | ||||||
| ---@type table<integer,vim.lsp.diagnostic.BufState> | ---@type table<integer, vim.lsp.diagnostic.BufState> | ||||||
| local bufstates = {} | local bufstates = {} | ||||||
|  |  | ||||||
| local DEFAULT_CLIENT_ID = -1 | local DEFAULT_CLIENT_ID = -1 | ||||||
| @@ -38,11 +39,11 @@ end | |||||||
| ---@param bufnr integer | ---@param bufnr integer | ||||||
| ---@return string[]? | ---@return string[]? | ||||||
| local function get_buf_lines(bufnr) | local function get_buf_lines(bufnr) | ||||||
|   if vim.api.nvim_buf_is_loaded(bufnr) then |   if api.nvim_buf_is_loaded(bufnr) then | ||||||
|     return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) |     return api.nvim_buf_get_lines(bufnr, 0, -1, false) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   local filename = vim.api.nvim_buf_get_name(bufnr) |   local filename = api.nvim_buf_get_name(bufnr) | ||||||
|   local f = io.open(filename) |   local f = io.open(filename) | ||||||
|   if not f then |   if not f then | ||||||
|     return |     return | ||||||
| @@ -74,7 +75,7 @@ local function tags_lsp_to_vim(diagnostic, client_id) | |||||||
|       tags = tags or {} |       tags = tags or {} | ||||||
|       tags.deprecated = true |       tags.deprecated = true | ||||||
|     else |     else | ||||||
|       vim.lsp.log.info(string.format('Unknown DiagnosticTag %d from LSP client %d', tag, client_id)) |       lsp.log.info(string.format('Unknown DiagnosticTag %d from LSP client %d', tag, client_id)) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|   return tags |   return tags | ||||||
| @@ -86,7 +87,7 @@ end | |||||||
| ---@return vim.Diagnostic.Set[] | ---@return vim.Diagnostic.Set[] | ||||||
| local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) | local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) | ||||||
|   local buf_lines = get_buf_lines(bufnr) |   local buf_lines = get_buf_lines(bufnr) | ||||||
|   local client = vim.lsp.get_client_by_id(client_id) |   local client = lsp.get_client_by_id(client_id) | ||||||
|   local position_encoding = client and client.offset_encoding or 'utf-16' |   local position_encoding = client and client.offset_encoding or 'utf-16' | ||||||
|   --- @param diagnostic lsp.Diagnostic |   --- @param diagnostic lsp.Diagnostic | ||||||
|   --- @return vim.Diagnostic.Set |   --- @return vim.Diagnostic.Set | ||||||
| @@ -97,7 +98,7 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) | |||||||
|     if type(message) ~= 'string' then |     if type(message) ~= 'string' then | ||||||
|       vim.notify_once( |       vim.notify_once( | ||||||
|         string.format('Unsupported Markup message from LSP client %d', client_id), |         string.format('Unsupported Markup message from LSP client %d', client_id), | ||||||
|         vim.lsp.log_levels.ERROR |         lsp.log_levels.ERROR | ||||||
|       ) |       ) | ||||||
|       --- @diagnostic disable-next-line: undefined-field,no-unknown |       --- @diagnostic disable-next-line: undefined-field,no-unknown | ||||||
|       message = diagnostic.message.value |       message = diagnostic.message.value | ||||||
| @@ -174,10 +175,10 @@ function M.from(diagnostics) | |||||||
| end | end | ||||||
|  |  | ||||||
| ---@type table<integer, integer> | ---@type table<integer, integer> | ||||||
| local _client_push_namespaces = {} | local client_push_namespaces = {} | ||||||
|  |  | ||||||
| ---@type table<string, integer> | ---@type table<string, integer> | ||||||
| local _client_pull_namespaces = {} | local client_pull_namespaces = {} | ||||||
|  |  | ||||||
| --- Get the diagnostic namespace associated with an LSP client |vim.diagnostic| for diagnostics | --- Get the diagnostic namespace associated with an LSP client |vim.diagnostic| for diagnostics | ||||||
| --- | --- | ||||||
| @@ -186,7 +187,7 @@ local _client_pull_namespaces = {} | |||||||
| function M.get_namespace(client_id, is_pull) | function M.get_namespace(client_id, is_pull) | ||||||
|   vim.validate('client_id', client_id, 'number') |   vim.validate('client_id', client_id, 'number') | ||||||
|  |  | ||||||
|   local client = vim.lsp.get_client_by_id(client_id) |   local client = lsp.get_client_by_id(client_id) | ||||||
|   if is_pull then |   if is_pull then | ||||||
|     local server_id = |     local server_id = | ||||||
|       vim.tbl_get((client or {}).server_capabilities or {}, 'diagnosticProvider', 'identifier') |       vim.tbl_get((client or {}).server_capabilities or {}, 'diagnosticProvider', 'identifier') | ||||||
| @@ -196,19 +197,19 @@ function M.get_namespace(client_id, is_pull) | |||||||
|       client_id, |       client_id, | ||||||
|       server_id or 'nil' |       server_id or 'nil' | ||||||
|     ) |     ) | ||||||
|     local ns = _client_pull_namespaces[key] |     local ns = client_pull_namespaces[key] | ||||||
|     if not ns then |     if not ns then | ||||||
|       ns = api.nvim_create_namespace(name) |       ns = api.nvim_create_namespace(name) | ||||||
|       _client_pull_namespaces[key] = ns |       client_pull_namespaces[key] = ns | ||||||
|     end |     end | ||||||
|     return ns |     return ns | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   local ns = _client_push_namespaces[client_id] |   local ns = client_push_namespaces[client_id] | ||||||
|   if not ns then |   if not ns then | ||||||
|     local name = ('nvim.lsp.%s.%d'):format(client and client.name or 'unknown', client_id) |     local name = ('nvim.lsp.%s.%d'):format(client and client.name or 'unknown', client_id) | ||||||
|     ns = api.nvim_create_namespace(name) |     ns = api.nvim_create_namespace(name) | ||||||
|     _client_push_namespaces[client_id] = ns |     client_push_namespaces[client_id] = ns | ||||||
|   end |   end | ||||||
|   return ns |   return ns | ||||||
| end | end | ||||||
| @@ -257,7 +258,7 @@ end | |||||||
| function M.on_diagnostic(error, result, ctx) | function M.on_diagnostic(error, result, ctx) | ||||||
|   if error ~= nil and error.code == protocol.ErrorCodes.ServerCancelled then |   if error ~= nil and error.code == protocol.ErrorCodes.ServerCancelled then | ||||||
|     if error.data == nil or error.data.retriggerRequest ~= false then |     if error.data == nil or error.data.retriggerRequest ~= false then | ||||||
|       local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) |       local client = assert(lsp.get_client_by_id(ctx.client_id)) | ||||||
|       client:request(ctx.method, ctx.params) |       client:request(ctx.method, ctx.params) | ||||||
|     end |     end | ||||||
|     return |     return | ||||||
| @@ -271,7 +272,7 @@ function M.on_diagnostic(error, result, ctx) | |||||||
|   handle_diagnostics(ctx.params.textDocument.uri, client_id, result.items, true) |   handle_diagnostics(ctx.params.textDocument.uri, client_id, result.items, true) | ||||||
|  |  | ||||||
|   local bufnr = assert(ctx.bufnr) |   local bufnr = assert(ctx.bufnr) | ||||||
|   local bufstate = assert(bufstates[bufnr]) |   local bufstate = bufstates[bufnr] | ||||||
|   bufstate.client_result_id[client_id] = result.resultId |   bufstate.client_result_id[client_id] = result.resultId | ||||||
| end | end | ||||||
|  |  | ||||||
| @@ -329,7 +330,7 @@ end | |||||||
|  |  | ||||||
| --- Clear diagnostics from pull based clients | --- Clear diagnostics from pull based clients | ||||||
| local function clear(bufnr) | local function clear(bufnr) | ||||||
|   for _, namespace in pairs(_client_pull_namespaces) do |   for _, namespace in pairs(client_pull_namespaces) do | ||||||
|     vim.diagnostic.reset(namespace, bufnr) |     vim.diagnostic.reset(namespace, bufnr) | ||||||
|   end |   end | ||||||
| end | end | ||||||
| @@ -339,7 +340,7 @@ end | |||||||
| local function disable(bufnr) | local function disable(bufnr) | ||||||
|   local bufstate = bufstates[bufnr] |   local bufstate = bufstates[bufnr] | ||||||
|   if bufstate then |   if bufstate then | ||||||
|     bufstate.enabled = false |     bufstate.pull_kind = 'disabled' | ||||||
|   end |   end | ||||||
|   clear(bufnr) |   clear(bufnr) | ||||||
| end | end | ||||||
| @@ -348,7 +349,7 @@ end | |||||||
| ---@param bufnr integer buffer number | ---@param bufnr integer buffer number | ||||||
| ---@param client_id? integer Client ID to refresh (default: all clients) | ---@param client_id? integer Client ID to refresh (default: all clients) | ||||||
| ---@param only_visible? boolean Whether to only refresh for the visible regions of the buffer (default: false) | ---@param only_visible? boolean Whether to only refresh for the visible regions of the buffer (default: false) | ||||||
| local function _refresh(bufnr, client_id, only_visible) | local function refresh(bufnr, client_id, only_visible) | ||||||
|   if |   if | ||||||
|     only_visible |     only_visible | ||||||
|     and vim.iter(api.nvim_list_wins()):all(function(window) |     and vim.iter(api.nvim_list_wins()):all(function(window) | ||||||
| @@ -359,8 +360,8 @@ local function _refresh(bufnr, client_id, only_visible) | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   local method = ms.textDocument_diagnostic |   local method = ms.textDocument_diagnostic | ||||||
|   local clients = vim.lsp.get_clients({ bufnr = bufnr, method = method, id = client_id }) |   local clients = lsp.get_clients({ bufnr = bufnr, method = method, id = client_id }) | ||||||
|   local bufstate = assert(bufstates[bufnr]) |   local bufstate = bufstates[bufnr] | ||||||
|  |  | ||||||
|   util._cancel_requests({ |   util._cancel_requests({ | ||||||
|     bufnr = bufnr, |     bufnr = bufnr, | ||||||
| @@ -383,54 +384,130 @@ end | |||||||
| function M._enable(bufnr) | function M._enable(bufnr) | ||||||
|   bufnr = vim._resolve_bufnr(bufnr) |   bufnr = vim._resolve_bufnr(bufnr) | ||||||
|  |  | ||||||
|   if not bufstates[bufnr] then |   if bufstates[bufnr] then | ||||||
|     bufstates[bufnr] = { enabled = true, client_result_id = {} } |     -- If we're already pulling diagnostics for this buffer, nothing to do here. | ||||||
|  |     if bufstates[bufnr].pull_kind == 'document' then | ||||||
|     api.nvim_create_autocmd('LspNotify', { |       return | ||||||
|       buffer = bufnr, |     end | ||||||
|       callback = function(opts) |     -- Else diagnostics were disabled or we were using workspace diagnostics. | ||||||
|         if |     bufstates[bufnr].pull_kind = 'document' | ||||||
|           opts.data.method ~= ms.textDocument_didChange |  | ||||||
|           and opts.data.method ~= ms.textDocument_didOpen |  | ||||||
|         then |  | ||||||
|           return |  | ||||||
|         end |  | ||||||
|         if bufstates[bufnr] and bufstates[bufnr].enabled then |  | ||||||
|           local client_id = opts.data.client_id --- @type integer? |  | ||||||
|           _refresh(bufnr, client_id, true) |  | ||||||
|         end |  | ||||||
|       end, |  | ||||||
|       group = augroup, |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     api.nvim_buf_attach(bufnr, false, { |  | ||||||
|       on_reload = function() |  | ||||||
|         if bufstates[bufnr] and bufstates[bufnr].enabled then |  | ||||||
|           _refresh(bufnr) |  | ||||||
|         end |  | ||||||
|       end, |  | ||||||
|       on_detach = function() |  | ||||||
|         disable(bufnr) |  | ||||||
|       end, |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     api.nvim_create_autocmd('LspDetach', { |  | ||||||
|       buffer = bufnr, |  | ||||||
|       callback = function(args) |  | ||||||
|         local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_diagnostic }) |  | ||||||
|  |  | ||||||
|         if |  | ||||||
|           not vim.iter(clients):any(function(c) |  | ||||||
|             return c.id ~= args.data.client_id |  | ||||||
|           end) |  | ||||||
|         then |  | ||||||
|           disable(bufnr) |  | ||||||
|         end |  | ||||||
|       end, |  | ||||||
|       group = augroup, |  | ||||||
|     }) |  | ||||||
|   else |   else | ||||||
|     bufstates[bufnr].enabled = true |     bufstates[bufnr] = { pull_kind = 'document', client_result_id = {} } | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   api.nvim_create_autocmd('LspNotify', { | ||||||
|  |     buffer = bufnr, | ||||||
|  |     callback = function(opts) | ||||||
|  |       if | ||||||
|  |         opts.data.method ~= ms.textDocument_didChange | ||||||
|  |         and opts.data.method ~= ms.textDocument_didOpen | ||||||
|  |       then | ||||||
|  |         return | ||||||
|  |       end | ||||||
|  |       if bufstates[bufnr] and bufstates[bufnr].pull_kind == 'document' then | ||||||
|  |         local client_id = opts.data.client_id --- @type integer? | ||||||
|  |         refresh(bufnr, client_id, true) | ||||||
|  |       end | ||||||
|  |     end, | ||||||
|  |     group = augroup, | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   api.nvim_buf_attach(bufnr, false, { | ||||||
|  |     on_reload = function() | ||||||
|  |       if bufstates[bufnr] and bufstates[bufnr].pull_kind == 'document' then | ||||||
|  |         refresh(bufnr) | ||||||
|  |       end | ||||||
|  |     end, | ||||||
|  |     on_detach = function() | ||||||
|  |       disable(bufnr) | ||||||
|  |     end, | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   api.nvim_create_autocmd('LspDetach', { | ||||||
|  |     buffer = bufnr, | ||||||
|  |     callback = function(args) | ||||||
|  |       local clients = lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_diagnostic }) | ||||||
|  |  | ||||||
|  |       if | ||||||
|  |         not vim.iter(clients):any(function(c) | ||||||
|  |           return c.id ~= args.data.client_id | ||||||
|  |         end) | ||||||
|  |       then | ||||||
|  |         disable(bufnr) | ||||||
|  |       end | ||||||
|  |     end, | ||||||
|  |     group = augroup, | ||||||
|  |   }) | ||||||
|  | end | ||||||
|  |  | ||||||
|  | --- Returns the result IDs from the reports provided by the given client. | ||||||
|  | --- @return lsp.PreviousResultId[] | ||||||
|  | local function previous_result_ids(client_id) | ||||||
|  |   local results = {} | ||||||
|  |  | ||||||
|  |   for bufnr, state in pairs(bufstates) do | ||||||
|  |     if state.pull_kind ~= 'disabled' then | ||||||
|  |       for buf_client_id, result_id in pairs(state.client_result_id) do | ||||||
|  |         if buf_client_id == client_id then | ||||||
|  |           table.insert(results, { | ||||||
|  |             textDocument = util.make_text_document_params(bufnr), | ||||||
|  |             previousResultId = result_id, | ||||||
|  |           }) | ||||||
|  |           break | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   return results | ||||||
|  | end | ||||||
|  |  | ||||||
|  | --- Request workspace-wide diagnostics. | ||||||
|  | --- @param opts vim.lsp.WorkspaceDiagnosticsOpts | ||||||
|  | function M._workspace_diagnostics(opts) | ||||||
|  |   local clients = lsp.get_clients({ method = ms.workspace_diagnostic, id = opts.client_id }) | ||||||
|  |  | ||||||
|  |   --- @param error lsp.ResponseError? | ||||||
|  |   --- @param result lsp.WorkspaceDiagnosticReport | ||||||
|  |   --- @param ctx lsp.HandlerContext | ||||||
|  |   local function handler(error, result, ctx) | ||||||
|  |     -- Check for retrigger requests on cancellation errors. | ||||||
|  |     -- Unless `retriggerRequest` is explicitly disabled, try again. | ||||||
|  |     if error ~= nil and error.code == lsp.protocol.ErrorCodes.ServerCancelled then | ||||||
|  |       if error.data == nil or error.data.retriggerRequest ~= false then | ||||||
|  |         local client = assert(lsp.get_client_by_id(ctx.client_id)) | ||||||
|  |         client:request(ms.workspace_diagnostic, ctx.params, handler) | ||||||
|  |       end | ||||||
|  |       return | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     if error == nil and result ~= nil then | ||||||
|  |       for _, report in ipairs(result.items) do | ||||||
|  |         local bufnr = vim.uri_to_bufnr(report.uri) | ||||||
|  |  | ||||||
|  |         -- Start tracking the buffer (but don't send "textDocument/diagnostic" requests for it). | ||||||
|  |         if not bufstates[bufnr] then | ||||||
|  |           bufstates[bufnr] = { pull_kind = 'workspace', client_result_id = {} } | ||||||
|  |         end | ||||||
|  |  | ||||||
|  |         -- We favor document pull requests over workspace results, so only update the buffer | ||||||
|  |         -- state if we're not pulling document diagnostics for this buffer. | ||||||
|  |         if bufstates[bufnr].pull_kind == 'workspace' and report.kind == 'full' then | ||||||
|  |           handle_diagnostics(report.uri, ctx.client_id, report.items, true) | ||||||
|  |           bufstates[bufnr].client_result_id[ctx.client_id] = report.resultId | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   for _, client in ipairs(clients) do | ||||||
|  |     --- @type lsp.WorkspaceDiagnosticParams | ||||||
|  |     local params = { | ||||||
|  |       identifier = vim.tbl_get(client, 'server_capabilities, diagnosticProvider', 'identifier'), | ||||||
|  |       previousResultIds = previous_result_ids(client.id), | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     client:request(ms.workspace_diagnostic, params, handler) | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|   | |||||||
| @@ -566,6 +566,9 @@ function protocol.make_client_capabilities() | |||||||
|       inlayHint = { |       inlayHint = { | ||||||
|         refreshSupport = true, |         refreshSupport = true, | ||||||
|       }, |       }, | ||||||
|  |       workspace = { | ||||||
|  |         refreshSupport = false, | ||||||
|  |       }, | ||||||
|     }, |     }, | ||||||
|     experimental = nil, |     experimental = nil, | ||||||
|     window = { |     window = { | ||||||
|   | |||||||
| @@ -6801,4 +6801,128 @@ describe('LSP', function() | |||||||
|       eq(false, exec_lua([[return vim.lsp.is_enabled('foo')]])) |       eq(false, exec_lua([[return vim.lsp.is_enabled('foo')]])) | ||||||
|     end) |     end) | ||||||
|   end) |   end) | ||||||
|  |  | ||||||
|  |   describe('vim.lsp.buf.workspace_diagnostics()', function() | ||||||
|  |     local fake_uri = 'file:///fake/uri' | ||||||
|  |  | ||||||
|  |     --- @param kind lsp.DocumentDiagnosticReportKind | ||||||
|  |     --- @param msg string | ||||||
|  |     --- @param pos integer | ||||||
|  |     --- @return lsp.WorkspaceDocumentDiagnosticReport | ||||||
|  |     local function make_report(kind, msg, pos) | ||||||
|  |       return { | ||||||
|  |         kind = kind, | ||||||
|  |         uri = fake_uri, | ||||||
|  |         items = { | ||||||
|  |           { | ||||||
|  |             range = { | ||||||
|  |               start = { line = pos, character = pos }, | ||||||
|  |               ['end'] = { line = pos, character = pos }, | ||||||
|  |             }, | ||||||
|  |             message = msg, | ||||||
|  |             severity = 1, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       } | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     --- @param items lsp.WorkspaceDocumentDiagnosticReport[] | ||||||
|  |     --- @return integer | ||||||
|  |     local function setup_server(items) | ||||||
|  |       exec_lua(create_server_definition) | ||||||
|  |       return exec_lua(function() | ||||||
|  |         _G.server = _G._create_server({ | ||||||
|  |           capabilities = { | ||||||
|  |             diagnosticProvider = { workspaceDiagnostics = true }, | ||||||
|  |           }, | ||||||
|  |           handlers = { | ||||||
|  |             ['workspace/diagnostic'] = function(_, _, callback) | ||||||
|  |               callback(nil, { items = items }) | ||||||
|  |             end, | ||||||
|  |           }, | ||||||
|  |         }) | ||||||
|  |         local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })) | ||||||
|  |         vim.lsp.buf.workspace_diagnostics() | ||||||
|  |         return client_id | ||||||
|  |       end, { items }) | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     it('updates diagnostics obtained with vim.diagnostic.get()', function() | ||||||
|  |       setup_server({ make_report('full', 'Error here', 1) }) | ||||||
|  |  | ||||||
|  |       retry(nil, nil, function() | ||||||
|  |         eq( | ||||||
|  |           1, | ||||||
|  |           exec_lua(function() | ||||||
|  |             return #vim.diagnostic.get() | ||||||
|  |           end) | ||||||
|  |         ) | ||||||
|  |       end) | ||||||
|  |  | ||||||
|  |       eq( | ||||||
|  |         'Error here', | ||||||
|  |         exec_lua(function() | ||||||
|  |           return vim.diagnostic.get()[1].message | ||||||
|  |         end) | ||||||
|  |       ) | ||||||
|  |     end) | ||||||
|  |  | ||||||
|  |     it('ignores unchanged diagnostic reports', function() | ||||||
|  |       setup_server({ make_report('unchanged', '', 1) }) | ||||||
|  |  | ||||||
|  |       eq( | ||||||
|  |         0, | ||||||
|  |         exec_lua(function() | ||||||
|  |           -- Wait for diagnostics to be processed. | ||||||
|  |           vim.uv.sleep(50) | ||||||
|  |  | ||||||
|  |           return #vim.diagnostic.get() | ||||||
|  |         end) | ||||||
|  |       ) | ||||||
|  |     end) | ||||||
|  |  | ||||||
|  |     it('favors document diagnostics over workspace diagnostics', function() | ||||||
|  |       local client_id = setup_server({ make_report('full', 'Workspace error', 1) }) | ||||||
|  |       local diagnostic_bufnr = exec_lua(function() | ||||||
|  |         return vim.uri_to_bufnr(fake_uri) | ||||||
|  |       end) | ||||||
|  |  | ||||||
|  |       exec_lua(function() | ||||||
|  |         vim.lsp.diagnostic.on_diagnostic(nil, { | ||||||
|  |           kind = 'full', | ||||||
|  |           items = { | ||||||
|  |             { | ||||||
|  |               range = { | ||||||
|  |                 start = { line = 2, character = 2 }, | ||||||
|  |                 ['end'] = { line = 2, character = 2 }, | ||||||
|  |               }, | ||||||
|  |               message = 'Document error', | ||||||
|  |               severity = 1, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         }, { | ||||||
|  |           method = 'textDocument/diagnostic', | ||||||
|  |           params = { | ||||||
|  |             textDocument = { uri = fake_uri }, | ||||||
|  |           }, | ||||||
|  |           client_id = client_id, | ||||||
|  |           bufnr = diagnostic_bufnr, | ||||||
|  |         }) | ||||||
|  |       end) | ||||||
|  |  | ||||||
|  |       eq( | ||||||
|  |         1, | ||||||
|  |         exec_lua(function() | ||||||
|  |           return #vim.diagnostic.get(diagnostic_bufnr) | ||||||
|  |         end) | ||||||
|  |       ) | ||||||
|  |  | ||||||
|  |       eq( | ||||||
|  |         'Document error', | ||||||
|  |         exec_lua(function() | ||||||
|  |           return vim.diagnostic.get(vim.uri_to_bufnr(fake_uri))[1].message | ||||||
|  |         end) | ||||||
|  |       ) | ||||||
|  |     end) | ||||||
|  |   end) | ||||||
| end) | end) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user