feat(api): nvim_select_popupmenu_item support cmdline pum (#20652)

This commit is contained in:
zeertzjq 2022-10-17 21:00:50 +08:00 committed by GitHub
parent 39911d76be
commit 637ab296cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 386 additions and 192 deletions

View File

@ -1251,19 +1251,21 @@ nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special})
*nvim_select_popupmenu_item()*
nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts})
Selects an item in the completion popupmenu.
Selects an item in the completion popup menu.
If |ins-completion| is not active this API call is silently ignored.
Useful for an external UI using |ui-popupmenu| to control the popupmenu
with the mouse. Can also be used in a mapping; use <cmd> |:map-cmd| to
ensure the mapping doesn't end completion mode.
If neither |ins-completion| nor |cmdline-completion| popup menu is active
this API call is silently ignored. Useful for an external UI using
|ui-popupmenu| to control the popup menu with the mouse. Can also be used
in a mapping; use <Cmd> |:map-cmd| or a Lua mapping to ensure the mapping
doesn't end completion mode.
Parameters: ~
• {item} Index (zero-based) of the item to select. Value of -1
selects nothing and restores the original text.
• {insert} Whether the selection should be inserted in the buffer.
• {finish} Finish the completion and dismiss the popupmenu. Implies
`insert`.
• {insert} For |ins-completion|, whether the selection should be
inserted in the buffer. Ignored for |cmdline-completion|.
• {finish} Finish the completion and dismiss the popup menu. Implies
{insert}.
• {opts} Optional parameters. Reserved for future use.
*nvim_set_client_info()*

View File

@ -39,6 +39,8 @@ NEW FEATURES *news-features*
The following new APIs or features were added.
|nvim_select_popupmenu_item()| now supports |cmdline-completion| popup menu.
==============================================================================
CHANGED FEATURES *news-changes*

View File

@ -1908,19 +1908,20 @@ Object nvim_get_proc(Integer pid, Error *err)
return rvobj;
}
/// Selects an item in the completion popupmenu.
/// Selects an item in the completion popup menu.
///
/// If |ins-completion| is not active this API call is silently ignored.
/// Useful for an external UI using |ui-popupmenu| to control the popupmenu
/// with the mouse. Can also be used in a mapping; use <cmd> |:map-cmd| to
/// ensure the mapping doesn't end completion mode.
/// If neither |ins-completion| nor |cmdline-completion| popup menu is active
/// this API call is silently ignored.
/// Useful for an external UI using |ui-popupmenu| to control the popup menu with the mouse.
/// Can also be used in a mapping; use <Cmd> |:map-cmd| or a Lua mapping to ensure the mapping
/// doesn't end completion mode.
///
/// @param item Index (zero-based) of the item to select. Value of -1 selects
/// nothing and restores the original text.
/// @param insert Whether the selection should be inserted in the buffer.
/// @param finish Finish the completion and dismiss the popupmenu. Implies
/// `insert`.
/// @param opts Optional parameters. Reserved for future use.
/// @param item Index (zero-based) of the item to select. Value of -1 selects nothing
/// and restores the original text.
/// @param insert For |ins-completion|, whether the selection should be inserted in the buffer.
/// Ignored for |cmdline-completion|.
/// @param finish Finish the completion and dismiss the popup menu. Implies {insert}.
/// @param opts Optional parameters. Reserved for future use.
/// @param[out] err Error details, if any
void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Dictionary opts,
Error *err)

View File

