changeset 16880:998603a243d7 v8.1.1441

patch 8.1.1441: popup window filter not yet implemented commit https://github.com/vim/vim/commit/bf0eff0b724ebf4951f7ca82e6c648451f9f0c01 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Jun 1 17:13:36 2019 +0200 patch 8.1.1441: popup window filter not yet implemented Problem: Popup window filter not yet implemented. Solution: Implement the popup filter.
author Bram Moolenaar <Bram@vim.org>
date Sat, 01 Jun 2019 17:15:06 +0200
parents 3fc7f1139ab4
children 8d5af04e5336
files runtime/doc/popup.txt src/getchar.c src/misc2.c src/popupwin.c src/proto/misc2.pro src/proto/popupwin.pro src/screen.c src/structs.h src/testdir/test_popupwin.vim src/version.c src/vim.h src/window.c
diffstat 12 files changed, 230 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/popup.txt
+++ b/runtime/doc/popup.txt
@@ -1,4 +1,4 @@
-*popup.txt*  For Vim version 8.1.  Last change: 2019 May 31
+*popup.txt*  For Vim version 8.1.  Last change: 2019 Jun 01
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -90,11 +90,11 @@ Probably 2. is the best choice.
 
 IMPLEMENTATION:
 - Code is in popupwin.c
