patch 9.1.0754: fixed order of items in insert-mode completion menu

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: #14006
closes: #15760

Signed-off-by: glepnir <glephunter@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/insexpand.c b/src/insexpand.c
index 63bf070..a774a33 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -88,15 +88,6 @@
 #endif
 
 /*
- * 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;
@@ -1338,8 +1329,7 @@
 	    }
 
 	    if (compl->cp_text[CPT_ABBR] != NULL)
-		compl_match_array[i].pum_text =
-		    compl->cp_text[CPT_ABBR];
+		compl_match_array[i].pum_text = compl->cp_text[CPT_ABBR];
 	    else
 		compl_match_array[i].pum_text = compl->cp_str;
 	    compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND];
diff --git a/src/option.h b/src/option.h
index fba2672..ef39031 100644
--- a/src/option.h
+++ b/src/option.h
@@ -513,6 +513,8 @@
 EXTERN int	p_confirm;	// 'confirm'
 #endif
 EXTERN int	p_cp;		// 'compatible'
+EXTERN char_u	*p_cia;		// 'completeitemalign'
+EXTERN unsigned cia_flags;	// order flags of 'completeitemalign'
 EXTERN char_u	*p_cot;		// 'completeopt'
 EXTERN unsigned	cot_flags;	// flags from 'completeopt'
 // Keep in sync with p_cot_values in optionstr.c
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 8982ac6..4a5bd63 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -653,6 +653,10 @@
 			    {(char_u *)0L, (char_u *)0L}
 #endif
 			    SCTX_INIT},
+    {"completeitemalign", "cia", P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
+			    (char_u *)&p_cia, PV_NONE, did_set_completeitemalign, NULL,
+			    {(char_u *)"abbr,kind,menu", (char_u *)0L}
+			    SCTX_INIT},
     {"completeopt",   "cot",  P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
 			    (char_u *)&p_cot, PV_COT, did_set_completeopt, expand_set_completeopt,
 			    {(char_u *)"menu,preview", (char_u *)0L}
diff --git a/src/optionstr.c b/src/optionstr.c
index b6249a2..bf7135a 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -1635,6 +1635,58 @@
 	    matches);
 }
 
+/*
+ * The 'completeitemalign' option is changed.
+ */
+    char *
+did_set_completeitemalign(optset_T *args UNUSED)
+{
+    char_u	*p = p_cia;
+    unsigned	new_cia_flags = 0;
+    int		seen[3] = { FALSE, FALSE, FALSE };
+    int		count = 0;
+    char_u	buf[10];
+
+    while (*p)
+    {
+	copy_option_part(&p, buf, sizeof(buf), ",");
+	if (count >= 3)
+	    return e_invalid_argument;
+
+	if (STRCMP(buf, "abbr") == 0)
+	{
+	    if (seen[CPT_ABBR])
+		return e_invalid_argument;
+	    new_cia_flags = new_cia_flags * 10 + CPT_ABBR;
+	    seen[CPT_ABBR] = TRUE;
+	    count++;
+	}
+	else if (STRCMP(buf, "kind") == 0)
+	{
+	    if (seen[CPT_KIND])
+		return e_invalid_argument;
+	    new_cia_flags = new_cia_flags * 10 + CPT_KIND;
+	    seen[CPT_KIND] = TRUE;
+	    count++;
+	}
+	else if (STRCMP(buf, "menu") == 0)
+	{
+	    if (seen[CPT_MENU])
+		return e_invalid_argument;
+	    new_cia_flags = new_cia_flags * 10 + CPT_MENU;
+	    seen[CPT_MENU] = TRUE;
+	    count++;
+	}
+	else
+	    return e_invalid_argument;
+    }
+    if (new_cia_flags == 0 || count != 3)
+	return e_invalid_argument;
+
+    cia_flags = new_cia_flags;
+    return NULL;
+}
+
 #if (defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)) || defined(PROTO)
 /*
  * The 'completepopup' option is changed.
diff --git a/src/popupmenu.c b/src/popupmenu.c
index da8241b..010a987 100644
--- a/src/popupmenu.c
+++ b/src/popupmenu.c
@@ -536,6 +536,28 @@
     }
 }
 
+
+    static inline void
+pum_align_order(int *order)
+{
+    int 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_u *
+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".
  */
@@ -549,19 +571,25 @@
     hlf_T	*hlfs; // array used for highlights
     hlf_T	hlf;
     int		attr;
-    int		i;
+    int		i, j;
     int		idx;
     char_u	*s;
     char_u	*p = NULL;
