feat(lsp): convert inlay_hint to capability framework #40569

Problem:
Inlay hints used separate global and per-buffer bufstates tables and
bespoke global autocmds for managing the inlay hint state across buffers
and clients, duplicating the lifecycle logic already provided by the
Capability framework. This caused inconsistencies in how client state
was handled and inlay hint state lifecycle was managed compared to other
LSP features.

Solution:
Replace the ad-hoc bufstate tracking and global autocmds in
vim.lsp.inlay_hint with a proper InlayHint subclass of Capability.

This also refactors the way inlay hint state is managed and fixes bugs I
found while doing this:

1. For each line with inlay hints, the list of the hints along with
   whether they have been applied is stored in a current result on the
   client state. This allows the on_win decorator to clear all inlay
   hints for an old document version once, and then re-add the new
   version's hints line-by-line as they are drawn to the screen,
   modeling the semantic tokens module.
2. It fixes problems with mixing results from multiple clients attached
   to the buffer by fully moving each client's state to its own table.
   Previously, only the most recent document version used to populate a
   line's inlay hints was stored, but there was no distinction for which
   client the hints may have come from. (Fixes #36318)
3. It fixes the workspace/inlayHint/refresh server->client notification
   behavior. Previously it would only re-request inlay hints for buffers
   currently displayed in a window but would not invalidate them in
   non-displayed buffers (or provide any mechanism for those buffers to
   re-request at a later time). Model semantic token module here again
   by invalidating all buffers, and adding a BufWinEnter autocmd to
   refresh hints.
4. Add a mechanism to cancel in-flight requests if a new request for a
   newer document version is made before the last one returned
5. Handle stale results by simply dropping them.
This commit is contained in:
jdrouhard
2026-07-05 11:47:30 -05:00
committed by GitHub
parent e494c4777b
commit 1f18ea1cf7
6 changed files with 363 additions and 351 deletions

View File

@@ -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`)

View File

@@ -5,6 +5,7 @@ local api = vim.api
---| 'diagnostics'
---| 'document_color'
---| 'folding_range'
---| 'inlay_hint'
---| 'inline_completion'
---| 'linked_editing_range'
---| 'semantic_tokens'

View File

@@ -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')

View File

@@ -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<integer, vim.lsp.inlay_hint.LineHints> 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<integer, InlayHints>
---@field client_state table<integer, vim.lsp.inlay_hint.ClientState>
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<integer, table<integer, lsp.InlayHint[]>> client_id -> (lnum -> hints)
---@field applied table<integer, integer> Last version of hints applied to this line
---@package
function InlayHint:new(bufnr)
self = Capability.new(self, bufnr)
---@type table<integer, vim.lsp.inlay_hint.bufstate>
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<integer, vim.lsp.inlay_hint.LineHints>
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<integer, [string, string?][]>
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

View File

@@ -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

View File

@@ -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 = {