mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 19:25:11 -07:00
Merge pull request #9445 from bfredl/pum_api
API: select items in popupmenu
This commit is contained in:
commit
8510d5ff86
@ -28,6 +28,7 @@
|
||||
#include "nvim/screen.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/edit.h"
|
||||
#include "nvim/eval.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/option.h"
|
||||
@ -1915,6 +1916,35 @@ Object nvim_get_proc(Integer pid, Error *err)
|
||||
return rvobj;
|
||||
}
|
||||
|
||||
/// Selects an item in the completion popupmenu
|
||||
///
|
||||
/// When insert completion is not active, this API call is silently ignored.
|
||||
/// It is mostly useful for an external UI using |ui-popupmenu| for instance
|
||||
/// to control the popupmenu with the mouse. But it can also be used in an
|
||||
/// insert mode mapping, use <cmd> mapping |:map-cmd| to ensure the mapping
|
||||
/// doesn't end completion mode.
|
||||
///
|
||||
/// @param item Index of the item to select, starting with zero. Pass in "-1"
|
||||
/// to select no item (restore original text).
|
||||
/// @param insert Whether the selection should be inserted in the buffer.
|
||||
/// @param finish If true, completion will be finished with this item, and the
|
||||
/// popupmenu dissmissed. Implies `insert`.
|
||||
void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish,
|
||||
Dictionary opts, Error *err)
|
||||
FUNC_API_SINCE(6)
|
||||
{
|
||||
if (opts.size > 0) {
|
||||
api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (finish) {
|
||||
insert = true;
|
||||
}
|
||||
|
||||
pum_ext_select_item((int)item, insert, finish);
|
||||
}
|
||||
|
||||
/// NB: if your UI doesn't use hlstate, this will not return hlstate first time
|
||||
Array nvim__inspect_cell(Integer row, Integer col, Error *err)
|
||||
{
|
||||
|
@ -184,6 +184,16 @@ static expand_T compl_xp;
|
||||
|
||||
static int compl_opt_refresh_always = FALSE;
|
||||
|
||||
static int pum_selected_item = -1;
|
||||
|
||||
/// state for pum_ext_select_item.
|
||||
struct {
|
||||
bool active;
|
||||
int item;
|
||||
bool insert;
|
||||
bool finish;
|
||||
} pum_want;
|
||||
|
||||
typedef struct insert_state {
|
||||
VimState state;
|
||||
cmdarg_T *ca;
|
||||
@ -976,10 +986,25 @@ static int insert_handle_key(InsertState *s)
|
||||
|
||||
case K_EVENT: // some event
|
||||
multiqueue_process_events(main_loop.events);
|
||||
break;
|
||||
goto check_pum;
|
||||
|
||||
case K_COMMAND: // some command
|
||||
do_cmdline(NULL, getcmdkeycmd, NULL, 0);
|
||||
|
||||
check_pum:
|
||||
// TODO(bfredl): Not entirely sure this indirection is necessary
|
||||
// but doing like this ensures using nvim_select_popupmenu_item is
|
||||
// equivalent to selecting the item with a typed key.
|
||||
if (pum_want.active) {
|
||||
if (pum_visible()) {
|
||||
insert_do_complete(s);
|
||||
if (pum_want.finish) {
|
||||
// accept the item and stop completion
|
||||
ins_compl_prep(Ctrl_Y);
|
||||
}
|
||||
}
|
||||
pum_want.active = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case K_HOME: // <Home>
|
||||
@ -2666,6 +2691,7 @@ void ins_compl_show_pum(void)
|
||||
// Use the cursor to get all wrapping and other settings right.
|
||||
col = curwin->w_cursor.col;
|
||||
curwin->w_cursor.col = compl_col;
|
||||
pum_selected_item = cur;
|
||||
pum_display(compl_match_array, compl_match_arraysize, cur, array_changed);
|
||||
curwin->w_cursor.col = col;
|
||||
}
|
||||
@ -4346,6 +4372,17 @@ ins_compl_next (
|
||||
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
|
||||
@ -4406,6 +4443,9 @@ void ins_compl_check_keys(int frequency, int in_compl_func)
|
||||
*/
|
||||
static int ins_compl_key2dir(int c)
|
||||
{
|
||||
if (c == K_EVENT || c == K_COMMAND) {
|
||||
return pum_want.item < pum_selected_item ? BACKWARD : FORWARD;
|
||||
}
|
||||
if (c == Ctrl_P || c == Ctrl_L
|
||||
|| c == K_PAGEUP || c == K_KPAGEUP
|
||||
|| c == K_S_UP || c == K_UP) {
|
||||
@ -4433,6 +4473,11 @@ static int ins_compl_key2count(int c)
|
||||
{
|
||||
int h;
|
||||
|
||||
if (c == K_EVENT || c == K_COMMAND) {
|
||||
int offset = pum_want.item - pum_selected_item;
|
||||
return abs(offset);
|
||||
}
|
||||
|
||||
if (ins_compl_pum_key(c) && c != K_UP && c != K_DOWN) {
|
||||
h = pum_get_height();
|
||||
if (h > 3)
|
||||
@ -4459,6 +4504,9 @@ static bool ins_compl_use_match(int c)
|
||||
case K_KPAGEUP:
|
||||
case K_S_UP:
|
||||
return false;
|
||||
case K_EVENT:
|
||||
case K_COMMAND:
|
||||
return pum_want.active && pum_want.insert;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ local Screen = require('test.functional.ui.screen')
|
||||
local clear, feed = helpers.clear, helpers.feed
|
||||
local source = helpers.source
|
||||
local insert = helpers.insert
|
||||
local meths = helpers.meths
|
||||
local command = helpers.command
|
||||
|
||||
describe('ui/ext_popupmenu', function()
|
||||
local screen
|
||||
@ -15,22 +17,25 @@ describe('ui/ext_popupmenu', function()
|
||||
[2] = {bold = true},
|
||||
[3] = {reverse = true},
|
||||
[4] = {bold = true, reverse = true},
|
||||
[5] = {bold = true, foreground = Screen.colors.SeaGreen}
|
||||
[5] = {bold = true, foreground = Screen.colors.SeaGreen},
|
||||
[6] = {background = Screen.colors.WebGray},
|
||||
[7] = {background = Screen.colors.LightMagenta},
|
||||
})
|
||||
end)
|
||||
|
||||
it('works', function()
|
||||
source([[
|
||||
function! TestComplete() abort
|
||||
call complete(1, ['foo', 'bar', 'spam'])
|
||||
call complete(1, [{'word':'foo', 'abbr':'fo', 'menu':'the foo', 'info':'foo-y', 'kind':'x'}, 'bar', 'spam'])
|
||||
return ''
|
||||
endfunction
|
||||
]])
|
||||
local expected = {
|
||||
{'foo', '', '', ''},
|
||||
{'bar', '', '', ''},
|
||||
{'spam', '', '', ''},
|
||||
}
|
||||
end)
|
||||
|
||||
local expected = {
|
||||
{'fo', 'x', 'the foo', 'foo-y'},
|
||||
{'bar', '', '', ''},
|
||||
{'spam', '', '', ''},
|
||||
}
|
||||
|
||||
it('works', function()
|
||||
feed('o<C-r>=TestComplete()<CR>')
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
@ -92,8 +97,277 @@ describe('ui/ext_popupmenu', function()
|
||||
{2:-- INSERT --} |
|
||||
]]}
|
||||
end)
|
||||
|
||||
it('can be controlled by API', function()
|
||||
feed('o<C-r>=TestComplete()<CR>')
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
foo^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=0,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
meths.select_popupmenu_item(1,false,false,{})
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
foo^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=1,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
meths.select_popupmenu_item(2,true,false,{})
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
spam^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=2,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
meths.select_popupmenu_item(0,true,true,{})
|
||||
screen:expect([[
|
||||
|
|
||||
foo^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]])
|
||||
|
||||
|
||||
feed('<c-w><C-r>=TestComplete()<CR>')
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
foo^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=0,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
meths.select_popupmenu_item(-1,false,false,{})
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
foo^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=-1,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
meths.select_popupmenu_item(1,true,false,{})
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
bar^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=1,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
meths.select_popupmenu_item(-1,true,false,{})
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=-1,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
meths.select_popupmenu_item(0,true,false,{})
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
foo^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=0,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
meths.select_popupmenu_item(-1,true,true,{})
|
||||
screen:expect([[
|
||||
|
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{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=[[
|
||||
|
|
||||
foo^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=0,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
feed('<f1>')
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
spam^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=2,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
feed('<f2>')
|
||||
screen:expect{grid=[[
|
||||
|
|
||||
spam^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]], popupmenu={
|
||||
items=expected,
|
||||
pos=-1,
|
||||
anchor={1,0},
|
||||
}}
|
||||
|
||||
feed('<f3>')
|
||||
screen:expect([[
|
||||
|
|
||||
bar^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{2:-- INSERT --} |
|
||||
]])
|
||||
|
||||
-- also should work for builtin popupmenu
|
||||
screen:set_option('ext_popupmenu', false)
|
||||
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 --} |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
describe('popup placement', function()
|
||||
local screen
|
||||
before_each(function()
|
||||
|
Loading…
Reference in New Issue
Block a user