patch 8.1.1589: popup window does not indicate scroll position

Problem:    Popup window does not indicate scroll position.
Solution:   Add a scrollbar.
diff --git a/src/popupwin.c b/src/popupwin.c
index 16ec1a2..aa8bba0 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -287,6 +287,10 @@
     if (wp->w_firstline < 1)
 	wp->w_firstline = 1;
 
+    di = dict_find(dict, (char_u *)"scrollbar", -1);
+    if (di != NULL)
+	wp->w_want_scrollbar = dict_get_number(dict, (char_u *)"scrollbar");
+
     str = dict_get_string(dict, (char_u *)"title", FALSE);
     if (str != NULL)
     {
@@ -733,6 +737,9 @@
 	    break;
     }
 
+    wp->w_has_scrollbar = wp->w_want_scrollbar
+	   && (wp->w_topline > 1 || lnum <= wp->w_buffer->b_ml.ml_line_count);
+
     minwidth = wp->w_minwidth;
     if (wp->w_popup_title != NULL && *wp->w_popup_title != NUL)
     {
@@ -1047,6 +1054,7 @@
 	VIM_CLEAR(wp->w_border_highlight[i]);
     for (i = 0; i < 8; ++i)
 	wp->w_border_char[i] = 0;
+    wp->w_want_scrollbar = 1;
 
     // Deal with options.
     apply_options(wp, argvars[1].vval.v_dict);
@@ -1483,6 +1491,7 @@
     dict_T	*dict;
     int		id = (int)tv_get_number(argvars);
     win_T	*wp = find_popup_win(id);
+    linenr_T	old_firstline;
 
     if (wp == NULL)
 	return;  // invalid {id}
@@ -1493,10 +1502,13 @@
 	return;
     }
     dict = argvars[1].vval.v_dict;
+    old_firstline = wp->w_firstline;
 
     apply_move_options(wp, dict);
     apply_general_options(wp, dict);
 
+    if (old_firstline != wp->w_firstline)
+	redraw_win_later(wp, NOT_VALID);
     popup_mask_refresh = TRUE;
     popup_adjust_position(wp);
 }
@@ -1534,6 +1546,7 @@
 	dict_add_number(dict, "core_width", wp->w_width);
 	dict_add_number(dict, "core_height", wp->w_height);
 
+	dict_add_number(dict, "scrollbar", wp->w_has_scrollbar);
 	dict_add_number(dict, "visible",
 		      win_valid(wp) && (wp->w_popup_flags & POPF_HIDDEN) == 0);
     }
@@ -1656,6 +1669,7 @@
 	dict_add_number(dict, "maxheight", wp->w_maxheight);
 	dict_add_number(dict, "maxwidth", wp->w_maxwidth);
 	dict_add_number(dict, "firstline", wp->w_firstline);
+	dict_add_number(dict, "scrollbar", wp->w_want_scrollbar);
 	dict_add_number(dict, "zindex", wp->w_zindex);
 	dict_add_number(dict, "fixed", wp->w_popup_fixed);
 	dict_add_string(dict, "title", wp->w_popup_title);
@@ -2114,6 +2128,10 @@
     char_u  buf[MB_MAXBYTES];
     int	    row;
     int	    i;
+    int	    sb_thumb_top;
+    int	    sb_thumb_height;
+    int	    attr_scroll = highlight_attr[HLF_PSB];
+    int	    attr_thumb = highlight_attr[HLF_PST];
 
     // 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
@@ -2143,7 +2161,8 @@
 	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];
+		+ wp->w_width + wp->w_popup_padding[1] + wp->w_popup_border[1]
+		+ wp->w_has_scrollbar;
 	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);
@@ -2203,7 +2222,8 @@
 	    row = wp->w_winrow + wp->w_popup_border[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],
+		    wp->w_wincol + total_width - wp->w_popup_border[1]
+							- wp->w_has_scrollbar,
 							 ' ', ' ', popup_attr);
 	}
 
@@ -2212,10 +2232,24 @@
 	    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)
