mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06: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/screen.h"
|
||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
#include "nvim/message.h"
|
#include "nvim/message.h"
|
||||||
|
#include "nvim/edit.h"
|
||||||
#include "nvim/eval.h"
|
#include "nvim/eval.h"
|
||||||
#include "nvim/eval/typval.h"
|
#include "nvim/eval/typval.h"
|
||||||
#include "nvim/option.h"
|
#include "nvim/option.h"
|
||||||
@ -1915,6 +1916,35 @@ Object nvim_get_proc(Integer pid, Error *err)
|
|||||||
return rvobj;
|
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
|
/// 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)
|
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 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 {
|
typedef struct insert_state {
|
||||||
VimState state;
|
VimState state;
|
||||||
cmdarg_T *ca;
|
cmdarg_T *ca;
|
||||||
@ -976,10 +986,25 @@ static int insert_handle_key(InsertState *s)
|
|||||||
|
|
||||||
case K_EVENT: // some event
|
case K_EVENT: // some event
|
||||||
multiqueue_process_events(main_loop.events);
|
multiqueue_process_events(main_loop.events);
|
||||||
break;
|
goto check_pum;
|
||||||
|
|
||||||
case K_COMMAND: // some command
|
case K_COMMAND: // some command
|
||||||
do_cmdline(NULL, getcmdkeycmd, NULL, 0);
|
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;
|
break;
|
||||||
|
|
||||||
case K_HOME: // <Home>
|
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.
|
// Use the cursor to get all wrapping and other settings right.
|
||||||
col = curwin->w_cursor.col;
|
col = curwin->w_cursor.col;
|
||||||
curwin->w_cursor.col = compl_col;
|
curwin->w_cursor.col = compl_col;
|
||||||
|
pum_selected_item = cur;
|
||||||
pum_display(compl_match_array, compl_match_arraysize, cur, array_changed);
|
pum_display(compl_match_array, compl_match_arraysize, cur, array_changed);
|
||||||
curwin->w_cursor.col = col;
|
curwin->w_cursor.col = col;
|
||||||
}
|
}
|
||||||
@ -4346,6 +4372,17 @@ ins_compl_next (
|
|||||||
return num_matches;
|
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
|
// Call this while finding completions, to check whether the user has hit a key
|
||||||
// that should change the currently displayed completion, or exit completion
|
// that should change the currently displayed completion, or exit completion
|
||||||
// mode. Also, when compl_pending is not zero, show a completion as soon as
|
// 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)
|
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
|
if (c == Ctrl_P || c == Ctrl_L
|
||||||
|| c == K_PAGEUP || c == K_KPAGEUP
|
|| c == K_PAGEUP || c == K_KPAGEUP
|
||||||
|| c == K_S_UP || c == K_UP) {
|
|| c == K_S_UP || c == K_UP) {
|
||||||
@ -4433,6 +4473,11 @@ static int ins_compl_key2count(int c)
|
|||||||
{
|
{
|
||||||
int h;
|
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) {
|
if (ins_compl_pum_key(c) && c != K_UP && c != K_DOWN) {
|
||||||
h = pum_get_height();
|
h = pum_get_height();
|
||||||
if (h > 3)
|
if (h > 3)
|
||||||
@ -4459,6 +4504,9 @@ static bool ins_compl_use_match(int c)
|
|||||||
case K_KPAGEUP:
|
case K_KPAGEUP:
|
||||||
case K_S_UP:
|
case K_S_UP:
|
||||||
return false;
|
return false;
|
||||||
|
case K_EVENT:
|
||||||
|
case K_COMMAND:
|
||||||
|
return pum_want.active && pum_want.insert;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ local Screen = require('test.functional.ui.screen')
|
|||||||
local clear, feed = helpers.clear, helpers.feed
|
local clear, feed = helpers.clear, helpers.feed
|
||||||
local source = helpers.source
|
local source = helpers.source
|
||||||
local insert = helpers.insert
|
local insert = helpers.insert
|
||||||
|
local meths = helpers.meths
|
||||||
|
local command = helpers.command
|
||||||
|
|
||||||
describe('ui/ext_popupmenu', function()
|
describe('ui/ext_popupmenu', function()
|
||||||
local screen
|
local screen
|
||||||
@ -15,22 +17,25 @@ describe('ui/ext_popupmenu', function()
|
|||||||
[2] = {bold = true},
|
[2] = {bold = true},
|
||||||
[3] = {reverse = true},
|
[3] = {reverse = true},
|
||||||
[4] = {bold = true, 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([[
|
source([[
|
||||||
function! TestComplete() abort
|
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 ''
|
return ''
|
||||||
endfunction
|
endfunction
|
||||||
]])
|
]])
|
||||||
local expected = {
|
end)
|
||||||
{'foo', '', '', ''},
|
|
||||||
{'bar', '', '', ''},
|
local expected = {
|
||||||
{'spam', '', '', ''},
|
{'fo', 'x', 'the foo', 'foo-y'},
|
||||||
}
|
{'bar', '', '', ''},
|
||||||
|
{'spam', '', '', ''},
|
||||||
|
}
|
||||||
|
|
||||||
|
it('works', function()
|
||||||
feed('o<C-r>=TestComplete()<CR>')
|
feed('o<C-r>=TestComplete()<CR>')
|
||||||
screen:expect{grid=[[
|
screen:expect{grid=[[
|
||||||
|
|
|
|
||||||
@ -92,8 +97,277 @@ describe('ui/ext_popupmenu', function()
|
|||||||
{2:-- INSERT --} |
|
{2:-- INSERT --} |
|
||||||
]]}
|
]]}
|
||||||
end)
|
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)
|
end)
|
||||||
|
|
||||||
|
|
||||||
describe('popup placement', function()
|
describe('popup placement', function()
|
||||||
local screen
|
local screen
|
||||||
before_each(function()
|
before_each(function()
|
||||||
|
Loading…
Reference in New Issue
Block a user