patch 9.1.1086: completion doesn't work with multi lines Commit: https://github.com/vim/vim/commit/76bdb82527a13b5b2baa8f7d7ce14b4d5dc05b82 Author: glepnir <glephun...@gmail.com> Date: Sat Feb 8 19:04:51 2025 +0100
patch 9.1.1086: completion doesn't work with multi lines Problem: completion doesn't work with multi lines (Łukasz Jan Niemier) Solution: handle linebreaks in completion code as expected (glepnir) fixes: #2505 closes: #15373 Signed-off-by: glepnir <glephun...@gmail.com> Signed-off-by: Christian Brabandt <c...@256bit.org> diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 43c5cfe55..3614ce3d2 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -41638,6 +41638,7 @@ Changed~ - new digraph "APPROACHES THE LIMIT" using ".=" - Add the optional {opts} |Dict| argument to |getchar()| to control: cursor behaviour, return type and whether or not to simplify the returned key +- handle multi-line completion as expected *added-9.2* Added ~ diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 6904a18c1..46650e9c4 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types " " Maintainer: The Vim Project <https://github.com/vim/vim> -" Last Change: 2025 Jan 21 +" Last Change: 2025 Feb 08 " Former Maintainer: Bram Moolenaar <b...@vim.org> " Listen very carefully, I will say this only once diff --git a/src/drawline.c b/src/drawline.c index a6b6317f8..f0a7a92a9 100644 --- a/src/drawline.c +++ b/src/drawline.c @@ -1877,7 +1877,8 @@ win_line( } #endif - 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; #ifdef FEAT_SYN_HL @@ -2423,11 +2424,11 @@ win_line( #endif // 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) + && (in_curline || ins_compl_lnum_in_range(lnum))) { int ins_match_attr = - ins_compl_col_range_attr((int)(ptr - line)); + 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/insexpand.c b/src/insexpand.c index 856c06d59..9fdc1c0ff 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -172,6 +172,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; @@ -226,6 +227,8 @@ static int ins_compl_pum_key(int c); static int ins_compl_key2count(int c); static void show_pum(int prev_w_wrow, int prev_w_leftcol); static unsigned quote_meta(char_u *dest, char_u *str, int len); +static int ins_compl_has_multiple(void); +static void ins_compl_expand_multiple(char_u *str); #ifdef FEAT_SPELL static void spell_back_to_badword(void); @@ -919,20 +922,54 @@ ins_compl_insert_bytes(char_u *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. + * -1 means normal item. */ int -ins_compl_col_range_attr(int col) +ins_compl_col_range_attr(linenr_T lnum, int col) { - if ((get_cot_flags() & COT_FUZZY)) + int start_col; + int attr; + + if ((get_cot_flags() & COT_FUZZY) + || (attr = syn_name2attr((char_u *)"ComplMatchIns")) == 0) return -1; - if (col >= (compl_col + (int)ins_compl_leader_len()) && col < compl_ins_end_col) - return syn_name2attr((char_u *)"ComplMatchIns"); + 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 int +ins_compl_has_multiple(void) +{ + return vim_strchr(compl_shown_match->cp_str.string, ' ') != 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. + */ + int +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". */ @@ -3123,6 +3160,7 @@ set_completion(colnr_T startcol, list_T *list) if (startcol > curwin->w_cursor.col) startcol = curwin->w_cursor.col; compl_col = startcol; + compl_lnum = curwin->w_cursor.lnum; compl_length = (int)curwin->w_cursor.col - (int)startcol; // compl_pattern doesn't need to be set compl_orig_text.string = vim_strnsave(ml_get_curline() + compl_col, @@ -4342,6 +4380,8 @@ ins_compl_delete(void) // In insert mode: Delete the typed part. // In replace mode: Put the old characters back, if any. int col = compl_col + (compl_status_adding() ? compl_length : 0); + char_u *remaining = NULL; + int orig_col; int has_preinsert = ins_compl_preinsert_effect(); if (has_preinsert) { @@ -4349,6 +4389,30 @@ ins_compl_delete(void) curwin->w_cursor.col = compl_ins_end_col; } + if (curwin->w_cursor.lnum > compl_lnum) + { + if (curwin->w_cursor.col < ml_get_curline_len()) + { + char_u *line = ml_get_curline(); + remaining = vim_strnsave(line + curwin->w_cursor.col, + (size_t)STRLEN(line + curwin->w_cursor.col)); + if (remaining == NULL) + return; + } + while (curwin->w_cursor.lnum > compl_lnum) + { + if (ml_delete(curwin->w_cursor.lnum) == FAIL) + { + if (remaining) + VIM_CLEAR(remaining); + return; + } + curwin->w_cursor.lnum--; + } + // move cursor to end of line + curwin->w_cursor.col = ml_get_curline_len(); + } + if ((int)curwin->w_cursor.col > col) { if (stop_arrow() == FAIL) @@ -4357,6 +4421,13 @@ ins_compl_delete(void) 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; + vim_free(remaining); + } // TODO: is this sufficient for redrawing? Redrawing everything causes // flicker, thus we can't do that. changed_cline_bef_curs(); @@ -4366,6 +4437,38 @@ ins_compl_delete(void) #endif } +/* + * Insert a completion string that contains newlines. + * The string is split and inserted line by line. + */ + static void +ins_compl_expand_multiple(char_u *str) +{ + char_u *start = str; + char_u *curr = str; + + while (*curr != NUL) + { + if (*curr == ' ') + { + // Insert the text chunk before newline + if (curr > start) + ins_char_bytes(start, (int)(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, (int)(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(). @@ -4375,19 +4478,25 @@ ins_compl_delete(void) void ins_compl_insert(int in_compl_func, int move_cursor) { - int compl_len = get_compl_len(); - int preinsert = ins_compl_has_preinsert(); - char_u *cp_str = compl_shown_match->cp_str.string; - size_t cp_str_len = compl_shown_match->cp_str.length; - size_t leader_len = ins_compl_leader_len(); + int compl_len = get_compl_len(); + int preinsert = ins_compl_has_preinsert(); + char_u *cp_str = compl_shown_match->cp_str.string; + size_t cp_str_len = compl_shown_match->cp_str.length; + size_t leader_len = ins_compl_leader_len(); + char_u *has_multiple = vim_strchr(cp_str, ' '); // 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); + } } if (match_at_original_text(compl_shown_match) || preinsert) compl_used_match = FALSE; @@ -5373,6 +5482,7 @@ ins_compl_start(void) line = ml_get(curwin->w_cursor.lnum); 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) @@ -5423,6 +5533,7 @@ 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 if (ctrl_x_mode_normal() && in_fuzzy) { diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro index fe3fead25..9dfbd9129 100644 --- a/src/proto/insexpand.pro +++ b/src/proto/insexpand.pro @@ -61,7 +61,8 @@ void ins_compl_delete(void); void ins_compl_insert(int in_compl_func, int move_cursor); void ins_compl_check_keys(int frequency, int in_compl_func); int ins_complete(int c, int enable_pum); -int ins_compl_col_range_attr(int col); +int ins_compl_col_range_attr(linenr_T lnum, int col); void free_insexpand_stuff(void); int ins_compl_preinsert_effect(void); +int ins_compl_lnum_in_range(linenr_T lnum); /* vim: set ft=c : */ diff --git a/src/testdir/dumps/Test_pum_with_special_characters_01.dump b/src/testdir/dumps/Test_pum_with_special_characters_01.dump new file mode 100644 index 000000000..ae3160676 --- /dev/null +++ b/src/testdir/dumps/Test_pum_with_special_characters_01.dump @@ -0,0 +1,12 @@ +|f+0&#ffffff0|u|n|c| |(|)| @67 +@75 +|e|n|d> @71 +|f+0#0000001#e0e0e08|u|n|c|t|i|o|n| |(|)| @3| +0#4040ff13#ffffff0@59 +|f+0#0000001#ffd7ff255|o@1|b|a|r| @8| +0#4040ff13#ffffff0@59 +|你*0#0000001#ffd7ff255|好|^+&|@| @1|^|@|我*&|好| +&| +0#4040ff13#ffffff0@59 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_with_special_characters_02.dump b/src/testdir/dumps/Test_pum_with_special_characters_02.dump new file mode 100644 index 000000000..6a3dfe9ae --- /dev/null +++ b/src/testdir/dumps/Test_pum_with_special_characters_02.dump @@ -0,0 +1,12 @@ +|f+0&#ffffff0|o@1|b|a|r> @68 +|f+0#0000001#ffd7ff255|u|n|c|t|i|o|n| |(|)| @3| +0#4040ff13#ffffff0@59 +|f+0#0000001#e0e0e08|o@1|b|a|r| @8| +0#4040ff13#ffffff0@59 +|你*0#0000001#ffd7ff255|好|^+&|@| @1|^|@|我*&|好| +&| +0#4040ff13#ffffff0@59 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |2| |o|f| |3| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_with_special_characters_03.dump b/src/testdir/dumps/Test_pum_with_special_characters_03.dump new file mode 100644 index 000000000..7809629d5 --- /dev/null +++ b/src/testdir/dumps/Test_pum_with_special_characters_03.dump @@ -0,0 +1,12 @@ +|h+0&#ffffff0|e|l@1|o| |f|u|n|c| |(|)| @61 +@75 +|e|n|d> |h|e|r|o| @66 +|~+0#4040ff13&| @3| +0#0000001#e0e0e08|f|u|n|c|t|i|o|n| |(|)| @3| +0#4040ff13#ffffff0@53 +|~| @3| +0#0000001#ffd7ff255|f|o@1|b|a|r| @8| +0#4040ff13#ffffff0@53 +|~| @3| +0#0000001#ffd7ff255|你*&|好|^+&|@| @1|^|@|我*&|好| +&| +0#4040ff13#ffffff0@53 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_with_special_characters_04.dump b/src/testdir/dumps/Test_pum_with_special_characters_04.dump new file mode 100644 index 000000000..e45b162cf --- /dev/null +++ b/src/testdir/dumps/Test_pum_with_special_characters_04.dump @@ -0,0 +1,12 @@ +|h+0&#ffffff0|e|l@1|o| |f|o@1|b|a|r> |h|e|r|o| @57 +|~+0#4040ff13&| @3| +0#0000001#ffd7ff255|f|u|n|c|t|i|o|n| |(|)| @3| +0#4040ff13#ffffff0@53 +|~| @3| +0#0000001#e0e0e08|f|o@1|b|a|r| @8| +0#4040ff13#ffffff0@53 +|~| @3| +0#0000001#ffd7ff255|你*&|好|^+&|@| @1|^|@|我*&|好| +&| +0#4040ff13#ffffff0@53 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |2| |o|f| |3| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_with_special_characters_05.dump b/src/testdir/dumps/Test_pum_with_special_characters_05.dump new file mode 100644 index 000000000..7cb665606 --- /dev/null +++ b/src/testdir/dumps/Test_pum_with_special_characters_05.dump @@ -0,0 +1,12 @@ +|h+0&#ffffff0|e|l@1|o| |你*&|好| +&@64 +@75 +|我*&|好> +&|h|e|r|o| @65 +|~+0#4040ff13&| @1| +0#0000001#ffd7ff255|f|u|n|c|t|i|o|n| |(|)| @3| +0#4040ff13#ffffff0@55 +|~| @1| +0#0000001#ffd7ff255|f|o@1|b|a|r| @8| +0#4040ff13#ffffff0@55 +|~| @1| +0#0000001#e0e0e08|你*&|好|^+&|@| @1|^|@|我*&|好| +&| +0#4040ff13#ffffff0@55 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |3| |o|f| |3| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_with_special_characters_06.dump b/src/testdir/dumps/Test_pum_with_special_characters_06.dump new file mode 100644 index 000000000..526970071 --- /dev/null +++ b/src/testdir/dumps/Test_pum_with_special_characters_06.dump @@ -0,0 +1,12 @@ +|h+0&#ffffff0|e|l@1|o| > |h|e|r|o| @63 +|~+0#4040ff13&| @3| +0#0000001#ffd7ff255|f|u|n|c|t|i|o|n| |(|)| @3| +0#4040ff13#ffffff0@53 +|~| @3| +0#0000001#ffd7ff255|f|o@1|b|a|r| @8| +0#4040ff13#ffffff0@53 +|~| @3| +0#0000001#ffd7ff255|你*&|好|^+&|@| @1|^|@|我*&|好| +&| +0#4040ff13#ffffff0@53 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |B+0#e000002&|a|c|k| |a|t| |o|r|i|g|i|n|a|l| +0#0000000&@30 diff --git a/src/testdir/dumps/Test_pum_with_special_characters_07.dump b/src/testdir/dumps/Test_pum_with_special_characters_07.dump new file mode 100644 index 000000000..adafb9f47 --- /dev/null +++ b/src/testdir/dumps/Test_pum_with_special_characters_07.dump @@ -0,0 +1,12 @@ +|f+0#ff404010#ffffff0|u|n|c| |(|)| +0#0000000&@67 +| +0#ff404010&@7| +0#0000000&@66 +|e+0#ff404010&|n|d> +0#0000000&@71 +|f+0#0000001#e0e0e08|u|n|c|t|i|o|n| |(|)| @3| +0#4040ff13#ffffff0@59 +|f+0#0000001#ffd7ff255|o@1|b|a|r| @8| +0#4040ff13#ffffff0@59 +|你*0#0000001#ffd7ff255|好|^+&|@| @1|^|@|我*&|好| +&| +0#4040ff13#ffffff0@59 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_with_special_characters_08.dump b/src/testdir/dumps/Test_pum_with_special_characters_08.dump new file mode 100644 index 000000000..bf558c1a0 --- /dev/null +++ b/src/testdir/dumps/Test_pum_with_special_characters_08.dump @@ -0,0 +1,12 @@ +|h+0&#ffffff0|e|l@1|o| |f+0#ff404010&|u|n|c| |(|)| +0#0000000&@61 +| +0#ff404010&@7| +0#0000000&@66 +|e+0#ff404010&|n|d> |h+0#0000000&|e|r|o| @66 +|~+0#4040ff13&| @3| +0#0000001#e0e0e08|f|u|n|c|t|i|o|n| |(|)| @3| +0#4040ff13#ffffff0@53 +|~| @3| +0#0000001#ffd7ff255|f|o@1|b|a|r| @8| +0#4040ff13#ffffff0@53 +|~| @3| +0#0000001#ffd7ff255|你*&|好|^+&|@| @1|^|@|我*&|好| +&| +0#4040ff13#ffffff0@53 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34 diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim index fb5d07b39..7c8faa957 100644 --- a/src/testdir/test_popup.vim +++ b/src/testdir/test_popup.vim @@ -1894,4 +1894,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 () end", abbr: "function ()",}, #{word: "foobar"}, #{word: "你好 我好"}] + 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 diff --git a/src/version.c b/src/version.c index 20eafbbe4..b8f783e96 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1086, /**/ 1085, /**/ -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1tgpN1-00EqP5-Fg%40256bit.org.