diff options
author | glepnir <glephunter@gmail.com> | 2025-02-09 10:43:02 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-09 10:43:02 +0800 |
commit | 53e737748e0e5c032fd6e2c2172c94bc5baf0cb7 (patch) | |
tree | 30049854ad1b714530a3d470703e25a05fd7de65 | |
parent | ec3071ffad8e4cf043e93e1c679a37c947856a26 (diff) | |
download | rneovim-53e737748e0e5c032fd6e2c2172c94bc5baf0cb7.tar.gz rneovim-53e737748e0e5c032fd6e2c2172c94bc5baf0cb7.tar.bz2 rneovim-53e737748e0e5c032fd6e2c2172c94bc5baf0cb7.zip |
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
https://github.com/vim/vim/commit/76bdb82527a13b5b2baa8f7d7ce14b4d5dc05b82
-rw-r--r-- | src/nvim/drawline.c | 8 | ||||
-rw-r--r-- | src/nvim/insexpand.c | 107 | ||||
-rw-r--r-- | test/functional/ui/popupmenu_spec.lua | 108 | ||||
-rw-r--r-- | test/old/testdir/test_popup.vim | 55 |
4 files changed, 267 insertions, 11 deletions
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 37a42917b0..6be5830f4d 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -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 } - 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; } @@ -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. - if ((State & MODE_INSERT) && in_curline && ins_compl_win_active(wp)) { - int ins_match_attr = ins_compl_col_range_attr((int)(ptr - line)); + if ((State & MODE_INSERT) && ins_compl_win_active(wp) + && (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) { search_attr = hl_combine_attr(search_attr, ins_match_attr); } diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index e21433f5ec..a5fc669b6e 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -254,6 +254,7 @@ static pos_T compl_startpos; /// Length in bytes of the text being completed (this is deleted to be replaced /// by the match.) 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 ///< that is being completed 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 /// column range. If it is, it returns a special highlight attribute. -/// -1 mean normal item. -int ins_compl_col_range_attr(int col) +/// -1 means normal item. +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; } - if (col >= (compl_col + (int)ins_compl_leader_len()) && col < compl_ins_end_col) { - return syn_name2attr("ComplMatchIns"); + int start_col = compl_col + (int)ins_compl_leader_len(); + 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; } +/// 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". 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; } compl_col = startcol; + compl_lnum = curwin->w_cursor.lnum; compl_length = curwin->w_cursor.col - startcol; // compl_pattern doesn't need to be set 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; } + 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 (stop_arrow() == FAIL) { return; @@ -3745,6 +3794,13 @@ void ins_compl_delete(bool new_leader) 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 // causes flicker, thus we can't do that. 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)); } +/// 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. /// "in_compl_func" is true when called from complete_check(). /// "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; size_t cp_str_len = compl_shown_match->cp_str.size; 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 // illegal bytes. if (compl_len < (int)cp_str_len) { - ins_compl_insert_bytes(cp_str + compl_len, -1); - if (preinsert && move_cursor) { - curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len); + if (has_multiple) { + ins_compl_expand_multiple(cp_str + compl_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); @@ -4551,6 +4640,7 @@ static int ins_compl_start(void) char *line = ml_get(curwin->w_cursor.lnum); colnr_T curs_col = curwin->w_cursor.col; compl_pending = 0; + compl_lnum = curwin->w_cursor.lnum; if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT && compl_cont_mode == ctrl_x_mode) { @@ -4602,6 +4692,7 @@ static int ins_compl_start(void) curbuf->b_p_com = old; compl_length = 0; compl_col = curwin->w_cursor.col; + compl_lnum = curwin->w_cursor.lnum; } } else { edit_submode_pre = NULL; diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 4c5b1d2bd2..0a28ea2d0a 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5935,6 +5935,114 @@ describe('builtin popupmenu', function() ]]) feed('<C-E><Esc>') 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 diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index e4abf978ab..7f80c60118 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -1885,4 +1885,59 @@ func Test_popup_completion_many_ctrlp() bw! 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 |