forked from aniani/vim
patch 8.2.4037: Insert mode completion is insufficiently tested
Problem: Insert mode completion is insufficiently tested.
Solution: Add more tests. Fix uncovered memory leak. (Yegappan Lakshmanan,
closes #9489)
This commit is contained in:
committed by
Bram Moolenaar
parent
d844862bce
commit
370791465e
@@ -698,7 +698,20 @@ ins_compl_add_infercase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add a match to the list of matches.
|
* Add a match to the list of matches. The arguments are:
|
||||||
|
* str - text of the match to add
|
||||||
|
* len - length of "str". If -1, then the length of "str" is
|
||||||
|
* computed.
|
||||||
|
* fname - file name to associate with this match.
|
||||||
|
* cptext - list of strings to use with this match (for abbr, menu, info
|
||||||
|
* and kind)
|
||||||
|
* user_data - user supplied data (any vim type) for this match
|
||||||
|
* cdir - match direction. If 0, use "compl_direction".
|
||||||
|
* flags_arg - match flags (cp_flags)
|
||||||
|
* adup - accept this match even if it is already present.
|
||||||
|
* If "cdir" is FORWARD, then the match is added after the current match.
|
||||||
|
* Otherwise, it is added before the current match.
|
||||||
|
*
|
||||||
* If the given string is already in the list of completions, then return
|
* If the given string is already in the list of completions, then return
|
||||||
* NOTDONE, otherwise add it to the list and return OK. If there is an error,
|
* NOTDONE, otherwise add it to the list and return OK. If there is an error,
|
||||||
* maybe because alloc() returns NULL, then FAIL is returned.
|
* maybe because alloc() returns NULL, then FAIL is returned.
|
||||||
@@ -789,7 +802,8 @@ ins_compl_add(
|
|||||||
match->cp_user_data = *user_data;
|
match->cp_user_data = *user_data;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Link the new match structure in the list of matches.
|
// Link the new match structure after (FORWARD) or before (BACKWARD) the
|
||||||
|
// current match in the list of matches .
|
||||||
if (compl_first_match == NULL)
|
if (compl_first_match == NULL)
|
||||||
match->cp_next = match->cp_prev = NULL;
|
match->cp_next = match->cp_prev = NULL;
|
||||||
else if (dir == FORWARD)
|
else if (dir == FORWARD)
|
||||||
@@ -2704,6 +2718,7 @@ ins_compl_add_tv(typval_T *tv, int dir, int fast)
|
|||||||
int flags = fast ? CP_FAST : 0;
|
int flags = fast ? CP_FAST : 0;
|
||||||
char_u *(cptext[CPT_COUNT]);
|
char_u *(cptext[CPT_COUNT]);
|
||||||
typval_T user_data;
|
typval_T user_data;
|
||||||
|
int status;
|
||||||
|
|
||||||
user_data.v_type = VAR_UNKNOWN;
|
user_data.v_type = VAR_UNKNOWN;
|
||||||
if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL)
|
if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL)
|
||||||
@@ -2735,8 +2750,14 @@ ins_compl_add_tv(typval_T *tv, int dir, int fast)
|
|||||||
CLEAR_FIELD(cptext);
|
CLEAR_FIELD(cptext);
|
||||||
}
|
}
|
||||||
if (word == NULL || (!empty && *word == NUL))
|
if (word == NULL || (!empty && *word == NUL))
|
||||||
|
{
|
||||||
|
clear_tv(&user_data);
|
||||||
return FAIL;
|
return FAIL;
|
||||||
return ins_compl_add(word, -1, NULL, cptext, &user_data, dir, flags, dup);
|
}
|
||||||
|
status = ins_compl_add(word, -1, NULL, cptext, &user_data, dir, flags, dup);
|
||||||
|
if (status != OK)
|
||||||
|
clear_tv(&user_data);
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -3157,8 +3178,8 @@ typedef struct
|
|||||||
* st->dict_f - flag specifying whether "dict" is an exact file name or not
|
* st->dict_f - flag specifying whether "dict" is an exact file name or not
|
||||||
*
|
*
|
||||||
* Returns INS_COMPL_CPT_OK if the next value is processed successfully.
|
* Returns INS_COMPL_CPT_OK if the next value is processed successfully.
|
||||||
* Returns INS_COMPL_CPT_CONT to skip the current value and process the next
|
* Returns INS_COMPL_CPT_CONT to skip the current completion source matching
|
||||||
* option value.
|
* the "st->e_cpt" option value and process the next matching source.
|
||||||
* Returns INS_COMPL_CPT_END if all the values in "st->e_cpt" are processed.
|
* Returns INS_COMPL_CPT_END if all the values in "st->e_cpt" are processed.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
@@ -4521,7 +4542,7 @@ get_userdefined_compl_info(colnr_T curs_col UNUSED)
|
|||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset extended parameters of completion, when start new
|
// Reset extended parameters of completion, when starting new
|
||||||
// completion.
|
// completion.
|
||||||
compl_opt_refresh_always = FALSE;
|
compl_opt_refresh_always = FALSE;
|
||||||
compl_opt_suppress_empty = FALSE;
|
compl_opt_suppress_empty = FALSE;
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ func Test_ins_complete()
|
|||||||
call assert_equal('Xtest11.one', getline('.'))
|
call assert_equal('Xtest11.one', getline('.'))
|
||||||
normal ddk
|
normal ddk
|
||||||
|
|
||||||
|
" Test for expanding a non-existing filename
|
||||||
|
exe "normal oa1b2X3Y4\<C-X>\<C-F>"
|
||||||
|
call assert_equal('a1b2X3Y4', getline('.'))
|
||||||
|
normal ddk
|
||||||
|
|
||||||
set cpt=w
|
set cpt=w
|
||||||
" checks make_cyclic in other window
|
" checks make_cyclic in other window
|
||||||
exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>"
|
exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>"
|
||||||
@@ -981,6 +986,27 @@ func Test_complete_erase_firstmatch()
|
|||||||
bw!
|
bw!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
" Test for completing words from unloaded buffers
|
||||||
|
func Test_complete_from_unloadedbuf()
|
||||||
|
call writefile(['abc'], "Xfile1")
|
||||||
|
call writefile(['def'], "Xfile2")
|
||||||
|
edit Xfile1
|
||||||
|
edit Xfile2
|
||||||
|
new | close
|
||||||
|
enew
|
||||||
|
bunload Xfile1 Xfile2
|
||||||
|
set complete=u
|
||||||
|
" complete from an unloaded buffer
|
||||||
|
exe "normal! ia\<C-P>"
|
||||||
|
call assert_equal('abc', getline(1))
|
||||||
|
exe "normal! od\<C-P>"
|
||||||
|
call assert_equal('def', getline(2))
|
||||||
|
set complete&
|
||||||
|
%bw!
|
||||||
|
call delete("Xfile1")
|
||||||
|
call delete("Xfile2")
|
||||||
|
endfunc
|
||||||
|
|
||||||
" Test for completing whole lines from unloaded buffers
|
" Test for completing whole lines from unloaded buffers
|
||||||
func Test_complete_wholeline_unloadedbuf()
|
func Test_complete_wholeline_unloadedbuf()
|
||||||
call writefile(['a line1', 'a line2', 'a line3'], "Xfile1")
|
call writefile(['a line1', 'a line2', 'a line3'], "Xfile1")
|
||||||
@@ -999,6 +1025,26 @@ func Test_complete_wholeline_unloadedbuf()
|
|||||||
call delete("Xfile1")
|
call delete("Xfile1")
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
" Test for completing words from unlisted buffers
|
||||||
|
func Test_complete_from_unlistedbuf()
|
||||||
|
call writefile(['abc'], "Xfile1")
|
||||||
|
call writefile(['def'], "Xfile2")
|
||||||
|
edit Xfile1
|
||||||
|
edit Xfile2
|
||||||
|
new | close
|
||||||
|
bdel Xfile1 Xfile2
|
||||||
|
set complete=U
|
||||||
|
" complete from an unlisted buffer
|
||||||
|
exe "normal! ia\<C-P>"
|
||||||
|
call assert_equal('abc', getline(1))
|
||||||
|
exe "normal! od\<C-P>"
|
||||||
|
call assert_equal('def', getline(2))
|
||||||
|
set complete&
|
||||||
|
%bw!
|
||||||
|
call delete("Xfile1")
|
||||||
|
call delete("Xfile2")
|
||||||
|
endfunc
|
||||||
|
|
||||||
" Test for completing whole lines from unlisted buffers
|
" Test for completing whole lines from unlisted buffers
|
||||||
func Test_complete_wholeline_unlistedbuf()
|
func Test_complete_wholeline_unlistedbuf()
|
||||||
call writefile(['a line1', 'a line2', 'a line3'], "Xfile1")
|
call writefile(['a line1', 'a line2', 'a line3'], "Xfile1")
|
||||||
@@ -1032,6 +1078,195 @@ func Test_complete_mbyte_char_add()
|
|||||||
bw!
|
bw!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
" Test for using <C-X><C-P> for local expansion even if 'complete' is set to
|
||||||
|
" not to complete matches from the local buffer. Also test using multiple
|
||||||
|
" <C-X> to cancel the current completion mode.
|
||||||
|
func Test_complete_local_expansion()
|
||||||
|
new
|
||||||
|
set complete=t
|
||||||
|
call setline(1, ['abc', 'def'])
|
||||||
|
exe "normal! Go\<C-X>\<C-P>"
|
||||||
|
call assert_equal("def", getline(3))
|
||||||
|
exe "normal! Go\<C-P>"
|
||||||
|
call assert_equal("", getline(4))
|
||||||
|
exe "normal! Go\<C-X>\<C-N>"
|
||||||
|
call assert_equal("abc", getline(5))
|
||||||
|
exe "normal! Go\<C-N>"
|
||||||
|
call assert_equal("", getline(6))
|
||||||
|
|
||||||
|
" use multiple <C-X> to cancel the previous completion mode
|
||||||
|
exe "normal! Go\<C-P>\<C-X>\<C-P>"
|
||||||
|
call assert_equal("", getline(7))
|
||||||
|
exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-P>"
|
||||||
|
call assert_equal("", getline(8))
|
||||||
|
exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-X>\<C-P>"
|
||||||
|
call assert_equal("abc", getline(9))
|
||||||
|
|
||||||
|
" interrupt the current completion mode
|
||||||
|
set completeopt=menu,noinsert
|
||||||
|
exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-X>\<C-P>\<C-Y>"
|
||||||
|
call assert_equal("abc", getline(10))
|
||||||
|
|
||||||
|
" when only one <C-X> is used to interrupt, do normal expansion
|
||||||
|
exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-P>"
|
||||||
|
call assert_equal("", getline(11))
|
||||||
|
set completeopt&
|
||||||
|
|
||||||
|
" using two <C-X> in non-completion mode and restarting the same mode
|
||||||
|
exe "normal! God\<C-X>\<C-X>\<C-P>\<C-X>\<C-X>\<C-P>\<C-Y>"
|
||||||
|
call assert_equal("def", getline(12))
|
||||||
|
|
||||||
|
" test for adding a match from the original empty text
|
||||||
|
%d
|
||||||
|
call setline(1, 'abc def g')
|
||||||
|
exe "normal! o\<C-X>\<C-P>\<C-N>\<C-X>\<C-P>"
|
||||||
|
call assert_equal('def', getline(2))
|
||||||
|
exe "normal! 0C\<C-X>\<C-N>\<C-P>\<C-X>\<C-N>"
|
||||||
|
call assert_equal('abc', getline(2))
|
||||||
|
|
||||||
|
bw!
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
" Test for undoing changes after a insert-mode completion
|
||||||
|
func Test_complete_undo()
|
||||||
|
new
|
||||||
|
set complete=.
|
||||||
|
" undo with 'ignorecase'
|
||||||
|
call setline(1, ['ABOVE', 'BELOW'])
|
||||||
|
set ignorecase
|
||||||
|
exe "normal! Goab\<C-G>u\<C-P>"
|
||||||
|
call assert_equal("ABOVE", getline(3))
|
||||||
|
undo
|
||||||
|
call assert_equal("ab", getline(3))
|
||||||
|
set ignorecase&
|
||||||
|
%d
|
||||||
|
" undo with longest match
|
||||||
|
set completeopt=menu,longest
|
||||||
|
call setline(1, ['above', 'about'])
|
||||||
|
exe "normal! Goa\<C-G>u\<C-P>"
|
||||||
|
call assert_equal("abo", getline(3))
|
||||||
|
undo
|
||||||
|
call assert_equal("a", getline(3))
|
||||||
|
set completeopt&
|
||||||
|
%d
|
||||||
|
" undo for line completion
|
||||||
|
call setline(1, ['above that change', 'below that change'])
|
||||||
|
exe "normal! Goabove\<C-G>u\<C-X>\<C-L>"
|
||||||
|
call assert_equal("above that change", getline(3))
|
||||||
|
undo
|
||||||
|
call assert_equal("above", getline(3))
|
||||||
|
|
||||||
|
bw!
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
" Test for completing a very long word
|
||||||
|
func Test_complete_long_word()
|
||||||
|
set complete&
|
||||||
|
new
|
||||||
|
call setline(1, repeat('x', 950) .. ' one two three')
|
||||||
|
exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
|
||||||
|
call assert_equal(repeat('x', 950) .. ' one two three', getline(2))
|
||||||
|
%d
|
||||||
|
" should fail when more than 950 characters are in a word
|
||||||
|
call setline(1, repeat('x', 951) .. ' one two three')
|
||||||
|
exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>"
|
||||||
|
call assert_equal(repeat('x', 951), getline(2))
|
||||||
|
|
||||||
|
" Test for adding a very long word to an existing completion
|
||||||
|
%d
|
||||||
|
call setline(1, ['abc', repeat('x', 1016) .. '012345'])
|
||||||
|
exe "normal! Goab\<C-P>\<C-X>\<C-P>"
|
||||||
|
call assert_equal('abc ' .. repeat('x', 1016) .. '0123', getline(3))
|
||||||
|
bw!
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
" Test for some fields in the complete items used by complete()
|
||||||
|
func Test_complete_items()
|
||||||
|
func CompleteItems(idx)
|
||||||
|
let items = [[#{word: "one", dup: 1, user_data: 'u1'}, #{word: "one", dup: 1, user_data: 'u2'}],
|
||||||
|
\ [#{word: "one", dup: 0, user_data: 'u3'}, #{word: "one", dup: 0, user_data: 'u4'}],
|
||||||
|
\ [#{word: "one", icase: 1, user_data: 'u7'}, #{word: "oNE", icase: 1, user_data: 'u8'}],
|
||||||
|
\ [#{user_data: 'u9'}],
|
||||||
|
\ [#{word: "", user_data: 'u10'}],
|
||||||
|
\ [#{word: "", empty: 1, user_data: 'u11'}]]
|
||||||
|
call complete(col('.'), items[a:idx])
|
||||||
|
return ''
|
||||||
|
endfunc
|
||||||
|
new
|
||||||
|
exe "normal! i\<C-R>=CompleteItems(0)\<CR>\<C-N>\<C-Y>"
|
||||||
|
call assert_equal('u2', v:completed_item.user_data)
|
||||||
|
call assert_equal('one', getline(1))
|
||||||
|
exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-Y>"
|
||||||
|
call assert_equal('u3', v:completed_item.user_data)
|
||||||
|
call assert_equal('one', getline(2))
|
||||||
|
exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-N>"
|
||||||
|
call assert_equal('', getline(3))
|
||||||
|
set completeopt=menu,noinsert
|
||||||
|
exe "normal! o\<C-R>=CompleteItems(2)\<CR>one\<C-N>\<C-Y>"
|
||||||
|
call assert_equal('oNE', getline(4))
|
||||||
|
call assert_equal('u8', v:completed_item.user_data)
|
||||||
|
set completeopt&
|
||||||
|
exe "normal! o\<C-R>=CompleteItems(3)\<CR>"
|
||||||
|
call assert_equal('', getline(5))
|
||||||
|
exe "normal! o\<C-R>=CompleteItems(4)\<CR>"
|
||||||
|
call assert_equal('', getline(6))
|
||||||
|
exe "normal! o\<C-R>=CompleteItems(5)\<CR>"
|
||||||
|
call assert_equal('', getline(7))
|
||||||
|
call assert_equal('u11', v:completed_item.user_data)
|
||||||
|
" pass invalid argument to complete()
|
||||||
|
let cmd = "normal! o\<C-R>=complete(1, [[]])\<CR>"
|
||||||
|
call assert_fails('exe cmd', 'E730:')
|
||||||
|
bw!
|
||||||
|
delfunc CompleteItems
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
" Test for the "refresh" item in the dict returned by an insert completion
|
||||||
|
" function
|
||||||
|
func Test_complete_item_refresh_always()
|
||||||
|
let g:CallCount = 0
|
||||||
|
func! Tcomplete(findstart, base)
|
||||||
|
if a:findstart
|
||||||
|
" locate the start of the word
|
||||||
|
let line = getline('.')
|
||||||
|
let start = col('.') - 1
|
||||||
|
while start > 0 && line[start - 1] =~ '\a'
|
||||||
|
let start -= 1
|
||||||
|
endwhile
|
||||||
|
return start
|
||||||
|
else
|
||||||
|
let g:CallCount += 1
|
||||||
|
let res = ["update1", "update12", "update123"]
|
||||||
|
return #{words: res, refresh: 'always'}
|
||||||
|
endif
|
||||||
|
endfunc
|
||||||
|
new
|
||||||
|
set completeopt=menu,longest
|
||||||
|
set completefunc=Tcomplete
|
||||||
|
exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>"
|
||||||
|
call assert_equal('up', getline(1))
|
||||||
|
call assert_equal(2, g:CallCount)
|
||||||
|
set completeopt&
|
||||||
|
set completefunc&
|
||||||
|
bw!
|
||||||
|
delfunc Tcomplete
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
" Test for completing from a thesaurus file without read permission
|
||||||
|
func Test_complete_unreadable_thesaurus_file()
|
||||||
|
CheckUnix
|
||||||
|
CheckNotRoot
|
||||||
|
|
||||||
|
call writefile(['about', 'above'], 'Xfile')
|
||||||
|
call setfperm('Xfile', '---r--r--')
|
||||||
|
new
|
||||||
|
set complete=sXfile
|
||||||
|
exe "normal! ia\<C-P>"
|
||||||
|
call assert_equal('a', getline(1))
|
||||||
|
bw!
|
||||||
|
call delete('Xfile')
|
||||||
|
set complete&
|
||||||
|
endfunc
|
||||||
|
|
||||||
" Test to ensure 'Scanning...' messages are not recorded in messages history
|
" Test to ensure 'Scanning...' messages are not recorded in messages history
|
||||||
func Test_z1_complete_no_history()
|
func Test_z1_complete_no_history()
|
||||||
new
|
new
|
||||||
|
|||||||
@@ -750,6 +750,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
4037,
|
||||||
/**/
|
/**/
|
||||||
4036,
|
4036,
|
||||||
/**/
|
/**/
|
||||||
|
|||||||
Reference in New Issue
Block a user