diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 73bb1329d5..328dec84ea 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -2388,9 +2388,11 @@ enable({enable}, {filter}) *vim.lsp.inlay_hint.enable()* Parameters: ~ • {enable} (`boolean?`) true/nil to enable, false to disable - • {filter} (`table?`) Optional filters |kwargs|, or `nil` for all. - • {bufnr} (`integer?`) Buffer number, or 0 for current - buffer, or nil for all. + • {filter} (`table?`) Optional filters |kwargs|, + • {bufnr}? (`integer`, default: all) Buffer number, or 0 for + current buffer, or nil for all. + • {client_id}? (`integer`, default: all) Client ID, or nil + for all. get({filter}) *vim.lsp.inlay_hint.get()* Get the list of inlay hints, (optionally) restricted by buffer or range. @@ -2434,9 +2436,11 @@ is_enabled({filter}) *vim.lsp.inlay_hint.is_enabled()* Since: 0.10.0 Parameters: ~ - • {filter} (`table?`) Optional filters |kwargs|, or `nil` for all. - • {bufnr} (`integer?`) Buffer number, or 0 for current - buffer, or nil for all. + • {filter} (`table?`) Optional filters |kwargs|, + • {bufnr}? (`integer`, default: all) Buffer number, or 0 for + current buffer, or nil for all. + • {client_id}? (`integer`, default: all) Client ID, or nil + for all. Return: ~ (`boolean`) diff --git a/runtime/lua/vim/lsp/_capability.lua b/runtime/lua/vim/lsp/_capability.lua index d4e0b8f90e..c679700e51 100644 --- a/runtime/lua/vim/lsp/_capability.lua +++ b/runtime/lua/vim/lsp/_capability.lua @@ -5,6 +5,7 @@ local api = vim.api ---| 'diagnostics' ---| 'document_color' ---| 'folding_range' +---| 'inlay_hint' ---| 'inline_completion' ---| 'linked_editing_range' ---| 'semantic_tokens' diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 63f1d34ff2..5b3ea2ae25 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -557,6 +557,7 @@ function Client:initialize() require('vim.lsp._folding_range') require('vim.lsp.diagnostic') require('vim.lsp.document_color') + require('vim.lsp.inlay_hint') require('vim.lsp.inline_completion') require('vim.lsp.linked_editing_range') require('vim.lsp.semantic_tokens') diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 470bbe456f..d5a0b672dd 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -1,37 +1,143 @@ -local util = require('vim.lsp.util') +local api = vim.api local log = require('vim.lsp.log') local nvim_on = require('vim._core.util').nvim_on -local api = vim.api +local util = require('vim.lsp.util') + +local Capability = require('vim.lsp._capability') + local M = {} ----@class (private) vim.lsp.inlay_hint.globalstate Global state for inlay hints ----@field enabled boolean Whether inlay hints are enabled for this scope ----@type vim.lsp.inlay_hint.globalstate -local globalstate = { - enabled = false, +---@class (private) vim.lsp.inlay_hint.LineHints +---@field hints lsp.InlayHint[] +---@field applied boolean whether this line's hints have had extmarks applied + +---@class (private) vim.lsp.inlay_hint.CurrentResult Info for current result +---@field version? integer document version associated with this result +---@field namespace_cleared? boolean whether the namespace was cleared for this result yet +---@field hints? table lnum -> hints + +---@class (private) vim.lsp.inlay_hint.ActiveRequest +---@field request_id? integer the LSP request ID of the most recent request sent to the server +---@field version? integer the document version associated with the most recent request + +---@class (private) vim.lsp.inlay_hint.ClientState Buffer local state for inlay hints +---@field namespace integer +---@field active_request vim.lsp.inlay_hint.ActiveRequest +---@field current_result vim.lsp.inlay_hint.CurrentResult + +---@class (private) InlayHints : vim.lsp.Capability +---@field active table +---@field client_state table +local InlayHint = { + name = 'inlay_hint', + method = 'textDocument/inlayHint', + active = {}, } +InlayHint.__index = InlayHint +setmetatable(InlayHint, Capability) +Capability.all[InlayHint.name] = InlayHint ----@class (private) vim.lsp.inlay_hint.bufstate: vim.lsp.inlay_hint.globalstate Buffer local state for inlay hints ----@field version? integer ----@field client_hints? table> client_id -> (lnum -> hints) ----@field applied table Last version of hints applied to this line +---@package +function InlayHint:new(bufnr) + self = Capability.new(self, bufnr) ----@type table -local bufstates = vim.defaulttable(function(_) - return setmetatable({ applied = {} }, { - __index = globalstate, - __newindex = function(state, key, value) - if globalstate[key] == value then - rawset(state, key, nil) - else - rawset(state, key, value) - end - end, - }) -end) + nvim_on('LspNotify', self.augroup, { buf = self.bufnr }, function(ev) + local client_id = ev.data.client_id ---@type integer -local namespace = api.nvim_create_namespace('nvim.lsp.inlayhint') -local augroup = api.nvim_create_augroup('nvim.lsp.inlayhint', {}) + if not self.client_state[client_id] then + return + end + + if ev.data.method == 'textDocument/didClose' then + self:reset(client_id) + end + + if ev.data.method == 'textDocument/didChange' or ev.data.method == 'textDocument/didOpen' then + self:refresh(client_id) + end + end) + + nvim_on('BufWinEnter', self.augroup, { buf = self.bufnr }, function() + for client_id, _ in pairs(self.client_state) do + self:refresh(client_id) + end + end) + + return self +end + +---@package +function InlayHint:on_attach(client_id) + if not self.client_state[client_id] then + self.client_state[client_id] = { + namespace = api.nvim_create_namespace('nvim.lsp.inlay_hint:' .. client_id), + active_request = {}, + current_result = {}, + } + end + self:refresh(client_id) +end + +---@package +function InlayHint:on_detach(client_id) + local state = self.client_state[client_id] + if state then + self:reset(client_id) + self.client_state[client_id] = nil + end +end + +--- Reset the buffer's inlay hint state and clear the extmarks +---@package +---@param client_id integer +function InlayHint:reset(client_id) + local state = assert(self.client_state[client_id]) + self:cancel_active_request(client_id) + api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) + state.current_result = {} +end + +--- Refresh inlay hints by requesting them from the server +--- +--- Only sends a request if there is no active request in flight for the current document version. +--- Otherwise, it cancels any previous in-progress request before sending a new one. +--- +---@package +---@param client_id integer +function InlayHint:refresh(client_id) + local version = util.buf_versions[self.bufnr] + local state = self.client_state[client_id] + local client = vim.lsp.get_client_by_id(client_id) + + if state and client then + local current_result = state.current_result + local active_request = state.active_request + + -- Only send a request for this client if the current result is out of date and + -- there isn't a current a request in flight for this version + if current_result.version == version or active_request.version == version then + return + end + + -- cancel stale in-flight request + self:cancel_active_request(client_id) + + ---@type lsp.InlayHintParams + local params = { + textDocument = util.make_text_document_params(self.bufnr), + range = vim + .range(self.bufnr, 0, 0, api.nvim_buf_line_count(self.bufnr), 0) + :to_lsp(client.offset_encoding), + } + + local success, request_id = client:request('textDocument/inlayHint', params, nil, self.bufnr) + + if success then + active_request.request_id = request_id + active_request.version = version + end + end +end --- |lsp-handler| for the method `textDocument/inlayHint` --- Store hints for a specific buffer and client @@ -39,72 +145,85 @@ local augroup = api.nvim_create_augroup('nvim.lsp.inlayhint', {}) ---@param ctx lsp.HandlerContext ---@private function M.on_inlayhint(err, result, ctx) + local bufnr = assert(ctx.bufnr) + local provider = InlayHint.active[bufnr] + if not provider then + return + end + + local state = provider.client_state[ctx.client_id] + if not state then + return + end + if err then log.error('inlay_hint', err) + state.active_request = {} return end - local bufnr = assert(ctx.bufnr) - if - util.buf_versions[bufnr] ~= ctx.version - or not api.nvim_buf_is_loaded(bufnr) - or not bufstates[bufnr].enabled - then + if util.buf_versions[bufnr] ~= ctx.version or not api.nvim_buf_is_loaded(bufnr) then return end - local client_id = ctx.client_id - local bufstate = bufstates[bufnr] - if not (bufstate.client_hints and bufstate.version) then - bufstate.client_hints = vim.defaulttable() - bufstate.version = ctx.version + + -- ignore stale responses + if state.active_request.request_id and ctx.request_id ~= state.active_request.request_id then + return end - local client_hints = bufstate.client_hints - local client = assert(vim.lsp.get_client_by_id(client_id)) -- If there's no error but the result is nil, clear existing hints. result = result or {} - local new_lnum_hints = vim.defaulttable() + local new_lnum_hints = {} ---@type table local num_unprocessed = #result if num_unprocessed == 0 then - client_hints[client_id] = {} - bufstate.version = ctx.version - api.nvim__redraw({ buf = bufnr, valid = true, flush = false }) + state.active_request = {} + state.current_result = {} + if vim.fn.win_gettype(vim.fn.bufwinid(bufnr)) == '' then + api.nvim__redraw({ buf = bufnr, valid = true, flush = false }) + end return end local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) for _, hint in ipairs(result) do local lnum = hint.position.line local line = lines and lines[lnum + 1] or '' hint.position.character = vim.str_byteindex(line, client.offset_encoding, hint.position.character, false) - table.insert(new_lnum_hints[lnum], hint) + if not new_lnum_hints[lnum] then + new_lnum_hints[lnum] = { + hints = {}, + applied = false, + } + end + table.insert(new_lnum_hints[lnum].hints, hint) end - client_hints[client_id] = new_lnum_hints - bufstate.version = ctx.version - api.nvim__redraw({ buf = bufnr, valid = true, flush = false }) + state.active_request = {} + state.current_result = { + hints = new_lnum_hints, + version = ctx.version, + namespace_cleared = false, + } + + if vim.fn.win_gettype(vim.fn.bufwinid(bufnr)) == '' then + api.nvim__redraw({ buf = bufnr, valid = true, flush = false }) + end end ---- Refresh inlay hints, only if we have attached clients that support it ----@param bufnr (integer) Buffer handle, or 0 for current ----@param client_id? (integer) Client ID, or nil for all -local function refresh(bufnr, client_id) - for _, client in - ipairs(vim.lsp.get_clients({ - bufnr = bufnr, - id = client_id, - method = 'textDocument/inlayHint', - })) - do - client:request('textDocument/inlayHint', { - textDocument = util.make_text_document_params(bufnr), - range = vim - .range(bufnr, 0, 0, api.nvim_buf_line_count(bufnr), 0) - :to_lsp(client.offset_encoding), - }, nil, bufnr) +---@private +function InlayHint:cancel_active_request(client_id) + local state = assert(self.client_state[client_id]) + local client = vim.lsp.get_client_by_id(client_id) + local active_request = state.active_request + + if client and active_request.request_id then + client:cancel_request(active_request.request_id) + active_request.request_id = nil + active_request.version = nil end end @@ -115,13 +234,14 @@ function M.on_refresh(err, _, ctx) if err then return vim.NIL end + for bufnr in pairs(vim.lsp.get_client_by_id(ctx.client_id).attached_buffers or {}) do - for _, winid in ipairs(api.nvim_list_wins()) do - if api.nvim_win_get_buf(winid) == bufnr then - if bufstates[bufnr] and bufstates[bufnr].enabled then - bufstates[bufnr].applied = {} - refresh(bufnr) - end + local provider = InlayHint.active[bufnr] + if provider and provider.client_state[ctx.client_id] then + provider:reset(ctx.client_id) + + if not vim.tbl_isempty(vim.fn.win_findbuf(bufnr)) then + provider:refresh(ctx.client_id) end end end @@ -172,27 +292,19 @@ function M.get(filter) local bufnr = filter.bufnr if not bufnr then - --- @type vim.lsp.inlay_hint.get.ret[] - local hints = {} - --- @param buf integer - vim.tbl_map(function(buf) - vim.list_extend(hints, M.get(vim.tbl_extend('keep', { bufnr = buf }, filter))) - end, api.nvim_list_bufs()) - return hints + return vim + .iter(api.nvim_list_bufs()) + :map(function(buf) + return M.get(vim.tbl_extend('keep', { bufnr = buf }, filter)) + end) + :flatten() + :totable() else bufnr = vim._resolve_bufnr(bufnr) end - local bufstate = bufstates[bufnr] - if not bufstate.client_hints then - return {} - end - - local clients = vim.lsp.get_clients({ - bufnr = bufnr, - method = 'textDocument/inlayHint', - }) - if #clients == 0 then + local provider = InlayHint.active[bufnr] + if not provider then return {} end @@ -206,12 +318,12 @@ function M.get(filter) --- @type vim.lsp.inlay_hint.get.ret[] local result = {} - for _, client in pairs(clients) do - local lnum_hints = bufstate.client_hints[client.id] + for client_id, state in pairs(provider.client_state) do + local lnum_hints = state.current_result.hints if lnum_hints then for lnum = range.start.line, range['end'].line do - local hints = lnum_hints[lnum] or {} - for _, hint in pairs(hints) do + local line_hints = lnum_hints[lnum] or { hints = {}, applied = false } + for _, hint in pairs(line_hints.hints) do local line, char = hint.position.line, hint.position.character if (line > range.start.line or char >= range.start.character) @@ -219,7 +331,7 @@ function M.get(filter) then table.insert(result, { bufnr = bufnr, - client_id = client.id, + client_id = client_id, inlay_hint = hint, }) end @@ -230,105 +342,27 @@ function M.get(filter) return result end ---- Clear inlay hints ----@param bufnr (integer) Buffer handle, or 0 for current -local function clear(bufnr) - bufnr = vim._resolve_bufnr(bufnr) - local bufstate = bufstates[bufnr] - local client_lens = (bufstate or {}).client_hints or {} - local client_ids = vim.tbl_keys(client_lens) --- @type integer[] - for _, iter_client_id in ipairs(client_ids) do - if bufstate then - bufstate.client_hints[iter_client_id] = {} - end - end - api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) - api.nvim__redraw({ buf = bufnr, valid = true, flush = false }) -end - ---- Disable inlay hints for a buffer ----@param bufnr (integer) Buffer handle, or 0 for current -local function _disable(bufnr) - bufnr = vim._resolve_bufnr(bufnr) - clear(bufnr) - bufstates[bufnr] = nil - bufstates[bufnr].enabled = false -end - ---- Enable inlay hints for a buffer ----@param bufnr (integer) Buffer handle, or 0 for current -local function _enable(bufnr) - bufnr = vim._resolve_bufnr(bufnr) - bufstates[bufnr] = nil - bufstates[bufnr].enabled = true - refresh(bufnr) -end - -nvim_on('LspNotify', augroup, function(ev) - ---@type integer - local bufnr = ev.buf - - if ev.data.method ~= 'textDocument/didChange' and ev.data.method ~= 'textDocument/didOpen' then - return - end - if bufstates[bufnr].enabled then - refresh(bufnr, ev.data.client_id) - end -end) -nvim_on('LspAttach', augroup, function(ev) - ---@type integer - local bufnr = ev.buf - - api.nvim_buf_attach(bufnr, false, { - on_reload = function(_, cb_bufnr) - clear(cb_bufnr) - if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then - bufstates[cb_bufnr].applied = {} - refresh(cb_bufnr) +--- on_win handler for the decoration provider (see |nvim_set_decoration_provider|) +---@package +---@param topline integer +---@param botline integer +function InlayHint:on_win(topline, botline) + for _, state in pairs(self.client_state) do + local current_result = state.current_result + if current_result.version == util.buf_versions[self.bufnr] then + if not current_result.namespace_cleared then + api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) + current_result.namespace_cleared = true end - end, - on_detach = function(_, cb_bufnr) - _disable(cb_bufnr) - bufstates[cb_bufnr] = nil - end, - }) -end) -nvim_on('LspDetach', augroup, function(ev) - ---@type integer - local bufnr = ev.buf - local clients = vim.lsp.get_clients({ bufnr = bufnr, method = 'textDocument/inlayHint' }) - if not vim.iter(clients):any(function(c) - return c.id ~= ev.data.client_id - end) then - _disable(bufnr) - end -end) -api.nvim_set_decoration_provider(namespace, { - on_win = function(_, _, bufnr, topline, botline) - ---@type vim.lsp.inlay_hint.bufstate - local bufstate = rawget(bufstates, bufnr) - if not bufstate then - return - end - - if bufstate.version ~= util.buf_versions[bufnr] then - return - end - - if not bufstate.client_hints then - return - end - local client_hints = assert(bufstate.client_hints) - - for lnum = topline, botline do - if bufstate.applied[lnum] ~= bufstate.version then - api.nvim_buf_clear_namespace(bufnr, namespace, lnum, lnum + 1) + local hints = assert(current_result.hints) + for lnum = topline, botline do local hint_virtual_texts = {} --- @type table - for _, lnum_hints in pairs(client_hints) do - local hints = lnum_hints[lnum] or {} - for _, hint in pairs(hints) do + local line_hints = hints[lnum] + if line_hints and not line_hints.applied then + line_hints.applied = true + for _, hint in pairs(line_hints.hints) do local text = '' local label = hint.label if type(label) == 'string' then @@ -351,40 +385,25 @@ api.nvim_set_decoration_provider(namespace, { end for pos, vt in pairs(hint_virtual_texts) do - api.nvim_buf_set_extmark(bufnr, namespace, lnum, pos, { + api.nvim_buf_set_extmark(self.bufnr, state.namespace, lnum, pos, { virt_text_pos = 'inline', ephemeral = false, virt_text = vt, }) end - - bufstate.applied[lnum] = bufstate.version end end - end, -}) + end +end --- Query whether inlay hint is enabled in the {filter}ed scope ---- @param filter? vim.lsp.inlay_hint.enable.Filter +--- @param filter? vim.lsp.capability.enable.Filter --- @return boolean --- @since 12 function M.is_enabled(filter) - vim.validate('filter', filter, 'table', true) - filter = filter or {} - local bufnr = filter.bufnr - - if bufnr == nil then - return globalstate.enabled - end - return bufstates[vim._resolve_bufnr(bufnr)].enabled + return Capability.is_enabled('inlay_hint', filter) end ---- Optional filters |kwargs|, or `nil` for all. ---- @class vim.lsp.inlay_hint.enable.Filter ---- @inlinedoc ---- Buffer number, or 0 for current buffer, or nil for all. ---- @field bufnr integer? - --- Enables or disables inlay hints for the {filter}ed scope. --- --- To "toggle", pass the inverse of `is_enabled()`: @@ -393,35 +412,21 @@ end --- vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled()) --- ``` --- ---- @param enable (boolean|nil) true/nil to enable, false to disable ---- @param filter vim.lsp.inlay_hint.enable.Filter? +--- @param enable boolean? true/nil to enable, false to disable +--- @param filter? vim.lsp.capability.enable.Filter --- @since 12 function M.enable(enable, filter) - vim.validate('enable', enable, 'boolean', true) - vim.validate('filter', filter, 'table', true) - enable = enable == nil or enable - filter = filter or {} - - if filter.bufnr == nil then - globalstate.enabled = enable - for _, bufnr in ipairs(api.nvim_list_bufs()) do - if api.nvim_buf_is_loaded(bufnr) then - if enable == false then - _disable(bufnr) - else - _enable(bufnr) - end - else - bufstates[bufnr] = nil - end - end - else - if enable == false then - _disable(filter.bufnr) - else - _enable(filter.bufnr) - end - end + Capability.enable('inlay_hint', enable, filter) end +local namespace = api.nvim_create_namespace('nvim.lsp.inlay_hint') +api.nvim_set_decoration_provider(namespace, { + on_win = function(_, _, bufnr, topline, botline) + local provider = InlayHint.active[bufnr] + if provider then + provider:on_win(topline, botline) + end + end, +}) + return M diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index db4c437d2d..5bacb34de6 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -22,7 +22,7 @@ local M = {} --- @field version? integer document version associated with this result --- @field result_id? string resultId from the server; used with delta requests --- @field highlights? STTokenRange[] cache of highlight ranges for this document version ---- @field tokens? integer[] raw token array as received by the server. used for calculating delta responses +--- @field tokens? uinteger[] raw token array as received by the server. used for calculating delta responses --- @field namespace_cleared? boolean whether the namespace was cleared for this result yet --- @class (private) STActiveRequest @@ -283,12 +283,11 @@ end --- This is the entry point for getting all the tokens in a buffer. --- ---- For the given clients (or all attached, if not provided), this sends semantic token requests to ---- ask for semantic tokens. If the server supports range requests and a full result has not been ---- processed yet, it will send a range request for the current visible range. Additionally, if a ---- result for the current document version hasn't been processed yet, it sends either a full or ---- delta request, depending on what the server supports and whether there's a current full result ---- for the previous document version. +--- For the given client, this sends semantic token requests to ask for semantic tokens. If the +--- server supports range requests and a full result has not been processed yet, it will send a +--- range request for the current visible range. Additionally, if a result for the current document +--- version hasn't been processed yet, it sends either a full or delta request, depending on what +--- the server supports and whether there's a current full result for the previous document version. --- --- This function will skip full/delta requests on servers where there is an already an active --- full/delta request in flight for the same version. If there is a stale request in flight, that @@ -738,10 +737,10 @@ end ---@param client_id integer function STHighlighter:reset(client_id) local state = assert(self.client_state[client_id]) + self:cancel_active_request(client_id) api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) state.current_result = {} state.has_full_result = false - self:cancel_active_request(client_id) end --- Mark a client's results as dirty. This method will cancel any active @@ -987,8 +986,9 @@ end --- Refresh requests are sent by the server to indicate a project-wide change --- that requires all tokens to be re-requested by the client. This handler will --- invalidate the current results of all buffers and automatically kick off a ---- new request for buffers that are displayed in a window. For those that aren't, a +--- new request for buffers that are displayed in a window. For those that aren't, --- the BufWinEnter event should take care of it next time it's displayed. +---@param ctx lsp.HandlerContext function M._refresh(err, _, ctx) if err then return vim.NIL diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index be7d17856b..cb5f44ef39 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -92,6 +92,7 @@ int main() { client_id = exec_lua(function() _G.server = _G._create_server({ capabilities = { + textDocumentSync = vim.lsp.protocol.TextDocumentSyncKind.Full, inlayHintProvider = true, }, handlers = { @@ -126,6 +127,7 @@ int main() { local client_id2 = exec_lua(function() _G.server2 = _G._create_server({ capabilities = { + textDocumentSync = vim.lsp.protocol.TextDocumentSyncKind.Full, inlayHintProvider = true, }, handlers = { @@ -134,9 +136,7 @@ int main() { end, }, }) - local client_id2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd }) - vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) - return client_id2 + return vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd }) end) exec_lua(function() @@ -169,67 +169,67 @@ int main() { end) ) end) + end) - describe('clears/applies inlay hints when passed false/true/nil', function() - local bufnr2 --- @type integer - before_each(function() - bufnr2 = exec_lua(function() - local bufnr2_0 = vim.api.nvim_create_buf(true, false) - vim.lsp.buf_attach_client(bufnr2_0, client_id) - vim.api.nvim_win_set_buf(0, bufnr2_0) - return bufnr2_0 - end) - insert(text) - exec_lua(function() - vim.lsp.inlay_hint.enable(true, { bufnr = bufnr2 }) - end) - n.api.nvim_win_set_buf(0, bufnr) - screen:expect({ grid = grid_with_inlay_hints }) + describe('clears/applies inlay hints when passed false/true/nil', function() + local bufnr2 --- @type integer + before_each(function() + bufnr2 = exec_lua(function() + local bufnr2_0 = vim.api.nvim_create_buf(true, false) + vim.lsp.buf_attach_client(bufnr2_0, client_id) + vim.api.nvim_win_set_buf(0, bufnr2_0) + return bufnr2_0 end) - - it('for one single buffer', function() - exec_lua(function() - vim.lsp.inlay_hint.enable(false, { bufnr = bufnr }) - vim.api.nvim_win_set_buf(0, bufnr2) - end) - screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) - n.api.nvim_win_set_buf(0, bufnr) - screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - - exec_lua(function() - vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) - end) - screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) - - exec_lua(function() - vim.lsp.inlay_hint.enable( - not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), - { bufnr = bufnr } - ) - end) - screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - - exec_lua(function() - vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) - end) - screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) + insert(text) + screen:expect({ grid = grid_without_inlay_hints }) + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr2 }) end) + screen:expect({ grid = grid_with_inlay_hints }) + end) - it('for all buffers', function() - exec_lua(function() - vim.lsp.inlay_hint.enable(false) - end) - screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - n.api.nvim_win_set_buf(0, bufnr2) - screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - - exec_lua(function() - vim.lsp.inlay_hint.enable(true) - end) - screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) - n.api.nvim_win_set_buf(0, bufnr) - screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) + it('for one single buffer', function() + exec_lua(function() + vim.lsp.inlay_hint.enable(false, { bufnr = bufnr }) + vim.api.nvim_win_set_buf(0, bufnr2) end) + screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) + n.api.nvim_win_set_buf(0, bufnr) + screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) + + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) + end) + screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) + + exec_lua(function() + vim.lsp.inlay_hint.enable( + not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), + { bufnr = bufnr } + ) + end) + screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) + + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) + end) + screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) + end) + + it('for all buffers', function() + exec_lua(function() + vim.lsp.inlay_hint.enable(false) + end) + screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) + n.api.nvim_win_set_buf(0, bufnr2) + screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) + + exec_lua(function() + vim.lsp.inlay_hint.enable(true) + end) + screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) + n.api.nvim_win_set_buf(0, bufnr) + screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) end) end) @@ -249,6 +249,7 @@ int main() { exec_lua(function() _G.server2 = _G._create_server({ capabilities = { + textDocumentSync = vim.lsp.protocol.TextDocumentSyncKind.Full, inlayHintProvider = true, }, handlers = { @@ -258,7 +259,6 @@ int main() { }, }) _G.client2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd }) - vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) end) --- @type vim.lsp.inlay_hint.get.ret @@ -313,50 +313,50 @@ int main() { end) ) end) + end) - it('does not request hints from lsp when disabled', function() - exec_lua(function() - _G.server2 = _G._create_server({ - capabilities = { - inlayHintProvider = true, - }, - handlers = { - ['textDocument/inlayHint'] = function(_, _, callback) - _G.got_inlay_hint_request = true - callback(nil, {}) - end, - }, - }) - _G.client2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd }) - end) - - local function was_request_sent() - return exec_lua(function() - return _G.got_inlay_hint_request == true - end) - end - - eq(false, was_request_sent()) - - exec_lua(function() - vim.lsp.inlay_hint.get() - end) - - eq(false, was_request_sent()) - - exec_lua(function() - vim.lsp.inlay_hint.enable(false, { bufnr = bufnr }) - vim.lsp.inlay_hint.get() - end) - - eq(false, was_request_sent()) - - exec_lua(function() - vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) - end) - - eq(true, was_request_sent()) + it('does not request hints from lsp when disabled', function() + local client_id2 = exec_lua(function() + _G.server2 = _G._create_server({ + capabilities = { + textDocumentSync = vim.lsp.protocol.TextDocumentSyncKind.Full, + inlayHintProvider = true, + }, + handlers = { + ['textDocument/inlayHint'] = function(_, _, callback) + _G.got_inlay_hint_request = true + callback(nil, {}) + end, + }, + }) + return vim.lsp.start({ + name = 'dummy2', + cmd = _G.server2.cmd, + on_attach = function(client, _) + vim.lsp.inlay_hint.enable(false, { client_id = client.id }) + end, + }) end) + + local function was_request_sent() + return exec_lua(function() + return _G.got_inlay_hint_request or false + end) + end + + eq(false, was_request_sent()) + + exec_lua(function() + vim.lsp.inlay_hint.get() + end) + + eq(false, was_request_sent()) + + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { client_id = client_id2 }) + end) + + eq(true, was_request_sent()) end) end) @@ -403,6 +403,7 @@ test text client_id = exec_lua(function() _G.server = _G._create_server({ capabilities = { + textDocumentSync = vim.lsp.protocol.TextDocumentSyncKind.Full, inlayHintProvider = true, }, handlers = {