aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/getchar.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/getchar.c')
-rw-r--r--src/nvim/getchar.c725
1 files changed, 417 insertions, 308 deletions
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 0020b57482..f8c7dc613b 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -2502,286 +2502,334 @@ int fix_input_buffer(char_u *buf, int len)
return len;
}
-/*
- * 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}
- *
- * maptype: 0 for :map, 1 for :unmap, 2 for noremap.
- *
- * arg is pointer to any arguments. Note: arg cannot be a read-only string,
- * it will be modified.
- *
- * 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
- *
- * 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 */
-)
+/// Replace termcodes in the given LHS and RHS and store the results into the
+/// `lhs` and `rhs` of the given @ref MapArguments struct.
+///
+/// `rhs` and `orig_rhs` will both point to new allocated buffers. `orig_rhs`
+/// will hold a copy of the given `orig_rhs`.
+///
+/// The `*_len` variables will be set appropriately. If the length of
+/// the final `lhs` exceeds `MAXMAPLEN`, `lhs_len` will be set equal to the
+/// original larger length and `lhs` will be truncated.
+///
+/// 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.
+///
+/// Any memory allocated by @ref replace_termcodes is freed before this function
+/// returns.
+///
+/// @param[in] orig_lhs Original mapping LHS, with characters to replace.
+/// @param[in] orig_lhs_len `strlen` of orig_lhs.
+/// @param[in] orig_rhs Original mapping RHS, with characters to replace.
+/// @param[in] orig_rhs_len `strlen` of orig_rhs.
+/// @param[in] cpo_flags See param docs for @ref replace_termcodes.
+/// @param[out] mapargs MapArguments struct holding the replaced strings.
+void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len,
+ const char_u *orig_rhs, const size_t orig_rhs_len,
+ int cpo_flags, MapArguments *mapargs)
{
- char_u *keys;
- mapblock_T *mp, **mpp;
- 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;
+ char_u *lhs_buf = NULL;
+ char_u *rhs_buf = NULL;
- keys = arg;
- map_table = maphash;
- abbr_table = &first_abbr;
+ // If mapping has been given as ^V<C_UP> say, then replace the term codes
+ // with the appropriate two bytes. If it is a shifted special key, unshift
+ // 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));
+
+ mapargs->orig_rhs_len = orig_rhs_len;
+ mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u));
+ xstrlcpy((char *)mapargs->orig_rhs, (char *)orig_rhs,
+ mapargs->orig_rhs_len + 1);
+
+ if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing
+ mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char
+ mapargs->rhs_len = 0;
+ mapargs->rhs_is_noop = true;
+ } else {
+ replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf,
+ false, true, true, cpo_flags);
+ mapargs->rhs_len = STRLEN(replaced);
+ mapargs->rhs_is_noop = false;
+ mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u));
+ xstrlcpy((char *)mapargs->rhs, (char *)replaced, mapargs->rhs_len + 1);
+ }
- /* For ":noremap" don't remap, otherwise do remap. */
- if (maptype == 2)
- noremap = REMAP_NONE;
- else
- noremap = REMAP_YES;
+ xfree(lhs_buf);
+ xfree(rhs_buf);
+}
- /* Accept <buffer>, <nowait>, <silent>, <expr> <script> and <unique> in
- * any order. */
- for (;; ) {
- /*
- * Check for "<buffer>": mapping local to buffer.
- */
- if (STRNCMP(keys, "<buffer>", 8) == 0) {
- keys = skipwhite(keys + 8);
- map_table = curbuf->b_maphash;
- abbr_table = &curbuf->b_first_abbr;
+/// 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;
}
- /*
- * Check for "<nowait>": don't wait for more characters.
- */
- if (STRNCMP(keys, "<nowait>", 8) == 0) {
- keys = skipwhite(keys + 8);
- nowait = true;
+ if (STRNCMP(to_parse, "<nowait>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.nowait = true;
continue;
}
- /*
- * Check for "<silent>": don't echo commands.
- */
- if (STRNCMP(keys, "<silent>", 8) == 0) {
- keys = skipwhite(keys + 8);
- silent = true;
+ if (STRNCMP(to_parse, "<silent>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.silent = true;
continue;
}
// Ignore obsolete "<special>" modifier.
- if (STRNCMP(keys, "<special>", 9) == 0) {
- keys = skipwhite(keys + 9);
+ if (STRNCMP(to_parse, "<special>", 9) == 0) {
+ to_parse = skipwhite(to_parse + 9);
continue;
}
- /*
- * Check for "<script>": remap script-local mappings only
- */
- if (STRNCMP(keys, "<script>", 8) == 0) {
- keys = skipwhite(keys + 8);
- noremap = REMAP_SCRIPT;
+ if (STRNCMP(to_parse, "<script>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.script = true;
continue;
}
- /*
- * Check for "<expr>": {rhs} is an expression.
- */
- if (STRNCMP(keys, "<expr>", 6) == 0) {
- keys = skipwhite(keys + 6);
- expr = true;
+ if (STRNCMP(to_parse, "<expr>", 6) == 0) {
+ to_parse = skipwhite(to_parse + 6);
+ parsed_args.expr = true;
continue;
}
- /*
- * Check for "<unique>": don't overwrite an existing mapping.
- */
- if (STRNCMP(keys, "<unique>", 8) == 0) {
- keys = skipwhite(keys + 8);
- unique = true;
+
+ if (STRNCMP(to_parse, "<unique>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.unique = true;
continue;
}
break;
}
- validate_maphash();
-
- /*
- * Find end of keys and skip CTRL-Vs (and backslashes) in it.
- * 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
+ // 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
}
- p++;
+ 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;
+}
+
+/// Sets or removes a mapping or abbreviation in buffer `buf`.
+///
+/// @param maptype @see do_map
+/// @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.
+/// @param mode @see do_map
+/// @param is_abbrev @see do_map
+/// @param buf Target Buffer
+int buf_do_map(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 (*p != NUL) {
- *p++ = NUL;
+ if (args->script) {
+ noremap = REMAP_SCRIPT;
}
- p = skipwhite(p);
- rhs = p;
- hasarg = (*rhs != NUL);
- haskey = (*keys != NUL);
+ validate_maphash();
+
+ bool has_lhs = (args->lhs[0] != NUL);
+ bool has_rhs = (args->rhs[0] != NUL) || args->rhs_is_noop;
- /* check for :unmap without argument */
- if (maptype == 1 && !haskey) {
+ // check for :unmap without argument
+ if (maptype == 1 && !has_lhs) {
retval = 1;
goto theend;
}
- // If mapping has been given as ^V<C_UP> say, then replace the term codes
- // with the appropriate two bytes. If it is a shifted special key, unshift
- // it too, giving another two bytes.
- // 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);
- }
- }
+ char_u *lhs = (char_u *)&args->lhs;
+ char_u *rhs = (char_u *)args->rhs;
+ char_u *orig_rhs = args->orig_rhs;
- //
// check arguments and translate function keys
- //
- if (haskey) {
- len = (int)STRLEN(keys);
- if (len > MAXMAPLEN) { /* maximum length of MAXMAPLEN chars */
+ if (has_lhs) {
+ len = (int)args->lhs_len;
+ if (len > MAXMAPLEN) {
retval = 1;
goto theend;
}
- if (abbrev && maptype != 1) {
- /*
- * If an abbreviation ends in a keyword character, the
- * 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
- * vi-compatible way.
- */
+ if (is_abbrev && maptype != 1) {
+ //
+ // If an abbreviation ends in a keyword character, the
+ // 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
+ // vi-compatible way.
+ //
if (has_mbyte) {
int first, last;
int same = -1;
- first = vim_iswordp(keys);
+ first = vim_iswordp(lhs);
last = first;
- p = keys + (*mb_ptr2len)(keys);
+ p = lhs + (*mb_ptr2len)(lhs);
n = 1;
- while (p < keys + len) {
- ++n; /* nr of (multi-byte) chars */
- last = vim_iswordp(p); /* type of last char */
- if (same == -1 && last != first)
- same = n - 1; /* count of same char type */
+ while (p < lhs + len) {
+ n++; // nr of (multi-byte) chars
+ last = vim_iswordp(p); // type of last char
+ if (same == -1 && last != first) {
+ same = n - 1; // count of same char type
+ }
p += (*mb_ptr2len)(p);
}
if (last && n > 2 && same >= 0 && same < n - 1) {
retval = 1;
goto theend;
}
- } else if (vim_iswordc(keys[len - 1])) /* ends in keyword char */
- for (n = 0; n < len - 2; ++n)
- if (vim_iswordc(keys[n]) != vim_iswordc(keys[len - 2])) {
+ } else if (vim_iswordc(lhs[len - 1])) { // ends in keyword char
+ for (n = 0; n < len - 2; n++) {
+ if (vim_iswordc(lhs[n]) != vim_iswordc(lhs[len - 2])) {
retval = 1;
goto theend;
}
- /* An abbreviation cannot contain white space. */
- for (n = 0; n < len; ++n)
- if (ascii_iswhite(keys[n])) {
+ } // for
+ }
+ // An abbreviation cannot contain white space.
+ for (n = 0; n < len; n++) {
+ if (ascii_iswhite(lhs[n])) {
retval = 1;
goto theend;
}
+ } // for
}
}
- if (haskey && hasarg && abbrev) /* if we will add an abbreviation */
- no_abbr = FALSE; /* reset flag that indicates there are
- no abbreviations */
+ if (has_lhs && has_rhs && is_abbrev) { // if we will add an abbreviation,
+ no_abbr = false; // reset flag that indicates there are no abbreviations
+ }
- if (!haskey || (maptype != 1 && !hasarg))
+ if (!has_lhs || (maptype != 1 && !has_rhs)) {
msg_start();
+ }
- /*
- * Check if a new local mapping wasn't already defined globally.
- */
- if (map_table == curbuf->b_maphash && haskey && hasarg && maptype != 1) {
- /* need to loop over all global hash lists */
- for (hash = 0; hash < 256 && !got_int; ++hash) {
- if (abbrev) {
- if (hash != 0) /* there is only one abbreviation list */
+ // 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
+ for (hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash != 0) { // there is only one abbreviation list
break;
+ }
mp = first_abbr;
- } else
+ } else {
mp = maphash[hash];
+ }
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
&& mp->m_keylen == len
- && unique
- && STRNCMP(mp->m_keys, keys, (size_t)len) == 0) {
- if (abbrev)
+ && args->unique
+ && STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) {
+ if (is_abbrev) {
EMSG2(_("E224: global abbreviation already exists for %s"),
- mp->m_keys);
- else
- EMSG2(_("E225: global mapping already exists for %s"),
- mp->m_keys);
+ mp->m_keys);
+ } else {
+ EMSG2(_("E225: global mapping already exists for %s"), mp->m_keys);
+ }
retval = 5;
goto theend;
}
@@ -2789,30 +2837,29 @@ do_map (
}
}
- /*
- * When listing global mappings, also list buffer-local ones here.
- */
- if (map_table != curbuf->b_maphash && !hasarg && maptype != 1) {
- /* need to loop over all global hash lists */
- for (hash = 0; hash < 256 && !got_int; ++hash) {
- if (abbrev) {
- if (hash != 0) /* there is only one abbreviation list */
+ // 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
+ for (hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash != 0) { // there is only one abbreviation list
break;
- mp = curbuf->b_first_abbr;
- } else
- mp = curbuf->b_maphash[hash];
+ }
+ mp = buf->b_first_abbr;
+ } else {
+ mp = buf->b_maphash[hash];
+ }
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 (!haskey) { /* show all entries */
- showmap(mp, TRUE);
- did_local = TRUE;
+ if (!has_lhs) { // show all entries
+ showmap(mp, true);
+ did_local = true;
} else {
n = mp->m_keylen;
- if (STRNCMP(mp->m_keys, keys,
- (size_t)(n < len ? n : len)) == 0) {
- showmap(mp, TRUE);
- did_local = TRUE;
+ if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
+ showmap(mp, true);
+ did_local = true;
}
}
}
@@ -2820,103 +2867,98 @@ do_map (
}
}
- /*
- * 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
- * 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"
- * to be unmapped by typing ":unab foo", where "foo" will be replaced by
- * "bar" because of the abbreviation.
- */
+ // 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
+ // 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"
+ // to be unmapped by typing ":unab foo", where "foo" will be replaced by
+ // "bar" because of the abbreviation.
for (round = 0; (round == 0 || maptype == 1) && round <= 1
- && !did_it && !got_int; ++round) {
- /* need to loop over all hash lists */
- for (hash = 0; hash < 256 && !got_int; ++hash) {
- if (abbrev) {
- if (hash > 0) /* there is only one abbreviation list */
+ && !did_it && !got_int; round++) {
+ // need to loop over all hash lists
+ for (hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash > 0) { // there is only one abbreviation list
break;
+ }
mpp = abbr_table;
- } else
+ } else {
mpp = &(map_table[hash]);
+ }
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);
continue;
}
- if (!haskey) { /* show all entries */
+ if (!has_lhs) { // show all entries
showmap(mp, map_table != maphash);
- did_it = TRUE;
- } else { /* do we have a match? */
- if (round) { /* second round: Try unmap "rhs" string */
+ did_it = true;
+ } else { // do we have a match?
+ if (round) { // second round: Try unmap "rhs" string
n = (int)STRLEN(mp->m_str);
p = mp->m_str;
} else {
n = mp->m_keylen;
p = mp->m_keys;
}
- if (STRNCMP(p, keys, (size_t)(n < len ? n : len)) == 0) {
- if (maptype == 1) { /* delete entry */
- /* Only accept a full match. For abbreviations we
- * ignore trailing space when matching with the
- * "lhs", since an abbreviation can't have
- * trailing space. */
- if (n != len && (!abbrev || round || n > len
- || *skipwhite(keys + n) != NUL)) {
+ if (STRNCMP(p, lhs, (size_t)(n < len ? n : len)) == 0) {
+ if (maptype == 1) { // delete entry
+ // Only accept a full match. For abbreviations we
+ // ignore trailing space when matching with the
+ // "lhs", since an abbreviation can't have
+ // trailing space.
+ if (n != len && (!is_abbrev || round || n > len
+ || *skipwhite(lhs + n) != NUL)) {
mpp = &(mp->m_next);
continue;
}
- /*
- * We reset the indicated mode bits. If nothing is
- * left the entry is deleted below.
- */
+ // We reset the indicated mode bits. If nothing is
+ // left the entry is deleted below.
mp->m_mode &= ~mode;
- did_it = TRUE; /* remember we did something */
- } else if (!hasarg) { /* show matching entry */
+ did_it = true; // remember we did something
+ } else if (!has_rhs) { // show matching entry
showmap(mp, map_table != maphash);
- did_it = TRUE;
- } else if (n != len) { /* new entry is ambiguous */
+ did_it = true;
+ } else if (n != len) { // new entry is ambiguous
mpp = &(mp->m_next);
continue;
- } else if (unique) {
- if (abbrev)
- EMSG2(_("E226: abbreviation already exists for %s"),
- p);
- else
+ } else if (args->unique) {
+ if (is_abbrev) {
+ EMSG2(_("E226: abbreviation already exists for %s"), p);
+ } else {
EMSG2(_("E227: mapping already exists for %s"), p);
+ }
retval = 5;
goto theend;
- } else { /* new rhs for existing entry */
- mp->m_mode &= ~mode; /* remove mode bits */
- if (mp->m_mode == 0 && !did_it) { /* reuse entry */
+ } else { // new rhs for existing entry
+ mp->m_mode &= ~mode; // remove mode bits
+ if (mp->m_mode == 0 && !did_it) { // reuse entry
xfree(mp->m_str);
mp->m_str = vim_strsave(rhs);
xfree(mp->m_orig_str);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_noremap = noremap;
- mp->m_nowait = nowait;
- mp->m_silent = silent;
+ mp->m_nowait = args->nowait;
+ mp->m_silent = args->silent;
mp->m_mode = mode;
- mp->m_expr = expr;
+ mp->m_expr = args->expr;
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);
- 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]);
- if (!abbrev && new_hash != hash) {
+ if (!is_abbrev && new_hash != hash) {
*mpp = mp->m_next;
mp->m_next = map_table[new_hash];
map_table[new_hash] = mp;
- continue; /* continue with *mpp */
+ continue; // continue with *mpp
}
}
}
@@ -2925,13 +2967,13 @@ do_map (
}
}
- if (maptype == 1) { /* delete entry */
+ if (maptype == 1) { // delete entry
if (!did_it) {
- retval = 2; /* no match */
- } else if (*keys == Ctrl_C) {
+ retval = 2; // no match
+ } else if (*lhs == Ctrl_C) {
// If CTRL-C has been unmapped, reuse it for Interrupting.
- if (map_table == curbuf->b_maphash) {
- curbuf->b_mapped_ctrl_c &= ~mode;
+ if (map_table == buf->b_maphash) {
+ buf->b_mapped_ctrl_c &= ~mode;
} else {
mapped_ctrl_c &= ~mode;
}
@@ -2939,48 +2981,46 @@ do_map (
goto theend;
}
- if (!haskey || !hasarg) { /* print entries */
- if (!did_it
- && !did_local
- ) {
- if (abbrev)
+ if (!has_lhs || !has_rhs) { // print entries
+ if (!did_it && !did_local) {
+ if (is_abbrev) {
MSG(_("No abbreviation found"));
- else
+ } else {
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;
+ }
- /*
- * 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));
// If CTRL-C has been mapped, don't always use it for Interrupting.
- if (*keys == Ctrl_C) {
- if (map_table == curbuf->b_maphash) {
- curbuf->b_mapped_ctrl_c |= mode;
+ if (*lhs == Ctrl_C) {
+ if (map_table == buf->b_maphash) {
+ buf->b_mapped_ctrl_c |= mode;
} else {
mapped_ctrl_c |= mode;
}
}
- mp->m_keys = vim_strsave(keys);
+ mp->m_keys = vim_strsave(lhs);
mp->m_str = vim_strsave(rhs);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_keylen = (int)STRLEN(mp->m_keys);
mp->m_noremap = noremap;
- mp->m_nowait = nowait;
- mp->m_silent = silent;
+ mp->m_nowait = args->nowait;
+ mp->m_silent = args->silent;
mp->m_mode = mode;
- mp->m_expr = expr;
+ mp->m_expr = args->expr;
mp->m_script_ID = current_SID;
- /* add the new entry in front of the abbrlist or maphash[] list */
- if (abbrev) {
+ // add the new entry in front of the abbrlist or maphash[] list
+ if (is_abbrev) {
mp->m_next = *abbr_table;
*abbr_table = mp;
} else {
@@ -2990,11 +3030,80 @@ do_map (
}
theend:
- xfree(keys_buf);
- xfree(arg_buf);
return retval;
}
+
+/// 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)
+{
+ 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(maptype, &parsed_args, mode, is_abbrev, curbuf);
+
+free_and_return:
+ xfree(parsed_args.rhs);
+ xfree(parsed_args.orig_rhs);
+ return result;
+}
+
/*
* Delete one entry from the abbrlist or maphash[].
* "mpp" is a pointer to the m_next field of the PREVIOUS entry!