@ -180,7 +180,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
assert(ccline->cmdpos >= i);
xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i;
if (type == WILD_NEXT || type == WILD_PREV) {
if (type == WILD_NEXT || type == WILD_PREV || type == WILD_PUM_WANT) {
// Get next/previous match for a previous expanded pattern.
p2 = (char_u *)ExpandOne(xp, NULL, NULL, 0, type);
} else {
@ -290,8 +290,11 @@ static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char
findex = xp->xp_numfiles;
}
findex--;
} else { // mode == WILD_NEXT
} else if (mode == WILD_NEXT) {
findex++;
} else { // mode == WILD_PUM_WANT
assert(pum_want.active);
findex = pum_want.item;
}
// When wrapping around, return the original string, set findex to -1.
@ -419,7 +422,7 @@ static char *find_longest_match(expand_T *xp, int options)
return xstrndup(xp->xp_files[0], len);
}
/// Do wildcard expansion on the string 'str'.
/// 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.
/// Return NULL for failure.
@ -443,6 +446,7 @@ static char *find_longest_match(expand_T *xp, int options)
/// popup menu and close the menu.
/// mode = WILD_CANCEL: cancel and close the cmdline completion popup and
/// use the original text.
/// mode = WILD_PUM_WANT: use the match at index pum_want.item
///
/// options = WILD_LIST_NOTFOUND: list entries without a match
/// options = WILD_HOME_REPLACE: do home_replace() for buffer names
@ -466,7 +470,7 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
int i;
// first handle the case of using an old match
if (mode == WILD_NEXT || mode == WILD_PREV) {
if (mode == WILD_NEXT || mode == WILD_PREV || mode == WILD_PUM_WANT) {
return get_next_or_prev_match(mode, xp, &findex, orig_save);
}

View File

@ -19,6 +19,9 @@ enum {
WILD_ALL_KEEP = 8,
WILD_CANCEL = 9,
WILD_APPLY = 10,
// WILD_PAGEUP = 11, not ported yet
// WILD_PAGEDOWN = 12, not ported yet
WILD_PUM_WANT = 13,
};
enum {

View File

@ -919,6 +919,22 @@ static int command_line_check(VimState *state)
return 1;
}
static void command_line_end_wildmenu(CommandLineState *s)
{
if (cmdline_pum_active()) {
cmdline_pum_remove();
}
if (s->xpc.xp_numfiles != -1) {
(void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE);
}
s->did_wild_list = false;
if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) {
s->xpc.xp_context = EXPAND_NOTHING;
}
s->wim_index = 0;
wildmenu_cleanup(&ccline);
}
static int command_line_execute(VimState *state, int key)
{
if (key == K_IGNORE || key == K_NOP) {
@ -937,6 +953,19 @@ static int command_line_execute(VimState *state, int key)
map_execute_lua();
}
// nvim_select_popupmenu_item() can be called from the handling of
// K_EVENT, K_COMMAND, or K_LUA.
if (pum_want.active) {
if (cmdline_pum_active()) {
nextwild(&s->xpc, WILD_PUM_WANT, 0, s->firstc != '@');
if (pum_want.finish) {
nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP, s->firstc != '@');
command_line_end_wildmenu(s);
}
}
pum_want.active = false;
}
if (!cmdline_was_last_drawn) {
redrawcmdline();
}
@ -1016,18 +1045,7 @@ static int command_line_execute(VimState *state, int key)
if (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z
&& s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A
&& s->c != Ctrl_L) {
if (cmdline_pum_active()) {
cmdline_pum_remove();
}
if (s->xpc.xp_numfiles != -1) {
(void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE);
}
s->did_wild_list = false;
if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) {
s->xpc.xp_context = EXPAND_NOTHING;
}
s->wim_index = 0;
wildmenu_cleanup(&ccline);
command_line_end_wildmenu(s);
}
if (p_wmnu) {

View File

@ -3563,17 +3563,6 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
return num_matches;
}
void pum_ext_select_item(int item, bool insert, bool finish)
{
if (!pum_visible() || item < -1 || item >= compl_match_arraysize) {
return;
}
pum_want.active = true;
pum_want.item = item;
pum_want.insert = insert;
pum_want.finish = finish;
}
/// Call this while finding completions, to check whether the user has hit a key
/// that should change the currently displayed completion, or exit completion
/// mode. Also, when compl_pending is not zero, show a completion as soon as

View File

@ -3,14 +3,6 @@
#include "nvim/vim.h"
/// state for pum_ext_select_item.
EXTERN struct {
bool active;
int item;
bool insert;
bool finish;
} pum_want;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "insexpand.h.generated.h"
#endif

View File

@ -31,7 +31,6 @@
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/iconv.h"
#include "nvim/insexpand.h"
#include "nvim/locale.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"

View File

@ -904,6 +904,17 @@ void pum_recompose(void)
ui_comp_compose_grid(&pum_grid);
}
void pum_ext_select_item(int item, bool insert, bool finish)
{
if (!pum_visible() || item < -1 || item >= pum_size) {
return;
}
pum_want.active = true;
pum_want.item = item;
pum_want.insert = insert;
pum_want.finish = finish;
}
/// Gets the height of the menu.
///
/// @return the height of the popup menu, the number of entries visible.

