# HG changeset patch # User Bram Moolenaar # Date 1559402106 -7200 # Node ID 998603a243d7406bc4793cbc9da6993d427293af # Parent 3fc7f1139ab41f73271583da29a34595a260bac2 patch 8.1.1441: popup window filter not yet implemented commit https://github.com/vim/vim/commit/bf0eff0b724ebf4951f7ca82e6c648451f9f0c01 Author: Bram Moolenaar 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. diff --git a/runtime/doc/popup.txt b/runtime/doc/popup.txt --- 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 == "\" + " 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* diff --git a/src/getchar.c b/src/getchar.c --- 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; } diff --git a/src/misc2.c b/src/misc2.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) { diff --git a/src/popupwin.c b/src/popupwin.c --- 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 == "\" + 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 diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro --- 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); diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro --- 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 : */ diff --git a/src/screen.c b/src/screen.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 diff --git a/src/structs.h b/src/structs.h --- 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 diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim --- 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 diff --git a/src/version.c b/src/version.c --- 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, diff --git a/src/vim.h b/src/vim.h --- 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. diff --git a/src/window.c b/src/window.c --- 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);