diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index b5e8b7a99a..2e0c1f8cc4 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -7020,7 +7020,8 @@ A jump table for the options with a short description can be found at |Q_op|. *'wildoptions'* *'wop'* 'wildoptions' 'wop' string (default "pum,tagfile") global - List of words that change how |cmdline-completion| is done. + A list of words that change how |cmdline-completion| is done. + The following values are supported: pum Display the completion matches using the popup menu in the same style as the |ins-completion-menu|. tagfile When using CTRL-D to list matching tags, the kind of diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 76beaf9830..53effa1443 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -207,7 +207,6 @@ Commands: |:checkhealth| |:drop| is always available |:Man| is available by default, with many improvements such as completion - |:sign-define| accepts a `numhl` argument, to highlight the line number |:match| can be invoked before highlight group is defined |:source| works with Lua User commands can support |:command-preview| to show results as you type @@ -374,6 +373,9 @@ Commands: |:doautocmd| does not warn about "No matching autocommands". |:wincmd| accepts a count. +Command line completion: + The meanings of arrow keys do not change depending on 'wildoptions'. + Functions: |input()| and |inputdialog()| support for each other’s features (return on cancel and completion respectively) via dictionary argument (replaces all diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 56fc39c83d..e82e98ba4e 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -258,7 +258,7 @@ void cmdline_pum_display(bool changed_array) bool cmdline_pum_active(void) { - // return p_wmnu && pum_visible() && compl_match_array != NULL; + // compl_match_array != NULL should already imply pum_visible() in Nvim. return compl_match_array != NULL; } @@ -269,6 +269,12 @@ void cmdline_pum_remove(void) XFREE_CLEAR(compl_match_array); } +void cmdline_pum_cleanup(CmdlineInfo *cclp) +{ + cmdline_pum_remove(); + wildmenu_cleanup(cclp); +} + /// Do wildcard expansion on the string 'str'. /// Chars that should not be expanded must be preceded with a backslash. /// Return a pointer to allocated memory containing the new string. diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e15244b515..9c3c859771 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -9633,7 +9633,7 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "wildmenumode()" function static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (wild_menu_showing || ((State & MODE_CMDLINE) && pum_visible())) { + if (wild_menu_showing || ((State & MODE_CMDLINE) && cmdline_pum_active())) { rettv->vval.v_number = 1; } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 5a5491c79e..a0bcccb5be 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1801,6 +1801,10 @@ static int command_line_handle_key(CommandLineState *s) if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) { break; } + if (cmdline_pum_active()) { + cmdline_pum_cleanup(&ccline); + s->xpc.xp_context = EXPAND_NOTHING; + } return command_line_changed(s); case Ctrl_L: diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index b9f027afb2..e10fd64560 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1857,6 +1857,179 @@ func Test_recalling_cmdline() cunmap (save-cmdline) endfunc +" Test for using a popup menu for the command line completion matches +" (wildoptions=pum) +func Test_wildmenu_pum() + CheckRunVimInTerminal + + let commands =<< trim [CODE] + set wildmenu + set wildoptions=pum + set shm+=I + set noruler + set noshowcmd + [CODE] + call writefile(commands, 'Xtest') + + let buf = RunVimInTerminal('-S Xtest', #{rows: 10}) + + call term_sendkeys(buf, ":sign \") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_01', {}) + + call term_sendkeys(buf, "\\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_02', {}) + + call term_sendkeys(buf, "\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_03', {}) + + call term_sendkeys(buf, "\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_04', {}) + + call term_sendkeys(buf, "\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_05', {}) + + " pressing should end completion and go back to the original match + call term_sendkeys(buf, "\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_06', {}) + + " pressing should select the current match and end completion + call term_sendkeys(buf, "\\\\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_07', {}) + + " With 'wildmode' set to 'longest,full', completing a match should display + " the longest match, the wildmenu should not be displayed. + call term_sendkeys(buf, ":\set wildmode=longest,full\") + call TermWait(buf) + call term_sendkeys(buf, ":sign u\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_08', {}) + + " pressing should display the wildmenu + call term_sendkeys(buf, "\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_09', {}) + + " pressing second time should select the next entry in the menu + call term_sendkeys(buf, "\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_10', {}) + + call term_sendkeys(buf, ":\set wildmode=full\") + " " showing popup menu in different columns in the cmdline + call term_sendkeys(buf, ":sign define \") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_11', {}) + + call term_sendkeys(buf, " \") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_12', {}) + + call term_sendkeys(buf, " \") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_13', {}) + + " Directory name completion + call mkdir('Xdir/XdirA/XdirB', 'p') + call writefile([], 'Xdir/XfileA') + call writefile([], 'Xdir/XdirA/XfileB') + call writefile([], 'Xdir/XdirA/XdirB/XfileC') + + call term_sendkeys(buf, "\e Xdi\\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_14', {}) + + " Pressing on a directory name should go into that directory + call term_sendkeys(buf, "\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_15', {}) + + " Pressing on a directory name should go to the parent directory + call term_sendkeys(buf, "\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_16', {}) + + " Pressing when the popup menu is displayed should list all the + " matches and remove the popup menu + call term_sendkeys(buf, "\sign \\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_17', {}) + + " Pressing when the popup menu is displayed should remove the popup + " menu + call term_sendkeys(buf, "\sign \\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_18', {}) + + " Pressing should open the popup menu with the last entry selected + call term_sendkeys(buf, "\\:sign \\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_19', {}) + + " Pressing should close the popup menu and cancel the cmd line + call term_sendkeys(buf, "\\:sign \\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_20', {}) + + " Typing a character when the popup is open, should close the popup + call term_sendkeys(buf, ":sign \x") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_21', {}) + + " When the popup is open, entering the cmdline window should close the popup + call term_sendkeys(buf, "\sign \\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_22', {}) + call term_sendkeys(buf, ":q\") + + " After the last popup menu item, should show the original string + call term_sendkeys(buf, ":sign u\\\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_23', {}) + + " Use the popup menu for the command name + call term_sendkeys(buf, "\bu\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_24', {}) + + " Pressing the left arrow should remove the popup menu + call term_sendkeys(buf, "\\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_25', {}) + + " Pressing should remove the popup menu and erase the last character + call term_sendkeys(buf, "\\sign \\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_26', {}) + + " Pressing should remove the popup menu and erase the previous word + call term_sendkeys(buf, "\\sign \\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_27', {}) + + " Pressing should remove the popup menu and erase the entire line + call term_sendkeys(buf, "\\sign \\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_28', {}) + + " Using to cancel the popup menu and then pressing should recall + " the cmdline from history + call term_sendkeys(buf, "sign xyz\:sign \\\") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_29', {}) + + call term_sendkeys(buf, "\\") + call StopVimInTerminal(buf) + call delete('Xtest') + call delete('Xdir', 'rf') +endfunc + " this was going over the end of IObuff func Test_report_error_with_composing() let caught = 'no' diff --git a/test/functional/fixtures/compdir/file1 b/test/functional/fixtures/wildpum/Xdir/XdirA/XdirB/XfileC similarity index 100% rename from test/functional/fixtures/compdir/file1 rename to test/functional/fixtures/wildpum/Xdir/XdirA/XdirB/XfileC diff --git a/test/functional/fixtures/compdir/file2 b/test/functional/fixtures/wildpum/Xdir/XdirA/XfileB similarity index 100% rename from test/functional/fixtures/compdir/file2 rename to test/functional/fixtures/wildpum/Xdir/XdirA/XfileB diff --git a/test/functional/fixtures/wildpum/Xdir/XfileA b/test/functional/fixtures/wildpum/Xdir/XfileA new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional/fixtures/wildpum/compdir/file1 b/test/functional/fixtures/wildpum/compdir/file1 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional/fixtures/wildpum/compdir/file2 b/test/functional/fixtures/wildpum/compdir/file2 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 7b0005bcf1..f148e2643f 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -7,7 +7,6 @@ local insert = helpers.insert local meths = helpers.meths local command = helpers.command local funcs = helpers.funcs -local get_pathsep = helpers.get_pathsep local eq = helpers.eq local pcall_err = helpers.pcall_err local exec_lua = helpers.exec_lua @@ -1785,6 +1784,8 @@ describe('builtin popupmenu', function() screen:try_resize(32,10) command('set wildmenu') command('set wildoptions=pum') + command('set shellslash') + command("cd test/functional/fixtures/wildpum") feed(':sign ') screen:expect([[ @@ -1800,7 +1801,7 @@ describe('builtin popupmenu', function() :sign ^ | ]]) - feed('') + feed('') screen:expect([[ | {1:~ }| @@ -1814,21 +1815,199 @@ describe('builtin popupmenu', function() :sign define^ | ]]) - feed('') + feed('') screen:expect([[ | {1:~ }| {1:~ }| {1:~ }{n: define }{1: }| {1:~ }{n: jump }{1: }| + {1:~ }{s: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign list^ | + ]]) + + feed('') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{n: list }{1: }| + {1:~ }{s: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign place^ | + ]]) + + feed('') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{n: jump }{1: }| + {1:~ }{s: list }{1: }| + {1:~ }{n: place }{1: }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign list^ | + ]]) + + feed('') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{n: define }{1: }| + {1:~ }{s: jump }{1: }| {1:~ }{n: list }{1: }| {1:~ }{n: place }{1: }| {1:~ }{n: undefine }{1: }| {1:~ }{n: unplace }{1: }| + :sign jump^ | + ]]) + + -- pressing should end completion and go back to the original match + feed('') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| :sign ^ | ]]) - feed('') + -- pressing should select the current match and end completion + feed('') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign unplace^ | + ]]) + + -- showing popup menu in different columns in the cmdline + feed('sign define ') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: culhl= }{1: }| + {1:~ }{n: icon= }{1: }| + {1:~ }{n: linehl= }{1: }| + {1:~ }{n: numhl= }{1: }| + {1:~ }{n: text= }{1: }| + {1:~ }{n: texthl= }{1: }| + :sign define culhl=^ | + ]]) + + feed('') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }{s: culhl= }{1: }| + {1:~ }{n: icon= }{1: }| + {1:~ }{n: linehl= }{1: }| + {1:~ }{n: numhl= }{1: }| + {1:~ }{n: text= }{1: }| + {1:~ }{n: texthl= }{1: }| + :sign define culhl= culhl=^ | + ]]) + + feed('e Xdi') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{s: XdirA/ }{1: }| + {1:~ }{n: XfileA }{1: }| + :e Xdir/XdirA/^ | + ]]) + + -- Pressing on a directory name should go into that directory + feed('') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{s: XdirB/ }{1: }| + {1:~ }{n: XfileB }{1: }| + :e Xdir/XdirA/XdirB/^ | + ]]) + + -- Pressing on a directory name should go to the parent directory + feed('') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{s: XdirA/ }{1: }| + {1:~ }{n: XfileA }{1: }| + :e Xdir/XdirA/^ | + ]]) + + -- Pressing when the popup menu is displayed should list all the + -- matches and remove the popup menu + feed(':sign ') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4: }| + :sign define jump list place und| + efine unplace^ | + ]]) + + -- Pressing when the popup menu is displayed should remove the popup + -- menu + feed('sign ') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4: }| + :sign define | + define | + :sign define^ | + ]]) + + -- Pressing should open the popup menu with the last entry selected + feed(':sign ') screen:expect([[ | {1:~ }| @@ -1837,12 +2016,28 @@ describe('builtin popupmenu', function() {1:~ }{n: jump }{1: }| {1:~ }{n: list }{1: }| {1:~ }{n: place }{1: }| - {1:~ }{n: undefine }{1: }| - {1:~ }{s: unplace }{1: }| - :sign unplace^ | + {1:~ }{s: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign undefine^ | ]]) - feed('x') + -- Pressing should close the popup menu and cancel the cmd line + feed(':sign ') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + -- Typing a character when the popup is open, should close the popup + feed(':sign x') screen:expect([[ | {1:~ }| @@ -1853,7 +2048,114 @@ describe('builtin popupmenu', function() {1:~ }| {1:~ }| {1:~ }| - :sign unplacex^ | + :sign definex^ | + ]]) + + -- When the popup is open, entering the cmdline window should close the popup + feed('sign ') + screen:expect([[ + | + {3:[No Name] }| + {1::}sign define | + {1::}sign define^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:[Command Line] }| + :sign define | + ]]) + feed(':q') + + -- After the last popup menu item, should show the original string + feed(':sign u') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign u^ | + ]]) + + -- Use the popup menu for the command name + feed('bu') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {s: bufdo }{1: }| + {n: buffer }{1: }| + {n: buffers }{1: }| + {n: bunload }{1: }| + :bufdo^ | + ]]) + + -- Pressing should remove the popup menu and erase the last character + feed('sign ') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign defin^ | + ]]) + + -- Pressing should remove the popup menu and erase the previous word + feed('sign ') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign ^ | + ]]) + + -- Pressing should remove the popup menu and erase the entire line + feed('sign ') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :^ | + ]]) + + -- Using to cancel the popup menu and then pressing should recall + -- the cmdline from history + feed('sign xyz:sign ') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :sign xyz^ | ]]) feed('') @@ -1932,7 +2234,6 @@ describe('builtin popupmenu', function() feed('') command("close") command('set wildmode=full') - command("cd test/functional/fixtures/") feed(':e compdir/') screen:expect([[ | @@ -1949,7 +2250,7 @@ describe('builtin popupmenu', function() {1:~ }| {1:~ }{s: file1 }{1: }| {1:~ }{n: file2 }{1: }| - :e compdir]]..get_pathsep()..[[file1^ | + :e compdir/file1^ | ]]) end) @@ -2007,7 +2308,9 @@ describe('builtin popupmenu', function() command('set wildoptions=pum') command('set wildmode=longest,full') - feed(':sign u') + -- With 'wildmode' set to 'longest,full', completing a match should display + -- the longest match, the wildmenu should not be displayed. + feed(':sign u') screen:expect{grid=[[ | {1:~ }| @@ -2020,7 +2323,8 @@ describe('builtin popupmenu', function() ]]} eq(0, funcs.wildmenumode()) - feed('') + -- pressing should display the wildmenu + feed('') screen:expect{grid=[[ | {1:~ }| @@ -2032,6 +2336,19 @@ describe('builtin popupmenu', function() :sign undefine^ | ]]} eq(1, funcs.wildmenumode()) + + -- pressing second time should select the next entry in the menu + feed('') + screen:expect{grid=[[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }{n: undefine }{1: }| + {1:~ }{s: unplace }{1: }| + :sign unplace^ | + ]]} end) it("'pumblend' RGB-color", function()