aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2022-08-25 08:04:25 +0800
committerzeertzjq <zeertzjq@outlook.com>2022-08-25 08:27:44 +0800
commit3290e472c09c126c014411c73a17d0b1827a795f (patch)
tree4064785e9951b08ecf766b777a4ec1766735af78 /src
parentcabc1861c478b36570bc8d6633097cd4729b8303 (diff)
downloadrneovim-3290e472c09c126c014411c73a17d0b1827a795f.tar.gz
rneovim-3290e472c09c126c014411c73a17d0b1827a795f.tar.bz2
rneovim-3290e472c09c126c014411c73a17d0b1827a795f.zip
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 vim/vim#9489) https://github.com/vim/vim/commit/370791465e745354d66696de8cbd15504cf958c0
Diffstat (limited to 'src')
-rw-r--r--src/nvim/insexpand.c38
-rw-r--r--src/nvim/testdir/test_ins_complete.vim235
2 files changed, 260 insertions, 13 deletions
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 21285a4365..ec885b33c3 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -659,17 +659,23 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname,
/// Add a match to the list of matches
///
-/// @param[in] str Match to add.
-/// @param[in] len Match length, -1 to use #STRLEN.
-/// @param[in] fname File name match comes from. May be NULL.
-/// @param[in] cptext Extra text for popup menu. May be NULL. If not NULL,
-/// must have exactly #CPT_COUNT items.
+/// @param[in] str text of the match to add
+/// @param[in] len length of "str". If -1, then the length of "str" is computed.
+/// @param[in] fname file name to associate with this match. May be NULL.
+/// @param[in] cptext list of strings to use with this match (for abbr, menu, info
+/// and kind). May be NULL.
+/// If not NULL, must have exactly #CPT_COUNT items.
/// @param[in] cptext_allocated If true, will not copy cptext strings.
///
/// @note Will free strings in case of error.
/// cptext itself will not be freed.
-/// @param[in] cdir Completion direction.
-/// @param[in] adup True if duplicate matches are to be accepted.
+/// @param[in] user_data user supplied data (any vim type) for this match
+/// @param[in] cdir match direction. If 0, use "compl_direction".
+/// @param[in] flags_arg match flags (cp_flags)
+/// @param[in] 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.
///
/// @return NOTDONE if the given string is already in the list of completions,
/// otherwise it is added to the list and OK is returned. FAIL will be
@@ -768,7 +774,8 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname,
match->cp_user_data = *user_data;
}
- // 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) {
match->cp_next = match->cp_prev = NULL;
} else if (dir == FORWARD) {
@@ -2265,10 +2272,15 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
for (size_t i = 0; i < CPT_COUNT; i++) {
xfree(cptext[i]);
}
+ tv_clear(&user_data);
return FAIL;
}
- return ins_compl_add((char_u *)word, -1, NULL,
- (char_u **)cptext, true, &user_data, dir, flags, dup);
+ int status = ins_compl_add((char_u *)word, -1, NULL, (char_u **)cptext, true,
+ &user_data, dir, flags, dup);
+ if (status != OK) {
+ tv_clear(&user_data);
+ }
+ return status;
}
/// Add completions from a list.
@@ -2606,8 +2618,8 @@ enum {
/// st->dict_f - flag specifying whether "dict" is an exact file name or not
///
/// @return INS_COMPL_CPT_OK if the next value is processed successfully.
-/// INS_COMPL_CPT_CONT to skip the current value and process the next
-/// option value.
+/// INS_COMPL_CPT_CONT to skip the current completion source matching
+/// the "st->e_cpt" option value and process the next matching source.
/// INS_COMPL_CPT_END if all the values in "st->e_cpt" are processed.
static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_arg,
pos_T *start_match_pos)
@@ -3788,7 +3800,7 @@ static int get_userdefined_compl_info(colnr_T curs_col)
return FAIL;
}
- // Reset extended parameters of completion, when start new
+ // Reset extended parameters of completion, when starting new
// completion.
compl_opt_refresh_always = false;
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index 914d818eef..3e563f29f9 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -68,6 +68,11 @@ func Test_ins_complete()
call assert_equal('Xtest11.one', getline('.'))
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
" checks make_cyclic in other window
exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>"
@@ -955,6 +960,27 @@ func Test_complete_erase_firstmatch()
bw!
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
func Test_complete_wholeline_unloadedbuf()
call writefile(['a line1', 'a line2', 'a line3'], "Xfile1")
@@ -973,6 +999,26 @@ func Test_complete_wholeline_unloadedbuf()
call delete("Xfile1")
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
func Test_complete_wholeline_unlistedbuf()
call writefile(['a line1', 'a line2', 'a line3'], "Xfile1")
@@ -1006,6 +1052,195 @@ func Test_complete_mbyte_char_add()
bw!
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
func Test_z1_complete_no_history()
new