aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2016-06-08 11:19:33 +0200
committerBjörn Linse <bjorn.linse@gmail.com>2019-01-09 10:17:48 +0100
commitae218c108f31a61b958bdbdf6bbb098ae5b71314 (patch)
treef6a5af9c5d922b9d3e623125930883ab1a0b92c8
parentaadfea715962a71650a9c23afacc675ab14d2661 (diff)
downloadrneovim-ae218c108f31a61b958bdbdf6bbb098ae5b71314.tar.gz
rneovim-ae218c108f31a61b958bdbdf6bbb098ae5b71314.tar.bz2
rneovim-ae218c108f31a61b958bdbdf6bbb098ae5b71314.zip
api: select items in popupmenu
-rw-r--r--src/nvim/api/vim.c30
-rw-r--r--src/nvim/edit.c50
-rw-r--r--test/functional/ui/popupmenu_spec.lua294
3 files changed, 363 insertions, 11 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 796923ffcb..24e76ecf88 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -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)
{
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 90da6c8abf..5d918d8f69 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -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;
}
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index af9abeba80..9424931de4 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -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()