patch 8.1.1553: not easy to change the text in a popup window

Problem:    Not easy to change the text in a popup window.
Solution:   Add popup_settext(). (Ben Jackson, closes #4549)
            Also display a space for an empty popup.
diff --git a/src/evalfunc.c b/src/evalfunc.c
index d371c28..8c8bb4d 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -822,6 +822,7 @@
     {"popup_hide",	1, 1, f_popup_hide},
     {"popup_move",	2, 2, f_popup_move},
     {"popup_notification", 2, 2, f_popup_notification},
+    {"popup_settext",	2, 2, f_popup_settext},
     {"popup_show",	1, 1, f_popup_show},
 #endif
 #ifdef FEAT_FLOAT
diff --git a/src/popupwin.c b/src/popupwin.c
index c0b5e13..99ef290 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -601,8 +601,10 @@
 	wp->w_topline = wp->w_buffer->b_ml.ml_line_count;
 
     // Compute width based on longest text line and the 'wrap' option.
+    // Use a minimum width of one, so that something shows when there is no
+    // text.
     // TODO: more accurate wrapping
-    wp->w_width = 0;
+    wp->w_width = 1;
     for (lnum = wp->w_topline; lnum <= wp->w_buffer->b_ml.ml_line_count; ++lnum)
     {
 	int len = vim_strsize(ml_get_buf(wp->w_buffer, lnum, FALSE));
@@ -704,6 +706,48 @@
 } create_type_T;
 
 /*
+ * Make "buf" empty and set the contents to "text".
+ * Used by popup_create() and popup_settext().
+ */
+    static void
+popup_set_buffer_text(buf_T *buf, typval_T text)
+{
+    int	    lnum;
+
+    // Clear the buffer, then replace the lines.
+    curbuf = buf;
+    for (lnum = buf->b_ml.ml_line_count; lnum > 0; --lnum)
+	ml_delete(lnum, FALSE);
+    curbuf = curwin->w_buffer;
+
+    // Add text to the buffer.
+    if (text.v_type == VAR_STRING)
+    {
+	// just a string
+	ml_append_buf(buf, 0, text.vval.v_string, (colnr_T)0, TRUE);
+    }
+    else
+    {
+	list_T *l = text.vval.v_list;
+
+	if (l->lv_len > 0)
+	{
+	    if (l->lv_first->li_tv.v_type == VAR_STRING)
+		// list of strings
+		add_popup_strings(buf, l);
+	    else
+		// list of dictionaries
+		add_popup_dicts(buf, l);
+	}
+    }
+
+    // delete the line that was in the empty buffer
+    curbuf = buf;
+    ml_delete(buf->b_ml.ml_line_count, FALSE);
+    curbuf = curwin->w_buffer;
+}
+
+/*
  * popup_create({text}, {options})
  * popup_atcursor({text}, {options})
  */
@@ -789,31 +833,7 @@
 	// TODO: find tab page "nr"
 	emsg("Not implemented yet");
 
-    // Add text to the buffer.
-    if (argvars[0].v_type == VAR_STRING)
-    {
-	// just a string
-	ml_append_buf(buf, 0, argvars[0].vval.v_string, (colnr_T)0, TRUE);
-    }
-    else
-    {
-	list_T *l = argvars[0].vval.v_list;
-
-	if (l->lv_len > 0)
-	{
-	    if (l->lv_first->li_tv.v_type == VAR_STRING)
-		// list of strings
-		add_popup_strings(buf, l);
-	    else
-		// list of dictionaries
-		add_popup_dicts(buf, l);
-	}
-    }
-
-    // Delete the line of the empty buffer.
-    curbuf = buf;
-    ml_delete(buf->b_ml.ml_line_count, FALSE);
-    curbuf = curwin->w_buffer;
+    popup_set_buffer_text(buf, argvars[0]);
 
     if (type == TYPE_ATCURSOR)
     {
@@ -1112,6 +1132,22 @@
     }
 }
 
