From: Barrett Ruth Date: Mon, 22 Jun 2026 20:13:22 +0000 (+0000) Subject: patch 9.2.0707: completion: popup misplaced when text before it is concealed X-Git-Tag: v9.2.0707^0 X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;ds=sidebyside;p=thirdparty%2Fvim.git patch 9.2.0707: completion: popup misplaced when text before it is concealed Problem: When the cursor line has concealed text before the start of the completion, the insert-mode completion popup is drawn at the wrong screen column and the cursor no longer lines up with the completed text. Solution: Record the concealed width before the cursor on its screen line in a new `win_T` field while `win_line()` draws it, subtract it in `pum_display()` to place the menu over the visible text, and redraw the cursor line so `win_line()` corrects the cursor too. closes: #20539 Signed-off-by: Barrett Ruth Signed-off-by: Christian Brabandt --- diff --git a/src/drawline.c b/src/drawline.c index 86911cefc2..a2679e1467 100644 --- a/src/drawline.c +++ b/src/drawline.c @@ -3813,6 +3813,10 @@ win_line( else # endif wp->w_wcol = wlv.col - wlv.boguscols; + // Screen cells concealed before the cursor on this screen line, so + // pum_display() can line the menu up with the visible text; + // "skip_cells" is the concealed cell at the cursor not yet counted. + wp->w_wcol_conceal_off = wlv.vcol_off_co + skip_cells; if (wlv.vcol + skip_cells < wp->w_virtcol) // Cursor beyond end of the line with 'virtualedit'. wp->w_wcol += wp->w_virtcol - wlv.vcol - skip_cells; diff --git a/src/insexpand.c b/src/insexpand.c index 5adad45309..c7186b9297 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -1896,6 +1896,15 @@ ins_compl_show_pum(void) pum_display(compl_match_array, compl_match_arraysize, cur); curwin->w_cursor.col = col; +#ifdef FEAT_CONCEAL + // The cursor was temporarily moved to "compl_col" above to position the + // menu, so the screen update left w_wcol conceal-corrected for that column + // rather than for the real cursor. Redraw the cursor line so the caret is + // positioned correctly when the cursor line has concealed text. + if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin)) + redrawWinline(curwin, curwin->w_cursor.lnum); +#endif + // After adding leader, set the current match to shown match. if (compl_started && compl_curr_match != compl_shown_match) compl_curr_match = compl_shown_match; diff --git a/src/popupmenu.c b/src/popupmenu.c index b7929607d9..bed2495b87 100644 --- a/src/popupmenu.c +++ b/src/popupmenu.c @@ -361,6 +361,17 @@ pum_display( { // w_wcol includes virtual text "above" int wcol = curwin->w_wcol % curwin->w_width; +#ifdef FEAT_CONCEAL + // w_wcol does not account for text concealed before the cursor; + // shift by the offset win_line() recorded for the cursor line so the + // menu lines up with the visible text. + if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin)) + { + wcol -= curwin->w_wcol_conceal_off; + if (wcol < 0) + wcol = 0; + } +#endif #ifdef FEAT_RIGHTLEFT if (pum_rl) cursor_col = curwin->w_wincol + curwin->w_width - wcol - 1; diff --git a/src/structs.h b/src/structs.h index 5d6511a5a8..70010567d2 100644 --- a/src/structs.h +++ b/src/structs.h @@ -4367,6 +4367,10 @@ struct window_S * buffer, thus w_wrow is relative to w_winrow. */ int w_wrow, w_wcol; // cursor position in window +#ifdef FEAT_CONCEAL + int w_wcol_conceal_off; // screen cells concealed before w_wcol on + // the cursor's screen line, set by win_line() +#endif /* * Info about the lines currently in the window is remembered to avoid diff --git a/src/testdir/dumps/Test_pum_position_with_concealed_match.dump b/src/testdir/dumps/Test_pum_position_with_concealed_match.dump new file mode 100644 index 0000000000..f8d39c8bff --- /dev/null +++ b/src/testdir/dumps/Test_pum_position_with_concealed_match.dump @@ -0,0 +1,10 @@ +|++0#e0e0e08#6c6c6c255|f+0#0000000#ffffff0|o@1|b|a|r| @67 +|++0#e0e0e08#6c6c6c255|f+0#0000000#ffffff0|o@1|b|a|r> @67 +| +0#0000001#e0e0e08|f|o@1|b|a|r| @8| +0#4040ff13#ffffff0@58 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |K|e|y|w|o|r|d| |L|o|c|a|l| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |T|h|e| |o|n|l|y| |m|a|t|c|h| +0&&@25 diff --git a/src/testdir/dumps/Test_pum_position_with_concealed_rl.dump b/src/testdir/dumps/Test_pum_position_with_concealed_rl.dump new file mode 100644 index 0000000000..c2f497a871 --- /dev/null +++ b/src/testdir/dumps/Test_pum_position_with_concealed_rl.dump @@ -0,0 +1,10 @@ +| +0&#ffffff0@68|r|a|b|o@1|f +| @67> |r|a|b|o@1|f +| +0#4040ff13&@59| +0#0000001#e0e0e08@8|r|a|b|o@1|f +| +0#4040ff13#ffffff0@73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +|-+2#0000000&@1| |K|e|y|w|o|r|d| |L|o|c|a|l| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |T|h|e| |o|n|l|y| |m|a|t|c|h| +0&&@25 diff --git a/src/testdir/dumps/Test_pum_position_with_concealed_text.dump b/src/testdir/dumps/Test_pum_position_with_concealed_text.dump new file mode 100644 index 0000000000..a30e2f86bb --- /dev/null +++ b/src/testdir/dumps/Test_pum_position_with_concealed_text.dump @@ -0,0 +1,10 @@ +|f+0&#ffffff0|o@1|b|a|r| @68 +|f|o@1|b|a|r> @68 +|f+0#0000001#e0e0e08|o@1|b|a|r| @8| +0#4040ff13#ffffff0@59 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |K|e|y|w|o|r|d| |L|o|c|a|l| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |T|h|e| |o|n|l|y| |m|a|t|c|h| +0&&@25 diff --git a/src/testdir/dumps/Test_pum_position_with_concealed_wrap.dump b/src/testdir/dumps/Test_pum_position_with_concealed_wrap.dump new file mode 100644 index 0000000000..0f3a3cbe0f --- /dev/null +++ b/src/testdir/dumps/Test_pum_position_with_concealed_wrap.dump @@ -0,0 +1,10 @@ +|f+0&#ffffff0|o@1|b|a|r| @13 +|a@19 +| |f|o@1|b|a|r> @12 +| +0#0000001#e0e0e08|f|o@1|b|a|r| @8| +0#4040ff13#ffffff0@3 +|~| @18 +|~| @18 +|~| @18 +|~| @18 +|~| @18 +|-+2#0000000&@1| |T|h|e| |o|n|l|y| |m|a|t|c|h| +0&&@2 diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index 93ca66f5a9..cb901d609f 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -870,6 +870,104 @@ func Test_pum_stopped_by_timer() call StopVimInTerminal(buf) endfunc +" The completion popup menu must line up with the start of the completed text +" on screen, also when there is concealed text before it on the line. +func Test_pum_position_with_concealed_text() + CheckScreendump + + let lines =<< trim END + call setline(1, ['CONCEALED foobar', 'CONCEALED foo']) + syntax match Hidden /CONCEALED / conceal + setlocal conceallevel=3 concealcursor=nvic + set completeopt=menu,menuone + END + + call writefile(lines, 'Xpumconceal', 'D') + let buf = RunVimInTerminal('-S Xpumconceal', #{rows: 10}) + call TermWait(buf, 50) + call term_sendkeys(buf, "2GA") + call TermWait(buf, 50) + call term_sendkeys(buf, "\\") + call VerifyScreenDump(buf, 'Test_pum_position_with_concealed_text', {}) + + call term_sendkeys(buf, "\") + call StopVimInTerminal(buf) +endfunc + +" Same alignment when the concealed text comes from a match and is shown as a +" replacement character with 'conceallevel' 2. +func Test_pum_position_with_concealed_match() + CheckScreendump + + let lines =<< trim END + call setline(1, ['XXX foobar', 'XXX foo']) + call matchadd('Conceal', 'XXX ', 10, -1, {'conceal': '+'}) + setlocal conceallevel=2 concealcursor=nvic + set completeopt=menu,menuone + END + + call writefile(lines, 'Xpumconcealmatch', 'D') + let buf = RunVimInTerminal('-S Xpumconcealmatch', #{rows: 10}) + call TermWait(buf, 50) + call term_sendkeys(buf, "2GA") + call TermWait(buf, 50) + call term_sendkeys(buf, "\\") + call VerifyScreenDump(buf, 'Test_pum_position_with_concealed_match', {}) + + call term_sendkeys(buf, "\") + call StopVimInTerminal(buf) +endfunc + +" The menu lines up with the visible text in a 'rightleft' window too, where +" the cursor screen column is mirrored. +func Test_pum_position_with_concealed_rl() + CheckScreendump + CheckFeature rightleft + + let lines =<< trim END + set rightleft + call setline(1, ['CONCEALED foobar', 'CONCEALED foo']) + syntax match Hidden /CONCEALED / conceal + setlocal conceallevel=3 concealcursor=nvic + set completeopt=menu,menuone + END + + call writefile(lines, 'Xpumconcealrl', 'D') + let buf = RunVimInTerminal('-S Xpumconcealrl', #{rows: 10}) + call TermWait(buf, 50) + call term_sendkeys(buf, "2GA") + call TermWait(buf, 50) + call term_sendkeys(buf, "\\") + call VerifyScreenDump(buf, 'Test_pum_position_with_concealed_rl', {}) + + call term_sendkeys(buf, "\") + call StopVimInTerminal(buf) +endfunc + +" The recorded offset is per screen line, so the menu also lines up when the +" concealed text and the completion are on a wrapped continuation line. +func Test_pum_position_with_concealed_wrap() + CheckScreendump + + let lines =<< trim END + call setline(1, ['foobar', 'aaaaaaaaaaaaaaaaaaaa CONCEALED foo']) + syntax match Hidden /CONCEALED / conceal + setlocal conceallevel=3 concealcursor=nvic + set completeopt=menu,menuone + END + + call writefile(lines, 'Xpumconcealwrap', 'D') + let buf = RunVimInTerminal('-S Xpumconcealwrap', #{rows: 10, cols: 20}) + call TermWait(buf, 50) + call term_sendkeys(buf, "2GA") + call TermWait(buf, 50) + call term_sendkeys(buf, "\\") + call VerifyScreenDump(buf, 'Test_pum_position_with_concealed_wrap', {}) + + call term_sendkeys(buf, "\") + call StopVimInTerminal(buf) +endfunc + func Test_complete_stopinsert_startinsert() nnoremap startinsert inoremap stopinsert diff --git a/src/version.c b/src/version.c index e663c2117f..ff0e68e1c7 100644 --- a/src/version.c +++ b/src/version.c @@ -759,6 +759,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 707, /**/ 706, /**/