mirror of
https://github.com/neovim/neovim.git
synced 2026-07-23 17:32:51 +00:00
Problem: With a non-zero 'autocompletedelay', Insert-mode autocommands
(TextChangedI, TextChangedP, CursorMovedI) are delayed, and
while typing faster than the delay they are dropped entirely,
because the delay blocks the main loop.
Solution: Make 'autocompletedelay' non-blocking: instead of busy-waiting
before showing the popup menu, defer it with an input-wait
timeout (K_COMPLETE_DELAY) modeled on CursorHoldI, so typing
stays responsive and the Insert-mode autocommands fire normally.
The delay timer coexists with 'updatetime': the main loop waits for the
sooner of the two and triggers the event whose deadline was reached, so
'autocompletedelay' no longer shadows CursorHold timing. Changing the
completion leader, for example with Backspace, updates the visible popup
immediately like a zero delay; only the first popup is deferred.
Update the 'autocompletedelay' screendumps for the non-blocking display.
One test opened the menu with CTRL-N right after the delay expired and
could race with the deferred popup, so it now waits a little longer than
the delay before sending the key.
fixes: vim/vim#20591
closes: vim/vim#20598
8ce43ea4e3
Also include some insexpand.c and ui.c changes from patch 9.2.0750.
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1828 lines
80 KiB
Lua
1828 lines
80 KiB
Lua
local t = require('test.testutil')
|
|
local pcall_err = t.pcall_err
|
|
local n = require('test.functional.testnvim')()
|
|
local Screen = require('test.functional.ui.screen')
|
|
|
|
local assert_alive = n.assert_alive
|
|
local clear, feed = n.clear, n.feed
|
|
local eval, eq, ok = n.eval, t.eq, t.ok
|
|
local source, expect = n.source, n.expect
|
|
local fn = n.fn
|
|
local command = n.command
|
|
local api = n.api
|
|
local poke_eventloop = n.poke_eventloop
|
|
local exec_lua = n.exec_lua
|
|
|
|
describe('completion', function()
|
|
local screen
|
|
|
|
before_each(function()
|
|
clear()
|
|
source([[
|
|
set completeopt-=noselect
|
|
" Avoid tags completion (if running test locally).
|
|
set complete-=t
|
|
]])
|
|
screen = Screen.new(60, 8)
|
|
screen:add_extra_attr_ids {
|
|
[100] = { foreground = Screen.colors.Gray0, background = Screen.colors.Yellow },
|
|
[101] = { background = Screen.colors.Gray0 },
|
|
[102] = { foreground = Screen.colors.SeaGreen },
|
|
}
|
|
end)
|
|
|
|
it('ctrl-x_ctrl-f completes Windows drive letter', function()
|
|
t.skip(not t.is_os('win'), 'N/A for non-Windows')
|
|
feed('iblablaC:/W<C-x><C-f>')
|
|
screen:expect {
|
|
any = [[C:\Windows\]],
|
|
}
|
|
end)
|
|
|
|
describe('v:completed_item', function()
|
|
it('is empty dict until completion', function()
|
|
eq({}, eval('v:completed_item'))
|
|
end)
|
|
it('is empty dict if the candidate is not inserted', function()
|
|
feed('ifoo<ESC>o<C-x><C-n>')
|
|
screen:expect([[
|
|
foo |
|
|
foo^ |
|
|
{1:~ }|*5
|
|
{5:-- Keyword Local completion (^N^P) The only match} |
|
|
]])
|
|
feed('<C-e>')
|
|
screen:expect([[
|
|
foo |
|
|
^ |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<ESC>')
|
|
eq({}, eval('v:completed_item'))
|
|
end)
|
|
it('returns expected dict in normal completion', function()
|
|
feed('ifoo<ESC>o<C-x><C-n>')
|
|
eq('foo', eval('getline(2)'))
|
|
eq(
|
|
{ word = 'foo', abbr = '', menu = '', info = '', kind = '', user_data = '' },
|
|
eval('v:completed_item')
|
|
)
|
|
end)
|
|
it('is readonly', function()
|
|
screen:try_resize(80, 8)
|
|
feed('ifoo<ESC>o<C-x><C-n><ESC>')
|
|
t.matches('E46%: ', t.pcall_err(command, 'let v:completed_item.word = "bar"'))
|
|
t.matches('E46%: ', t.pcall_err(command, 'let v:completed_item.abbr = "bar"'))
|
|
t.matches('E46%: ', t.pcall_err(command, 'let v:completed_item.menu = "bar"'))
|
|
t.matches('E46%: ', t.pcall_err(command, 'let v:completed_item.info = "bar"'))
|
|
t.matches('E46%: ', t.pcall_err(command, 'let v:completed_item.kind = "bar"'))
|
|
t.matches('E46%: ', t.pcall_err(command, 'let v:completed_item.user_data = "bar"'))
|
|
end)
|
|
it('returns expected dict in omni completion', function()
|
|
source([[
|
|
function! TestOmni(findstart, base) abort
|
|
return a:findstart ? 0 : [{'word': 'foo', 'abbr': 'bar',
|
|
\ 'menu': 'baz', 'info': 'foobar', 'kind': 'foobaz'},
|
|
\ {'word': 'word', 'abbr': 'abbr', 'menu': 'menu',
|
|
\ 'info': 'info', 'kind': 'kind'}]
|
|
endfunction
|
|
setlocal omnifunc=TestOmni
|
|
]])
|
|
feed('i<C-x><C-o>')
|
|
eq('foo', eval('getline(1)'))
|
|
screen:expect([[
|
|
foo^ |
|
|
{12:bar foobaz baz }{1: }|
|
|
{4:abbr kind menu }{1: }|
|
|
{1:~ }|*4
|
|
{5:-- Omni completion (^O^N^P) }{6:match 1 of 2} |
|
|
]])
|
|
eq({
|
|
word = 'foo',
|
|
abbr = 'bar',
|
|
menu = 'baz',
|
|
info = 'foobar',
|
|
kind = 'foobaz',
|
|
user_data = '',
|
|
}, eval('v:completed_item'))
|
|
end)
|
|
end)
|
|
|
|
describe('completeopt', function()
|
|
before_each(function()
|
|
source([[
|
|
function! TestComplete() abort
|
|
call complete(1, ['foo'])
|
|
return ''
|
|
endfunction
|
|
]])
|
|
end)
|
|
|
|
it('inserts the first candidate if default', function()
|
|
command('set completeopt+=menuone')
|
|
feed('ifoo<ESC>o')
|
|
screen:expect([[
|
|
foo |
|
|
^ |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<C-x>')
|
|
-- the ^X prompt, only test this once
|
|
screen:expect([[
|
|
foo |
|
|
^ |
|
|
{1:~ }|*5
|
|
{5:-- ^X mode (^]^D^E^F^I^K^L^N^O^P^Rs^U^V^Y)} |
|
|
]])
|
|
feed('<C-n>')
|
|
screen:expect([[
|
|
foo |
|
|
foo^ |
|
|
{12:foo }{1: }|
|
|
{1:~ }|*4
|
|
{5:-- Keyword Local completion (^N^P) The only match} |
|
|
]])
|
|
feed('bar<ESC>')
|
|
eq('foobar', eval('getline(2)'))
|
|
feed('o<C-r>=TestComplete()<CR>')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foo^ |
|
|
{12:foo }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- INSERT --} |
|
|
]])
|
|
eq('foo', eval('getline(3)'))
|
|
end)
|
|
it('selects the first candidate if noinsert', function()
|
|
command('set completeopt+=menuone,noinsert')
|
|
feed('ifoo<ESC>o<C-x><C-n>')
|
|
screen:expect([[
|
|
foo |
|
|
^ |
|
|
{12:foo }{1: }|
|
|
{1:~ }|*4
|
|
{5:-- Keyword Local completion (^N^P) The only match} |
|
|
]])
|
|
feed('<C-y>')
|
|
screen:expect([[
|
|
foo |
|
|
foo^ |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<ESC>')
|
|
eq('foo', eval('getline(2)'))
|
|
feed('o<C-r>=TestComplete()<CR>')
|
|
screen:expect([[
|
|
foo |*2
|
|
^ |
|
|
{12:foo }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<C-y><ESC>')
|
|
eq('foo', eval('getline(3)'))
|
|
end)
|
|
it('does not insert the first candidate if noselect', function()
|
|
command('set completeopt+=menuone,noselect')
|
|
feed('ifoo<ESC>o<C-x><C-n>')
|
|
screen:expect([[
|
|
foo |
|
|
^ |
|
|
{4:foo }{1: }|
|
|
{1:~ }|*4
|
|
{5:-- Keyword Local completion (^N^P) }{19:Back at original} |
|
|
]])
|
|
feed('b')
|
|
screen:expect([[
|
|
foo |
|
|
b^ |
|
|
{1:~ }|*5
|
|
{5:-- Keyword Local completion (^N^P) }{19:Back at original} |
|
|
]])
|
|
feed('ar<ESC>')
|
|
eq('bar', eval('getline(2)'))
|
|
feed('o<C-r>=TestComplete()<CR>')
|
|
screen:expect([[
|
|
foo |
|
|
bar |
|
|
^ |
|
|
{4:foo }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('bar<ESC>')
|
|
eq('bar', eval('getline(3)'))
|
|
end)
|
|
it('does not select/insert the first candidate if noselect and noinsert', function()
|
|
command('set completeopt+=menuone,noselect,noinsert')
|
|
feed('ifoo<ESC>o<C-x><C-n>')
|
|
screen:expect([[
|
|
foo |
|
|
^ |
|
|
{4:foo }{1: }|
|
|
{1:~ }|*4
|
|
{5:-- Keyword Local completion (^N^P) }{19:Back at original} |
|
|
]])
|
|
feed('<ESC>')
|
|
screen:expect([[
|
|
foo |
|
|
^ |
|
|
{1:~ }|*5
|
|
|
|
|
]])
|
|
eq('', eval('getline(2)'))
|
|
feed('o<C-r>=TestComplete()<CR>')
|
|
screen:expect([[
|
|
foo |
|
|
|
|
|
^ |
|
|
{4:foo }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<ESC>')
|
|
screen:expect([[
|
|
foo |
|
|
|
|
|
^ |
|
|
{1:~ }|*4
|
|
|
|
|
]])
|
|
eq('', eval('getline(3)'))
|
|
end)
|
|
it('does not change modified state if noinsert', function()
|
|
command('set completeopt+=menuone,noinsert')
|
|
command('setlocal nomodified')
|
|
feed('i<C-r>=TestComplete()<CR><ESC>')
|
|
eq(0, eval('&l:modified'))
|
|
end)
|
|
it('does not change modified state if noselect', function()
|
|
command('set completeopt+=menuone,noselect')
|
|
command('setlocal nomodified')
|
|
feed('i<C-r>=TestComplete()<CR><ESC>')
|
|
eq(0, eval('&l:modified'))
|
|
end)
|
|
|
|
describe('"preselect"', function()
|
|
it('selects first item.preselect item', function()
|
|
source([[
|
|
function! TestComplete() abort
|
|
call complete(1, [
|
|
\ {'word': 'aaa'},
|
|
\ {'word': 'bbb'},
|
|
\ {'word': 'ccc', 'preselect': v:true},
|
|
\ {'word': 'ddd'},
|
|
\ ])
|
|
return ''
|
|
endfunction
|
|
function! TestCompleteMany() abort
|
|
let items = []
|
|
for i in range(20)
|
|
call add(items, {'word': 'item' .. i})
|
|
endfor
|
|
let items[15].preselect = v:true
|
|
call complete(1, items)
|
|
return ''
|
|
endfunction
|
|
]])
|
|
|
|
command('set completeopt=menuone,preselect')
|
|
feed('i<C-r>=TestComplete()<CR>')
|
|
screen:expect([[
|
|
ccc^ |
|
|
{4:aaa }{1: }|
|
|
{4:bbb }{1: }|
|
|
{12:ccc }{1: }|
|
|
{4:ddd }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
|
|
-- scrolls pum when preselect item is far down
|
|
feed('<Esc>S')
|
|
command('set pumheight=5')
|
|
feed('<C-r>=TestCompleteMany()<CR>')
|
|
screen:expect([[
|
|
item15^ |
|
|
{4:item13 }{12: }{1: }|
|
|
{4:item14 }{12: }{1: }|
|
|
{12:item15 }{1: }|
|
|
{4:item16 }{101: }{1: }|
|
|
{4:item17 }{12: }{1: }|
|
|
{1:~ }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
|
|
feed('<Esc>S')
|
|
command('set completeopt=menuone,noselect,preselect pumheight=0')
|
|
feed('<C-r>=TestComplete()<CR>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:aaa }{1: }|
|
|
{4:bbb }{1: }|
|
|
{12:ccc }{1: }|
|
|
{4:ddd }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
|
|
-- without "preselect" in completeopt, preselect attribute is ignored
|
|
feed('<Esc>S')
|
|
command('set completeopt-=preselect')
|
|
feed('<C-r>=TestComplete()<CR><C-N><C-Y>')
|
|
eq('aaa', api.nvim_get_current_line())
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
describe('completeopt+=noinsert does not add blank undo items', function()
|
|
before_each(function()
|
|
source([[
|
|
function! TestComplete() abort
|
|
call complete(1, ['foo', 'bar'])
|
|
return ''
|
|
endfunction
|
|
]])
|
|
command('set completeopt+=noselect,noinsert')
|
|
command('inoremap <right> <c-r>=TestComplete()<cr>')
|
|
end)
|
|
|
|
local tests = {
|
|
['<up>, <down>, <cr>'] = { '<down><cr>', '<up><cr>' },
|
|
['<c-n>, <c-p>, <c-y>'] = { '<c-n><c-y>', '<c-p><c-y>' },
|
|
}
|
|
|
|
for name, seq in pairs(tests) do
|
|
it('using ' .. name, function()
|
|
feed('iaaa<esc>')
|
|
feed('A<right>' .. seq[1] .. '<esc>')
|
|
feed('A<right><esc>A<right><esc>')
|
|
feed('A<cr>bbb<esc>')
|
|
feed('A<right>' .. seq[2] .. '<esc>')
|
|
feed('A<right><esc>A<right><esc>')
|
|
feed('A<cr>ccc<esc>')
|
|
feed('A<right>' .. seq[1] .. '<esc>')
|
|
feed('A<right><esc>A<right><esc>')
|
|
|
|
local expected = {
|
|
{ 'foo', 'bar', 'foo' },
|
|
{ 'foo', 'bar', 'ccc' },
|
|
{ 'foo', 'bar' },
|
|
{ 'foo', 'bbb' },
|
|
{ 'foo' },
|
|
{ 'aaa' },
|
|
{ '' },
|
|
}
|
|
|
|
for i = 1, #expected do
|
|
if i > 1 then
|
|
feed('u')
|
|
end
|
|
eq(expected[i], eval('getline(1, "$")'))
|
|
end
|
|
|
|
for i = #expected, 1, -1 do
|
|
if i < #expected then
|
|
feed('<c-r>')
|
|
end
|
|
eq(expected[i], eval('getline(1, "$")'))
|
|
end
|
|
end)
|
|
end
|
|
end)
|
|
|
|
describe('with refresh:always and noselect', function()
|
|
before_each(function()
|
|
source([[
|
|
function! TestCompletion(findstart, base) abort
|
|
if a:findstart
|
|
let line = getline('.')
|
|
let start = col('.') - 1
|
|
while start > 0 && line[start - 1] =~ '\a'
|
|
let start -= 1
|
|
endwhile
|
|
return start
|
|
else
|
|
let ret = []
|
|
for m in split("January February March April May June July August September October November December")
|
|
if m =~ a:base " match by regex
|
|
call add(ret, m)
|
|
endif
|
|
endfor
|
|
return {'words':ret, 'refresh':'always'}
|
|
endif
|
|
endfunction
|
|
|
|
set completeopt=menuone,noselect
|
|
set completefunc=TestCompletion
|
|
]])
|
|
end)
|
|
|
|
it('completes on each input char', function()
|
|
feed('i<C-x><C-u>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:January }{101: }{1: }|
|
|
{4:February }{101: }{1: }|
|
|
{4:March }{101: }{1: }|
|
|
{4:April }{12: }{1: }|
|
|
{4:May }{12: }{1: }|
|
|
{4:June }{12: }{1: }|
|
|
{5:-- User defined completion (^U^N^P) }{19:Back at original} |
|
|
]])
|
|
feed('u')
|
|
screen:expect([[
|
|
u^ |
|
|
{4:January }{1: }|
|
|
{4:February }{1: }|
|
|
{4:June }{1: }|
|
|
{4:July }{1: }|
|
|
{4:August }{1: }|
|
|
{1:~ }|
|
|
{5:-- User defined completion (^U^N^P) }{19:Back at original} |
|
|
]])
|
|
feed('g')
|
|
screen:expect([[
|
|
ug^ |
|
|
{4:August }{1: }|
|
|
{1:~ }|*5
|
|
{5:-- User defined completion (^U^N^P) }{19:Back at original} |
|
|
]])
|
|
feed('<Down>')
|
|
screen:expect([[
|
|
ug^ |
|
|
{12:August }{1: }|
|
|
{1:~ }|*5
|
|
{5:-- User defined completion (^U^N^P) The only match} |
|
|
]])
|
|
feed('<C-y>')
|
|
screen:expect([[
|
|
August^ |
|
|
{1:~ }|*6
|
|
{5:-- INSERT --} |
|
|
]])
|
|
expect('August')
|
|
end)
|
|
|
|
it('repeats correctly after backspace #2674', function()
|
|
feed('o<C-x><C-u>Ja')
|
|
screen:expect([[
|
|
|
|
|
Ja^ |
|
|
{4:January }{1: }|
|
|
{1:~ }|*4
|
|
{5:-- User defined completion (^U^N^P) }{19:Back at original} |
|
|
]])
|
|
feed('<BS>')
|
|
screen:expect([[
|
|
|
|
|
J^ |
|
|
{4:January }{1: }|
|
|
{4:June }{1: }|
|
|
{4:July }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- User defined completion (^U^N^P) }{19:Back at original} |
|
|
]])
|
|
feed('<C-n>')
|
|
screen:expect([[
|
|
|
|
|
January^ |
|
|
{12:January }{1: }|
|
|
{4:June }{1: }|
|
|
{4:July }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- User defined completion (^U^N^P) }{6:match 1 of 3} |
|
|
]])
|
|
feed('<C-n>')
|
|
screen:expect([[
|
|
|
|
|
June^ |
|
|
{4:January }{1: }|
|
|
{12:June }{1: }|
|
|
{4:July }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- User defined completion (^U^N^P) }{6:match 2 of 3} |
|
|
]])
|
|
feed('<Esc>')
|
|
screen:expect([[
|
|
|
|
|
Jun^e |
|
|
{1:~ }|*5
|
|
|
|
|
]])
|
|
feed('.')
|
|
screen:expect([[
|
|
|
|
|
June |
|
|
Jun^e |
|
|
{1:~ }|*4
|
|
|
|
|
]])
|
|
expect([[
|
|
|
|
June
|
|
June]])
|
|
end)
|
|
|
|
it('Enter inserts newline at original text after adding leader', function()
|
|
feed('iJ<C-x><C-u>')
|
|
poke_eventloop()
|
|
feed('u')
|
|
poke_eventloop()
|
|
feed('<CR>')
|
|
expect([[
|
|
Ju
|
|
]])
|
|
feed('J<C-x><C-u>')
|
|
poke_eventloop()
|
|
feed('<CR>')
|
|
expect([[
|
|
Ju
|
|
J
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
describe('with noselect but not refresh:always', function()
|
|
before_each(function()
|
|
source([[
|
|
function! TestCompletion(findstart, base) abort
|
|
if a:findstart
|
|
let line = getline('.')
|
|
let start = col('.') - 1
|
|
while start > 0 && line[start - 1] =~ '\a'
|
|
let start -= 1
|
|
endwhile
|
|
return start
|
|
else
|
|
let ret = []
|
|
for m in split("January February March April May June July August September October November December")
|
|
if m =~ a:base " match by regex
|
|
call add(ret, m)
|
|
endif
|
|
endfor
|
|
return {'words':ret}
|
|
endif
|
|
endfunction
|
|
|
|
set completeopt=menuone,noselect
|
|
set completefunc=TestCompletion
|
|
]])
|
|
end)
|
|
|
|
it('Enter inserts newline at original text after adding leader', function()
|
|
feed('iJ<C-x><C-u>')
|
|
poke_eventloop()
|
|
feed('u')
|
|
poke_eventloop()
|
|
feed('<CR>')
|
|
expect([[
|
|
Ju
|
|
]])
|
|
feed('<Esc>')
|
|
poke_eventloop()
|
|
-- The behavior should be the same when completion has been interrupted,
|
|
-- which can happen interactively if the completion function is slow.
|
|
feed('ggVGSJ<C-x><C-u>u<CR>')
|
|
expect([[
|
|
Ju
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
describe('with a lot of items', function()
|
|
before_each(function()
|
|
source([[
|
|
function! TestComplete() abort
|
|
call complete(1, map(range(0,100), "string(v:val)"))
|
|
return ''
|
|
endfunction
|
|
]])
|
|
command('set completeopt=menuone,noselect')
|
|
end)
|
|
|
|
it('works', function()
|
|
feed('i<C-r>=TestComplete()<CR>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:0 }{101: }{1: }|
|
|
{4:1 }{12: }{1: }|
|
|
{4:2 }{12: }{1: }|
|
|
{4:3 }{12: }{1: }|
|
|
{4:4 }{12: }{1: }|
|
|
{4:5 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('7')
|
|
screen:expect([[
|
|
7^ |
|
|
{4:7 }{101: }{1: }|
|
|
{4:70 }{101: }{1: }|
|
|
{4:71 }{101: }{1: }|
|
|
{4:72 }{12: }{1: }|
|
|
{4:73 }{12: }{1: }|
|
|
{4:74 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<c-n>')
|
|
screen:expect([[
|
|
7^ |
|
|
{12:7 }{101: }{1: }|
|
|
{4:70 }{101: }{1: }|
|
|
{4:71 }{101: }{1: }|
|
|
{4:72 }{12: }{1: }|
|
|
{4:73 }{12: }{1: }|
|
|
{4:74 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<c-n>')
|
|
screen:expect([[
|
|
70^ |
|
|
{4:7 }{101: }{1: }|
|
|
{12:70 }{101: }{1: }|
|
|
{4:71 }{101: }{1: }|
|
|
{4:72 }{12: }{1: }|
|
|
{4:73 }{12: }{1: }|
|
|
{4:74 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
end)
|
|
|
|
it('can be navigated with <PageDown>, <PageUp>', function()
|
|
feed('i<C-r>=TestComplete()<CR>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:0 }{101: }{1: }|
|
|
{4:1 }{12: }{1: }|
|
|
{4:2 }{12: }{1: }|
|
|
{4:3 }{12: }{1: }|
|
|
{4:4 }{12: }{1: }|
|
|
{4:5 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<PageDown>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:0 }{101: }{1: }|
|
|
{4:1 }{12: }{1: }|
|
|
{4:2 }{12: }{1: }|
|
|
{12:3 }{1: }|
|
|
{4:4 }{12: }{1: }|
|
|
{4:5 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<PageDown>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:5 }{101: }{1: }|
|
|
{4:6 }{12: }{1: }|
|
|
{12:7 }{1: }|
|
|
{4:8 }{12: }{1: }|
|
|
{4:9 }{12: }{1: }|
|
|
{4:10 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<Down>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:5 }{101: }{1: }|
|
|
{4:6 }{12: }{1: }|
|
|
{4:7 }{12: }{1: }|
|
|
{12:8 }{1: }|
|
|
{4:9 }{12: }{1: }|
|
|
{4:10 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<PageUp>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:2 }{101: }{1: }|
|
|
{4:3 }{12: }{1: }|
|
|
{12:4 }{1: }|
|
|
{4:5 }{12: }{1: }|
|
|
{4:6 }{12: }{1: }|
|
|
{4:7 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<PageUp>') -- stop on first item
|
|
screen:expect([[
|
|
^ |
|
|
{12:0 }{101: }{1: }|
|
|
{4:1 }{12: }{1: }|
|
|
{4:2 }{12: }{1: }|
|
|
{4:3 }{12: }{1: }|
|
|
{4:4 }{12: }{1: }|
|
|
{4:5 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<PageUp>') -- when on first item, unselect
|
|
screen:expect([[
|
|
^ |
|
|
{4:0 }{101: }{1: }|
|
|
{4:1 }{12: }{1: }|
|
|
{4:2 }{12: }{1: }|
|
|
{4:3 }{12: }{1: }|
|
|
{4:4 }{12: }{1: }|
|
|
{4:5 }{12: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<PageUp>') -- when unselected, select last item
|
|
screen:expect([[
|
|
^ |
|
|
{4:95 }{12: }{1: }|
|
|
{4:96 }{12: }{1: }|
|
|
{4:97 }{12: }{1: }|
|
|
{4:98 }{12: }{1: }|
|
|
{4:99 }{12: }{1: }|
|
|
{12:100 }{101: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<PageUp>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:94 }{12: }{1: }|
|
|
{4:95 }{12: }{1: }|
|
|
{12:96 }{1: }|
|
|
{4:97 }{12: }{1: }|
|
|
{4:98 }{12: }{1: }|
|
|
{4:99 }{101: }{1: }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<cr>')
|
|
screen:expect([[
|
|
96^ |
|
|
{1:~ }|*6
|
|
{5:-- INSERT --} |
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
it('does not indent until an item is selected #8345', function()
|
|
-- Indents on "ind", unindents on "unind".
|
|
source([[
|
|
function! TestIndent()
|
|
let line = getline(v:lnum)
|
|
if (line =~ '^\s*ind')
|
|
return indent(v:lnum-1) + shiftwidth()
|
|
elseif (line =~ '^\s*unind')
|
|
return indent(v:lnum-1) - shiftwidth()
|
|
else
|
|
return indent(v:lnum-1)
|
|
endif
|
|
endfunction
|
|
set indentexpr=TestIndent()
|
|
set indentkeys=o,O,!^F,=ind,=unind
|
|
set completeopt+=menuone
|
|
]])
|
|
|
|
-- Give some words to complete.
|
|
feed('iinc uninc indent unindent<CR>')
|
|
|
|
-- Does not indent when "ind" is typed.
|
|
feed('in<C-X><C-N>')
|
|
-- Completion list is generated incorrectly if we send everything at once
|
|
-- via nvim_input(). So poke_eventloop() before sending <BS>. #8480
|
|
poke_eventloop()
|
|
feed('<BS>d')
|
|
|
|
screen:expect([[
|
|
inc uninc indent unindent |
|
|
ind^ |
|
|
{12:indent }{1: }|
|
|
{1:~ }|*4
|
|
{5:-- Keyword Local completion (^N^P) }{6:match 1 of 2} |
|
|
]])
|
|
|
|
-- Indents when the item is selected
|
|
feed('<C-Y>')
|
|
screen:expect([[
|
|
inc uninc indent unindent |
|
|
indent^ |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
-- Indents when completion is exited using ESC.
|
|
feed('<CR>in<C-N><BS>d<Esc>')
|
|
screen:expect([[
|
|
inc uninc indent unindent |
|
|
indent |
|
|
in^d |
|
|
{1:~ }|*4
|
|
|
|
|
]])
|
|
-- Works for unindenting too.
|
|
feed('ounin<C-X><C-N>')
|
|
poke_eventloop()
|
|
feed('<BS>d')
|
|
screen:expect([[
|
|
inc uninc indent unindent |
|
|
indent |
|
|
ind |
|
|
unind^ |
|
|
{1:~ }{12: unindent }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- Keyword Local completion (^N^P) }{6:match 1 of 2} |
|
|
]])
|
|
-- Works when going back and forth.
|
|
feed('<BS>c')
|
|
screen:expect([[
|
|
inc uninc indent unindent |
|
|
indent |
|
|
ind |
|
|
uninc^ |
|
|
{1:~ }{12: uninc }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- Keyword Local completion (^N^P) }{6:match 1 of 2} |
|
|
]])
|
|
feed('<BS>d')
|
|
screen:expect([[
|
|
inc uninc indent unindent |
|
|
indent |
|
|
ind |
|
|
unind^ |
|
|
{1:~ }{12: unindent }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- Keyword Local completion (^N^P) }{6:match 1 of 2} |
|
|
]])
|
|
feed('<C-N><C-N><C-Y><Esc>')
|
|
screen:expect([[
|
|
inc uninc indent unindent |
|
|
indent |
|
|
ind |
|
|
uninden^t |
|
|
{1:~ }|*3
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('disables folding during completion', function()
|
|
command('set foldmethod=indent')
|
|
feed('i<Tab>foo<CR><Tab>bar<Esc>gg')
|
|
screen:expect([[
|
|
^foo |
|
|
bar |
|
|
{1:~ }|*5
|
|
|
|
|
]])
|
|
feed('A<C-x><C-l>')
|
|
screen:expect([[
|
|
foo^ |
|
|
bar |
|
|
{1:~ }|*5
|
|
{5:-- Whole line completion (^L^N^P) }{9:Pattern not found} |
|
|
]])
|
|
eq(-1, eval('foldclosed(1)'))
|
|
end)
|
|
|
|
it('popupmenu is not interrupted by events', function()
|
|
command('set complete=.')
|
|
|
|
feed('ifoobar fooegg<cr>f<c-p>')
|
|
screen:expect([[
|
|
foobar fooegg |
|
|
fooegg^ |
|
|
{4:foobar }{1: }|
|
|
{12:fooegg }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- Keyword completion (^N^P) }{6:match 1 of 2} |
|
|
]])
|
|
|
|
assert_alive()
|
|
-- popupmenu still visible
|
|
screen:expect {
|
|
grid = [[
|
|
foobar fooegg |
|
|
fooegg^ |
|
|
{4:foobar }{1: }|
|
|
{12:fooegg }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- Keyword completion (^N^P) }{6:match 1 of 2} |
|
|
]],
|
|
unchanged = true,
|
|
}
|
|
|
|
feed('<c-p>')
|
|
-- Didn't restart completion: old matches still used
|
|
screen:expect([[
|
|
foobar fooegg |
|
|
foobar^ |
|
|
{12:foobar }{1: }|
|
|
{4:fooegg }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- Keyword completion (^N^P) }{6:match 2 of 2} |
|
|
]])
|
|
end)
|
|
|
|
describe('lua completion', function()
|
|
it('expands when there is only one match', function()
|
|
feed(':lua CURRENT_TESTING_VAR = 1<CR>')
|
|
feed(':lua CURRENT_TESTING_<TAB>')
|
|
screen:expect {
|
|
grid = [[
|
|
|
|
|
{1:~ }|*6
|
|
:lua CURRENT_TESTING_VAR^ |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('expands when there is only one match', function()
|
|
feed(':lua CURRENT_TESTING_FOO = 1<CR>')
|
|
feed(':lua CURRENT_TESTING_BAR = 1<CR>')
|
|
feed(':lua CURRENT_TESTING_<TAB>')
|
|
screen:expect {
|
|
grid = [[
|
|
|
|
|
{1:~ }|*5
|
|
{100:CURRENT_TESTING_BAR}{3: CURRENT_TESTING_FOO }|
|
|
:lua CURRENT_TESTING_BAR^ |
|
|
]],
|
|
unchanged = true,
|
|
}
|
|
end)
|
|
|
|
it('prefix is not included in completion for cmdline mode', function()
|
|
feed(':lua math.a<Tab>')
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*5
|
|
{100:abs}{3: acos asin atan atan2 }|
|
|
:lua math.abs^ |
|
|
]])
|
|
feed('<Tab>')
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*5
|
|
{3:abs }{100:acos}{3: asin atan atan2 }|
|
|
:lua math.acos^ |
|
|
]])
|
|
end)
|
|
|
|
it('prefix is not included in completion for i_CTRL-X_CTRL-V #19623', function()
|
|
feed('ilua math.a<C-X><C-V>')
|
|
screen:expect([[
|
|
lua math.abs^ |
|
|
{1:~ }{12: abs }{1: }|
|
|
{1:~ }{4: acos }{1: }|
|
|
{1:~ }{4: asin }{1: }|
|
|
{1:~ }{4: atan }{1: }|
|
|
{1:~ }{4: atan2 }{1: }|
|
|
{1:~ }|
|
|
{5:-- Command-line completion (^V^N^P) }{6:match 1 of 5} |
|
|
]])
|
|
feed('<C-V>')
|
|
screen:expect([[
|
|
lua math.acos^ |
|
|
{1:~ }{4: abs }{1: }|
|
|
{1:~ }{12: acos }{1: }|
|
|
{1:~ }{4: asin }{1: }|
|
|
{1:~ }{4: atan }{1: }|
|
|
{1:~ }{4: atan2 }{1: }|
|
|
{1:~ }|
|
|
{5:-- Command-line completion (^V^N^P) }{6:match 2 of 5} |
|
|
]])
|
|
end)
|
|
|
|
it('works when cursor is in the middle of cmdline #29586', function()
|
|
feed(':lua math.a(); 1<Left><Left><Left><Left><Left><Tab>')
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*5
|
|
{100:abs}{3: acos asin atan atan2 }|
|
|
:lua math.abs^(); 1 |
|
|
]])
|
|
end)
|
|
|
|
it('provides completion from `getcompletion()`', function()
|
|
eq({ 'vim' }, fn.getcompletion('vi', 'lua'))
|
|
eq({ 'api' }, fn.getcompletion('vim.ap', 'lua'))
|
|
eq({ 'tbl_filter' }, fn.getcompletion('vim.tbl_fil', 'lua'))
|
|
eq({ 'vim' }, fn.getcompletion('print(vi', 'lua'))
|
|
eq({ 'abs', 'acos', 'asin', 'atan', 'atan2' }, fn.getcompletion('math.a', 'lua'))
|
|
eq({ 'abs', 'acos', 'asin', 'atan', 'atan2' }, fn.getcompletion('lua math.a', 'cmdline'))
|
|
-- fuzzy completion is not supported, so the result should be the same
|
|
command('set wildoptions+=fuzzy')
|
|
eq({ 'vim' }, fn.getcompletion('vi', 'lua'))
|
|
end)
|
|
|
|
it('completes _defer_require() modules', function()
|
|
-- vim.lsp.c<tab> -> vim.lsp.completion
|
|
ok(vim.tbl_contains(fn.getcompletion('lua vim.lsp.c', 'cmdline'), 'completion'))
|
|
-- vim.lsp.completion.g<tab> -> vim.lsp.completion.get
|
|
ok(vim.tbl_contains(fn.getcompletion('lua vim.lsp.completion.g', 'cmdline'), 'get'))
|
|
end)
|
|
end)
|
|
|
|
it('cmdline completion supports various string options', function()
|
|
eq('auto', fn.getcompletion('set foldcolumn=', 'cmdline')[2])
|
|
eq({ 'nosplit', 'split' }, fn.getcompletion('set inccommand=', 'cmdline'))
|
|
eq({ 'ver:3,hor:6', 'hor:', 'ver:' }, fn.getcompletion('set mousescroll=', 'cmdline'))
|
|
eq('BS', fn.getcompletion('set termpastefilter=', 'cmdline')[2])
|
|
eq('SpecialKey', fn.getcompletion('set winhighlight=', 'cmdline')[1])
|
|
eq('SpecialKey', fn.getcompletion('set winhighlight=NonText:', 'cmdline')[1])
|
|
end)
|
|
|
|
it('cmdline completion for -complete does not contain spaces', function()
|
|
for _, str in ipairs(fn.getcompletion('command -complete=', 'cmdline')) do
|
|
ok(not str:find(' '), 'string without spaces', str)
|
|
end
|
|
end)
|
|
|
|
it('cmdline completion for :restart', function()
|
|
eq('qall', fn.getcompletion('restart +qa', 'cmdline')[1])
|
|
eq('edit', fn.getcompletion('restart +qall ed', 'cmdline')[1])
|
|
eq('edit', fn.getcompletion('restart ed', 'cmdline')[1])
|
|
end)
|
|
|
|
describe('from the commandline window', function()
|
|
it('is cleared after CTRL-C', function()
|
|
feed('q:')
|
|
feed('ifoo faa fee f')
|
|
screen:expect([[
|
|
|
|
|
{2:[No Name] }|
|
|
{1::}foo faa fee f^ |
|
|
{1:~ }|*3
|
|
{3:[Command Line] }|
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<c-x><c-n>')
|
|
screen:expect([[
|
|
|
|
|
{2:[No Name] }|
|
|
{1::}foo faa fee foo^ |
|
|
{1:~ }{12: foo }{1: }|
|
|
{1:~ }{4: faa }{1: }|
|
|
{1:~ }{4: fee }{1: }|
|
|
{3:[Command Line] }|
|
|
{5:-- Keyword Local completion (^N^P) }{6:match 1 of 3} |
|
|
]])
|
|
feed('<c-c>')
|
|
-- Non-blocking cmdwin (#40312): <C-C> closes the cmdwin and drops back
|
|
-- into ":" cmdline mode pre-filled with the line under the cursor.
|
|
screen:expect([[
|
|
|
|
|
{1:~ }|*6
|
|
:foo faa fee foo^ |
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
describe('with numeric items', function()
|
|
before_each(function()
|
|
source([[
|
|
function! TestComplete() abort
|
|
call complete(1, g:_complist)
|
|
return ''
|
|
endfunction
|
|
]])
|
|
api.nvim_set_option_value('completeopt', 'menuone,noselect', {})
|
|
api.nvim_set_var('_complist', {
|
|
{
|
|
word = 0,
|
|
abbr = 1,
|
|
menu = 2,
|
|
kind = 3,
|
|
info = 4,
|
|
icase = 5,
|
|
dup = 6,
|
|
empty = 7,
|
|
},
|
|
})
|
|
end)
|
|
|
|
it('shows correct variant as word', function()
|
|
feed('i<C-r>=TestComplete()<CR>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:1 3 2 }{1: }|
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
end)
|
|
end)
|
|
|
|
it("'ignorecase' 'infercase' CTRL-X CTRL-N #6451", function()
|
|
command('set ignorecase infercase')
|
|
command('edit runtime/doc/credits.txt')
|
|
feed('oX<C-X><C-N>')
|
|
screen:expect {
|
|
grid = [[
|
|
*credits.txt* Nvim |
|
|
Xvi^ |
|
|
{12:Xvi }{101: } |
|
|
{4:Xvim }{101: } |
|
|
{4:X11 }{12: } NVIM REFERENCE MANUAL |
|
|
{4:Xnull }{12: } |
|
|
{4:Xoxomoon }{12: } |
|
|
{5:-- Keyword Local completion (^N^P) }{6:match 1 of 10} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('CompleteChanged autocommand', function()
|
|
api.nvim_buf_set_lines(0, 0, 1, false, { 'foo', 'bar', 'foobar', '' })
|
|
source([[
|
|
set complete=. completeopt=noinsert,noselect,menuone
|
|
function! OnPumChange()
|
|
let g:event = copy(v:event)
|
|
let g:item = get(v:event, 'completed_item', {})
|
|
let g:word = get(g:item, 'word', v:null)
|
|
endfunction
|
|
autocmd! CompleteChanged * :call OnPumChange()
|
|
call cursor(4, 1)
|
|
]])
|
|
|
|
-- v:event.size should be set with ext_popupmenu #20646
|
|
screen:set_option('ext_popupmenu', true)
|
|
feed('Sf<C-N>')
|
|
screen:expect({
|
|
grid = [[
|
|
foo |
|
|
bar |
|
|
foobar |
|
|
f^ |
|
|
{1:~ }|*3
|
|
{5:-- Keyword completion (^N^P) }{19:Back at original} |
|
|
]],
|
|
popupmenu = {
|
|
anchor = { 1, 3, 0 },
|
|
items = { { 'foo', '', '', '' }, { 'foobar', '', '', '' } },
|
|
pos = -1,
|
|
},
|
|
})
|
|
eq(
|
|
{ completed_item = {}, width = 0, height = 2, size = 2, col = 0, row = 4, scrollbar = false },
|
|
eval('g:event')
|
|
)
|
|
feed('oob')
|
|
screen:expect({
|
|
grid = [[
|
|
foo |
|
|
bar |
|
|
foobar |
|
|
foob^ |
|
|
{1:~ }|*3
|
|
{5:-- Keyword completion (^N^P) }{19:Back at original} |
|
|
]],
|
|
popupmenu = {
|
|
anchor = { 1, 3, 0 },
|
|
items = { { 'foobar', '', '', '' } },
|
|
pos = -1,
|
|
},
|
|
})
|
|
eq(
|
|
{ completed_item = {}, width = 0, height = 1, size = 1, col = 0, row = 4, scrollbar = false },
|
|
eval('g:event')
|
|
)
|
|
feed('<Esc>')
|
|
screen:set_option('ext_popupmenu', false)
|
|
|
|
feed('Sf<C-N>')
|
|
screen:expect([[
|
|
foo |
|
|
bar |
|
|
foobar |
|
|
f^ |
|
|
{4:foo }{1: }|
|
|
{4:foobar }{1: }|
|
|
{1:~ }|
|
|
{5:-- Keyword completion (^N^P) }{19:Back at original} |
|
|
]])
|
|
eq(
|
|
{ completed_item = {}, width = 15, height = 2, size = 2, col = 0, row = 4, scrollbar = false },
|
|
eval('g:event')
|
|
)
|
|
feed('<C-N>')
|
|
screen:expect([[
|
|
foo |
|
|
bar |
|
|
foobar |
|
|
foo^ |
|
|
{12:foo }{1: }|
|
|
{4:foobar }{1: }|
|
|
{1:~ }|
|
|
{5:-- Keyword completion (^N^P) }{6:match 1 of 2} |
|
|
]])
|
|
eq('foo', eval('g:word'))
|
|
feed('<C-N>')
|
|
screen:expect([[
|
|
foo |
|
|
bar |
|
|
foobar |
|
|
foobar^ |
|
|
{4:foo }{1: }|
|
|
{12:foobar }{1: }|
|
|
{1:~ }|
|
|
{5:-- Keyword completion (^N^P) }{6:match 2 of 2} |
|
|
]])
|
|
eq('foobar', eval('g:word'))
|
|
feed('<up>')
|
|
screen:expect([[
|
|
foo |
|
|
bar |
|
|
foobar |
|
|
foobar^ |
|
|
{12:foo }{1: }|
|
|
{4:foobar }{1: }|
|
|
{1:~ }|
|
|
{5:-- Keyword completion (^N^P) }{6:match 1 of 2} |
|
|
]])
|
|
eq('foo', eval('g:word'))
|
|
feed('<down>')
|
|
screen:expect([[
|
|
foo |
|
|
bar |
|
|
foobar |
|
|
foobar^ |
|
|
{4:foo }{1: }|
|
|
{12:foobar }{1: }|
|
|
{1:~ }|
|
|
{5:-- Keyword completion (^N^P) }{6:match 2 of 2} |
|
|
]])
|
|
eq('foobar', eval('g:word'))
|
|
feed('<esc>')
|
|
end)
|
|
|
|
it('is stopped by :stopinsert from timer #12976', function()
|
|
screen:try_resize(32, 14)
|
|
command([[call setline(1, ['hello', 'hullo', 'heeee', ''])]])
|
|
feed('Gah<c-x><c-n>')
|
|
screen:expect([[
|
|
hello |
|
|
hullo |
|
|
heeee |
|
|
hello^ |
|
|
{12:hello }{1: }|
|
|
{4:hullo }{1: }|
|
|
{4:heeee }{1: }|
|
|
{1:~ }|*6
|
|
{5:-- }{6:match 1 of 3} |
|
|
]])
|
|
command([[call timer_start(100, { -> execute('stopinsert') })]])
|
|
vim.uv.sleep(200)
|
|
feed('k') -- cursor should move up in Normal mode
|
|
screen:expect([[
|
|
hello |
|
|
hullo |
|
|
heee^e |
|
|
hello |
|
|
{1:~ }|*9
|
|
|
|
|
]])
|
|
end)
|
|
|
|
-- oldtest: Test_complete_changed_complete_info()
|
|
it('no crash calling complete_info() in CompleteChanged', function()
|
|
source([[
|
|
set completeopt=menuone
|
|
autocmd CompleteChanged * call complete_info(['items'])
|
|
call feedkeys("iii\<cr>\<c-p>")
|
|
]])
|
|
screen:expect([[
|
|
ii |
|
|
ii^ |
|
|
{12:ii }{1: }|
|
|
{1:~ }|*4
|
|
{5:-- Keyword completion (^N^P) The only match} |
|
|
]])
|
|
assert_alive()
|
|
end)
|
|
|
|
it('no crash if text changed by first call to complete function #17489', function()
|
|
source([[
|
|
func Complete(findstart, base) abort
|
|
if a:findstart
|
|
let col = col('.')
|
|
call complete_add('#')
|
|
return col - 1
|
|
else
|
|
return []
|
|
endif
|
|
endfunc
|
|
|
|
set completeopt=longest
|
|
set completefunc=Complete
|
|
]])
|
|
feed('ifoo#<C-X><C-U>')
|
|
assert_alive()
|
|
end)
|
|
|
|
it('no crash using i_CTRL-X_CTRL-V to complete non-existent colorscheme', function()
|
|
feed('icolorscheme NOSUCHCOLORSCHEME<C-X><C-V>')
|
|
expect('colorscheme NOSUCHCOLORSCHEME')
|
|
assert_alive()
|
|
end)
|
|
|
|
it('complete with f flag #25598', function()
|
|
screen:try_resize(20, 9)
|
|
command('set complete+=f | edit foo | edit bar |edit foa |edit .hidden')
|
|
feed('i<C-n>')
|
|
screen:expect {
|
|
grid = [[
|
|
foo^ |
|
|
{12:foo }{1: }|
|
|
{4:bar }{1: }|
|
|
{4:foa }{1: }|
|
|
{4:.hidden }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- }{6:match 1 of 4} |
|
|
]],
|
|
}
|
|
feed('<Esc>ccf<C-n>')
|
|
screen:expect {
|
|
grid = [[
|
|
foo^ |
|
|
{12:foo }{1: }|
|
|
{4:foa }{1: }|
|
|
{1:~ }|*5
|
|
{5:-- }{6:match 1 of 2} |
|
|
]],
|
|
}
|
|
end)
|
|
|
|
it('restores extmarks if original text is restored #23653', function()
|
|
screen:try_resize(screen._width, 4)
|
|
command([[
|
|
call setline(1, ['aaaa'])
|
|
let ns_id = nvim_create_namespace('extmark')
|
|
let mark_id = nvim_buf_set_extmark(0, ns_id, 0, 0, { 'end_col':2, 'hl_group':'Error' })
|
|
let mark = nvim_buf_get_extmark_by_id(0, ns_id, mark_id, { 'details':1 })
|
|
inoremap <C-x> <C-r>=Complete()<CR>
|
|
function Complete() abort
|
|
call complete(1, [{ 'word': 'aaaaa' }])
|
|
return ''
|
|
endfunction
|
|
]])
|
|
feed('A<C-X><C-E><Esc>')
|
|
eq(eval('mark'), eval("nvim_buf_get_extmark_by_id(0, ns_id, mark_id, { 'details':1 })"))
|
|
feed('A<C-N>')
|
|
eq(eval('mark'), eval("nvim_buf_get_extmark_by_id(0, ns_id, mark_id, { 'details':1 })"))
|
|
feed('<Esc>0Yppia<Esc>ggI<C-N>')
|
|
screen:expect([[
|
|
aaaa{9:^aa}aa |
|
|
{12:aaaa } |
|
|
{4:aaaaa } |
|
|
{5:-- Keyword completion (^N^P) }{6:match 1 of 2} |
|
|
]])
|
|
feed('<C-N><C-N><Esc>')
|
|
eq(eval('mark'), eval("nvim_buf_get_extmark_by_id(0, ns_id, mark_id, { 'details':1 })"))
|
|
feed('A<C-N>')
|
|
eq(eval('mark'), eval("nvim_buf_get_extmark_by_id(0, ns_id, mark_id, { 'details':1 })"))
|
|
feed('<C-N>')
|
|
screen:expect([[
|
|
aaaaa^ |
|
|
{4:aaaa } |
|
|
{12:aaaaa } |
|
|
{5:-- Keyword completion (^N^P) }{6:match 2 of 2} |
|
|
]])
|
|
feed('<C-E>')
|
|
screen:expect([[
|
|
{9:aa}aa^ |
|
|
aaaa |
|
|
aaaaa |
|
|
{5:-- INSERT --} |
|
|
]])
|
|
-- Also when completion leader is changed #31384
|
|
feed('<Esc>hi<C-N><C-P>a')
|
|
screen:expect({
|
|
grid = [[
|
|
{9:aa}a^aa |
|
|
{4:aaaa } |
|
|
{4:aaaaa } |
|
|
{5:-- Keyword completion (^N^P) }{19:Back at original} |
|
|
]],
|
|
})
|
|
-- But still grows with end_right_gravity #31437
|
|
command(
|
|
"call nvim_buf_set_extmark(0, ns_id, 1, 0, { 'end_col':2, 'hl_group':'Error', 'end_right_gravity': 1 })"
|
|
)
|
|
feed('<Esc>ji<C-N>a')
|
|
screen:expect({
|
|
grid = [[
|
|
{9:aa}aaa |
|
|
{9:aaa}^aa |
|
|
aaaaa |
|
|
{5:-- INSERT --} |
|
|
]],
|
|
})
|
|
end)
|
|
|
|
describe('nvim__complete_set', function()
|
|
it("fails when 'completeopt' does not include popup", function()
|
|
exec_lua([[
|
|
function _G.omni_test(findstart, base)
|
|
if findstart == 1 then
|
|
return vim.fn.col('.') - 1
|
|
end
|
|
return { { word = 'one' } }
|
|
end
|
|
vim.api.nvim_create_autocmd('CompleteChanged', {
|
|
callback = function()
|
|
local ok, err = pcall(vim.api.nvim__complete_set, 0, { info = '1info' })
|
|
if not ok then
|
|
vim.g.err_msg = err
|
|
end
|
|
end,
|
|
})
|
|
vim.opt.completeopt = 'menu,menuone'
|
|
vim.opt.omnifunc = 'v:lua.omni_test'
|
|
]])
|
|
feed('S<C-X><C-O>')
|
|
eq('completeopt option does not include popup', api.nvim_get_var('err_msg'))
|
|
end)
|
|
end)
|
|
|
|
it([[does not include buffer from non-focusable window for 'complete' "w"]], function()
|
|
local buf = api.nvim_create_buf(false, true)
|
|
local cfg = { focusable = false, relative = 'win', bufpos = { 1, 0 }, width = 1, height = 1 }
|
|
local win = api.nvim_open_win(buf, false, cfg)
|
|
api.nvim_buf_set_lines(buf, 0, -1, false, { 'foo' })
|
|
feed('i<C-N>')
|
|
screen:expect([[
|
|
^ |
|
|
{4:f}{1: }|
|
|
{1:~ }|*5
|
|
{5:-- Keyword completion (^N^P) }{9:Pattern not found} |
|
|
]])
|
|
api.nvim_win_set_config(win, { focusable = true })
|
|
feed('<Esc>i<C-N>')
|
|
screen:expect([[
|
|
foo^ |
|
|
{4:f}{1: }|
|
|
{1:~ }|*5
|
|
{5:-- Keyword completion (^N^P) The only match} |
|
|
]])
|
|
end)
|
|
|
|
-- oldtest: Test_shortmess()
|
|
it('shortmess+=c turns off completion messages', function()
|
|
command('set ruler')
|
|
command([[call setline(1, ['hello', 'hullo', 'heee'])]])
|
|
feed('Goh<C-N>')
|
|
screen:expect([[
|
|
hello |
|
|
hullo |
|
|
heee |
|
|
hello^ |
|
|
{12:hello }{1: }|
|
|
{4:hullo }{1: }|
|
|
{4:heee }{1: }|
|
|
{5:-- Keyword completion (^N^P) }{6:match 1 of 3} |
|
|
]])
|
|
feed('<Esc>')
|
|
command('set shm+=c')
|
|
feed('Sh<C-N>')
|
|
screen:expect([[
|
|
hello |
|
|
hullo |
|
|
heee |
|
|
hello^ |
|
|
{12:hello }{1: }|
|
|
{4:hullo }{1: }|
|
|
{4:heee }{1: }|
|
|
{5:-- INSERT --} 4,6 All |
|
|
]])
|
|
end)
|
|
|
|
-- oldtest: Test_autocompletedelay()
|
|
it("'autocompletedelay' option", function()
|
|
screen:try_resize(60, 10)
|
|
source([[
|
|
call setline(1, ['foo', 'foobar', 'foobarbaz'])
|
|
setlocal autocomplete
|
|
]])
|
|
screen:expect([[
|
|
^foo |
|
|
foobar |
|
|
foobarbaz |
|
|
{1:~ }|*6
|
|
|
|
|
]])
|
|
screen.timeout = 400
|
|
|
|
feed('Gof')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
f^ |
|
|
{4:foobarbaz }{1: }|
|
|
{4:foobar }{1: }|
|
|
{4:foo }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
|
|
feed('<Esc>')
|
|
command('set autocompletedelay=500')
|
|
feed('Sf')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
f^ |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('o')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
fo^ |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
vim.uv.sleep(500)
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
fo^ |
|
|
{4:foobarbaz }{1: }|
|
|
{4:foobar }{1: }|
|
|
{4:foo }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<BS>')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
f^ |
|
|
{4:foobarbaz }{1: }|
|
|
{4:foobar }{1: }|
|
|
{4:foo }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
vim.uv.sleep(500)
|
|
screen:expect_unchanged()
|
|
|
|
-- During delay wait, user can open menu using CTRL_N completion
|
|
feed('<Esc>')
|
|
command('set completeopt=menuone')
|
|
feed('Sf<C-N>')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
foo^ |
|
|
{12:foo }{1: }|
|
|
{4:foobar }{1: }|
|
|
{4:foobarbaz }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- Keyword completion (^N^P) }{6:match 1 of 3} |
|
|
]])
|
|
|
|
-- After the menu is open, ^N/^P and Up/Down should not delay.
|
|
-- Wait a bit longer than 'autocompletedelay' so the popup is surely shown
|
|
-- before sending CTRL-N, otherwise the keys race with the deferred popup.
|
|
feed('<Esc>')
|
|
command('set completeopt=menu')
|
|
feed('Sf')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
f^ |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
vim.uv.sleep(600)
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
f^ |
|
|
{4:foobarbaz }{1: }|
|
|
{4:foobar }{1: }|
|
|
{4:foo }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<C-N>')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
foobarbaz^ |
|
|
{12:foobarbaz }{1: }|
|
|
{4:foobar }{1: }|
|
|
{4:foo }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<Down>')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
foobarbaz^ |
|
|
{4:foobarbaz }{1: }|
|
|
{12:foobar }{1: }|
|
|
{4:foo }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
|
|
-- When menu is not open Up/Down moves cursor to different line
|
|
feed('<Esc>Sf')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
f^ |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<Up>')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
f^oobarbaz |
|
|
f |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<Down>')
|
|
screen:expect([[
|
|
foo |
|
|
foobar |
|
|
foobarbaz |
|
|
f^ |
|
|
{1:~ }|*5
|
|
{5:-- INSERT --} |
|
|
]])
|
|
|
|
feed('<esc>')
|
|
end)
|
|
|
|
-- oldtest: Test_fuzzy_select_item_when_acl()
|
|
it([[first item isn't selected with "fuzzy" and 'acl']], function()
|
|
screen:try_resize(60, 10)
|
|
source([[
|
|
call setline(1, ["v", "vi", "vim"])
|
|
set autocomplete completeopt=menuone,noinsert,fuzzy autocompletedelay=300
|
|
]])
|
|
|
|
feed('Govi')
|
|
screen:expect([[
|
|
v |
|
|
vi |
|
|
vim |
|
|
vi^ |
|
|
{4:vi }{1: }|
|
|
{4:vim }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- INSERT --} |
|
|
]])
|
|
|
|
feed('<Esc>Sv')
|
|
screen:expect([[
|
|
v |
|
|
vi |
|
|
vim |
|
|
v^ |
|
|
{4:v }{1: }|
|
|
{4:vi }{1: }|
|
|
{4:vim }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('i')
|
|
screen:expect([[
|
|
v |
|
|
vi |
|
|
vim |
|
|
vi^ |
|
|
{4:vi }{1: }|
|
|
{4:vim }{1: }|
|
|
{1:~ }|*3
|
|
{5:-- INSERT --} |
|
|
]])
|
|
end)
|
|
|
|
-- oldtest: Test_autocompletedelay_longest_preinsert()
|
|
it("'autocompletedelay' with 'completeopt' longest/preinsert", function()
|
|
source([[
|
|
call setline(1, ['autocomplete', 'autocomxxx'])
|
|
set autocomplete completeopt+=longest autocompletedelay=500
|
|
]])
|
|
screen:expect([[
|
|
^autocomplete |
|
|
autocomxxx |
|
|
{1:~ }|*5
|
|
|
|
|
]])
|
|
screen.timeout = 400
|
|
|
|
-- No spurious characters when autocompletedelay is in effect
|
|
feed('Goau')
|
|
screen:expect([[
|
|
autocomplete |
|
|
autocomxxx |
|
|
au^ |
|
|
{1:~ }|*4
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('toc')
|
|
screen:expect([[
|
|
autocomplete |
|
|
autocomxxx |
|
|
autoc^ |
|
|
{1:~ }|*4
|
|
{5:-- INSERT --} |
|
|
]])
|
|
vim.uv.sleep(500)
|
|
screen:expect([[
|
|
autocomplete |
|
|
autocomxxx |
|
|
autoc{102:^om} |
|
|
{4:autocomxxx }{1: }|
|
|
{4:autocomplete }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
|
|
-- Deleting a char should still show longest text
|
|
feed('<Esc>Saut')
|
|
screen:expect([[
|
|
autocomplete |
|
|
autocomxxx |
|
|
aut^ |
|
|
{1:~ }|*4
|
|
{5:-- INSERT --} |
|
|
]])
|
|
feed('<BS>')
|
|
screen:expect([[
|
|
autocomplete |
|
|
autocomxxx |
|
|
au^ |
|
|
{1:~ }|*4
|
|
{5:-- INSERT --} |
|
|
]])
|
|
vim.uv.sleep(500)
|
|
screen:expect([[
|
|
autocomplete |
|
|
autocomxxx |
|
|
au{102:^tocom} |
|
|
{4:autocomxxx }{1: }|
|
|
{4:autocomplete }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
|
|
-- Preinsert
|
|
command('set completeopt& completeopt+=preinsert')
|
|
|
|
-- Show preinserted text right away but display popup later
|
|
feed('<Esc>Sau')
|
|
screen:expect([[
|
|
autocomplete |
|
|
autocomxxx |
|
|
au^ |
|
|
{1:~ }|*4
|
|
{5:-- INSERT --} |
|
|
]])
|
|
vim.uv.sleep(500)
|
|
screen:expect([[
|
|
autocomplete |
|
|
autocomxxx |
|
|
au{102:^tocomplete} |
|
|
{12:autocomplete }{1: }|
|
|
{4:autocomxxx }{1: }|
|
|
{1:~ }|*2
|
|
{5:-- INSERT --} |
|
|
]])
|
|
end)
|
|
|
|
-- oldtest: Test_fuzzy_filenames_compl_autocompl()
|
|
it('fuzzy file name does not crash with autocomplete', function()
|
|
feed('iset ac cot=fuzzy,longest<Esc>')
|
|
command('source')
|
|
feed('o')
|
|
poke_eventloop()
|
|
feed('.')
|
|
poke_eventloop()
|
|
feed('n')
|
|
poke_eventloop()
|
|
feed('a')
|
|
poke_eventloop()
|
|
feed('<C-X><C-F>') -- this used to cause segfault
|
|
screen:expect([[
|
|
set ac cot=fuzzy,longest |
|
|
.na^ |
|
|
{1:~ }|*5
|
|
{5:-- File name completion (^F^N^P) }{9:Pattern not found} |
|
|
]])
|
|
end)
|
|
end)
|