+/*
+ * popup_settext({id}, {text})
+ */
+    void
+f_popup_settext(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    int		id = (int)tv_get_number(&argvars[0]);
+    win_T	*wp = find_popup_win(id);
+
+    if (wp != NULL)
+    {
+	popup_set_buffer_text(wp->w_buffer, argvars[1]);
+	popup_adjust_position(wp);
+    }
+}
+
     static void
 popup_free(win_T *wp)
 {
diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro
index 6e93791..e026506 100644
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -14,6 +14,7 @@
 void f_popup_close(typval_T *argvars, typval_T *rettv);
 void f_popup_hide(typval_T *argvars, typval_T *rettv);
 void f_popup_show(typval_T *argvars, typval_T *rettv);
+void f_popup_settext(typval_T *argvars, typval_T *rettv);
 void popup_close(int id);
 void popup_close_tabpage(tabpage_T *tp, int id);
 void close_all_popups(void);
diff --git a/src/testdir/dumps/Test_popup_settext_01.dump b/src/testdir/dumps/Test_popup_settext_01.dump
new file mode 100644
index 0000000..768c1bd
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_01.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @28|t+0#0000001#ffd7ff255|h|i|s| |i|s| |a| |t|e|x|t| +0#4040ff13#ffffff0@30
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1| 
diff --git a/src/testdir/dumps/Test_popup_settext_02.dump b/src/testdir/dumps/Test_popup_settext_02.dump
new file mode 100644
index 0000000..565e97f
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_02.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @35| +0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|a|l@1| |p|o|p|u|p|_|s|e|t@1|e|x|t|(|p|,| |'@1|)| @30|0|,|0|-|1| @8|A|l@1| 
diff --git a/src/testdir/dumps/Test_popup_settext_03.dump b/src/testdir/dumps/Test_popup_settext_03.dump
new file mode 100644
index 0000000..b2c0504
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_03.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @35|a+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @35|b+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @35|c+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|a|l@1| |p|o|p|u|p|_|s|e|t@1|e|x|t|(|p|,| |[|'|a|'|,|'|b|'|,|'|c|'|]|)| @19|0|,|0|-|1| @8|A|l@1| 
diff --git a/src/testdir/dumps/Test_popup_settext_04.dump b/src/testdir/dumps/Test_popup_settext_04.dump
new file mode 100644
index 0000000..8e20916
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_04.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @35|a+0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|a|l@1| |p|o|p|u|p|_|s|e|t@1|e|x|t|(|p|,| |[|'|a|'|]|)| @27|0|,|0|-|1| @8|A|l@1| 
diff --git a/src/testdir/dumps/Test_popup_settext_05.dump b/src/testdir/dumps/Test_popup_settext_05.dump
new file mode 100644
index 0000000..1ae670e
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_05.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @35| +0#0000001#ffd7ff255| +0#4040ff13#ffffff0@36
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|c|a|l@1| |p|o|p|u|p|_|s|e|t@1|e|x|t|(|p|,| |[|]|)| @30|0|,|0|-|1| @8|A|l@1| 
diff --git a/src/testdir/dumps/Test_popup_settext_06.dump b/src/testdir/dumps/Test_popup_settext_06.dump
new file mode 100644
index 0000000..99c32c8
--- /dev/null
+++ b/src/testdir/dumps/Test_popup_settext_06.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @33|a+0#0000001#ffd7ff255@3| +0#4040ff13#ffffff0@35
+|~| @33|b+0#0000001#ffd7ff255@3| +0#4040ff13#ffffff0@35
+|~| @33|c+0#0000001#ffd7ff255@3| +0#4040ff13#ffffff0@35
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|0|,|0|-|1| @8|A|l@1| 
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index ae41512..ebe2400 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -916,13 +916,13 @@
   let winid = popup_create('', {'padding': [2,2,2,2]})
   redraw
   let pos = popup_getpos(winid)
-  call assert_equal(4, pos.width)
+  call assert_equal(5, pos.width)
   call assert_equal(5, pos.height)
 
   let winid = popup_create([], {'border': []})
   redraw
   let pos = popup_getpos(winid)
-  call assert_equal(2, pos.width)
+  call assert_equal(3, pos.width)
   call assert_equal(3, pos.height)
 endfunc
 
@@ -1231,3 +1231,47 @@
   call StopVimInTerminal(buf)
   call delete('XtestNotifications')
 endfunc
+
+function Test_popup_settext()
+  if !CanRunVimInTerminal()
+    throw 'Skipped: cannot make screendumps'
+  endif
+
+  let lines =<< trim END
+    let opts = {'wrap': 0}
+    let p = popup_create('test', opts)
+    call popup_settext(p, 'this is a text')
+  END
+
+  call writefile( lines, 'XtestPopupSetText' )
+  let buf = RunVimInTerminal('-S XtestPopupSetText', {'rows': 10})
+  call VerifyScreenDump(buf, 'Test_popup_settext_01', {})
+
+  " Setting to empty string clears it
+  call term_sendkeys(buf, ":call popup_settext(p, '')\<CR>")
+  call VerifyScreenDump(buf, 'Test_popup_settext_02', {})
+
+  " Setting a list
+  call term_sendkeys(buf, ":call popup_settext(p, ['a','b','c'])\<CR>")
+  call VerifyScreenDump(buf, 'Test_popup_settext_03', {})
+
+  " Shrinking with a list
+  call term_sendkeys(buf, ":call popup_settext(p, ['a'])\<CR>")
+  call VerifyScreenDump(buf, 'Test_popup_settext_04', {})
+
+  " Growing with a list
+  call term_sendkeys(buf, ":call popup_settext(p, ['a','b','c'])\<CR>")
+  call VerifyScreenDump(buf, 'Test_popup_settext_03', {})
+
+  " Empty list clears
+  call term_sendkeys(buf, ":call popup_settext(p, [])\<CR>")
+  call VerifyScreenDump(buf, 'Test_popup_settext_05', {})
+
+  " Dicts
+  call term_sendkeys(buf, ":call popup_settext(p, [{'text': 'aaaa'}, {'text': 'bbbb'}, {'text': 'cccc'}])\<CR>")
+  call VerifyScreenDump(buf, 'Test_popup_settext_06', {})
+
+  " clean up
+  call StopVimInTerminal(buf)
+  call delete('XtestPopupSetText')
+endfunction
diff --git a/src/version.c b/src/version.c
index f960d52..aa11b7e 100644
--- a/src/version.c
+++ b/src/version.c
@@ -778,6 +778,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1553,
+/**/
     1552,
 /**/
     1551,