patch 8.1.1453: popup window "moved" property not implemented yet

Problem:    Popup window "moved" property not implemented yet.
Solution:   Implement it.
diff --git a/runtime/doc/popup.txt b/runtime/doc/popup.txt
index 86476d0..7fa172c 100644
--- a/runtime/doc/popup.txt
+++ b/runtime/doc/popup.txt
@@ -90,7 +90,6 @@
 
 IMPLEMENTATION:
 - Code is in popupwin.c
-- Fix positioning with border and padding.
 - Why does 'nrformats' leak from the popup window buffer???
 - Make redrawing more efficient and avoid flicker.
     First draw popups, creating a mask, use the mask in screen_line() when
@@ -410,13 +409,14 @@
 	zindex		Priority for the popup, default 50.
 	time		Time in milliseconds after which the popup will close.
 			When omitted |popup_close()| must be used.
-	moved		"cell": close the popup if the cursor moved at least
-			one screen cell.
-			"word" allows for moving the cursor within |<cword>|
-			"WORD" allows for moving the cursor within |<cWORD>|
-			a list with two numbers specifies the start and end
-			column outside of which the popup will close
-			{not implemented yet}
+	moved		Specifies to close the popup if the cursor moved:
+			- "any": if the cursor moved at all
+			- "word": if the cursor moved outside |<cword>|
+			- "WORD": if the cursor moved outside |<cWORD>|
+			- [{start}, {end}]: if the cursor moved before column
+			  {start} or after {end}
+			The popup also closes if the cursor moves to another
+			line or to another window.
 	filter		A callback that can filter typed characters, see 
 			|popup-filter|.
 	callback	A callback that is called when the popup closes, e.g.
@@ -510,6 +510,9 @@
 result, which could be an index in the popup lines, or whatever was passed as
 the second argument of `popup_close()`.
 
+If the popup is closed because the cursor moved, the number -1 is passed to
+the callback.
+
 ==============================================================================
 3. Examples						*popup-examples*
 
diff --git a/src/edit.c b/src/edit.c
index 6e4b474..ad25252 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -1456,8 +1456,7 @@
  * inserting sequences of characters (e.g., for CTRL-R).
  */
     void
