mirror of
https://github.com/neovim/neovim
synced 2025-07-15 16:51:49 +00:00
vim-patch:9.1.1512: completion: can only complete from keyword characters (#34798)
Problem: completion: can only complete from keyword characters
Solution: remove this restriction, allow completion functions when
called from i_CTRL-N/i_CTRL-P to be triggered from non-keyword
characters (Girish Palya)
Previously, functions specified in the `'complete'` option were
restricted to starting completion only from keyword characters (as
introduced in PR 17065). This change removes that restriction.
With this change, user-defined functions (e.g., `omnifunc`, `userfunc`)
used in `'complete'` can now initiate completion even when triggered
from non-keyword characters. This makes it easier to reuse existing
functions alongside other sources without having to consider whether the
cursor is on a keyword or non-keyword character, or worry about where
the replacement should begin (i.e., the `findstart=1` return value).
The logic for both the “collection” and “filtering” phases now fully
respects each source’s specified start column. This also extends to
fuzzy matching, making completions more predictable.
Internally, this builds on previously merged infrastructure that tracks
per-source metadata. This PR focuses on applying that metadata to
compute the leader string and insertion text appropriately for each
match.
Also, a memory corruption has been fixed in prepare_cpt_compl_funcs().
closes: vim/vim#17651
ba11e78f1d
Co-authored-by: Girish Palya <girishji@gmail.com>
This commit is contained in:
@ -137,8 +137,7 @@ func Test_omni_dash()
|
||||
%d
|
||||
set complete=o
|
||||
exe "normal Gofind -\<C-n>"
|
||||
" 'complete' inserts at 'iskeyword' boundary (so you get --help)
|
||||
call assert_equal("find --help", getline('$'))
|
||||
call assert_equal("find -help", getline('$'))
|
||||
|
||||
bwipe!
|
||||
delfunc Omni
|
||||
@ -368,7 +367,7 @@ func Test_CompleteDone_vevent_keys()
|
||||
call assert_equal('spell', g:complete_type)
|
||||
|
||||
bwipe!
|
||||
set completeopt& omnifunc& completefunc& spell& spelllang& dictionary&
|
||||
set completeopt& omnifunc& completefunc& spell& spelllang& dictionary& complete&
|
||||
autocmd! CompleteDone
|
||||
delfunc OnDone
|
||||
delfunc CompleteFunc
|
||||
@ -1112,6 +1111,7 @@ func Test_completefunc_invalid_data()
|
||||
exe "normal i\<C-N>"
|
||||
call assert_equal('moon', getline(1))
|
||||
set completefunc& complete&
|
||||
delfunc! CompleteFunc
|
||||
bw!
|
||||
endfunc
|
||||
|
||||
@ -4943,4 +4943,120 @@ func Test_complete_fuzzy_omnifunc_backspace()
|
||||
unlet g:do_complete
|
||||
endfunc
|
||||
|
||||
" Test 'complete' containing F{func} that complete from nonkeyword
|
||||
func Test_nonkeyword_trigger()
|
||||
|
||||
" Trigger expansion even when another char is waiting in the typehead
|
||||
call Ntest_override("char_avail", 1)
|
||||
|
||||
let g:CallCount = 0
|
||||
func! NonKeywordComplete(findstart, base)
|
||||
let line = getline('.')->strpart(0, col('.') - 1)
|
||||
let nonkeyword2 = len(line) > 1 && match(line[-2:-2], '\k') != 0
|
||||
if a:findstart
|
||||
return nonkeyword2 ? col('.') - 3 : (col('.') - 2)
|
||||
else
|
||||
let g:CallCount += 1
|
||||
return [$"{a:base}foo", $"{a:base}bar"]
|
||||
endif
|
||||
endfunc
|
||||
|
||||
new
|
||||
inoremap <buffer> <F2> <Cmd>let b:matches = complete_info(["matches"]).matches<CR>
|
||||
inoremap <buffer> <F3> <Cmd>let b:selected = complete_info(["selected"]).selected<CR>
|
||||
call setline(1, ['abc', 'abcd', 'fo', 'b', ''])
|
||||
|
||||
" Test 1a: Nonkeyword before cursor lists words with at least two letters
|
||||
call feedkeys("GS=\<C-N>\<F2>\<Esc>0", 'tx!')
|
||||
call assert_equal(['abc', 'abcd', 'fo'], b:matches->mapnew('v:val.word'))
|
||||
call assert_equal('=abc', getline('.'))
|
||||
|
||||
" Test 1b: With F{func} nonkeyword collects matches
|
||||
set complete=.,FNonKeywordComplete
|
||||
for noselect in range(2)
|
||||
if noselect
|
||||
set completeopt+=noselect
|
||||
endif
|
||||
let g:CallCount = 0
|
||||
call feedkeys("S=\<C-N>\<F2>\<Esc>0", 'tx!')
|
||||
call assert_equal(['abc', 'abcd', 'fo', '=foo', '=bar'], b:matches->mapnew('v:val.word'))
|
||||
call assert_equal(1, g:CallCount)
|
||||
call assert_equal(noselect ? '=' : '=abc', getline('.'))
|
||||
let g:CallCount = 0
|
||||
call feedkeys("S->\<C-N>\<F2>\<Esc>0", 'tx!')
|
||||
call assert_equal(['abc', 'abcd', 'fo', '->foo', '->bar'], b:matches->mapnew('v:val.word'))
|
||||
call assert_equal(1, g:CallCount)
|
||||
call assert_equal(noselect ? '->' : '->abc', getline('.'))
|
||||
set completeopt&
|
||||
endfor
|
||||
|
||||
" Test 1c: Keyword collects from {func}
|
||||
let g:CallCount = 0
|
||||
call feedkeys("Sa\<C-N>\<F2>\<Esc>0", 'tx!')
|
||||
call assert_equal(['abc', 'abcd', 'afoo', 'abar'], b:matches->mapnew('v:val.word'))
|
||||
call assert_equal(1, g:CallCount)
|
||||
call assert_equal('abc', getline('.'))
|
||||
|
||||
set completeopt+=noselect
|
||||
let g:CallCount = 0
|
||||
call feedkeys("Sa\<C-N>\<F2>\<Esc>0", 'tx!')
|
||||
call assert_equal(['abc', 'abcd', 'afoo', 'abar'], b:matches->mapnew('v:val.word'))
|
||||
call assert_equal(1, g:CallCount)
|
||||
call assert_equal('a', getline('.'))
|
||||
|
||||
" Test 1d: Nonkeyword after keyword collects items again
|
||||
let g:CallCount = 0
|
||||
call feedkeys("Sa\<C-N>#\<C-N>\<F2>\<Esc>0", 'tx!')
|
||||
call assert_equal(['abc', 'abcd', 'fo', '#foo', '#bar'], b:matches->mapnew('v:val.word'))
|
||||
call assert_equal(2, g:CallCount)
|
||||
call assert_equal('a#', getline('.'))
|
||||
set completeopt&
|
||||
|
||||
" Test 2: Filter nonkeyword and keyword matches with differet startpos
|
||||
set completeopt+=menuone,noselect
|
||||
call feedkeys("S#a\<C-N>b\<F2>\<F3>\<Esc>0", 'tx!')
|
||||
call assert_equal(['abc', 'abcd', '#abar'], b:matches->mapnew('v:val.word'))
|
||||
call assert_equal(-1, b:selected)
|
||||
call assert_equal('#ab', getline('.'))
|
||||
|
||||
set completeopt+=fuzzy
|
||||
call feedkeys("S#a\<C-N>b\<F2>\<F3>\<Esc>0", 'tx!')
|
||||
call assert_equal(['#abar', 'abc', 'abcd'], b:matches->mapnew('v:val.word'))
|
||||
call assert_equal(-1, b:selected)
|
||||
call assert_equal('#ab', getline('.'))
|
||||
set completeopt&
|
||||
|
||||
" Test 3: Navigate menu containing nonkeyword and keyword items
|
||||
call feedkeys("S->\<C-N>\<F2>\<Esc>0", 'tx!')
|
||||
call assert_equal(['abc', 'abcd', 'fo', '->foo', '->bar'], b:matches->mapnew('v:val.word'))
|
||||
call assert_equal('->abc', getline('.'))
|
||||
call feedkeys("S->" . repeat("\<C-N>", 3) . "\<Esc>0", 'tx!')
|
||||
call assert_equal('->fo', getline('.'))
|
||||
call feedkeys("S->" . repeat("\<C-N>", 4) . "\<Esc>0", 'tx!')
|
||||
call assert_equal('->foo', getline('.'))
|
||||
call feedkeys("S->" . repeat("\<C-N>", 4) . "\<C-P>\<Esc>0", 'tx!')
|
||||
call assert_equal('->fo', getline('.'))
|
||||
call feedkeys("S->" . repeat("\<C-N>", 5) . "\<Esc>0", 'tx!')
|
||||
call assert_equal('->bar', getline('.'))
|
||||
call feedkeys("S->" . repeat("\<C-N>", 5) . "\<C-P>\<Esc>0", 'tx!')
|
||||
call assert_equal('->foo', getline('.'))
|
||||
call feedkeys("S->" . repeat("\<C-N>", 6) . "\<Esc>0", 'tx!')
|
||||
call assert_equal('->', getline('.'))
|
||||
call feedkeys("S->" . repeat("\<C-N>", 7) . "\<Esc>0", 'tx!')
|
||||
call assert_equal('->abc', getline('.'))
|
||||
call feedkeys("S->" . repeat("\<C-P>", 7) . "\<Esc>0", 'tx!')
|
||||
call assert_equal('->fo', getline('.'))
|
||||
" Replace
|
||||
call feedkeys("S# x y z\<Esc>0lR\<C-N>\<Esc>0", 'tx!')
|
||||
call assert_equal('#abcy z', getline('.'))
|
||||
call feedkeys("S# x y z\<Esc>0lR" . repeat("\<C-P>", 4) . "\<Esc>0", 'tx!')
|
||||
call assert_equal('#bary z', getline('.'))
|
||||
|
||||
bw!
|
||||
call Ntest_override("char_avail", 0)
|
||||
delfunc NonKeywordComplete
|
||||
set complete&
|
||||
unlet g:CallCount
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab nofoldenable
|
||||
|
Reference in New Issue
Block a user