From 353c351bd22e4dac4c3ae9cd7731032348f248c4 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 15 Mar 2020 14:19:26 +0100 Subject: [PATCH 01/94] patch 8.2.0382: some tests fail when run under valgrind Problem: Some tests fail when run under valgrind. Solution: Increase timeouts. --- src/testdir/dumps/Test_terminal_popup_1.dump | 2 +- src/testdir/dumps/Test_terminal_popup_2.dump | 2 +- src/testdir/dumps/Test_terminal_popup_3.dump | 2 +- src/testdir/dumps/Test_terminal_popup_5.dump | 2 +- src/testdir/dumps/Test_terminal_popup_6.dump | 2 +- src/testdir/dumps/Test_terminal_popup_7.dump | 2 +- src/testdir/dumps/Test_terminal_popup_8.dump | 2 +- src/testdir/dumps/Test_terminal_popup_m1.dump | 2 +- src/testdir/test_autocmd.vim | 2 +- src/testdir/test_channel.vim | 6 +++--- src/testdir/test_debugger.vim | 2 +- src/testdir/test_ins_complete.vim | 2 +- src/testdir/test_terminal.vim | 14 ++++++++------ src/version.c | 2 ++ 14 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/testdir/dumps/Test_terminal_popup_1.dump b/src/testdir/dumps/Test_terminal_popup_1.dump index 3e071498df..d4311ba596 100644 --- a/src/testdir/dumps/Test_terminal_popup_1.dump +++ b/src/testdir/dumps/Test_terminal_popup_1.dump @@ -12,4 +12,4 @@ |1@1| @11|╚+0#0000001#ffd7ff255|═@44|⇲| +0#0000000#ffffff0@13 |1|2| @72 |1|3| @72 -|t|e|r|m|i|n|a|l| |p|o|p|u|p| @42|1|,|1| @10|T|o|p| +|t|e|r|m|i|n|a|l| |p|o|p|u|p| @60 diff --git a/src/testdir/dumps/Test_terminal_popup_2.dump b/src/testdir/dumps/Test_terminal_popup_2.dump index 478d0b49a9..5e53e713d4 100644 --- a/src/testdir/dumps/Test_terminal_popup_2.dump +++ b/src/testdir/dumps/Test_terminal_popup_2.dump @@ -12,4 +12,4 @@ |1@1| @72 |1|2| @72 |1|3| @72 -|"|[|N|o| |N|a|m|e|]|"| |[|M|o|d|i|f|i|e|d|]| |2|0| |l|i|n|e|s| |-@1|5|%|-@1| @18|1|,|1| @10|T|o|p| +|"|[|N|o| |N|a|m|e|]|"| |[|M|o|d|i|f|i|e|d|]| |l|i|n|e| |1| |o|f| |2|0| |-@1|5|%|-@1| |c|o|l| |1| @26 diff --git a/src/testdir/dumps/Test_terminal_popup_3.dump b/src/testdir/dumps/Test_terminal_popup_3.dump index 6a6e0eb390..91770827db 100644 --- a/src/testdir/dumps/Test_terminal_popup_3.dump +++ b/src/testdir/dumps/Test_terminal_popup_3.dump @@ -12,4 +12,4 @@ |1@1| @11|╚+0&#a8a8a8255|═@44|⇲| +0&#ffffff0@13 |1|2| @72 |1|3| @72 -@57|0|,|0|-|1| @8|A|l@1| +@75 diff --git a/src/testdir/dumps/Test_terminal_popup_5.dump b/src/testdir/dumps/Test_terminal_popup_5.dump index 7dbf1a6162..42808b0c06 100644 --- a/src/testdir/dumps/Test_terminal_popup_5.dump +++ b/src/testdir/dumps/Test_terminal_popup_5.dump @@ -12,4 +12,4 @@ |1@1| @72 |1|2| @72 |1|3| @72 -|:|c|a|l@1| |C|l|o|s|e|P|o|p|u|p|(|)| @38|1|,|1| @10|T|o|p| +|:|c|a|l@1| |C|l|o|s|e|P|o|p|u|p|(|)| @56 diff --git a/src/testdir/dumps/Test_terminal_popup_6.dump b/src/testdir/dumps/Test_terminal_popup_6.dump index ebc67708ac..da5cf46938 100644 --- a/src/testdir/dumps/Test_terminal_popup_6.dump +++ b/src/testdir/dumps/Test_terminal_popup_6.dump @@ -12,4 +12,4 @@ |1@1| @11|╚+0#0000001#ffd7ff255|═@44|╝| +0#0000000#ffffff0@13 |1|2| @72 |1|3| @72 -@57|1|,|1| @10|A|l@1| +@75 diff --git a/src/testdir/dumps/Test_terminal_popup_7.dump b/src/testdir/dumps/Test_terminal_popup_7.dump index 6d0d7b69ac..e7e5aae50d 100644 --- a/src/testdir/dumps/Test_terminal_popup_7.dump +++ b/src/testdir/dumps/Test_terminal_popup_7.dump @@ -12,4 +12,4 @@ |1@1| @11|╚+0#0000001#ffd7ff255|═@44|╝| +0#0000000#ffffff0@13 |1|2| @72 |1|3| @72 -|-+2&&@1| |V|I|S|U|A|L| |-@1| +0&&@34|6| @8|3|,|6| @10|A|l@1| +|-+2&&@1| |V|I|S|U|A|L| |-@1| +0&&@51|6| @9 diff --git a/src/testdir/dumps/Test_terminal_popup_8.dump b/src/testdir/dumps/Test_terminal_popup_8.dump index f8f6d9edf5..da5cf46938 100644 --- a/src/testdir/dumps/Test_terminal_popup_8.dump +++ b/src/testdir/dumps/Test_terminal_popup_8.dump @@ -12,4 +12,4 @@ |1@1| @11|╚+0#0000001#ffd7ff255|═@44|╝| +0#0000000#ffffff0@13 |1|2| @72 |1|3| @72 -@57|1|,|1| @10|T|o|p| +@75 diff --git a/src/testdir/dumps/Test_terminal_popup_m1.dump b/src/testdir/dumps/Test_terminal_popup_m1.dump index 05a78f21ff..75432945ae 100644 --- a/src/testdir/dumps/Test_terminal_popup_m1.dump +++ b/src/testdir/dumps/Test_terminal_popup_m1.dump @@ -12,4 +12,4 @@ |1@1| @72 |1|2| @72 |1|3| @72 -|:| @55|1|,|1| @10|T|o|p| +|:| @73 diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim index 8a1d5d835b..7e3035ae42 100644 --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -2283,7 +2283,7 @@ func Test_autocmd_CmdWinEnter() call term_sendkeys(buf, "q:") call term_wait(buf) call term_sendkeys(buf, ":echo b:dummy_var\") - call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 1000) + call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 2000) call term_sendkeys(buf, ":echo &buftype\") call WaitForAssert({-> assert_notmatch('^nofile', term_getline(buf, 6))}, 1000) call term_sendkeys(buf, ":echo winnr\") diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim index e1f8a5e352..af767d2c40 100644 --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -1999,15 +1999,15 @@ func Test_issue_5150() endif let g:job = job_start(cmd, {}) call job_stop(g:job) - sleep 10m + sleep 50m call assert_equal(-1, job_info(g:job).exitval) let g:job = job_start(cmd, {}) call job_stop(g:job, 'term') - sleep 10m + sleep 50m call assert_equal(-1, job_info(g:job).exitval) let g:job = job_start(cmd, {}) call job_stop(g:job, 'kill') - sleep 10m + sleep 50m call assert_equal(-1, job_info(g:job).exitval) endfunc diff --git a/src/testdir/test_debugger.vim b/src/testdir/test_debugger.vim index c5418025bd..8ba3998f3f 100644 --- a/src/testdir/test_debugger.vim +++ b/src/testdir/test_debugger.vim @@ -8,7 +8,7 @@ source check.vim " If the expected output argument is supplied, then check for it. func RunDbgCmd(buf, cmd, ...) call term_sendkeys(a:buf, a:cmd . "\r") - call term_wait(a:buf) + call term_wait(a:buf, 20) if a:0 != 0 " Verify the expected output diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index 77045f4d09..3a83577394 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -429,7 +429,7 @@ func Test_pum_with_preview_win() let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12}) call term_wait(buf, 100) call term_sendkeys(buf, "Gi\\") - call term_wait(buf, 100) + call term_wait(buf, 200) call term_sendkeys(buf, "\") call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {}) diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim index 88e433d7d4..d763f78a7d 100644 --- a/src/testdir/test_terminal.vim +++ b/src/testdir/test_terminal.vim @@ -2342,6 +2342,7 @@ func Test_terminal_in_popup() \ 'call setline(1, range(20))', \ 'hi PopTerm ctermbg=grey', \ 'func OpenTerm(setColor)', + \ " set noruler", \ " let s:buf = term_start('" .. cmd .. " Xtext', #{hidden: 1, term_finish: 'close'})", \ ' let g:winid = popup_create(s:buf, #{minwidth: 45, minheight: 7, border: [], drag: 1, resize: 1})', \ ' if a:setColor', @@ -2360,11 +2361,11 @@ func Test_terminal_in_popup() \ ] call writefile(lines, 'XtermPopup') let buf = RunVimInTerminal('-S XtermPopup', #{rows: 15}) - call term_wait(buf, 100) - call term_sendkeys(buf, "\") + call term_wait(buf, 200) call term_sendkeys(buf, ":call OpenTerm(0)\") - call term_wait(buf, 100) + call term_wait(buf, 200) call term_sendkeys(buf, ":\") + call term_wait(buf, 200) call term_sendkeys(buf, "\:echo getwinvar(g:winid, \"&buftype\") win_gettype(g:winid)\") call VerifyScreenDump(buf, 'Test_terminal_popup_1', {}) @@ -2372,6 +2373,7 @@ func Test_terminal_in_popup() call VerifyScreenDump(buf, 'Test_terminal_popup_2', {}) call term_sendkeys(buf, ":call OpenTerm(1)\") + call term_wait(buf, 300) call term_sendkeys(buf, ":set hlsearch\") call term_sendkeys(buf, "/edit\") call VerifyScreenDump(buf, 'Test_terminal_popup_3', {}) @@ -2397,7 +2399,7 @@ func Test_terminal_in_popup() call term_wait(buf, 100) call term_sendkeys(buf, ":q\") - call term_wait(buf, 100) " wait for terminal to vanish + call term_wait(buf, 200) " wait for terminal to vanish call StopVimInTerminal(buf) call delete('Xtext') @@ -2425,8 +2427,8 @@ func Test_terminal_in_popup_min_size() \ ] call writefile(lines, 'XtermPopup') let buf = RunVimInTerminal('-S XtermPopup', #{rows: 15}) - call term_wait(buf, 100) - call term_sendkeys(buf, "\") + call term_wait(buf, 200) + call term_sendkeys(buf, ":set noruler\") call term_sendkeys(buf, ":call OpenTerm()\") call term_wait(buf, 100) call term_sendkeys(buf, ":\") diff --git a/src/version.c b/src/version.c index e49c628a33..b6b408da37 100644 --- a/src/version.c +++ b/src/version.c @@ -738,6 +738,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 382, /**/ 381, /**/ From 4f32f9c90e5710627467d74b415b3dc46fcc593a Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 15 Mar 2020 14:53:35 +0100 Subject: [PATCH 02/94] patch 8.2.0383: wrong feature check causes test not to be run Problem: Wrong feature check causes test not to be run. Solution: Use CheckFunction instead of CheckFeature. (Ozaki Kiichi, closes #5781) --- src/testdir/test_channel.vim | 2 +- src/version.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim index af767d2c40..2034aecfba 100644 --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -1775,7 +1775,7 @@ endfunc func Test_job_start_in_timer() CheckFeature timers - CheckFeature reltimefloat + CheckFunction reltimefloat func OutCb(chan, msg) let g:val += 1 diff --git a/src/version.c b/src/version.c index b6b408da37..b7ae44476d 100644 --- a/src/version.c +++ b/src/version.c @@ -738,6 +738,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 383, /**/ 382, /**/ From 5e4d1eba9579ea6b876ad699d77742e657505d35 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 15 Mar 2020 15:10:11 +0100 Subject: [PATCH 03/94] patch 8.2.0384: Travis CI has warnings Problem: Travis CI has warnings. Solution: Avoid warnings, clean up the config. (Ozaki Kiichi, closes #5779) --- .travis.yml | 16 +++++++++------- src/version.c | 2 ++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3f55cc90bd..a4fc6dda2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: c -anchors: +_anchors: envs: - &tiny-nogui BUILD=yes TEST=test COVERAGE=no FEATURES=tiny "CONFOPT='--disable-gui'" SHADOWOPT= SRCDIR=./src CHECK_AUTOCONF=no @@ -85,6 +85,10 @@ anchors: osx: &osx os: osx + before_script: + - do_test() { "$@"; } + + homebrew: &osx-homebrew addons: homebrew: packages: @@ -93,13 +97,11 @@ anchors: cache: directories: - /usr/local/Homebrew/Library/Homebrew/vendor/ - - /usr/local/Homebrew/Library/Taps/ + - /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/ before_install: - rvm reset # Lua is not installed on Travis OSX - export LUA_PREFIX=/usr/local - before_script: - - do_test() { "$@"; } coverage: &coverage - ~/.local/bin/coveralls -b "${SRCDIR}" -x .xs -e "${SRCDIR}"/if_perl.c -e "${SRCDIR}"/xxd -e "${SRCDIR}"/libvterm --encodings utf-8 @@ -111,8 +113,6 @@ anchors: asan_symbolize < "${log}" done < <(find . -type f -name 'asan.*' -size +0) -sudo: false - branches: except: - /^v[0-9]/ @@ -155,7 +155,7 @@ script: # exclude some builds on mac os x and linux # on mac os x "tiny" is always without GUI # linux: 2*compiler + 5*env + mac: 2*compiler + 2*env -matrix: +jobs: include: - <<: *osx name: tiny-nogui/clang @@ -166,10 +166,12 @@ matrix: compiler: gcc env: *tiny-nogui - <<: *osx + <<: *osx-homebrew name: huge/clang compiler: clang env: *osx-huge - <<: *osx + <<: *osx-homebrew name: huge/gcc compiler: gcc env: *osx-huge diff --git a/src/version.c b/src/version.c index b7ae44476d..e9e1ec908b 100644 --- a/src/version.c +++ b/src/version.c @@ -738,6 +738,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 384, /**/ 383, /**/ From 0eabd4dc8ff50658f0ea0e92c7918a42242f6b80 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Sun, 15 Mar 2020 16:13:53 +0100 Subject: [PATCH 04/94] patch 8.2.0385: menu functionality insufficiently tested Problem: Menu functionality insufficiently tested. Solution: Add tests. Add menu_info(). (Yegappan Lakshmanan, closes #5760) --- runtime/doc/eval.txt | 62 ++++++ runtime/doc/gui.txt | 4 +- runtime/doc/usr_41.txt | 3 +- src/evalfunc.c | 13 +- src/menu.c | 269 ++++++++++++++++++++++--- src/proto/menu.pro | 1 + src/testdir/test_menu.vim | 357 +++++++++++++++++++++++++++++++++ src/testdir/test_popup.vim | 6 + src/testdir/test_termcodes.vim | 35 ++++ src/version.c | 2 + 10 files changed, 721 insertions(+), 31 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 5dfc08ea48..e3bb34eb7a 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2601,6 +2601,7 @@ matchstr({expr}, {pat} [, {start} [, {count}]]) matchstrpos({expr}, {pat} [, {start} [, {count}]]) List {count}'th match of {pat} in {expr} max({expr}) Number maximum value of items in {expr} +menu_info({name} [, {mode}]) Dict get menu item information min({expr}) Number minimum value of items in {expr} mkdir({name} [, {path} [, {prot}]]) Number create directory {name} @@ -7124,6 +7125,7 @@ matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()* Can also be used as a |method|: > GetText()->matchstrpos('word') < + *max()* max({expr}) Return the maximum value of all items in {expr}. {expr} can be a List or a Dictionary. For a Dictionary, @@ -7135,6 +7137,66 @@ max({expr}) Return the maximum value of all items in {expr}. Can also be used as a |method|: > mylist->max() + +menu_info({name} [, {mode}]) *menu_info()* + Return information about the specified menu {name} in + mode {mode}. The menu name should be specified without the + shortcut character ('&'). + + {mode} can be one of these strings: + "n" Normal + "v" Visual (including Select) + "o" Operator-pending + "i" Insert + "c" Cmd-line + "s" Select + "x" Visual + "t" Terminal-Job + "" Normal, Visual and Operator-pending + "!" Insert and Cmd-line + When {mode} is omitted, the modes for "" are used. + + Returns a |Dictionary| containing the following items: + accel menu item accelerator text |menu-text| + display display name (name without '&') + enabled v:true if this menu item is enabled + Refer to |:menu-enable| + icon name of the icon file (for toolbar) + |toolbar-icon| + iconidx index of a built-in icon + modes modes for which the menu is defined. In + addition to the modes mentioned above, these + characters will be used: + " " Normal, Visual and Operator-pending + name menu item name. + noremenu v:true if the {rhs} of the menu item is not + remappable else v:false. + priority menu order priority |menu-priority| + rhs right-hand-side of the menu item. The returned + string has special characters translated like + in the output of the ":menu" command listing. + When the {rhs} of a menu item is empty, then + "" is returned. + script v:true if script-local remapping of {rhs} is + allowed else v:false. See |:menu-script|. + shortcut shortcut key (character after '&' in + the menu name) |menu-shortcut| + silent v:true if the menu item is created + with argument |:menu-silent| + submenus |List| containing the names of + all the submenus. Present only if the menu + item has submenus. + + Returns an empty dictionary if the menu item is not found. + + Examples: > + :echo maparg('Edit.Cut') + :echo maparg('File.Save', 'n') +< + Can also be used as a |method|: > + GetMenuName()->maparg('v') + + < *min()* min({expr}) Return the minimum value of all items in {expr}. {expr} can be a List or a Dictionary. For a Dictionary, diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index 1a38496040..41e20b1060 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -578,9 +578,11 @@ tooltips for menus. See |terminal-typing|. Special characters in a menu name: + *menu-shortcut* & The next character is the shortcut key. Make sure each shortcut key is only used once in a (sub)menu. If you want to insert a literal "&" in the menu name use "&&". + *menu-text* Separates the menu name from right-aligned text. This can be used to show the equivalent typed command. The text "" can be used here for convenience. If you are using a real @@ -954,7 +956,7 @@ item for the keyword under the cursor. The register "z" is used. > mappings, or put these lines in your gvimrc; "" is CTRL-R, "" is the key. |<>|) - + *tooltips* *menu-tips* 5.8 Tooltips & Menu tips See section |42.4| in the user manual. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 1bed01f418..dfd64c85f2 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -942,10 +942,11 @@ Window size and position: *window-size-functions* winsaveview() get view of current window winrestview() restore saved view of current window -Mappings: *mapping-functions* +Mappings and Menus: *mapping-functions* hasmapto() check if a mapping exists mapcheck() check if a matching mapping exists maparg() get rhs of a mapping + menu_info() get information about a menu item wildmenumode() check if the wildmode is active Testing: *test-functions* diff --git a/src/evalfunc.c b/src/evalfunc.c index 9e8e0fed0b..5db6718fb6 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -646,6 +646,7 @@ static funcentry_T global_functions[] = {"matchstr", 2, 4, FEARG_1, ret_string, f_matchstr}, {"matchstrpos", 2, 4, FEARG_1, ret_list_any, f_matchstrpos}, {"max", 1, 1, FEARG_1, ret_any, f_max}, + {"menu_info", 1, 2, FEARG_1, ret_dict_any, f_menu_info}, {"min", 1, 1, FEARG_1, ret_any, f_min}, {"mkdir", 1, 3, FEARG_1, ret_number, f_mkdir}, {"mode", 0, 1, FEARG_1, ret_string, f_mode}, @@ -2469,7 +2470,17 @@ f_feedkeys(typval_T *argvars, typval_T *rettv UNUSED) if (lowlevel) { #ifdef USE_INPUT_BUF - add_to_input_buf(keys, (int)STRLEN(keys)); + int idx; + int len = (int)STRLEN(keys); + + for (idx = 0; idx < len; ++idx) + { + // if a CTRL-C was typed, set got_int + if (keys[idx] == 3 && ctrl_c_interrupts) + got_int = TRUE; + else + add_to_input_buf(keys + idx, 1); + } #else emsg(_("E980: lowlevel input not supported")); #endif diff --git a/src/menu.c b/src/menu.c index c57a6c1a6f..8e33cff765 100644 --- a/src/menu.c +++ b/src/menu.c @@ -1684,6 +1684,49 @@ get_menu_cmd_modes( return modes; } +/* + * Return the string representation of the menu modes. Does the opposite + * of get_menu_cmd_modes(). + */ + static char_u * +get_menu_mode_str(int modes) +{ + if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE | + MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE)) + == (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE | + MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE)) + return (char_u *)"a"; + if ((modes & (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | + MENU_OP_PENDING_MODE)) + == (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | + MENU_OP_PENDING_MODE)) + return (char_u *)" "; + if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE)) + == (MENU_INSERT_MODE | MENU_CMDLINE_MODE)) + return (char_u *)"!"; + if ((modes & (MENU_VISUAL_MODE | MENU_SELECT_MODE)) + == (MENU_VISUAL_MODE | MENU_SELECT_MODE)) + return (char_u *)"v"; + if (modes & MENU_VISUAL_MODE) + return (char_u *)"x"; + if (modes & MENU_SELECT_MODE) + return (char_u *)"s"; + if (modes & MENU_OP_PENDING_MODE) + return (char_u *)"o"; + if (modes & MENU_INSERT_MODE) + return (char_u *)"i"; + if (modes & MENU_TERMINAL_MODE) + return (char_u *)"tl"; + if (modes & MENU_CMDLINE_MODE) + return (char_u *)"c"; + if (modes & MENU_NORMAL_MODE) + return (char_u *)"n"; + if (modes & MENU_TIP_MODE) + return (char_u *)"t"; + + return (char_u *)""; +} + /* * Modify a menu name starting with "PopUp" to include the mode character. * Returns the name in allocated memory (NULL for failure). @@ -2393,40 +2436,21 @@ execute_menu(exarg_T *eap, vimmenu_T *menu, int mode_idx) } /* - * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and - * execute it. + * Lookup a menu by the descriptor name e.g. "File.New" + * Returns NULL if the menu is not found */ - void -ex_emenu(exarg_T *eap) + static vimmenu_T * +menu_getbyname(char_u *name_arg) { - vimmenu_T *menu; char_u *name; char_u *saved_name; - char_u *arg = eap->arg; + vimmenu_T *menu; char_u *p; int gave_emsg = FALSE; - int mode_idx = -1; - if (arg[0] && VIM_ISWHITE(arg[1])) - { - switch (arg[0]) - { - case 'n': mode_idx = MENU_INDEX_NORMAL; break; - case 'v': mode_idx = MENU_INDEX_VISUAL; break; - case 's': mode_idx = MENU_INDEX_SELECT; break; - case 'o': mode_idx = MENU_INDEX_OP_PENDING; break; - case 't': mode_idx = MENU_INDEX_TERMINAL; break; - case 'i': mode_idx = MENU_INDEX_INSERT; break; - case 'c': mode_idx = MENU_INDEX_CMDLINE; break; - default: semsg(_(e_invarg2), arg); - return; - } - arg = skipwhite(arg + 2); - } - - saved_name = vim_strsave(arg); + saved_name = vim_strsave(name_arg); if (saved_name == NULL) - return; + return NULL; menu = *get_root_menu(saved_name); name = saved_name; @@ -2463,10 +2487,45 @@ ex_emenu(exarg_T *eap) if (menu == NULL) { if (!gave_emsg) - semsg(_("E334: Menu not found: %s"), arg); - return; + semsg(_("E334: Menu not found: %s"), name_arg); + return NULL; } + return menu; +} + +/* + * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and + * execute it. + */ + void +ex_emenu(exarg_T *eap) +{ + vimmenu_T *menu; + char_u *arg = eap->arg; + int mode_idx = -1; + + if (arg[0] && VIM_ISWHITE(arg[1])) + { + switch (arg[0]) + { + case 'n': mode_idx = MENU_INDEX_NORMAL; break; + case 'v': mode_idx = MENU_INDEX_VISUAL; break; + case 's': mode_idx = MENU_INDEX_SELECT; break; + case 'o': mode_idx = MENU_INDEX_OP_PENDING; break; + case 't': mode_idx = MENU_INDEX_TERMINAL; break; + case 'i': mode_idx = MENU_INDEX_INSERT; break; + case 'c': mode_idx = MENU_INDEX_CMDLINE; break; + default: semsg(_(e_invarg2), arg); + return; + } + arg = skipwhite(arg + 2); + } + + menu = menu_getbyname(arg); + if (menu == NULL) + return; + // Found the menu, so execute. execute_menu(eap, menu, mode_idx); } @@ -2773,4 +2832,158 @@ menu_translate_tab_and_shift(char_u *arg_start) return arg; } +/* + * Get the information about a menu item in mode 'which' + */ + static int +menuitem_getinfo(vimmenu_T *menu, int modes, dict_T *dict) +{ + int status; + + if (menu_is_tearoff(menu->dname)) // skip tearoff menu item + return OK; + + status = dict_add_string(dict, "name", menu->name); + if (status == OK) + status = dict_add_string(dict, "display", menu->dname); + if (status == OK && menu->actext != NULL) + status = dict_add_string(dict, "accel", menu->actext); + if (status == OK) + status = dict_add_number(dict, "priority", menu->priority); + if (status == OK) + status = dict_add_string(dict, "modes", + get_menu_mode_str(menu->modes)); +#ifdef FEAT_TOOLBAR + if (status == OK && menu->iconfile != NULL) + status = dict_add_string(dict, "icon", menu->iconfile); + if (status == OK && menu->iconidx >= 0) + status = dict_add_number(dict, "iconidx", menu->iconidx); +#endif + if (status == OK) + { + char_u buf[NUMBUFLEN]; + + if (has_mbyte) + buf[utf_char2bytes(menu->mnemonic, buf)] = NUL; + else + { + buf[0] = (char_u)menu->mnemonic; + buf[1] = NUL; + } + status = dict_add_string(dict, "shortcut", buf); + } + if (status == OK && menu->children == NULL) + { + int bit; + + // Get the first mode in which the menu is available + for (bit = 0; (bit < MENU_MODES) && !((1 << bit) & modes); bit++) + ; + if (menu->strings[bit] != NULL) + status = dict_add_string(dict, "rhs", + *menu->strings[bit] == NUL ? + vim_strsave((char_u *)"") : + str2special_save(menu->strings[bit], FALSE)); + if (status == OK) + status = dict_add_bool(dict, "noremenu", + menu->noremap[bit] == REMAP_NONE); + if (status == OK) + status = dict_add_bool(dict, "script", + menu->noremap[bit] == REMAP_SCRIPT); + if (status == OK) + status = dict_add_bool(dict, "silent", menu->silent[bit]); + if (status == OK) + status = dict_add_bool(dict, "enabled", + ((menu->enabled & (1 << bit)) != 0)); + } + // If there are submenus, add all the submenu display names + if (status == OK && menu->children != NULL) + { + list_T *l = list_alloc(); + vimmenu_T *child; + + if (l == NULL) + return FAIL; + + dict_add_list(dict, "submenus", l); + child = menu->children; + while (child) + { + if (!menu_is_tearoff(child->dname)) // skip tearoff menu + list_append_string(l, child->dname, -1); + child = child->next; + } + } + + return status; +} + +/* + * "menu_info()" function + * Return information about a menu (including all the child menus) + */ + void +f_menu_info(typval_T *argvars, typval_T *rettv) +{ + char_u *menu_name; + char_u *which; + int modes; + char_u *saved_name; + char_u *name; + vimmenu_T *menu; + dict_T *retdict; + + if (rettv_dict_alloc(rettv) != OK) + return; + retdict = rettv->vval.v_dict; + + menu_name = tv_get_string_chk(&argvars[0]); + if (menu_name == NULL) + return; + + // menu mode + if (argvars[1].v_type != VAR_UNKNOWN) + which = tv_get_string_chk(&argvars[1]); + else + which = (char_u *)""; // Default is modes for "menu" + if (which == NULL) + return; + + modes = get_menu_cmd_modes(which, *which == '!', NULL, NULL); + + // Locate the specified menu or menu item + menu = *get_root_menu(menu_name); + saved_name = vim_strsave(menu_name); + if (saved_name == NULL) + return; + if (*saved_name != NUL) + { + char_u *p; + + name = saved_name; + while (*name) + { + // Find in the menu hierarchy + p = menu_name_skip(name); + while (menu != NULL) + { + if (menu_name_equal(name, menu)) + break; + menu = menu->next; + } + if (menu == NULL || *p == NUL) + break; + menu = menu->children; + name = p; + } + } + vim_free(saved_name); + + if (menu == NULL) // specified menu not found + return; + + if (menu->modes & modes) + menuitem_getinfo(menu, modes, retdict); +} + #endif // FEAT_MENU diff --git a/src/proto/menu.pro b/src/proto/menu.pro index 95ee48b541..b95f5316de 100644 --- a/src/proto/menu.pro +++ b/src/proto/menu.pro @@ -23,4 +23,5 @@ void ex_emenu(exarg_T *eap); void winbar_click(win_T *wp, int col); vimmenu_T *gui_find_menu(char_u *path_name); void ex_menutranslate(exarg_T *eap); +void f_menu_info(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */ diff --git a/src/testdir/test_menu.vim b/src/testdir/test_menu.vim index 4af75be514..5e7a77b249 100644 --- a/src/testdir/test_menu.vim +++ b/src/testdir/test_menu.vim @@ -89,6 +89,35 @@ func Test_menu_commands() unlet g:did_menu endfun +" Test various menu related errors +func Test_menu_errors() + menu Test.Foo :version + + " Error cases + call assert_fails('menu .Test.Foo :ls', 'E475:') + call assert_fails('menu Test. :ls', 'E330:') + call assert_fails('menu Foo. :ls', 'E331:') + call assert_fails('unmenu Test.Foo abc', 'E488:') + call assert_fails('menu :ls :ls', 'E792:') + call assert_fails('menu Test.:ls :ls', 'E792:') + call assert_fails('menu Test.Foo.Bar :ls', 'E327:') + call assert_fails('menu Test.-Sep-.Baz :ls', 'E332:') + call assert_fails('menu Foo.Bar.--.Baz :ls', 'E332:') + call assert_fails('menu disable Test.Foo.Bar', 'E327:') + call assert_fails('menu disable T.Foo', 'E329:') + call assert_fails('unmenu Test.Foo.Bar', 'E327:') + call assert_fails('cunmenu Test.Foo', 'E328:') + call assert_fails('unmenu Test.Bar', 'E329:') + call assert_fails('menu Test.Foo.Bar', 'E327:') + call assert_fails('cmenu Test.Foo', 'E328:') + call assert_fails('emenu x Test.Foo', 'E475:') + call assert_fails('emenu Test.Foo.Bar', 'E334:') + call assert_fails('menutranslate Test', 'E474:') + + silent! unmenu Foo + unmenu Test +endfunc + " Test for menu item completion in command line func Test_menu_expand() " Create the menu itmes for test @@ -119,8 +148,336 @@ func Test_menu_expand() \ "\\\"\", 'xt') call assert_equal('"emenu Buffers. Xmenu.', @:) + " Test for expanding only submenus + call feedkeys(":popup Xmenu.\\\"\", 'xt') + call assert_equal('"popup Xmenu.A1 A2 A3 A4', @:) + + " Test for expanding menus after enable/disable + call feedkeys(":menu enable Xmenu.\\\"\", 'xt') + call assert_equal('"menu enable Xmenu.A1. A2. A3. A4.', @:) + call feedkeys(":menu disable Xmenu.\\\"\", 'xt') + call assert_equal('"menu disable Xmenu.A1. A2. A3. A4.', @:) + + " Test for expanding non-existing menu path + call feedkeys(":menu xyz.\\\"\", 'xt') + call assert_equal('"menu xyz.', @:) + call feedkeys(":menu Xmenu.A1.A1B1.xyz.\\\"\", 'xt') + call assert_equal('"menu Xmenu.A1.A1B1.xyz.', @:) + set wildmenu& unmenu Xmenu + + " Test for expanding popup menus with some hidden items + menu Xmenu.foo.A1 a1 + menu Xmenu.]bar bar + menu Xmenu.]baz.B1 b1 + menu Xmenu.-sep- : + call feedkeys(":popup Xmenu.\\\"\", 'xt') + call assert_equal('"popup Xmenu.foo', @:) + unmenu Xmenu + +endfunc + +" Test for the menu_info() function +func Test_menu_info() + " Define menus with various attributes + 10nnoremenu 10.10 T&est.F&oo :echo 'foo' + 10nmenu 10.20 T&est.B&ar:bar :echo 'bar' + 10nmenu