patch 8.1.1410: popup_move() is not implemented yet

Problem:    Popup_move() is not implemented yet.
Solution:   Implement it. (Yasuhiro Matsumoto, closes #4441)  Improve the
            positioning and resizing.
diff --git a/src/evalfunc.c b/src/evalfunc.c
index b15e74d..03e3260 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -811,6 +811,7 @@
     {"popup_close",	1, 1, f_popup_close},
     {"popup_create",	2, 2, f_popup_create},
     {"popup_hide",	1, 1, f_popup_hide},
+    {"popup_move",	2, 2, f_popup_move},
     {"popup_show",	1, 1, f_popup_show},
 #endif
 #ifdef FEAT_FLOAT
diff --git a/src/popupwin.c b/src/popupwin.c
index 9222119..9970c5a 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -25,10 +25,14 @@
     int	    nr;
     char_u  *str;
 
+    wp->w_minwidth = dict_get_number(dict, (char_u *)"minwidth");
+    wp->w_minheight = dict_get_number(dict, (char_u *)"minheight");
     wp->w_maxwidth = dict_get_number(dict, (char_u *)"maxwidth");
     wp->w_maxheight = dict_get_number(dict, (char_u *)"maxheight");
-    wp->w_winrow = dict_get_number(dict, (char_u *)"line");
-    wp->w_wincol = dict_get_number(dict, (char_u *)"col");
+
+    wp->w_wantline = dict_get_number(dict, (char_u *)"line");
+    wp->w_wantcol = dict_get_number(dict, (char_u *)"col");
+
     wp->w_zindex = dict_get_number(dict, (char_u *)"zindex");
 
 #if defined(FEAT_TIMERS)
@@ -145,6 +149,49 @@
 }
 
 /*
+ * Adjust the position and size of the popup to fit on the screen.
+ */
+    static void
+popup_adjust_position(win_T *wp)
+{
+    // TODO: Compute the size and position properly.
+    if (wp->w_wantline > 0)
+	wp->w_winrow = wp->w_wantline - 1;
+    else
+	// TODO: better default
+	wp->w_winrow = Rows > 5 ? Rows / 2 - 2 : 0;
+    if (wp->w_winrow >= Rows)
+	wp->w_winrow = Rows - 1;
+
+    if (wp->w_wantcol > 0)
+	wp->w_wincol = wp->w_wantcol - 1;
+    else
+	// TODO: better default
+	wp->w_wincol = Columns > 20 ? Columns / 2 - 10 : 0;
+    if (wp->w_wincol >= Columns - 3)
+	wp->w_wincol = Columns - 3;
+
+    // TODO: set width based on longest text line and the 'wrap' option
+    wp->w_width = vim_strsize(ml_get_buf(wp->w_buffer, 1, FALSE));
+    if (wp->w_minwidth > 0 && wp->w_width < wp->w_minwidth)
+	wp->w_width = wp->w_minwidth;
+    if (wp->w_maxwidth > 0 && wp->w_width > wp->w_maxwidth)
+	wp->w_width = wp->w_maxwidth;
+    if (wp->w_width > Columns - wp->w_wincol)
+	wp->w_width = Columns - wp->w_wincol;
+
+    if (wp->w_height <= 1)
+	// TODO: adjust height for wrapped lines
+	wp->w_height = wp->w_buffer->b_ml.ml_line_count;
+    if (wp->w_minheight > 0 && wp->w_height < wp->w_minheight)
+	wp->w_height = wp->w_minheight;
+    if (wp->w_maxheight > 0 && wp->w_height > wp->w_maxheight)
+	wp->w_height = wp->w_maxheight;
+    if (wp->w_height > Rows - wp->w_winrow)
+	wp->w_height = Rows - wp->w_winrow;
+}
+
+/*
  * popup_create({text}, {options})
  */
     void
@@ -241,32 +288,7 @@
     if (wp->w_zindex == 0)
 	wp->w_zindex = 50;
 
