vim-patch:9.1.0754: fixed order of items in insert-mode completion menu (#30619)

Problem:  fixed order of items in insert-mode completion menu
Solution: Introduce the 'completeitemalign' option with default
          value "abbr,kind,menu" (glepnir).

Adding an new option `completeitemalign` abbr is `cia` to custom
the complete-item order in popupmenu.

closes: vim/vim#14006
closes: vim/vim#15760

6a89c94a9e
This commit is contained in:
glepnir
2024-10-03 06:45:01 +08:00
committed by GitHub
parent d3b4772ddc
commit 6a2f8958e8
11 changed files with 309 additions and 39 deletions

View File

@@ -1529,6 +1529,16 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
*'completeitemalign'* *'cia'*
'completeitemalign' 'cia' string (default "abbr,kind,menu")
global
A comma-separated list of |complete-items| that controls the alignment
and display order of items in the popup menu during Insert mode
completion. The supported values are abbr, kind, and menu. These
options allow to customize how the completion items are shown in the
popup menu. Note: must always contain those three values in any
order.
*'completeopt'* *'cot'*
'completeopt' 'cot' string (default "menu,preview")
global or local to buffer |global-local|

View File

@@ -1055,6 +1055,19 @@ vim.o.cfu = vim.o.completefunc
vim.bo.completefunc = vim.o.completefunc
vim.bo.cfu = vim.bo.completefunc
--- A comma-separated list of `complete-items` that controls the alignment
--- and display order of items in the popup menu during Insert mode
--- completion. The supported values are abbr, kind, and menu. These
--- options allow to customize how the completion items are shown in the
--- popup menu. Note: must always contain those three values in any
--- order.
---
--- @type string
vim.o.completeitemalign = "abbr,kind,menu"
vim.o.cia = vim.o.completeitemalign
vim.go.completeitemalign = vim.o.completeitemalign
vim.go.cia = vim.go.completeitemalign
--- A comma-separated list of options for Insert mode completion
--- `ins-completion`. The supported values are:
---

View File

@@ -726,6 +726,8 @@ if has("insert_expand")
call <SID>OptionL("cpt")
call <SID>AddOption("completeopt", gettext("whether to use a popup menu for Insert mode completion"))
call <SID>OptionL("cot")
call <SID>AddOption("completeitemalign", gettext("popup menu item align order"))
call <SID>OptionG("cia", &cia)
call <SID>AddOption("pumheight", gettext("maximum height of the popup menu"))
call <SID>OptionG("ph", &ph)
call <SID>AddOption("pumwidth", gettext("minimum width of the popup menu"))

View File

@@ -151,13 +151,6 @@ static char *ctrl_x_mode_names[] = {
"cmdline",
};
// Array indexes used for cp_text[].
#define CPT_ABBR 0 ///< "abbr"
#define CPT_MENU 1 ///< "menu"
#define CPT_KIND 2 ///< "kind"
#define CPT_INFO 3 ///< "info"
#define CPT_COUNT 4 ///< Number of entries
/// Structure used to store one match for insert completion.
typedef struct compl_S compl_T;
struct compl_S {

View File

@@ -8,3 +8,12 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "insexpand.h.generated.h"
#endif
/// Array indexes used for cp_text[].
typedef enum {
CPT_ABBR, ///< "abbr"
CPT_KIND, ///< "kind"
CPT_MENU, ///< "menu"
CPT_INFO, ///< "info"
CPT_COUNT, ///< Number of entries
} cpitem_T;

View File

@@ -429,6 +429,8 @@ EXTERN char *p_cms; ///< 'commentstring'
EXTERN char *p_cpt; ///< 'complete'
EXTERN OptInt p_columns; ///< 'columns'
EXTERN int p_confirm; ///< 'confirm'
EXTERN char *p_cia; ///< 'completeitemalign'
EXTERN unsigned cia_flags; ///< order flags of 'completeitemalign'
EXTERN char *p_cot; ///< 'completeopt'
EXTERN unsigned cot_flags; ///< flags from 'completeopt'
// Keep in sync with p_cot_values in optionstr.c

View File

@@ -1426,6 +1426,26 @@ return {
type = 'string',
varname = 'p_cfu',
},
{
abbreviation = 'cia',
cb = 'did_set_completeitemalign',
defaults = { if_true = 'abbr,kind,menu' },
deny_duplicates = true,
desc = [=[
A comma-separated list of |complete-items| that controls the alignment
and display order of items in the popup menu during Insert mode
completion. The supported values are abbr, kind, and menu. These
options allow to customize how the completion items are shown in the
popup menu. Note: must always contain those three values in any
order.
]=],
full_name = 'completeitemalign',
list = 'onecomma',
scope = { 'global' },
short_desc = N_('Insert mode completion item align order'),
type = 'string',
varname = 'p_cia',
},
{
abbreviation = 'cot',
cb = 'did_set_completeopt',

View File

@@ -995,6 +995,51 @@ int expand_set_complete(optexpand_T *args, int *numMatches, char ***matches)
matches);
}
/// The 'completeitemalign' option is changed.
const char *did_set_completeitemalign(optset_T *args)
{
char *p = p_cia;
unsigned new_cia_flags = 0;
bool seen[3] = { false, false, false };
int count = 0;
char buf[10];
while (*p) {
copy_option_part(&p, buf, sizeof(buf), ",");
if (count >= 3) {
return e_invarg;
}
if (strequal(buf, "abbr")) {
if (seen[CPT_ABBR]) {
return e_invarg;
}
new_cia_flags = new_cia_flags * 10 + CPT_ABBR;
seen[CPT_ABBR] = true;
count++;
} else if (strequal(buf, "kind")) {
if (seen[CPT_KIND]) {
return e_invarg;
}
new_cia_flags = new_cia_flags * 10 + CPT_KIND;
seen[CPT_KIND] = true;
count++;
} else if (strequal(buf, "menu")) {
if (seen[CPT_MENU]) {
return e_invarg;
}
new_cia_flags = new_cia_flags * 10 + CPT_MENU;
seen[CPT_MENU] = true;
count++;
} else {
return e_invarg;
}
}
if (new_cia_flags == 0 || count != 3) {
return e_invarg;
}
cia_flags = new_cia_flags;
return NULL;
}
/// The 'completeopt' option is changed.
const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED)
{

View File

@@ -525,6 +525,27 @@ static void pum_grid_puts_with_attrs(int col, int cells, const char *text, int t
}
}
static inline void pum_align_order(int *order)
{
bool is_default = cia_flags == 0;
order[0] = is_default ? CPT_ABBR : cia_flags / 100;
order[1] = is_default ? CPT_KIND : (cia_flags / 10) % 10;
order[2] = is_default ? CPT_MENU : cia_flags % 10;
}
static inline char *pum_get_item(int index, int type)
{
switch (type) {
case CPT_ABBR:
return pum_array[index].pum_text;
case CPT_KIND:
return pum_array[index].pum_kind;
case CPT_MENU:
return pum_array[index].pum_extra;
}
return NULL;
}
/// Redraw the popup menu, using "pum_first" and "pum_selected".
void pum_redraw(void)
{
@@ -621,34 +642,27 @@ void pum_redraw(void)
}
// Display each entry, use two spaces for a Tab.
// Do this 3 times:
// 0 - main text
// 1 - kind
// 2 - extra info
// Do this 3 times and order from p_cia
int grid_col = col_off;
int totwidth = 0;
for (int round = 0; round < 3; round++) {
hlf = hlfs[round];
int order[3];
int items_width_array[3] = { pum_base_width, pum_kind_width, pum_extra_width };
pum_align_order(order);
int basic_width = items_width_array[order[0]]; // first item width
bool last_isabbr = order[2] == CPT_ABBR;
for (int j = 0; j < 3; j++) {
int item_type = order[j];
hlf = hlfs[item_type];
attr = win_hl_attr(curwin, (int)hlf);
if (pum_array[idx].pum_user_hlattr > 0) {
attr = hl_combine_attr(attr, pum_array[idx].pum_user_hlattr);
}
if (round == 1 && pum_array[idx].pum_user_kind_hlattr > 0) {
if (item_type == CPT_KIND && pum_array[idx].pum_user_kind_hlattr > 0) {
attr = hl_combine_attr(attr, pum_array[idx].pum_user_kind_hlattr);
}
int width = 0;
char *s = NULL;
switch (round) {
case 0:
p = pum_array[idx].pum_text; break;
case 1:
p = pum_array[idx].pum_kind; break;
case 2:
p = pum_array[idx].pum_extra; break;
}
p = pum_get_item(idx, item_type);
if (p != NULL) {
for (;; MB_PTR_ADV(p)) {
if (s == NULL) {
@@ -737,31 +751,31 @@ void pum_redraw(void)
}
}
if (round > 0) {
n = pum_kind_width + 1;
if (j > 0) {
n = items_width_array[order[1]] + (last_isabbr ? 0 : 1);
} else {
n = 1;
n = order[j] == CPT_ABBR ? 1 : 0;
}
bool next_isempty = false;
if (j + 1 < 3) {
next_isempty = pum_get_item(idx, order[j + 1]) == NULL;
}
// Stop when there is nothing more to display.
if ((round == 2)
|| ((round == 1)
&& (pum_array[idx].pum_extra == NULL))
|| ((round == 0)
&& (pum_array[idx].pum_kind == NULL)
&& (pum_array[idx].pum_extra == NULL))
if ((j == 2)
|| (next_isempty && (j == 1 || (j == 0 && pum_get_item(idx, order[j + 2]) == NULL)))
|| (pum_base_width + n >= pum_width)) {
break;
}
if (pum_rl) {
grid_line_fill(col_off - pum_base_width - n + 1, grid_col + 1, schar_from_ascii(' '), attr);
grid_col = col_off - pum_base_width - n;
grid_line_fill(col_off - basic_width - n + 1, grid_col + 1, schar_from_ascii(' '), attr);
grid_col = col_off - basic_width - n;
} else {
grid_line_fill(grid_col, col_off + pum_base_width + n, schar_from_ascii(' '), attr);
grid_col = col_off + pum_base_width + n;
grid_line_fill(grid_col, col_off + basic_width + n, schar_from_ascii(' '), attr);
grid_col = col_off + basic_width + n;
}
totwidth = pum_base_width + n;
totwidth = basic_width + n;
}
if (pum_rl) {

View File

@@ -5061,6 +5061,108 @@ describe('builtin popupmenu', function()
]])
feed('<C-E><Esc>')
end)
-- oldtest: Test_pum_completeitemalign()
it('completeitemalign option', function()
screen:try_resize(30, 15)
exec([[
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return {
\ 'words': [
\ { 'word': 'foo', 'kind': 'S', 'menu': 'menu' },
\ { 'word': 'bar', 'kind': 'T', 'menu': 'menu' },
\ { 'word': '你好', 'kind': 'C', 'menu': '中文' },
\]}
endfunc
set omnifunc=Omni_test
]])
-- T1
command('set cia=abbr,kind,menu')
feed('S<C-X><C-O>')
screen:expect([[
foo^ |
{s:foo S menu }{1: }|
{n:bar T menu }{1: }|
{n:你好 C 中文 }{1: }|
{1:~ }|*10
{2:-- }{5:match 1 of 3} |
]])
feed('<C-E><ESC>')
-- T2
command('set cia=abbr,menu,kind')
feed('S<C-X><C-O>')
screen:expect([[
foo^ |
{s:foo menu S }{1: }|
{n:bar menu T }{1: }|
{n:你好 中文 C }{1: }|
{1:~ }|*10
{2:-- }{5:match 1 of 3} |
]])
feed('<C-E><ESC>')
-- T3
command('set cia=kind,abbr,menu')
feed('S<C-X><C-O>')
screen:expect([[
foo^ |
{s:S foo menu }{1: }|
{n:T bar menu }{1: }|
{n:C 你好 中文 }{1: }|
{1:~ }|*10
{2:-- }{5:match 1 of 3} |
]])
feed('<C-E><ESC>')
-- T4
command('set cia=kind,menu,abbr')
feed('S<C-X><C-O>')
screen:expect([[
foo^ |
{s:S menu foo }{1: }|
{n:T menu bar }{1: }|
{n:C 中文 你好 }{1: }|
{1:~ }|*10
{2:-- }{5:match 1 of 3} |
]])
feed('<C-E><ESC>')
-- T5
command('set cia=menu,abbr,kind')
feed('S<C-X><C-O>')
screen:expect([[
foo^ |
{s:menu foo S }{1: }|
{n:menu bar T }{1: }|
{n:中文 你好 C }{1: }|
{1:~ }|*10
{2:-- }{5:match 1 of 3} |
]])
feed('<C-E><ESC>')
-- T6
command('set cia=menu,kind,abbr')
feed('S<C-X><C-O>')
screen:expect([[
foo^ |
{s:menu S foo }{1: }|
{n:menu T bar }{1: }|
{n:中文 C 你好 }{1: }|
{1:~ }|*10
{2:-- }{5:match 1 of 3} |
]])
feed('<C-E><ESC>')
-- T7
command('set cia&')
feed('S<C-X><C-O>')
screen:expect([[
foo^ |
{s:foo S menu }{1: }|
{n:bar T menu }{1: }|
{n:你好 C 中文 }{1: }|
{1:~ }|*10
{2:-- }{5:match 1 of 3} |
]])
end)
end
end

View File

@@ -1583,4 +1583,64 @@ func Test_pum_user_kind_hlgroup()
call StopVimInTerminal(buf)
endfunc
func Test_pum_completeitemalign()
CheckScreendump
let lines =<< trim END
func Omni_test(findstart, base)
if a:findstart
return col(".")
endif
return {
\ 'words': [
\ { 'word': 'foo', 'kind': 'S', 'menu': 'menu' },
\ { 'word': 'bar', 'kind': 'T', 'menu': 'menu' },
\ { 'word': '你好', 'kind': 'C', 'menu': '中文' },
\]}
endfunc
set omnifunc=Omni_test
command! -nargs=0 T1 set cia=abbr,kind,menu
command! -nargs=0 T2 set cia=abbr,menu,kind
command! -nargs=0 T3 set cia=kind,abbr,menu
command! -nargs=0 T4 set cia=kind,menu,abbr
command! -nargs=0 T5 set cia=menu,abbr,kind
command! -nargs=0 T6 set cia=menu,kind,abbr
command! -nargs=0 T7 set cia&
END
call writefile(lines, 'Xscript', 'D')
let buf = RunVimInTerminal('-S Xscript', {})
call TermWait(buf)
" T1 is default
call term_sendkeys(buf, ":T1\<CR>S\<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_completeitemalign_01', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
" T2
call term_sendkeys(buf, ":T2\<CR>S\<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_completeitemalign_02', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
" T3
call term_sendkeys(buf, ":T3\<CR>S\<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_completeitemalign_03', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
" T4
call term_sendkeys(buf, ":T4\<CR>S\<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_completeitemalign_04', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
" T5
call term_sendkeys(buf, ":T5\<CR>S\<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_completeitemalign_05', {})
call term_sendkeys(buf, "\<C-E>\<Esc>")
" T6
call term_sendkeys(buf, ":T6\<CR>S\<C-X>\<C-O>")
call VerifyScreenDump(buf, 'Test_pum_completeitemalign_06', {})
call term_sendkeys(buf, "\<C-E>\<Esc>:T7\<CR>")
call StopVimInTerminal(buf)
endfunc
" vim: shiftwidth=2 sts=2 expandtab