mirror of
https://github.com/neovim/neovim.git
synced 2026-07-10 03:19:44 +00:00
vim-patch:9.2.0707: completion: popup misplaced when text before it is concealed (#40374)
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: vim/vim#20539
d167c50de4
Co-authored-by: Barrett Ruth <br@barrettruth.com>
This commit is contained in:
@@ -1254,6 +1254,8 @@ struct window_S {
|
||||
// This is related to positions in the window, not in the display or
|
||||
// buffer, thus w_wrow is relative to w_winrow.
|
||||
int w_wrow, w_wcol; // cursor position in window
|
||||
int w_wcol_conceal_off; // screen cells concealed before w_wcol on
|
||||
// the cursor's screen line, set by win_line()
|
||||
|
||||
linenr_T w_botline; // number of the line below the bottom of
|
||||
// the window
|
||||
|
||||
@@ -2756,6 +2756,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
&& in_curline && conceal_cursor_line(wp)
|
||||
&& (wlv.vcol + wlv.skip_cells >= wp->w_virtcol || mb_schar == NUL)) {
|
||||
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 + wlv.skip_cells;
|
||||
if (wlv.vcol + wlv.skip_cells < wp->w_virtcol) {
|
||||
// Cursor beyond end of the line with 'virtualedit'.
|
||||
wp->w_wcol += wp->w_virtcol - wlv.vcol - wlv.skip_cells;
|
||||
|
||||
@@ -1777,6 +1777,14 @@ void ins_compl_show_pum(void)
|
||||
pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0);
|
||||
curwin->w_cursor.col = col;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -331,10 +331,20 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
|
||||
} else {
|
||||
// anchor position: the start of the completed word
|
||||
pum_win_row = curwin->w_wrow;
|
||||
int wcol = curwin->w_wcol;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
if (pum_rl) {
|
||||
cursor_col = curwin->w_view_width - curwin->w_wcol - 1;
|
||||
cursor_col = curwin->w_view_width - wcol - 1;
|
||||
} else {
|
||||
cursor_col = curwin->w_wcol;
|
||||
cursor_col = wcol;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1102,6 +1102,116 @@ describe('builtin popupmenu', function()
|
||||
})
|
||||
end)
|
||||
|
||||
-- oldtest: Test_pum_position_with_concealed_text()
|
||||
it('position with fully concealed text', function()
|
||||
screen:try_resize(55, 10)
|
||||
exec([[
|
||||
call setline(1, ['CONCEALED foobar', 'CONCEALED foo'])
|
||||
syntax match Hidden /CONCEALED / conceal
|
||||
setlocal conceallevel=3 concealcursor=nvic
|
||||
set completeopt=menu,menuone
|
||||
]])
|
||||
feed('2GA')
|
||||
poke_eventloop()
|
||||
feed('<C-X><C-N>')
|
||||
if multigrid then
|
||||
screen:expect({
|
||||
float_pos = { [4] = { -1, 'NW', 2, 2, 0, false, 100, 1, 2, 0 } },
|
||||
})
|
||||
else
|
||||
screen:expect([[
|
||||
foobar |
|
||||
foobar^ |
|
||||
{12:foobar }{1: }|
|
||||
{1:~ }|*6
|
||||
{5:-- Keyword Local completion (^N^P) The only match} |
|
||||
]])
|
||||
end
|
||||
end)
|
||||
|
||||
-- oldtest: Test_pum_position_with_concealed_match()
|
||||
it('position with concealed text replacement char', function()
|
||||
screen:try_resize(55, 10)
|
||||
exec([[
|
||||
call setline(1, ['XXX foobar', 'XXX foo'])
|
||||
call matchadd('Conceal', 'XXX ', 10, -1, {'conceal': '+'})
|
||||
setlocal conceallevel=2 concealcursor=nvic
|
||||
set completeopt=menu,menuone
|
||||
]])
|
||||
feed('2GA')
|
||||
poke_eventloop()
|
||||
feed('<C-X><C-N>')
|
||||
if multigrid then
|
||||
screen:expect({
|
||||
float_pos = { [4] = { -1, 'NW', 2, 2, 0, false, 100, 1, 2, 0 } },
|
||||
})
|
||||
else
|
||||
screen:expect([[
|
||||
{14:+}foobar |
|
||||
{14:+}foobar^ |
|
||||
{12: foobar }{1: }|
|
||||
{1:~ }|*6
|
||||
{5:-- Keyword Local completion (^N^P) The only match} |
|
||||
]])
|
||||
end
|
||||
end)
|
||||
|
||||
-- oldtest: Test_pum_position_with_concealed_rl()
|
||||
it('position with concealed text and rightleft', function()
|
||||
screen:try_resize(55, 10)
|
||||
exec([[
|
||||
set rightleft
|
||||
call setline(1, ['CONCEALED foobar', 'CONCEALED foo'])
|
||||
syntax match Hidden /CONCEALED / conceal
|
||||
setlocal conceallevel=3 concealcursor=nvic
|
||||
set completeopt=menu,menuone
|
||||
]])
|
||||
feed('2GA')
|
||||
poke_eventloop()
|
||||
feed('<C-X><C-N>')
|
||||
if multigrid then
|
||||
screen:expect({
|
||||
float_pos = { [4] = { -1, 'NW', 2, 2, 40, false, 100, 1, 2, 40 } },
|
||||
})
|
||||
else
|
||||
screen:expect([[
|
||||
raboof|
|
||||
^ raboof|
|
||||
{1: }{12: raboof}|
|
||||
{1: ~}|*6
|
||||
{5:-- Keyword Local completion (^N^P) The only match} |
|
||||
]])
|
||||
end
|
||||
end)
|
||||
|
||||
-- oldtest: Test_pum_position_with_concealed_wrap()
|
||||
it('position with wrapped concealed text', function()
|
||||
screen:try_resize(20, 10)
|
||||
exec([[
|
||||
call setline(1, ['foobar', 'aaaaaaaaaaaaaaaaaaaa CONCEALED foo'])
|
||||
syntax match Hidden /CONCEALED / conceal
|
||||
setlocal conceallevel=3 concealcursor=nvic
|
||||
set completeopt=menu,menuone
|
||||
]])
|
||||
feed('2GA')
|
||||
poke_eventloop()
|
||||
feed('<C-X><C-N>')
|
||||
if multigrid then
|
||||
screen:expect({
|
||||
float_pos = { [4] = { -1, 'NW', 2, 3, 0, false, 100, 1, 3, 0 } },
|
||||
})
|
||||
else
|
||||
screen:expect([[
|
||||
foobar |
|
||||
aaaaaaaaaaaaaaaaaaaa|
|
||||
foobar^ |
|
||||
{12: foobar }{1: }|
|
||||
{1:~ }|*5
|
||||
{5:-- The only match} |
|
||||
]])
|
||||
end
|
||||
end)
|
||||
|
||||
it('with preview-window above', function()
|
||||
feed(':ped<CR><c-w>4+')
|
||||
feed('iaa bb cc dd ee ff gg hh ii jj<cr>')
|
||||
|
||||
@@ -960,7 +960,7 @@ func Test_pum_stopped_by_timer()
|
||||
endfunc
|
||||
END
|
||||
|
||||
call writefile(lines, 'Xpumscript')
|
||||
call writefile(lines, 'Xpumscript', 'D')
|
||||
let buf = RunVimInTerminal('-S Xpumscript', #{rows: 12})
|
||||
call term_sendkeys(buf, ":call StartCompl()\<CR>")
|
||||
call TermWait(buf, 200)
|
||||
@@ -968,7 +968,104 @@ func Test_pum_stopped_by_timer()
|
||||
call VerifyScreenDump(buf, 'Test_pum_stopped_by_timer', {})
|
||||
|
||||
call StopVimInTerminal(buf)
|
||||
call delete('Xpumscript')
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user