]> granicus.if.org Git - vim/commitdiff
patch 8.1.1441: popup window filter not yet implemented v8.1.1441
authorBram Moolenaar <Bram@vim.org>
Sat, 1 Jun 2019 15:13:36 +0000 (17:13 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 1 Jun 2019 15:13:36 +0000 (17:13 +0200)
Problem:    Popup window filter not yet implemented.
Solution:   Implement the popup filter.

12 files changed:
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

index 02d037adb75d80a6fd17c5cebf0c9ab1bb924322..1a4a9143da77f56bf158b0213a2a47e87f88c047 100644 (file)
@@ -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()| is a dictionary with options:
                        {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 the popup window with the highest zindex
 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 border).
 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*
 
index 02535ce20143205fa320e73eba478bc77643d0e7..80e98ec60e25b9aa449495943a61a01d9433df5e 100644 (file)
@@ -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;
 }
index 69b9347bcba7c2b9c6513d95f7d8e76294d39f5e..2ac7f5e77cbc0d657d780ad79fc8185d48e92f0e 100644 (file)
@@ -2731,17 +2731,31 @@ get_special_key_name(int c, int modifiers)
 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)
     {
index 2ebfda2d9ccd43f90b931f1d9647ceeb9df549da..620ef0506231619c4aa58d9e37dde2334a290793 100644 (file)
@@ -149,25 +149,33 @@ apply_options(win_T *wp, buf_T *buf UNUSED, dict_T *dict, int atcursor)
        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
index 84ed66a57ae69fdf9232902351d4d1d2629a6c3f..43921ed3a2395968d8bbd258ec3715a0b11ff608 100644 (file)
@@ -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);
index 107a4273c5e4a108fae41523cc8490e957477b50..24a1023950bab3204cb39aeb010147c0f4dfac97 100644 (file)
@@ -14,4 +14,7 @@ void f_popup_move(typval_T *argvars, typval_T *rettv);
 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 : */
index 5f3947bf9eeb2784f8d51b32bfbdf560fba4a4c1..a8b10dedb986779c6a4cb07a396700e0d3ac5b57 100644 (file)
@@ -996,48 +996,19 @@ update_debug_sign(buf_T *buf, linenr_T lnum)
 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 (;;)
+    popup_reset_handled();
+    while ((wp = find_next_popup(TRUE)) != NULL)
     {
-       // 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;
-
        // Recompute the position if the text changed.
-       if (lowest_wp->w_popup_last_changedtick
-                                          != CHANGEDTICK(lowest_wp->w_buffer))
-           popup_adjust_position(lowest_wp);
+       if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
+           popup_adjust_position(wp);
 
-       win_update(lowest_wp);
-       lowest_wp->w_popup_flags |= POPF_REDRAWN;
+       win_update(wp);
     }
 }
 #endif
index 2c164ca2bde368b0e37b095fb304f3ce0c7c16b6..9589b67dbe99fa7ee342191dfa0a27af0c16d5b6 100644 (file)
@@ -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
index 1c8f7ef6c1658fa2e892c54e4e1865c96f306929..b9d6a062c36103be3a73769a020dad0226d31150 100644 (file)
@@ -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
index 96571af02ba5ef8365e1cd8fcfd6307debae392e..4b472ee89db2e8d4464b68bfda10c27a10c5e37c 100644 (file)
@@ -767,6 +767,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1441,
 /**/
     1440,
 /**/
index 378fe5f8736d552be02394fd0125cb23dd2135a8..72bb109b4dc299e9dbe6709bb035dc67b87f9ce9 100644 (file)
--- a/src/vim.h
+++ b/src/vim.h
@@ -615,7 +615,7 @@ extern int (*dyn_libintl_wputenv)(const wchar_t *envstring);
 
 // 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.
index 42689ee0bc535293d66e338ffaeb006f21f3db51..b4ab11be31cfe3997381c881dc576809f5731b22 100644 (file)
@@ -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);