aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/api.txt16
-rw-r--r--src/nvim/api/ui.c58
-rw-r--r--src/nvim/eval/typval.c22
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua6
-rw-r--r--src/nvim/popupmnu.c15
-rw-r--r--src/nvim/testdir/test_popup.vim14
-rw-r--r--src/nvim/ui.c17
-rw-r--r--src/nvim/ui.h7
-rw-r--r--test/functional/ui/popupmenu_spec.lua85
-rw-r--r--test/unit/eval/typval_spec.lua20
10 files changed, 231 insertions, 29 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 39b6c6417d..a2e0c56f85 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -2341,6 +2341,22 @@ nvim_ui_pum_set_height({height}) *nvim_ui_pum_set_height()*
Parameters: ~
{height} Popupmenu height, must be greater than zero.
+ *nvim_ui_pum_set_bounds()*
+nvim_ui_pum_set_bounds({width}, {height}, {row}, {col})
+
+ Tells Nvim the geometry of the popumenu, to align floating
+ windows with an external popup menu. Note that this method
+ is not to be confused with |nvim_ui_pum_set_height()|, which
+ sets the number of visible items in the popup menu, while
+ this function sets the bounding box of the popup menu,
+ including visual decorations such as boarders and sliders.
+
+ Parameters: ~
+ {width} Popupmenu width.
+ {height} Popupmenu height.
+ {row} Popupmenu row.
+ {height} Popupmenu height.
+
nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()*
TODO: Documentation
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 75ee05761b..717713b948 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -109,7 +109,12 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->pum_height = 0;
+ ui->pum_nlines = 0;
+ ui->pum_pos = false;
+ ui->pum_width = 0.0;
+ ui->pum_height = 0.0;
+ ui->pum_row = -1.0;
+ ui->pum_col = -1.0;
ui->rgb = true;
ui->override = false;
ui->grid_resize = remote_ui_grid_resize;
@@ -340,7 +345,56 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
"It must support the ext_popupmenu option");
return;
}
- ui->pum_height = (int)height;
+
+ ui->pum_nlines = (int)height;
+}
+
+/// Tells Nvim the geometry of the popumenu, to align floating windows with an
+/// external popup menu.
+///
+/// Note that this method is not to be confused with |nvim_ui_pum_set_height()|,
+/// which sets the number of visible items in the popup menu, while this
+/// function sets the bounding box of the popup menu, including visual
+/// decorations such as boarders and sliders. Floats need not use the same font
+/// size, nor be anchored to exact grid corners, so one can set floating-point
+/// numbers to the popup menu geometry.
+///
+/// @param channel_id
+/// @param width Popupmenu width.
+/// @param height Popupmenu height.
+/// @param row Popupmenu row.
+/// @param col Popupmenu height.
+/// @param[out] err Error details, if any.
+void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height,
+ Float row, Float col, Error *err)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY
+{
+ if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(err, kErrorTypeException,
+ "UI not attached to channel: %" PRId64, channel_id);
+ return;
+ }
+
+ UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
+ if (!ui->ui_ext[kUIPopupmenu]) {
+ api_set_error(err, kErrorTypeValidation,
+ "UI must support the ext_popupmenu option");
+ return;
+ }
+
+ if (width <= 0) {
+ api_set_error(err, kErrorTypeValidation, "Expected width > 0");
+ return;
+ } else if (height <= 0) {
+ api_set_error(err, kErrorTypeValidation, "Expected height > 0");
+ return;
+ }
+
+ ui->pum_row = (double)row;
+ ui->pum_col = (double)col;
+ ui->pum_width = (double)width;
+ ui->pum_height = (double)height;
+ ui->pum_pos = true;
}
/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index c8b5fc294c..773e493d0b 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1634,6 +1634,28 @@ int tv_dict_add_nr(dict_T *const d, const char *const key,
return OK;
}
+/// Add a floating point number entry to dictionary
+///
+/// @param[out] d Dictionary to add entry to.
+/// @param[in] key Key to add.
+/// @param[in] key_len Key length.
+/// @param[in] nr Floating point number to add.
+///
+/// @return OK in case of success, FAIL when key already exists.
+int tv_dict_add_float(dict_T *const d, const char *const key,
+ const size_t key_len, const float_T nr)
+{
+ dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
+
+ item->di_tv.v_type = VAR_FLOAT;
+ item->di_tv.vval.v_float = nr;
+ if (tv_dict_add(d, item) == FAIL) {
+ tv_dict_item_free(item);
+ return FAIL;
+ }
+ return OK;
+}
+
/// Add a special entry to dictionary
///
/// @param[out] d Dictionary to add entry to.
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index e861cfda35..6e80ad0e5c 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -237,6 +237,12 @@ for i = 1, #functions do
(j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {')
output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;')
end
+ if rt:match('^Float$') then
+ -- accept integers for Floats
+ output:write('\n } else if (args.items['..
+ (j - 1)..'].type == kObjectTypeInteger) {')
+ output:write('\n '..converted..' = (Float)args.items['..(j - 1)..'].data.integer;')
+ end
-- accept empty lua tables as empty dictionarys
if rt:match('^Dictionary') then
output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') --luacheck: ignore 631
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index da34d85c00..532bf68190 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -908,10 +908,17 @@ void pum_set_event_info(dict_T *dict)
if (!pum_visible()) {
return;
}
- tv_dict_add_nr(dict, S_LEN("height"), pum_height);
- tv_dict_add_nr(dict, S_LEN("width"), pum_width);
- tv_dict_add_nr(dict, S_LEN("row"), pum_row);
- tv_dict_add_nr(dict, S_LEN("col"), pum_col);
+ double w, h, r, c;
+ if (!ui_pum_get_pos(&w, &h, &r, &c)) {
+ w = (double)pum_width;
+ h = (double)pum_height;
+ r = (double)pum_row;
+ c = (double)pum_col;
+ }
+ tv_dict_add_float(dict, S_LEN("height"), h);
+ tv_dict_add_float(dict, S_LEN("width"), w);
+ tv_dict_add_float(dict, S_LEN("row"), r);
+ tv_dict_add_float(dict, S_LEN("col"), c);
tv_dict_add_nr(dict, S_LEN("size"), pum_size);
tv_dict_add_special(dict, S_LEN("scrollbar"),
pum_scrollbar ? kSpecialVarTrue : kSpecialVarFalse);
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index e5696f4cbb..bb0ed6e00c 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -979,9 +979,9 @@ func Test_CompleteChanged()
call cursor(4, 1)
call feedkeys("Sf\<C-N>", 'tx')
- call assert_equal({'completed_item': {}, 'width': 15,
- \ 'height': 2, 'size': 2,
- \ 'col': 0, 'row': 4, 'scrollbar': v:false}, g:event)
+ call assert_equal({'completed_item': {}, 'width': 15.0,
+ \ 'height': 2.0, 'size': 2,
+ \ 'col': 0.0, 'row': 4.0, 'scrollbar': v:false}, g:event)
call feedkeys("a\<C-N>\<C-N>\<C-E>", 'tx')
call assert_equal('foo', g:word)
call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
@@ -1009,10 +1009,10 @@ func Test_pum_getpos()
setlocal completefunc=UserDefinedComplete
let d = {
- \ 'height': 5,
- \ 'width': 15,
- \ 'row': 1,
- \ 'col': 0,
+ \ 'height': 5.0,
+ \ 'width': 15.0,
+ \ 'row': 1.0,
+ \ 'col': 0.0,
\ 'size': 5,
\ 'scrollbar': v:false,
\ }
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 3a5aa95ad3..685da77b39 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -226,7 +226,7 @@ int ui_pum_get_height(void)
{
int pum_height = 0;
for (size_t i = 1; i < ui_count; i++) {
- int ui_pum_height = uis[i]->pum_height;
+ int ui_pum_height = uis[i]->pum_nlines;
if (ui_pum_height) {
pum_height =
pum_height != 0 ? MIN(pum_height, ui_pum_height) : ui_pum_height;
@@ -235,6 +235,21 @@ int ui_pum_get_height(void)
return pum_height;
}
+bool ui_pum_get_pos(double *pwidth, double *pheight, double *prow, double *pcol)
+{
+ for (size_t i = 1; i < ui_count; i++) {
+ if (!uis[i]->pum_pos) {
+ continue;
+ }
+ *pwidth = uis[i]->pum_width;
+ *pheight = uis[i]->pum_height;
+ *prow = uis[i]->pum_row;
+ *pcol = uis[i]->pum_col;
+ return true;
+ }
+ return false;
+}
+
static void ui_refresh_event(void **argv)
{
ui_refresh();
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index 8867b5ee24..d00243d35f 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -53,7 +53,12 @@ struct ui_t {
bool ui_ext[kUIExtCount]; ///< Externalized UI capabilities.
int width;
int height;
- int pum_height;
+ int pum_nlines; /// actual nr. lines shown in PUM
+ bool pum_pos; /// UI reports back pum position?
+ double pum_row;
+ double pum_col;
+ double pum_height;
+ double pum_width;
void *data;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index 11c3f4123e..c1c5d1ce2e 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -8,7 +8,7 @@ local command = helpers.command
local funcs = helpers.funcs
local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
-local matches = helpers.matches
+local pcall_err = helpers.pcall_err
describe('ui/ext_popupmenu', function()
local screen
@@ -382,7 +382,7 @@ describe('ui/ext_popupmenu', function()
end
describe('pum_set_height', function()
- it('can be set pum height', function()
+ it('can set pum height', function()
source_complete_month()
local month_expected = {
{'January', '', '', ''},
@@ -421,22 +421,79 @@ describe('ui/ext_popupmenu', function()
end)
it('an error occurs if set 0 or less', function()
- local ok, err, _
- ok, _ = pcall(meths.ui_pum_set_height, 1)
- eq(ok, true)
- ok, err = pcall(meths.ui_pum_set_height, 0)
- eq(ok, false)
- matches('.*: Expected pum height > 0', err)
+ meths.ui_pum_set_height(1)
+ eq('Expected pum height > 0',
+ pcall_err(meths.ui_pum_set_height, 0))
end)
it('an error occurs when ext_popupmenu is false', function()
- local ok, err, _
- ok, _ = pcall(meths.ui_pum_set_height, 1)
- eq(ok, true)
+ meths.ui_pum_set_height(1)
screen:set_option('ext_popupmenu', false)
- ok, err = pcall(meths.ui_pum_set_height, 1)
- eq(ok, false)
- matches('.*: It must support the ext_popupmenu option', err)
+ eq('It must support the ext_popupmenu option',
+ pcall_err(meths.ui_pum_set_height, 1))
+ end)
+ end)
+
+ describe('pum_set_bounds', function()
+ it('can set pum bounds', function()
+ source_complete_month()
+ local month_expected = {
+ {'January', '', '', ''},
+ {'February', '', '', ''},
+ {'March', '', '', ''},
+ {'April', '', '', ''},
+ {'May', '', '', ''},
+ {'June', '', '', ''},
+ {'July', '', '', ''},
+ {'August', '', '', ''},
+ {'September', '', '', ''},
+ {'October', '', '', ''},
+ {'November', '', '', ''},
+ {'December', '', '', ''},
+ }
+ local pum_height = 6
+ feed('o<C-r>=TestCompleteMonth()<CR>')
+ meths.ui_pum_set_height(pum_height)
+ -- set bounds w h r c
+ meths.ui_pum_set_bounds(10.5, 5.2, 6.3, 7.4)
+ feed('<PageDown>')
+ -- pos becomes pum_height-2 because it is subtracting 2 to keep some
+ -- context in ins_compl_key2count()
+ screen:expect{grid=[[
+ |
+ January^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]], popupmenu={
+ items=month_expected,
+ pos=pum_height-2,
+ anchor={1,1,0},
+ }}
+ end)
+
+ it('no error occurs if row or col set less than 0', function()
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5)
+ meths.ui_pum_set_bounds(1.0, 1.0, -1.0, 0.0)
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, -1.0)
+ end)
+
+ it('an error occurs if width or height set 0 or less', function()
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5)
+ eq('Expected width > 0',
+ pcall_err(meths.ui_pum_set_bounds, 0.0, 1.0, 1.0, 0.0))
+ eq('Expected height > 0',
+ pcall_err(meths.ui_pum_set_bounds, 1.0, -1.0, 1.0, 0.0))
+ end)
+
+ it('an error occurs when ext_popupmenu is false', function()
+ meths.ui_pum_set_bounds(1.0, 1.0, 0.0, 1.5)
+ screen:set_option('ext_popupmenu', false)
+ eq('UI must support the ext_popupmenu option',
+ pcall_err(meths.ui_pum_set_bounds, 1.0, 1.0, 0.0, 1.5))
end)
end)
diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua
index 1651eb9bcc..ea86ccbf1c 100644
--- a/test/unit/eval/typval_spec.lua
+++ b/test/unit/eval/typval_spec.lua
@@ -2026,6 +2026,26 @@ describe('typval.c', function()
alloc_log:check({})
end)
end)
+ describe('float()', function()
+ itp('works', function()
+ local d = dict({test=10})
+ alloc_log:clear()
+ eq({test=10}, dct2tbl(d))
+ eq(OK, lib.tv_dict_add_float(d, 'testt', 3, 1.5))
+ local dis = dict_items(d)
+ alloc_log:check({a.di(dis.tes, 'tes')})
+ eq({test=10, tes=1.5}, dct2tbl(d))
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_float(d, 'testt', 3, 1.5) end,
+ 'E685: Internal error: hash_add()'))
+ alloc_log:clear()
+ lib.emsg_skip = lib.emsg_skip + 1
+ eq(FAIL, check_emsg(function() return lib.tv_dict_add_float(d, 'testt', 3, 1.5) end,
+ nil))
+ lib.emsg_skip = lib.emsg_skip - 1
+ alloc_log:clear_tmp_allocs()
+ alloc_log:check({})
+ end)
+ end)
describe('str()', function()
itp('works', function()
local d = dict({test=10})