patch 9.1.1250: cannot set the maximum popup menu width

Problem:  cannot set the maximum popup menu width
          (Lucas Mior)
Solution: add the new global option value 'pummaxwidth'
          (glepnir)

fixes: #10901
closes: #16943

Signed-off-by: glepnir <glephunter@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/option.h b/src/option.h
index 5714e0c..0b568f1 100644
--- a/src/option.h
+++ b/src/option.h
@@ -545,6 +545,7 @@
 #endif
 EXTERN long	p_ph;		// 'pumheight'
 EXTERN long	p_pw;		// 'pumwidth'
+EXTERN long	p_pmw;		// 'pummaxwidth'
 EXTERN char_u	*p_com;		// 'comments'
 EXTERN char_u	*p_cpo;		// 'cpoptions'
 #ifdef FEAT_CSCOPE
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 3621635..b6b5d49 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -2021,6 +2021,9 @@
     {"pumheight",   "ph",   P_NUM|P_VI_DEF,
 			    (char_u *)&p_ph, PV_NONE, NULL, NULL,
 			    {(char_u *)0L, (char_u *)0L} SCTX_INIT},
+    {"pummaxwidth", "pmw",   P_NUM|P_VI_DEF,
+			    (char_u *)&p_pmw, PV_NONE, NULL, NULL,
+			    {(char_u *)0L, (char_u *)0L} SCTX_INIT},
     {"pumwidth",    "pw",   P_NUM|P_VI_DEF,
 			    (char_u *)&p_pw, PV_NONE, NULL, NULL,
 			    {(char_u *)15L, (char_u *)15L} SCTX_INIT},
diff --git a/src/popupmenu.c b/src/popupmenu.c
index 443223d..d69c7bd 100644
--- a/src/popupmenu.c
+++ b/src/popupmenu.c
@@ -115,6 +115,8 @@
     do
     {
 	def_width = p_pw;
+	if (p_pmw > 0 && def_width > p_pmw)
+	    def_width = p_pmw;
 	above_row = 0;
 	below_row = cmdline_row;
 
@@ -226,6 +228,8 @@
 	pum_size = size;
 	pum_compute_size();
 	max_width = pum_base_width;
+        if (p_pmw > 0 && max_width > p_pmw)
+	    max_width = p_pmw;
 
 	// Calculate column
 	if (State == MODE_CMDLINE)
@@ -275,8 +279,12 @@
 
 	    content_width = max_width + pum_kind_width + pum_extra_width + 1;
 	    if (pum_width > content_width && pum_width > p_pw)
+	    {
 		// Reduce width to fit item
-		pum_width = MAX(content_width , p_pw);
+		pum_width = MAX(content_width, p_pw);
+		if (p_pmw > 0 && pum_width > p_pmw)
+		    pum_width = p_pmw;
+	    }
 	    else if (((cursor_col > p_pw || cursor_col > max_width)
 #ifdef FEAT_RIGHTLEFT
 			&& !pum_rl)
@@ -313,6 +321,8 @@
 		if (pum_width < p_pw)
 		{
 		    pum_width = p_pw;
+		    if (p_pmw > 0 && pum_width > p_pmw)
+			pum_width = p_pmw;
 #ifdef FEAT_RIGHTLEFT
 		    if (pum_rl)
 		    {
@@ -327,7 +337,11 @@
 		    }
 		}
 		else if (pum_width > content_width && pum_width > p_pw)
+		{
 		    pum_width = MAX(content_width, p_pw);
+		    if (p_pmw > 0 && pum_width > p_pmw)
+			pum_width = p_pmw;
+		}
 	    }
 
 	}
@@ -341,11 +355,15 @@
 #endif
 		pum_col = 0;
 	    pum_width = Columns - 1;