-    int		totwidth, width, w;
+    int		totwidth, width, w;  // total-width item-width char-width
     int		thumb_pos = 0;
     int		thumb_height = 1;
-    int		round;
+    int		item_type;
+    int		order[3];
+    int		next_isempty = FALSE;
     int		n;
+    int		items_width_array[3] = { pum_base_width, pum_kind_width,
+							    pum_extra_width };
+    int		basic_width;  // first item width
+    int		last_isabbr = FALSE;
 
     hlf_T	hlfsNorm[3];
     hlf_T	hlfsSel[3];
-    // "word"
+    // "word"/"abbr"
     hlfsNorm[0] = HLF_PNI;
     hlfsSel[0] = HLF_PSI;
     // "kind"
@@ -621,28 +649,24 @@
 		screen_putchar(' ', row, pum_col - 1, attr);
 
 	// 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
 	col = pum_col;
 	totwidth = 0;
-	for (round = 0; round < 3; ++round)
+	pum_align_order(order);
+	basic_width = items_width_array[order[0]];
+	last_isabbr = order[2] == CPT_ABBR;
+	for (j = 0; j < 3; ++j)
 	{
-	    hlf = hlfs[round];
+	    item_type = order[j];
+	    hlf = hlfs[item_type];
 	    attr = highlight_attr[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);
 	    width = 0;
 	    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))
 		{
@@ -774,33 +798,35 @@
 			width += w;
 		}
 
