Skip to content

Commit

Permalink
Merge pull request #16594 from shadmansaleh/feat/api/lua_keymaps
Browse files Browse the repository at this point in the history
feat(api): add support for lua function & description in keymap
  • Loading branch information
bfredl committed Dec 31, 2021
2 parents 5c1b8b7 + b411f43 commit b218d02
Show file tree
Hide file tree
Showing 16 changed files with 460 additions and 69 deletions.
7 changes: 5 additions & 2 deletions runtime/doc/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1580,8 +1580,11 @@ nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()*
{rhs} Right-hand-side |{rhs}| of the mapping.
{opts} Optional parameters map. Accepts all
|:map-arguments| as keys excluding |<buffer>| but
including |noremap|. Values are Booleans. Unknown
key is an error.
including |noremap| and "desc". |desc| can be used
to give a description to keymap. When called from
Lua, also accepts a "callback" key that takes a
Lua function to call when the mapping is executed.
Values are Booleans. Unknown key is an error.

nvim_set_option({name}, {value}) *nvim_set_option()*
Sets the global value of an option.
Expand Down
4 changes: 2 additions & 2 deletions src/nvim/api/buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err)
/// @param[out] err Error details, if any
/// @returns Array of maparg()-like dictionaries describing mappings.
/// The "buffer" key holds the associated buffer handle.
ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, String mode, Error *err)
FUNC_API_SINCE(3)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
Expand All @@ -844,7 +844,7 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
return (Array)ARRAY_DICT_INIT;
}

return keymap_array(mode, buf);
return keymap_array(mode, buf, channel_id == LUA_INTERNAL_CALL);
}

/// Sets a buffer-local |mapping| for the given mode.
Expand Down
2 changes: 2 additions & 0 deletions src/nvim/api/keysets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ return {
"script";
"expr";
"unique";
"callback";
"desc";
};
get_commands = {
"builtin";
Expand Down
46 changes: 37 additions & 9 deletions src/nvim/api/private/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ Array string_to_array(const String input, bool crlf)
void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String rhs,
Dict(keymap) *opts, Error *err)
{
LuaRef lua_funcref = LUA_NOREF;
bool global = (buffer == -1);
if (global) {
buffer = 0;
Expand All @@ -604,6 +605,9 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
return;
}

if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) {
lua_funcref = api_new_luaref(opts->callback.data.luaref);
}
MapArguments parsed_args = MAP_ARGUMENTS_INIT;
if (opts) {
#define KEY_TO_BOOL(name) \
Expand All @@ -623,9 +627,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
parsed_args.buffer = !global;

set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size,
(char_u *)rhs.data, rhs.size,
(char_u *)rhs.data, rhs.size, lua_funcref,
CPO_TO_CPO_FLAGS, &parsed_args);

if (opts != NULL && opts->desc.type == kObjectTypeString) {
parsed_args.desc = xstrdup(opts->desc.data.string.data);
} else {
parsed_args.desc = NULL;
}
if (parsed_args.lhs_len > MAXMAPLEN) {
api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data);
goto fail_and_free;
Expand Down Expand Up @@ -658,7 +666,8 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
bool is_noremap = parsed_args.noremap;
assert(!(is_unmap && is_noremap));

if (!is_unmap && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
if (!is_unmap && lua_funcref == LUA_NOREF
&& (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop>
parsed_args.rhs_is_noop = true;
} else {
Expand All @@ -668,9 +677,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data);
goto fail_and_free;
}
} else if (is_unmap && parsed_args.rhs_len) {
api_set_error(err, kErrorTypeValidation,
"Gave nonempty RHS in unmap command: %s", parsed_args.rhs);
} else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) {
if (parsed_args.rhs_len) {
api_set_error(err, kErrorTypeValidation,
"Gave nonempty RHS in unmap command: %s", parsed_args.rhs);
} else {
api_set_error(err, kErrorTypeValidation, "Gave nonempty RHS for unmap");
}
goto fail_and_free;
}

Expand Down Expand Up @@ -700,9 +713,12 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String
goto fail_and_free;
} // switch

parsed_args.rhs_lua = LUA_NOREF; // don't clear ref on success
fail_and_free:
NLUA_CLEAR_REF(parsed_args.rhs_lua);
xfree(parsed_args.rhs);
xfree(parsed_args.orig_rhs);
XFREE_CLEAR(parsed_args.desc);
return;
}

Expand Down Expand Up @@ -1052,8 +1068,9 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...)
///
/// @param mode The abbreviation for the mode
/// @param buf The buffer to get the mapping array. NULL for global
/// @param from_lua Whether it is called from internal lua api.
/// @returns Array of maparg()-like dictionaries describing mappings
ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
{
Array mappings = ARRAY_DICT_INIT;
dict_T *const dict = tv_dict_alloc();
Expand All @@ -1073,8 +1090,19 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
// Check for correct mode
if (int_mode & current_maphash->m_mode) {
mapblock_fill_dict(dict, current_maphash, buffer_value, false);
ADD(mappings, vim_to_object((typval_T[]) { { .v_type = VAR_DICT, .vval.v_dict = dict } }));

Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT,
.vval.v_dict = dict } });
if (from_lua) {
Dictionary d = api_dict.data.dictionary;
for (size_t j = 0; j < d.size; j++) {
if (strequal("callback", d.items[j].key.data)) {
d.items[j].value.type = kObjectTypeLuaRef;
d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer);
break;
}
}
}
ADD(mappings, api_dict);
tv_dict_clear(dict);
}
}
Expand Down
9 changes: 6 additions & 3 deletions src/nvim/api/vim.c
Original file line number Diff line number Diff line change
Expand Up @@ -1538,10 +1538,10 @@ Dictionary nvim_get_mode(void)
/// @param mode Mode short-name ("n", "i", "v", ...)
/// @returns Array of maparg()-like dictionaries describing mappings.
/// The "buffer" key is always zero.
ArrayOf(Dictionary) nvim_get_keymap(String mode)
ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode)
FUNC_API_SINCE(3)
{
return keymap_array(mode, NULL);
return keymap_array(mode, NULL, channel_id == LUA_INTERNAL_CALL);
}

