]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0707: completion: popup misplaced when text before it is concealed master v9.2.0707
authorBarrett Ruth <br@barrettruth.com>
Mon, 22 Jun 2026 20:13:22 +0000 (20:13 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 22 Jun 2026 20:13:22 +0000 (20:13 +0000)
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 <br@barrettruth.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/drawline.c
src/insexpand.c
src/popupmenu.c
src/structs.h
src/testdir/dumps/Test_pum_position_with_concealed_match.dump [new file with mode: 0644]
src/testdir/dumps/Test_pum_position_with_concealed_rl.dump [new file with mode: 0644]
src/testdir/dumps/Test_pum_position_with_concealed_text.dump [new file with mode: 0644]
src/testdir/dumps/Test_pum_position_with_concealed_wrap.dump [new file with mode: 0644]
src/testdir/test_ins_complete.vim
src/version.c

index 86911cefc281c8394bc8896797958289ab7093f2..a2679e14679249e1e849b47f30c628334fa7e255 100644 (file)
@@ -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;
index 5adad45309a8ce6c47e7652b54501f2b62bd54f2..c7186b9297d9ca68fc3f20f132f88e61ad14b2de 100644 (file)
@@ -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;
index b7929607d98b710991fe1aba5af8d597116dcb72..bed2495b87f1d7ffab8f7e1d198e9e76f1be1875 100644 (file)
@@ -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;
index 5d6511a5a8e977242ca68efd3a2619d23cbd4887..70010567d2abd9e7a990db88fd5f03a313df7b77 100644 (file)
@@ -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 (file)
index 0000000..f8d39c8
--- /dev/null
@@ -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 (file)
index 0000000..c2f497a
--- /dev/null
@@ -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 (file)
index 0000000..a30e2f8
--- /dev/null
@@ -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 (file)
index 0000000..0f3a3cb
--- /dev/null
@@ -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
index 93ca66f5a9ad1a1d3a442c2415a0cff832dabd06..cb901d609f5a42077caa5e61c90ffdb2699f3062 100644 (file)
@@ -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, "\<C-X>\<C-N>")
+  call VerifyScreenDump(buf, 'Test_pum_position_with_concealed_text', {})
+
+  call term_sendkeys(buf, "\<Esc>")
+  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, "\<C-X>\<C-N>")
+  call VerifyScreenDump(buf, 'Test_pum_position_with_concealed_match', {})
+
+  call term_sendkeys(buf, "\<Esc>")
+  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, "\<C-X>\<C-N>")
+  call VerifyScreenDump(buf, 'Test_pum_position_with_concealed_rl', {})
+
+  call term_sendkeys(buf, "\<Esc>")
+  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, "\<C-X>\<C-N>")
+  call VerifyScreenDump(buf, 'Test_pum_position_with_concealed_wrap', {})
+
+  call term_sendkeys(buf, "\<Esc>")
+  call StopVimInTerminal(buf)
+endfunc
+
 func Test_complete_stopinsert_startinsert()
   nnoremap <F2> <Cmd>startinsert<CR>
   inoremap <F2> <Cmd>stopinsert<CR>
index e663c2117fc1d6f9011045bafe16d8ed8dd66ee9..ff0e68e1c77d16fb542cccb2f29aa7042c8b9b11 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    707,
 /**/
     706,
 /**/