View File

@ -16,6 +16,14 @@ typedef struct {
EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT);
/// state for pum_ext_select_item.
EXTERN struct {
bool active;
int item;
bool insert;
bool finish;
} pum_want;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "popupmenu.h.generated.h"
#endif

View File

@ -259,174 +259,339 @@ describe('ui/ext_popupmenu', function()
{2:-- INSERT --} |
]])
command('imap <f1> <cmd>call nvim_select_popupmenu_item(2,v:true,v:false,{})<cr>')
command('imap <f2> <cmd>call nvim_select_popupmenu_item(-1,v:false,v:false,{})<cr>')
command('imap <f3> <cmd>call nvim_select_popupmenu_item(1,v:false,v:true,{})<cr>')
feed('<C-r>=TestComplete()<CR>')
screen:expect{grid=[[
command('set wildmenu')
command('set wildoptions=pum')
local expected_wildpum = {
{ "define", "", "", "" },
{ "jump", "", "", "" },
{ "list", "", "", "" },
{ "place", "", "", "" },
{ "undefine", "", "", "" },
{ "unplace", "", "", "" },
}
feed('<Esc>:sign <Tab>')
screen:expect({grid = [[
|
foo^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], popupmenu={
items=expected,
pos=0,
anchor={1,1,0},
}}
feed('<f1>')
screen:expect{grid=[[
|
spam^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], popupmenu={
items=expected,
pos=2,
anchor={1,1,0},
}}
:sign define^ |
]], popupmenu = {
items = expected_wildpum,
pos = 0,
anchor = { 1, 7, 6 },
}})
feed('<f2>')
screen:expect{grid=[[
meths.select_popupmenu_item(-1, true, false, {})
screen:expect({grid = [[
|
spam^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], popupmenu={
items=expected,
pos=-1,
anchor={1,1,0},
}}
feed('<f3>')
screen:expect([[
|
bar^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
:sign ^ |
]], popupmenu = {
items = expected_wildpum,
pos = -1,
anchor = { 1, 7, 6 },
}})
-- also should work for builtin popupmenu
screen:set_option('ext_popupmenu', false)
feed('<C-r>=TestComplete()<CR>')
screen:expect([[
meths.select_popupmenu_item(5, true, false, {})
screen:expect({grid = [[
|
foo^ |
{6:fo x the foo }{1: }|
{7:bar }{1: }|
{7:spam }{1: }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<f1>')
screen:expect([[
|
spam^ |
{7:fo x the foo }{1: }|
{7:bar }{1: }|
{6:spam }{1: }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
{1:~ }|
{1:~ }|
{1:~ }|
:sign unplace^ |
]], popupmenu = {
items = expected_wildpum,
pos = 5,
anchor = { 1, 7, 6 },
}})
feed('<f2>')
screen:expect([[
meths.select_popupmenu_item(-1, true, true, {})
screen:expect({grid = [[
|
spam^ |
{7:fo x the foo }{1: }|
{7:bar }{1: }|
{7:spam }{1: }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<f3>')
screen:expect([[
|
bar^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
:sign ^ |
]]})
command('iunmap <f1>')
command('iunmap <f2>')
command('iunmap <f3>')
feed('<Tab>')
screen:expect({grid = [[
|
|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
:sign define^ |
]], popupmenu = {
items = expected_wildpum,
pos = 0,
anchor = { 1, 7, 6 },
}})
meths.select_popupmenu_item(5, true, true, {})
screen:expect({grid = [[
|
|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
:sign unplace^ |
]]})
local function test_pum_select_mappings()
screen:set_option('ext_popupmenu', true)
feed('<Esc>A<C-r>=TestComplete()<CR>')
screen:expect{grid=[[
|
foo^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], popupmenu={
items=expected,
pos=0,
anchor={1,1,0},
}}
feed('<f1>')
screen:expect{grid=[[
|
spam^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], popupmenu={
items=expected,
pos=2,
anchor={1,1,0},
}}
feed('<f2>')
screen:expect{grid=[[
|
spam^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]], popupmenu={
items=expected,
pos=-1,
anchor={1,1,0},
}}
feed('<f3>')
screen:expect([[
|
bar^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<Esc>:sign <Tab>')
screen:expect({grid = [[
|
bar |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
:sign define^ |
]], popupmenu = {
items = expected_wildpum,
pos = 0,
anchor = { 1, 7, 6 },
}})
feed('<f1>')
screen:expect({grid = [[
|
bar |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
:sign list^ |
]], popupmenu = {
items = expected_wildpum,
pos = 2,
anchor = { 1, 7, 6 },
}})
feed('<f2>')
screen:expect({grid = [[
|
bar |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
:sign ^ |
]], popupmenu = {
items = expected_wildpum,
pos = -1,
anchor = { 1, 7, 6 },
}})
feed('<f3>')
screen:expect({grid = [[
|
bar |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
:sign jump^ |
]]})
-- also should work for builtin popupmenu
screen:set_option('ext_popupmenu', false)
feed('<Esc>A<C-r>=TestComplete()<CR>')
screen:expect([[
|
foo^ |
{6:fo x the foo }{1: }|
{7:bar }{1: }|
{7:spam }{1: }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<f1>')
screen:expect([[
|
spam^ |
{7:fo x the foo }{1: }|
{7:bar }{1: }|
{6:spam }{1: }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<f2>')
screen:expect([[
|
spam^ |
{7:fo x the foo }{1: }|
{7:bar }{1: }|
{7:spam }{1: }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<f3>')
screen:expect([[
|
bar^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<Esc>:sign <Tab>')
screen:expect([[
|
bar {6: define } |
{1:~ }{7: jump }{1: }|
{1:~ }{7: list }{1: }|
{1:~ }{7: place }{1: }|
{1:~ }{7: undefine }{1: }|
{1:~ }{7: unplace }{1: }|
:sign define^ |
]])
feed('<f1>')
screen:expect([[
|
bar {7: define } |
{1:~ }{7: jump }{1: }|
{1:~ }{6: list }{1: }|
{1:~ }{7: place }{1: }|
{1:~ }{7: undefine }{1: }|
{1:~ }{7: unplace }{1: }|
:sign list^ |
]])
feed('<f2>')
screen:expect([[
|
bar {7: define } |
{1:~ }{7: jump }{1: }|
{1:~ }{7: list }{1: }|
{1:~ }{7: place }{1: }|
{1:~ }{7: undefine }{1: }|
{1:~ }{7: unplace }{1: }|
:sign ^ |
]])
feed('<f3>')
screen:expect([[
|
bar |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
:sign jump^ |
]])
end
command('map! <f1> <cmd>call nvim_select_popupmenu_item(2,v:true,v:false,{})<cr>')
command('map! <f2> <cmd>call nvim_select_popupmenu_item(-1,v:false,v:false,{})<cr>')
command('map! <f3> <cmd>call nvim_select_popupmenu_item(1,v:false,v:true,{})<cr>')
test_pum_select_mappings()
command('unmap! <f1>')
command('unmap! <f2>')
command('unmap! <f3>')
exec_lua([[
vim.keymap.set('i', '<f1>', function() vim.api.nvim_select_popupmenu_item(2, true, false, {}) end)
vim.keymap.set('i', '<f2>', function() vim.api.nvim_select_popupmenu_item(-1, false, false, {}) end)
vim.keymap.set('i', '<f3>', function() vim.api.nvim_select_popupmenu_item(1, false, true, {}) end)
]])
feed('<C-r>=TestComplete()<CR>')
screen:expect([[
|
foo^ |
{6:fo x the foo }{1: }|
{7:bar }{1: }|
{7:spam }{1: }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<f1>')
screen:expect([[
|
spam^ |
{7:fo x the foo }{1: }|
{7:bar }{1: }|
{6:spam }{1: }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<f2>')
screen:expect([[
|
spam^ |
{7:fo x the foo }{1: }|
{7:bar }{1: }|
{7:spam }{1: }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
]])
feed('<f3>')
screen:expect([[
|
bar^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- INSERT --} |
vim.keymap.set('!', '<f1>', function() vim.api.nvim_select_popupmenu_item(2, true, false, {}) end)
vim.keymap.set('!', '<f2>', function() vim.api.nvim_select_popupmenu_item(-1, false, false, {}) end)
vim.keymap.set('!', '<f3>', function() vim.api.nvim_select_popupmenu_item(1, false, true, {}) end)
]])
test_pum_select_mappings()
feed('<esc>ddiaa bb cc<cr>')
feed('<c-x><c-n>')