-- Implement filter.
-  Check that popup_close() works in the filter.
+- Invoke filter with character before mapping?
+- Handle screen resize in screenalloc(). (Ben Jackson, #4467)
+- Why does 'nrformats' leak from the popup window buffer???
 - Implement padding
 - Implement border
-- Handle screen resize in screenalloc().
 - Make redrawing more efficient and avoid flicker.
     Store popup info in a mask, use the mask in screen_line()
     Keep mask until next update_screen(), find differences and redraw affected
@@ -102,8 +102,8 @@ IMPLEMENTATION:
     Fix redrawing problem with completion.
     Fix redrawing problem when scrolling non-current window
     Fix redrawing the statusline on top of a popup
-- Disable commands, feedkeys(), CTRL-W, etc. in a popup window.  Or whitelist
-  commands that are allowed?
+- Disable commands, feedkeys(), CTRL-W, etc. in a popup window.
+  Use NOT_IN_POPUP_WINDOW.
 - Figure out the size and position better.
     if wrapping splits a double-wide character
     if wrapping inserts indent
@@ -385,7 +385,6 @@ The second argument of |popup_create()| 
 			{not implemented yet}
 	filter		a callback that can filter typed characters, see 
 			|popup-filter|
-			{not implemented yet}
 	callback	a callback to be used when the popup closes, e.g. when
 			using |popup_filter_menu()|, see |popup-callback|.
 			{not implemented yet}
@@ -426,7 +425,6 @@ So we get:
 
 POPUP FILTER						*popup-filter*
 
-{not implemented yet}
 A callback that gets any typed keys while a popup is displayed.  The filter is
 not invoked when the popup is hidden.
 
@@ -437,10 +435,23 @@ filter is also called.  The filter of th
 is called first.
 
 The filter function is called with two arguments: the ID of the popup and the
-key.
+key, e.g.: >
+	func MyFilter(winid, key)
+	  if a:key == "\<F2>"
+	    " do something
+	    return 1
+	  endif
+	  if a:key == 'x'
+	    call popup_close(a:winid)
+	    return 1
+	  endif
+	  return 0
+  	endfunc
+
+Currently the key is what results after any mapping.  This may change...
 
 Some common key actions:
-	Esc		close the popup
+	x		close the popup (see note below)
 	cursor keys	select another entry
 	Tab		accept current suggestion
 
@@ -451,6 +462,11 @@ popup is col 1, row 1 (not counting the 
 Vim provides standard filters |popup_filter_menu()| and
 |popup_filter_yesno()|.
 
+Note that "x" is the normal way to close a popup.  You may want to use Esc,
+but since many keys start with an Esc character, there may be a delay before
+Vim recognizes the Esc key.  If you do use Esc, it is reecommended to set the
+'ttimeoutlen' option to 100 and set 'timeout' and/or 'ttimeout'.
+
 
 POPUP CALLBACK						*popup-callback*
 
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -1801,6 +1801,10 @@ vgetc(void)
 	ui_remove_balloon();
     }
 #endif
+#ifdef FEAT_TEXT_PROP
+    if (popup_do_filter(c))
+	c = K_IGNORE;
+#endif
 
     return c;
 }
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -2731,17 +2731,31 @@ get_special_key_name(int c, int modifier
 trans_special(
     char_u	**srcp,
     char_u	*dst,
-    int		keycode, /* prefer key code, e.g. K_DEL instead of DEL */
-    int		in_string) /* TRUE when inside a double quoted string */
+    int		keycode,    // prefer key code, e.g. K_DEL instead of DEL
+    int		in_string)  // TRUE when inside a double quoted string
 {
     int		modifiers = 0;
     int		key;
-    int		dlen = 0;
 
     key = find_special_key(srcp, &modifiers, keycode, FALSE, in_string);
     if (key == 0)
 	return 0;
 
+    return special_to_buf(key, modifiers, keycode, dst);
+}
+
+/*
+ * Put the character sequence for "key" with "modifiers" into "dst" and return
+ * the resulting length.
+ * When "keycode" is TRUE prefer key code, e.g. K_DEL instead of DEL.
+ * The sequence is not NUL terminated.
+ * This is how characters in a string are encoded.
+ */
+    int
+special_to_buf(int key, int modifiers, int keycode, char_u *dst)
+{
+    int		dlen = 0;
+
     /* Put the appropriate modifier in a string */
     if (modifiers != 0)
     {
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -149,25 +149,33 @@ apply_options(win_T *wp, buf_T *buf UNUS
 	if (get_lambda_tv(&ptr, &tv, TRUE) == OK)
 	{
 	    wp->w_popup_timer = create_timer(nr, 0);
-	    wp->w_popup_timer->tr_callback.cb_name =
-				  vim_strsave(partial_name(tv.vval.v_partial));
-	    func_ref(wp->w_popup_timer->tr_callback.cb_name);
-	    wp->w_popup_timer->tr_callback.cb_partial = tv.vval.v_partial;
+	    wp->w_popup_timer->tr_callback = get_callback(&tv);
+	    clear_tv(&tv);
 	}
     }
 #endif
 
     // Option values resulting in setting an option.
-    str = dict_get_string(dict, (char_u *)"highlight", TRUE);
+    str = dict_get_string(dict, (char_u *)"highlight", FALSE);
     if (str != NULL)
 	set_string_option_direct_in_win(wp, (char_u *)"wincolor", -1,
 						   str, OPT_FREE|OPT_LOCAL, 0);
+
     di = dict_find(dict, (char_u *)"wrap", -1);
     if (di != NULL)
     {
 	nr = dict_get_number(dict, (char_u *)"wrap");
 	wp->w_p_wrap = nr != 0;
     }
+
+    di = dict_find(dict, (char_u *)"filter", -1);
+    if (di != NULL)
+    {
+	callback_T	callback = get_callback(&di->di_tv);
+
+	if (callback.cb_name != NULL)
+	    set_callback(&wp->w_filter_cb, &callback);
+    }
 }
 
 /*
@@ -759,4 +767,109 @@ not_in_popup_window()
     return FALSE;
 }
 
+/*
+ * Reset all the POPF_HANDLED flags in global popup windows and popup windows
+ * in the current tab.
+ */
+    void
+popup_reset_handled()
+{
+    win_T *wp;
+
+    for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
+	wp->w_popup_flags &= ~POPF_HANDLED;
+    for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
+	wp->w_popup_flags &= ~POPF_HANDLED;
+}
+
+/*
+ * Find the next visible popup where POPF_HANDLED is not set.
+ * Must have called popup_reset_handled() first.
+ * When "lowest" is TRUE find the popup with the lowest zindex, otherwise the
+ * popup with the highest zindex.
+ */
+    win_T *
+find_next_popup(int lowest)
+{
+    win_T   *wp;
+    win_T   *found_wp;
+    int	    found_zindex;
+
+    found_zindex = lowest ? INT_MAX : 0;
+    found_wp = NULL;
+    for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
+	if ((wp->w_popup_flags & (POPF_HANDLED|POPF_HIDDEN)) == 0
+		&& (lowest ? wp->w_zindex < found_zindex
+			   : wp->w_zindex > found_zindex))
+	{
+	    found_zindex = wp->w_zindex;
+	    found_wp = wp;
+	}
+    for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
+	if ((wp->w_popup_flags & (POPF_HANDLED|POPF_HIDDEN)) == 0
+		&& (lowest ? wp->w_zindex < found_zindex
+			   : wp->w_zindex > found_zindex))
+	{
+	    found_zindex = wp->w_zindex;
+	    found_wp = wp;
+	}
+
+    if (found_wp != NULL)
+	found_wp->w_popup_flags |= POPF_HANDLED;
+    return found_wp;
+}
+
+/*
+ * Invoke the filter callback for window "wp" with typed character "c".
+ * Uses the global "mod_mask" for modifiers.
+ * Returns the return value of the filter.
+ * Careful: The filter may make "wp" invalid!
+ */
+    static int
+invoke_popup_filter(win_T *wp, int c)
+{
+    int		res;
+    typval_T	rettv;
+    int		dummy;
+    typval_T	argv[3];
+    char_u	buf[NUMBUFLEN];
+
+    argv[0].v_type = VAR_NUMBER;
+    argv[0].vval.v_number = (varnumber_T)wp->w_id;
+
+    // Convert the number to a string, so that the function can use:
+    //	    if a:c == "\<F2>"
+    buf[special_to_buf(c, mod_mask, TRUE, buf)] = NUL;
+    argv[1].v_type = VAR_STRING;
+    argv[1].vval.v_string = vim_strsave(buf);
+
+    argv[2].v_type = VAR_UNKNOWN;
+
+    call_callback(&wp->w_filter_cb, -1,
+			    &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, NULL);
+    res = tv_get_number(&rettv);
+    vim_free(argv[1].vval.v_string);
+    clear_tv(&rettv);
+    return res;
+}
+
+/*
+ * Called when "c" was typed: invoke popup filter callbacks.
+ * Returns TRUE when the character was consumed,
+ */
+    int
+popup_do_filter(int c)
+{
+    int		res = FALSE;
+    win_T   *wp;
+
+    popup_reset_handled();
+
+    while (!res && (wp = find_next_popup(FALSE)) != NULL)
+	if (wp->w_filter_cb.cb_name != NULL)
+	    res = invoke_popup_filter(wp, c);
+
+    return res;
+}
+
 #endif // FEAT_TEXT_PROP
--- a/src/proto/misc2.pro
+++ b/src/proto/misc2.pro
@@ -69,6 +69,7 @@ int simplify_key(int key, int *modifiers
 int handle_x_keys(int key);
 char_u *get_special_key_name(int c, int modifiers);
 int trans_special(char_u **srcp, char_u *dst, int keycode, int in_string);
+int special_to_buf(int key, int modifiers, int keycode, char_u *dst);
 int find_special_key(char_u **srcp, int *modp, int keycode, int keep_x_key, int in_string);
 int extract_modifiers(int key, int *modp);
 int find_special_key_in_table(int c);
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -14,4 +14,7 @@ void f_popup_move(typval_T *argvars, typ
 void f_popup_getpos(typval_T *argvars, typval_T *rettv);
 void f_popup_getoptions(typval_T *argvars, typval_T *rettv);
 int not_in_popup_window(void);
+void popup_reset_handled(void);
+win_T *find_next_popup(int lowest);
+int popup_do_filter(int c);
 /* vim: set ft=c : */
--- a/src/screen.c
+++ b/src/screen.c
@@ -996,48 +996,19 @@ update_debug_sign(buf_T *buf, linenr_T l
 update_popups(void)
 {
     win_T   *wp;
-    win_T   *lowest_wp;
-    int	    lowest_zindex;
-
-    // Reset all the VALID_POPUP flags.
-    for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
-	wp->w_popup_flags &= ~POPF_REDRAWN;
-    for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
-	wp->w_popup_flags &= ~POPF_REDRAWN;
-
+
+    // 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.
-    for (;;)
-    {
-	// 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.
-	lowest_zindex = INT_MAX;
-	lowest_wp = NULL;
-	for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
-	    if ((wp->w_popup_flags & (POPF_REDRAWN|POPF_HIDDEN)) == 0
-					       && wp->w_zindex < lowest_zindex)
-	    {
-		lowest_zindex = wp->w_zindex;
-		lowest_wp = wp;
-	    }
-	for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
-	    if ((wp->w_popup_flags & (POPF_REDRAWN|POPF_HIDDEN)) == 0
-					       && wp->w_zindex < lowest_zindex)
-	    {
-		lowest_zindex = wp->w_zindex;
-		lowest_wp = wp;
-	    }
-
-	if (lowest_wp == NULL)
-	    break;
-
+    popup_reset_handled();
+    while ((wp = find_next_popup(TRUE)) != NULL)
+    {
 	// Recompute the position if the text changed.
-	if (lowest_wp->w_popup_last_changedtick
-					   != CHANGEDTICK(lowest_wp->w_buffer))
-	    popup_adjust_position(lowest_wp);
-
-	win_update(lowest_wp);
-	lowest_wp->w_popup_flags |= POPF_REDRAWN;
+	if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
+	    popup_adjust_position(wp);
+
+	win_update(wp);
     }
 }
 #endif
--- a/src/structs.h
+++ b/src/structs.h
@@ -2890,6 +2890,7 @@ struct window_S
     int		w_wantcol;	    // "col" for popup window
     varnumber_T	w_popup_last_changedtick; // b:changedtick when position was
 					  // computed
+    callback_T	w_filter_cb;	    // popup filter callback
 # if defined(FEAT_TIMERS)
     timer_T	*w_popup_timer;	    // timer for closing popup window
 # endif
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -473,3 +473,46 @@ func Test_popup_atcursor()
 
   bwipe!
 endfunc
+
+func Test_popup_filter()
+  new
+  call setline(1, 'some text')
+
+  func MyPopupFilter(winid, c)
+    if a:c == 'e'
+      let g:eaten = 'e'
+      return 1
+    endif
+    if a:c == '0'
+      let g:ignored = '0'
+      return 0
+    endif
+    if a:c == 'x'
+      call popup_close(a:winid)
+      return 1
+    endif
+    return 0
+  endfunc
+
+  let winid = popup_create('something', {'filter': 'MyPopupFilter'})
+  redraw
+
+  " e is consumed by the filter
+  call feedkeys('e', 'xt')
+  call assert_equal('e', g:eaten)
+
+  " 0 is ignored by the filter
+  normal $
+  call assert_equal(9, getcurpos()[2])
+  call feedkeys('0', 'xt')
+  call assert_equal('0', g:ignored)
+  call assert_equal(1, getcurpos()[2])
+
+  " x closes the popup
+  call feedkeys('x', 'xt')
+  call assert_equal('e', g:eaten)
+  call assert_equal(-1, winbufnr(winid))
+
+  delfunc MyPopupFilter
+  popupclear
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1441,
+/**/
     1440,
 /**/
     1439,
--- a/src/vim.h
+++ b/src/vim.h
@@ -615,7 +615,7 @@ extern int (*dyn_libintl_wputenv)(const 
 
 // Values for w_popup_flags.
 #define POPF_HIDDEN	1	// popup is not displayed
-#define POPF_REDRAWN	2	// popup was just redrawn
+#define POPF_HANDLED	2	// popup was just redrawn or filtered
 
 /*
  * Terminal highlighting attribute bits.
--- a/src/window.c
+++ b/src/window.c
@@ -4844,6 +4844,9 @@ win_free(
 #ifdef FEAT_MENU
     remove_winbar(wp);
 #endif
+#ifdef FEAT_TEXT_PROP
+    free_callback(&wp->w_filter_cb);
+#endif
 
 #ifdef FEAT_SYN_HL
     vim_free(wp->w_p_cc_cols);