mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	feat(lsp)!: vim.lsp.inlay_hint.get(), enable(), is_enabled() #25512
refactor!: `vim.lsp.inlay_hint()` -> `vim.lsp.inlay_hint.enable()` Problem: The LSP specification allows inlay hints to include tooltips, clickable label parts, and code actions; but Neovim provides no API to query for these. Solution: Add minimal viable extension point from which plugins can query for inlay hints in a range, in order to build functionality on top of. Possible Next Steps --- - Add `virt_text_idx` field to `vim.fn.getmousepos()` return value, for usage in mappings of `<LeftMouse>`, `<C-LeftMouse>`, etc
This commit is contained in:
		@@ -856,13 +856,6 @@ get_log_path()                                        *vim.lsp.get_log_path()*
 | 
			
		||||
    Return: ~
 | 
			
		||||
        (string) path to log file
 | 
			
		||||
 | 
			
		||||
inlay_hint({bufnr}, {enable})                           *vim.lsp.inlay_hint()*
 | 
			
		||||
    Enable/disable/toggle inlay hints for a buffer
 | 
			
		||||
 | 
			
		||||
    Parameters: ~
 | 
			
		||||
      • {bufnr}   (integer) Buffer handle, or 0 for current
 | 
			
		||||
      • {enable}  (boolean|nil) true/false to enable/disable, nil to toggle
 | 
			
		||||
 | 
			
		||||
omnifunc({findstart}, {base})                             *vim.lsp.omnifunc()*
 | 
			
		||||
    Implements 'omnifunc' compatible LSP completion.
 | 
			
		||||
 | 
			
		||||
@@ -1469,6 +1462,53 @@ save({lenses}, {bufnr}, {client_id})                 *vim.lsp.codelens.save()*
 | 
			
		||||
      • {client_id}  (integer)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
==============================================================================
 | 
			
		||||
Lua module: vim.lsp.inlay_hint                                *lsp-inlay_hint*
 | 
			
		||||
 | 
			
		||||
enable({bufnr}, {enable})                        *vim.lsp.inlay_hint.enable()*
 | 
			
		||||
    Enable/disable/toggle inlay hints for a buffer
 | 
			
		||||
 | 
			
		||||
    Parameters: ~
 | 
			
		||||
      • {bufnr}   (integer|nil) Buffer handle, or 0 or nil for current
 | 
			
		||||
      • {enable}  (boolean|nil) true/nil to enable, false to disable
 | 
			
		||||
 | 
			
		||||
get({filter})                                       *vim.lsp.inlay_hint.get()*
 | 
			
		||||
    Get the list of inlay hints, (optionally) restricted by buffer, client, or
 | 
			
		||||
    range.
 | 
			
		||||
 | 
			
		||||
    Example usage: >lua
 | 
			
		||||
        local hint = vim.lsp.inlay_hint.get({ bufnr = 0 })[1] -- 0 for current buffer
 | 
			
		||||
 | 
			
		||||
        local client = vim.lsp.get_client_by_id(hint.client_id)
 | 
			
		||||
        resolved_hint = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0).result
 | 
			
		||||
        vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding)
 | 
			
		||||
 | 
			
		||||
        location = resolved_hint.label[1].location
 | 
			
		||||
        client.request("textDocument/hover", {
 | 
			
		||||
          textDocument = { uri = location.uri },
 | 
			
		||||
          position = location.range.start,
 | 
			
		||||
        })
 | 
			
		||||
<
 | 
			
		||||
 | 
			
		||||
    Parameters: ~
 | 
			
		||||
      • {filter}  vim.lsp.inlay_hint.get.filter ? Optional filters |kwargs|:
 | 
			
		||||
                  • bufnr (integer?): 0 for current buffer
 | 
			
		||||
                  • range (lsp.Range?)
 | 
			
		||||
 | 
			
		||||
    Return: ~
 | 
			
		||||
        vim.lsp.inlay_hint.get.ret [] Each list item is a table with the following fields:
 | 
			
		||||
        • bufnr (integer)
 | 
			
		||||
        • client_id (integer)
 | 
			
		||||
        • inlay_hint (lsp.InlayHint)
 | 
			
		||||
 | 
			
		||||
is_enabled({bufnr})                          *vim.lsp.inlay_hint.is_enabled()*
 | 
			
		||||
    Parameters: ~
 | 
			
		||||
      • {bufnr}  (integer|nil) Buffer handle, or 0 or nil for current
 | 
			
		||||
 | 
			
		||||
    Return: ~
 | 
			
		||||
        (boolean)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
==============================================================================
 | 
			
		||||
Lua module: vim.lsp.semantic_tokens                      *lsp-semantic_tokens*
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -130,7 +130,7 @@ The following new APIs and features were added.
 | 
			
		||||
 | 
			
		||||
