mirror of
https://github.com/neovim/neovim.git
synced 2025-10-09 03:16:31 +00:00
API: nvim_set_keymap, nvim_del_keymap #9924
closes #9136 - Treat empty {rhs} like <Nop> - getchar.c: Pull "repl. MapArg termcodes" into func The "preprocessing code" surrounding the replace_termcodes calls needs to invoke replace_termcodes, and also check if RHS is equal to "<Nop>". To reduce code duplication, factor this out into a helper function. Also add an rhs_is_noop flag to MapArguments; buf_do_map_explicit expects an empty {rhs} string for "<Nop>", but also needs to distinguish that from something like ":map lhs<cr>" where no {rhs} was provided. - getchar.c: Use allocated buffer for rhs in MapArgs Since the MAXMAPLEN limit does not apply to the RHS of a mapping (or else an RHS that calls a really long autoload function from a plugin would be incorrectly rejected as being too long), use an allocated buffer for RHS rather than a static buffer of length MAXMAPLEN + 1. - Mappings LHS and RHS can contain literal space characters, newlines, etc. - getchar.c: replace_termcodes in str_to_mapargs It makes sense to do this; str_to_mapargs is, intuitively, supposed to take a "raw" command string and parse it into a totally "do_map-ready" struct. - api/vim.c: Update lhs, rhs len after replace_termcodes Fixes a bug in which replace_termcodes changes the length of lhs or rhs, but the later search through the mappings/abbreviations hashtables still uses the old length value. This would cause the search to fail erroneously and throw 'E31: No such mapping' errors or 'E24: No such abbreviation' errors. - getchar: Create new map_arguments struct So that a string of map arguments can be parsed into a more useful, more portable data structure. - getchar.c: Add buf_do_map function Exactly the same as the old do_map, but replace the hardcoded references to the global `buf_T* curbuf` with a function parameter so that we can invoke it from nvim_buf_set_keymap. - Remove gettext calls in do_map error handling
This commit is contained in:

committed by
Justin M. Keyes