+	    if (p_pmw > 0 && pum_width > p_pmw)
+		pum_width = p_pmw;
 	}
 	else
 	{
 	    if (max_width > p_pw)
 		max_width = p_pw;	// truncate
+	    if (p_pmw > 0 && max_width > p_pmw)
+		max_width = p_pmw;
 #ifdef FEAT_RIGHTLEFT
 	    if (pum_rl)
 		pum_col = max_width - 1;
@@ -582,6 +600,13 @@
     int		last_isabbr = FALSE;
     int		orig_attr = -1;
     int		scroll_range = pum_size - pum_height;
+    int		need_ellipsis = FALSE;
+    int		char_cells = 0;
+    int		ellipsis_width = 3;
+    int		over_cell = 0;
+    char_u	*new_str = NULL;
+    int		kept_len = 0;
+    char_u	*last_char = NULL;
 
     hlf_T	hlfsNorm[3];
     hlf_T	hlfsSel[3];
@@ -698,8 +723,14 @@
 			    {
 				char_u		*rt_start = rt;
 				int		cells;
+				int		used_cells = 0;
+				char_u		*old_rt = NULL;
+				char_u		*orig_rt = NULL;
 
 				cells = vim_strsize(rt);
+				need_ellipsis = p_pmw > ellipsis_width
+						    && pum_width == p_pmw
+						    && cells > pum_width;
 				if (cells > pum_width)
 				{
 				    do
@@ -709,7 +740,42 @@
 					MB_PTR_ADV(rt);
 				    } while (cells > pum_width);
 
-				    if (cells < pum_width)
+				    if (need_ellipsis)
+				    {
+					orig_rt = rt;
+					while (*orig_rt != NUL)
+					{
+					    char_cells = has_mbyte ? (*mb_ptr2cells)(orig_rt) : 1;
+					    if (used_cells + char_cells > ellipsis_width)
+						break;
+					    used_cells += char_cells;
+					    MB_PTR_ADV(orig_rt);
+					    last_char = orig_rt;
+					}
+
+					if (last_char != NULL)
+					{
+					    if (used_cells < ellipsis_width)
+					    {
+						over_cell = ellipsis_width - used_cells;
+						MB_PTR_ADV(orig_rt);
+						last_char = orig_rt;
+					    }
+					    kept_len = STRLEN(last_char);
+					    new_str = alloc(ellipsis_width + over_cell + kept_len + 1);
+					    if (!new_str)
+						return;
+					    vim_memset(new_str, '.', ellipsis_width);
+					    if (over_cell > 0)
+						vim_memset(new_str + ellipsis_width, ' ', over_cell);
+					    memcpy(new_str + ellipsis_width + over_cell, last_char, kept_len);
+					    new_str[ellipsis_width + kept_len + over_cell] = NUL;
+					    old_rt = rt_start;
+					    rt = rt_start = new_str;
+					    vim_free(old_rt);
+					}
+				    }
+				    else if (cells < pum_width)
 				    {
 					// Most left character requires 2-cells
 					// but only 1 cell is available on
@@ -739,8 +805,13 @@
 		    {
 			if (st != NULL)
 			{
-			    int size = (int)STRLEN(st);
-			    int cells = (*mb_string2cells)(st, size);
+			    int		size = (int)STRLEN(st);
+			    int		cells = (*mb_string2cells)(st, size);
+			    int		used_cells = 0;
+			    char_u	*st_end = NULL;
+			    need_ellipsis = p_pmw > ellipsis_width
+					&& pum_width == p_pmw
+					&& col + cells > pum_col + pum_width;
 
 			    // only draw the text that fits
 			    while (size > 0
@@ -756,6 +827,42 @@
 				    --cells;
 			    }
 
+			    // Add '...' indicator if truncated due to p_pmw
+			    if (need_ellipsis)
+			    {
+				st_end = st + size;
+				while (st_end > st)
+				{
+				    char_cells = has_mbyte ? (*mb_ptr2cells)(st_end) : 1;
+				    if (used_cells + char_cells > ellipsis_width)
+					break;
+				    used_cells += char_cells;
+				    MB_PTR_BACK(st, st_end);
+				    last_char = st_end;
+				}
+
+				if (last_char != NULL)
+				{
+				    if (used_cells < ellipsis_width)
+				    {
+					MB_PTR_BACK(st, st_end);
+					last_char = st_end;
+					over_cell = ellipsis_width - used_cells;
+				    }
+				    kept_len = last_char - st;
+				    new_str = alloc(ellipsis_width + over_cell + kept_len + 1);
+				    if (!new_str)
+					return;
+				    memcpy(new_str, st, kept_len);
+				    if (over_cell > 0)
+					vim_memset(new_str + kept_len, ' ', over_cell);
+				    vim_memset(new_str + kept_len + over_cell, '.', ellipsis_width);
+				    new_str[kept_len + ellipsis_width + over_cell] = NUL;
+				    vim_free(st);
+				    st = new_str;
+				}
+			    }
+
 			    if (attrs == NULL)
 				screen_puts_len(st, size, row, col, attr);
 			    else
diff --git a/src/testdir/dumps/Test_pum_maxwidth_01.dump b/src/testdir/dumps/Test_pum_maxwidth_01.dump
new file mode 100644
index 0000000..a836ab0
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_maxwidth_01.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @43
+|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @43
+@12|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a> @31
+|~+0#4040ff13&| @9| +0#0000001#e0e0e08|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| | +0#4040ff13#ffffff0@30
+|~| @9| +0#0000001#ffd7ff255|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| | +0#4040ff13#ffffff0@30
+|~| @73
+|~| @73
+|~| @73
diff --git a/src/testdir/dumps/Test_pum_maxwidth_02.dump b/src/testdir/dumps/Test_pum_maxwidth_02.dump
new file mode 100644
index 0000000..427ea9f
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_maxwidth_02.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @43
+|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @43
+@12|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a> @31
+|~+0#4040ff13&| @9| +0#0000001#e0e0e08|1|2|3|4|5|6|7|.@2| +0#4040ff13#ffffff0@52
+|~| @9| +0#0000001#ffd7ff255|1|2|3|4|5|6|7|.@2| +0#4040ff13#ffffff0@52
+|~| @73
+|~| @73
+|~| @73
diff --git a/src/testdir/dumps/Test_pum_maxwidth_03.dump b/src/testdir/dumps/Test_pum_maxwidth_03.dump
new file mode 100644
index 0000000..e303108
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_maxwidth_03.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @43
+|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @43
+@12|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a> @31
+|~+0#4040ff13&| @9| +0#0000001#e0e0e08|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|.@2| +0#4040ff13#ffffff0@42
+|~| @9| +0#0000001#ffd7ff255|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|.@2| +0#4040ff13#ffffff0@42
+|~| @73
+|~| @73
+|~| @73
diff --git a/src/testdir/dumps/Test_pum_maxwidth_04.dump b/src/testdir/dumps/Test_pum_maxwidth_04.dump
new file mode 100644
index 0000000..a6d257e
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_maxwidth_04.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a| @43
+|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|b| @43
+@12|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|a> @31
+|~+0#4040ff13&| @9| +0#0000001#e0e0e08|1|2|3|4|5|.@2| +0#4040ff13#ffffff0@54
+|~| @9| +0#0000001#ffd7ff255|1|2|3|4|5|.@2| +0#4040ff13#ffffff0@54
+|~| @73
+|~| @73
+|~| @73
diff --git a/src/testdir/dumps/Test_pum_maxwidth_05.dump b/src/testdir/dumps/Test_pum_maxwidth_05.dump
new file mode 100644
index 0000000..0dd6a39
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_maxwidth_05.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_> @44
+|1+0#0000001#e0e0e08|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_| | +0#4040ff13#ffffff0@43
+|一*0#0000001#ffd7ff255|二|三|四|五|六|七|八|九|十| +&@10| +0#4040ff13#ffffff0@43
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
diff --git a/src/testdir/dumps/Test_pum_maxwidth_06.dump b/src/testdir/dumps/Test_pum_maxwidth_06.dump
new file mode 100644
index 0000000..7321870
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_maxwidth_06.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_> @44
+|1+0#0000001#e0e0e08|2|3|4|5|6|7|.@2| +0#4040ff13#ffffff0@64
+|一*0#0000001#ffd7ff255|二|三| +&|.@2| +0#4040ff13#ffffff0@64
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
diff --git a/src/testdir/dumps/Test_pum_maxwidth_07.dump b/src/testdir/dumps/Test_pum_maxwidth_07.dump
new file mode 100644
index 0000000..8e81d31
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_maxwidth_07.dump
@@ -0,0 +1,8 @@
+| +0&#ffffff0@43> |_|9|8|7|6|5|4|3|2|1|_|9|8|7|6|5|4|3|2|1|_|9|8|7|6|5|4|3|2|1
+| +0#4040ff13&@64|.+0#0000001#e0e0e08@2|7|6|5|4|3|2|1
+| +0#4040ff13#ffffff0@64|.+0#0000001#ffd7ff255@2| |三*&|二|一
+| +0#4040ff13#ffffff0@73|~
+| @73|~
+| @73|~
+| @73|~
+| @73|~
diff --git a/src/testdir/dumps/Test_pum_maxwidth_08.dump b/src/testdir/dumps/Test_pum_maxwidth_08.dump
new file mode 100644
index 0000000..8681e5f
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_maxwidth_08.dump
@@ -0,0 +1,8 @@
+|1+0&#ffffff0|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_|1|2|3|4|5|6|7|8|9|_> @44
+|1+0#0000001#e0e0e08|2| +0#4040ff13#ffffff0@72
+|一*0#0000001#ffd7ff255| +0#4040ff13#ffffff0@72
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim
index e599a8d..94839b5 100644
--- a/src/testdir/test_popup.vim
+++ b/src/testdir/test_popup.vim
@@ -1986,4 +1986,81 @@
   call StopVimInTerminal(buf)
 endfunc
 
+func Test_pum_maxwidth()
+  CheckScreendump
+
+  let lines =<< trim END
+    123456789_123456789_123456789_a
+    123456789_123456789_123456789_b
+                123
+  END
+  call writefile(lines, 'Xtest', 'D')
+  let buf = RunVimInTerminal('Xtest', {})
+
+  call term_sendkeys(buf, "G\"zyy")
+  call term_sendkeys(buf, "A\<C-N>")
+  call VerifyScreenDump(buf, 'Test_pum_maxwidth_01', {'rows': 8})
+  call term_sendkeys(buf, "\<Esc>3Gdd\"zp")
+
+  call term_sendkeys(buf, ":set pummaxwidth=10\<CR>")
+  call term_sendkeys(buf, "GA\<C-N>")
+  call VerifyScreenDump(buf, 'Test_pum_maxwidth_02', {'rows': 8})
+  call term_sendkeys(buf, "\<Esc>3Gdd\"zp")
+
+  call term_sendkeys(buf, ":set pummaxwidth=20\<CR>")
+  call term_sendkeys(buf, "GA\<C-N>")
+  call VerifyScreenDump(buf, 'Test_pum_maxwidth_03', {'rows': 8})
+  call term_sendkeys(buf, "\<Esc>3Gdd\"zp")
+
+  call term_sendkeys(buf, ":set pumwidth=20 pummaxwidth=8\<CR>")
+  call term_sendkeys(buf, "GA\<C-N>")
+  call VerifyScreenDump(buf, 'Test_pum_maxwidth_04', {'rows': 8})
+  call term_sendkeys(buf, "\<Esc>3Gdd\"zp")
+
+  call StopVimInTerminal(buf)
+endfunc
+
+func Test_pum_maxwidth_multibyte()
+  CheckScreendump
+
+  let lines =<< trim END
+    func Omni_test(findstart, base)
+      if a:findstart
+        return col(".")
+      endif
+      return [
+        \ #{word: "123456789_123456789_123456789_"},
+        \ #{word: "一二三四五六七八九十"},
+        \ ]
+    endfunc
+    set omnifunc=Omni_test
+  END
+  call writefile(lines, 'Xtest', 'D')
+  let  buf = RunVimInTerminal('-S Xtest', {})
+  call TermWait(buf)
+
+  call term_sendkeys(buf, "S\<C-X>\<C-O>")
+  call VerifyScreenDump(buf, 'Test_pum_maxwidth_05', {'rows': 8})
+  call term_sendkeys(buf, "\<ESC>")
+
+  call term_sendkeys(buf, ":set pummaxwidth=10\<CR>")
+  call term_sendkeys(buf, "S\<C-X>\<C-O>")
+  call VerifyScreenDump(buf, 'Test_pum_maxwidth_06', {'rows': 8})
+  call term_sendkeys(buf, "\<ESC>")
+
+  if has('rightleft')
+    call term_sendkeys(buf, ":set rightleft\<CR>")
+    call term_sendkeys(buf, "S\<C-X>\<C-O>")
+    call VerifyScreenDump(buf, 'Test_pum_maxwidth_07', {'rows': 8})
+    call term_sendkeys(buf, "\<Esc>:set norightleft\<CR>")
+  endif
+
+  call term_sendkeys(buf, ":set pummaxwidth=2\<CR>")
+  call term_sendkeys(buf, "S\<C-X>\<C-O>")
+  call VerifyScreenDump(buf, 'Test_pum_maxwidth_08', {'rows': 8})
+  call term_sendkeys(buf, "\<ESC>")
+
+  call StopVimInTerminal(buf)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 7339d58..cb559cf 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1250,
+/**/
     1249,
 /**/
     1248,