• LSP
 | 
			
		||||
  • LSP method names are available in |vim.lsp.protocol.Methods|.
 | 
			
		||||
  • Implemented LSP inlay hints: |vim.lsp.inlay_hint()|
 | 
			
		||||
  • Implemented LSP inlay hints: |lsp-inlay_hint|
 | 
			
		||||
    https://microsoft.github.io/language-server-protocol/specification/#textDocument_inlayHint
 | 
			
		||||
  • Implemented pull diagnostic textDocument/diagnostic: |vim.lsp.diagnostic.on_diagnostic()|
 | 
			
		||||
    https://microsoft.github.io/language-server-protocol/specification/#textDocument_diagnostic
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ local lsp = {
 | 
			
		||||
  buf = require('vim.lsp.buf'),
 | 
			
		||||
  diagnostic = require('vim.lsp.diagnostic'),
 | 
			
		||||
  codelens = require('vim.lsp.codelens'),
 | 
			
		||||
  inlay_hint = require('vim.lsp.inlay_hint'),
 | 
			
		||||
  semantic_tokens = semantic_tokens,
 | 
			
		||||
  util = util,
 | 
			
		||||
 | 
			
		||||
@@ -2439,13 +2440,6 @@ function lsp.with(handler, override_config)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Enable/disable/toggle inlay hints for a buffer
 | 
			
		||||
---@param bufnr (integer) Buffer handle, or 0 for current
 | 
			
		||||
---@param enable (boolean|nil) true/false to enable/disable, nil to toggle
 | 
			
		||||
function lsp.inlay_hint(bufnr, enable)
 | 
			
		||||
  return require('vim.lsp.inlay_hint')(bufnr, enable)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Helper function to use when implementing a handler.
 | 
			
		||||
--- This will check that all of the keys in the user configuration
 | 
			
		||||
--- are valid keys and make sense to include for this handler.
 | 
			
		||||
 
 | 
			
		||||
@@ -98,6 +98,107 @@ function M.on_refresh(err, _, ctx, _)
 | 
			
		||||
  return vim.NIL
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- @class vim.lsp.inlay_hint.get.filter
 | 
			
		||||
--- @field bufnr integer?
 | 
			
		||||
--- @field range lsp.Range?
 | 
			
		||||
---
 | 
			
		||||
--- @class vim.lsp.inlay_hint.get.ret
 | 
			
		||||
--- @field bufnr integer
 | 
			
		||||
--- @field client_id integer
 | 
			
		||||
--- @field inlay_hint lsp.InlayHint
 | 
			
		||||
 | 
			
		||||
--- Get the list of inlay hints, (optionally) restricted by buffer, client, or range.
 | 
			
		||||
---
 | 
			
		||||
--- Example usage:
 | 
			
		||||
---
 | 
			
		||||
--- ```lua
 | 
			
		||||
--- local hint = vim.lsp.inlay_hint.get({ bufnr = 0 })[1] -- 0 for current buffer
 | 
			
		||||
---
 | 
			
		||||
--- local client = vim.lsp.get_client_by_id(hint.client_id)
 | 
			
		||||
--- resolved_hint = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0).result
 | 
			
		||||
--- vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding)
 | 
			
		||||
---
 | 
			
		||||
--- location = resolved_hint.label[1].location
 | 
			
		||||
--- client.request('textDocument/hover', {
 | 
			
		||||
---   textDocument = { uri = location.uri },
 | 
			
		||||
---   position = location.range.start,
 | 
			
		||||
--- })
 | 
			
		||||
--- ```
 | 
			
		||||
---
 | 
			
		||||
--- @param filter vim.lsp.inlay_hint.get.filter?
 | 
			
		||||
--- Optional filters |kwargs|:
 | 
			
		||||
--- - bufnr (integer?): 0 for current buffer
 | 
			
		||||
--- - range (lsp.Range?)
 | 
			
		||||
---
 | 
			
		||||
--- @return vim.lsp.inlay_hint.get.ret[]
 | 
			
		||||
--- Each list item is a table with the following fields:
 | 
			
		||||
---  - bufnr (integer)
 | 
			
		||||
---  - client_id (integer)
 | 
			
		||||
---  - inlay_hint (lsp.InlayHint)
 | 
			
		||||
function M.get(filter)
 | 
			
		||||
  vim.validate({ filter = { filter, 'table', true } })
 | 
			
		||||
  filter = filter or {}
 | 
			
		||||
 | 
			
		||||
  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, vim.api.nvim_list_bufs())
 | 
			
		||||
    return hints
 | 
			
		||||
  elseif bufnr == 0 then
 | 
			
		||||
    bufnr = api.nvim_get_current_buf()
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  local bufstate = bufstates[bufnr]
 | 
			
		||||
  if not (bufstate and bufstate.client_hint) then
 | 
			
		||||
    return {}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  local clients = vim.lsp.get_clients({
 | 
			
		||||
    bufnr = bufnr,
 | 
			
		||||
    method = ms.textDocument_inlayHint,
 | 
			
		||||
  })
 | 
			
		||||
  if #clients == 0 then
 | 
			
		||||
    return {}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  local range = filter.range
 | 
			
		||||
  if not range then
 | 
			
		||||
    range = {
 | 
			
		||||
      start = { line = 0, character = 0 },
 | 
			
		||||
      ['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 },
 | 
			
		||||
    }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  --- @type vim.lsp.inlay_hint.get.ret[]
 | 
			
		||||
  local hints = {}
 | 
			
		||||
  for _, client in pairs(clients) do
 | 
			
		||||
    local hints_by_lnum = bufstate.client_hint[client.id]
 | 
			
		||||
    if hints_by_lnum then
 | 
			
		||||
      for lnum = range.start.line, range['end'].line do
 | 
			
		||||
        local line_hints = hints_by_lnum[lnum] or {}
 | 
			
		||||
        for _, hint in pairs(line_hints) do
 | 
			
		||||
          local line, char = hint.position.line, hint.position.character
 | 
			
		||||
          if
 | 
			
		||||
            (line > range.start.line or char >= range.start.character)
 | 
			
		||||
            and (line < range['end'].line or char <= range['end'].character)
 | 
			
		||||
          then
 | 
			
		||||
            table.insert(hints, {
 | 
			
		||||
              bufnr = bufnr,
 | 
			
		||||
              client_id = client.id,
 | 
			
		||||
              inlay_hint = hint,
 | 
			
		||||
            })
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
  return hints
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Clear inlay hints
 | 
			
		||||
---@param bufnr (integer) Buffer handle, or 0 for current
 | 
			
		||||
local function clear(bufnr)
 | 
			
		||||
@@ -120,8 +221,8 @@ local function clear(bufnr)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Disable inlay hints for a buffer
 | 
			
		||||
---@param bufnr (integer) Buffer handle, or 0 for current
 | 
			
		||||
local function disable(bufnr)
 | 
			
		||||
---@param bufnr (integer|nil) Buffer handle, or 0 or nil for current
 | 
			
		||||
local function _disable(bufnr)
 | 
			
		||||
  if bufnr == nil or bufnr == 0 then
 | 
			
		||||
    bufnr = api.nvim_get_current_buf()
 | 
			
		||||
  end
 | 
			
		||||
@@ -142,8 +243,8 @@ local function _refresh(bufnr, opts)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Enable inlay hints for a buffer
 | 
			
		||||
---@param bufnr (integer) Buffer handle, or 0 for current
 | 
			
		||||
local function enable(bufnr)
 | 
			
		||||
---@param bufnr (integer|nil) Buffer handle, or 0 or nil for current
 | 
			
		||||
local function _enable(bufnr)
 | 
			
		||||
  if bufnr == nil or bufnr == 0 then
 | 
			
		||||
    bufnr = api.nvim_get_current_buf()
 | 
			
		||||
  end
 | 
			
		||||
@@ -175,7 +276,7 @@ local function enable(bufnr)
 | 
			
		||||
        end
 | 
			
		||||
      end,
 | 
			
		||||
      on_detach = function(_, cb_bufnr)
 | 
			
		||||
        disable(cb_bufnr)
 | 
			
		||||
        _disable(cb_bufnr)
 | 
			
		||||
      end,
 | 
			
		||||
    })
 | 
			
		||||
    api.nvim_create_autocmd('LspDetach', {
 | 
			
		||||
@@ -188,7 +289,7 @@ local function enable(bufnr)
 | 
			
		||||
            return c.id ~= args.data.client_id
 | 
			
		||||
          end)
 | 
			
		||||
        then
 | 
			
		||||
          disable(bufnr)
 | 
			
		||||
          _disable(bufnr)
 | 
			
		||||
        end
 | 
			
		||||
      end,
 | 
			
		||||
      group = augroup,
 | 
			
		||||
@@ -199,20 +300,6 @@ local function enable(bufnr)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Toggle inlay hints for a buffer
 | 
			
		||||
---@param bufnr (integer) Buffer handle, or 0 for current
 | 
			
		||||
local function toggle(bufnr)
 | 
			
		||||
  if bufnr == nil or bufnr == 0 then
 | 
			
		||||
    bufnr = api.nvim_get_current_buf()
 | 
			
		||||
  end
 | 
			
		||||
  local bufstate = bufstates[bufnr]
 | 
			
		||||
  if bufstate and bufstate.enabled then
 | 
			
		||||
    disable(bufnr)
 | 
			
		||||
  else
 | 
			
		||||
    enable(bufnr)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
api.nvim_set_decoration_provider(namespace, {
 | 
			
		||||
  on_win = function(_, _, bufnr, topline, botline)
 | 
			
		||||
    local bufstate = bufstates[bufnr]
 | 
			
		||||
@@ -260,15 +347,27 @@ api.nvim_set_decoration_provider(namespace, {
 | 
			
		||||
  end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
return setmetatable(M, {
 | 
			
		||||
  __call = function(_, bufnr, enable_)
 | 
			
		||||
    vim.validate({ enable = { enable_, { 'boolean', 'nil' } }, bufnr = { bufnr, 'number' } })
 | 
			
		||||
    if enable_ then
 | 
			
		||||
      enable(bufnr)
 | 
			
		||||
    elseif enable_ == false then
 | 
			
		||||
      disable(bufnr)
 | 
			
		||||
    else
 | 
			
		||||
      toggle(bufnr)
 | 
			
		||||
    end
 | 
			
		||||
  end,
 | 
			
		||||
})
 | 
			
		||||
--- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current
 | 
			
		||||
--- @return boolean
 | 
			
		||||
function M.is_enabled(bufnr)
 | 
			
		||||
  vim.validate({ bufnr = { bufnr, 'number', true } })
 | 
			
		||||
  if bufnr == nil or bufnr == 0 then
 | 
			
		||||
    bufnr = api.nvim_get_current_buf()
 | 
			
		||||
  end
 | 
			
		||||
  return bufstates[bufnr] and bufstates[bufnr].enabled or false
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Enable/disable/toggle inlay hints for a buffer
 | 
			
		||||
---
 | 
			
		||||
--- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current
 | 
			
		||||
--- @param enable (boolean|nil) true/nil to enable, false to disable
 | 
			
		||||
function M.enable(bufnr, enable)
 | 
			
		||||
  vim.validate({ enable = { enable, 'boolean', true }, bufnr = { bufnr, 'number', true } })
 | 
			
		||||
  if enable == false then
 | 
			
		||||
    _disable(bufnr)
 | 
			
		||||
  else
 | 
			
		||||
    _enable(bufnr)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
return M
 | 
			
		||||
 
 | 
			
		||||
@@ -648,7 +648,12 @@ function protocol.make_client_capabilities()
 | 
			
		||||
      inlayHint = {
 | 
			
		||||
        dynamicRegistration = true,
 | 
			
		||||
        resolveSupport = {
 | 
			
		||||
          properties = {},
 | 
			
		||||
          properties = {
 | 
			
		||||
            'textEdits',
 | 
			
		||||
            'tooltip',
 | 
			
		||||
            'location',
 | 
			
		||||
            'command',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      semanticTokens = {
 | 
			
		||||
 
 | 
			
		||||
@@ -259,6 +259,7 @@ CONFIG = {
 | 
			
		||||
            'buf.lua',
 | 
			
		||||
            'diagnostic.lua',
 | 
			
		||||
            'codelens.lua',
 | 
			
		||||
            'inlay_hint.lua',
 | 
			
		||||
            'tagfunc.lua',
 | 
			
		||||
            'semantic_tokens.lua',
 | 
			
		||||
            'handlers.lua',
 | 
			
		||||
 
 | 
			
		||||
@@ -18,8 +18,7 @@ local sleep = global_helpers.sleep
 | 
			
		||||
local tbl_contains = global_helpers.tbl_contains
 | 
			
		||||
local fail = global_helpers.fail
 | 
			
		||||
 | 
			
		||||
local module = {
 | 
			
		||||
}
 | 
			
		||||
local module = {}
 | 
			
		||||
 | 
			
		||||
local start_dir = luv.cwd()
 | 
			
		||||
local runtime_set = 'set runtimepath^=./build/lib/nvim/'
 | 
			
		||||
@@ -834,6 +833,8 @@ function module.exec_capture(code)
 | 
			
		||||
  return module.meths.exec2(code, { output = true }).output
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- @param code string
 | 
			
		||||
--- @return any
 | 
			
		||||
function module.exec_lua(code, ...)
 | 
			
		||||
  return module.meths.exec_lua(code, {...})
 | 
			
		||||
end
 | 
			
		||||
@@ -948,8 +949,10 @@ function module.mkdir_p(path)
 | 
			
		||||
    or 'mkdir -p '..path))
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- @class test.functional.helpers: test.helpers
 | 
			
		||||
module = global_helpers.tbl_extend('error', module, global_helpers)
 | 
			
		||||
 | 
			
		||||
--- @return test.functional.helpers
 | 
			
		||||
return function(after_each)
 | 
			
		||||
  if after_each then
 | 
			
		||||
    after_each(function()
 | 
			
		||||
 
 | 
			
		||||
@@ -10,228 +10,195 @@ local insert = helpers.insert
 | 
			
		||||
local clear_notrace = lsp_helpers.clear_notrace
 | 
			
		||||
local create_server_definition = lsp_helpers.create_server_definition
 | 
			
		||||
 | 
			
		||||
local text = dedent([[
 | 
			
		||||
auto add(int a, int b) { return a + b; }
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
    int x = 1;
 | 
			
		||||
    int y = 2;
 | 
			
		||||
    return add(x,y);
 | 
			
		||||
}
 | 
			
		||||
}]])
 | 
			
		||||
 | 
			
		||||
local response = [==[
 | 
			
		||||
[
 | 
			
		||||
{"kind":1,"paddingLeft":false,"label":"-> int","position":{"character":22,"line":0},"paddingRight":false},
 | 
			
		||||
{"kind":2,"paddingLeft":false,"label":"a:","position":{"character":15,"line":5},"paddingRight":true},
 | 
			
		||||
{"kind":2,"paddingLeft":false,"label":"b:","position":{"character":17,"line":5},"paddingRight":true}
 | 
			
		||||
]
 | 
			
		||||
]==]
 | 
			
		||||
 | 
			
		||||
local grid_without_inlay_hints = [[
 | 
			
		||||
  auto add(int a, int b) { return a + b; }          |
 | 
			
		||||
                                                    |
 | 
			
		||||
  int main() {                                      |
 | 
			
		||||
      int x = 1;                                    |
 | 
			
		||||
      int y = 2;                                    |
 | 
			
		||||
      return add(x,y);                              |
 | 
			
		||||
  }                                                 |
 | 
			
		||||
  ^}                                                 |
 | 
			
		||||
                                                    |
 | 
			
		||||
]]
 | 
			
		||||
 | 
			
		||||
local grid_with_inlay_hints =  [[
 | 
			
		||||
  auto add(int a, int b)-> int { return a + b; }    |
 | 
			
		||||
                                                    |
 | 
			
		||||
  int main() {                                      |
 | 
			
		||||
      int x = 1;                                    |
 | 
			
		||||
      int y = 2;                                    |
 | 
			
		||||
      return add(a: x,b: y);                        |
 | 
			
		||||
  }                                                 |
 | 
			
		||||
  ^}                                                 |
 | 
			
		||||
                                                    |
 | 
			
		||||
]]
 | 
			
		||||
 | 
			
		||||
--- @type test.functional.ui.screen
 | 
			
		||||
local screen
 | 
			
		||||
before_each(function()
 | 
			
		||||
  clear_notrace()
 | 
			
		||||
  screen = Screen.new(50, 9)
 | 
			
		||||
  screen:attach()
 | 
			
		||||
 | 
			
		||||
  exec_lua(create_server_definition)
 | 
			
		||||
  exec_lua([[
 | 
			
		||||
    local response = ...
 | 
			
		||||
    server = _create_server({
 | 
			
		||||
      capabilities = {
 | 
			
		||||
        inlayHintProvider = true,
 | 
			
		||||
      },
 | 
			
		||||
      handlers = {
 | 
			
		||||
        ['textDocument/inlayHint'] = function()
 | 
			
		||||
          return vim.json.decode(response)
 | 
			
		||||
        end,
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    bufnr = vim.api.nvim_get_current_buf()
 | 
			
		||||
    vim.api.nvim_win_set_buf(0, bufnr)
 | 
			
		||||
 | 
			
		||||
    client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
 | 
			
		||||
  ]], response)
 | 
			
		||||
 | 
			
		||||
  insert(text)
 | 
			
		||||
  exec_lua([[vim.lsp.inlay_hint.enable(bufnr)]])
 | 
			
		||||
  screen:expect({ grid = grid_with_inlay_hints })
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
after_each(function()
 | 
			
		||||
  exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })")
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
describe('inlay hints', function()
 | 
			
		||||
  local screen
 | 
			
		||||
  before_each(function()
 | 
			
		||||
    screen = Screen.new(50, 9)
 | 
			
		||||
    screen:attach()
 | 
			
		||||
describe('vim.lsp.inlay_hint', function()
 | 
			
		||||
  it('clears inlay hints when sole client detaches', function()
 | 
			
		||||
    exec_lua([[vim.lsp.stop_client(client_id)]])
 | 
			
		||||
    screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  describe('general', function()
 | 
			
		||||
    local text = dedent([[
 | 
			
		||||
    auto add(int a, int b) { return a + b; }
 | 
			
		||||
  it('does not clear inlay hints when one of several clients detaches', function()
 | 
			
		||||
    exec_lua([[
 | 
			
		||||
      server2 = _create_server({
 | 
			
		||||
        capabilities = {
 | 
			
		||||
          inlayHintProvider = true,
 | 
			
		||||
        },
 | 
			
		||||
        handlers = {
 | 
			
		||||
          ['textDocument/inlayHint'] = function()
 | 
			
		||||
            return {}
 | 
			
		||||
          end,
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })
 | 
			
		||||
      vim.lsp.inlay_hint.enable(bufnr)
 | 
			
		||||
    ]])
 | 
			
		||||
 | 
			
		||||
    int main() {
 | 
			
		||||
        int x = 1;
 | 
			
		||||
        int y = 2;
 | 
			
		||||
        return add(x,y);
 | 
			
		||||
    }
 | 
			
		||||
    }]])
 | 
			
		||||
    exec_lua([[ vim.lsp.stop_client(client2) ]])
 | 
			
		||||
    screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  describe('enable()', function()
 | 
			
		||||
    it('clears/applies inlay hints when passed false/true/nil', function()
 | 
			
		||||
      exec_lua([[vim.lsp.inlay_hint.enable(bufnr, false)]])
 | 
			
		||||
      screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
 | 
			
		||||
 | 
			
		||||
    local response = [==[
 | 
			
		||||
    [
 | 
			
		||||
    {"kind":1,"paddingLeft":false,"label":"-> int","position":{"character":22,"line":0},"paddingRight":false},
 | 
			
		||||
    {"kind":2,"paddingLeft":false,"label":"a:","position":{"character":15,"line":5},"paddingRight":true},
 | 
			
		||||
    {"kind":2,"paddingLeft":false,"label":"b:","position":{"character":17,"line":5},"paddingRight":true}
 | 
			
		||||
    ]
 | 
			
		||||
    ]==]
 | 
			
		||||
      exec_lua([[vim.lsp.inlay_hint.enable(bufnr, true)]])
 | 
			
		||||
      screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
 | 
			
		||||
 | 
			
		||||
      exec_lua([[vim.lsp.inlay_hint.enable(bufnr, not vim.lsp.inlay_hint.is_enabled(bufnr))]])
 | 
			
		||||
      screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
 | 
			
		||||
 | 
			
		||||
      exec_lua([[vim.lsp.inlay_hint.enable(bufnr)]])
 | 
			
		||||
      screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
 | 
			
		||||
    end)
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  describe('get()', function()
 | 
			
		||||
    it('returns filtered inlay hints', function()
 | 
			
		||||
      --- @type lsp.InlayHint[]
 | 
			
		||||
      local expected = vim.json.decode(response)
 | 
			
		||||
      local expected2 = {
 | 
			
		||||
        kind = 1,
 | 
			
		||||
        paddingLeft = false,
 | 
			
		||||
        label = ': int',
 | 
			
		||||
        position = {
 | 
			
		||||
          character = 10,
 | 
			
		||||
          line = 2,
 | 
			
		||||
        },
 | 
			
		||||
        paddingRight = false,
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    before_each(function()
 | 
			
		||||
      exec_lua(create_server_definition)
 | 
			
		||||
      exec_lua([[
 | 
			
		||||
        local response = ...
 | 
			
		||||
        server = _create_server({
 | 
			
		||||
        local expected2 = ...
 | 
			
		||||
        server2 = _create_server({
 | 
			
		||||
          capabilities = {
 | 
			
		||||
            inlayHintProvider = true,
 | 
			
		||||
          },
 | 
			
		||||
          handlers = {
 | 
			
		||||
            ['textDocument/inlayHint'] = function()
 | 
			
		||||
              return vim.json.decode(response)
 | 
			
		||||
              return { expected2 }
 | 
			
		||||
            end,
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
      ]], response)
 | 
			
		||||
        client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })
 | 
			
		||||
        vim.lsp.inlay_hint.enable(bufnr)
 | 
			
		||||
      ]], expected2)
 | 
			
		||||
 | 
			
		||||
      --- @type vim.lsp.inlay_hint.get.ret
 | 
			
		||||
      local res = exec_lua([[return vim.lsp.inlay_hint.get()]])
 | 
			
		||||
      eq(res, {
 | 
			
		||||
        { bufnr = 1, client_id = 1, inlay_hint = expected[1] },
 | 
			
		||||
        { bufnr = 1, client_id = 1, inlay_hint = expected[2] },
 | 
			
		||||
        { bufnr = 1, client_id = 1, inlay_hint = expected[3] },
 | 
			
		||||
        { bufnr = 1, client_id = 2, inlay_hint = expected2 },
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      --- @type vim.lsp.inlay_hint.get.ret
 | 
			
		||||
      res = exec_lua([[return vim.lsp.inlay_hint.get({
 | 
			
		||||
        range = {
 | 
			
		||||
          start = { line = 2, character = 10 },
 | 
			
		||||
          ["end"] = { line = 2, character = 10 },
 | 
			
		||||
        },
 | 
			
		||||
      })]])
 | 
			
		||||
      eq(res, {
 | 
			
		||||
        { bufnr = 1, client_id = 2, inlay_hint = expected2 },
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      --- @type vim.lsp.inlay_hint.get.ret
 | 
			
		||||
      res = exec_lua([[return vim.lsp.inlay_hint.get({
 | 
			
		||||
        bufnr = vim.api.nvim_get_current_buf(),
 | 
			
		||||
        range = {
 | 
			
		||||
          start = { line = 4, character = 18 },
 | 
			
		||||
          ["end"] = { line = 5, character = 17 },
 | 
			
		||||
        },
 | 
			
		||||
      })]])
 | 
			
		||||
      eq(res, {
 | 
			
		||||
        { bufnr = 1, client_id = 1, inlay_hint = expected[2] },
 | 
			
		||||
        { bufnr = 1, client_id = 1, inlay_hint = expected[3] },
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      --- @type vim.lsp.inlay_hint.get.ret
 | 
			
		||||
      res = exec_lua([[return vim.lsp.inlay_hint.get({
 | 
			
		||||
        bufnr = vim.api.nvim_get_current_buf() + 1,
 | 
			
		||||
      })]])
 | 
			
		||||
      eq(res, {})
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it(
 | 
			
		||||
      'inlay hints are applied when vim.lsp.inlay_hint(true) is called',
 | 
			
		||||
      function()
 | 
			
		||||
        local res = exec_lua([[
 | 
			
		||||
          bufnr = vim.api.nvim_get_current_buf()
 | 
			
		||||
          vim.api.nvim_win_set_buf(0, bufnr)
 | 
			
		||||
          client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
 | 
			
		||||
          local client = vim.lsp.get_client_by_id(client_id)
 | 
			
		||||
          return {
 | 
			
		||||
            supports_method = client.supports_method("textDocument/inlayHint")
 | 
			
		||||
          }
 | 
			
		||||
        ]])
 | 
			
		||||
        eq(res, { supports_method = true })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        insert(text)
 | 
			
		||||
        exec_lua([[vim.lsp.inlay_hint(bufnr, true)]])
 | 
			
		||||
        screen:expect({
 | 
			
		||||
          grid = [[
 | 
			
		||||
  auto add(int a, int b)-> int { return a + b; }    |
 | 
			
		||||
                                                    |
 | 
			
		||||
  int main() {                                      |
 | 
			
		||||
      int x = 1;                                    |
 | 
			
		||||
      int y = 2;                                    |
 | 
			
		||||
      return add(a: x,b: y);                        |
 | 
			
		||||
  }                                                 |
 | 
			
		||||
  ^}                                                 |
 | 
			
		||||
                                                    |
 | 
			
		||||
]]
 | 
			
		||||
        })
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
    it(
 | 
			
		||||
      'inlay hints are cleared when vim.lsp.inlay_hint(false) is called',
 | 
			
		||||
      function()
 | 
			
		||||
        exec_lua([[
 | 
			
		||||
        bufnr = vim.api.nvim_get_current_buf()
 | 
			
		||||
        vim.api.nvim_win_set_buf(0, bufnr)
 | 
			
		||||
        client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
 | 
			
		||||
      ]])
 | 
			
		||||
 | 
			
		||||
        insert(text)
 | 
			
		||||
        exec_lua([[vim.lsp.inlay_hint(bufnr, true)]])
 | 
			
		||||
        screen:expect({
 | 
			
		||||
          grid = [[
 | 
			
		||||
  auto add(int a, int b)-> int { return a + b; }    |
 | 
			
		||||
                                                    |
 | 
			
		||||
  int main() {                                      |
 | 
			
		||||
      int x = 1;                                    |
 | 
			
		||||
      int y = 2;                                    |
 | 
			
		||||
      return add(a: x,b: y);                        |
 | 
			
		||||
  }                                                 |
 | 
			
		||||
  ^}                                                 |
 | 
			
		||||
                                                    |
 | 
			
		||||
]]
 | 
			
		||||
        })
 | 
			
		||||
        exec_lua([[vim.lsp.inlay_hint(bufnr, false)]])
 | 
			
		||||
        screen:expect({
 | 
			
		||||
          grid = [[
 | 
			
		||||
  auto add(int a, int b) { return a + b; }          |
 | 
			
		||||
                                                    |
 | 
			
		||||
  int main() {                                      |
 | 
			
		||||
      int x = 1;                                    |
 | 
			
		||||
      int y = 2;                                    |
 | 
			
		||||
      return add(x,y);                              |
 | 
			
		||||
  }                                                 |
 | 
			
		||||
  ^}                                                 |
 | 
			
		||||
                                                    |
 | 
			
		||||
]],
 | 
			
		||||
          unchanged = true
 | 
			
		||||
        })
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
    it(
 | 
			
		||||
      'inlay hints are cleared when the client detaches',
 | 
			
		||||
      function()
 | 
			
		||||
        exec_lua([[
 | 
			
		||||
          bufnr = vim.api.nvim_get_current_buf()
 | 
			
		||||
          vim.api.nvim_win_set_buf(0, bufnr)
 | 
			
		||||
          client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
 | 
			
		||||
        ]])
 | 
			
		||||
 | 
			
		||||
        insert(text)
 | 
			
		||||
        exec_lua([[vim.lsp.inlay_hint(bufnr, true)]])
 | 
			
		||||
        screen:expect({
 | 
			
		||||
          grid = [[
 | 
			
		||||
  auto add(int a, int b)-> int { return a + b; }    |
 | 
			
		||||
                                                    |
 | 
			
		||||
  int main() {                                      |
 | 
			
		||||
      int x = 1;                                    |
 | 
			
		||||
      int y = 2;                                    |
 | 
			
		||||
      return add(a: x,b: y);                        |
 | 
			
		||||
  }                                                 |
 | 
			
		||||
  ^}                                                 |
 | 
			
		||||
                                                    |
 | 
			
		||||
]]
 | 
			
		||||
        })
 | 
			
		||||
        exec_lua([[vim.lsp.stop_client(client_id)]])
 | 
			
		||||
        screen:expect({
 | 
			
		||||
          grid = [[
 | 
			
		||||
  auto add(int a, int b) { return a + b; }          |
 | 
			
		||||
                                                    |
 | 
			
		||||
  int main() {                                      |
 | 
			
		||||
      int x = 1;                                    |
 | 
			
		||||
      int y = 2;                                    |
 | 
			
		||||
      return add(x,y);                              |
 | 
			
		||||
  }                                                 |
 | 
			
		||||
  ^}                                                 |
 | 
			
		||||
                                                    |
 | 
			
		||||
]],
 | 
			
		||||
          unchanged = true
 | 
			
		||||
        })
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
    it(
 | 
			
		||||
      'inlay hints are not cleared when one of several clients detaches',
 | 
			
		||||
      function()
 | 
			
		||||
        -- Start two clients
 | 
			
		||||
        exec_lua([[
 | 
			
		||||
          bufnr = vim.api.nvim_get_current_buf()
 | 
			
		||||
          vim.api.nvim_win_set_buf(0, bufnr)
 | 
			
		||||
          server2 = _create_server({
 | 
			
		||||
            capabilities = {
 | 
			
		||||
              inlayHintProvider = true,
 | 
			
		||||
            },
 | 
			
		||||
            handlers = {
 | 
			
		||||
              ['textDocument/inlayHint'] = function()
 | 
			
		||||
                return {}
 | 
			
		||||
              end,
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
          client1 = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
 | 
			
		||||
          client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })
 | 
			
		||||
        ]])
 | 
			
		||||
 | 
			
		||||
        insert(text)
 | 
			
		||||
        exec_lua([[vim.lsp.inlay_hint(bufnr, true)]])
 | 
			
		||||
        screen:expect({
 | 
			
		||||
          grid = [[
 | 
			
		||||
  auto add(int a, int b)-> int { return a + b; }    |
 | 
			
		||||
                                                    |
 | 
			
		||||
  int main() {                                      |
 | 
			
		||||
      int x = 1;                                    |
 | 
			
		||||
      int y = 2;                                    |
 | 
			
		||||
      return add(a: x,b: y);                        |
 | 
			
		||||
  }                                                 |
 | 
			
		||||
  ^}                                                 |
 | 
			
		||||
                                                    |
 | 
			
		||||
]]
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        -- Now stop one client
 | 
			
		||||
        exec_lua([[ vim.lsp.stop_client(client2) ]])
 | 
			
		||||
 | 
			
		||||
        -- We should still see the hints
 | 
			
		||||
        screen:expect({
 | 
			
		||||
          grid = [[
 | 
			
		||||
  auto add(int a, int b)-> int { return a + b; }    |
 | 
			
		||||
                                                    |
 | 
			
		||||
  int main() {                                      |
 | 
			
		||||
      int x = 1;                                    |
 | 
			
		||||
      int y = 2;                                    |
 | 
			
		||||
      return add(a: x,b: y);                        |
 | 
			
		||||
  }                                                 |
 | 
			
		||||
  ^}                                                 |
 | 
			
		||||
                                                    |
 | 
			
		||||
]],
 | 
			
		||||
          unchanged = true
 | 
			
		||||
        })
 | 
			
		||||
      end)
 | 
			
		||||
  end)
 | 
			
		||||
end)
 | 
			
		||||
 
 | 
			
		||||
@@ -1291,7 +1291,7 @@ describe('LSP', function()
 | 
			
		||||
        on_handler = function(err, result, ctx)
 | 
			
		||||
          if ctx.method == 'start' then
 | 
			
		||||
            exec_lua [[
 | 
			
		||||
              vim.lsp.inlay_hint(BUFFER, true)
 | 
			
		||||
              vim.lsp.inlay_hint.enable(BUFFER)
 | 
			
		||||
            ]]
 | 
			
		||||
          end
 | 
			
		||||
          if ctx.method == 'textDocument/inlayHint' then
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,7 @@ local function isempty(v)
 | 
			
		||||
  return type(v) == 'table' and next(v) == nil
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- @class test.functional.ui.screen
 | 
			
		||||
local Screen = {}
 | 
			
		||||
Screen.__index = Screen
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user