parent
24f9dd73d5
commit
fbf2c414ad
@@ -15,6 +15,7 @@
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/charset.h"
|
||||
#include "nvim/cursor.h"
|
||||
#include "nvim/getchar.h"
|
||||
#include "nvim/memline.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/misc1.h"
|
||||
@@ -576,6 +577,27 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
|
||||
return keymap_array(mode, buf);
|
||||
}
|
||||
|
||||
/// Like |nvim_set_keymap|, but for a specific buffer.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for the current buffer.
|
||||
void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs,
|
||||
Dictionary opts, Error *err)
|
||||
FUNC_API_SINCE(6)
|
||||
{
|
||||
modify_keymap(buffer, false, mode, lhs, rhs, opts, err);
|
||||
}
|
||||
|
||||
/// Like |nvim_del_keymap|, but for a specific buffer.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for the current buffer.
|
||||
void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err)
|
||||
FUNC_API_SINCE(6)
|
||||
{
|
||||
String rhs = { .data = "", .size = 0 };
|
||||
Dictionary opts = ARRAY_DICT_INIT;
|
||||
modify_keymap(buffer, true, mode, lhs, rhs, opts, err);
|
||||
}
|
||||
|
||||
/// Gets a map of buffer-local |user-commands|.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for current buffer
|
||||
|
@@ -744,6 +744,234 @@ String ga_take_string(garray_T *ga)
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
|
||||
/// functions like @ref nvim_buf_set_keymap.
|
||||
///
|
||||
/// Arguments are handled like @ref nvim_set_keymap unless noted.
|
||||
/// @param buffer Buffer handle for a specific buffer, or 0 for the current
|
||||
/// buffer, or -1 to signify global behavior ("all buffers")
|
||||
/// @param is_unmap When true, removes the mapping that matches {lhs}.
|
||||
void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs,
|
||||
String rhs, Dictionary opts, Error *err)
|
||||
{
|
||||
char *err_msg = NULL; // the error message to report, if any
|
||||
char *err_arg = NULL; // argument for the error message format string
|
||||
ErrorType err_type = kErrorTypeNone;
|
||||
|
||||
char_u *lhs_buf = NULL;
|
||||
char_u *rhs_buf = NULL;
|
||||
|
||||
bool global = (buffer == -1);
|
||||
if (global) {
|
||||
buffer = 0;
|
||||
}
|
||||
buf_T *target_buf = find_buffer_by_handle(buffer, err);
|
||||
|
||||
MapArguments parsed_args;
|
||||
memset(&parsed_args, 0, sizeof(parsed_args));
|
||||
if (parse_keymap_opts(opts, &parsed_args, err)) {
|
||||
goto FAIL_AND_FREE;
|
||||
}
|
||||
parsed_args.buffer = !global;
|
||||
|
||||
set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size,
|
||||
(char_u *)rhs.data, rhs.size,
|
||||
CPO_TO_CPO_FLAGS, &parsed_args);
|
||||
|
||||
if (parsed_args.lhs_len > MAXMAPLEN) {
|
||||
err_msg = "LHS exceeds maximum map length: %s";
|
||||
err_arg = lhs.data;
|
||||
err_type = kErrorTypeValidation;
|
||||
goto FAIL_WITH_MESSAGE;
|
||||
}
|
||||
|
||||
if (mode.size > 1) {
|
||||
err_msg = "Shortname is too long: %s";
|
||||
err_arg = mode.data;
|
||||
err_type = kErrorTypeValidation;
|
||||
goto FAIL_WITH_MESSAGE;
|
||||
}
|
||||
int mode_val; // integer value of the mapping mode, to be passed to do_map()
|
||||
char_u *p = (char_u *)((mode.size) ? mode.data : "m");
|
||||
if (STRNCMP(p, "!", 2) == 0) {
|
||||
mode_val = get_map_mode(&p, true); // mapmode-ic
|
||||
} else {
|
||||
mode_val = get_map_mode(&p, false);
|
||||
if (mode_val == VISUAL + SELECTMODE + NORMAL + OP_PENDING) {
|
||||
// get_map_mode will treat "unrecognized" mode shortnames like "map"
|
||||
// if it does, and the given shortname wasn't "m" or " ", then error
|
||||
if (STRNCMP(p, "m", 2) && STRNCMP(p, " ", 2)) {
|
||||
err_msg = "Invalid mode shortname: %s";
|
||||
err_arg = (char *)p;
|
||||
err_type = kErrorTypeValidation;
|
||||
goto FAIL_WITH_MESSAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parsed_args.lhs_len == 0) {
|
||||
err_msg = "Invalid (empty) LHS";
|
||||
err_arg = "";
|
||||
err_type = kErrorTypeValidation;
|
||||
goto FAIL_WITH_MESSAGE;
|
||||
}
|
||||
|
||||
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 (rhs.size == 0) { // assume that the user wants RHS to be a <Nop>
|
||||
parsed_args.rhs_is_noop = true;
|
||||
} else {
|
||||
// the given RHS was nonempty and not a <Nop>, but was parsed as if it
|
||||
// were empty?
|
||||
assert(false && "Failed to parse nonempty RHS!");
|
||||
err_msg = "Parsing of nonempty RHS failed: %s";
|
||||
err_arg = rhs.data;
|
||||
err_type = kErrorTypeException;
|
||||
goto FAIL_WITH_MESSAGE;
|
||||
}
|
||||
} else if (is_unmap && parsed_args.rhs_len) {
|
||||
err_msg = "Gave nonempty RHS in unmap command: %s";
|
||||
err_arg = (char *)parsed_args.rhs;
|
||||
err_type = kErrorTypeValidation;
|
||||
goto FAIL_WITH_MESSAGE;
|
||||
}
|
||||
|
||||
// buf_do_map_explicit reads noremap/unmap as its own argument
|
||||
int maptype_val = 0;
|
||||
if (is_unmap) {
|
||||
maptype_val = 1;
|
||||
} else if (is_noremap) {
|
||||
maptype_val = 2;
|
||||
}
|
||||
|
||||
switch (buf_do_map_explicit(maptype_val, &parsed_args, mode_val,
|
||||
0, target_buf)) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
api_set_error(err, kErrorTypeException, (char *)e_invarg, 0);
|
||||
goto FAIL_AND_FREE;
|
||||
case 2:
|
||||
api_set_error(err, kErrorTypeException, (char *)e_nomap, 0);
|
||||
goto FAIL_AND_FREE;
|
||||
case 5:
|
||||
api_set_error(err, kErrorTypeException,
|
||||
"E227: mapping already exists for %s", parsed_args.lhs);
|
||||
goto FAIL_AND_FREE;
|
||||
default:
|
||||
assert(false && "Unrecognized return code!");
|
||||
goto FAIL_AND_FREE;
|
||||
} // switch
|
||||
|
||||
xfree(lhs_buf);
|
||||
xfree(rhs_buf);
|
||||
xfree(parsed_args.rhs);
|
||||
xfree(parsed_args.orig_rhs);
|
||||
|
||||
return;
|
||||
|
||||
FAIL_WITH_MESSAGE:
|
||||
api_set_error(err, err_type, err_msg, err_arg);
|
||||
|
||||
FAIL_AND_FREE:
|
||||
xfree(lhs_buf);
|
||||
xfree(rhs_buf);
|
||||
xfree(parsed_args.rhs);
|
||||
xfree(parsed_args.orig_rhs);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Read in the given opts, setting corresponding flags in `out`.
|
||||
///
|
||||
/// @param opts A dictionary passed to @ref nvim_set_keymap or
|
||||
/// @ref nvim_buf_set_keymap.
|
||||
/// @param[out] out MapArguments object in which to set parsed
|
||||
/// |:map-arguments| flags.
|
||||
/// @param[out] err Error details, if any.
|
||||
///
|
||||
/// @returns Zero on success, nonzero on failure.
|
||||
Integer parse_keymap_opts(Dictionary opts, MapArguments *out, Error *err)
|
||||
{
|
||||
char *err_msg = NULL; // the error message to report, if any
|
||||
char *err_arg = NULL; // argument for the error message format string
|
||||
ErrorType err_type = kErrorTypeNone;
|
||||
|
||||
out->buffer = false;
|
||||
out->nowait = false;
|
||||
out->silent = false;
|
||||
out->script = false;
|
||||
out->expr = false;
|
||||
out->unique = false;
|
||||
|
||||
for (size_t i = 0; i < opts.size; i++) {
|
||||
KeyValuePair *key_and_val = &opts.items[i];
|
||||
char *optname = key_and_val->key.data;
|
||||
|
||||
if (key_and_val->value.type != kObjectTypeBoolean) {
|
||||
err_msg = "Gave non-boolean value for an opt: %s";
|
||||
err_arg = optname;
|
||||
err_type = kErrorTypeValidation;
|
||||
goto FAIL_WITH_MESSAGE;
|
||||
}
|
||||
|
||||
bool was_valid_opt = false;
|
||||
switch (optname[0]) {
|
||||
// note: strncmp up to and including the null terminator, so that
|
||||
// "nowaitFoobar" won't match against "nowait"
|
||||
|
||||
// don't recognize 'buffer' as a key; user shouldn't provide <buffer>
|
||||
// when calling nvim_set_keymap or nvim_buf_set_keymap, since it can be
|
||||
// inferred from which function they called
|
||||
case 'n':
|
||||
if (STRNCMP(optname, "noremap", 8) == 0) {
|
||||
was_valid_opt = true;
|
||||
out->noremap = key_and_val->value.data.boolean;
|
||||
} else if (STRNCMP(optname, "nowait", 7) == 0) {
|
||||
was_valid_opt = true;
|
||||
out->nowait = key_and_val->value.data.boolean;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (STRNCMP(optname, "silent", 7) == 0) {
|
||||
was_valid_opt = true;
|
||||
out->silent = key_and_val->value.data.boolean;
|
||||
} else if (STRNCMP(optname, "script", 7) == 0) {
|
||||
was_valid_opt = true;
|
||||
out->script = key_and_val->value.data.boolean;
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
if (STRNCMP(optname, "expr", 5) == 0) {
|
||||
was_valid_opt = true;
|
||||
out->expr = key_and_val->value.data.boolean;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
if (STRNCMP(optname, "unique", 7) == 0) {
|
||||
was_valid_opt = true;
|
||||
out->unique = key_and_val->value.data.boolean;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
} // switch
|
||||
if (!was_valid_opt) {
|
||||
err_msg = "Invalid key: %s";
|
||||
err_arg = optname;
|
||||
err_type = kErrorTypeValidation;
|
||||
goto FAIL_WITH_MESSAGE;
|
||||
}
|
||||
} // for
|
||||
|
||||
return 0;
|
||||
|
||||
FAIL_WITH_MESSAGE:
|
||||
api_set_error(err, err_type, err_msg, err_arg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// Collects `n` buffer lines into array `l`, optionally replacing newlines
|
||||
/// with NUL.
|
||||
///
|
||||
|
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/getchar.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/ex_eval.h"
|
||||
#include "nvim/lib/kvec.h"
|
||||
|
@@ -1262,6 +1262,53 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
|
||||
return keymap_array(mode, NULL);
|
||||
}
|
||||
|
||||
/// Sets a global |mapping| for the given mode.
|
||||
///
|
||||
/// To set a buffer-local mapping, use |nvim_buf_set_keymap|.
|
||||
///
|
||||
/// Unlike ordinary Ex mode |:map| commands, special characters like literal
|
||||
/// spaces and newlines are treated as an actual part of the {lhs} or {rhs}.
|
||||
/// An empty {rhs} is treated like a |<Nop>|. |keycodes| are still replaced as
|
||||
/// usual.
|
||||
///
|
||||
/// `call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})`
|
||||
///
|
||||
/// Is equivalent to,
|
||||
///
|
||||
/// `nmap <nowait> <Space><NL> <Nop>`
|
||||
///
|
||||
/// @param mode Mode short-name (the first character of an map command,
|
||||
/// e.g. "n", "i", "v", "x", etc.) OR the string "!" (for
|
||||
/// |:map!|). |:map| can be represented with a single space " ",
|
||||
/// an empty string, or "m".
|
||||
/// @param lhs Left-hand-side |{lhs}| of the mapping.
|
||||
/// @param rhs Right-hand-side |{rhs}| of the mapping.
|
||||
/// @param opts |dict| of optional parameters. Accepts all |:map-arguments|
|
||||
/// as keys excluding |<buffer>| but also including |noremap|.
|
||||
/// Values should all be Booleans. Unrecognized keys will result
|
||||
/// in an error.
|
||||
/// @param[out] err Error details, if any.
|
||||
void nvim_set_keymap(String mode, String lhs, String rhs,
|
||||
Dictionary opts, Error *err)
|
||||
FUNC_API_SINCE(6)
|
||||
{
|
||||
modify_keymap(-1, false, mode, lhs, rhs, opts, err);
|
||||
}
|
||||
|
||||
/// Unmap a global |mapping| for the given mode.
|
||||
///
|
||||
/// To unmap a buffer-local mapping, use |nvim_buf_del_keymap|.
|
||||
///
|
||||
/// Arguments are handled like |nvim_set_keymap|. Like with ordinary |:unmap|
|
||||
/// commands (and `nvim_set_keymap`), the given {lhs} is interpreted literally:
|
||||
/// for instance, trailing whitespace is treated as part of the {lhs}.
|
||||
/// |keycodes| are still replaced as usual.
|
||||
void nvim_del_keymap(String mode, String lhs, Error *err)
|
||||
FUNC_API_SINCE(6)
|
||||
{
|
||||
nvim_buf_del_keymap(-1, mode, lhs, err);
|
||||
}
|
||||
|
||||
/// Gets a map of global (non-buffer-local) Ex commands.
|
||||
///
|
||||
/// Currently only |user-commands| are supported, not builtin Ex commands.
|
||||
|
Reference in New Issue
Block a user