patch 8.1.1559: popup window title property not implemented yet

Problem:    Popup window title property not implemented yet.
Solution:   Implement the title property.
diff --git a/src/popupwin.c b/src/popupwin.c
index ccf1f66..3603a1c 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -294,6 +294,13 @@
 	set_string_option_direct_in_win(wp, (char_u *)"wincolor", -1,
 						   str, OPT_FREE|OPT_LOCAL, 0);
 
+    str = dict_get_string(dict, (char_u *)"title", FALSE);
+    if (str != NULL)
+    {
+	vim_free(wp->w_popup_title);
+	wp->w_popup_title = vim_strsave(str);
+    }
+
     wp->w_firstline = dict_get_number(dict, (char_u *)"firstline");
     if (wp->w_firstline < 1)
 	wp->w_firstline = 1;
@@ -532,6 +539,19 @@
 }
 
 /*
+ * Get the padding plus border at the top, adjusted to 1 if there is a title.
+ */
+    static int
+popup_top_extra(win_T *wp)
+{
+    int	extra = wp->w_popup_border[0] + wp->w_popup_padding[0];
+
+    if (extra == 0 && wp->w_popup_title != NULL && *wp->w_popup_title != NUL)
+	return 1;
+    return extra;
+}
+
+/*
  * Adjust the position and size of the popup to fit on the screen.
  */
     void
@@ -543,7 +563,7 @@
     int		center_vert = FALSE;
     int		center_hor = FALSE;
     int		allow_adjust_left = !wp->w_popup_fixed;
-    int		top_extra = wp->w_popup_border[0] + wp->w_popup_padding[0];
+    int		top_extra = popup_top_extra(wp);
     int		right_extra = wp->w_popup_border[1] + wp->w_popup_padding[1];
     int		bot_extra = wp->w_popup_border[2] + wp->w_popup_padding[2];
     int		left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3];
@@ -553,6 +573,7 @@
     int		org_wincol = wp->w_wincol;
     int		org_width = wp->w_width;
     int		org_height = wp->w_height;
+    int		minwidth;
 
     wp->w_winrow = 0;
     wp->w_wincol = 0;
@@ -646,8 +667,17 @@
 	    break;
     }
 
-    if (wp->w_minwidth > 0 && wp->w_width < wp->w_minwidth)
-	wp->w_width = wp->w_minwidth;
+    minwidth = wp->w_minwidth;
+    if (wp->w_popup_title != NULL && *wp->w_popup_title != NUL)
+    {
+	int title_len = vim_strsize(wp->w_popup_title) + 2 - extra_width;
+
+	if (minwidth < title_len)
+	    minwidth = title_len;
+    }
+
+    if (minwidth > 0 && wp->w_width < minwidth)
+	wp->w_width = minwidth;
     if (wp->w_width > maxwidth)
 	wp->w_width = maxwidth;
     if (center_hor)
@@ -1384,7 +1414,7 @@
     {
 	if (wp == NULL)
 	    return;  // invalid {id}
-	top_extra = wp->w_popup_border[0] + wp->w_popup_padding[0];
+	top_extra = popup_top_extra(wp);
 	left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3];
 
 	dict = rettv->vval.v_dict;
@@ -1750,6 +1780,7 @@
     int	    left_off;
     int	    total_width;
     int	    total_height;
+    int	    top_padding;
     int	    popup_attr;
     int	    border_attr[4];
     int	    border_char[8];
@@ -1770,7 +1801,7 @@
 
 	// 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];
+	top_off = popup_top_extra(wp);
 	left_off = wp->w_popup_padding[3] + wp->w_popup_border[3];
 	wp->w_winrow += top_off;
 	wp->w_wincol += left_off;
@@ -1783,7 +1814,7 @@
 
 	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]
+	total_height = popup_top_extra(wp)
 		+ wp->w_height + wp->w_popup_padding[2] + wp->w_popup_border[2];
 	popup_attr = get_wcr_attr(wp);
 
@@ -1816,6 +1847,7 @@
 		border_attr[i] = syn_name2attr(wp->w_border_highlight[i]);
 	}
 
+	top_padding = wp->w_popup_padding[0];
 	if (wp->w_popup_border[0] > 0)
 	{
 	    // top border
@@ -1832,17 +1864,24 @@
 			       wp->w_wincol + total_width - 1, border_attr[1]);
 	    }
 	}
+	else if (wp->w_popup_padding[0] == 0 && popup_top_extra(wp) > 0)
+	    top_padding = 1;
 
