aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/private/helpers.c
diff options
context:
space:
mode:
authorYilin Yang <yiliny@umich.edu>2019-05-12 11:44:48 +0200
committerJustin M. Keyes <justinkz@gmail.com>2019-05-12 11:44:48 +0200
commitfbf2c414ad3409e8359ff744765e7486043bb4f7 (patch)
tree980bbbe5ce39cd6d82ee28fbef9b9ce1fb5949ab /src/nvim/api/private/helpers.c
parent24f9dd73d5f78bad48ac6dd4e2615ccdb95d9daa (diff)
downloadrneovim-fbf2c414ad3409e8359ff744765e7486043bb4f7.tar.gz
rneovim-fbf2c414ad3409e8359ff744765e7486043bb4f7.tar.bz2
rneovim-fbf2c414ad3409e8359ff744765e7486043bb4f7.zip
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
Diffstat (limited to 'src/nvim/api/private/helpers.c')
-rw-r--r--src/nvim/api/private/helpers.c228
1 files changed, 228 insertions, 0 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index c2b382804d..aa9b3f5f18 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -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.
///