aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lua.txt31
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim9
-rw-r--r--src/nvim/api/extmark.c2
-rw-r--r--src/nvim/api/ui.c65
-rw-r--r--src/nvim/api/ui_events.in.h4
-rw-r--r--src/nvim/api/vim.c22
-rw-r--r--src/nvim/buffer_updates.c8
-rw-r--r--src/nvim/cmdexpand.c1
-rw-r--r--src/nvim/cmdhist.c1
-rw-r--r--src/nvim/eval.c9
-rw-r--r--src/nvim/ex_cmds.c1
-rw-r--r--src/nvim/ex_docmd.c3
-rwxr-xr-xsrc/nvim/generators/gen_api_ui_events.lua2
-rw-r--r--src/nvim/highlight.c30
-rw-r--r--src/nvim/highlight.h2
-rw-r--r--src/nvim/highlight_group.c20
-rw-r--r--src/nvim/lua/executor.c89
-rw-r--r--src/nvim/map.c1
-rw-r--r--src/nvim/map.h1
-rw-r--r--src/nvim/mapping.c54
-rw-r--r--src/nvim/mark.c3
-rw-r--r--src/nvim/memory.c2
-rw-r--r--src/nvim/ops.c1
-rw-r--r--src/nvim/option.c1
-rw-r--r--src/nvim/quickfix.c2
-rw-r--r--src/nvim/search.c2
-rw-r--r--src/nvim/tag.c1
-rw-r--r--src/nvim/testdir/test_gui.vim43
-rw-r--r--src/nvim/testdir/test_maparg.vim33
-rw-r--r--src/nvim/ui.c7
-rw-r--r--src/nvim/ui.h5
-rw-r--r--src/nvim/ui_compositor.c87
-rw-r--r--test/functional/lua/ui_event_spec.lua107
-rw-r--r--test/functional/vimscript/map_functions_spec.lua42
34 files changed, 550 insertions, 141 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 499b3f9a6f..a634cc1e93 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -967,6 +967,37 @@ vim.wait({time} [, {callback}, {interval}, {fast_only}]) *vim.wait()*
end
<
+vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()*
+ Attach to ui events, similar to |nvim_ui_attach()| but receive events
+ as lua callback. Can be used to implement screen elements like
+ popupmenu or message handling in lua.
+
+ {options} should be a dictionary-like table, where `ext_...` options should
+ be set to true to receive events for the respective external element.
+
+ {callback} receives event name plus additional parameters. See |ui-popupmenu|
+ and the sections below for event format for respective events.
+
+ Example (stub for a |ui-popupmenu| implementation): >
+
+ ns = vim.api.nvim_create_namespace('my_fancy_pum')
+
+ vim.ui_attach(ns, {ext_popupmenu=true}, function(event, ...)
+ if event == "popupmenu_show" then
+ local items, selected, row, col, grid = ...
+ print("display pum ", #items)
+ elseif event == "popupmenu_select" then
+ local selected = ...
+ print("selected", selected)
+ elseif event == "popupmenu_hide" then
+ print("FIN")
+ end
+ end)
+
+vim.ui_detach({ns}) *vim.ui_detach()*
+ Detach a callback previously attached with |vim.ui_attach()| for the
+ given namespace {ns}.
+
vim.type_idx *vim.type_idx*
Type index for use in |lua-special-tbl|. Specifying one of the values from
|vim.types| allows typing the empty table (it is unclear whether empty Lua
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index 802ebd42b5..bfece6aa72 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -964,14 +964,7 @@ func s:DeleteCommands()
nunmap K
else
" call mapset(s:k_map_saved)
- let mode = s:k_map_saved.mode !=# ' ' ? s:k_map_saved.mode : ''
- call nvim_set_keymap(mode, 'K', s:k_map_saved.rhs, {
- \ 'expr': s:k_map_saved.expr ? v:true : v:false,
- \ 'noremap': s:k_map_saved.noremap ? v:true : v:false,
- \ 'nowait': s:k_map_saved.nowait ? v:true : v:false,
- \ 'script': s:k_map_saved.script ? v:true : v:false,
- \ 'silent': s:k_map_saved.silent ? v:true : v:false,
- \ })
+ call mapset('n', 0, s:k_map_saved)
endif
unlet s:k_map_saved
endif
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 09b004637f..6ff0a2ed21 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -87,7 +87,7 @@ const char *describe_ns(NS ns_id)
}
// Is the Namespace in use?
-static bool ns_initialized(uint32_t ns)
+bool ns_initialized(uint32_t ns)
{
if (ns < 1) {
return false;
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index e34dcbdb46..1534e547b0 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -223,6 +223,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
ui->msg_set_pos = remote_ui_msg_set_pos;
ui->event = remote_ui_event;
ui->inspect = remote_ui_inspect;
+ ui->win_viewport = remote_ui_win_viewport;
CLEAR_FIELD(ui->ui_ext);
@@ -748,10 +749,12 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAt
UIData *data = ui->data;
Array args = data->call_buf;
ADD_C(args, INTEGER_OBJ(id));
- MAXSIZE_TEMP_DICT(rgb, 16);
- MAXSIZE_TEMP_DICT(cterm, 16);
- ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(&rgb, rgb_attrs, true)));
- ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(&cterm, cterm_attrs, false)));
+ MAXSIZE_TEMP_DICT(rgb, HLATTRS_DICT_SIZE);
+ MAXSIZE_TEMP_DICT(cterm, HLATTRS_DICT_SIZE);
+ hlattrs2dict(&rgb, rgb_attrs, true);
+ hlattrs2dict(&cterm, rgb_attrs, false);
+ ADD_C(args, DICTIONARY_OBJ(rgb));
+ ADD_C(args, DICTIONARY_OBJ(cterm));
if (ui->ui_ext[kUIHlState]) {
ADD_C(args, ARRAY_OBJ(info));
@@ -771,8 +774,9 @@ static void remote_ui_highlight_set(UI *ui, int id)
return;
}
data->hl_id = id;
- MAXSIZE_TEMP_DICT(dict, 16);
- ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb)));
+ MAXSIZE_TEMP_DICT(dict, HLATTRS_DICT_SIZE);
+ hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb);
+ ADD_C(args, DICTIONARY_OBJ(dict));
push_call(ui, "highlight_set", args);
}
@@ -952,65 +956,63 @@ static void remote_ui_flush(UI *ui)
}
}
-static Array translate_contents(UI *ui, Array contents)
+static Array translate_contents(UI *ui, Array contents, Arena *arena)
{
- Array new_contents = ARRAY_DICT_INIT;
+ Array new_contents = arena_array(arena, contents.size);
for (size_t i = 0; i < contents.size; i++) {
Array item = contents.items[i].data.array;
- Array new_item = ARRAY_DICT_INIT;
+ Array new_item = arena_array(arena, 2);
int attr = (int)item.items[0].data.integer;
if (attr) {
- Dictionary rgb_attrs = hlattrs2dict(NULL, syn_attr2entry(attr), ui->rgb);
+ Dictionary rgb_attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
+ hlattrs2dict(&rgb_attrs, syn_attr2entry(attr), ui->rgb);
ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
} else {
ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
}
- ADD(new_item, copy_object(item.items[1], NULL));
+ ADD(new_item, item.items[1]);
ADD(new_contents, ARRAY_OBJ(new_item));
}
return new_contents;
}
-static Array translate_firstarg(UI *ui, Array args)
+static Array translate_firstarg(UI *ui, Array args, Arena *arena)
{
- Array new_args = ARRAY_DICT_INIT;
+ Array new_args = arena_array(arena, args.size);
Array contents = args.items[0].data.array;
- ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents)));
+ ADD_C(new_args, ARRAY_OBJ(translate_contents(ui, contents, arena)));
for (size_t i = 1; i < args.size; i++) {
- ADD(new_args, copy_object(args.items[i], NULL));
+ ADD(new_args, args.items[i]);
}
return new_args;
}
static void remote_ui_event(UI *ui, char *name, Array args)
{
+ Arena arena = ARENA_EMPTY;
UIData *data = ui->data;
if (!ui->ui_ext[kUILinegrid]) {
// the representation of highlights in cmdline changed, translate back
// never consumes args
if (strequal(name, "cmdline_show")) {
- Array new_args = translate_firstarg(ui, args);
+ Array new_args = translate_firstarg(ui, args, &arena);
push_call(ui, name, new_args);
- api_free_array(new_args);
- return;
+ goto free_ret;
} else if (strequal(name, "cmdline_block_show")) {
Array new_args = data->call_buf;
Array block = args.items[0].data.array;
- Array new_block = ARRAY_DICT_INIT;
+ Array new_block = arena_array(&arena, block.size);
for (size_t i = 0; i < block.size; i++) {
- ADD(new_block,
- ARRAY_OBJ(translate_contents(ui, block.items[i].data.array)));
+ ADD_C(new_block, ARRAY_OBJ(translate_contents(ui, block.items[i].data.array, &arena)));
}
ADD_C(new_args, ARRAY_OBJ(new_block));
push_call(ui, name, new_args);
- api_free_array(new_block);
- return;
+ goto free_ret;
} else if (strequal(name, "cmdline_block_append")) {
- Array new_args = translate_firstarg(ui, args);
+ Array new_args = translate_firstarg(ui, args, &arena);
push_call(ui, name, new_args);
- api_free_array(new_args);
- return;
+ goto free_ret;
}
}
@@ -1022,19 +1024,18 @@ static void remote_ui_event(UI *ui, char *name, Array args)
if (data->wildmenu_active) {
Array new_args = data->call_buf;
Array items = args.items[0].data.array;
- Array new_items = ARRAY_DICT_INIT;
+ Array new_items = arena_array(&arena, items.size);
for (size_t i = 0; i < items.size; i++) {
- ADD(new_items, copy_object(items.items[i].data.array.items[0], NULL));
+ ADD_C(new_items, items.items[i].data.array.items[0]);
}
ADD_C(new_args, ARRAY_OBJ(new_items));
push_call(ui, "wildmenu_show", new_args);
- api_free_array(new_items);
if (args.items[1].data.integer != -1) {
Array new_args2 = data->call_buf;
ADD_C(new_args2, args.items[1]);
push_call(ui, "wildmenu_select", new_args2);
}
- return;
+ goto free_ret;
}
} else if (strequal(name, "popupmenu_select")) {
if (data->wildmenu_active) {
@@ -1048,6 +1049,10 @@ static void remote_ui_event(UI *ui, char *name, Array args)
}
push_call(ui, name, args);
+ return;
+
+free_ret:
+ arena_mem_free(arena_finish(&arena));
}
static void remote_ui_inspect(UI *ui, Dictionary *info)
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 8b7e01e1c3..17930dca85 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -100,7 +100,7 @@ void raw_line(Integer grid, Integer row, Integer startcol,
FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL;
void event(char *name, Array args)
- FUNC_API_NOEXPORT;
+ FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL;
void win_pos(Integer grid, Window win, Integer startrow,
Integer startcol, Integer width, Integer height)
@@ -121,7 +121,7 @@ void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
void win_viewport(Integer grid, Window win, Integer topline,
Integer botline, Integer curline, Integer curcol,
Integer line_count)
- FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY;
+ FUNC_API_SINCE(7) FUNC_API_BRIDGE_IMPL;
void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id,
Integer row, Integer col)
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 9e1f2dd631..83d6ba8dc7 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -79,7 +79,7 @@
/// @param[out] err Error details, if any
/// @return Highlight definition map
/// @see nvim_get_hl_by_id
-Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err)
+Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
FUNC_API_SINCE(3)
{
Dictionary result = ARRAY_DICT_INIT;
@@ -89,8 +89,7 @@ Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err)
api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data);
return result;
}
- result = nvim_get_hl_by_id(id, rgb, err);
- return result;
+ return nvim_get_hl_by_id(id, rgb, arena, err);
}
/// Gets a highlight definition by id. |hlID()|
@@ -99,7 +98,7 @@ Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err)
/// @param[out] err Error details, if any
/// @return Highlight definition map
/// @see nvim_get_hl_by_name
-Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err)
+Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
FUNC_API_SINCE(3)
{
Dictionary dic = ARRAY_DICT_INIT;
@@ -108,7 +107,7 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err)
return dic;
}
int attrcode = syn_id2attr((int)hl_id);
- return hl_get_attr_by_id(attrcode, rgb, err);
+ return hl_get_attr_by_id(attrcode, rgb, arena, err);
}
/// Gets a highlight group by name
@@ -120,10 +119,10 @@ Integer nvim_get_hl_id_by_name(String name)
return syn_check_group(name.data, name.size);
}
-Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
+Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
{
if (ns_id == 0) {
- return get_global_hl_defs();
+ return get_global_hl_defs(arena);
}
abort();
}
@@ -1934,7 +1933,7 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Di
}
/// NB: if your UI doesn't use hlstate, this will not return hlstate first time
-Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
+Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, Error *err)
{
Array ret = ARRAY_DICT_INIT;
@@ -1958,13 +1957,14 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
|| col < 0 || col >= g->cols) {
return ret;
}
+ ret = arena_array(arena, 3);
size_t off = g->line_offset[(size_t)row] + (size_t)col;
- ADD(ret, STRING_OBJ(cstr_to_string((char *)g->chars[off])));
+ ADD_C(ret, STRING_OBJ(cstr_as_string((char *)g->chars[off])));
int attr = g->attrs[off];
- ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err)));
+ ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
// will not work first time
if (!highlight_use_hlstate()) {
- ADD(ret, ARRAY_OBJ(hl_inspect(attr)));
+ ADD_C(ret, ARRAY_OBJ(hl_inspect(attr)));
}
return ret;
}
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index 14973502ab..1b3c0bc28f 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -285,14 +285,13 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
}
textlock++;
- Object res = nlua_call_ref(cb.on_lines, "lines", args, true, NULL);
+ Object res = nlua_call_ref(cb.on_lines, "lines", args, false, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
buffer_update_callbacks_free(cb);
keep = false;
}
- api_free_object(res);
}
if (keep) {
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
@@ -335,7 +334,7 @@ void buf_updates_send_splice(buf_T *buf, int start_row, colnr_T start_col, bcoun
ADD_C(args, INTEGER_OBJ(new_byte));
textlock++;
- Object res = nlua_call_ref(cb.on_bytes, "bytes", args, true, NULL);
+ Object res = nlua_call_ref(cb.on_bytes, "bytes", args, false, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -371,14 +370,13 @@ void buf_updates_changedtick(buf_T *buf)
textlock++;
Object res = nlua_call_ref(cb.on_changedtick, "changedtick",
- args, true, NULL);
+ args, false, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
buffer_update_callbacks_free(cb);
keep = false;
}
- api_free_object(res);
}
if (keep) {
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index 7e2adf7f29..6fc63f72a1 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -717,7 +717,6 @@ int showmatches(expand_T *xp, int wildmenu)
msg_clr_eos();
msg_putchar('\n');
}
- ui_flush(); // show one line at a time
if (got_int) {
got_int = false;
break;
diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c
index 1426054d0b..2057b0db9e 100644
--- a/src/nvim/cmdhist.c
+++ b/src/nvim/cmdhist.c
@@ -650,7 +650,6 @@ void ex_history(exarg_T *eap)
STRCAT(IObuff, hist[i].hisstr);
}
msg_outtrans((char *)IObuff);
- ui_flush();
}
if (i == idx) {
break;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 0b03cf3011..8927e52349 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5858,13 +5858,8 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
break;
case kCallbackLua:
- rv = nlua_call_ref(callback->data.luaref, NULL, args, true, NULL);
- switch (rv.type) {
- case kObjectTypeBoolean:
- return rv.data.boolean;
- default:
- return false;
- }
+ rv = nlua_call_ref(callback->data.luaref, NULL, args, false, NULL);
+ return (rv.type == kObjectTypeBoolean && rv.data.boolean == true);
case kCallbackNone:
return false;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index d050562e4c..db79187b7d 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -5004,7 +5004,6 @@ void ex_oldfiles(exarg_T *eap)
msg_outtrans((char *)tv_get_string(TV_LIST_ITEM_TV(li)));
msg_clr_eos();
msg_putchar('\n');
- ui_flush(); // output one line at a time
os_breakcheck();
}
});
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 01d141e557..0af737ff50 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -4833,7 +4833,6 @@ static void ex_print(exarg_T *eap)
if (++eap->line1 > eap->line2) {
break;
}
- ui_flush(); // show one line at a time
}
setpcmark();
// put cursor at last line
@@ -5035,7 +5034,6 @@ static void ex_tabs(exarg_T *eap)
msg_putchar('\n');
vim_snprintf((char *)IObuff, IOSIZE, _("Tab page %d"), tabcount++);
msg_outtrans_attr((char *)IObuff, HL_ATTR(HLF_T));
- ui_flush(); // output one line at a time
os_breakcheck();
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
@@ -5054,7 +5052,6 @@ static void ex_tabs(exarg_T *eap)
home_replace(wp->w_buffer, wp->w_buffer->b_fname, (char *)IObuff, IOSIZE, true);
}
msg_outtrans((char *)IObuff);
- ui_flush(); // output one line at a time
os_breakcheck();
}
}
diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua
index f9e888c20d..ea66be7ee8 100755
--- a/src/nvim/generators/gen_api_ui_events.lua
+++ b/src/nvim/generators/gen_api_ui_events.lua
@@ -75,6 +75,8 @@ local function call_ui_event_method(output, ev)
hlattrs_args_count = hlattrs_args_count + 1
elseif kind == 'Object' then
output:write('args.items['..(j-1)..'];\n')
+ elseif kind == 'Window' then
+ output:write('(Window)args.items['..(j-1)..'].data.integer;\n')
else
output:write('args.items['..(j-1)..'].data.'..string.lower(kind)..';\n')
end
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index a78b933108..e42cb66422 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -788,7 +788,7 @@ HlAttrs syn_attr2entry(int attr)
}
/// Gets highlight description for id `attr_id` as a map.
-Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err)
+Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *err)
{
Dictionary dic = ARRAY_DICT_INIT;
@@ -801,25 +801,21 @@ Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err)
"Invalid attribute id: %" PRId64, attr_id);
return dic;
}
-
- return hlattrs2dict(NULL, syn_attr2entry((int)attr_id), rgb);
+ Dictionary retval = arena_dict(arena, HLATTRS_DICT_SIZE);
+ hlattrs2dict(&retval, syn_attr2entry((int)attr_id), rgb);
+ return retval;
}
/// Converts an HlAttrs into Dictionary
///
-/// @param[out] hl optional pre-allocated dictionary for return value
-/// if present, must be allocated with at least 16 elements!
+/// @param[in/out] hl Dictionary with pre-allocated space for HLATTRS_DICT_SIZE elements
/// @param[in] aep data to convert
/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*'
-Dictionary hlattrs2dict(Dictionary *hl_alloc, HlAttrs ae, bool use_rgb)
+void hlattrs2dict(Dictionary *dict, HlAttrs ae, bool use_rgb)
{
+ assert(dict->capacity >= HLATTRS_DICT_SIZE); // at most 16 items
+ Dictionary hl = *dict;
int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr;
- Dictionary hl = ARRAY_DICT_INIT;
- if (hl_alloc) {
- hl = *hl_alloc;
- } else {
- kv_ensure_space(hl, 16);
- }
if (mask & HL_BOLD) {
PUT_C(hl, "bold", BOOLEAN_OBJ(true));
@@ -899,14 +895,7 @@ Dictionary hlattrs2dict(Dictionary *hl_alloc, HlAttrs ae, bool use_rgb)
PUT_C(hl, "blend", INTEGER_OBJ(ae.hl_blend));
}
- if (hl_alloc) {
- *hl_alloc = hl;
- return hl;
- } else {
- Dictionary allocated = copy_dictionary(hl, NULL);
- kv_destroy(hl);
- return allocated;
- }
+ *dict = hl;
}
HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err)
@@ -1086,6 +1075,7 @@ int object_to_color(Object val, char *key, bool rgb, Error *err)
Array hl_inspect(int attr)
{
+ // TODO(bfredl): use arena allocation
Array ret = ARRAY_DICT_INIT;
if (hlstate_active) {
hl_inspect_impl(&ret, attr);
diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h
index 50299bb91c..e85e3859e2 100644
--- a/src/nvim/highlight.h
+++ b/src/nvim/highlight.h
@@ -19,6 +19,8 @@ static inline int win_hl_attr(win_T *wp, int hlf)
return ((wp->w_ns_hl_attr && ns_hl_fast < 0) ? wp->w_ns_hl_attr : hl_attr_active)[hlf];
}
+#define HLATTRS_DICT_SIZE 16
+
#define HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp) \
do { \
bool dark_ = (*p_bg == 'd'); \
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index ed1f0185b7..5c07784db3 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -632,7 +632,9 @@ int load_colors(char_u *name)
retval = source_runtime((char *)buf, DIP_START + DIP_OPT);
}
xfree(buf);
- apply_autocmds(EVENT_COLORSCHEME, (char *)name, curbuf->b_fname, false, curbuf);
+ if (retval == OK) {
+ apply_autocmds(EVENT_COLORSCHEME, (char *)name, curbuf->b_fname, false, curbuf);
+ }
recursive = false;
@@ -1466,19 +1468,21 @@ static void highlight_list_one(const int id)
}
}
-Dictionary get_global_hl_defs(void)
+Dictionary get_global_hl_defs(Arena *arena)
{
- Dictionary rv = ARRAY_DICT_INIT;
- for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) {
+ Dictionary rv = arena_dict(arena, (size_t)highlight_ga.ga_len);
+ for (int i = 1; i <= highlight_ga.ga_len; i++) {
Dictionary attrs = ARRAY_DICT_INIT;
HlGroup *h = &hl_table[i - 1];
if (h->sg_attr > 0) {
- attrs = hlattrs2dict(NULL, syn_attr2entry(h->sg_attr), true);
+ attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
+ hlattrs2dict(&attrs, syn_attr2entry(h->sg_attr), true);
} else if (h->sg_link > 0) {
- const char *link = (const char *)hl_table[h->sg_link - 1].sg_name;
- PUT(attrs, "link", STRING_OBJ(cstr_to_string(link)));
+ attrs = arena_dict(arena, 1);
+ char *link = (char *)hl_table[h->sg_link - 1].sg_name;
+ PUT_C(attrs, "link", STRING_OBJ(cstr_as_string(link)));
}
- PUT(rv, (const char *)h->sg_name, DICTIONARY_OBJ(attrs));
+ PUT_C(rv, (char *)h->sg_name, DICTIONARY_OBJ(attrs));
}
return rv;
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 42aa13cfc1..f144e47c3a 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -7,6 +7,7 @@
#include <tree_sitter/api.h>
#include "luv/luv.h"
+#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
@@ -40,6 +41,9 @@
#include "nvim/os/os.h"
#include "nvim/profile.h"
#include "nvim/runtime.h"
+#include "nvim/screen.h"
+#include "nvim/ui.h"
+#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
#include "nvim/version.h"
@@ -589,6 +593,71 @@ static bool nlua_init_packages(lua_State *lstate)
return true;
}
+/// "vim.ui_attach(ns_id, {ext_foo=true}, cb)" function
+static int nlua_ui_attach(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ uint32_t ns_id = (uint32_t)luaL_checkinteger(lstate, 1);
+
+ if (!ns_initialized(ns_id)) {
+ return luaL_error(lstate, "invalid ns_id");
+ }
+ if (!lua_istable(lstate, 2)) {
+ return luaL_error(lstate, "ext_widgets must be a table");
+ }
+ if (!lua_isfunction(lstate, 3)) {
+ return luaL_error(lstate, "callback must be a Lua function");
+ }
+
+ bool ext_widgets[kUIGlobalCount] = { false };
+ bool tbl_has_true_val = false;
+
+ lua_pushvalue(lstate, 2);
+ lua_pushnil(lstate);
+ while (lua_next(lstate, -2)) {
+ // [dict, key, val]
+ size_t len;
+ const char *s = lua_tolstring(lstate, -2, &len);
+ bool val = lua_toboolean(lstate, -1);
+
+ for (size_t i = 0; i < kUIGlobalCount; i++) {
+ if (strequal(s, ui_ext_names[i])) {
+ if (val) {
+ tbl_has_true_val = true;
+ }
+ ext_widgets[i] = val;
+ goto ok;
+ }
+ }
+
+ return luaL_error(lstate, "Unexpected key: %s", s);
+ok:
+ lua_pop(lstate, 1);
+ }
+
+ if (!tbl_has_true_val) {
+ return luaL_error(lstate, "ext_widgets table must contain at least one 'true' value");
+ }
+
+ LuaRef ui_event_cb = nlua_ref_global(lstate, 3);
+ ui_comp_add_cb(ns_id, ui_event_cb, ext_widgets);
+ return 0;
+}
+
+/// "vim.ui_detach(ns_id)" function
+static int nlua_ui_detach(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ uint32_t ns_id = (uint32_t)luaL_checkinteger(lstate, 1);
+
+ if (!ns_initialized(ns_id)) {
+ return luaL_error(lstate, "invalid ns_id");
+ }
+
+ ui_comp_remove_cb(ns_id);
+ return 0;
+}
+
/// Initialize lua interpreter state
///
/// Called by lua interpreter itself to initialize state.
@@ -649,6 +718,14 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, &nlua_wait);
lua_setfield(lstate, -2, "wait");
+ // ui_attach
+ lua_pushcfunction(lstate, &nlua_ui_attach);
+ lua_setfield(lstate, -2, "ui_attach");
+
+ // ui_detach
+ lua_pushcfunction(lstate, &nlua_ui_detach);
+ lua_setfield(lstate, -2, "ui_detach");
+
nlua_common_vim_init(lstate, false);
// patch require() (only for --startuptime)
@@ -1422,9 +1499,10 @@ bool nlua_ref_is_function(LuaRef ref)
/// @param name if non-NULL, sent to callback as first arg
/// if NULL, only args are used
/// @param retval if true, convert return value to Object
-/// if false, discard return value
+/// if false, only check if return value is truthy
/// @param err Error details, if any (if NULL, errors are echoed)
-/// @return Return value of function, if retval was set. Otherwise NIL.
+/// @return Return value of function, if retval was set. Otherwise
+/// BOOLEAN_OBJ(true) or NIL.
Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Error *err)
{
lua_State *const lstate = global_lstate;
@@ -1438,7 +1516,7 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Erro
nlua_push_Object(lstate, args.items[i], false);
}
- if (nlua_pcall(lstate, nargs, retval ? 1 : 0)) {
+ if (nlua_pcall(lstate, nargs, 1)) {
// if err is passed, the caller will deal with the error.
if (err) {
size_t len;
@@ -1458,7 +1536,10 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Erro
}
return nlua_pop_Object(lstate, false, err);
} else {
- return NIL;
+ bool value = lua_toboolean(lstate, -1);
+ lua_pop(lstate, 1);
+
+ return value ? BOOLEAN_OBJ(true) : NIL;
}
}
diff --git a/src/nvim/map.c b/src/nvim/map.c
index d3058a5d52..1561b089a7 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -168,6 +168,7 @@ MAP_IMPL(int, cstr_t, DEFAULT_INITIALIZER)
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(cstr_t, int, DEFAULT_INITIALIZER)
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
+MAP_IMPL(uint32_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 845daac3f7..f5f30f5a85 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -39,6 +39,7 @@ MAP_DECLS(int, cstr_t)
MAP_DECLS(cstr_t, ptr_t)
MAP_DECLS(cstr_t, int)
MAP_DECLS(ptr_t, ptr_t)
+MAP_DECLS(uint32_t, ptr_t)
MAP_DECLS(uint64_t, ptr_t)
MAP_DECLS(uint64_t, ssize_t)
MAP_DECLS(uint64_t, uint64_t)
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index ae37141eac..923eea145c 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -214,7 +214,6 @@ static void showmap(mapblock_T *mp, bool local)
last_set_msg(mp->m_script_ctx);
}
msg_clr_eos();
- ui_flush(); // show one line at a time
}
/// Replace termcodes in the given LHS and RHS and store the results into the
@@ -277,21 +276,29 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs
mapargs->alt_lhs_len = 0;
}
+ set_maparg_rhs(orig_rhs, orig_rhs_len, rhs_lua, cpo_flags, mapargs);
+
+ return true;
+}
+
+/// @see set_maparg_lhs_rhs
+static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len,
+ const LuaRef rhs_lua, const int cpo_flags, MapArguments *const mapargs)
+{
mapargs->rhs_lua = rhs_lua;
if (rhs_lua == LUA_NOREF) {
mapargs->orig_rhs_len = orig_rhs_len;
mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u));
STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1);
-
if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing
- mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char
+ mapargs->rhs = xcalloc(1, sizeof(char_u)); // single NUL-char
mapargs->rhs_len = 0;
mapargs->rhs_is_noop = true;
} else {
char *rhs_buf = NULL;
- replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL,
- cpo_flags);
+ char *replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL,
+ cpo_flags);
mapargs->rhs_len = STRLEN(replaced);
// NB: replace_termcodes may produce an empty string even if orig_rhs is non-empty
// (e.g. a single ^V, see :h map-empty-rhs)
@@ -308,7 +315,6 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs
(char_u)KS_EXTRA, KE_LUA, rhs_lua);
mapargs->rhs = vim_strsave((char_u *)tmp_buf);
}
- return true;
}
/// Parse a string of |:map-arguments| into a @ref MapArguments struct.
@@ -2001,8 +2007,7 @@ static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhs
FUNC_ATTR_NONNULL_ARG(1)
{
Dictionary dict = ARRAY_DICT_INIT;
- char *const lhs = str2special_save((const char *)mp->m_keys,
- compatible, !compatible);
+ char *const lhs = str2special_save((const char *)mp->m_keys, compatible, !compatible);
char *const mapmode = map_mode_to_chars(mp->m_mode);
varnumber_T noremap_value;
@@ -2115,13 +2120,15 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
}
} else {
// Return a dictionary.
- tv_dict_alloc_ret(rettv);
if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) {
Dictionary dict = mapblock_fill_dict(mp,
did_simplify ? (char *)keys_simplified : NULL,
buffer_local, true);
(void)object_to_vim(DICTIONARY_OBJ(dict), rettv, NULL);
api_free_dictionary(dict);
+ } else {
+ // Return an empty dictionary.
+ tv_dict_alloc_ret(rettv);
}
}
@@ -2150,29 +2157,36 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char *lhs = tv_dict_get_string(d, "lhs", false);
char *lhsraw = tv_dict_get_string(d, "lhsraw", false);
char *lhsrawalt = tv_dict_get_string(d, "lhsrawalt", false);
- char *rhs = tv_dict_get_string(d, "rhs", false);
- if (lhs == NULL || lhsraw == NULL || rhs == NULL) {
+ char *orig_rhs = tv_dict_get_string(d, "rhs", false);
+ LuaRef rhs_lua = LUA_NOREF;
+ dictitem_T *callback_di = tv_dict_find(d, S_LEN("callback"));
+ if (callback_di != NULL) {
+ Object callback_obj = vim_to_object(&callback_di->di_tv);
+ if (callback_obj.type == kObjectTypeLuaRef && callback_obj.data.luaref != LUA_NOREF) {
+ rhs_lua = callback_obj.data.luaref;
+ orig_rhs = "";
+ callback_obj.data.luaref = LUA_NOREF;
+ }
+ api_free_object(callback_obj);
+ }
+ if (lhs == NULL || lhsraw == NULL || orig_rhs == NULL) {
emsg(_("E460: entries missing in mapset() dict argument"));
+ api_free_luaref(rhs_lua);
return;
}
- char *orig_rhs = rhs;
- char *arg_buf = NULL;
- rhs = replace_termcodes(rhs, STRLEN(rhs), &arg_buf, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
- int noremap = tv_dict_get_number(d, "noremap") ? REMAP_NONE : 0;
+ int noremap = tv_dict_get_number(d, "noremap") != 0 ? REMAP_NONE : 0;
if (tv_dict_get_number(d, "script") != 0) {
noremap = REMAP_SCRIPT;
}
- MapArguments args = { // TODO(zeertzjq): support restoring "callback"?
- .rhs = (char_u *)rhs,
- .rhs_lua = LUA_NOREF,
- .orig_rhs = vim_strsave((char_u *)orig_rhs),
+ MapArguments args = {
.expr = tv_dict_get_number(d, "expr") != 0,
.silent = tv_dict_get_number(d, "silent") != 0,
.nowait = tv_dict_get_number(d, "nowait") != 0,
.replace_keycodes = tv_dict_get_number(d, "replace_keycodes") != 0,
.desc = tv_dict_get_string(d, "desc", false),
};
+ set_maparg_rhs(orig_rhs, strlen(orig_rhs), rhs_lua, CPO_TO_CPO_FLAGS, &args);
scid_T sid = (scid_T)tv_dict_get_number(d, "sid");
linenr_T lnum = (linenr_T)tv_dict_get_number(d, "lnum");
bool buffer = tv_dict_get_number(d, "buffer") != 0;
@@ -2183,7 +2197,7 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Delete any existing mapping for this lhs and mode.
MapArguments unmap_args = MAP_ARGUMENTS_INIT;
- set_maparg_lhs_rhs(lhs, strlen(lhs), rhs, strlen(rhs), LUA_NOREF, 0, &unmap_args);
+ set_maparg_lhs_rhs(lhs, strlen(lhs), "", 0, LUA_NOREF, 0, &unmap_args);
unmap_args.buffer = buffer;
buf_do_map(MAPTYPE_UNMAP, &unmap_args, mode, false, curbuf);
xfree(unmap_args.rhs);
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 816599271d..7eab37e797 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -925,7 +925,6 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu
msg_outtrans_attr((char *)name, current ? HL_ATTR(HLF_D) : 0);
}
}
- ui_flush(); // show one line at a time
}
if (mustfree) {
xfree(name);
@@ -1058,7 +1057,6 @@ void ex_jumps(exarg_T *eap)
xfree(name);
os_breakcheck();
}
- ui_flush();
}
if (curwin->w_jumplistidx == curwin->w_jumplistlen) {
msg_puts("\n>");
@@ -1101,7 +1099,6 @@ void ex_changes(exarg_T *eap)
xfree(name);
os_breakcheck();
}
- ui_flush();
}
if (curwin->w_changelistidx == curbuf->b_changelistlen) {
msg_puts("\n>");
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index a9785fcb7c..acd2478c81 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -23,6 +23,7 @@
#include "nvim/message.h"
#include "nvim/sign.h"
#include "nvim/ui.h"
+#include "nvim/ui_compositor.h"
#include "nvim/vim.h"
#ifdef UNIT_TESTING
@@ -824,6 +825,7 @@ void free_all_mem(void)
nlua_free_all_mem();
ui_free_all_mem();
+ ui_comp_free_all_mem();
// should be last, in case earlier free functions deallocates arenas
arena_free_reuse_blks();
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index adb071b754..d2321be4a4 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -3866,7 +3866,6 @@ void ex_display(exarg_T *eap)
if (n > 1 && yb->y_type == kMTLineWise) {
msg_puts_attr("^J", attr);
}
- ui_flush(); // show one line at a time
}
os_breakcheck();
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 455070c742..269a21b512 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3355,7 +3355,6 @@ static void showoptions(int all, int opt_flags)
showoneopt(items[i], opt_flags);
col += INC;
}
- ui_flush();
os_breakcheck();
}
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index f1fc48ef40..86f60cb57e 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -3094,8 +3094,6 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel)
if (tbuf != IObuff) {
xfree(tbuf);
}
-
- ui_flush(); // show one line at a time
}
// ":clist": list all errors
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 226d949825..db1c766eb2 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -3714,7 +3714,6 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo
}
}
}
- ui_flush(); // output each line directly
}
if (new_fname != NULL) {
@@ -4147,7 +4146,6 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action,
msg_puts(" ");
}
msg_prt_line((char *)line, false);
- ui_flush(); // show one line at a time
// Definition continues until line that doesn't end with '\'
if (got_int || type != FIND_DEFINE || p < line || *p != '\\') {
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 54c1dc7741..648eb751fe 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1056,7 +1056,6 @@ void do_tags(exarg_T *eap)
? HL_ATTR(HLF_D) : 0);
xfree(name);
}
- ui_flush(); // show one line at a time
}
if (tagstackidx == tagstacklen) { // idx at top of stack
msg_puts("\n>");
diff --git a/src/nvim/testdir/test_gui.vim b/src/nvim/testdir/test_gui.vim
new file mode 100644
index 0000000000..c3f1f3163a
--- /dev/null
+++ b/src/nvim/testdir/test_gui.vim
@@ -0,0 +1,43 @@
+
+func Test_colorscheme()
+ " call assert_equal('16777216', &t_Co)
+
+ let colorscheme_saved = exists('g:colors_name') ? g:colors_name : 'default'
+ let g:color_count = 0
+ augroup TestColors
+ au!
+ au ColorScheme * let g:color_count += 1
+ \ | let g:after_colors = g:color_count
+ \ | let g:color_after = expand('<amatch>')
+ au ColorSchemePre * let g:color_count += 1
+ \ | let g:before_colors = g:color_count
+ \ | let g:color_pre = expand('<amatch>')
+ augroup END
+
+ colorscheme torte
+ redraw!
+ call assert_equal('dark', &background)
+ call assert_equal(1, g:before_colors)
+ call assert_equal(2, g:after_colors)
+ call assert_equal('torte', g:color_pre)
+ call assert_equal('torte', g:color_after)
+ call assert_equal("\ntorte", execute('colorscheme'))
+
+ let a = substitute(execute('hi Search'), "\n\\s\\+", ' ', 'g')
+ " FIXME: temporarily check less while the colorscheme changes
+ " call assert_match("\nSearch xxx term=reverse cterm=reverse ctermfg=196 ctermbg=16 gui=reverse guifg=#ff0000 guibg=#000000", a)
+ " call assert_match("\nSearch xxx term=reverse ", a)
+
+ call assert_fails('colorscheme does_not_exist', 'E185:')
+ call assert_equal('does_not_exist', g:color_pre)
+ call assert_equal('torte', g:color_after)
+
+ exec 'colorscheme' colorscheme_saved
+ augroup TestColors
+ au!
+ augroup END
+ unlet g:color_count g:after_colors g:before_colors
+ redraw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim
index 17c5f433ac..f903f5b934 100644
--- a/src/nvim/testdir/test_maparg.vim
+++ b/src/nvim/testdir/test_maparg.vim
@@ -158,11 +158,11 @@ func Test_range_map()
call assert_equal("abcd", getline(1))
endfunc
-func One_mapset_test(keys)
- exe 'nnoremap ' .. a:keys .. ' original<CR>'
+func One_mapset_test(keys, rhs)
+ exe 'nnoremap ' .. a:keys .. ' ' .. a:rhs
let orig = maparg(a:keys, 'n', 0, 1)
call assert_equal(a:keys, orig.lhs)
- call assert_equal('original<CR>', orig.rhs)
+ call assert_equal(a:rhs, orig.rhs)
call assert_equal('n', orig.mode)
exe 'nunmap ' .. a:keys
@@ -172,15 +172,16 @@ func One_mapset_test(keys)
call mapset('n', 0, orig)
let d = maparg(a:keys, 'n', 0, 1)
call assert_equal(a:keys, d.lhs)
- call assert_equal('original<CR>', d.rhs)
+ call assert_equal(a:rhs, d.rhs)
call assert_equal('n', d.mode)
exe 'nunmap ' .. a:keys
endfunc
func Test_mapset()
- call One_mapset_test('K')
- call One_mapset_test('<F3>')
+ call One_mapset_test('K', 'original<CR>')
+ call One_mapset_test('<F3>', 'original<CR>')
+ call One_mapset_test('<F3>', '<lt>Nop>')
" Check <> key conversion
new
@@ -203,6 +204,26 @@ func Test_mapset()
iunmap K
+ " Test that <Nop> is restored properly
+ inoremap K <Nop>
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('', getline(1))
+
+ let orig = maparg('K', 'i', 0, 1)
+ call assert_equal('K', orig.lhs)
+ call assert_equal('<Nop>', orig.rhs)
+ call assert_equal('i', orig.mode)
+
+ inoremap K foo
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('foo', getline(1))
+
+ call mapset('i', 0, orig)
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('', getline(1))
+
+ iunmap K
+
" Test literal <CR> using a backslash
let cpo_save = &cpo
set cpo-=B
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 08c1946e13..911eefadd8 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -198,13 +198,16 @@ void ui_refresh(void)
ext_widgets[i] = true;
}
+ UI *compositor = uis[0];
+
bool inclusive = ui_override();
- for (size_t i = 0; i < ui_count; i++) {
+ for (size_t i = 1; i < ui_count; i++) {
UI *ui = uis[i];
width = MIN(ui->width, width);
height = MIN(ui->height, height);
for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
- ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
+ bool in_compositor = ui->composed && compositor->ui_ext[j];
+ ext_widgets[j] &= (ui->ui_ext[j] || in_compositor || inclusive);
}
}
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index 996b3467a6..9034e7b764 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -69,6 +69,11 @@ struct ui_t {
void (*inspect)(UI *ui, Dictionary *info);
};
+typedef struct ui_event_callback {
+ LuaRef cb;
+ bool ext_widgets[kUIGlobalCount];
+} UIEventCallback;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.h.generated.h"
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 2216e25db9..5167f291c3 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -20,6 +20,7 @@
#include "nvim/log.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
+#include "nvim/map.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/os/os.h"
@@ -54,6 +55,8 @@ static bool msg_was_scrolled = false;
static int msg_sep_row = -1;
static schar_T msg_sep_char = { ' ', NUL };
+static PMap(uint32_t) ui_event_cbs = MAP_INIT;
+
static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
void ui_comp_init(void)
@@ -69,14 +72,18 @@ void ui_comp_init(void)
compositor->grid_cursor_goto = ui_comp_grid_cursor_goto;
compositor->raw_line = ui_comp_raw_line;
compositor->msg_set_pos = ui_comp_msg_set_pos;
+ compositor->event = ui_comp_event;
// Be unopinionated: will be attached together with a "real" ui anyway
compositor->width = INT_MAX;
compositor->height = INT_MAX;
- for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
+ for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) {
compositor->ui_ext[i] = true;
}
+ // TODO(bfredl): one day. in the future.
+ compositor->ui_ext[kUIMultigrid] = false;
+
// TODO(bfredl): this will be more complicated if we implement
// hlstate per UI (i e reduce hl ids for non-hlstate UIs)
compositor->ui_ext[kUIHlState] = false;
@@ -87,6 +94,15 @@ void ui_comp_init(void)
ui_attach_impl(compositor, 0);
}
+void ui_comp_free_all_mem(void)
+{
+ UIEventCallback *event_cb;
+ map_foreach_value(&ui_event_cbs, event_cb, {
+ xfree(event_cb);
+ })
+ pmap_destroy(uint32_t)(&ui_event_cbs);
+}
+
void ui_comp_syn_init(void)
{
dbghl_normal = syn_check_group(S_LEN("RedrawDebugNormal"));
@@ -676,3 +692,72 @@ static void ui_comp_grid_resize(UI *ui, Integer grid, Integer width, Integer hei
}
}
}
+
+static void ui_comp_event(UI *ui, char *name, Array args)
+{
+ Error err = ERROR_INIT;
+ UIEventCallback *event_cb;
+ bool handled = false;
+
+ map_foreach_value(&ui_event_cbs, event_cb, {
+ Object res = nlua_call_ref(event_cb->cb, name, args, false, &err);
+ if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
+ handled = true;
+ }
+ })
+
+ if (!handled) {
+ ui_composed_call_event(name, args);
+ }
+}
+
+static void ui_comp_update_ext(void)
+{
+ memset(compositor->ui_ext, 0, ARRAY_SIZE(compositor->ui_ext));
+
+ for (size_t i = 0; i < kUIGlobalCount; i++) {
+ UIEventCallback *event_cb;
+
+ map_foreach_value(&ui_event_cbs, event_cb, {
+ if (event_cb->ext_widgets[i]) {
+ compositor->ui_ext[i] = true;
+ break;
+ }
+ })
+ }
+}
+
+void free_ui_event_callback(UIEventCallback *event_cb)
+{
+ api_free_luaref(event_cb->cb);
+ xfree(event_cb);
+}
+
+void ui_comp_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets)
+{
+ UIEventCallback *event_cb = xcalloc(1, sizeof(UIEventCallback));
+ event_cb->cb = cb;
+ memcpy(event_cb->ext_widgets, ext_widgets, ARRAY_SIZE(event_cb->ext_widgets));
+ if (event_cb->ext_widgets[kUIMessages]) {
+ event_cb->ext_widgets[kUICmdline] = true;
+ }
+
+ UIEventCallback **item = (UIEventCallback **)pmap_ref(uint32_t)(&ui_event_cbs, ns_id, true);
+ if (*item) {
+ free_ui_event_callback(*item);
+ }
+ *item = event_cb;
+
+ ui_comp_update_ext();
+ ui_schedule_refresh();
+}
+
+void ui_comp_remove_cb(uint32_t ns_id)
+{
+ if (pmap_has(uint32_t)(&ui_event_cbs, ns_id)) {
+ free_ui_event_callback(pmap_get(uint32_t)(&ui_event_cbs, ns_id));
+ pmap_del(uint32_t)(&ui_event_cbs, ns_id);
+ }
+ ui_comp_update_ext();
+ ui_schedule_refresh();
+}
diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua
new file mode 100644
index 0000000000..05322a0fdb
--- /dev/null
+++ b/test/functional/lua/ui_event_spec.lua
@@ -0,0 +1,107 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+local clear = helpers.clear
+local feed = helpers.feed
+local funcs = helpers.funcs
+local inspect = require'vim.inspect'
+
+describe('vim.ui_attach', function()
+ local screen
+ before_each(function()
+ clear()
+ exec_lua [[
+ ns = vim.api.nvim_create_namespace 'testspace'
+ events = {}
+ function on_event(event, ...)
+ events[#events+1] = {event, ...}
+ return true
+ end
+
+ function get_events()
+ local ret_events = events
+ events = {}
+ return ret_events
+ end
+ ]]
+
+ screen = Screen.new(40,5)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1};
+ [2] = {bold = true};
+ [3] = {background = Screen.colors.Grey};
+ [4] = {background = Screen.colors.LightMagenta};
+ })
+ screen:attach()
+ end)
+
+ local function expect_events(expected)
+ local evs = exec_lua "return get_events(...)"
+ eq(expected, evs, inspect(evs))
+ end
+
+ it('can receive popupmenu events', function()
+ exec_lua [[ vim.ui_attach(ns, {ext_popupmenu=true}, on_event) ]]
+ feed('ifo')
+ screen:expect{grid=[[
+ fo^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]]}
+
+ funcs.complete(1, {'food', 'foobar', 'foo'})
+ screen:expect{grid=[[
+ food^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]]}
+ expect_events {
+ { "popupmenu_show", { { "food", "", "", "" }, { "foobar", "", "", "" }, { "foo", "", "", "" } }, 0, 0, 0, 1 };
+ }
+
+ feed '<c-n>'
+ screen:expect{grid=[[
+ foobar^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]]}
+ expect_events {
+ { "popupmenu_select", 1 };
+ }
+
+ feed '<c-y>'
+ screen:expect{grid=[[
+ foobar^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]], intermediate=true}
+ expect_events {
+ { "popupmenu_hide" };
+ }
+
+ -- ui_detach stops events, and reenables builtin pum
+ exec_lua [[ vim.ui_detach(ns) ]]
+
+ funcs.complete(1, {'food', 'foobar', 'foo'})
+ screen:expect{grid=[[
+ food^ |
+ {3:food }{1: }|
+ {4:foobar }{1: }|
+ {4:foo }{1: }|
+ {2:-- INSERT --} |
+ ]]}
+ expect_events {
+ }
+
+
+ end)
+end)
diff --git a/test/functional/vimscript/map_functions_spec.lua b/test/functional/vimscript/map_functions_spec.lua
index aa64006de0..96b86d053e 100644
--- a/test/functional/vimscript/map_functions_spec.lua
+++ b/test/functional/vimscript/map_functions_spec.lua
@@ -3,6 +3,8 @@ local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local eval = helpers.eval
+local exec = helpers.exec
+local exec_lua = helpers.exec_lua
local expect = helpers.expect
local feed = helpers.feed
local funcs = helpers.funcs
@@ -10,6 +12,7 @@ local meths = helpers.meths
local nvim = helpers.nvim
local source = helpers.source
local command = helpers.command
+local pcall_err = helpers.pcall_err
describe('maparg()', function()
before_each(clear)
@@ -194,4 +197,43 @@ describe('mapset()', function()
feed('foo')
expect('<<lt><')
end)
+
+ it('can restore Lua callback from the dict returned by maparg()', function()
+ eq(0, exec_lua([[
+ GlobalCount = 0
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ return GlobalCount
+ ]]))
+ feed('asdf')
+ eq(1, exec_lua([[return GlobalCount]]))
+
+ exec_lua([[
+ _G.saved_asdf_map = vim.fn.maparg('asdf', 'n', false, true)
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 10 end })
+ ]])
+ feed('asdf')
+ eq(11, exec_lua([[return GlobalCount]]))
+
+ exec_lua([[vim.fn.mapset('n', false, _G.saved_asdf_map)]])
+ feed('asdf')
+ eq(12, exec_lua([[return GlobalCount]]))
+
+ exec([[
+ let g:saved_asdf_map = maparg('asdf', 'n', v:false, v:true)
+ lua vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 10 end })
+ ]])
+ feed('asdf')
+ eq(22, exec_lua([[return GlobalCount]]))
+
+ command([[call mapset('n', v:false, g:saved_asdf_map)]])
+ feed('asdf')
+ eq(23, exec_lua([[return GlobalCount]]))
+ end)
+
+ it('does not leak memory if lhs is missing', function()
+ eq('Error executing lua: Vim:E460: entries missing in mapset() dict argument',
+ pcall_err(exec_lua, [[vim.fn.mapset('n', false, {rhs = 'foo'})]]))
+ eq('Error executing lua: Vim:E460: entries missing in mapset() dict argument',
+ pcall_err(exec_lua, [[vim.fn.mapset('n', false, {callback = function() end})]]))
+ end)
end)