-ins_redraw(
-    int		ready UNUSED)	    /* not busy with something */
+ins_redraw(int ready)	    // not busy with something
 {
 #ifdef FEAT_CONCEAL
     linenr_T	conceal_old_cursor_line = 0;
@@ -1468,10 +1467,12 @@
     if (char_avail())
 	return;
 
-#if defined(FEAT_CONCEAL)
     /* Trigger CursorMoved if the cursor moved.  Not when the popup menu is
      * visible, the command might delete it. */
     if (ready && (has_cursormovedI()
+# ifdef FEAT_TEXT_PROP
+		|| popup_visible
+# endif
 # if defined(FEAT_CONCEAL)
 		|| curwin->w_p_cole > 0
 # endif
@@ -1497,6 +1498,10 @@
 	    update_curswant();
 	    ins_apply_autocmds(EVENT_CURSORMOVEDI);
 	}
+#ifdef FEAT_TEXT_PROP
+	if (popup_visible)
+	    popup_check_cursor_pos();
+#endif
 # ifdef FEAT_CONCEAL
 	if (curwin->w_p_cole > 0)
 	{
@@ -1507,7 +1512,6 @@
 # endif
 	last_cursormoved = curwin->w_cursor;
     }
-#endif
 
     /* Trigger TextChangedI if b_changedtick differs. */
     if (ready && has_textchangedI()
@@ -3859,7 +3863,7 @@
     if (replace_stack_len <= replace_stack_nr)
     {
 	replace_stack_len += 50;
-	p = alloc(sizeof(char_u) * replace_stack_len);
+	p = ALLOC_MULT(char_u, replace_stack_len);
 	if (p == NULL)	    /* out of memory */
 	{
 	    replace_stack_len -= 50;
diff --git a/src/globals.h b/src/globals.h
index 43ad8e9..4973d31 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -558,24 +558,25 @@
 EXTERN win_T	*firstwin;		/* first window */
 EXTERN win_T	*lastwin;		/* last window */
 EXTERN win_T	*prevwin INIT(= NULL);	/* previous window */
-# define ONE_WINDOW (firstwin == lastwin)
-# define W_NEXT(wp) ((wp)->w_next)
-# define FOR_ALL_WINDOWS(wp) for (wp = firstwin; wp != NULL; wp = wp->w_next)
-# define FOR_ALL_FRAMES(frp, first_frame) \
+#define ONE_WINDOW (firstwin == lastwin)
+#define W_NEXT(wp) ((wp)->w_next)
+#define FOR_ALL_WINDOWS(wp) for (wp = firstwin; wp != NULL; wp = wp->w_next)
+#define FOR_ALL_FRAMES(frp, first_frame) \
     for (frp = first_frame; frp != NULL; frp = frp->fr_next)
-# define FOR_ALL_TABPAGES(tp) for (tp = first_tabpage; tp != NULL; tp = tp->tp_next)
-# define FOR_ALL_WINDOWS_IN_TAB(tp, wp) \
+#define FOR_ALL_TABPAGES(tp) for (tp = first_tabpage; tp != NULL; tp = tp->tp_next)
+#define FOR_ALL_WINDOWS_IN_TAB(tp, wp) \
     for ((wp) = ((tp) == NULL || (tp) == curtab) \
 	    ? firstwin : (tp)->tp_firstwin; (wp); (wp) = (wp)->w_next)
 /*
  * When using this macro "break" only breaks out of the inner loop. Use "goto"
  * to break out of the tabpage loop.
  */
-# define FOR_ALL_TAB_WINDOWS(tp, wp) \
+#define FOR_ALL_TAB_WINDOWS(tp, wp) \
     for ((tp) = first_tabpage; (tp) != NULL; (tp) = (tp)->tp_next) \
 	for ((wp) = ((tp) == curtab) \
 		? firstwin : (tp)->tp_firstwin; (wp); (wp) = (wp)->w_next)
 
+
 EXTERN win_T	*curwin;	/* currently active window */
 
 EXTERN win_T	*aucmd_win;	/* window used in aucmd_prepbuf() */
@@ -1663,4 +1664,5 @@
 
 #ifdef FEAT_TEXT_PROP
 EXTERN int text_prop_frozen INIT(= 0);
+EXTERN int popup_visible INIT(= FALSE);
 #endif
diff --git a/src/gui.c b/src/gui.c
index 8edc86c..c4df7d9 100644
--- a/src/gui.c
+++ b/src/gui.c
@@ -5117,6 +5117,9 @@
 
     /* Trigger CursorMoved if the cursor moved. */
     if (!finish_op && (has_cursormoved()
+# ifdef FEAT_TEXT_PROP
+		|| popup_visible
+# endif
 # ifdef FEAT_CONCEAL
 		|| curwin->w_p_cole > 0
 # endif
@@ -5124,6 +5127,10 @@
     {
 	if (has_cursormoved())
 	    apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, FALSE, curbuf);
+#ifdef FEAT_TEXT_PROP
+	if (popup_visible)
+	    popup_check_cursor_pos();
+#endif
 # ifdef FEAT_CONCEAL
 	if (curwin->w_p_cole > 0)
 	{
diff --git a/src/main.c b/src/main.c
index e1ddbf7..e9165c6 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1159,6 +1159,9 @@
 	    /* Trigger CursorMoved if the cursor moved. */
 	    if (!finish_op && (
 			has_cursormoved()
+#ifdef FEAT_TEXT_PROP
+			|| popup_visible
+#endif
 #ifdef FEAT_CONCEAL
 			|| curwin->w_p_cole > 0
 #endif
@@ -1168,14 +1171,18 @@
 		if (has_cursormoved())
 		    apply_autocmds(EVENT_CURSORMOVED, NULL, NULL,
 							       FALSE, curbuf);
-# ifdef FEAT_CONCEAL
+#ifdef FEAT_TEXT_PROP
+		if (popup_visible)
+		    popup_check_cursor_pos();
+#endif
+#ifdef FEAT_CONCEAL
 		if (curwin->w_p_cole > 0)
 		{
 		    conceal_old_cursor_line = last_cursormoved.lnum;
 		    conceal_new_cursor_line = curwin->w_cursor.lnum;
 		    conceal_update_lines = TRUE;
 		}
-# endif
+#endif
 		last_cursormoved = curwin->w_cursor;
 	    }
 
diff --git a/src/popupwin.c b/src/popupwin.c
index 357b761..2d61ab8 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -285,6 +285,49 @@
 	    }
 	}
     }
+
+    di = dict_find(dict, (char_u *)"moved", -1);
+    if (di != NULL)
+    {
+	wp->w_popup_curwin = curwin;
+	wp->w_popup_lnum = curwin->w_cursor.lnum;
+	wp->w_popup_mincol = curwin->w_cursor.col;
+	wp->w_popup_maxcol = curwin->w_cursor.col;
+	if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL)
+	{
+	    char_u  *s = di->di_tv.vval.v_string;
+	    int	    flags = 0;
+
+	    if (STRCMP(s, "word") == 0)
+		flags = FIND_IDENT | FIND_STRING;
+	    else if (STRCMP(s, "WORD") == 0)
+		flags = FIND_STRING;
+	    else if (STRCMP(s, "any") != 0)
+		semsg(_(e_invarg2), s);
+	    if (flags != 0)
+	    {
+		char_u	*ptr;
+		int	len = find_ident_under_cursor(&ptr, flags);
+
+		if (len > 0)
+		{
+		    wp->w_popup_mincol = (int)(ptr - ml_get_curline());
+		    wp->w_popup_maxcol = wp->w_popup_mincol + len - 1;
+		}
+	    }
+	}
+	else if (di->di_tv.v_type == VAR_LIST
+		&& di->di_tv.vval.v_list != NULL
+		&& di->di_tv.vval.v_list->lv_len == 2)
+	{
+	    list_T *l = di->di_tv.vval.v_list;
+
+	    wp->w_popup_mincol = tv_get_number(&l->lv_first->li_tv);
+	    wp->w_popup_maxcol = tv_get_number(&l->lv_first->li_next->li_tv);
+	}
+	else
+	    semsg(_(e_invarg2), tv_get_string(&di->di_tv));
+    }
 }
 
 /*
@@ -708,6 +751,21 @@
 }
 
 /*
+ * Close popup "wp" and invoke any close callback for it.
+ */
+    static void
+popup_close_and_callback(win_T *wp, typval_T *arg)
+{
+    int id = wp->w_id;
+
+    if (wp->w_close_cb.cb_name != NULL)
+	// Careful: This may make "wp" invalid.
+	invoke_popup_callback(wp, arg);
+
+    popup_close(id);
+}
+
+/*
  * popup_close({id})
  */
     void
@@ -717,13 +775,7 @@
     win_T	*wp = find_popup_win(id);
 
     if (wp != NULL)
-    {
-	if (wp->w_close_cb.cb_name != NULL)
-	    // Careful: This may make "wp" invalid.
-	    invoke_popup_callback(wp, &argvars[1]);
-
-	popup_close(id);
-    }
+	popup_close_and_callback(wp, &argvars[1]);
 }
 
 /*
@@ -1066,4 +1118,28 @@
     return res;
 }
 
+/*
+ * Called when the cursor moved: check if any popup needs to be closed if the
+ * cursor moved far enough.
+ */
+    void
+popup_check_cursor_pos()
+{
+    win_T *wp;
+    typval_T tv;
+
+    popup_reset_handled();
+    while ((wp = find_next_popup(TRUE)) != NULL)
+	if (wp->w_popup_curwin != NULL
+		&& (curwin != wp->w_popup_curwin
+		    || curwin->w_cursor.lnum != wp->w_popup_lnum
+		    || curwin->w_cursor.col < wp->w_popup_mincol
+		    || curwin->w_cursor.col > wp->w_popup_maxcol))
+	{
+	    tv.v_type = VAR_NUMBER;
+	    tv.vval.v_number = -1;
+	    popup_close_and_callback(wp, &tv);
+	}
+}
+
 #endif // FEAT_TEXT_PROP
diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro
index 24a1023..a50e7b7 100644
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -17,4 +17,5 @@
 void popup_reset_handled(void);
 win_T *find_next_popup(int lowest);
 int popup_do_filter(int c);
+void popup_check_cursor_pos(void);
 /* vim: set ft=c : */
diff --git a/src/screen.c b/src/screen.c
index f179618..8c97e88 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -1050,6 +1050,7 @@
     // so that the window with a higher zindex is drawn later, thus goes on
     // top.
     // TODO: don't redraw every popup every time.
+    popup_visible = FALSE;
     popup_reset_handled();
     while ((wp = find_next_popup(TRUE)) != NULL)
     {
@@ -1066,6 +1067,7 @@
 
 	// Draw the popup text.
 	win_update(wp);
+	popup_visible = TRUE;
 
 	wp->w_winrow -= top_off;
 	wp->w_wincol -= left_off;
diff --git a/src/structs.h b/src/structs.h
index 591c5a2..bc1e487 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -2897,6 +2897,12 @@
 					  // computed
     callback_T	w_close_cb;	    // popup close callback
     callback_T	w_filter_cb;	    // popup filter callback
+
+    win_T	*w_popup_curwin;    // close popup if curwin differs
+    linenr_T	w_popup_lnum;	    // close popup if cursor not on this line
+    colnr_T	w_popup_mincol;	    // close popup if cursor before this col
+    colnr_T	w_popup_maxcol;	    // close popup if cursor after this col
+
 # if defined(FEAT_TIMERS)
     timer_T	*w_popup_timer;	    // timer for closing popup window
 # endif
diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim
index ca95977..6d3279b 100644
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -909,7 +909,7 @@
   %bwipe!
 endfunc
 
-function Test_adjust_left_past_screen_width()
+func Test_adjust_left_past_screen_width()
   " width of screen
   let X = join(map(range(&columns), {->'X'}), '')
 
@@ -973,4 +973,65 @@
 
   popupclear
   %bwipe!
-endfunction
+endfunc
+
+func Test_popup_moved()
+  new
+  call test_override('char_avail', 1)
+  call setline(1, ['one word to move around', 'a WORD.and->some thing'])
+
+  exe "normal gg0/word\<CR>"
+  let winid = popup_atcursor('text', {'moved': 'any'})
+  redraw
+  call assert_equal(1, popup_getpos(winid).visible)
+  " trigger the check for last_cursormoved by going into insert mode
+  call feedkeys("li\<Esc>", 'xt')
+  call assert_equal({}, popup_getpos(winid))
+  popupclear
+
+  exe "normal gg0/word\<CR>"
+  let winid = popup_atcursor('text', {'moved': 'word'})
+  redraw
+  call assert_equal(1, popup_getpos(winid).visible)
+  call feedkeys("hi\<Esc>", 'xt')
+  call assert_equal({}, popup_getpos(winid))
+  popupclear
+
+  exe "normal gg0/word\<CR>"
+  let winid = popup_atcursor('text', {'moved': 'word'})
+  redraw
+  call assert_equal(1, popup_getpos(winid).visible)
+  call feedkeys("li\<Esc>", 'xt')
+  call assert_equal(1, popup_getpos(winid).visible)
+  call feedkeys("ei\<Esc>", 'xt')
+  call assert_equal(1, popup_getpos(winid).visible)
+  call feedkeys("eli\<Esc>", 'xt')
+  call assert_equal({}, popup_getpos(winid))
+  popupclear
+
+  exe "normal gg0/WORD\<CR>"
+  let winid = popup_atcursor('text', {'moved': 'WORD'})
+  redraw
+  call assert_equal(1, popup_getpos(winid).visible)
+  call feedkeys("eli\<Esc>", 'xt')
+  call assert_equal(1, popup_getpos(winid).visible)
+  call feedkeys("wi\<Esc>", 'xt')
+  call assert_equal(1, popup_getpos(winid).visible)
+  call feedkeys("Eli\<Esc>", 'xt')
+  call assert_equal({}, popup_getpos(winid))
+  popupclear
+
+  exe "normal gg0/word\<CR>"
+  let winid = popup_atcursor('text', {'moved': [5, 10]})
+  redraw
+  call assert_equal(1, popup_getpos(winid).visible)
+  call feedkeys("eli\<Esc>", 'xt')
+  call feedkeys("ei\<Esc>", 'xt')
+  call assert_equal(1, popup_getpos(winid).visible)
+  call feedkeys("eli\<Esc>", 'xt')
+  call assert_equal({}, popup_getpos(winid))
+  popupclear
+
+  bwipe!
+  call test_override('ALL', 0)
+endfunc
diff --git a/src/version.c b/src/version.c
index 33eb93c..2cefa3b 100644
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1453,
+/**/
     1452,
 /**/
     1451,