mirror of
https://github.com/neovim/neovim.git
synced 2025-11-15 06:49:01 +00:00
fix(lsp): don't overlay insertion-style inline completions (#36477)
* feat(lua): `Range:is_empty()` to check vim.range emptiness * fix(lsp): don't overlay insertion-style inline completions **Problem:** Some servers commonly respond with an empty inline completion range which acts as a position where text should be inserted. However, the inline completion module assumes that all responses with a range are deletions + insertions that thus require an `overlay` display style. This causes an incorrect preview, because the virtual text should have the `inline` display style (to reflect that this is purely an insertion). **Solution:** Only use `overlay` for non-empty replacement ranges.
This commit is contained in:
@@ -4136,6 +4136,7 @@ Provides operations to compare, calculate, and convert ranges represented by
|
|||||||
Fields: ~
|
Fields: ~
|
||||||
• {start} (`vim.Pos`) Start position.
|
• {start} (`vim.Pos`) Start position.
|
||||||
• {end_} (`vim.Pos`) End position, exclusive.
|
• {end_} (`vim.Pos`) End position, exclusive.
|
||||||
|
• {is_empty} (`fun(self: vim.Range): boolean`) See |Range:is_empty()|.
|
||||||
• {has} (`fun(outer: vim.Range, inner: vim.Range): boolean`) See
|
• {has} (`fun(outer: vim.Range, inner: vim.Range): boolean`) See
|
||||||
|Range:has()|.
|
|Range:has()|.
|
||||||
• {intersect} (`fun(r1: vim.Range, r2: vim.Range): vim.Range?`) See
|
• {intersect} (`fun(r1: vim.Range, r2: vim.Range): vim.Range?`) See
|
||||||
@@ -4167,6 +4168,12 @@ Range:intersect({r1}, {r2}) *Range:intersect()*
|
|||||||
(`vim.Range?`) range that is present inside both `r1` and `r2`. `nil`
|
(`vim.Range?`) range that is present inside both `r1` and `r2`. `nil`
|
||||||
if such range does not exist. See |vim.Range|.
|
if such range does not exist. See |vim.Range|.
|
||||||
|
|
||||||
|
Range:is_empty() *Range:is_empty()*
|
||||||
|
Checks whether the given range is empty; i.e., start >= end.
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
(`boolean`) `true` if the given range is empty
|
||||||
|
|
||||||
Range:lsp({buf}, {range}, {position_encoding}) *Range:lsp()*
|
Range:lsp({buf}, {range}, {position_encoding}) *Range:lsp()*
|
||||||
Creates a new |vim.Range| from `lsp.Range`.
|
Creates a new |vim.Range| from `lsp.Range`.
|
||||||
|
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ LUA
|
|||||||
• Experimental `vim.pos` and `vim.range` for Position/Range abstraction.
|
• Experimental `vim.pos` and `vim.range` for Position/Range abstraction.
|
||||||
• |vim.json.encode()| has an `indent` option for pretty-formatting.
|
• |vim.json.encode()| has an `indent` option for pretty-formatting.
|
||||||
• |vim.json.encode()| has an `sort_keys` option.
|
• |vim.json.encode()| has an `sort_keys` option.
|
||||||
|
• |Range:is_empty()| to check if a |vim.Range| is empty.
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
|
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ function Completor:show(hint)
|
|||||||
api.nvim_buf_set_extmark(self.bufnr, namespace, row, col, {
|
api.nvim_buf_set_extmark(self.bufnr, namespace, row, col, {
|
||||||
virt_text = virt_text,
|
virt_text = virt_text,
|
||||||
virt_lines = virt_lines,
|
virt_lines = virt_lines,
|
||||||
virt_text_pos = current.range and 'overlay' or 'inline',
|
virt_text_pos = (current.range and not current.range:is_empty() and 'overlay') or 'inline',
|
||||||
hl_mode = 'combine',
|
hl_mode = 'combine',
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -99,6 +99,13 @@ function Range.__eq(r1, r2)
|
|||||||
return r1.start == r2.start and r1.end_ == r2.end_
|
return r1.start == r2.start and r1.end_ == r2.end_
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Checks whether the given range is empty; i.e., start >= end.
|
||||||
|
---
|
||||||
|
---@return boolean `true` if the given range is empty
|
||||||
|
function Range:is_empty()
|
||||||
|
return self.start >= self.end_
|
||||||
|
end
|
||||||
|
|
||||||
--- Checks whether {outer} range contains {inner} range.
|
--- Checks whether {outer} range contains {inner} range.
|
||||||
---
|
---
|
||||||
---@param outer vim.Range
|
---@param outer vim.Range
|
||||||
|
|||||||
@@ -83,6 +83,27 @@ describe('vim.lsp.inline_completion', function()
|
|||||||
},
|
},
|
||||||
handlers = {
|
handlers = {
|
||||||
['textDocument/inlineCompletion'] = function(_, _, callback)
|
['textDocument/inlineCompletion'] = function(_, _, callback)
|
||||||
|
if _G.empty then
|
||||||
|
callback(nil, {
|
||||||
|
items = {
|
||||||
|
{
|
||||||
|
insertText = 'foobar',
|
||||||
|
range = {
|
||||||
|
start = {
|
||||||
|
line = 0,
|
||||||
|
character = 19,
|
||||||
|
},
|
||||||
|
['end'] = {
|
||||||
|
line = 0,
|
||||||
|
character = 19,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
callback(nil, {
|
callback(nil, {
|
||||||
items = {
|
items = {
|
||||||
{
|
{
|
||||||
@@ -198,6 +219,19 @@ describe('vim.lsp.inline_completion', function()
|
|||||||
screen:expect({ grid = grid_applied_candidates })
|
screen:expect({ grid = grid_applied_candidates })
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('correctly displays with absent/empty range', function()
|
||||||
|
exec_lua(function()
|
||||||
|
_G.empty = true
|
||||||
|
end)
|
||||||
|
feed('I')
|
||||||
|
screen:expect([[
|
||||||
|
function fibonacci({1:foobar}) |
|
||||||
|
^ |
|
||||||
|
{1:~ }|*11
|
||||||
|
{3:-- INSERT --} |
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
it('accepts on_accept callback', function()
|
it('accepts on_accept callback', function()
|
||||||
feed('i')
|
feed('i')
|
||||||
screen:expect({ grid = grid_with_candidates })
|
screen:expect({ grid = grid_with_candidates })
|
||||||
|
|||||||
Reference in New Issue
Block a user