mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00
vim-patch:9.1.1086: completion doesn't work with multi lines (#32377)
Problem: completion doesn't work with multi lines
(Łukasz Jan Niemier)
Solution: handle linebreaks in completion code as expected
(glepnir)
fixes: vim/vim#2505
closes: vim/vim#15373
76bdb82527
This commit is contained in:
@@ -1539,7 +1539,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
|
|||||||
ptr = line + v; // "line" may have been updated
|
ptr = line + v; // "line" may have been updated
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((State & MODE_INSERT) && in_curline && ins_compl_win_active(wp)) {
|
if ((State & MODE_INSERT) && ins_compl_win_active(wp)
|
||||||
|
&& (in_curline || ins_compl_lnum_in_range(lnum))) {
|
||||||
area_highlighting = true;
|
area_highlighting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1787,8 +1788,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if ComplMatchIns highlight is needed.
|
// Check if ComplMatchIns highlight is needed.
|
||||||
if ((State & MODE_INSERT) && in_curline && ins_compl_win_active(wp)) {
|
if ((State & MODE_INSERT) && ins_compl_win_active(wp)
|
||||||
int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line));
|
&& (in_curline || ins_compl_lnum_in_range(lnum))) {
|
||||||
|
int ins_match_attr = ins_compl_col_range_attr(lnum, (int)(ptr - line));
|
||||||
if (ins_match_attr > 0) {
|
if (ins_match_attr > 0) {
|
||||||
search_attr = hl_combine_attr(search_attr, ins_match_attr);
|
search_attr = hl_combine_attr(search_attr, ins_match_attr);
|
||||||
}
|
}
|
||||||
|
@@ -254,6 +254,7 @@ static pos_T compl_startpos;
|
|||||||
/// Length in bytes of the text being completed (this is deleted to be replaced
|
/// Length in bytes of the text being completed (this is deleted to be replaced
|
||||||
/// by the match.)
|
/// by the match.)
|
||||||
static int compl_length = 0;
|
static int compl_length = 0;
|
||||||
|
static linenr_T compl_lnum = 0; ///< lnum where the completion start
|
||||||
static colnr_T compl_col = 0; ///< column where the text starts
|
static colnr_T compl_col = 0; ///< column where the text starts
|
||||||
///< that is being completed
|
///< that is being completed
|
||||||
static colnr_T compl_ins_end_col = 0;
|
static colnr_T compl_ins_end_col = 0;
|
||||||
@@ -963,20 +964,47 @@ static void ins_compl_insert_bytes(char *p, int len)
|
|||||||
|
|
||||||
/// Checks if the column is within the currently inserted completion text
|
/// Checks if the column is within the currently inserted completion text
|
||||||
/// column range. If it is, it returns a special highlight attribute.
|
/// column range. If it is, it returns a special highlight attribute.
|
||||||
/// -1 mean normal item.
|
/// -1 means normal item.
|
||||||
int ins_compl_col_range_attr(int col)
|
int ins_compl_col_range_attr(linenr_T lnum, int col)
|
||||||
{
|
{
|
||||||
if (get_cot_flags() & kOptCotFlagFuzzy) {
|
int attr;
|
||||||
|
if ((get_cot_flags() & kOptCotFlagFuzzy) || (attr = syn_name2attr("ComplMatchIns")) == 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (col >= (compl_col + (int)ins_compl_leader_len()) && col < compl_ins_end_col) {
|
int start_col = compl_col + (int)ins_compl_leader_len();
|
||||||
return syn_name2attr("ComplMatchIns");
|
if (!ins_compl_has_multiple()) {
|
||||||
|
return (col >= start_col && col < compl_ins_end_col) ? attr : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiple lines
|
||||||
|
if ((lnum == compl_lnum && col >= start_col && col < MAXCOL)
|
||||||
|
|| (lnum > compl_lnum && lnum < curwin->w_cursor.lnum)
|
||||||
|
|| (lnum == curwin->w_cursor.lnum && col <= compl_ins_end_col)) {
|
||||||
|
return attr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the current completion string contains newline characters,
|
||||||
|
/// indicating it's a multi-line completion.
|
||||||
|
static bool ins_compl_has_multiple(void)
|
||||||
|
{
|
||||||
|
return vim_strchr(compl_shown_match->cp_str.data, '\n') != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given line number falls within the range of a multi-line
|
||||||
|
/// completion, i.e. between the starting line (compl_lnum) and current cursor
|
||||||
|
/// line. Always returns false for single-line completions.
|
||||||
|
bool ins_compl_lnum_in_range(linenr_T lnum)
|
||||||
|
{
|
||||||
|
if (!ins_compl_has_multiple()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return lnum >= compl_lnum && lnum <= curwin->w_cursor.lnum;
|
||||||
|
}
|
||||||
|
|
||||||
/// Reduce the longest common string for match "match".
|
/// Reduce the longest common string for match "match".
|
||||||
static void ins_compl_longest_match(compl_T *match)
|
static void ins_compl_longest_match(compl_T *match)
|
||||||
{
|
{
|
||||||
@@ -2777,6 +2805,7 @@ static void set_completion(colnr_T startcol, list_T *list)
|
|||||||
startcol = curwin->w_cursor.col;
|
startcol = curwin->w_cursor.col;
|
||||||
}
|
}
|
||||||
compl_col = startcol;
|
compl_col = startcol;
|
||||||
|
compl_lnum = curwin->w_cursor.lnum;
|
||||||
compl_length = curwin->w_cursor.col - startcol;
|
compl_length = curwin->w_cursor.col - startcol;
|
||||||
// compl_pattern doesn't need to be set
|
// compl_pattern doesn't need to be set
|
||||||
compl_orig_text = cbuf_to_string(get_cursor_line_ptr() + compl_col,
|
compl_orig_text = cbuf_to_string(get_cursor_line_ptr() + compl_col,
|
||||||
@@ -3737,6 +3766,26 @@ void ins_compl_delete(bool new_leader)
|
|||||||
curwin->w_cursor.col = compl_ins_end_col;
|
curwin->w_cursor.col = compl_ins_end_col;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *remaining = NULL;
|
||||||
|
if (curwin->w_cursor.lnum > compl_lnum) {
|
||||||
|
if (curwin->w_cursor.col < get_cursor_line_len()) {
|
||||||
|
char *line = get_cursor_line_ptr();
|
||||||
|
remaining = xstrdup(line + curwin->w_cursor.col);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (curwin->w_cursor.lnum > compl_lnum) {
|
||||||
|
if (ml_delete(curwin->w_cursor.lnum, false) == FAIL) {
|
||||||
|
if (remaining) {
|
||||||
|
XFREE_CLEAR(remaining);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
curwin->w_cursor.lnum--;
|
||||||
|
}
|
||||||
|
// move cursor to end of line
|
||||||
|
curwin->w_cursor.col = get_cursor_line_len();
|
||||||
|
}
|
||||||
|
|
||||||
if ((int)curwin->w_cursor.col > col) {
|
if ((int)curwin->w_cursor.col > col) {
|
||||||
if (stop_arrow() == FAIL) {
|
if (stop_arrow() == FAIL) {
|
||||||
return;
|
return;
|
||||||
@@ -3745,6 +3794,13 @@ void ins_compl_delete(bool new_leader)
|
|||||||
compl_ins_end_col = curwin->w_cursor.col;
|
compl_ins_end_col = curwin->w_cursor.col;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (remaining != NULL) {
|
||||||
|
orig_col = curwin->w_cursor.col;
|
||||||
|
ins_str(remaining);
|
||||||
|
curwin->w_cursor.col = orig_col;
|
||||||
|
xfree(remaining);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(vim): is this sufficient for redrawing? Redrawing everything
|
// TODO(vim): is this sufficient for redrawing? Redrawing everything
|
||||||
// causes flicker, thus we can't do that.
|
// causes flicker, thus we can't do that.
|
||||||
changed_cline_bef_curs(curwin);
|
changed_cline_bef_curs(curwin);
|
||||||
@@ -3752,6 +3808,34 @@ void ins_compl_delete(bool new_leader)
|
|||||||
set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
|
set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert a completion string that contains newlines.
|
||||||
|
/// The string is split and inserted line by line.
|
||||||
|
static void ins_compl_expand_multiple(char *str)
|
||||||
|
{
|
||||||
|
char *start = str;
|
||||||
|
char *curr = str;
|
||||||
|
while (*curr != NUL) {
|
||||||
|
if (*curr == '\n') {
|
||||||
|
// Insert the text chunk before newline
|
||||||
|
if (curr > start) {
|
||||||
|
ins_char_bytes(start, (size_t)(curr - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle newline
|
||||||
|
open_line(FORWARD, OPENLINE_KEEPTRAIL, false, NULL);
|
||||||
|
start = curr + 1;
|
||||||
|
}
|
||||||
|
curr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle remaining text after last newline (if any)
|
||||||
|
if (curr > start) {
|
||||||
|
ins_char_bytes(start, (size_t)(curr - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
compl_ins_end_col = curwin->w_cursor.col;
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert the new text being completed.
|
/// Insert the new text being completed.
|
||||||
/// "in_compl_func" is true when called from complete_check().
|
/// "in_compl_func" is true when called from complete_check().
|
||||||
/// "move_cursor" is used when 'completeopt' includes "preinsert" and when true
|
/// "move_cursor" is used when 'completeopt' includes "preinsert" and when true
|
||||||
@@ -3763,13 +3847,18 @@ void ins_compl_insert(bool in_compl_func, bool move_cursor)
|
|||||||
char *cp_str = compl_shown_match->cp_str.data;
|
char *cp_str = compl_shown_match->cp_str.data;
|
||||||
size_t cp_str_len = compl_shown_match->cp_str.size;
|
size_t cp_str_len = compl_shown_match->cp_str.size;
|
||||||
size_t leader_len = ins_compl_leader_len();
|
size_t leader_len = ins_compl_leader_len();
|
||||||
|
char *has_multiple = strchr(cp_str, '\n');
|
||||||
|
|
||||||
// Make sure we don't go over the end of the string, this can happen with
|
// Make sure we don't go over the end of the string, this can happen with
|
||||||
// illegal bytes.
|
// illegal bytes.
|
||||||
if (compl_len < (int)cp_str_len) {
|
if (compl_len < (int)cp_str_len) {
|
||||||
ins_compl_insert_bytes(cp_str + compl_len, -1);
|
if (has_multiple) {
|
||||||
if (preinsert && move_cursor) {
|
ins_compl_expand_multiple(cp_str + compl_len);
|
||||||
curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len);
|
} else {
|
||||||
|
ins_compl_insert_bytes(cp_str + compl_len, -1);
|
||||||
|
if (preinsert && move_cursor) {
|
||||||
|
curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compl_used_match = !(match_at_original_text(compl_shown_match) || preinsert);
|
compl_used_match = !(match_at_original_text(compl_shown_match) || preinsert);
|
||||||
@@ -4551,6 +4640,7 @@ static int ins_compl_start(void)
|
|||||||
char *line = ml_get(curwin->w_cursor.lnum);
|
char *line = ml_get(curwin->w_cursor.lnum);
|
||||||
colnr_T curs_col = curwin->w_cursor.col;
|
colnr_T curs_col = curwin->w_cursor.col;
|
||||||
compl_pending = 0;
|
compl_pending = 0;
|
||||||
|
compl_lnum = curwin->w_cursor.lnum;
|
||||||
|
|
||||||
if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT
|
if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT
|
||||||
&& compl_cont_mode == ctrl_x_mode) {
|
&& compl_cont_mode == ctrl_x_mode) {
|
||||||
@@ -4602,6 +4692,7 @@ static int ins_compl_start(void)
|
|||||||
curbuf->b_p_com = old;
|
curbuf->b_p_com = old;
|
||||||
compl_length = 0;
|
compl_length = 0;
|
||||||
compl_col = curwin->w_cursor.col;
|
compl_col = curwin->w_cursor.col;
|
||||||
|
compl_lnum = curwin->w_cursor.lnum;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
edit_submode_pre = NULL;
|
edit_submode_pre = NULL;
|
||||||
|
@@ -5935,6 +5935,114 @@ describe('builtin popupmenu', function()
|
|||||||
]])
|
]])
|
||||||
feed('<C-E><Esc>')
|
feed('<C-E><Esc>')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- oldtest: Test_pum_complete_with_special_characters()
|
||||||
|
it('multi-line completion', function()
|
||||||
|
exec([[
|
||||||
|
func Omni_test(findstart, base)
|
||||||
|
if a:findstart
|
||||||
|
return col(".")
|
||||||
|
endif
|
||||||
|
return [#{word: "func ()\n\t\nend", abbr: "function ()",}, #{word: "foobar"}, #{word: "你好\n\t\n我好"}]
|
||||||
|
endfunc
|
||||||
|
set omnifunc=Omni_test
|
||||||
|
]])
|
||||||
|
|
||||||
|
feed('S<C-X><C-O>')
|
||||||
|
screen:expect([[
|
||||||
|
func () |
|
||||||
|
|
|
||||||
|
end^ |
|
||||||
|
{s:function () }{1: }|
|
||||||
|
{n:foobar }{1: }|
|
||||||
|
{n:你好^@ ^@我好 }{1: }|
|
||||||
|
{1:~ }|*13
|
||||||
|
{2:-- }{5:match 1 of 3} |
|
||||||
|
]])
|
||||||
|
|
||||||
|
feed('<C-N>')
|
||||||
|
screen:expect([[
|
||||||
|
foobar^ |
|
||||||
|
{n:function () }{1: }|
|
||||||
|
{s:foobar }{1: }|
|
||||||
|
{n:你好^@ ^@我好 }{1: }|
|
||||||
|
{1:~ }|*15
|
||||||
|
{2:-- }{5:match 2 of 3} |
|
||||||
|
]])
|
||||||
|
feed('<C-E><ESC>')
|
||||||
|
|
||||||
|
feed('Shello hero<ESC>hhhhha<C-X><C-O>')
|
||||||
|
screen:expect([[
|
||||||
|
hello func () |
|
||||||
|
|
|
||||||
|
end^ hero |
|
||||||
|
{1:~ }{s: function () }{1: }|
|
||||||
|
{1:~ }{n: foobar }{1: }|
|
||||||
|
{1:~ }{n: 你好^@ ^@我好 }{1: }|
|
||||||
|
{1:~ }|*13
|
||||||
|
{2:-- }{5:match 1 of 3} |
|
||||||
|
]])
|
||||||
|
|
||||||
|
feed('<C-N>')
|
||||||
|
screen:expect([[
|
||||||
|
hello foobar^ hero |
|
||||||
|
{1:~ }{n: function () }{1: }|
|
||||||
|
{1:~ }{s: foobar }{1: }|
|
||||||
|
{1:~ }{n: 你好^@ ^@我好 }{1: }|
|
||||||
|
{1:~ }|*15
|
||||||
|
{2:-- }{5:match 2 of 3} |
|
||||||
|
]])
|
||||||
|
|
||||||
|
feed('<C-N>')
|
||||||
|
screen:expect([[
|
||||||
|
hello 你好 |
|
||||||
|
|
|
||||||
|
我好^ hero |
|
||||||
|
{1:~ }{n: function () }{1: }|
|
||||||
|
{1:~ }{n: foobar }{1: }|
|
||||||
|
{1:~ }{s: 你好^@ ^@我好 }{1: }|
|
||||||
|
{1:~ }|*13
|
||||||
|
{2:-- }{5:match 3 of 3} |
|
||||||
|
]])
|
||||||
|
|
||||||
|
feed('<C-N>')
|
||||||
|
screen:expect([[
|
||||||
|
hello ^ hero |
|
||||||
|
{1:~ }{n: function () }{1: }|
|
||||||
|
{1:~ }{n: foobar }{1: }|
|
||||||
|
{1:~ }{n: 你好^@ ^@我好 }{1: }|
|
||||||
|
{1:~ }|*15
|
||||||
|
{2:-- }{8:Back at original} |
|
||||||
|
]])
|
||||||
|
feed('<C-E><ESC>')
|
||||||
|
|
||||||
|
command(':hi ComplMatchIns guifg=red')
|
||||||
|
feed('S<C-X><C-O>')
|
||||||
|
screen:expect([[
|
||||||
|
{8:func ()} |
|
||||||
|
{8: } |
|
||||||
|
{8:end}^ |
|
||||||
|
{s:function () }{1: }|
|
||||||
|
{n:foobar }{1: }|
|
||||||
|
{n:你好^@ ^@我好 }{1: }|
|
||||||
|
{1:~ }|*13
|
||||||
|
{2:-- }{5:match 1 of 3} |
|
||||||
|
]])
|
||||||
|
feed('<C-E><ESC>')
|
||||||
|
|
||||||
|
feed('Shello hero<ESC>hhhhha<C-X><C-O>')
|
||||||
|
screen:expect([[
|
||||||
|
hello {8:func ()} |
|
||||||
|
{8: } |
|
||||||
|
{8:end^ }hero |
|
||||||
|
{1:~ }{s: function () }{1: }|
|
||||||
|
{1:~ }{n: foobar }{1: }|
|
||||||
|
{1:~ }{n: 你好^@ ^@我好 }{1: }|
|
||||||
|
{1:~ }|*13
|
||||||
|
{2:-- }{5:match 1 of 3} |
|
||||||
|
]])
|
||||||
|
feed('<C-E><ESC>')
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -1885,4 +1885,59 @@ func Test_popup_completion_many_ctrlp()
|
|||||||
bw!
|
bw!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func Test_pum_complete_with_special_characters()
|
||||||
|
CheckScreendump
|
||||||
|
|
||||||
|
let lines =<< trim END
|
||||||
|
func Omni_test(findstart, base)
|
||||||
|
if a:findstart
|
||||||
|
return col(".")
|
||||||
|
endif
|
||||||
|
return [#{word: "func ()\n\t\nend", abbr: "function ()",}, #{word: "foobar"}, #{word: "你好\n\t\n我好"}]
|
||||||
|
endfunc
|
||||||
|
set omnifunc=Omni_test
|
||||||
|
END
|
||||||
|
|
||||||
|
call writefile(lines, 'Xpreviewscript', 'D')
|
||||||
|
let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12})
|
||||||
|
call term_sendkeys(buf, "S\<C-X>\<C-O>")
|
||||||
|
call TermWait(buf, 50)
|
||||||
|
call VerifyScreenDump(buf, 'Test_pum_with_special_characters_01', {})
|
||||||
|
|
||||||
|
call term_sendkeys(buf, "\<C-N>")
|
||||||
|
call TermWait(buf, 50)
|
||||||
|
call VerifyScreenDump(buf, 'Test_pum_with_special_characters_02', {})
|
||||||
|
call term_sendkeys(buf, "\<C-E>\<Esc>")
|
||||||
|
|
||||||
|
call term_sendkeys(buf, "Shello hero\<ESC>hhhhha\<C-X>\<C-O>")
|
||||||
|
call TermWait(buf, 50)
|
||||||
|
call VerifyScreenDump(buf, 'Test_pum_with_special_characters_03', {})
|
||||||
|
|
||||||
|
call term_sendkeys(buf, "\<C-N>")
|
||||||
|
call TermWait(buf, 50)
|
||||||
|
call VerifyScreenDump(buf, 'Test_pum_with_special_characters_04', {})
|
||||||
|
|
||||||
|
call term_sendkeys(buf, "\<C-N>")
|
||||||
|
call TermWait(buf, 50)
|
||||||
|
call VerifyScreenDump(buf, 'Test_pum_with_special_characters_05', {})
|
||||||
|
|
||||||
|
call term_sendkeys(buf, "\<C-N>")
|
||||||
|
call TermWait(buf, 50)
|
||||||
|
call VerifyScreenDump(buf, 'Test_pum_with_special_characters_06', {})
|
||||||
|
call term_sendkeys(buf, "\<C-E>\<Esc>")
|
||||||
|
|
||||||
|
call term_sendkeys(buf, ":hi ComplMatchIns ctermfg=red\<CR>")
|
||||||
|
call TermWait(buf, 50)
|
||||||
|
call term_sendkeys(buf, "S\<C-X>\<C-O>")
|
||||||
|
call VerifyScreenDump(buf, 'Test_pum_with_special_characters_07', {})
|
||||||
|
call term_sendkeys(buf, "\<C-E>\<Esc>")
|
||||||
|
|
||||||
|
call term_sendkeys(buf, "Shello hero\<ESC>hhhhha\<C-X>\<C-O>")
|
||||||
|
call TermWait(buf, 50)
|
||||||
|
call VerifyScreenDump(buf, 'Test_pum_with_special_characters_08', {})
|
||||||
|
call term_sendkeys(buf, "\<C-E>\<Esc>")
|
||||||
|
|
||||||
|
call StopVimInTerminal(buf)
|
||||||
|
endfunc
|
||||||
|
|
||||||
" vim: shiftwidth=2 sts=2 expandtab
|
" vim: shiftwidth=2 sts=2 expandtab
|
||||||
|
Reference in New Issue
Block a user