-    // TODO: Compute the size and position properly.
-
-    // Default position is in middle of the screen, assuming a small popup
-    if (wp->w_winrow == 0)
-	wp->w_winrow = Rows > 5 ? Rows / 2 - 2 : 0;
-    else
-	--wp->w_winrow;  // option value is one-based
-    if (wp->w_wincol == 0)
-	wp->w_wincol = Columns > 20 ? Columns / 2 - 10 : 0;
-    else
-	--wp->w_wincol;  // option value is one-based
-
-
-    // TODO: set width based on longest text line and the 'wrap' option
-    wp->w_width = wp->w_maxwidth == 0 ? 20 : wp->w_maxwidth;
-    if (wp->w_maxwidth > 0 && wp->w_width > wp->w_maxwidth)
-	wp->w_width = wp->w_maxwidth;
-    if (wp->w_width > Columns - wp->w_wincol)
-	wp->w_width = Columns - wp->w_wincol;
-
-    // TODO: adjust height for wrapped lines
-    wp->w_height = buf->b_ml.ml_line_count;
-    if (wp->w_maxheight > 0 && wp->w_height > wp->w_maxheight)
-	wp->w_height = wp->w_maxheight;
-    if (wp->w_height > Rows - wp->w_winrow)
-	wp->w_height = Rows - wp->w_winrow;
+    popup_adjust_position(wp);
 
     wp->w_vsep_width = 0;
 
@@ -424,4 +446,45 @@
     close_all_popups();
 }
 
+/*
+ * popup_move({id}, {options})
+ */
+    void
+f_popup_move(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    dict_T	*d;
+    int		nr;
+    int		id = (int)tv_get_number(argvars);
+    win_T	*wp = find_popup_win(id);
+
+    if (wp == NULL)
+	return;  // invalid {id}
+
+    if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL)
+    {
+	emsg(_(e_dictreq));
+	return;
+    }
+    d = argvars[1].vval.v_dict;
+
+    if ((nr = dict_get_number(d, (char_u *)"minwidth")) > 0)
+	wp->w_minwidth = nr;
+    if ((nr = dict_get_number(d, (char_u *)"minheight")) > 0)
+	wp->w_minheight = nr;
+    if ((nr = dict_get_number(d, (char_u *)"maxwidth")) > 0)
+	wp->w_maxwidth = nr;
+    if ((nr = dict_get_number(d, (char_u *)"maxheight")) > 0)
+	wp->w_maxheight = nr;
+    if ((nr = dict_get_number(d, (char_u *)"line")) > 0)
+	wp->w_wantline = nr;
+    if ((nr = dict_get_number(d, (char_u *)"col")) > 0)
+	wp->w_wantcol = nr;
+    // TODO: "pos"
+
+    if (wp->w_winrow + wp->w_height >= cmdline_row)
+	clear_cmdline = TRUE;
+    popup_adjust_position(wp);
+    redraw_all_later(NOT_VALID);
+}
+
 #endif // FEAT_TEXT_PROP
diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro
index 64fca5f..f4d4760 100644
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -8,4 +8,5 @@
 void popup_close_tabpage(tabpage_T *tp, int id);
 void close_all_popups(void);
 void ex_popupclear(exarg_T *eap);
+void f_popup_move(typval_T *argvars, typval_T *rettv);
 /* vim: set ft=c : */
diff --git a/src/screen.c b/src/screen.c
index 02cd172..d527223 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -1034,6 +1034,24 @@
 }
 #endif
 