-	    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;
+
+	    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;
 #ifdef FEAT_RIGHTLEFT
 	    if (pum_rl)
 	    {
-		screen_fill(row, row + 1, pum_col - pum_base_width - n + 1,
+		screen_fill(row, row + 1, pum_col - basic_width - n + 1,
 						    col + 1, ' ', ' ', attr);
-		col = pum_col - pum_base_width - n;
+		col = pum_col - basic_width - n;
 	    }
 	    else
 #endif
 	    {
-		screen_fill(row, row + 1, col, pum_col + pum_base_width + n,
+		screen_fill(row, row + 1, col, pum_col + basic_width + n,
 							      ' ', ' ', attr);
-		col = pum_col + pum_base_width + n;
+		col = pum_col + basic_width + n;
 	    }
-	    totwidth = pum_base_width + n;
+	    totwidth = basic_width + n;
 	}
 
 #ifdef FEAT_RIGHTLEFT
diff --git a/src/proto/optionstr.pro b/src/proto/optionstr.pro
index 39c40f3..c034c11 100644
--- a/src/proto/optionstr.pro
+++ b/src/proto/optionstr.pro
@@ -42,6 +42,7 @@
 int expand_set_complete(optexpand_T *args, int *numMatches, char_u ***matches);
 char *did_set_completeopt(optset_T *args);
 int expand_set_completeopt(optexpand_T *args, int *numMatches, char_u ***matches);
+char *did_set_completeitemalign(optset_T *args);
 char *did_set_completepopup(optset_T *args);
 char *did_set_completeslash(optset_T *args);
 int expand_set_completeslash(optexpand_T *args, int *numMatches, char_u ***matches);
diff --git a/src/testdir/dumps/Test_pum_completeitemalign_01.dump b/src/testdir/dumps/Test_pum_completeitemalign_01.dump
new file mode 100644
index 0000000..2a2f6f0
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_completeitemalign_01.dump
@@ -0,0 +1,20 @@
+|f+0&#ffffff0|o@1> @71
+|f+0#0000001#e0e0e08|o@1| @1|S| |m|e|n|u| @3| +0#4040ff13#ffffff0@59
+|b+0#0000001#ffd7ff255|a|r| @1|T| |m|e|n|u| @3| +0#4040ff13#ffffff0@59
+|你*0#0000001#ffd7ff255|好| +&|C| |中*&|文| +&@3| +0#4040ff13#ffffff0@59
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34
diff --git a/src/testdir/dumps/Test_pum_completeitemalign_02.dump b/src/testdir/dumps/Test_pum_completeitemalign_02.dump
new file mode 100644
index 0000000..f006827
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_completeitemalign_02.dump
@@ -0,0 +1,20 @@
+|f+0&#ffffff0|o@1> @71
+|f+0#0000001#e0e0e08|o@1| @1|m|e|n|u| |S| @3| +0#4040ff13#ffffff0@59
+|b+0#0000001#ffd7ff255|a|r| @1|m|e|n|u| |T| @3| +0#4040ff13#ffffff0@59
+|你*0#0000001#ffd7ff255|好| +&|中*&|文| +&|C| @3| +0#4040ff13#ffffff0@59
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34
diff --git a/src/testdir/dumps/Test_pum_completeitemalign_03.dump b/src/testdir/dumps/Test_pum_completeitemalign_03.dump
new file mode 100644
index 0000000..a16954f
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_completeitemalign_03.dump
@@ -0,0 +1,20 @@
+|f+0&#ffffff0|o@1> @71
+|S+0#0000001#e0e0e08| |f|o@1| @1|m|e|n|u| @3| +0#4040ff13#ffffff0@59
+|T+0#0000001#ffd7ff255| |b|a|r| @1|m|e|n|u| @3| +0#4040ff13#ffffff0@59
+|C+0#0000001#ffd7ff255| |你*&|好| +&|中*&|文| +&@3| +0#4040ff13#ffffff0@59
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34
diff --git a/src/testdir/dumps/Test_pum_completeitemalign_04.dump b/src/testdir/dumps/Test_pum_completeitemalign_04.dump
new file mode 100644
index 0000000..8c9123af
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_completeitemalign_04.dump
@@ -0,0 +1,20 @@
+|f+0&#ffffff0|o@1> @71
+|S+0#0000001#e0e0e08| |m|e|n|u| |f|o@1| @4| +0#4040ff13#ffffff0@59
+|T+0#0000001#ffd7ff255| |m|e|n|u| |b|a|r| @4| +0#4040ff13#ffffff0@59
+|C+0#0000001#ffd7ff255| |中*&|文| +&|你*&|好| +&@3| +0#4040ff13#ffffff0@59
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34
diff --git a/src/testdir/dumps/Test_pum_completeitemalign_05.dump b/src/testdir/dumps/Test_pum_completeitemalign_05.dump
new file mode 100644
index 0000000..7209c85
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_completeitemalign_05.dump
@@ -0,0 +1,20 @@
+|f+0&#ffffff0|o@1> @71
+|m+0#0000001#e0e0e08|e|n|u| |f|o@1| @1|S| @3| +0#4040ff13#ffffff0@59
+|m+0#0000001#ffd7ff255|e|n|u| |b|a|r| @1|T| @3| +0#4040ff13#ffffff0@59
+|中*0#0000001#ffd7ff255|文| +&|你*&|好| +&|C| @3| +0#4040ff13#ffffff0@59
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34
diff --git a/src/testdir/dumps/Test_pum_completeitemalign_06.dump b/src/testdir/dumps/Test_pum_completeitemalign_06.dump
new file mode 100644
index 0000000..03210ac
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_completeitemalign_06.dump
@@ -0,0 +1,20 @@
+|f+0&#ffffff0|o@1> @71
+|m+0#0000001#e0e0e08|e|n|u| |S| |f|o@1| @4| +0#4040ff13#ffffff0@59
+|m+0#0000001#ffd7ff255|e|n|u| |T| |b|a|r| @4| +0#4040ff13#ffffff0@59
+|中*0#0000001#ffd7ff255|文| +&|C| |你*&|好| +&@3| +0#4040ff13#ffffff0@59
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@34
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
index c601158..936a0fa 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -80,6 +80,7 @@
       \ 'complete': [['', 'w,b'], ['xxx']],
       \ 'concealcursor': [['', 'n', 'nvic'], ['xxx']],
       \ 'completeopt': [['', 'menu', 'menu,longest'], ['xxx', 'menu,,,longest,']],
+      \ 'completeitemalign': [['abbr,kind,menu'], ['xxx','abbr,menu','abbr,menu,kind,abbr', 'abbr', 'abbr1234,kind', '']],
       \ 'completepopup': [['', 'height:13', 'highlight:That', 'width:10,height:234,highlight:Mine'], ['height:yes', 'width:no', 'xxx', 'xxx:99', 'border:maybe', 'border:1']],
       \ 'completeslash': [['', 'slash', 'backslash'], ['xxx']],
       \ 'cryptmethod': [['', 'zip'], ['xxx']],
diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim
index caec8ff..1758f74 100644
--- a/src/testdir/test_popup.vim
+++ b/src/testdir/test_popup.vim
@@ -1581,4 +1581,64 @@
   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
diff --git a/src/version.c b/src/version.c
index f5d85cb..8cc38cc 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    754,
+/**/
     753,
 /**/
     752,
diff --git a/src/vim.h b/src/vim.h
index 9c1434c..ebca6ae 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2373,6 +2373,17 @@
 } funcerror_T;
 
 /*
+ * 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;
+
+/*
  * Type for the callback function that is invoked after an option value is
  * changed to validate and apply the new value.
  *