-	if (wp->w_popup_padding[0] > 0)
+	if (top_padding > 0)
 	{
 	    // top padding
 	    row = wp->w_winrow + wp->w_popup_border[0];
-	    screen_fill(row, row + wp->w_popup_padding[0],
+	    screen_fill(row, row + top_padding,
 		    wp->w_wincol + wp->w_popup_border[3],
 		    wp->w_wincol + total_width - wp->w_popup_border[1],
 							 ' ', ' ', popup_attr);
 	}
 
+	// Title goes on top of border or padding.
+	if (wp->w_popup_title != NULL)
+	    screen_puts(wp->w_popup_title, wp->w_winrow, wp->w_wincol + 1,
+		    wp->w_popup_border[0] > 0 ? border_attr[0] : popup_attr);
+
 	for (row = wp->w_winrow + wp->w_popup_border[0];
 		row < wp->w_winrow + total_height - wp->w_popup_border[2];
 		    ++row)
diff --git a/src/structs.h b/src/structs.h
index 09158a0..5787d2b 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -2891,6 +2891,7 @@
     pos_save_T	w_save_cursor;	    /* backup of cursor pos and topline */
 #ifdef FEAT_TEXT_PROP
     int		w_popup_flags;	    // POPF_ values
+    char_u	*w_popup_title;
     poppos_T	w_popup_pos;
     int		w_popup_fixed;	    // do not shift popup to fit on screen
     int		w_zindex;
diff --git a/src/testdir/dumps/Test_popupwin_menu_01.dump b/src/testdir/dumps/Test_popupwin_menu_01.dump
index debd962..479edf6 100644
--- a/src/testdir/dumps/Test_popupwin_menu_01.dump
+++ b/src/testdir/dumps/Test_popupwin_menu_01.dump
@@ -1,10 +1,10 @@
 >1+0&#ffffff0| @73
-|2| @30|╔+0#0000001#ffd7ff255|═@8|╗| +0#0000000#ffffff0@31
-|3| @30|║+0#0000001#ffd7ff255| @8|║| +0#0000000#ffffff0@31
-|4| @30|║+0#0000001#ffd7ff255| |o+0#0000000#5fd7ff255|n|e| +0#0000001#ffd7ff255@4|║| +0#0000000#ffffff0@31
-|5| @30|║+0#0000001#ffd7ff255| |t|w|o| @4|║| +0#0000000#ffffff0@31
-|6| @30|║+0#0000001#ffd7ff255| |a|n|o|t|h|e|r| |║| +0#0000000#ffffff0@31
-|7| @30|║+0#0000001#ffd7ff255| @8|║| +0#0000000#ffffff0@31
-|8| @30|╚+0#0000001#ffd7ff255|═@8|╝| +0#0000000#ffffff0@31
+|2| @20|╔+0#0000001#ffd7ff255| |m|a|k|e| |a| |c|h|o|i|c|e| |f|r|o|m| |t|h|e| |l|i|s|t| |╗| +0#0000000#ffffff0@21
+|3| @20|║+0#0000001#ffd7ff255| @28|║| +0#0000000#ffffff0@21
+|4| @20|║+0#0000001#ffd7ff255| |o+0#0000000#5fd7ff255|n|e| +0#0000001#ffd7ff255@24|║| +0#0000000#ffffff0@21
+|5| @20|║+0#0000001#ffd7ff255| |t|w|o| @24|║| +0#0000000#ffffff0@21
+|6| @20|║+0#0000001#ffd7ff255| |a|n|o|t|h|e|r| @20|║| +0#0000000#ffffff0@21
+|7| @20|║+0#0000001#ffd7ff255| @28|║| +0#0000000#ffffff0@21
+|8| @20|╚+0#0000001#ffd7ff255|═@28|╝| +0#0000000#ffffff0@21
 |9| @73
 @57|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/dumps/Test_popupwin_menu_02.dump b/src/testdir/dumps/Test_popupwin_menu_02.dump
index 2495edf..4a407d4 100644
--- a/src/testdir/dumps/Test_popupwin_menu_02.dump
+++ b/src/testdir/dumps/Test_popupwin_menu_02.dump
@@ -1,10 +1,10 @@
 >1+0&#ffffff0| @73
-|2| @30|╔+0#0000001#ffd7ff255|═@8|╗| +0#0000000#ffffff0@31
-|3| @30|║+0#0000001#ffd7ff255| @8|║| +0#0000000#ffffff0@31
-|4| @30|║+0#0000001#ffd7ff255| |o|n|e| @4|║| +0#0000000#ffffff0@31
-|5| @30|║+0#0000001#ffd7ff255| |t|w|o| @4|║| +0#0000000#ffffff0@31
-|6| @30|║+0#0000001#ffd7ff255| |a+0#0000000#5fd7ff255|n|o|t|h|e|r| +0#0000001#ffd7ff255|║| +0#0000000#ffffff0@31
-|7| @30|║+0#0000001#ffd7ff255| @8|║| +0#0000000#ffffff0@31
-|8| @30|╚+0#0000001#ffd7ff255|═@8|╝| +0#0000000#ffffff0@31
+|2| @20|╔+0#0000001#ffd7ff255| |m|a|k|e| |a| |c|h|o|i|c|e| |f|r|o|m| |t|h|e| |l|i|s|t| |╗| +0#0000000#ffffff0@21
+|3| @20|║+0#0000001#ffd7ff255| @28|║| +0#0000000#ffffff0@21
+|4| @20|║+0#0000001#ffd7ff255| |o|n|e| @24|║| +0#0000000#ffffff0@21
+|5| @20|║+0#0000001#ffd7ff255| |t|w|o| @24|║| +0#0000000#ffffff0@21
+|6| @20|║+0#0000001#ffd7ff255| |a+0#0000000#5fd7ff255|n|o|t|h|e|r| +0#0000001#ffd7ff255@20|║| +0#0000000#ffffff0@21
+|7| @20|║+0#0000001#ffd7ff255| @28|║| +0#0000000#ffffff0@21
+|8| @20|╚+0#0000001#ffd7ff255|═@28|╝| +0#0000000#ffffff0@21
 |9| @73
 @57|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/dumps/Test_popupwin_title.dump b/src/testdir/dumps/Test_popupwin_title.dump
new file mode 100644
index 0000000..ae15279
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_title.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @28| +0#0000001#ffd7ff255|T|i|t|l|e| |S|t|r|i|n|g| | +0#0000000#ffffff0@30
+|5| @28|o+0#0000001#ffd7ff255|n|e| @10| +0#0000000#ffffff0@30
+|6| @28|t+0#0000001#ffd7ff255|w|o| @10| +0#0000000#ffffff0@30
+|7| @28|a+0#0000001#ffd7ff255|n|o|t|h|e|r| @6| +0#0000000#ffffff0@30
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index 477cda3..3f5c9d1 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -940,7 +940,7 @@
   let lines =<< trim END
 	call setline(1, range(1, 20))
 	hi PopupSelected ctermbg=lightblue
-	call popup_menu(['one', 'two', 'another'], {'callback': 'MenuDone'})
+	call popup_menu(['one', 'two', 'another'], {'callback': 'MenuDone', 'title': ' make a choice from the list '})
 	func MenuDone(id, res)
 	  echomsg "selected " .. a:res
 	endfunc
@@ -960,6 +960,26 @@
   call delete('XtestPopupMenu')
 endfunc
 
+func Test_popup_title()
+  if !CanRunVimInTerminal()
+    throw 'Skipped: cannot make screendumps'
+  endif
+
+  " Create a popup without title or border, a line of padding will be added to
+  " put the title on.
+  let lines =<< trim END
+	call setline(1, range(1, 20))
+	call popup_create(['one', 'two', 'another'], {'title': 'Title String'})
+  END
+  call writefile(lines, 'XtestPopupTitle')
+  let buf = RunVimInTerminal('-S XtestPopupTitle', {'rows': 10})
+  call VerifyScreenDump(buf, 'Test_popupwin_title', {})
+
+  " clean up
+  call StopVimInTerminal(buf)
+  call delete('XtestPopupTitle')
+endfunc
+
 func Test_popup_close_callback()
   func PopupDone(id, result)
     let g:result = a:result
diff --git a/src/version.c b/src/version.c
index a59803b..ad09b56 100644
--- a/src/version.c
+++ b/src/version.c
@@ -778,6 +778,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1559,
+/**/
     1558,
 /**/
     1557,
diff --git a/src/window.c b/src/window.c
index ee616ee..7f8bb78 100644
--- a/src/window.c
+++ b/src/window.c
@@ -4857,6 +4857,7 @@
     free_callback(&wp->w_filter_cb);
     for (i = 0; i < 4; ++i)
 	VIM_CLEAR(wp->w_border_highlight[i]);
+    vim_free(wp->w_popup_title);
 #endif
 
 #ifdef FEAT_SYN_HL