vim-patch:9.1.1011: popupmenu internal error with some abbr in completion item (#31988)

Problem:  Popup menu internal error with some abbr in completion item.
Solution: Don't compute attributes when there is no corresponding text.
          Reduce indent in pum_redraw() while at it (zeertzjq).

fixes: vim/vim#16427
closes: vim/vim#16435

3a0cc36c69
This commit is contained in:
zeertzjq 2025-01-13 15:18:47 +08:00 committed by GitHub
parent 99c4bd2f69
commit 2c16c84998
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 152 additions and 82 deletions

View File

@ -412,7 +412,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
/// Returns attributes for every cell, or NULL if all attributes are the same.
static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr)
{
if ((hlf != HLF_PSI && hlf != HLF_PNI)
if (*text == NUL || (hlf != HLF_PSI && hlf != HLF_PNI)
|| (win_hl_attr(curwin, HLF_PMSI) == win_hl_attr(curwin, HLF_PSI)
&& win_hl_attr(curwin, HLF_PMNI) == win_hl_attr(curwin, HLF_PNI))) {
return NULL;
@ -654,89 +654,87 @@ void pum_redraw(void)
s = p;
}
int w = ptr2cells(p);
if ((*p == NUL) || (*p == TAB) || (totwidth + w > pum_width)) {
// Display the text that fits or comes before a Tab.
// First convert it to printable characters.
char *st;
char saved = *p;
if (saved != NUL) {
*p = NUL;
}
st = transstr(s, true);
if (saved != NUL) {
*p = saved;
}
int *attrs = NULL;
if (item_type == CPT_ABBR) {
attrs = pum_compute_text_attrs(st, hlf, pum_array[idx].pum_user_abbr_hlattr);
}
if (pum_rl) {
char *rt = reverse_text(st);
char *rt_start = rt;
int cells = vim_strsize(rt);
if (cells > pum_width) {
do {
cells -= utf_ptr2cells(rt);
MB_PTR_ADV(rt);
} while (cells > pum_width);
if (cells < pum_width) {
// Most left character requires 2-cells but only 1 cell
// is available on screen. Put a '<' on the left of the
// pum item
*(--rt) = '<';
cells++;
}
}
if (attrs == NULL) {
grid_line_puts(grid_col - cells + 1, rt, -1, attr);
} else {
pum_grid_puts_with_attrs(grid_col - cells + 1, cells, rt, -1, attrs);
}
xfree(rt_start);
xfree(st);
grid_col -= width;
} else {
if (attrs == NULL) {
grid_line_puts(grid_col, st, -1, attr);
} else {
pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs);
}
xfree(st);
grid_col += width;
}
if (attrs != NULL) {
XFREE_CLEAR(attrs);
}
if (*p != TAB) {
break;
}
// Display two spaces for a Tab.
if (pum_rl) {
grid_line_puts(grid_col - 1, " ", 2, attr);
grid_col -= 2;
} else {
grid_line_puts(grid_col, " ", 2, attr);
grid_col += 2;
}
totwidth += 2;
// start text at next char
s = NULL;
width = 0;
} else {
if (*p != NUL && *p != TAB && totwidth + w <= pum_width) {
width += w;
continue;
}
// Display the text that fits or comes before a Tab.
// First convert it to printable characters.
char saved = *p;
if (saved != NUL) {
*p = NUL;
}
char *st = transstr(s, true);
if (saved != NUL) {
*p = saved;
}
int *attrs = NULL;
if (item_type == CPT_ABBR) {
attrs = pum_compute_text_attrs(st, hlf,
pum_array[idx].pum_user_abbr_hlattr);
}
if (pum_rl) {
char *rt = reverse_text(st);
char *rt_start = rt;
int cells = vim_strsize(rt);
if (cells > pum_width) {
do {
cells -= utf_ptr2cells(rt);
MB_PTR_ADV(rt);
} while (cells > pum_width);
if (cells < pum_width) {
// Most left character requires 2-cells but only 1 cell is available on
// screen. Put a '<' on the left of the pum item.
*(--rt) = '<';
cells++;
}
}
if (attrs == NULL) {
grid_line_puts(grid_col - cells + 1, rt, -1, attr);
} else {
pum_grid_puts_with_attrs(grid_col - cells + 1, cells, rt, -1, attrs);
}
xfree(rt_start);
xfree(st);
grid_col -= width;
} else {
if (attrs == NULL) {
grid_line_puts(grid_col, st, -1, attr);
} else {
pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs);
}
xfree(st);
grid_col += width;
}
if (attrs != NULL) {
XFREE_CLEAR(attrs);
}
if (*p != TAB) {
break;
}
// Display two spaces for a Tab.
if (pum_rl) {
grid_line_puts(grid_col - 1, " ", 2, attr);
grid_col -= 2;
} else {
grid_line_puts(grid_col, " ", 2, attr);
grid_col += 2;
}
totwidth += 2;
s = NULL; // start text at next char
width = 0;
}
}

View File

@ -5415,6 +5415,45 @@ describe('builtin popupmenu', function()
feed('<C-E><Esc>')
end)
-- oldtest: Test_pum_highlights_match_with_abbr()
it('can highlight matched text with abbr', function()
exec([[
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return {
\ 'words': [
\ { 'word': 'foobar', 'abbr': "foobar\t\t!" },
\ { 'word': 'foobaz', 'abbr': "foobaz\t\t!" },
\]}
endfunc
set omnifunc=Omni_test
set completeopt=menuone,noinsert
hi PmenuMatchSel guifg=Blue guibg=Grey
hi PmenuMatch guifg=Blue guibg=Plum1
]])
feed('i<C-X><C-O>')
screen:expect([[
^ |
{s:foobar ! }{1: }|
{n:foobaz ! }{1: }|
{1:~ }|*16
{2:-- }{5:match 1 of 2} |
]])
feed('foo')
screen:expect([[
foo^ |
{ms:foo}{s:bar ! }{1: }|
{mn:foo}{n:baz ! }{1: }|
{1:~ }|*16
{2:-- }{5:match 1 of 2} |
]])
feed('<C-E><Esc>')
end)
-- oldtest: Test_pum_user_abbr_hlgroup()
it('custom abbr_hlgroup override', function()
exec([[

View File

@ -1519,6 +1519,39 @@ func Test_pum_highlights_match()
call StopVimInTerminal(buf)
endfunc
func Test_pum_highlights_match_with_abbr()
CheckScreendump
let lines =<< trim END
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return {
\ 'words': [
\ { 'word': 'foobar', 'abbr': "foobar\t\t!" },
\ { 'word': 'foobaz', 'abbr': "foobaz\t\t!" },
\]}
endfunc
set omnifunc=Omni_test
set completeopt=menuone,noinsert
hi PmenuMatchSel ctermfg=6 ctermbg=7
hi PmenuMatch ctermfg=4 ctermbg=225
END
call writefile(lines, 'Xscript', 'D')
let buf = RunVimInTerminal('-S Xscript', {})
call TermWait(buf)
call term_sendkeys(buf, "i\<C-X>\<C-O>")
call TermWait(buf, 50)
call term_sendkeys(buf, "foo")
call VerifyScreenDump(buf, 'Test_pum_highlights_19', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
call TermWait(buf)
call StopVimInTerminal(buf)
endfunc
func Test_pum_user_abbr_hlgroup()
CheckScreendump
let lines =<< trim END