+/*
+ * Get 'wincolor' attribute for window "wp".  If not set and "wp" is a popup
+ * window then get the "Pmenu" highlight attribute.
+ */
+    static int
+get_wcr_attr(win_T *wp)
+{
+    int wcr_attr = 0;
+
+    if (*wp->w_p_wcr != NUL)
+	wcr_attr = syn_name2attr(wp->w_p_wcr);
+#ifdef FEAT_TEXT_PROP
+    if (bt_popup(wp->w_buffer) && wcr_attr == 0)
+	wcr_attr = HL_ATTR(HLF_PNI);
+#endif
+    return wcr_attr;
+}
+
 #if defined(FEAT_GUI) || defined(PROTO)
 /*
  * Update a single window, its status line and maybe the command line msg.
@@ -2419,13 +2437,9 @@
 {
     int		n = 0;
     int		attr = HL_ATTR(hl);
-    int		wcr_attr = 0;
+    int		wcr_attr = get_wcr_attr(wp);
 
-    if (*wp->w_p_wcr != NUL)
-    {
-	wcr_attr = syn_name2attr(wp->w_p_wcr);
-	attr = hl_combine_attr(wcr_attr, attr);
-    }
+    attr = hl_combine_attr(wcr_attr, attr);
 
     if (draw_margin)
     {
@@ -3115,6 +3129,8 @@
     int		vi_attr = 0;		// attributes for Visual and incsearch
 					// highlighting
     int		wcr_attr = 0;		// attributes from 'wincolor'
+    int		win_attr = 0;		// background for whole window, except
+					// margins and "~" lines.
     int		area_attr = 0;		// attributes desired by highlighting
     int		search_attr = 0;	// attributes desired by 'hlsearch'
 #ifdef FEAT_SYN_HL
@@ -3196,7 +3212,6 @@
 #ifdef FEAT_TERMINAL
     int		get_term_attr = FALSE;
 #endif
-    int		win_attr = 0;		// background for whole window
 
     /* draw_state: items that are drawn in sequence: */
 #define WL_START	0		/* nothing done yet */
@@ -3566,28 +3581,15 @@
 	}
     }
 
-    if (*wp->w_p_wcr != NUL)
+    wcr_attr = get_wcr_attr(wp);
+    if (wcr_attr != 0)
     {
-	wcr_attr = syn_name2attr(wp->w_p_wcr);
-
-	// 'wincolor' highlighting for the whole window
-	if (wcr_attr != 0)
-	{
-	    win_attr = wcr_attr;
-	    area_highlighting = TRUE;
-	}
+	win_attr = wcr_attr;
+	area_highlighting = TRUE;
     }
 #ifdef FEAT_TEXT_PROP
     if (bt_popup(wp->w_buffer))
