Merge pull request #9445 from bfredl/pum_api

API: select items in popupmenu
This commit is contained in:
Björn Linse 2019-01-09 11:43:19 +01:00 committed by GitHub
commit 8510d5ff86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 363 additions and 11 deletions

View File

@ -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)
{

View File

@ -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;
}

View File

@ -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
]])
end)
local expected = {
{'foo', '', '', ''},
{'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()