+	// Compute scrollbar thumb position and size.
+	if (wp->w_has_scrollbar)
 	{
+	    linenr_T linecount = wp->w_buffer->b_ml.ml_line_count;
+
+	    sb_thumb_height = wp->w_height * wp->w_height / linecount;
+	    if (sb_thumb_height == 0)
+		sb_thumb_height = 1;
+	    sb_thumb_top = ((wp->w_topline * (wp->w_height - sb_thumb_height)
+			    + (linecount - wp->w_height) / 2))
+			      / (linecount - (wp->w_height - sb_thumb_height));
+	}
+
+	for (i = wp->w_popup_border[0];
+				 i < total_height - wp->w_popup_border[2]; ++i)
+	{
+	    row = wp->w_winrow + i;
+
 	    // left border
 	    if (wp->w_popup_border[3] > 0)
 	    {
@@ -2226,6 +2260,21 @@
 	    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);
+	    // scrollbar
+	    if (wp->w_has_scrollbar)
+	    {
+		int line = i - top_off;
+		int scroll_col = wp->w_wincol + total_width - 1
+						       - wp->w_popup_border[1];
+
+		if (line >= 0 && line < wp->w_height)
+		    screen_putchar(' ', row, scroll_col,
+			    line >= sb_thumb_top
+				       && line < sb_thumb_top + sb_thumb_height
+						  ? attr_thumb : attr_scroll);
+		else
+		    screen_putchar(' ', row, scroll_col, popup_attr);
+	    }
 	    // right border
 	    if (wp->w_popup_border[1] > 0)
 	    {
diff --git a/src/structs.h b/src/structs.h
index c664f0e..9992e99 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -2902,6 +2902,8 @@
     int		w_wantline;	    // "line" for popup window
     int		w_wantcol;	    // "col" for popup window
     int		w_firstline;	    // "firstline" for popup window
+    int		w_want_scrollbar;   // when zero don't use a scrollbar
+    int		w_has_scrollbar;    // scrollbar displayed
     int		w_popup_padding[4]; // popup padding top/right/bot/left
     int		w_popup_border[4];  // popup border top/right/bot/left
     char_u	*w_border_highlight[4];  // popup border highlight
diff --git a/src/testdir/dumps/Test_popupwin_firstline.dump b/src/testdir/dumps/Test_popupwin_firstline.dump
index 276f4e5..7efcf3e 100644
--- a/src/testdir/dumps/Test_popupwin_firstline.dump
+++ b/src/testdir/dumps/Test_popupwin_firstline.dump
@@ -1,10 +1,10 @@
 >1+0&#ffffff0| @73
 |2| @73
 |3| @73
-|4| @33|3+0#0000001#ffd7ff255@4| +0#0000000#ffffff0@34
-|5| @33|4+0#0000001#ffd7ff255@1| @2| +0#0000000#ffffff0@34
-|6| @33|5+0#0000001#ffd7ff255| @3| +0#0000000#ffffff0@34
-|7| @33|6+0#0000001#ffd7ff255@4| +0#0000000#ffffff0@34
+|4| @33|3+0#0000001#ffd7ff255@4| +0#0000000#a8a8a8255| +0&#ffffff0@33
+|5| @33|4+0#0000001#ffd7ff255@1| @2| +0#0000000#0000001| +0&#ffffff0@33
+|6| @33|5+0#0000001#ffd7ff255| @3| +0#0000000#a8a8a8255| +0&#ffffff0@33
+|7| @33|6+0#0000001#ffd7ff255@4| +0#0000000#a8a8a8255| +0&#ffffff0@33
 |8| @73
 |9| @73
 @57|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/dumps/Test_popupwin_scroll_1.dump b/src/testdir/dumps/Test_popupwin_scroll_1.dump
new file mode 100644
index 0000000..dd0e0b3
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_1.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|o+0#0000001#ffd7ff255|n|e| @4| +0#0000000#0000001| +0&#ffffff0@32
+|5| @31|t+0#0000001#ffd7ff255|w|o| @4| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|6| @31|t+0#0000001#ffd7ff255|h|r|e@1| @2| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|7| @31|f+0#0000001#ffd7ff255|o|u|r| @3| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|8| @73
+|9| @73
+@57|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/dumps/Test_popupwin_scroll_2.dump b/src/testdir/dumps/Test_popupwin_scroll_2.dump
new file mode 100644
index 0000000..21d33da
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_2.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|t+0#0000001#ffd7ff255|w|o| @4| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|5| @31|t+0#0000001#ffd7ff255|h|r|e@1| @2| +0#0000000#0000001| +0&#ffffff0@32
+|6| @31|f+0#0000001#ffd7ff255|o|u|r| @3| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|7| @31|f+0#0000001#ffd7ff255|i|v|e| @3| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|8| @73
+|9| @73
+|:|c|a|l@1| |p|o|p|u|p|_|s|e|t|o|p|t|i|o|n|s|(|w|i|n|i|d|,| |{|'|f|i|r|s|t|l|i|n|e|'|:| |2|}|)| @9|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/dumps/Test_popupwin_scroll_3.dump b/src/testdir/dumps/Test_popupwin_scroll_3.dump
new file mode 100644
index 0000000..656e058
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_3.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @31|s+0#0000001#ffd7ff255|i|x| @4| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|5| @31|s+0#0000001#ffd7ff255|e|v|e|n| @2| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|6| @31|e+0#0000001#ffd7ff255|i|g|h|t| @2| +0#0000000#a8a8a8255| +0&#ffffff0@32
+|7| @31|n+0#0000001#ffd7ff255|i|n|e| @3| +0#0000000#0000001| +0&#ffffff0@32
+|8| @73
+|9| @73
+|:|c|a|l@1| |p|o|p|u|p|_|s|e|t|o|p|t|i|o|n|s|(|w|i|n|i|d|,| |{|'|f|i|r|s|t|l|i|n|e|'|:| |6|}|)| @9|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/dumps/Test_popupwin_scroll_4.dump b/src/testdir/dumps/Test_popupwin_scroll_4.dump
new file mode 100644
index 0000000..5f35a1f
--- /dev/null
+++ b/src/testdir/dumps/Test_popupwin_scroll_4.dump
@@ -0,0 +1,10 @@
+>1+0&#ffffff0| @73
+|2| @73
+|3| @73
+|4| @73
+|5| @31|n+0#0000001#ffd7ff255|i|n|e| @3| +0#0000000#0000001| +0&#ffffff0@32
+|6| @73
+|7| @73
+|8| @73
+|9| @73
+|:|c|a|l@1| |p|o|p|u|p|_|s|e|t|o|p|t|i|o|n|s|(|w|i|n|i|d|,| |{|'|f|i|r|s|t|l|i|n|e|'|:| |9|}|)| @9|1|,|1| @10|T|o|p| 
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index b4bfcef..1e8f522 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -1407,12 +1407,42 @@
   call term_sendkeys(buf, ":call popup_notification('another important notification', {})\<CR>")
   call VerifyScreenDump(buf, 'Test_popupwin_notify_02', {})
 
-
   " clean up
   call StopVimInTerminal(buf)
   call delete('XtestNotifications')
 endfunc
 
+func Test_popup_scrollbar()
+  if !CanRunVimInTerminal()
+    throw 'Skipped: cannot make screendumps'
+  endif
+
+  let lines =<< trim END
+    call setline(1, range(1, 20))
+    let winid = popup_create(['one', 'two', 'three', 'four', 'five',
+	  \ 'six', 'seven', 'eight', 'nine'], {
+	  \ 'minwidth': 8,
+	  \ 'maxheight': 4,
+	  \ })
+  END
+  call writefile(lines, 'XtestPopupScroll')
+  let buf = RunVimInTerminal('-S XtestPopupScroll', {'rows': 10})
+  call VerifyScreenDump(buf, 'Test_popupwin_scroll_1', {})
+
+  call term_sendkeys(buf, ":call popup_setoptions(winid, {'firstline': 2})\<CR>")
+  call VerifyScreenDump(buf, 'Test_popupwin_scroll_2', {})
+
+  call term_sendkeys(buf, ":call popup_setoptions(winid, {'firstline': 6})\<CR>")
+  call VerifyScreenDump(buf, 'Test_popupwin_scroll_3', {})
+
+  call term_sendkeys(buf, ":call popup_setoptions(winid, {'firstline': 9})\<CR>")
+  call VerifyScreenDump(buf, 'Test_popupwin_scroll_4', {})
+
+  " clean up
+  call StopVimInTerminal(buf)
+  call delete('XtestPopupScroll')
+endfunc
+
 func Test_popup_settext()
   if !CanRunVimInTerminal()
     throw 'Skipped: cannot make screendumps'
diff --git a/src/version.c b/src/version.c
index 3f3d77f..34af0de 100644
--- a/src/version.c
+++ b/src/version.c
@@ -778,6 +778,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1589,
+/**/
     1588,
 /**/
     1587,