# HG changeset patch # User Bram Moolenaar # Date 1560705305 -7200 # Node ID 7ef5283ace3ccff3cd1e49103a2f75ba82a76446 # Parent 3cc7132b7a5c36fb95f8e93a89424804cdf19e0a patch 8.1.1558: popup_menu() and popup_filter_menu() are not implemented yet commit https://github.com/vim/vim/commit/a730e55cc2d3045a79a340a5af1ad4a749058a32 Author: Bram Moolenaar Date: Sun Jun 16 19:05:31 2019 +0200 patch 8.1.1558: popup_menu() and popup_filter_menu() are not implemented yet Problem: Popup_menu() and popup_filter_menu() are not implemented yet. Solution: Implement the functions. Fix that centering didn't take the border and padding into account. diff --git a/runtime/doc/popup.txt b/runtime/doc/popup.txt --- a/runtime/doc/popup.txt +++ b/runtime/doc/popup.txt @@ -89,7 +89,7 @@ that it is in. TODO: - Why does 'nrformats' leak from the popup window buffer??? - Disable commands, feedkeys(), CTRL-W, etc. in a popup window. - Use NOT_IN_POPUP_WINDOW for more commands. + Use ERROR_IF_POPUP_WINDOW for more commands. - Add 'balloonpopup': instead of showing text, let the callback open a popup window and return the window ID. The popup will then be closed when the mouse moves, except when it moves inside the popup. @@ -109,8 +109,6 @@ TODO: - When the lines do not fit show a scrollbar (like in the popup menu). Use the mouse wheel for scrolling. - Implement: - popup_filter_menu({id}, {key}) - popup_menu({text}, {options}) popup_setoptions({id}, {options}) hidden option tabpage option with number @@ -220,12 +218,20 @@ popup_dialog({text}, {options}) *popu popup_filter_menu({id}, {key}) *popup_filter_menu()* - {not implemented yet} - Filter that can be used for a popup. It handles the cursor - keys to move the selected index in the popup. Space and Enter - can be used to select an item. Invokes the "callback" of the - popup menu with the index of the selected line as the second - argument. + Filter that can be used for a popup. These keys can be used: + j select item below + k select item above + accept current selection + x Esc CTRL-C cancel the menu + Other keys are ignored. + + A match is set on that line to highlight it, see + |popup_menu()|. + + When the current selection is accepted the "callback" of the + popup menu is invoked with the index of the selected line as + the second argument. The first entry has index one. + Cancelling the menu invokes the callback with -1. popup_filter_yesno({id}, {key}) *popup_filter_yesno()* @@ -279,7 +285,6 @@ popup_hide({id}) *popup_hide()* popup_menu({text}, {options}) *popup_menu()* - {not implemented yet} Show the {text} near the cursor, handle selecting one of the items with cursorkeys, and close it an item is selected with Space or Enter. {text} should have multiple lines to make this @@ -287,11 +292,16 @@ popup_menu({text}, {options}) *popup call popup_create({text}, { \ 'pos': 'center', \ 'zindex': 200, + \ 'drag': 1, \ 'wrap': 0, \ 'border': [], + \ 'padding': [], \ 'filter': 'popup_filter_menu', \ }) -< Use {options} to change the properties. Should at least set +< The current line is highlighted with a match using + PopupSelected, or |PmenuSel| if that is not defined. + + Use {options} to change the properties. Should at least set "callback" to a function that handles the selected item. @@ -320,7 +330,8 @@ popup_notification({text}, {options}) \ }) < The PopupNotification highlight group is used instead of WarningMsg if it is defined. -< The position will be adjusted to avoid overlap with other + + The position will be adjusted to avoid overlap with other notifications. Use {options} to change the properties. diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -816,10 +816,12 @@ static struct fst {"popup_close", 1, 2, f_popup_close}, {"popup_create", 2, 2, f_popup_create}, {"popup_dialog", 2, 2, f_popup_dialog}, + {"popup_filter_menu", 2, 2, f_popup_filter_menu}, {"popup_filter_yesno", 2, 2, f_popup_filter_yesno}, {"popup_getoptions", 1, 1, f_popup_getoptions}, {"popup_getpos", 1, 1, f_popup_getpos}, {"popup_hide", 1, 1, f_popup_hide}, + {"popup_menu", 2, 2, f_popup_menu}, {"popup_move", 2, 2, f_popup_move}, {"popup_notification", 2, 2, f_popup_notification}, {"popup_settext", 2, 2, f_popup_settext}, diff --git a/src/popupwin.c b/src/popupwin.c --- a/src/popupwin.c +++ b/src/popupwin.c @@ -651,7 +651,11 @@ popup_adjust_position(win_T *wp) if (wp->w_width > maxwidth) wp->w_width = maxwidth; if (center_hor) - wp->w_wincol = (Columns - wp->w_width) / 2; + { + wp->w_wincol = (Columns - wp->w_width - extra_width) / 2; + if (wp->w_wincol < 0) + wp->w_wincol = 0; + } else if (wp->w_popup_pos == POPPOS_BOTRIGHT || wp->w_popup_pos == POPPOS_TOPRIGHT) { @@ -671,7 +675,11 @@ popup_adjust_position(win_T *wp) wp->w_height = Rows - wp->w_winrow; if (center_vert) - wp->w_winrow = (Rows - wp->w_height) / 2; + { + wp->w_winrow = (Rows - wp->w_height - extra_height) / 2; + if (wp->w_winrow < 0) + wp->w_winrow = 0; + } else if (wp->w_popup_pos == POPPOS_BOTRIGHT || wp->w_popup_pos == POPPOS_BOTLEFT) { @@ -702,7 +710,8 @@ typedef enum TYPE_NORMAL, TYPE_ATCURSOR, TYPE_NOTIFICATION, - TYPE_DIALOG + TYPE_DIALOG, + TYPE_MENU } create_type_T; /* @@ -751,7 +760,7 @@ popup_set_buffer_text(buf_T *buf, typval * popup_create({text}, {options}) * popup_atcursor({text}, {options}) */ - static void + static win_T * popup_create(typval_T *argvars, typval_T *rettv, create_type_T type) { win_T *wp; @@ -764,25 +773,25 @@ popup_create(typval_T *argvars, typval_T && !(argvars[0].v_type == VAR_LIST && argvars[0].vval.v_list != NULL)) { emsg(_(e_listreq)); - return; + return NULL; } if (argvars[1].v_type != VAR_DICT || argvars[1].vval.v_dict == NULL) { emsg(_(e_dictreq)); - return; + return NULL; } d = argvars[1].vval.v_dict; // Create the window and buffer. wp = win_alloc_popup_win(); if (wp == NULL) - return; + return NULL; rettv->vval.v_number = wp->w_id; wp->w_popup_pos = POPPOS_TOPLEFT; buf = buflist_new(NULL, NULL, (linenr_T)0, BLN_NEW|BLN_LISTED|BLN_DUMMY); if (buf == NULL) - return; + return NULL; ml_open(buf); win_init_popup_win(wp, buf); @@ -898,7 +907,7 @@ popup_create(typval_T *argvars, typval_T OPT_FREE|OPT_LOCAL, 0); } - if (type == TYPE_DIALOG) + if (type == TYPE_DIALOG || type == TYPE_MENU) { int i; @@ -912,6 +921,20 @@ popup_create(typval_T *argvars, typval_T } } + if (type == TYPE_MENU) + { + typval_T tv; + callback_T callback; + + tv.v_type = VAR_STRING; + tv.vval.v_string = (char_u *)"popup_filter_menu"; + callback = get_callback(&tv); + if (callback.cb_name != NULL) + set_callback(&wp->w_filter_cb, &callback); + + wp->w_p_wrap = 0; + } + // Deal with options. apply_options(wp, buf, argvars[1].vval.v_dict); @@ -924,6 +947,8 @@ popup_create(typval_T *argvars, typval_T redraw_all_later(NOT_VALID); popup_mask_refresh = TRUE; + + return wp; } /* @@ -1000,6 +1025,93 @@ popup_close_and_callback(win_T *wp, typv } /* + * In a filter: check if the typed key is a mouse event that is used for + * dragging the popup. + */ + static void +filter_handle_drag(win_T *wp, int c, typval_T *rettv) +{ + int row = mouse_row; + int col = mouse_col; + + if (wp->w_popup_drag + && is_mouse_key(c) + && (wp == popup_dragwin + || wp == mouse_find_win(&row, &col, FIND_POPUP))) + // do not consume the key, allow for dragging the popup + rettv->vval.v_number = 0; +} + + static void +popup_highlight_curline(win_T *wp) +{ + int id; + char buf[100]; + + match_delete(wp, 1, FALSE); + + id = syn_name2id((char_u *)"PopupSelected"); + vim_snprintf(buf, sizeof(buf), "\\%%%dl.*", (int)wp->w_cursor.lnum); + match_add(wp, (char_u *)(id == 0 ? "PmenuSel" : "PopupSelected"), + (char_u *)buf, 10, 1, NULL, NULL); +} + +/* + * popup_filter_menu({text}, {options}) + */ + void +f_popup_filter_menu(typval_T *argvars, typval_T *rettv) +{ + int id = tv_get_number(&argvars[0]); + win_T *wp = win_id2wp(id); + char_u *key = tv_get_string(&argvars[1]); + typval_T res; + int c; + linenr_T old_lnum; + + // If the popup has been closed do not consume the key. + if (wp == NULL) + return; + + c = *key; + if (c == K_SPECIAL && key[1] != NUL) + c = TO_SPECIAL(key[1], key[2]); + + // consume all keys until done + rettv->vval.v_number = 1; + res.v_type = VAR_NUMBER; + + old_lnum = wp->w_cursor.lnum; + if ((c == 'k' || c == 'K' || c == K_UP) && wp->w_cursor.lnum > 1) + --wp->w_cursor.lnum; + if ((c == 'j' || c == 'J' || c == K_DOWN) + && wp->w_cursor.lnum < wp->w_buffer->b_ml.ml_line_count) + ++wp->w_cursor.lnum; + if (old_lnum != wp->w_cursor.lnum) + { + popup_highlight_curline(wp); + return; + } + + if (c == 'x' || c == 'X' || c == ESC || c == Ctrl_C) + { + // Cancelled, invoke callback with -1 + res.vval.v_number = -1; + popup_close_and_callback(wp, &res); + return; + } + if (c == ' ' || c == K_KENTER || c == CAR || c == NL) + { + // Invoke callback with current index. + res.vval.v_number = wp->w_cursor.lnum; + popup_close_and_callback(wp, &res); + return; + } + + filter_handle_drag(wp, c, rettv); +} + +/* * popup_filter_yesno({text}, {options}) */ void @@ -1009,36 +1121,26 @@ f_popup_filter_yesno(typval_T *argvars, win_T *wp = win_id2wp(id); char_u *key = tv_get_string(&argvars[1]); typval_T res; + int c; // If the popup has been closed don't consume the key. if (wp == NULL) return; + c = *key; + if (c == K_SPECIAL && key[1] != NUL) + c = TO_SPECIAL(key[1], key[2]); + // consume all keys until done rettv->vval.v_number = 1; - if (STRCMP(key, "y") == 0 || STRCMP(key, "Y") == 0) + if (c == 'y' || c == 'Y') res.vval.v_number = 1; - else if (STRCMP(key, "n") == 0 || STRCMP(key, "N") == 0 - || STRCMP(key, "x") == 0 || STRCMP(key, "X") == 0 - || STRCMP(key, "\x1b") == 0) + else if (c == 'n' || c == 'N' || c == 'x' || c == 'X' || c == ESC) res.vval.v_number = 0; else { - int c = *key; - int row = mouse_row; - int col = mouse_col; - - if (c == K_SPECIAL && key[1] != NUL) - c = TO_SPECIAL(key[1], key[2]); - if (wp->w_popup_drag - && is_mouse_key(c) - && (wp == popup_dragwin - || wp == mouse_find_win(&row, &col, FIND_POPUP))) - // allow for dragging the popup - rettv->vval.v_number = 0; - - // ignore this key + filter_handle_drag(wp, c, rettv); return; } @@ -1057,6 +1159,18 @@ f_popup_dialog(typval_T *argvars, typval } /* + * popup_menu({text}, {options}) + */ + void +f_popup_menu(typval_T *argvars, typval_T *rettv) +{ + win_T *wp = popup_create(argvars, rettv, TYPE_MENU); + + if (wp != NULL) + popup_highlight_curline(wp); +} + +/* * popup_notification({text}, {options}) */ void diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro --- a/src/proto/popupwin.pro +++ b/src/proto/popupwin.pro @@ -8,8 +8,10 @@ void popup_adjust_position(win_T *wp); void f_popup_clear(typval_T *argvars, typval_T *rettv); void f_popup_create(typval_T *argvars, typval_T *rettv); void f_popup_atcursor(typval_T *argvars, typval_T *rettv); +void f_popup_filter_menu(typval_T *argvars, typval_T *rettv); void f_popup_filter_yesno(typval_T *argvars, typval_T *rettv); void f_popup_dialog(typval_T *argvars, typval_T *rettv); +void f_popup_menu(typval_T *argvars, typval_T *rettv); void f_popup_notification(typval_T *argvars, typval_T *rettv); void f_popup_close(typval_T *argvars, typval_T *rettv); void f_popup_hide(typval_T *argvars, typval_T *rettv); diff --git a/src/screen.c b/src/screen.c --- a/src/screen.c +++ b/src/screen.c @@ -4183,7 +4183,7 @@ win_line( */ v = (long)(ptr - line); cur = wp->w_match_head; - shl_flag = (screen_line_flags & SLF_POPUP); + shl_flag = FALSE; while (cur != NULL || shl_flag == FALSE) { if (shl_flag == FALSE @@ -4193,6 +4193,8 @@ win_line( { shl = &search_hl; shl_flag = TRUE; + if (screen_line_flags & SLF_POPUP) + continue; // do not use search_hl } else shl = &cur->hl; @@ -4272,9 +4274,9 @@ win_line( /* Use attributes from match with highest priority among * 'search_hl' and the match list. */ - search_attr = search_hl.attr_cur; cur = wp->w_match_head; shl_flag = FALSE; + search_attr = 0; while (cur != NULL || shl_flag == FALSE) { if (shl_flag == FALSE @@ -4284,6 +4286,8 @@ win_line( { shl = &search_hl; shl_flag = TRUE; + if (screen_line_flags & SLF_POPUP) + continue; // do not use search_hl } else shl = &cur->hl; @@ -5564,7 +5568,6 @@ win_line( { /* Use attributes from match with highest priority among * 'search_hl' and the match list. */ - char_attr = search_hl.attr; cur = wp->w_match_head; shl_flag = FALSE; while (cur != NULL || shl_flag == FALSE) @@ -5576,6 +5579,8 @@ win_line( { shl = &search_hl; shl_flag = TRUE; + if (screen_line_flags & SLF_POPUP) + continue; // do not use search_hl } else shl = &cur->hl; diff --git a/src/testdir/dumps/Test_popupwin_drag_01.dump b/src/testdir/dumps/Test_popupwin_drag_01.dump --- a/src/testdir/dumps/Test_popupwin_drag_01.dump +++ b/src/testdir/dumps/Test_popupwin_drag_01.dump @@ -3,8 +3,8 @@ |3| @73 |4| @73 |5| @73 -|6| @32|╔+0#0000001#ffd7ff255|═@5|╗| +0#0000000#ffffff0@32 -|7| @32|║+0#0000001#ffd7ff255|1@3| @1|║| +0#0000000#ffffff0@32 -|8| @32|║+0#0000001#ffd7ff255|2@5|║| +0#0000000#ffffff0@32 -|9| @32|║+0#0000001#ffd7ff255|3@4| |║| +0#0000000#ffffff0@32 -@34|╚+0#0000001#ffd7ff255|═@5|╝| +0#0000000#ffffff0@14|1|,|1| @10|T|o|p| +|6| @31|╔+0#0000001#ffd7ff255|═@5|╗| +0#0000000#ffffff0@33 +|7| @31|║+0#0000001#ffd7ff255|1@3| @1|║| +0#0000000#ffffff0@33 +|8| @31|║+0#0000001#ffd7ff255|2@5|║| +0#0000000#ffffff0@33 +|9| @31|║+0#0000001#ffd7ff255|3@4| |║| +0#0000000#ffffff0@33 +@33|╚+0#0000001#ffd7ff255|═@5|╝| +0#0000000#ffffff0@15|1|,|1| @10|T|o|p| diff --git a/src/testdir/dumps/Test_popupwin_drag_02.dump b/src/testdir/dumps/Test_popupwin_drag_02.dump --- a/src/testdir/dumps/Test_popupwin_drag_02.dump +++ b/src/testdir/dumps/Test_popupwin_drag_02.dump @@ -1,9 +1,9 @@ >1+0&#ffffff0| @73 -|2| @32|╔+0#0000001#ffd7ff255|═@5|╗| +0#0000000#ffffff0@32 -|3| @32|║+0#0000001#ffd7ff255|1@3| @1|║| +0#0000000#ffffff0@32 -|4| @32|║+0#0000001#ffd7ff255|2@5|║| +0#0000000#ffffff0@32 -|5| @32|║+0#0000001#ffd7ff255|3@4| |║| +0#0000000#ffffff0@32 -|6| @32|╚+0#0000001#ffd7ff255|═@5|╝| +0#0000000#ffffff0@32 +|2| @31|╔+0#0000001#ffd7ff255|═@5|╗| +0#0000000#ffffff0@33 +|3| @31|║+0#0000001#ffd7ff255|1@3| @1|║| +0#0000000#ffffff0@33 +|4| @31|║+0#0000001#ffd7ff255|2@5|║| +0#0000000#ffffff0@33 +|5| @31|║+0#0000001#ffd7ff255|3@4| |║| +0#0000000#ffffff0@33 +|6| @31|╚+0#0000001#ffd7ff255|═@5|╝| +0#0000000#ffffff0@33 |7| @73 |8| @73 |9| @73 diff --git a/src/testdir/dumps/Test_popupwin_menu_01.dump b/src/testdir/dumps/Test_popupwin_menu_01.dump new file mode 100644 --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_menu_01.dump @@ -0,0 +1,10 @@ +>1+0&#ffffff0| @73 +|2| @30|╔+0#0000001#ffd7ff255|═@8|╗| +0#0000000#ffffff0@31 +|3| @30|║+0#0000001#ffd7ff255| @8|║| +0#0000000#ffffff0@31 +|4| @30|║+0#0000001#ffd7ff255| |o+0#0000000#5fd7ff255|n|e| +0#0000001#ffd7ff255@4|║| +0#0000000#ffffff0@31 +|5| @30|║+0#0000001#ffd7ff255| |t|w|o| @4|║| +0#0000000#ffffff0@31 +|6| @30|║+0#0000001#ffd7ff255| |a|n|o|t|h|e|r| |║| +0#0000000#ffffff0@31 +|7| @30|║+0#0000001#ffd7ff255| @8|║| +0#0000000#ffffff0@31 +|8| @30|╚+0#0000001#ffd7ff255|═@8|╝| +0#0000000#ffffff0@31 +|9| @73 +@57|1|,|1| @10|T|o|p| diff --git a/src/testdir/dumps/Test_popupwin_menu_02.dump b/src/testdir/dumps/Test_popupwin_menu_02.dump new file mode 100644 --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_menu_02.dump @@ -0,0 +1,10 @@ +>1+0&#ffffff0| @73 +|2| @30|╔+0#0000001#ffd7ff255|═@8|╗| +0#0000000#ffffff0@31 +|3| @30|║+0#0000001#ffd7ff255| @8|║| +0#0000000#ffffff0@31 +|4| @30|║+0#0000001#ffd7ff255| |o|n|e| @4|║| +0#0000000#ffffff0@31 +|5| @30|║+0#0000001#ffd7ff255| |t|w|o| @4|║| +0#0000000#ffffff0@31 +|6| @30|║+0#0000001#ffd7ff255| |a+0#0000000#5fd7ff255|n|o|t|h|e|r| +0#0000001#ffd7ff255|║| +0#0000000#ffffff0@31 +|7| @30|║+0#0000001#ffd7ff255| @8|║| +0#0000000#ffffff0@31 +|8| @30|╚+0#0000001#ffd7ff255|═@8|╝| +0#0000000#ffffff0@31 +|9| @73 +@57|1|,|1| @10|T|o|p| diff --git a/src/testdir/dumps/Test_popupwin_menu_03.dump b/src/testdir/dumps/Test_popupwin_menu_03.dump new file mode 100644 --- /dev/null +++ b/src/testdir/dumps/Test_popupwin_menu_03.dump @@ -0,0 +1,10 @@ +>1+0&#ffffff0| @73 +|2| @73 +|3| @73 +|4| @73 +|5| @73 +|6| @73 +|7| @73 +|8| @73 +|9| @73 +|s|e|l|e|c|t|e|d| |3| @46|1|,|1| @10|T|o|p| 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 @@ -902,6 +902,64 @@ func Test_popup_dialog() delfunc QuitCallback endfunc +func ShowMenu(key, result) + let s:cb_res = 999 + let winid = popup_menu(['one', 'two', 'something else'], { + \ 'callback': 'QuitCallback', + \ }) + redraw + call feedkeys(a:key, "xt") + call assert_equal(winid, s:cb_winid) + call assert_equal(a:result, s:cb_res) +endfunc + +func Test_popup_menu() + func QuitCallback(id, res) + let s:cb_winid = a:id + let s:cb_res = a:res + endfunc + + let winid = ShowMenu(" ", 1) + let winid = ShowMenu("j \", 2) + let winid = ShowMenu("JjK \", 2) + let winid = ShowMenu("jjjjjj ", 3) + let winid = ShowMenu("kkk ", 1) + let winid = ShowMenu("x", -1) + let winid = ShowMenu("X", -1) + let winid = ShowMenu("\", -1) + let winid = ShowMenu("\", -1) + + delfunc QuitCallback +endfunc + +func Test_popup_menu_screenshot() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + + let lines =<< trim END + call setline(1, range(1, 20)) + hi PopupSelected ctermbg=lightblue + call popup_menu(['one', 'two', 'another'], {'callback': 'MenuDone'}) + func MenuDone(id, res) + echomsg "selected " .. a:res + endfunc + END + call writefile(lines, 'XtestPopupMenu') + let buf = RunVimInTerminal('-S XtestPopupMenu', {'rows': 10}) + call VerifyScreenDump(buf, 'Test_popupwin_menu_01', {}) + + call term_sendkeys(buf, "jj") + call VerifyScreenDump(buf, 'Test_popupwin_menu_02', {}) + + call term_sendkeys(buf, " ") + call VerifyScreenDump(buf, 'Test_popupwin_menu_03', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestPopupMenu') +endfunc + func Test_popup_close_callback() func PopupDone(id, result) let g:result = a:result diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -778,6 +778,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1558, +/**/ 1557, /**/ 1556,