/// Sets a global |mapping| for the given mode.
Expand All @@ -1566,7 +1566,10 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
/// @param lhs Left-hand-side |{lhs}| of the mapping.
/// @param rhs Right-hand-side |{rhs}| of the mapping.
/// @param opts Optional parameters map. Accepts all |:map-arguments|
/// as keys excluding |<buffer>| but including |noremap|.
/// as keys excluding |<buffer>| but including |noremap| and "desc".
/// |desc| can be used to give a description to keymap.
/// When called from Lua, also accepts a "callback" key that takes
/// a Lua function to call when the mapping is executed.
/// Values are Booleans. Unknown key is an error.
/// @param[out] err Error details, if any.
void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err)
Expand Down
2 changes: 2 additions & 0 deletions src/nvim/buffer_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,15 @@ struct mapblock {
char_u *m_keys; // mapped from, lhs
char_u *m_str; // mapped to, rhs
char_u *m_orig_str; // rhs as entered by the user
LuaRef m_luaref; // lua function reference as rhs
int m_keylen; // strlen(m_keys)
int m_mode; // valid mode
int m_noremap; // if non-zero no re-mapping for m_str
char m_silent; // <silent> used, don't echo commands
char m_nowait; // <nowait> used
char m_expr; // <expr> used, m_str is an expression
sctx_T m_script_ctx; // SCTX where map was defined
char *m_desc; // description of keymap
};

/// Used for highlighting in the status line.
Expand Down
4 changes: 4 additions & 0 deletions src/nvim/edit.c
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,10 @@ static int insert_handle_key(InsertState *s)

case K_COMMAND: // some command
do_cmdline(NULL, getcmdkeycmd, NULL, 0);
goto check_pum;

case K_LUA:
map_execute_lua();

check_pum:
// TODO(bfredl): Not entirely sure this indirection is necessary
Expand Down
17 changes: 12 additions & 5 deletions src/nvim/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -7299,12 +7299,19 @@ void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buf
noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap;
}

if (compatible) {
tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str);
if (mp->m_luaref != LUA_NOREF) {
tv_dict_add_nr(dict, S_LEN("callback"), mp->m_luaref);
} else {
tv_dict_add_allocated_str(dict, S_LEN("rhs"),
str2special_save((const char *)mp->m_str, false,
true));
if (compatible) {
tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str);
} else {
tv_dict_add_allocated_str(dict, S_LEN("rhs"),
str2special_save((const char *)mp->m_str, false,
true));
}
}
if (mp->m_desc != NULL) {
tv_dict_add_allocated_str(dict, S_LEN("desc"), xstrdup(mp->m_desc));
}
tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs);
tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value);
Expand Down
10 changes: 8 additions & 2 deletions src/nvim/eval/funcs.c
Original file line number Diff line number Diff line change
Expand Up @@ -5980,6 +5980,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
{
char_u *keys_buf = NULL;
char_u *rhs;
LuaRef rhs_lua;
int mode;
int abbr = FALSE;
int get_dict = FALSE;
Expand Down Expand Up @@ -6016,7 +6017,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)

keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
CPO_TO_CPO_FLAGS);
rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local);
rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
xfree(keys_buf);

if (!get_dict) {
Expand All @@ -6027,10 +6028,15 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
} else {
rettv->vval.v_string = (char_u *)str2special_save((char *)rhs, false, false);
}
} else if (rhs_lua != LUA_NOREF) {
size_t msglen = 100;
char *msg = (char *)xmalloc(msglen);
snprintf(msg, msglen, "<Lua function %d>", mp->m_luaref);
rettv->vval.v_string = (char_u *)msg;
}
} else {
tv_dict_alloc_ret(rettv);
if (rhs != NULL) {
if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) {
// Return a dictionary.
mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true);
}
Expand Down
6 changes: 4 additions & 2 deletions src/nvim/ex_getln.c
Original file line number Diff line number Diff line change
Expand Up @@ -1024,11 +1024,13 @@ static int command_line_execute(VimState *state, int key)
CommandLineState *s = (CommandLineState *)state;
s->c = key;

if (s->c == K_EVENT || s->c == K_COMMAND) {
if (s->c == K_EVENT || s->c == K_COMMAND || s->c == K_LUA) {
if (s->c == K_EVENT) {
state_handle_k_event();
} else {
} else if (s->c == K_COMMAND) {
do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT);
} else {
map_execute_lua();
}

if (!cmdline_was_last_drawn) {
Expand Down

0 comments on commit b218d02

Please sign in to comment.