mirror of
https://github.com/neovim/neovim.git
synced 2025-09-16 08:18:17 +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
1
.gitignore
vendored
1
.gitignore
vendored
@@ -54,3 +54,4 @@ local.mk
|
|||||||
/runtime/doc/*.html
|
/runtime/doc/*.html
|
||||||
/runtime/doc/tags.ref
|
/runtime/doc/tags.ref
|
||||||
/runtime/doc/errors.log
|
/runtime/doc/errors.log
|
||||||
|
compile_commands.json
|
||||||
|
@@ -807,6 +807,47 @@ nvim_get_keymap({mode}) *nvim_get_keymap()*
|
|||||||
Array of maparg()-like dictionaries describing mappings.
|
Array of maparg()-like dictionaries describing mappings.
|
||||||
The "buffer" key is always zero.
|
The "buffer" key is always zero.
|
||||||
|
|
||||||
|
nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()*
|
||||||
|
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>`
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{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".
|
||||||
|
{lhs} Left-hand-side |{lhs}| of the mapping.
|
||||||
|
{rhs} Right-hand-side |{rhs}| of the mapping.
|
||||||
|
{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.
|
||||||
|
|
||||||
|
nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()*
|
||||||
|
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.
|
||||||
|
|
||||||
nvim_get_commands({opts}) *nvim_get_commands()*
|
nvim_get_commands({opts}) *nvim_get_commands()*
|
||||||
Gets a map of global (non-buffer-local) Ex commands.
|
Gets a map of global (non-buffer-local) Ex commands.
|
||||||
|
|
||||||
@@ -1296,6 +1337,19 @@ nvim_buf_get_keymap({buffer}, {mode}) *nvim_buf_get_keymap()*
|
|||||||
Array of maparg()-like dictionaries describing mappings.
|
Array of maparg()-like dictionaries describing mappings.
|
||||||
The "buffer" key holds the associated buffer handle.
|
The "buffer" key holds the associated buffer handle.
|
||||||
|
|
||||||
|
*nvim_buf_set_keymap()*
|
||||||
|
nvim_buf_set_keymap({buffer}, {mode}, {lhs}, {rhs}, {opts})
|
||||||
|
Like |nvim_set_keymap|, but for a specific buffer.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{buffer} Buffer handle, or 0 for the current buffer.
|
||||||
|
|
||||||
|
nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()*
|
||||||
|
Like |nvim_del_keymap|, but for a specific buffer.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{buffer} Buffer handle, or 0 for the current buffer.
|
||||||
|
|
||||||
nvim_buf_get_commands({buffer}, {opts}) *nvim_buf_get_commands()*
|
nvim_buf_get_commands({buffer}, {opts}) *nvim_buf_get_commands()*
|
||||||
Gets a map of buffer-local |user-commands|.
|
Gets a map of buffer-local |user-commands|.
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
#include "nvim/cursor.h"
|
#include "nvim/cursor.h"
|
||||||
|
#include "nvim/getchar.h"
|
||||||
#include "nvim/memline.h"
|
#include "nvim/memline.h"
|
||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
#include "nvim/misc1.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);
|
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|.
|
/// Gets a map of buffer-local |user-commands|.
|
||||||
///
|
///
|
||||||
/// @param buffer Buffer handle, or 0 for current buffer
|
/// @param buffer Buffer handle, or 0 for current buffer
|
||||||
|
@@ -744,6 +744,234 @@ String ga_take_string(garray_T *ga)
|
|||||||
return str;
|
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
|
/// Collects `n` buffer lines into array `l`, optionally replacing newlines
|
||||||
/// with NUL.
|
/// with NUL.
|
||||||
///
|
///
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "nvim/api/private/defs.h"
|
#include "nvim/api/private/defs.h"
|
||||||
#include "nvim/vim.h"
|
#include "nvim/vim.h"
|
||||||
|
#include "nvim/getchar.h"
|
||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
#include "nvim/ex_eval.h"
|
#include "nvim/ex_eval.h"
|
||||||
#include "nvim/lib/kvec.h"
|
#include "nvim/lib/kvec.h"
|
||||||
|
@@ -1262,6 +1262,53 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
|
|||||||
return keymap_array(mode, NULL);
|
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.
|
/// Gets a map of global (non-buffer-local) Ex commands.
|
||||||
///
|
///
|
||||||
/// Currently only |user-commands| are supported, not builtin Ex commands.
|
/// Currently only |user-commands| are supported, not builtin Ex commands.
|
||||||
|
@@ -2502,286 +2502,331 @@ int fix_input_buffer(char_u *buf, int len)
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Replace termcodes in the given LHS and RHS and store the results into the
|
||||||
* map[!] : show all key mappings
|
/// `lhs` and `rhs` of the given @ref MapArguments struct.
|
||||||
* map[!] {lhs} : show key mapping for {lhs}
|
///
|
||||||
* map[!] {lhs} {rhs} : set key mapping for {lhs} to {rhs}
|
/// `rhs` and `orig_rhs` will both point to new allocated buffers. `orig_rhs`
|
||||||
* noremap[!] {lhs} {rhs} : same, but no remapping for {rhs}
|
/// will hold a copy of the given `orig_rhs`.
|
||||||
* unmap[!] {lhs} : remove key mapping for {lhs}
|
///
|
||||||
* abbr : show all abbreviations
|
/// The `*_len` variables will be set appropriately. If the length of
|
||||||
* abbr {lhs} : show abbreviations for {lhs}
|
/// the final `lhs` exceeds `MAXMAPLEN`, `lhs_len` will be set equal to the
|
||||||
* abbr {lhs} {rhs} : set abbreviation for {lhs} to {rhs}
|
/// original larger length and `lhs` will be truncated.
|
||||||
* noreabbr {lhs} {rhs} : same, but no remapping for {rhs}
|
///
|
||||||
* unabbr {lhs} : remove abbreviation for {lhs}
|
/// If RHS is equal to "<Nop>", `rhs` will be the empty string, `rhs_len`
|
||||||
*
|
/// will be zero, and `rhs_is_noop` will be set to true.
|
||||||
* maptype: 0 for :map, 1 for :unmap, 2 for noremap.
|
///
|
||||||
*
|
/// Any memory allocated by @ref replace_termcodes is freed before this function
|
||||||
* arg is pointer to any arguments. Note: arg cannot be a read-only string,
|
/// returns.
|
||||||
* it will be modified.
|
///
|
||||||
*
|
/// @param[in] orig_lhs Original mapping LHS, with characters to replace.
|
||||||
* for :map mode is NORMAL + VISUAL + SELECTMODE + OP_PENDING
|
/// @param[in] orig_lhs_len `strlen` of orig_lhs.
|
||||||
* for :map! mode is INSERT + CMDLINE
|
/// @param[in] orig_rhs Original mapping RHS, with characters to replace.
|
||||||
* for :cmap mode is CMDLINE
|
/// @param[in] orig_rhs_len `strlen` of orig_rhs.
|
||||||
* for :imap mode is INSERT
|
/// @param[in] cpo_flags See param docs for @ref replace_termcodes.
|
||||||
* for :lmap mode is LANGMAP
|
/// @param[out] mapargs MapArguments struct holding the replaced strings.
|
||||||
* for :nmap mode is NORMAL
|
void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len,
|
||||||
* for :vmap mode is VISUAL + SELECTMODE
|
const char_u *orig_rhs, const size_t orig_rhs_len,
|
||||||
* for :xmap mode is VISUAL
|
int cpo_flags, MapArguments *mapargs)
|
||||||
* for :smap mode is SELECTMODE
|
|
||||||
* for :omap mode is OP_PENDING
|
|
||||||
* for :tmap mode is TERM_FOCUS
|
|
||||||
*
|
|
||||||
* for :abbr mode is INSERT + CMDLINE
|
|
||||||
* for :iabbr mode is INSERT
|
|
||||||
* for :cabbr mode is CMDLINE
|
|
||||||
*
|
|
||||||
* Return 0 for success
|
|
||||||
* 1 for invalid arguments
|
|
||||||
* 2 for no match
|
|
||||||
* 4 for out of mem (deprecated, WON'T HAPPEN)
|
|
||||||
* 5 for entry not unique
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
do_map (
|
|
||||||
int maptype,
|
|
||||||
char_u *arg,
|
|
||||||
int mode,
|
|
||||||
int abbrev /* not a mapping but an abbreviation */
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
char_u *keys;
|
char_u *lhs_buf = NULL;
|
||||||
mapblock_T *mp, **mpp;
|
char_u *rhs_buf = NULL;
|
||||||
char_u *rhs;
|
|
||||||
char_u *p;
|
|
||||||
int n;
|
|
||||||
int len = 0; /* init for GCC */
|
|
||||||
int hasarg;
|
|
||||||
int haskey;
|
|
||||||
int did_it = FALSE;
|
|
||||||
int did_local = FALSE;
|
|
||||||
int round;
|
|
||||||
char_u *keys_buf = NULL;
|
|
||||||
char_u *arg_buf = NULL;
|
|
||||||
int retval = 0;
|
|
||||||
int do_backslash;
|
|
||||||
int hash;
|
|
||||||
int new_hash;
|
|
||||||
mapblock_T **abbr_table;
|
|
||||||
mapblock_T **map_table;
|
|
||||||
bool unique = false;
|
|
||||||
bool nowait = false;
|
|
||||||
bool silent = false;
|
|
||||||
bool expr = false;
|
|
||||||
int noremap;
|
|
||||||
char_u *orig_rhs;
|
|
||||||
|
|
||||||
keys = arg;
|
// If mapping has been given as ^V<C_UP> say, then replace the term codes
|
||||||
map_table = maphash;
|
// with the appropriate two bytes. If it is a shifted special key, unshift
|
||||||
abbr_table = &first_abbr;
|
// it too, giving another two bytes.
|
||||||
|
//
|
||||||
|
// replace_termcodes() may move the result to allocated memory, which
|
||||||
|
// needs to be freed later (*lhs_buf and *rhs_buf).
|
||||||
|
// replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
|
||||||
|
char_u *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &lhs_buf,
|
||||||
|
true, true, true, cpo_flags);
|
||||||
|
mapargs->lhs_len = STRLEN(replaced);
|
||||||
|
xstrlcpy((char *)mapargs->lhs, (char *)replaced, sizeof(mapargs->lhs));
|
||||||
|
|
||||||
/* For ":noremap" don't remap, otherwise do remap. */
|
mapargs->orig_rhs_len = orig_rhs_len;
|
||||||
if (maptype == 2)
|
mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u));
|
||||||
noremap = REMAP_NONE;
|
xstrlcpy((char *)mapargs->orig_rhs, (char *)orig_rhs,
|
||||||
else
|
mapargs->orig_rhs_len + 1);
|
||||||
noremap = REMAP_YES;
|
|
||||||
|
|
||||||
/* Accept <buffer>, <nowait>, <silent>, <expr> <script> and <unique> in
|
if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing
|
||||||
* any order. */
|
mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char
|
||||||
for (;; ) {
|
mapargs->rhs_len = 0;
|
||||||
/*
|
mapargs->rhs_is_noop = true;
|
||||||
* Check for "<buffer>": mapping local to buffer.
|
} else {
|
||||||
*/
|
replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf,
|
||||||
if (STRNCMP(keys, "<buffer>", 8) == 0) {
|
false, true, true, cpo_flags);
|
||||||
keys = skipwhite(keys + 8);
|
mapargs->rhs_len = STRLEN(replaced);
|
||||||
map_table = curbuf->b_maphash;
|
mapargs->rhs_is_noop = false;
|
||||||
abbr_table = &curbuf->b_first_abbr;
|
mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u));
|
||||||
|
xstrlcpy((char *)mapargs->rhs, (char *)replaced, mapargs->rhs_len + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree(lhs_buf);
|
||||||
|
xfree(rhs_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a string of |:map-arguments| into a @ref MapArguments struct.
|
||||||
|
///
|
||||||
|
/// Termcodes, backslashes, CTRL-V's, etc. inside the extracted {lhs} and
|
||||||
|
/// {rhs} are replaced by @ref set_maparg_lhs_rhs.
|
||||||
|
///
|
||||||
|
/// rhs and orig_rhs in the returned mapargs will be set to null or a pointer
|
||||||
|
/// to allocated memory and should be freed even on error.
|
||||||
|
///
|
||||||
|
/// @param[in] strargs String of map args, e.g. "<buffer> <expr><silent>".
|
||||||
|
/// May contain leading or trailing whitespace.
|
||||||
|
/// @param[in] is_unmap True, if strargs should be parsed like an |:unmap|
|
||||||
|
/// command. |:unmap| commands interpret *all* text to the
|
||||||
|
/// right of the last map argument as the {lhs} of the
|
||||||
|
/// mapping, i.e. a literal ' ' character is treated like
|
||||||
|
/// a "<space>", rather than separating the {lhs} from the
|
||||||
|
/// {rhs}.
|
||||||
|
/// @param[out] mapargs MapArguments struct holding all extracted argument
|
||||||
|
/// values.
|
||||||
|
/// @return 0 on success, 1 if invalid arguments are detected.
|
||||||
|
int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs)
|
||||||
|
{
|
||||||
|
const char_u *to_parse = strargs;
|
||||||
|
to_parse = skipwhite(to_parse);
|
||||||
|
MapArguments parsed_args; // copy these into mapargs "all at once" when done
|
||||||
|
memset(&parsed_args, 0, sizeof(parsed_args));
|
||||||
|
|
||||||
|
// Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in
|
||||||
|
// any order.
|
||||||
|
while (true) {
|
||||||
|
if (STRNCMP(to_parse, "<buffer>", 8) == 0) {
|
||||||
|
to_parse = skipwhite(to_parse + 8);
|
||||||
|
parsed_args.buffer = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (STRNCMP(to_parse, "<nowait>", 8) == 0) {
|
||||||
* Check for "<nowait>": don't wait for more characters.
|
to_parse = skipwhite(to_parse + 8);
|
||||||
*/
|
parsed_args.nowait = true;
|
||||||
if (STRNCMP(keys, "<nowait>", 8) == 0) {
|
|
||||||
keys = skipwhite(keys + 8);
|
|
||||||
nowait = true;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (STRNCMP(to_parse, "<silent>", 8) == 0) {
|
||||||
* Check for "<silent>": don't echo commands.
|
to_parse = skipwhite(to_parse + 8);
|
||||||
*/
|
parsed_args.silent = true;
|
||||||
if (STRNCMP(keys, "<silent>", 8) == 0) {
|
|
||||||
keys = skipwhite(keys + 8);
|
|
||||||
silent = true;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore obsolete "<special>" modifier.
|
// Ignore obsolete "<special>" modifier.
|
||||||
if (STRNCMP(keys, "<special>", 9) == 0) {
|
if (STRNCMP(to_parse, "<special>", 9) == 0) {
|
||||||
keys = skipwhite(keys + 9);
|
to_parse = skipwhite(to_parse + 9);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (STRNCMP(to_parse, "<script>", 8) == 0) {
|
||||||
* Check for "<script>": remap script-local mappings only
|
to_parse = skipwhite(to_parse + 8);
|
||||||
*/
|
parsed_args.script = true;
|
||||||
if (STRNCMP(keys, "<script>", 8) == 0) {
|
|
||||||
keys = skipwhite(keys + 8);
|
|
||||||
noremap = REMAP_SCRIPT;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (STRNCMP(to_parse, "<expr>", 6) == 0) {
|
||||||
* Check for "<expr>": {rhs} is an expression.
|
to_parse = skipwhite(to_parse + 6);
|
||||||
*/
|
parsed_args.expr = true;
|
||||||
if (STRNCMP(keys, "<expr>", 6) == 0) {
|
|
||||||
keys = skipwhite(keys + 6);
|
|
||||||
expr = true;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
* Check for "<unique>": don't overwrite an existing mapping.
|
if (STRNCMP(to_parse, "<unique>", 8) == 0) {
|
||||||
*/
|
to_parse = skipwhite(to_parse + 8);
|
||||||
if (STRNCMP(keys, "<unique>", 8) == 0) {
|
parsed_args.unique = true;
|
||||||
keys = skipwhite(keys + 8);
|
|
||||||
unique = true;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the next whitespace character, call that the end of {lhs}.
|
||||||
|
//
|
||||||
|
// If a character (e.g. whitespace) is immediately preceded by a CTRL-V,
|
||||||
|
// "scan past" that character, i.e. don't "terminate" LHS with that character
|
||||||
|
// if it's whitespace.
|
||||||
|
//
|
||||||
|
// Treat backslash like CTRL-V when 'cpoptions' does not contain 'B'.
|
||||||
|
//
|
||||||
|
// With :unmap, literal white space is included in the {lhs}; there is no
|
||||||
|
// separate {rhs}.
|
||||||
|
const char_u *lhs_end = to_parse;
|
||||||
|
bool do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
|
||||||
|
while (*lhs_end && (is_unmap || !ascii_iswhite(*lhs_end))) {
|
||||||
|
if ((lhs_end[0] == Ctrl_V || (do_backslash && lhs_end[0] == '\\'))
|
||||||
|
&& lhs_end[1] != NUL) {
|
||||||
|
lhs_end++; // skip CTRL-V or backslash
|
||||||
|
}
|
||||||
|
lhs_end++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// {lhs_end} is a pointer to the "terminating whitespace" after {lhs}.
|
||||||
|
// Use that to initialize {rhs_start}.
|
||||||
|
const char_u *rhs_start = skipwhite(lhs_end);
|
||||||
|
|
||||||
|
// Given {lhs} might be larger than MAXMAPLEN before replace_termcodes
|
||||||
|
// (e.g. "<Space>" is longer than ' '), so first copy into a buffer.
|
||||||
|
size_t orig_lhs_len = (size_t)(lhs_end - to_parse);
|
||||||
|
char_u *lhs_to_replace = xcalloc(orig_lhs_len + 1, sizeof(char_u));
|
||||||
|
xstrlcpy((char *)lhs_to_replace, (char *)to_parse, orig_lhs_len + 1);
|
||||||
|
|
||||||
|
size_t orig_rhs_len = STRLEN(rhs_start);
|
||||||
|
set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len,
|
||||||
|
rhs_start, orig_rhs_len,
|
||||||
|
CPO_TO_CPO_FLAGS, &parsed_args);
|
||||||
|
|
||||||
|
xfree(lhs_to_replace);
|
||||||
|
|
||||||
|
*mapargs = parsed_args;
|
||||||
|
|
||||||
|
if (parsed_args.lhs_len > MAXMAPLEN) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Actually set/unset a mapping or abbreviation.
|
||||||
|
///
|
||||||
|
/// Parameters are like @ref buf_do_map unless otherwise noted.
|
||||||
|
/// @param args Fully parsed and "preprocessed" arguments for the
|
||||||
|
/// (un)map/abbrev command. Termcodes should have already been
|
||||||
|
/// replaced; whitespace, `<` and `>` signs, etc. in {lhs} and
|
||||||
|
/// {rhs} are assumed to be literal components of the mapping.
|
||||||
|
int buf_do_map_explicit(int maptype, MapArguments *args, int mode,
|
||||||
|
bool is_abbrev, buf_T *buf)
|
||||||
|
{
|
||||||
|
mapblock_T *mp, **mpp;
|
||||||
|
char_u *p;
|
||||||
|
int n;
|
||||||
|
int len = 0; // init for GCC
|
||||||
|
int did_it = false;
|
||||||
|
int did_local = false;
|
||||||
|
int round;
|
||||||
|
int retval = 0;
|
||||||
|
int hash;
|
||||||
|
int new_hash;
|
||||||
|
mapblock_T **abbr_table;
|
||||||
|
mapblock_T **map_table;
|
||||||
|
int noremap;
|
||||||
|
|
||||||
|
map_table = maphash;
|
||||||
|
abbr_table = &first_abbr;
|
||||||
|
|
||||||
|
// For ":noremap" don't remap, otherwise do remap.
|
||||||
|
if (maptype == 2) {
|
||||||
|
noremap = REMAP_NONE;
|
||||||
|
} else {
|
||||||
|
noremap = REMAP_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->buffer) {
|
||||||
|
// If <buffer> was given, we'll be searching through the buffer's
|
||||||
|
// mappings/abbreviations, not the globals.
|
||||||
|
map_table = buf->b_maphash;
|
||||||
|
abbr_table = &buf->b_first_abbr;
|
||||||
|
}
|
||||||
|
if (args->script) {
|
||||||
|
noremap = REMAP_SCRIPT;
|
||||||
|
}
|
||||||
|
|
||||||
validate_maphash();
|
validate_maphash();
|
||||||
|
|
||||||
/*
|
bool has_lhs = (args->lhs[0] != NUL);
|
||||||
* Find end of keys and skip CTRL-Vs (and backslashes) in it.
|
bool has_rhs = (args->rhs[0] != NUL) || args->rhs_is_noop;
|
||||||
* Accept backslash like CTRL-V when 'cpoptions' does not contain 'B'.
|
|
||||||
* with :unmap white space is included in the keys, no argument possible.
|
|
||||||
*/
|
|
||||||
p = keys;
|
|
||||||
do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
|
|
||||||
while (*p && (maptype == 1 || !ascii_iswhite(*p))) {
|
|
||||||
if ((p[0] == Ctrl_V || (do_backslash && p[0] == '\\')) && p[1] != NUL) {
|
|
||||||
p++; // skip CTRL-V or backslash
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
if (*p != NUL) {
|
|
||||||
*p++ = NUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = skipwhite(p);
|
// check for :unmap without argument
|
||||||
rhs = p;
|
if (maptype == 1 && !has_lhs) {
|
||||||
hasarg = (*rhs != NUL);
|
|
||||||
haskey = (*keys != NUL);
|
|
||||||
|
|
||||||
/* check for :unmap without argument */
|
|
||||||
if (maptype == 1 && !haskey) {
|
|
||||||
retval = 1;
|
retval = 1;
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If mapping has been given as ^V<C_UP> say, then replace the term codes
|
char_u *lhs = (char_u *)&args->lhs;
|
||||||
// with the appropriate two bytes. If it is a shifted special key, unshift
|
char_u *rhs = (char_u *)args->rhs;
|
||||||
// it too, giving another two bytes.
|
char_u *orig_rhs = args->orig_rhs;
|
||||||
// replace_termcodes() may move the result to allocated memory, which
|
|
||||||
// needs to be freed later (*keys_buf and *arg_buf).
|
|
||||||
// replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
|
|
||||||
if (haskey) {
|
|
||||||
keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
|
|
||||||
CPO_TO_CPO_FLAGS);
|
|
||||||
}
|
|
||||||
orig_rhs = rhs;
|
|
||||||
if (hasarg) {
|
|
||||||
if (STRICMP(rhs, "<nop>") == 0) { // "<Nop>" means nothing
|
|
||||||
rhs = (char_u *)"";
|
|
||||||
} else {
|
|
||||||
rhs = replace_termcodes(rhs, STRLEN(rhs), &arg_buf, false, true, true,
|
|
||||||
CPO_TO_CPO_FLAGS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// check arguments and translate function keys
|
// check arguments and translate function keys
|
||||||
//
|
if (has_lhs) {
|
||||||
if (haskey) {
|
len = (int)args->lhs_len;
|
||||||
len = (int)STRLEN(keys);
|
if (len > MAXMAPLEN) {
|
||||||
if (len > MAXMAPLEN) { /* maximum length of MAXMAPLEN chars */
|
|
||||||
retval = 1;
|
retval = 1;
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abbrev && maptype != 1) {
|
if (is_abbrev && maptype != 1) {
|
||||||
/*
|
//
|
||||||
* If an abbreviation ends in a keyword character, the
|
// If an abbreviation ends in a keyword character, the
|
||||||
* rest must be all keyword-char or all non-keyword-char.
|
// rest must be all keyword-char or all non-keyword-char.
|
||||||
* Otherwise we won't be able to find the start of it in a
|
// Otherwise we won't be able to find the start of it in a
|
||||||
* vi-compatible way.
|
// vi-compatible way.
|
||||||
*/
|
//
|
||||||
if (has_mbyte) {
|
if (has_mbyte) {
|
||||||
int first, last;
|
int first, last;
|
||||||
int same = -1;
|
int same = -1;
|
||||||
|
|
||||||
first = vim_iswordp(keys);
|
first = vim_iswordp(lhs);
|
||||||
last = first;
|
last = first;
|
||||||
p = keys + (*mb_ptr2len)(keys);
|
p = lhs + (*mb_ptr2len)(lhs);
|
||||||
n = 1;
|
n = 1;
|
||||||
while (p < keys + len) {
|
while (p < lhs + len) {
|
||||||
++n; /* nr of (multi-byte) chars */
|
n++; // nr of (multi-byte) chars
|
||||||
last = vim_iswordp(p); /* type of last char */
|
last = vim_iswordp(p); // type of last char
|
||||||
if (same == -1 && last != first)
|
if (same == -1 && last != first) {
|
||||||
same = n - 1; /* count of same char type */
|
same = n - 1; // count of same char type
|
||||||
|
}
|
||||||
p += (*mb_ptr2len)(p);
|
p += (*mb_ptr2len)(p);
|
||||||
}
|
}
|
||||||
if (last && n > 2 && same >= 0 && same < n - 1) {
|
if (last && n > 2 && same >= 0 && same < n - 1) {
|
||||||
retval = 1;
|
retval = 1;
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
} else if (vim_iswordc(keys[len - 1])) /* ends in keyword char */
|
} else if (vim_iswordc(lhs[len - 1])) { // ends in keyword char
|
||||||
for (n = 0; n < len - 2; ++n)
|
for (n = 0; n < len - 2; n++) {
|
||||||
if (vim_iswordc(keys[n]) != vim_iswordc(keys[len - 2])) {
|
if (vim_iswordc(lhs[n]) != vim_iswordc(lhs[len - 2])) {
|
||||||
retval = 1;
|
retval = 1;
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
/* An abbreviation cannot contain white space. */
|
} // for
|
||||||
for (n = 0; n < len; ++n)
|
}
|
||||||
if (ascii_iswhite(keys[n])) {
|
// An abbreviation cannot contain white space.
|
||||||
|
for (n = 0; n < len; n++) {
|
||||||
|
if (ascii_iswhite(lhs[n])) {
|
||||||
retval = 1;
|
retval = 1;
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
|
} // for
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (haskey && hasarg && abbrev) /* if we will add an abbreviation */
|
if (has_lhs && has_rhs && is_abbrev) { // if we will add an abbreviation,
|
||||||
no_abbr = FALSE; /* reset flag that indicates there are
|
no_abbr = false; // reset flag that indicates there are no abbreviations
|
||||||
no abbreviations */
|
}
|
||||||
|
|
||||||
if (!haskey || (maptype != 1 && !hasarg))
|
if (!has_lhs || (maptype != 1 && !has_rhs)) {
|
||||||
msg_start();
|
msg_start();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
// Check if a new local mapping wasn't already defined globally.
|
||||||
* Check if a new local mapping wasn't already defined globally.
|
if (map_table == buf->b_maphash && has_lhs && has_rhs && maptype != 1) {
|
||||||
*/
|
// need to loop over all global hash lists
|
||||||
if (map_table == curbuf->b_maphash && haskey && hasarg && maptype != 1) {
|
for (hash = 0; hash < 256 && !got_int; hash++) {
|
||||||
/* need to loop over all global hash lists */
|
if (is_abbrev) {
|
||||||
for (hash = 0; hash < 256 && !got_int; ++hash) {
|
if (hash != 0) { // there is only one abbreviation list
|
||||||
if (abbrev) {
|
|
||||||
if (hash != 0) /* there is only one abbreviation list */
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
mp = first_abbr;
|
mp = first_abbr;
|
||||||
} else
|
} else {
|
||||||
mp = maphash[hash];
|
mp = maphash[hash];
|
||||||
|
}
|
||||||
for (; mp != NULL && !got_int; mp = mp->m_next) {
|
for (; mp != NULL && !got_int; mp = mp->m_next) {
|
||||||
/* check entries with the same mode */
|
// check entries with the same mode
|
||||||
if ((mp->m_mode & mode) != 0
|
if ((mp->m_mode & mode) != 0
|
||||||
&& mp->m_keylen == len
|
&& mp->m_keylen == len
|
||||||
&& unique
|
&& args->unique
|
||||||
&& STRNCMP(mp->m_keys, keys, (size_t)len) == 0) {
|
&& STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) {
|
||||||
if (abbrev)
|
if (is_abbrev) {
|
||||||
EMSG2(_("E224: global abbreviation already exists for %s"),
|
EMSG2(_("E224: global abbreviation already exists for %s"),
|
||||||
mp->m_keys);
|
mp->m_keys);
|
||||||
else
|
} else {
|
||||||
EMSG2(_("E225: global mapping already exists for %s"),
|
EMSG2(_("E225: global mapping already exists for %s"), mp->m_keys);
|
||||||
mp->m_keys);
|
}
|
||||||
retval = 5;
|
retval = 5;
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
@@ -2789,30 +2834,29 @@ do_map (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// When listing global mappings, also list buffer-local ones here.
|
||||||
* When listing global mappings, also list buffer-local ones here.
|
if (map_table != buf->b_maphash && !has_rhs && maptype != 1) {
|
||||||
*/
|
// need to loop over all global hash lists
|
||||||
if (map_table != curbuf->b_maphash && !hasarg && maptype != 1) {
|
for (hash = 0; hash < 256 && !got_int; hash++) {
|
||||||
/* need to loop over all global hash lists */
|
if (is_abbrev) {
|
||||||
for (hash = 0; hash < 256 && !got_int; ++hash) {
|
if (hash != 0) { // there is only one abbreviation list
|
||||||
if (abbrev) {
|
|
||||||
if (hash != 0) /* there is only one abbreviation list */
|
|
||||||
break;
|
break;
|
||||||
mp = curbuf->b_first_abbr;
|
}
|
||||||
} else
|
mp = buf->b_first_abbr;
|
||||||
mp = curbuf->b_maphash[hash];
|
} else {
|
||||||
|
mp = buf->b_maphash[hash];
|
||||||
|
}
|
||||||
for (; mp != NULL && !got_int; mp = mp->m_next) {
|
for (; mp != NULL && !got_int; mp = mp->m_next) {
|
||||||
/* check entries with the same mode */
|
// check entries with the same mode
|
||||||
if ((mp->m_mode & mode) != 0) {
|
if ((mp->m_mode & mode) != 0) {
|
||||||
if (!haskey) { /* show all entries */
|
if (!has_lhs) { // show all entries
|
||||||
showmap(mp, TRUE);
|
showmap(mp, true);
|
||||||
did_local = TRUE;
|
did_local = true;
|
||||||
} else {
|
} else {
|
||||||
n = mp->m_keylen;
|
n = mp->m_keylen;
|
||||||
if (STRNCMP(mp->m_keys, keys,
|
if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
|
||||||
(size_t)(n < len ? n : len)) == 0) {
|
showmap(mp, true);
|
||||||
showmap(mp, TRUE);
|
did_local = true;
|
||||||
did_local = TRUE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2820,103 +2864,98 @@ do_map (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Find an entry in the maphash[] list that matches.
|
||||||
* Find an entry in the maphash[] list that matches.
|
// For :unmap we may loop two times: once to try to unmap an entry with a
|
||||||
* For :unmap we may loop two times: once to try to unmap an entry with a
|
// matching 'from' part, a second time, if the first fails, to unmap an
|
||||||
* matching 'from' part, a second time, if the first fails, to unmap an
|
// entry with a matching 'to' part. This was done to allow ":ab foo bar"
|
||||||
* entry with a matching 'to' part. This was done to allow ":ab foo bar"
|
// to be unmapped by typing ":unab foo", where "foo" will be replaced by
|
||||||
* to be unmapped by typing ":unab foo", where "foo" will be replaced by
|
// "bar" because of the abbreviation.
|
||||||
* "bar" because of the abbreviation.
|
|
||||||
*/
|
|
||||||
for (round = 0; (round == 0 || maptype == 1) && round <= 1
|
for (round = 0; (round == 0 || maptype == 1) && round <= 1
|
||||||
&& !did_it && !got_int; ++round) {
|
&& !did_it && !got_int; round++) {
|
||||||
/* need to loop over all hash lists */
|
// need to loop over all hash lists
|
||||||
for (hash = 0; hash < 256 && !got_int; ++hash) {
|
for (hash = 0; hash < 256 && !got_int; hash++) {
|
||||||
if (abbrev) {
|
if (is_abbrev) {
|
||||||
if (hash > 0) /* there is only one abbreviation list */
|
if (hash > 0) { // there is only one abbreviation list
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
mpp = abbr_table;
|
mpp = abbr_table;
|
||||||
} else
|
} else {
|
||||||
mpp = &(map_table[hash]);
|
mpp = &(map_table[hash]);
|
||||||
|
}
|
||||||
for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
|
for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
|
||||||
|
if (!(mp->m_mode & mode)) { // skip entries with wrong mode
|
||||||
if (!(mp->m_mode & mode)) { /* skip entries with wrong mode */
|
|
||||||
mpp = &(mp->m_next);
|
mpp = &(mp->m_next);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!haskey) { /* show all entries */
|
if (!has_lhs) { // show all entries
|
||||||
showmap(mp, map_table != maphash);
|
showmap(mp, map_table != maphash);
|
||||||
did_it = TRUE;
|
did_it = true;
|
||||||
} else { /* do we have a match? */
|
} else { // do we have a match?
|
||||||
if (round) { /* second round: Try unmap "rhs" string */
|
if (round) { // second round: Try unmap "rhs" string
|
||||||
n = (int)STRLEN(mp->m_str);
|
n = (int)STRLEN(mp->m_str);
|
||||||
p = mp->m_str;
|
p = mp->m_str;
|
||||||
} else {
|
} else {
|
||||||
n = mp->m_keylen;
|
n = mp->m_keylen;
|
||||||
p = mp->m_keys;
|
p = mp->m_keys;
|
||||||
}
|
}
|
||||||
if (STRNCMP(p, keys, (size_t)(n < len ? n : len)) == 0) {
|
if (STRNCMP(p, lhs, (size_t)(n < len ? n : len)) == 0) {
|
||||||
if (maptype == 1) { /* delete entry */
|
if (maptype == 1) { // delete entry
|
||||||
/* Only accept a full match. For abbreviations we
|
// Only accept a full match. For abbreviations we
|
||||||
* ignore trailing space when matching with the
|
// ignore trailing space when matching with the
|
||||||
* "lhs", since an abbreviation can't have
|
// "lhs", since an abbreviation can't have
|
||||||
* trailing space. */
|
// trailing space.
|
||||||
if (n != len && (!abbrev || round || n > len
|
if (n != len && (!is_abbrev || round || n > len
|
||||||
|| *skipwhite(keys + n) != NUL)) {
|
|| *skipwhite(lhs + n) != NUL)) {
|
||||||
mpp = &(mp->m_next);
|
mpp = &(mp->m_next);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/*
|
// We reset the indicated mode bits. If nothing is
|
||||||
* We reset the indicated mode bits. If nothing is
|
// left the entry is deleted below.
|
||||||
* left the entry is deleted below.
|
|
||||||
*/
|
|
||||||
mp->m_mode &= ~mode;
|
mp->m_mode &= ~mode;
|
||||||
did_it = TRUE; /* remember we did something */
|
did_it = true; // remember we did something
|
||||||
} else if (!hasarg) { /* show matching entry */
|
} else if (!has_rhs) { // show matching entry
|
||||||
showmap(mp, map_table != maphash);
|
showmap(mp, map_table != maphash);
|
||||||
did_it = TRUE;
|
did_it = true;
|
||||||
} else if (n != len) { /* new entry is ambiguous */
|
} else if (n != len) { // new entry is ambiguous
|
||||||
mpp = &(mp->m_next);
|
mpp = &(mp->m_next);
|
||||||
continue;
|
continue;
|
||||||
} else if (unique) {
|
} else if (args->unique) {
|
||||||
if (abbrev)
|
if (is_abbrev) {
|
||||||
EMSG2(_("E226: abbreviation already exists for %s"),
|
EMSG2(_("E226: abbreviation already exists for %s"), p);
|
||||||
p);
|
} else {
|
||||||
else
|
|
||||||
EMSG2(_("E227: mapping already exists for %s"), p);
|
EMSG2(_("E227: mapping already exists for %s"), p);
|
||||||
|
}
|
||||||
retval = 5;
|
retval = 5;
|
||||||
goto theend;
|
goto theend;
|
||||||
} else { /* new rhs for existing entry */
|
} else { // new rhs for existing entry
|
||||||
mp->m_mode &= ~mode; /* remove mode bits */
|
mp->m_mode &= ~mode; // remove mode bits
|
||||||
if (mp->m_mode == 0 && !did_it) { /* reuse entry */
|
if (mp->m_mode == 0 && !did_it) { // reuse entry
|
||||||
xfree(mp->m_str);
|
xfree(mp->m_str);
|
||||||
mp->m_str = vim_strsave(rhs);
|
mp->m_str = vim_strsave(rhs);
|
||||||
xfree(mp->m_orig_str);
|
xfree(mp->m_orig_str);
|
||||||
mp->m_orig_str = vim_strsave(orig_rhs);
|
mp->m_orig_str = vim_strsave(orig_rhs);
|
||||||
mp->m_noremap = noremap;
|
mp->m_noremap = noremap;
|
||||||
mp->m_nowait = nowait;
|
mp->m_nowait = args->nowait;
|
||||||
mp->m_silent = silent;
|
mp->m_silent = args->silent;
|
||||||
mp->m_mode = mode;
|
mp->m_mode = mode;
|
||||||
mp->m_expr = expr;
|
mp->m_expr = args->expr;
|
||||||
mp->m_script_ID = current_SID;
|
mp->m_script_ID = current_SID;
|
||||||
did_it = TRUE;
|
did_it = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mp->m_mode == 0) { // entry can be deleted
|
if (mp->m_mode == 0) { // entry can be deleted
|
||||||
mapblock_free(mpp);
|
mapblock_free(mpp);
|
||||||
continue; // continue with *mpp
|
continue; // continue with *mpp
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// May need to put this entry into another hash list.
|
||||||
* May need to put this entry into another hash list.
|
|
||||||
*/
|
|
||||||
new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]);
|
new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]);
|
||||||
if (!abbrev && new_hash != hash) {
|
if (!is_abbrev && new_hash != hash) {
|
||||||
*mpp = mp->m_next;
|
*mpp = mp->m_next;
|
||||||
mp->m_next = map_table[new_hash];
|
mp->m_next = map_table[new_hash];
|
||||||
map_table[new_hash] = mp;
|
map_table[new_hash] = mp;
|
||||||
|
|
||||||
continue; /* continue with *mpp */
|
continue; // continue with *mpp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2925,13 +2964,13 @@ do_map (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maptype == 1) { /* delete entry */
|
if (maptype == 1) { // delete entry
|
||||||
if (!did_it) {
|
if (!did_it) {
|
||||||
retval = 2; /* no match */
|
retval = 2; // no match
|
||||||
} else if (*keys == Ctrl_C) {
|
} else if (*lhs == Ctrl_C) {
|
||||||
// If CTRL-C has been unmapped, reuse it for Interrupting.
|
// If CTRL-C has been unmapped, reuse it for Interrupting.
|
||||||
if (map_table == curbuf->b_maphash) {
|
if (map_table == buf->b_maphash) {
|
||||||
curbuf->b_mapped_ctrl_c &= ~mode;
|
buf->b_mapped_ctrl_c &= ~mode;
|
||||||
} else {
|
} else {
|
||||||
mapped_ctrl_c &= ~mode;
|
mapped_ctrl_c &= ~mode;
|
||||||
}
|
}
|
||||||
@@ -2939,48 +2978,46 @@ do_map (
|
|||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!haskey || !hasarg) { /* print entries */
|
if (!has_lhs || !has_rhs) { // print entries
|
||||||
if (!did_it
|
if (!did_it && !did_local) {
|
||||||
&& !did_local
|
if (is_abbrev) {
|
||||||
) {
|
|
||||||
if (abbrev)
|
|
||||||
MSG(_("No abbreviation found"));
|
MSG(_("No abbreviation found"));
|
||||||
else
|
} else {
|
||||||
MSG(_("No mapping found"));
|
MSG(_("No mapping found"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
goto theend; /* listing finished */
|
goto theend; // listing finished
|
||||||
}
|
}
|
||||||
|
|
||||||
if (did_it) /* have added the new entry already */
|
if (did_it) { // have added the new entry already
|
||||||
goto theend;
|
goto theend;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
// Get here when adding a new entry to the maphash[] list or abbrlist.
|
||||||
* Get here when adding a new entry to the maphash[] list or abbrlist.
|
|
||||||
*/
|
|
||||||
mp = xmalloc(sizeof(mapblock_T));
|
mp = xmalloc(sizeof(mapblock_T));
|
||||||
|
|
||||||
// If CTRL-C has been mapped, don't always use it for Interrupting.
|
// If CTRL-C has been mapped, don't always use it for Interrupting.
|
||||||
if (*keys == Ctrl_C) {
|
if (*lhs == Ctrl_C) {
|
||||||
if (map_table == curbuf->b_maphash) {
|
if (map_table == buf->b_maphash) {
|
||||||
curbuf->b_mapped_ctrl_c |= mode;
|
buf->b_mapped_ctrl_c |= mode;
|
||||||
} else {
|
} else {
|
||||||
mapped_ctrl_c |= mode;
|
mapped_ctrl_c |= mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mp->m_keys = vim_strsave(keys);
|
mp->m_keys = vim_strsave(lhs);
|
||||||
mp->m_str = vim_strsave(rhs);
|
mp->m_str = vim_strsave(rhs);
|
||||||
mp->m_orig_str = vim_strsave(orig_rhs);
|
mp->m_orig_str = vim_strsave(orig_rhs);
|
||||||
mp->m_keylen = (int)STRLEN(mp->m_keys);
|
mp->m_keylen = (int)STRLEN(mp->m_keys);
|
||||||
mp->m_noremap = noremap;
|
mp->m_noremap = noremap;
|
||||||
mp->m_nowait = nowait;
|
mp->m_nowait = args->nowait;
|
||||||
mp->m_silent = silent;
|
mp->m_silent = args->silent;
|
||||||
mp->m_mode = mode;
|
mp->m_mode = mode;
|
||||||
mp->m_expr = expr;
|
mp->m_expr = args->expr;
|
||||||
mp->m_script_ID = current_SID;
|
mp->m_script_ID = current_SID;
|
||||||
|
|
||||||
/* add the new entry in front of the abbrlist or maphash[] list */
|
// add the new entry in front of the abbrlist or maphash[] list
|
||||||
if (abbrev) {
|
if (is_abbrev) {
|
||||||
mp->m_next = *abbr_table;
|
mp->m_next = *abbr_table;
|
||||||
*abbr_table = mp;
|
*abbr_table = mp;
|
||||||
} else {
|
} else {
|
||||||
@@ -2990,11 +3027,86 @@ do_map (
|
|||||||
}
|
}
|
||||||
|
|
||||||
theend:
|
theend:
|
||||||
xfree(keys_buf);
|
|
||||||
xfree(arg_buf);
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like @ref do_map, but you can specify the target buffer.
|
||||||
|
int buf_do_map(int maptype, char_u *arg, int mode, bool is_abbrev, buf_T *buf)
|
||||||
|
{
|
||||||
|
MapArguments parsed_args;
|
||||||
|
int result = str_to_mapargs(arg, maptype == 1, &parsed_args);
|
||||||
|
switch (result) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
result = 1; // invalid arguments
|
||||||
|
goto FREE_AND_RETURN;
|
||||||
|
default:
|
||||||
|
assert(false && "Unknown return code from str_to_mapargs!");
|
||||||
|
result = -1;
|
||||||
|
goto FREE_AND_RETURN;
|
||||||
|
} // switch
|
||||||
|
|
||||||
|
result = buf_do_map_explicit(maptype, &parsed_args, mode, is_abbrev, buf);
|
||||||
|
|
||||||
|
FREE_AND_RETURN:
|
||||||
|
xfree(parsed_args.rhs);
|
||||||
|
xfree(parsed_args.orig_rhs);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Set or remove a mapping or an abbreviation in the current buffer, OR
|
||||||
|
/// display (matching) mappings/abbreviations.
|
||||||
|
///
|
||||||
|
/// ```vim
|
||||||
|
/// map[!] " show all key mappings
|
||||||
|
/// map[!] {lhs} " show key mapping for {lhs}
|
||||||
|
/// map[!] {lhs} {rhs} " set key mapping for {lhs} to {rhs}
|
||||||
|
/// noremap[!] {lhs} {rhs} " same, but no remapping for {rhs}
|
||||||
|
/// unmap[!] {lhs} " remove key mapping for {lhs}
|
||||||
|
/// abbr " show all abbreviations
|
||||||
|
/// abbr {lhs} " show abbreviations for {lhs}
|
||||||
|
/// abbr {lhs} {rhs} " set abbreviation for {lhs} to {rhs}
|
||||||
|
/// noreabbr {lhs} {rhs} " same, but no remapping for {rhs}
|
||||||
|
/// unabbr {lhs} " remove abbreviation for {lhs}
|
||||||
|
///
|
||||||
|
/// for :map mode is NORMAL + VISUAL + SELECTMODE + OP_PENDING
|
||||||
|
/// for :map! mode is INSERT + CMDLINE
|
||||||
|
/// for :cmap mode is CMDLINE
|
||||||
|
/// for :imap mode is INSERT
|
||||||
|
/// for :lmap mode is LANGMAP
|
||||||
|
/// for :nmap mode is NORMAL
|
||||||
|
/// for :vmap mode is VISUAL + SELECTMODE
|
||||||
|
/// for :xmap mode is VISUAL
|
||||||
|
/// for :smap mode is SELECTMODE
|
||||||
|
/// for :omap mode is OP_PENDING
|
||||||
|
/// for :tmap mode is TERM_FOCUS
|
||||||
|
///
|
||||||
|
/// for :abbr mode is INSERT + CMDLINE
|
||||||
|
/// for :iabbr mode is INSERT
|
||||||
|
/// for :cabbr mode is CMDLINE
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// @param maptype 0 for |:map|, 1 for |:unmap|, 2 for |noremap|.
|
||||||
|
/// @param arg C-string containing the arguments of the map/abbrev
|
||||||
|
/// command, i.e. everything except the initial `:[X][nore]map`.
|
||||||
|
/// - Cannot be a read-only string; it will be modified.
|
||||||
|
/// @param mode Bitflags representing the mode in which to set the mapping.
|
||||||
|
/// See @ref get_map_mode.
|
||||||
|
/// @param is_abbrev True if setting an abbreviation, false otherwise.
|
||||||
|
///
|
||||||
|
/// @return 0 on success. On failure, will return one of the following:
|
||||||
|
/// - 1 for invalid arguments
|
||||||
|
/// - 2 for no match
|
||||||
|
/// - 4 for out of mem (deprecated, WON'T HAPPEN)
|
||||||
|
/// - 5 for entry not unique
|
||||||
|
///
|
||||||
|
int do_map(int maptype, char_u *arg, int mode, bool is_abbrev)
|
||||||
|
{
|
||||||
|
return buf_do_map(maptype, arg, mode, is_abbrev, curbuf);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Delete one entry from the abbrlist or maphash[].
|
* Delete one entry from the abbrlist or maphash[].
|
||||||
* "mpp" is a pointer to the m_next field of the PREVIOUS entry!
|
* "mpp" is a pointer to the m_next field of the PREVIOUS entry!
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
#include "nvim/types.h"
|
#include "nvim/types.h"
|
||||||
#include "nvim/buffer_defs.h"
|
#include "nvim/buffer_defs.h"
|
||||||
#include "nvim/ex_cmds_defs.h"
|
#include "nvim/ex_cmds_defs.h"
|
||||||
|
#include "nvim/vim.h"
|
||||||
|
|
||||||
/// Values for "noremap" argument of ins_typebuf()
|
/// Values for "noremap" argument of ins_typebuf()
|
||||||
///
|
///
|
||||||
@@ -23,6 +24,39 @@ typedef enum {
|
|||||||
FLUSH_INPUT // flush typebuf and inchar() input
|
FLUSH_INPUT // flush typebuf and inchar() input
|
||||||
} flush_buffers_T;
|
} flush_buffers_T;
|
||||||
|
|
||||||
|
/// All possible |:map-arguments| usable in a |:map| command.
|
||||||
|
///
|
||||||
|
/// The <special> argument has no effect on mappings and is excluded from this
|
||||||
|
/// struct declaration. |noremap| is included, since it behaves like a map
|
||||||
|
/// argument when used in a mapping.
|
||||||
|
///
|
||||||
|
/// @see mapblock_T
|
||||||
|
struct map_arguments {
|
||||||
|
bool buffer;
|
||||||
|
bool expr;
|
||||||
|
bool noremap;
|
||||||
|
bool nowait;
|
||||||
|
bool script;
|
||||||
|
bool silent;
|
||||||
|
bool unique;
|
||||||
|
|
||||||
|
/// The {lhs} of the mapping.
|
||||||
|
///
|
||||||
|
/// vim limits this to MAXMAPLEN characters, allowing us to use a static
|
||||||
|
/// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal
|
||||||
|
/// that {lhs} was too long and truncated.
|
||||||
|
char_u lhs[MAXMAPLEN + 1];
|
||||||
|
size_t lhs_len;
|
||||||
|
|
||||||
|
char_u *rhs; /// The {rhs} of the mapping.
|
||||||
|
size_t rhs_len;
|
||||||
|
bool rhs_is_noop; /// True when the {orig_rhs} is <nop>.
|
||||||
|
|
||||||
|
char_u *orig_rhs; /// The original text of the {rhs}.
|
||||||
|
size_t orig_rhs_len;
|
||||||
|
};
|
||||||
|
typedef struct map_arguments MapArguments;
|
||||||
|
|
||||||
#define KEYLEN_PART_KEY -1 /* keylen value for incomplete key-code */
|
#define KEYLEN_PART_KEY -1 /* keylen value for incomplete key-code */
|
||||||
#define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */
|
#define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */
|
||||||
#define KEYLEN_REMOVED 9999 /* keylen value for removed sequence */
|
#define KEYLEN_REMOVED 9999 /* keylen value for removed sequence */
|
||||||
|
@@ -1,15 +1,19 @@
|
|||||||
local helpers = require('test.functional.helpers')(after_each)
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
local global_helpers = require('test.helpers')
|
local global_helpers = require('test.helpers')
|
||||||
|
|
||||||
|
local bufmeths = helpers.bufmeths
|
||||||
local clear = helpers.clear
|
local clear = helpers.clear
|
||||||
local command = helpers.command
|
local command = helpers.command
|
||||||
local curbufmeths = helpers.curbufmeths
|
local curbufmeths = helpers.curbufmeths
|
||||||
local eq = helpers.eq
|
local eq, neq = helpers.eq, helpers.neq
|
||||||
|
local expect_err = helpers.expect_err
|
||||||
|
local feed = helpers.feed
|
||||||
local funcs = helpers.funcs
|
local funcs = helpers.funcs
|
||||||
local meths = helpers.meths
|
local meths = helpers.meths
|
||||||
local source = helpers.source
|
local source = helpers.source
|
||||||
|
|
||||||
local shallowcopy = global_helpers.shallowcopy
|
local shallowcopy = global_helpers.shallowcopy
|
||||||
|
local sleep = global_helpers.sleep
|
||||||
|
|
||||||
describe('nvim_get_keymap', function()
|
describe('nvim_get_keymap', function()
|
||||||
before_each(clear)
|
before_each(clear)
|
||||||
@@ -308,3 +312,502 @@ describe('nvim_get_keymap', function()
|
|||||||
eq({space_table}, meths.get_keymap('n'))
|
eq({space_table}, meths.get_keymap('n'))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('nvim_[set/del]_keymap', function()
|
||||||
|
before_each(clear)
|
||||||
|
|
||||||
|
-- generate_expected is truthy when we want to generate an expected output for
|
||||||
|
-- maparg(); mapargs() won't take '!' as an input, though it will return '!'
|
||||||
|
-- in its output if getting a mapping set with |:map!|
|
||||||
|
local function normalize_mapmode(mode, generate_expected)
|
||||||
|
if not generate_expected and mode == '!' then
|
||||||
|
-- can't retrieve mapmode-ic mappings with '!', but can with 'i' or 'c'.
|
||||||
|
mode = 'i'
|
||||||
|
elseif mode == '' or mode == ' ' or mode == 'm' then
|
||||||
|
mode = generate_expected and ' ' or 'm'
|
||||||
|
end
|
||||||
|
return mode
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Generate a mapargs dict, for comparison against the mapping that was
|
||||||
|
-- actually set
|
||||||
|
local function generate_mapargs(mode, lhs, rhs, opts)
|
||||||
|
if not opts then
|
||||||
|
opts = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local to_return = {}
|
||||||
|
to_return.mode = normalize_mapmode(mode, true)
|
||||||
|
to_return.noremap = not opts.noremap and 0 or 1
|
||||||
|
to_return.lhs = lhs
|
||||||
|
to_return.rhs = rhs
|
||||||
|
to_return.silent = not opts.silent and 0 or 1
|
||||||
|
to_return.nowait = not opts.nowait and 0 or 1
|
||||||
|
to_return.expr = not opts.expr and 0 or 1
|
||||||
|
to_return.sid = not opts.sid and 0 or opts.sid
|
||||||
|
to_return.buffer = not opts.buffer and 0 or opts.buffer
|
||||||
|
|
||||||
|
-- mode 't' doesn't print when calling maparg
|
||||||
|
if mode == 't' then
|
||||||
|
to_return.mode = ''
|
||||||
|
end
|
||||||
|
|
||||||
|
return to_return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Retrieve a mapargs dict from neovim, if one exists
|
||||||
|
local function get_mapargs(mode, lhs)
|
||||||
|
return funcs.maparg(lhs, normalize_mapmode(mode), false, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test error handling
|
||||||
|
it('throws errors when given empty lhs', function()
|
||||||
|
-- escape parentheses in lua string, else comparison fails erroneously
|
||||||
|
expect_err('Invalid %(empty%) LHS',
|
||||||
|
meths.set_keymap, '', '', 'rhs', {})
|
||||||
|
expect_err('Invalid %(empty%) LHS',
|
||||||
|
meths.set_keymap, '', '', '', {})
|
||||||
|
|
||||||
|
expect_err('Invalid %(empty%) LHS', meths.del_keymap, '', '')
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('throws errors when given an lhs longer than MAXMAPLEN', function()
|
||||||
|
-- assume MAXMAPLEN of 50 chars, as declared in vim.h
|
||||||
|
local MAXMAPLEN = 50
|
||||||
|
local lhs = ''
|
||||||
|
for i=1,MAXMAPLEN do
|
||||||
|
lhs = lhs..(i % 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- exactly 50 chars should be fine
|
||||||
|
meths.set_keymap('', lhs, 'rhs', {})
|
||||||
|
|
||||||
|
-- del_keymap should unmap successfully
|
||||||
|
meths.del_keymap('', lhs)
|
||||||
|
eq({}, get_mapargs('', lhs))
|
||||||
|
|
||||||
|
-- 51 chars should produce an error
|
||||||
|
lhs = lhs..'1'
|
||||||
|
expect_err('LHS exceeds maximum map length: '..lhs,
|
||||||
|
meths.set_keymap, '', lhs, 'rhs', {})
|
||||||
|
expect_err('LHS exceeds maximum map length: '..lhs,
|
||||||
|
meths.del_keymap, '', lhs)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('does not throw errors when rhs is longer than MAXMAPLEN', function()
|
||||||
|
local MAXMAPLEN = 50
|
||||||
|
local rhs = ''
|
||||||
|
for i=1,MAXMAPLEN do
|
||||||
|
rhs = rhs..(i % 10)
|
||||||
|
end
|
||||||
|
rhs = rhs..'1'
|
||||||
|
meths.set_keymap('', 'lhs', rhs, {})
|
||||||
|
eq(generate_mapargs('', 'lhs', rhs),
|
||||||
|
get_mapargs('', 'lhs'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('throws errors when given too-long mode shortnames', function()
|
||||||
|
expect_err('Shortname is too long: map',
|
||||||
|
meths.set_keymap, 'map', 'lhs', 'rhs', {})
|
||||||
|
|
||||||
|
expect_err('Shortname is too long: vmap',
|
||||||
|
meths.set_keymap, 'vmap', 'lhs', 'rhs', {})
|
||||||
|
|
||||||
|
expect_err('Shortname is too long: xnoremap',
|
||||||
|
meths.set_keymap, 'xnoremap', 'lhs', 'rhs', {})
|
||||||
|
|
||||||
|
expect_err('Shortname is too long: map', meths.del_keymap, 'map', 'lhs')
|
||||||
|
expect_err('Shortname is too long: vmap', meths.del_keymap, 'vmap', 'lhs')
|
||||||
|
expect_err('Shortname is too long: xnoremap', meths.del_keymap, 'xnoremap', 'lhs')
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('throws errors when given unrecognized mode shortnames', function()
|
||||||
|
expect_err('Invalid mode shortname: ?',
|
||||||
|
meths.set_keymap, '?', 'lhs', 'rhs', {})
|
||||||
|
|
||||||
|
expect_err('Invalid mode shortname: y',
|
||||||
|
meths.set_keymap, 'y', 'lhs', 'rhs', {})
|
||||||
|
|
||||||
|
expect_err('Invalid mode shortname: p',
|
||||||
|
meths.set_keymap, 'p', 'lhs', 'rhs', {})
|
||||||
|
|
||||||
|
expect_err('Invalid mode shortname: ?', meths.del_keymap, '?', 'lhs')
|
||||||
|
expect_err('Invalid mode shortname: y', meths.del_keymap, 'y', 'lhs')
|
||||||
|
expect_err('Invalid mode shortname: p', meths.del_keymap, 'p', 'lhs')
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('throws errors when optnames are almost right', function()
|
||||||
|
expect_err('Invalid key: silentt',
|
||||||
|
meths.set_keymap, 'n', 'lhs', 'rhs', {silentt = true})
|
||||||
|
expect_err('Invalid key: sidd',
|
||||||
|
meths.set_keymap, 'n', 'lhs', 'rhs', {sidd = false})
|
||||||
|
expect_err('Invalid key: nowaiT',
|
||||||
|
meths.set_keymap, 'n', 'lhs', 'rhs', {nowaiT = false})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('does not recognize <buffer> as an option', function()
|
||||||
|
expect_err('Invalid key: buffer',
|
||||||
|
meths.set_keymap, 'n', 'lhs', 'rhs', {buffer = true})
|
||||||
|
end)
|
||||||
|
|
||||||
|
local optnames = {'nowait', 'silent', 'script', 'expr', 'unique'}
|
||||||
|
for _, opt in ipairs(optnames) do
|
||||||
|
-- note: need '%' to escape hyphens, which have special meaning in lua
|
||||||
|
it('throws an error when given non-boolean value for '..opt, function()
|
||||||
|
local opts = {}
|
||||||
|
opts[opt] = 2
|
||||||
|
expect_err('Gave non%-boolean value for an opt: '..opt,
|
||||||
|
meths.set_keymap, 'n', 'lhs', 'rhs', opts)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Perform tests of basic functionality
|
||||||
|
it('can set ordinary mappings', function()
|
||||||
|
meths.set_keymap('n', 'lhs', 'rhs', {})
|
||||||
|
eq(generate_mapargs('n', 'lhs', 'rhs'), get_mapargs('n', 'lhs'))
|
||||||
|
|
||||||
|
meths.set_keymap('v', 'lhs', 'rhs', {})
|
||||||
|
eq(generate_mapargs('v', 'lhs', 'rhs'), get_mapargs('v', 'lhs'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('doesn\'t throw when lhs or rhs have leading/trailing WS', function()
|
||||||
|
meths.set_keymap('n', ' lhs', 'rhs', {})
|
||||||
|
eq(generate_mapargs('n', '<Space><Space><Space>lhs', 'rhs'),
|
||||||
|
get_mapargs('n', ' lhs'))
|
||||||
|
|
||||||
|
meths.set_keymap('n', 'lhs ', 'rhs', {})
|
||||||
|
eq(generate_mapargs('n', 'lhs<Space><Space><Space><Space>', 'rhs'),
|
||||||
|
get_mapargs('n', 'lhs '))
|
||||||
|
|
||||||
|
meths.set_keymap('v', ' lhs ', '\trhs\t\f', {})
|
||||||
|
eq(generate_mapargs('v', '<Space>lhs<Space><Space>', '\trhs\t\f'),
|
||||||
|
get_mapargs('v', ' lhs '))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can set noremap mappings', function()
|
||||||
|
meths.set_keymap('x', 'lhs', 'rhs', {noremap = true})
|
||||||
|
eq(generate_mapargs('x', 'lhs', 'rhs', {noremap = true}),
|
||||||
|
get_mapargs('x', 'lhs'))
|
||||||
|
|
||||||
|
meths.set_keymap('t', 'lhs', 'rhs', {noremap = true})
|
||||||
|
eq(generate_mapargs('t', 'lhs', 'rhs', {noremap = true}),
|
||||||
|
get_mapargs('t', 'lhs'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can unmap mappings', function()
|
||||||
|
meths.set_keymap('v', 'lhs', 'rhs', {})
|
||||||
|
meths.del_keymap('v', 'lhs')
|
||||||
|
eq({}, get_mapargs('v', 'lhs'))
|
||||||
|
|
||||||
|
meths.set_keymap('t', 'lhs', 'rhs', {noremap = true})
|
||||||
|
meths.del_keymap('t', 'lhs')
|
||||||
|
eq({}, get_mapargs('t', 'lhs'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Test some edge cases
|
||||||
|
it('accepts "!" and " " and "" as synonyms for mapmode-nvo', function()
|
||||||
|
local nvo_shortnames = {'', ' ', '!'}
|
||||||
|
for _, name in ipairs(nvo_shortnames) do
|
||||||
|
meths.set_keymap(name, 'lhs', 'rhs', {})
|
||||||
|
meths.del_keymap(name, 'lhs')
|
||||||
|
eq({}, get_mapargs(name, 'lhs'))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local special_chars = {'<C-U>', '<S-Left>', '<F12><F2><Tab>', '<Space><Tab>'}
|
||||||
|
for _, lhs in ipairs(special_chars) do
|
||||||
|
for _, rhs in ipairs(special_chars) do
|
||||||
|
local mapmode = '!'
|
||||||
|
it('can set mappings with special characters, lhs: '..lhs..', rhs: '..rhs,
|
||||||
|
function()
|
||||||
|
meths.set_keymap(mapmode, lhs, rhs, {})
|
||||||
|
eq(generate_mapargs(mapmode, lhs, rhs), get_mapargs(mapmode, lhs))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it('can set mappings containing literal keycodes', function()
|
||||||
|
meths.set_keymap('n', '\n\r\n', 'rhs', {})
|
||||||
|
local expected = generate_mapargs('n', '<NL><CR><NL>', 'rhs')
|
||||||
|
eq(expected, get_mapargs('n', '<C-j><CR><C-j>'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can set mappings whose RHS is a <Nop>', function()
|
||||||
|
meths.set_keymap('i', 'lhs', '<Nop>', {})
|
||||||
|
command('normal ilhs')
|
||||||
|
eq({''}, curbufmeths.get_lines(0, -1, 0)) -- imap to <Nop> does nothing
|
||||||
|
eq(generate_mapargs('i', 'lhs', '<Nop>', {}),
|
||||||
|
get_mapargs('i', 'lhs'))
|
||||||
|
|
||||||
|
-- also test for case insensitivity
|
||||||
|
meths.set_keymap('i', 'lhs', '<nOp>', {})
|
||||||
|
command('normal ilhs')
|
||||||
|
eq({''}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
-- note: RHS in returned mapargs() dict reflects the original RHS
|
||||||
|
-- provided by the user
|
||||||
|
eq(generate_mapargs('i', 'lhs', '<nOp>', {}),
|
||||||
|
get_mapargs('i', 'lhs'))
|
||||||
|
|
||||||
|
meths.set_keymap('i', 'lhs', '<NOP>', {})
|
||||||
|
command('normal ilhs')
|
||||||
|
eq({''}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
eq(generate_mapargs('i', 'lhs', '<NOP>', {}),
|
||||||
|
get_mapargs('i', 'lhs'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('treats an empty RHS in a mapping like a <Nop>', function()
|
||||||
|
meths.set_keymap('i', 'lhs', '', {})
|
||||||
|
command('normal ilhs')
|
||||||
|
eq({''}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
eq(generate_mapargs('i', 'lhs', '', {}),
|
||||||
|
get_mapargs('i', 'lhs'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can set and unset <M-">', function()
|
||||||
|
-- Taken from the legacy test: test_mapping.vim. Exposes a bug in which
|
||||||
|
-- replace_termcodes changes the length of the mapping's LHS, but
|
||||||
|
-- do_map continues to use the *old* length of LHS.
|
||||||
|
meths.set_keymap('i', '<M-">', 'foo', {})
|
||||||
|
meths.del_keymap('i', '<M-">')
|
||||||
|
eq({}, get_mapargs('i', '<M-">'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('interprets control sequences in expr-quotes correctly when called '
|
||||||
|
..'inside vim', function()
|
||||||
|
command([[call nvim_set_keymap('i', "\<space>", "\<tab>", {})]])
|
||||||
|
eq(generate_mapargs('i', '<Space>', '\t', {}),
|
||||||
|
get_mapargs('i', '<Space>'))
|
||||||
|
feed('i ')
|
||||||
|
eq({'\t'}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('throws appropriate error messages when setting <unique> maps', function()
|
||||||
|
meths.set_keymap('l', 'lhs', 'rhs', {})
|
||||||
|
expect_err('E227: mapping already exists for lhs',
|
||||||
|
meths.set_keymap, 'l', 'lhs', 'rhs', {unique = true})
|
||||||
|
-- different mapmode, no error should be thrown
|
||||||
|
meths.set_keymap('t', 'lhs', 'rhs', {unique = true})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can set <expr> mappings whose RHS change dynamically', function()
|
||||||
|
meths.command_output([[
|
||||||
|
function! FlipFlop() abort
|
||||||
|
if !exists('g:flip') | let g:flip = 0 | endif
|
||||||
|
let g:flip = !g:flip
|
||||||
|
return g:flip
|
||||||
|
endfunction
|
||||||
|
]])
|
||||||
|
eq(1, meths.call_function('FlipFlop', {}))
|
||||||
|
eq(0, meths.call_function('FlipFlop', {}))
|
||||||
|
eq(1, meths.call_function('FlipFlop', {}))
|
||||||
|
eq(0, meths.call_function('FlipFlop', {}))
|
||||||
|
|
||||||
|
meths.set_keymap('i', 'lhs', 'FlipFlop()', {expr = true})
|
||||||
|
command('normal ilhs')
|
||||||
|
eq({'1'}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
|
||||||
|
command('normal! ggVGd')
|
||||||
|
|
||||||
|
command('normal ilhs')
|
||||||
|
eq({'0'}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can set mappings that do trigger other mappings', function()
|
||||||
|
meths.set_keymap('i', 'mhs', 'rhs', {})
|
||||||
|
meths.set_keymap('i', 'lhs', 'mhs', {})
|
||||||
|
|
||||||
|
command('normal imhs')
|
||||||
|
eq({'rhs'}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
|
||||||
|
command('normal! ggVGd')
|
||||||
|
|
||||||
|
command('normal ilhs')
|
||||||
|
eq({'rhs'}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("can set noremap mappings that don't trigger other mappings", function()
|
||||||
|
meths.set_keymap('i', 'mhs', 'rhs', {})
|
||||||
|
meths.set_keymap('i', 'lhs', 'mhs', {noremap = true})
|
||||||
|
|
||||||
|
command('normal imhs')
|
||||||
|
eq({'rhs'}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
|
||||||
|
command('normal! ggVGd')
|
||||||
|
|
||||||
|
command('normal ilhs') -- shouldn't trigger mhs-to-rhs mapping
|
||||||
|
eq({'mhs'}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("can set nowait mappings that fire without waiting", function()
|
||||||
|
meths.set_keymap('i', '123456', 'longer', {})
|
||||||
|
meths.set_keymap('i', '123', 'shorter', {nowait = true})
|
||||||
|
|
||||||
|
-- feed keys one at a time; if all keys arrive atomically, the longer
|
||||||
|
-- mapping will trigger
|
||||||
|
local keys = 'i123456'
|
||||||
|
for c in string.gmatch(keys, '.') do
|
||||||
|
feed(c)
|
||||||
|
sleep(5)
|
||||||
|
end
|
||||||
|
eq({'shorter456'}, curbufmeths.get_lines(0, -1, 0))
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Perform exhaustive tests of basic functionality
|
||||||
|
local mapmodes = {'n', 'v', 'x', 's', 'o', '!', 'i', 'l', 'c', 't', ' ', ''}
|
||||||
|
for _, mapmode in ipairs(mapmodes) do
|
||||||
|
it('can set/unset normal mappings in mapmode '..mapmode, function()
|
||||||
|
meths.set_keymap(mapmode, 'lhs', 'rhs', {})
|
||||||
|
eq(generate_mapargs(mapmode, 'lhs', 'rhs'),
|
||||||
|
get_mapargs(mapmode, 'lhs'))
|
||||||
|
|
||||||
|
-- some mapmodes (like 'o') will prevent other mapmodes (like '!') from
|
||||||
|
-- taking effect, so unmap after each mapping
|
||||||
|
meths.del_keymap(mapmode, 'lhs')
|
||||||
|
eq({}, get_mapargs(mapmode, 'lhs'))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, mapmode in ipairs(mapmodes) do
|
||||||
|
it('can set/unset noremap mappings using mapmode '..mapmode, function()
|
||||||
|
meths.set_keymap(mapmode, 'lhs', 'rhs', {noremap = true})
|
||||||
|
eq(generate_mapargs(mapmode, 'lhs', 'rhs', {noremap = true}),
|
||||||
|
get_mapargs(mapmode, 'lhs'))
|
||||||
|
|
||||||
|
meths.del_keymap(mapmode, 'lhs')
|
||||||
|
eq({}, get_mapargs(mapmode, 'lhs'))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test map-arguments, using optnames from above
|
||||||
|
-- remove some map arguments that are harder to test, or were already tested
|
||||||
|
optnames = {'nowait', 'silent', 'expr', 'noremap'}
|
||||||
|
for _, mapmode in ipairs(mapmodes) do
|
||||||
|
local printable_mode = normalize_mapmode(mapmode)
|
||||||
|
|
||||||
|
-- Test with single mappings
|
||||||
|
for _, maparg in ipairs(optnames) do
|
||||||
|
it('can set/unset '..printable_mode..'-mappings with maparg: '..maparg,
|
||||||
|
function()
|
||||||
|
meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = true})
|
||||||
|
eq(generate_mapargs(mapmode, 'lhs', 'rhs', {[maparg] = true}),
|
||||||
|
get_mapargs(mapmode, 'lhs'))
|
||||||
|
meths.del_keymap(mapmode, 'lhs')
|
||||||
|
eq({}, get_mapargs(mapmode, 'lhs'))
|
||||||
|
end)
|
||||||
|
it ('can set/unset '..printable_mode..'-mode mappings with maparg '..
|
||||||
|
maparg..', whose value is false', function()
|
||||||
|
meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = false})
|
||||||
|
eq(generate_mapargs(mapmode, 'lhs', 'rhs'),
|
||||||
|
get_mapargs(mapmode, 'lhs'))
|
||||||
|
meths.del_keymap(mapmode, 'lhs')
|
||||||
|
eq({}, get_mapargs(mapmode, 'lhs'))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test with triplets of mappings, one of which is false
|
||||||
|
for i = 1, (#optnames - 2) do
|
||||||
|
local opt1, opt2, opt3 = optnames[i], optnames[i + 1], optnames[i + 2]
|
||||||
|
it('can set/unset '..printable_mode..'-mode mappings with mapargs '..
|
||||||
|
opt1..', '..opt2..', '..opt3, function()
|
||||||
|
local opts = {[opt1] = true, [opt2] = false, [opt3] = true}
|
||||||
|
meths.set_keymap(mapmode, 'lhs', 'rhs', opts)
|
||||||
|
eq(generate_mapargs(mapmode, 'lhs', 'rhs', opts),
|
||||||
|
get_mapargs(mapmode, 'lhs'))
|
||||||
|
meths.del_keymap(mapmode, 'lhs')
|
||||||
|
eq({}, get_mapargs(mapmode, 'lhs'))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('nvim_buf_[set/del]_keymap', function()
|
||||||
|
before_each(clear)
|
||||||
|
|
||||||
|
-- nvim_set_keymap is implemented as a wrapped call to nvim_buf_set_keymap,
|
||||||
|
-- so its tests also effectively test nvim_buf_set_keymap
|
||||||
|
|
||||||
|
-- here, we mainly test for buffer specificity and other special cases
|
||||||
|
|
||||||
|
-- switch to the given buffer, abandoning any changes in the current buffer
|
||||||
|
local function switch_to_buf(bufnr)
|
||||||
|
command(bufnr..'buffer!')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- `set hidden`, then create two buffers and return their bufnr's
|
||||||
|
-- If start_from_first is truthy, the first buffer will be open when
|
||||||
|
-- the function returns; if falsy, the second buffer will be open.
|
||||||
|
local function make_two_buffers(start_from_first)
|
||||||
|
command('set hidden')
|
||||||
|
|
||||||
|
local first_buf = meths.call_function('bufnr', {'%'})
|
||||||
|
command('new')
|
||||||
|
local second_buf = meths.call_function('bufnr', {'%'})
|
||||||
|
neq(second_buf, first_buf) -- sanity check
|
||||||
|
|
||||||
|
if start_from_first then
|
||||||
|
switch_to_buf(first_buf)
|
||||||
|
end
|
||||||
|
|
||||||
|
return first_buf, second_buf
|
||||||
|
end
|
||||||
|
|
||||||
|
it('rejects negative bufnr values', function()
|
||||||
|
expect_err('Wrong type for argument 1, expecting Buffer',
|
||||||
|
bufmeths.set_keymap, -1, '', 'lhs', 'rhs', {})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can set mappings active in the current buffer but not others', function()
|
||||||
|
local first, second = make_two_buffers(true)
|
||||||
|
|
||||||
|
bufmeths.set_keymap(0, '', 'lhs', 'irhs<Esc>', {})
|
||||||
|
command('normal lhs')
|
||||||
|
eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1))
|
||||||
|
|
||||||
|
-- mapping should have no effect in new buffer
|
||||||
|
switch_to_buf(second)
|
||||||
|
command('normal lhs')
|
||||||
|
eq({''}, bufmeths.get_lines(0, 0, 1, 1))
|
||||||
|
|
||||||
|
-- mapping should remain active in old buffer
|
||||||
|
switch_to_buf(first)
|
||||||
|
command('normal ^lhs')
|
||||||
|
eq({'rhsrhs'}, bufmeths.get_lines(0, 0, 1, 1))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can set local mappings in buffer other than current', function()
|
||||||
|
local first = make_two_buffers(false)
|
||||||
|
bufmeths.set_keymap(first, '', 'lhs', 'irhs<Esc>', {})
|
||||||
|
|
||||||
|
-- shouldn't do anything
|
||||||
|
command('normal lhs')
|
||||||
|
eq({''}, bufmeths.get_lines(0, 0, 1, 1))
|
||||||
|
|
||||||
|
-- should take effect
|
||||||
|
switch_to_buf(first)
|
||||||
|
command('normal lhs')
|
||||||
|
eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can disable mappings made in another buffer, inside that buffer', function()
|
||||||
|
local first = make_two_buffers(false)
|
||||||
|
bufmeths.set_keymap(first, '', 'lhs', 'irhs<Esc>', {})
|
||||||
|
bufmeths.del_keymap(first, '', 'lhs')
|
||||||
|
switch_to_buf(first)
|
||||||
|
|
||||||
|
-- shouldn't do anything
|
||||||
|
command('normal lhs')
|
||||||
|
eq({''}, bufmeths.get_lines(0, 0, 1, 1))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("can't disable mappings given wrong buffer handle", function()
|
||||||
|
local first, second = make_two_buffers(false)
|
||||||
|
bufmeths.set_keymap(first, '', 'lhs', 'irhs<Esc>', {})
|
||||||
|
expect_err('E31: No such mapping',
|
||||||
|
bufmeths.del_keymap, second, '', 'lhs')
|
||||||
|
|
||||||
|
-- should still work
|
||||||
|
switch_to_buf(first)
|
||||||
|
command('normal lhs')
|
||||||
|
eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
Reference in New Issue
Block a user