mirror of
https://github.com/neovim/neovim.git
synced 2026-09-02 12:23:50 +00:00
Problem: `Completor:accept()` applies the item against the range the server answered with. Characters typed after the request but before accepting lie inside neither that range nor the replacement, so they are left behind by the insertion: typing `a` after `foob` and accepting `foobar` within the 200 ms debounce yields `foobara`. Solution: Grow the item's range over the characters typed since the request, so that `accept()` replaces them. Only while the candidate still matches, decided by the longest common prefix `show()` already computes, so input that contradicts it is left alone. Snippet items are unaffected, since `accept()` ignores the range for them.
383 lines
13 KiB
Lua
383 lines
13 KiB
Lua
local t = require('test.testutil')
|
|
local n = require('test.functional.testnvim')()
|
|
local t_lsp = require('test.functional.plugin.lsp.testutil')
|
|
local Screen = require('test.functional.ui.screen')
|
|
|
|
local describe, it, before_each, after_each = t.describe, t.it, t.before_each, t.after_each
|
|
local dedent = t.dedent
|
|
local eq = t.eq
|
|
|
|
local api = n.api
|
|
local exec_lua = n.exec_lua
|
|
local insert = n.insert
|
|
local feed = n.feed
|
|
|
|
local clear_notrace = t_lsp.clear_notrace
|
|
local create_server_definition = t_lsp.create_server_definition
|
|
|
|
describe('vim.lsp.inline_completion', function()
|
|
local text = dedent([[
|
|
function fibonacci()
|
|
]])
|
|
|
|
local grid_without_candidates = dedent([[
|
|
function fibonacci() |
|
|
^ |
|
|
{1:~ }|*11
|
|
|
|
|
]])
|
|
|
|
local grid_with_candidates = dedent([[
|
|
function fibonacci({1:n) {} |
|
|
{1: if (n <= 0) return 0;} |
|
|
{1: if (n === 1) return 1;} |
|
|
|
|
|
{1: let a = 0, b = 1, c;} |
|
|
{1: for (let i = 2; i <= n; i++) {} |
|
|
{1: c = a + b;} |
|
|
{1: a = b;} |
|
|
{1: b = c;} |
|
|
{1: }} |
|
|
{1: return b;} |
|
|
{1:}} |
|
|
^ |
|
|
{3:-- INSERT --} |
|
|
]])
|
|
|
|
local grid_applied_candidates = dedent([[
|
|
function fibonacci(n) { |
|
|
if (n <= 0) return 0; |
|
|
if (n === 1) return 1; |
|
|
|
|
|
let a = 0, b = 1, c; |
|
|
for (let i = 2; i <= n; i++) { |
|
|
c = a + b; |
|
|
a = b; |
|
|
b = c; |
|
|
} |
|
|
return b; |
|
|
^} |
|
|
|*2
|
|
]])
|
|
|
|
--- @type test.functional.ui.screen
|
|
local screen
|
|
|
|
--- @type integer
|
|
local client_id
|
|
|
|
before_each(function()
|
|
clear_notrace()
|
|
exec_lua(create_server_definition)
|
|
|
|
screen = Screen.new()
|
|
screen:set_default_attr_ids({
|
|
[1] = { bold = true, foreground = Screen.colors.Blue1 },
|
|
[2] = { bold = true, foreground = Screen.colors.SeaGreen4 },
|
|
[3] = { bold = true },
|
|
})
|
|
|
|
client_id = exec_lua(function()
|
|
_G.server = _G._create_server({
|
|
capabilities = {
|
|
inlineCompletionProvider = true,
|
|
},
|
|
handlers = {
|
|
['textDocument/inlineCompletion'] = function(_, _, callback)
|
|
if _G.items then
|
|
callback(nil, { items = _G.items })
|
|
return
|
|
end
|
|
|
|
if _G.empty then
|
|
callback(nil, {
|
|
items = {
|
|
{
|
|
insertText = 'foobar',
|
|
range = {
|
|
start = {
|
|
line = 0,
|
|
character = 19,
|
|
},
|
|
['end'] = {
|
|
line = 0,
|
|
character = 19,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return
|
|
end
|
|
|
|
callback(nil, {
|
|
items = {
|
|
{
|
|
command = {
|
|
command = 'dummy',
|
|
title = 'Completion Accepted',
|
|
},
|
|
insertText = 'function fibonacci(n) {\n if (n <= 0) return 0;\n if (n === 1) return 1;\n\n let a = 0, b = 1, c;\n for (let i = 2; i <= n; i++) {\n c = a + b;\n a = b;\n b = c;\n }\n return b;\n}',
|
|
range = {
|
|
['end'] = {
|
|
character = 20,
|
|
line = 0,
|
|
},
|
|
start = {
|
|
character = 0,
|
|
line = 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
command = {
|
|
command = 'dummy',
|
|
title = 'Completion Accepted',
|
|
},
|
|
insertText = 'function fibonacci(n) {\n if (n <= 0) return 0;\n if (n === 1) return 1;\n\n let a = 0, b = 1, c;\n for (let i = 2; i <= n; i++) {\n c = a + b;\n a = b;\n b = c;\n }\n return c;\n}',
|
|
range = {
|
|
['end'] = {
|
|
character = 20,
|
|
line = 0,
|
|
},
|
|
start = {
|
|
character = 0,
|
|
line = 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
command = {
|
|
command = 'dummy',
|
|
title = 'Completion Accepted',
|
|
},
|
|
insertText = 'function fibonacci(n) {\n if (n < 0) {\n throw new Error("Input must be a non-negative integer.");\n }\n if (n === 0) return 0;\n if (n === 1) return 1;\n\n let a = 0, b = 1, c;\n for (let i = 2; i <= n; i++) {\n c = a + b;\n a = b;\n b = c;\n }\n return b;\n}',
|
|
range = {
|
|
['end'] = {
|
|
character = 20,
|
|
line = 0,
|
|
},
|
|
start = {
|
|
character = 0,
|
|
line = 0,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
end,
|
|
},
|
|
})
|
|
|
|
return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })
|
|
end)
|
|
|
|
exec_lua(function()
|
|
local client = assert(vim.lsp.get_client_by_id(client_id))
|
|
_G.called = false
|
|
client.commands.dummy = function()
|
|
_G.called = true
|
|
end
|
|
end)
|
|
|
|
insert(text)
|
|
feed('$')
|
|
exec_lua(function()
|
|
vim.lsp.inline_completion.enable()
|
|
end)
|
|
end)
|
|
|
|
after_each(function()
|
|
api.nvim_exec_autocmds('VimLeavePre', { modeline = false })
|
|
end)
|
|
|
|
describe('enable()', function()
|
|
it('requests or abort when entered/left insert mode', function()
|
|
screen:expect({ grid = grid_without_candidates })
|
|
feed('i')
|
|
screen:expect({ grid = grid_with_candidates })
|
|
feed('<Esc>')
|
|
screen:expect({ grid = grid_without_candidates })
|
|
end)
|
|
|
|
it('no request when leaving insert mode immediately after typing', function()
|
|
screen:expect({ grid = grid_without_candidates })
|
|
feed('ifoobar<Esc>')
|
|
screen:expect([[
|
|
function fibonacci() |
|
|
fooba^r |
|
|
{1:~ }|*11
|
|
|
|
|
]])
|
|
screen:expect_unchanged(false, 500)
|
|
end)
|
|
end)
|
|
|
|
describe('get()', function()
|
|
it('applies the current candidate', function()
|
|
feed('i')
|
|
screen:expect({ grid = grid_with_candidates })
|
|
exec_lua(function()
|
|
vim.lsp.inline_completion.get()
|
|
end)
|
|
n.poke_eventloop()
|
|
feed('<Esc>')
|
|
screen:expect({ grid = grid_applied_candidates })
|
|
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()
|
|
feed('i')
|
|
screen:expect({ grid = grid_with_candidates })
|
|
local result = exec_lua(function()
|
|
---@type vim.lsp.inline_completion.Item
|
|
local result
|
|
vim.lsp.inline_completion.get({
|
|
on_accept = function(item)
|
|
result = item
|
|
end,
|
|
})
|
|
vim.wait(1000, function()
|
|
return result ~= nil
|
|
end) -- Wait for async callback.
|
|
return result
|
|
end)
|
|
feed('<Esc>')
|
|
screen:expect({ grid = grid_without_candidates })
|
|
eq({
|
|
_index = 1,
|
|
client_id = 1,
|
|
command = {
|
|
command = 'dummy',
|
|
title = 'Completion Accepted',
|
|
},
|
|
insert_text = dedent([[
|
|
function fibonacci(n) {
|
|
if (n <= 0) return 0;
|
|
if (n === 1) return 1;
|
|
|
|
let a = 0, b = 1, c;
|
|
for (let i = 2; i <= n; i++) {
|
|
c = a + b;
|
|
a = b;
|
|
b = c;
|
|
}
|
|
return b;
|
|
}]]),
|
|
range = {
|
|
0,
|
|
0,
|
|
0,
|
|
20,
|
|
1,
|
|
},
|
|
}, result)
|
|
end)
|
|
|
|
it('accepts an item after the line shrank past its range', function()
|
|
feed('i')
|
|
screen:expect({ grid = grid_with_candidates })
|
|
exec_lua(function()
|
|
-- Shrink the line the item's range covers, as backspacing would.
|
|
vim.api.nvim_buf_set_text(0, 0, 18, 0, 20, {})
|
|
vim.lsp.inline_completion.get()
|
|
end)
|
|
n.poke_eventloop()
|
|
feed('<Esc>')
|
|
screen:expect({ grid = grid_applied_candidates })
|
|
end)
|
|
|
|
it('does not leave behind text typed since the request', function()
|
|
exec_lua(function()
|
|
_G.items = {
|
|
{
|
|
insertText = 'foobar',
|
|
range = {
|
|
start = { line = 1, character = 0 },
|
|
['end'] = { line = 1, character = 1 },
|
|
},
|
|
},
|
|
}
|
|
end)
|
|
feed('ifo')
|
|
screen:expect([[
|
|
function fibonacci() |
|
|
fo{1:^obar} |
|
|
{1:~ }|*11
|
|
{3:-- INSERT --} |
|
|
]])
|
|
exec_lua(function()
|
|
vim.lsp.inline_completion.get()
|
|
end)
|
|
n.poke_eventloop()
|
|
feed('<Esc>')
|
|
screen:expect([[
|
|
function fibonacci() |
|
|
fooba^r |
|
|
{1:~ }|*11
|
|
|
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
describe('select()', function()
|
|
it('selects the next candidate', function()
|
|
feed('i')
|
|
screen:expect({ grid = grid_with_candidates })
|
|
|
|
exec_lua(function()
|
|
vim.lsp.inline_completion.select()
|
|
end)
|
|
|
|
screen:expect([[
|
|
function fibonacci({1:n) {} |
|
|
{1: if (n <= 0) return 0;} |
|
|
{1: if (n === 1) return 1;} |
|
|
|
|
|
{1: let a = 0, b = 1, c;} |
|
|
{1: for (let i = 2; i <= n; i++) {} |
|
|
{1: c = a + b;} |
|
|
{1: a = b;} |
|
|
{1: b = c;} |
|
|
{1: }} |
|
|
{1: return c;} |
|
|
{1:}}{2: (2/3)} |
|
|
^ |
|
|
{3:-- INSERT --} |
|
|
]])
|
|
exec_lua(function()
|
|
vim.lsp.inline_completion.get()
|
|
end)
|
|
n.poke_eventloop()
|
|
feed('<Esc>')
|
|
screen:expect([[
|
|
function fibonacci(n) { |
|
|
if (n <= 0) return 0; |
|
|
if (n === 1) return 1; |
|
|
|
|
|
let a = 0, b = 1, c; |
|
|
for (let i = 2; i <= n; i++) { |
|
|
c = a + b; |
|
|
a = b; |
|
|
b = c; |
|
|
} |
|
|
return c; |
|
|
^} |
|
|
|*2
|
|
]])
|
|
end)
|
|
end)
|
|
end)
|