-    {
 	screen_line_flags |= SLF_POPUP;
-
-	if (win_attr == 0)
-	{
-	    win_attr = HL_ATTR(HLF_PNI);
-	    area_highlighting = TRUE;
-	}
-    }
 #endif
 
     /*
diff --git a/src/structs.h b/src/structs.h
index 925bf03..07dc7e3 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -2873,8 +2873,12 @@
 #ifdef FEAT_TEXT_PROP
     int		w_popup_flags;	    // PFL_ values
     int		w_zindex;
+    int		w_minheight;	    // "minheight" for popup window
+    int		w_minwidth;	    // "minwidth" for popup window
     int		w_maxheight;	    // "maxheight" for popup window
     int		w_maxwidth;	    // "maxwidth" for popup window
+    int		w_wantline;	    // "line" for popup window
+    int		w_wantcol;	    // "col" for popup window
 # if defined(FEAT_TIMERS)
     timer_T	*w_popup_timer;	    // timer for closing popup window
 # endif
diff --git a/src/testdir/dumps/Test_popupwin_05.dump b/src/testdir/dumps/Test_popupwin_05.dump
new file mode 100644
index 0000000..bfb50f0
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_05.dump
@@ -0,0 +1,10 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @6|o+0#0000001#ffd7ff255|t|h|e|r| |t|a|b| @5| +0#4040ff13#ffffff0@51
+|~| @6|a+0#0000001#ffd7ff255| |c+0#ff404010&|o|m@1|e|n|t| +0#0000001&|l|i|n|e| | +0#4040ff13#ffffff0@51
+|~| @6|~+0&#ffd7ff255| @13| +0&#ffffff0@51
+|~| @73
+|~| @73
+|~| @73
+|:+0#0000000&|r|e|d|r|a|w| @49|0|,|0|-|1| @8|A|l@1| 
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index f1394fb..756ad68 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -16,8 +16,8 @@
 	\ "hi PopupColor2 ctermbg=lightcyan",
 	\ "hi Comment ctermfg=red",
 	\ "call prop_type_add('comment', {'highlight': 'Comment'})",
-	\ "let winid = popup_create('hello there', {'line': 3, 'col': 11, 'highlight': 'PopupColor1'})",
-	\ "let winid2 = popup_create(['another one', 'another two', 'another three'], {'line': 3, 'col': 25})",
+	\ "let winid = popup_create('hello there', {'line': 3, 'col': 11, 'minwidth': 20, 'highlight': 'PopupColor1'})",
+	\ "let winid2 = popup_create(['another one', 'another two', 'another three'], {'line': 3, 'col': 25, 'minwidth': 20})",
 	\ "call setwinvar(winid2, '&wincolor', 'PopupColor2')",
 	\], 'XtestPopup')
   let buf = RunVimInTerminal('-S XtestPopup', {'rows': 10})
@@ -25,12 +25,12 @@
 
   " Add a tabpage
   call term_sendkeys(buf, ":tabnew\<CR>")
-  call term_sendkeys(buf, ":call popup_create(["
+  call term_sendkeys(buf, ":let popupwin = popup_create(["
 	\ .. "{'text': 'other tab'},"
 	\ .. "{'text': 'a comment line', 'props': [{"
-	\ .. "'col': 3, 'length': 7, 'type': 'comment'"
+	\ .. "'col': 3, 'length': 7, 'minwidth': 20, 'type': 'comment'"
 	\ .. "}]},"
-	\ .. "], {'line': 4, 'col': 9})\<CR>")
+	\ .. "], {'line': 4, 'col': 9, 'minwidth': 20})\<CR>")
   call VerifyScreenDump(buf, 'Test_popupwin_02', {})
 
   " switch back to first tabpage
@@ -41,6 +41,11 @@
   call term_sendkeys(buf, ":quit!\<CR>")
   call VerifyScreenDump(buf, 'Test_popupwin_04', {})
 
+  " resize popup
+  call term_sendkeys(buf, ":call popup_move(popupwin, {'minwidth': 15, 'maxwidth': 25, 'minheight': 3, 'maxheight': 5})\<CR>")
+  call term_sendkeys(buf, ":redraw\<CR>")
+  call VerifyScreenDump(buf, 'Test_popupwin_05', {})
+
   " clean up
   call StopVimInTerminal(buf)
   call delete('XtestPopup')
@@ -56,6 +61,7 @@
   call popup_create('world', {
 	\ 'line': 1,
 	\ 'col': 1,
+	\ 'minwidth': 20,
 	\ 'time': 500,
 	\})
   redraw
@@ -70,6 +76,7 @@
   call popup_create('on the command line', {
 	\ 'line': &lines,
 	\ 'col': 10,
+	\ 'minwidth': 20,
 	\ 'time': 500,
 	\})
   redraw
@@ -91,6 +98,7 @@
   let winid = popup_create('world', {
 	\ 'line': 1,
 	\ 'col': 1,
+	\ 'minwidth': 20,
 	\})
   redraw
   let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
@@ -121,3 +129,33 @@
 
   bwipe!
 endfunc
+
+func Test_popup_move()
+  topleft vnew
+  call setline(1, 'hello')
+
+  let winid = popup_create('world', {
+	\ 'line': 1,
+	\ 'col': 1,
+	\ 'minwidth': 20,
+	\})
+  redraw
+  let line = join(map(range(1, 6), 'screenstring(1, v:val)'), '')
+  call assert_equal('world ', line)
+
+  call popup_move(winid, {'line': 2, 'col': 2})
+  redraw
+  let line = join(map(range(1, 6), 'screenstring(1, v:val)'), '')
+  call assert_equal('hello ', line)
+  let line = join(map(range(1, 6), 'screenstring(2, v:val)'), '')
+  call assert_equal('~world', line)
+
+  call popup_move(winid, {'line': 1})
+  redraw
+  let line = join(map(range(1, 6), 'screenstring(1, v:val)'), '')
+  call assert_equal('hworld', line)
+
+  call popup_close(winid)
+
+  bwipe!
+endfunc
diff --git a/src/version.c b/src/version.c
index b006864..1fa7db4 100644
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1410,
+/**/
     1409,
 /**/
     1408,