patch 8.1.1443: popup window padding and border not implemented yet

Problem:    Popup window padding and border not implemented yet.
Solution:   Implement padding and border.  Add core position and size to
            popup_getpos().
diff --git a/src/popupwin.c b/src/popupwin.c
index 620ef05..8052ca5 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -101,6 +101,38 @@
     }
 }
 
+    static void
+get_padding_border(dict_T *dict, int *array, char *name, int max_val)
+{
+    dictitem_T	*di;
+
+    vim_memset(array, 0, sizeof(int) * 4);
+    di = dict_find(dict, (char_u *)name, -1);
+    if (di != NULL)
+    {
+	if (di->di_tv.v_type != VAR_LIST)
+	    emsg(_(e_listreq));
+	else
+	{
+	    list_T	*list = di->di_tv.vval.v_list;
+	    listitem_T	*li;
+	    int		i;
+	    int		nr;
+
+	    for (i = 0; i < 4; ++i)
+		array[i] = 1;
+	    if (list != NULL)
+		for (i = 0, li = list->lv_first; i < 4 && i < list->lv_len;
+							 ++i, li = li->li_next)
+		{
+		    nr = (int)tv_get_number(&li->li_tv);
+		    if (nr >= 0)
+			array[i] = nr > max_val ? max_val : nr;
+		}
+	}
+    }
+}
+
 /*
  * Go through the options in "dict" and apply them to buffer "buf" displayed in
  * popup window "wp".
@@ -176,6 +208,9 @@
 	if (callback.cb_name != NULL)
 	    set_callback(&wp->w_filter_cb, &callback);
     }
+
+    get_padding_border(dict, wp->w_popup_padding, "padding", 999);
+    get_padding_border(dict, wp->w_popup_border, "border", 1);
 }
 
 /*
@@ -700,16 +735,28 @@
     dict_T	*dict;
     int		id = (int)tv_get_number(argvars);
     win_T	*wp = find_popup_win(id);
+    int		top_extra;
+    int		left_extra;
 
     if (rettv_dict_alloc(rettv) == OK)
     {
 	if (wp == NULL)
 	    return;  // invalid {id}
+	top_extra = wp->w_popup_border[0] + wp->w_popup_padding[0];
+	left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3];
+
 	dict = rettv->vval.v_dict;
+
 	dict_add_number(dict, "line", wp->w_winrow + 1);
 	dict_add_number(dict, "col", wp->w_wincol + 1);
-	dict_add_number(dict, "width", wp->w_width);
-	dict_add_number(dict, "height", wp->w_height);
+	dict_add_number(dict, "width", wp->w_width + left_extra + wp->w_popup_border[1] + wp->w_popup_padding[1]);
+	dict_add_number(dict, "height", wp->w_height + top_extra + wp->w_popup_border[2] + wp->w_popup_padding[2]);
+
+	dict_add_number(dict, "core_line", wp->w_winrow + 1 + top_extra);
+	dict_add_number(dict, "core_col", wp->w_wincol + 1 + left_extra);
+	dict_add_number(dict, "core_width", wp->w_width);
+	dict_add_number(dict, "core_height", wp->w_height);
+
 	dict_add_number(dict, "visible",
 				       (wp->w_popup_flags & POPF_HIDDEN) == 0);
     }
diff --git a/src/screen.c b/src/screen.c
index 304c8c6..bff4926 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -991,28 +991,6 @@
 }
 #endif
 
-#ifdef FEAT_TEXT_PROP
-    static void
-update_popups(void)
-{
-    win_T   *wp;
-
-    // Find the window with the lowest zindex that hasn't been updated yet,
-    // so that the window with a higher zindex is drawn later, thus goes on
-    // top.
-    // TODO: don't redraw every popup every time.
-    popup_reset_handled();
-    while ((wp = find_next_popup(TRUE)) != NULL)
-    {
-	// Recompute the position if the text changed.
-	if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
-	    popup_adjust_position(wp);
-
-	win_update(wp);
-    }
-}
-#endif
-
 /*
  * Get 'wincolor' attribute for window "wp".  If not set and "wp" is a popup
  * window then get the "Pmenu" highlight attribute.
@@ -1031,6 +1009,132 @@
     return wcr_attr;
 }
 
+#ifdef FEAT_TEXT_PROP
+/*
+ * Return a string of "len" spaces in IObuff.
+ */
+    static char_u *
+get_spaces(int len)
+{
+    vim_memset(IObuff, ' ', (size_t)len);
+    IObuff[len] = NUL;
+    return IObuff;
+}
+
+    static void
+update_popups(void)
+{
+    win_T   *wp;
+    int	    top_off;
+    int	    left_off;
+    int	    total_width;
+    int	    total_height;
+    int	    popup_attr;
+    int	    row;
+
+    // Find the window with the lowest zindex that hasn't been updated yet,
+    // so that the window with a higher zindex is drawn later, thus goes on
+    // top.
+    // TODO: don't redraw every popup every time.
+    popup_reset_handled();
+    while ((wp = find_next_popup(TRUE)) != NULL)
+    {
+	// Recompute the position if the text changed.
+	if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
+	    popup_adjust_position(wp);
+
+	// adjust w_winrow and w_wincol for border and padding, since
+	// win_update() doesn't handle them.
+	top_off = wp->w_popup_padding[0] + wp->w_popup_border[0];
+	left_off = wp->w_popup_padding[3] + wp->w_popup_border[3];
+	wp->w_winrow += top_off;
+	wp->w_wincol += left_off;
+
+	// Draw the popup text.
+	win_update(wp);
+
+	wp->w_winrow -= top_off;
+	wp->w_wincol -= left_off;
+
+	total_width = wp->w_popup_border[3] + wp->w_popup_padding[3]
+		+ wp->w_width + wp->w_popup_padding[1] + wp->w_popup_border[1];
+	total_height = wp->w_popup_border[0] + wp->w_popup_padding[0]
+		+ wp->w_height + wp->w_popup_padding[2] + wp->w_popup_border[2];
+	popup_attr = get_wcr_attr(wp);
+
+	if (wp->w_popup_border[0] > 0)
+	{
+	    // top border
+	    screen_fill(wp->w_winrow, wp->w_winrow + 1,
+		    wp->w_wincol,
+		    wp->w_wincol + total_width,
+		    wp->w_popup_border[3] != 0 ? '+' : '-',
+		    '-', popup_attr);
+	    if (wp->w_popup_border[1] > 0)
+		screen_puts((char_u *)"+", wp->w_winrow,
+			wp->w_wincol + total_width - 1, popup_attr);
+	}
+
+	if (wp->w_popup_padding[0] > 0)
+	{
+	    // top padding
+	    row = wp->w_winrow + wp->w_popup_border[0];
+	    screen_fill(row, row + wp->w_popup_padding[0],
+		    wp->w_wincol + wp->w_popup_border[3],
+		    wp->w_wincol + total_width - wp->w_popup_border[1],
+							 ' ', ' ', popup_attr);
+	}
+
+	for (row = wp->w_winrow + wp->w_popup_border[0];
+		row < wp->w_winrow + total_height - wp->w_popup_border[2];
+		    ++row)
+	{
+	    // left border
+	    if (wp->w_popup_border[3] > 0)
+		screen_puts((char_u *)"|", row, wp->w_wincol, popup_attr);
+	    // left padding
+	    if (wp->w_popup_padding[3] > 0)
+		screen_puts(get_spaces(wp->w_popup_padding[3]), row,
+			wp->w_wincol + wp->w_popup_border[3], popup_attr);
+	    // right border
+	    if (wp->w_popup_border[1] > 0)
+		screen_puts((char_u *)"|", row,
+			wp->w_wincol + total_width - 1, popup_attr);
+	    // right padding
+	    if (wp->w_popup_padding[1] > 0)
+		screen_puts(get_spaces(wp->w_popup_padding[1]), row,
+			wp->w_wincol + wp->w_popup_border[3]
+			   + wp->w_popup_padding[3] + wp->w_width, popup_attr);
+	}
+
+	if (wp->w_popup_padding[2] > 0)
+	{
+	    // bottom padding
+	    row = wp->w_winrow + wp->w_popup_border[0]
+				       + wp->w_popup_padding[0] + wp->w_height;
+	    screen_fill(row, row + wp->w_popup_padding[2],
+		    wp->w_wincol + wp->w_popup_border[3],
+		    wp->w_wincol + total_width - wp->w_popup_border[1],
+							 ' ', ' ', popup_attr);
+	}
+
+	if (wp->w_popup_border[2] > 0)
+	{
+	    // bottom border
+	    row = wp->w_winrow + total_height - 1;
+	    screen_fill(row , row + 1,
+		    wp->w_wincol,
+		    wp->w_wincol + total_width,
+		    wp->w_popup_border[3] != 0 ? '+' : '-',
+		    '-', popup_attr);
+	    if (wp->w_popup_border[1] > 0)
+		screen_puts((char_u *)"+", row,
+			wp->w_wincol + total_width - 1, popup_attr);
+	}
+    }
+}
+#endif
+
 #if defined(FEAT_GUI) || defined(PROTO)
 /*
  * Update a single window, its status line and maybe the command line msg.
diff --git a/src/structs.h b/src/structs.h
index 9589b67..fb27745 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -2888,6 +2888,8 @@
     int		w_maxwidth;	    // "maxwidth" for popup window
     int		w_wantline;	    // "line" for popup window
     int		w_wantcol;	    // "col" for popup window
+    int		w_popup_padding[4]; // popup padding top/right/bot/left
+    int		w_popup_border[4];  // popup border top/right/bot/left
     varnumber_T	w_popup_last_changedtick; // b:changedtick when position was
 					  // computed
     callback_T	w_filter_cb;	    // popup filter callback
diff --git a/src/testdir/dumps/Test_popupwin_20.dump b/src/testdir/dumps/Test_popupwin_20.dump
new file mode 100644
index 0000000..237b1de
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_20.dump
@@ -0,0 +1,15 @@
+>1+0&#ffffff0| @73
+|2| |++0#0000001#ffd7ff255|-@11|+| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@14| +0#0000000#ffffff0@4|++0#0000001#ffd7ff255|-@11|+| +0#0000000#ffffff0@18
+|3| ||+0#0000001#ffd7ff255|h|e|l@1|o| |b|o|r|d|e|r||| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255|h|e|l@1|o| |p|a|d@1|i|n|g| | +0#0000000#ffffff0@4||+0#0000001#ffd7ff255| @11||| +0#0000000#ffffff0@18
+|4| |++0#0000001#ffd7ff255|-@11|+| +0#0000000#ffffff0@5| +0#0000001#ffd7ff255@14| +0#0000000#ffffff0@4||+0#0000001#ffd7ff255| |h|e|l@1|o| |b|o|t|h| ||| +0#0000000#ffffff0@18
+|5| @40||+0#0000001#ffd7ff255| @11||| +0#0000000#ffffff0@18
+|6| |++0#0000001#ffd7ff255|-@8| +0#0000000#ffffff0@9| +0#0000001#ffd7ff255@14| +0#0000000#ffffff0@4|++0#0000001#ffd7ff255|-@11|+| +0#0000000#ffffff0@18
+|7| ||+0#0000001#ffd7ff255|b|o|r|d|e|r| |T|L| +0#0000000#ffffff0@9| +0#0000001#ffd7ff255@3|p|a|d@1|i|n|g|s| @2| +0#0000000#ffffff0@37
+|8| @20| +0#0000001#ffd7ff255@14| +0#0000000#ffffff0@37
+|9| @20| +0#0000001#ffd7ff255@14| +0#0000000#ffffff0@37
+|1|0| @72
+|1@1| @72
+|1|2| @72
+|1|3| @72
+|1|4| @72
+@57|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index b9d6a06..795a1be 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -56,6 +56,54 @@
   call delete('XtestPopup')
 endfunc
 
+func Test_popup_with_border_and_padding()
+  if !CanRunVimInTerminal()
+    return
+  endif
+  call writefile([
+	\ "call setline(1, range(1, 100))",
+	\ "call popup_create('hello border', {'line': 2, 'col': 3, 'border': []})",
+	\ "call popup_create('hello padding', {'line': 2, 'col': 23, 'padding': []})",
+	\ "call popup_create('hello both', {'line': 2, 'col': 43, 'border': [], 'padding': []})",
+	\ "call popup_create('border TL', {'line': 6, 'col': 3, 'border': [1, 0, 0, 4]})",
+	\ "call popup_create('paddings', {'line': 6, 'col': 23, 'padding': [1, 3, 2, 4]})",
+	\], 'XtestPopupBorder')
+  let buf = RunVimInTerminal('-S XtestPopupBorder', {'rows': 15})
+  call VerifyScreenDump(buf, 'Test_popupwin_20', {})
+
+  " clean up
+  call StopVimInTerminal(buf)
+  call delete('XtestPopupBorder')
+
+  let with_border_or_padding = {
+	\ 'line': 2,
+	\ 'core_line': 3,
+	\ 'col': 3,
+	\ 'core_col': 4,
+	\ 'width': 14,
+	\ 'core_width': 12,
+	\ 'height': 3,
+	\ 'core_height': 1,
+	\ 'visible': 1}
+  let winid = popup_create('hello border', {'line': 2, 'col': 3, 'border': []})",
+  call assert_equal(with_border_or_padding, popup_getpos(winid))
+
+  let winid = popup_create('hello paddng', {'line': 2, 'col': 3, 'padding': []})
+  call assert_equal(with_border_or_padding, popup_getpos(winid))
+
+  let winid = popup_create('hello both', {'line': 3, 'col': 8, 'border': [], 'padding': []})
+  call assert_equal({
+	\ 'line': 3,
+	\ 'core_line': 5,
+	\ 'col': 8,
+	\ 'core_col': 10,
+	\ 'width': 14,
+	\ 'core_width': 10,
+	\ 'height': 5,
+	\ 'core_height': 1,
+	\ 'visible': 1}, popup_getpos(winid))
+endfunc
+
 func Test_popup_with_syntax_win_execute()
   if !CanRunVimInTerminal()
     return
diff --git a/src/version.c b/src/version.c
index f2602d6..51dcae8 100644
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1443,
+/**/
     1442,
 /**/
     1441,