patch 8.1.1428: popup_atcursor() not implemented yet

Problem:    Popup_atcursor() not implemented yet.
Solution:   Implement it. (Yasuhiro Matsumoto, closes #4456)
diff --git a/src/evalfunc.c b/src/evalfunc.c
index d1e89eb..a6703d1 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -809,6 +809,7 @@
     {"perleval",	1, 1, f_perleval},
 #endif
 #ifdef FEAT_TEXT_PROP
+    {"popup_atcursor",	2, 2, f_popup_atcursor},
     {"popup_close",	1, 1, f_popup_close},
     {"popup_create",	2, 2, f_popup_create},
     {"popup_getoptions", 1, 1, f_popup_getoptions},
diff --git a/src/popupwin.c b/src/popupwin.c
index ee59af2..9e9bf27 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -16,11 +16,55 @@
 #ifdef FEAT_TEXT_PROP
 
 /*
+ * Get option value for"key", which is "line" or "col".
+ * Handles "cursor+N" and "cursor-N".
+ */
+    static int
+popup_options_pos(dict_T *dict, char_u *key)
+{
+    dictitem_T	*di;
+    char_u	*val;
+    char_u	*s;
+    char_u	*endp;
+    int		n = 0;
+
+    di = dict_find(dict, key, -1);
+    if (di == NULL)
+	return 0;
+
+    val = tv_get_string(&di->di_tv);
+    if (STRNCMP(val, "cursor", 6) != 0)
+	return dict_get_number(dict, key);
+
+    setcursor_mayforce(TRUE);
+    s = val + 6;
+    if (*s != NUL)
+    {
+	n = strtol((char *)s, (char **)&endp, 10);
+	if (endp != NULL && *skipwhite(endp) != NUL)
+	{
+	    semsg(_(e_invexpr2), val);
+	    return 0;
+	}
+    }
+
+    if (STRCMP(key, "line") == 0)
+	n = screen_screenrow() + 1 + n;
+    else // "col"
+	n = screen_screencol() + 1 + n;
+
+    if (n < 1)
+	n = 1;
+    return n;
+}
+
+/*
  * Go through the options in "dict" and apply them to buffer "buf" displayed in
  * popup window "wp".
+ * When called from f_popup_atcursor() "atcursor" is TRUE.
  */
     static void
-apply_options(win_T *wp, buf_T *buf UNUSED, dict_T *dict)
+apply_options(win_T *wp, buf_T *buf UNUSED, dict_T *dict, int atcursor)
 {
     int	    nr;
     char_u  *str;
@@ -30,8 +74,19 @@
     wp->w_maxwidth = dict_get_number(dict, (char_u *)"maxwidth");
     wp->w_maxheight = dict_get_number(dict, (char_u *)"maxheight");
 
-    wp->w_wantline = dict_get_number(dict, (char_u *)"line");
-    wp->w_wantcol = dict_get_number(dict, (char_u *)"col");
+    if (atcursor)
+    {
+	setcursor_mayforce(TRUE);
+	wp->w_wantline = screen_screenrow();
+	wp->w_wantcol = screen_screencol() + 1;
+    }
+
+    nr = popup_options_pos(dict, (char_u *)"line");
+    if (nr > 0)
+	wp->w_wantline = nr;
+    nr = popup_options_pos(dict, (char_u *)"col");
+    if (nr > 0)
+	wp->w_wantcol = nr;
 
     wp->w_zindex = dict_get_number(dict, (char_u *)"zindex");
 
@@ -215,9 +270,11 @@
 
 /*
  * popup_create({text}, {options})
+ * popup_atcursor({text}, {options})
+ * When called from f_popup_atcursor() "atcursor" is TRUE.
  */
-    void
-f_popup_create(typval_T *argvars, typval_T *rettv)
+    static void
+popup_create(typval_T *argvars, typval_T *rettv, int atcursor)
 {
     win_T   *wp;
     buf_T   *buf;
@@ -309,7 +366,7 @@
     curbuf = curwin->w_buffer;
 
     // Deal with options.
-    apply_options(wp, buf, argvars[1].vval.v_dict);
+    apply_options(wp, buf, argvars[1].vval.v_dict, atcursor);
 
     // set default values
     if (wp->w_zindex == 0)
@@ -323,6 +380,24 @@
 }
 
 /*
+ * popup_create({text}, {options})
+ */
+    void
+f_popup_create(typval_T *argvars, typval_T *rettv)
+{
+    popup_create(argvars, rettv, FALSE);
+}
+
+/*
+ * popup_atcursor({text}, {options})
+ */
+    void
+f_popup_atcursor(typval_T *argvars, typval_T *rettv)
+{
+    popup_create(argvars, rettv, TRUE);
+}
+
+/*
  * Find the popup window with window-ID "id".
  * If the popup window does not exist NULL is returned.
  * If the window is not a popup window, and error message is given.
diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro
index 7337457..404b6cf 100644
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -1,15 +1,16 @@
 /* popupwin.c */
-void popup_adjust_position(win_T *wp);
-void f_popup_create(typval_T *argvars, typval_T *rettv);
 int popup_any_visible(void);
-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 popup_close(int id);
-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);
+void f_popup_atcursor(typval_T *argvars, typval_T *rettv);
+void f_popup_close(typval_T *argvars, typval_T *rettv);
+void f_popup_create(typval_T *argvars, typval_T *rettv);
 void f_popup_getoptions(typval_T *argvars, typval_T *rettv);
 void f_popup_getposition(typval_T *argvars, typval_T *rettv);
+void f_popup_hide(typval_T *argvars, typval_T *rettv);
+void f_popup_move(typval_T *argvars, typval_T *rettv);
+void f_popup_show(typval_T *argvars, typval_T *rettv);
+void popup_adjust_position(win_T *wp);
+void popup_close(int id);
+void popup_close_tabpage(tabpage_T *tp, int id);
 /* vim: set ft=c : */
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index d92d8b1..ee188e3 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -335,3 +335,52 @@
   call popup_close(winid)
   bwipe
 endfunc
+
+func Test_popup_atcursor()
+  topleft vnew
+  call setline(1, [
+  \  'xxxxxxxxxxxxxxxxx',
+  \  'xxxxxxxxxxxxxxxxx',
+  \  'xxxxxxxxxxxxxxxxx',
+  \])
+
+  call cursor(2, 2)
+  redraw
+  let winid = popup_atcursor('vim', {})
+  redraw
+  let line = join(map(range(1, 17), 'screenstring(1, v:val)'), '')
+  call assert_equal('xvimxxxxxxxxxxxxx', line)
+  call popup_close(winid)
+
+  call cursor(3, 4)
+  redraw
+  let winid = popup_atcursor('vim', {})
+  redraw
+  let line = join(map(range(1, 17), 'screenstring(2, v:val)'), '')
+  call assert_equal('xxxvimxxxxxxxxxxx', line)
+  call popup_close(winid)
+
+  call cursor(1, 1)
+  redraw
+  let winid = popup_create('vim', {
+  \ 'line': 'cursor+2',
+  \ 'col': 'cursor+1',
+  \})
+  redraw
+  let line = join(map(range(1, 17), 'screenstring(3, v:val)'), '')
+  call assert_equal('xvimxxxxxxxxxxxxx', line)
+  call popup_close(winid)
+
+  call cursor(3, 3)
+  redraw
+  let winid = popup_create('vim', {
+  \ 'line': 'cursor-2',
+  \ 'col': 'cursor-1',
+  \})
+  redraw
+  let line = join(map(range(1, 17), 'screenstring(1, v:val)'), '')
+  call assert_equal('xvimxxxxxxxxxxxxx', line)
+  call popup_close(winid)
+
+  bwipe!
+endfunc
diff --git a/src/version.c b/src/version.c
index 282d045..6d04157 100644
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1428,
+/**/
     1427,
 /**/
     1426,