diff options
Diffstat (limited to 'src/map.c')
-rw-r--r-- | src/map.c | 2268 |
1 files changed, 2268 insertions, 0 deletions
diff --git a/src/map.c b/src/map.c new file mode 100644 index 0000000000..9ee64f2482 --- /dev/null +++ b/src/map.c @@ -0,0 +1,2268 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * map.c: functions for maps and abbreviations + */ + +#include "vim.h" + +/* + * List used for abbreviations. + */ +static mapblock_T *first_abbr = NULL; // first entry in abbrlist + +/* + * Each mapping is put in one of the 256 hash lists, to speed up finding it. + */ +static mapblock_T *(maphash[256]); +static int maphash_valid = FALSE; + +/* + * Make a hash value for a mapping. + * "mode" is the lower 4 bits of the State for the mapping. + * "c1" is the first character of the "lhs". + * Returns a value between 0 and 255, index in maphash. + * Put Normal/Visual mode mappings mostly separately from Insert/Cmdline mode. + */ +#define MAP_HASH(mode, c1) (((mode) & (NORMAL + VISUAL + SELECTMODE + OP_PENDING + TERMINAL)) ? (c1) : ((c1) ^ 0x80)) + +/* + * Get the start of the hashed map list for "state" and first character "c". + */ + mapblock_T * +get_maphash_list(int state, int c) +{ + return maphash[MAP_HASH(state, c)]; +} + +/* + * Get the buffer-local hashed map list for "state" and first character "c". + */ + mapblock_T * +get_buf_maphash_list(int state, int c) +{ + return curbuf->b_maphash[MAP_HASH(state, c)]; +} + + int +is_maphash_valid(void) +{ + return maphash_valid; +} + +/* + * Initialize maphash[] for first use. + */ + static void +validate_maphash(void) +{ + if (!maphash_valid) + { + vim_memset(maphash, 0, sizeof(maphash)); + maphash_valid = TRUE; + } +} + +/* + * Delete one entry from the abbrlist or maphash[]. + * "mpp" is a pointer to the m_next field of the PREVIOUS entry! + */ + static void +map_free(mapblock_T **mpp) +{ + mapblock_T *mp; + + mp = *mpp; + vim_free(mp->m_keys); + vim_free(mp->m_str); + vim_free(mp->m_orig_str); + *mpp = mp->m_next; + vim_free(mp); +} + +/* + * Return characters to represent the map mode in an allocated string. + * Returns NULL when out of memory. + */ + static char_u * +map_mode_to_chars(int mode) +{ + garray_T mapmode; + + ga_init2(&mapmode, 1, 7); + + if ((mode & (INSERT + CMDLINE)) == INSERT + CMDLINE) + ga_append(&mapmode, '!'); // :map! + else if (mode & INSERT) + ga_append(&mapmode, 'i'); // :imap + else if (mode & LANGMAP) + ga_append(&mapmode, 'l'); // :lmap + else if (mode & CMDLINE) + ga_append(&mapmode, 'c'); // :cmap + else if ((mode & (NORMAL + VISUAL + SELECTMODE + OP_PENDING)) + == NORMAL + VISUAL + SELECTMODE + OP_PENDING) + ga_append(&mapmode, ' '); // :map + else + { + if (mode & NORMAL) + ga_append(&mapmode, 'n'); // :nmap + if (mode & OP_PENDING) + ga_append(&mapmode, 'o'); // :omap + if (mode & TERMINAL) + ga_append(&mapmode, 't'); // :tmap + if ((mode & (VISUAL + SELECTMODE)) == VISUAL + SELECTMODE) + ga_append(&mapmode, 'v'); // :vmap + else + { + if (mode & VISUAL) + ga_append(&mapmode, 'x'); // :xmap + if (mode & SELECTMODE) + ga_append(&mapmode, 's'); // :smap + } + } + + ga_append(&mapmode, NUL); + return (char_u *)mapmode.ga_data; +} + + static void +showmap( + mapblock_T *mp, + int local) // TRUE for buffer-local map +{ + int len = 1; + char_u *mapchars; + + if (message_filtered(mp->m_keys) && message_filtered(mp->m_str)) + return; + + if (msg_didout || msg_silent != 0) + { + msg_putchar('\n'); + if (got_int) // 'q' typed at MORE prompt + return; + } + + mapchars = map_mode_to_chars(mp->m_mode); + if (mapchars != NULL) + { + msg_puts((char *)mapchars); + len = (int)STRLEN(mapchars); + vim_free(mapchars); + } + + while (++len <= 3) + msg_putchar(' '); + + // Display the LHS. Get length of what we write. + len = msg_outtrans_special(mp->m_keys, TRUE, 0); + do + { + msg_putchar(' '); // padd with blanks + ++len; + } while (len < 12); + + if (mp->m_noremap == REMAP_NONE) + msg_puts_attr("*", HL_ATTR(HLF_8)); + else if (mp->m_noremap == REMAP_SCRIPT) + msg_puts_attr("&", HL_ATTR(HLF_8)); + else + msg_putchar(' '); + + if (local) + msg_putchar('@'); + else + msg_putchar(' '); + + // Use FALSE below if we only want things like <Up> to show up as such on + // the rhs, and not M-x etc, TRUE gets both -- webb + if (*mp->m_str == NUL) + msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); + else + { + // Remove escaping of CSI, because "m_str" is in a format to be used + // as typeahead. + char_u *s = vim_strsave(mp->m_str); + if (s != NULL) + { + vim_unescape_csi(s); + msg_outtrans_special(s, FALSE, 0); + vim_free(s); + } + } +#ifdef FEAT_EVAL + if (p_verbose > 0) + last_set_msg(mp->m_script_ctx); +#endif + out_flush(); // show one line at a time +} + +/* + * 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 TERMINAL + * + * 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 + * 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; + mapblock_T *mp, **mpp; + char_u *rhs; + char_u *p; + int n; + int len = 0; // init for GCC + char_u *newstr; + 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; + int unique = FALSE; + int nowait = FALSE; + int silent = FALSE; + int special = FALSE; +#ifdef FEAT_EVAL + int expr = FALSE; +#endif + int noremap; + char_u *orig_rhs; + + keys = arg; + 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; + + // 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; + continue; + } + + // Check for "<nowait>": don't wait for more characters. + if (STRNCMP(keys, "<nowait>", 8) == 0) + { + keys = skipwhite(keys + 8); + nowait = TRUE; + continue; + } + + // Check for "<silent>": don't echo commands. + if (STRNCMP(keys, "<silent>", 8) == 0) + { + keys = skipwhite(keys + 8); + silent = TRUE; + continue; + } + + // Check for "<special>": accept special keys in <> + if (STRNCMP(keys, "<special>", 9) == 0) + { + keys = skipwhite(keys + 9); + special = TRUE; + continue; + } + +#ifdef FEAT_EVAL + // Check for "<script>": remap script-local mappings only + if (STRNCMP(keys, "<script>", 8) == 0) + { + keys = skipwhite(keys + 8); + noremap = REMAP_SCRIPT; + continue; + } + + // Check for "<expr>": {rhs} is an expression. + if (STRNCMP(keys, "<expr>", 6) == 0) + { + keys = skipwhite(keys + 6); + expr = TRUE; + continue; + } +#endif + // Check for "<unique>": don't overwrite an existing mapping. + if (STRNCMP(keys, "<unique>", 8) == 0) + { + keys = skipwhite(keys + 8); + 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 || !VIM_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); + rhs = p; + hasarg = (*rhs != NUL); + haskey = (*keys != NUL); + + // check for :unmap without argument + if (maptype == 1 && !haskey) + { + 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, &keys_buf, TRUE, TRUE, special); + orig_rhs = rhs; + if (hasarg) + { + if (STRICMP(rhs, "<nop>") == 0) // "<Nop>" means nothing + rhs = (char_u *)""; + else + rhs = replace_termcodes(rhs, &arg_buf, FALSE, TRUE, special); + } + + // check arguments and translate function keys + if (haskey) + { + len = (int)STRLEN(keys); + if (len > MAXMAPLEN) // maximum length of MAXMAPLEN chars + { + 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 (has_mbyte) + { + int first, last; + int same = -1; + + first = vim_iswordp(keys); + last = first; + p = keys + (*mb_ptr2len)(keys); + 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 + 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])) + { + retval = 1; + goto theend; + } + // An abbreviation cannot contain white space. + for (n = 0; n < len; ++n) + if (VIM_ISWHITE(keys[n])) + { + retval = 1; + goto theend; + } + } + } + + if (haskey && hasarg && abbrev) // if we will add an abbreviation + no_abbr = FALSE; // reset flag that indicates there are + // no abbreviations + + if (!haskey || (maptype != 1 && !hasarg)) + 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 + break; + mp = first_abbr; + } + else + mp = maphash[hash]; + for ( ; mp != NULL && !got_int; mp = mp->m_next) + { + // 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) + semsg(_("E224: global abbreviation already exists for %s"), + mp->m_keys); + else + semsg(_("E225: global mapping already exists for %s"), + mp->m_keys); + retval = 5; + goto theend; + } + } + } + } + + // 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 + break; + mp = curbuf->b_first_abbr; + } + else + mp = curbuf->b_maphash[hash]; + for ( ; mp != NULL && !got_int; mp = mp->m_next) + { + // check entries with the same mode + if ((mp->m_mode & mode) != 0) + { + if (!haskey) // 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; + } + } + } + } + } + } + + // 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 + break; + mpp = abbr_table; + } + else + mpp = &(map_table[hash]); + for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) + { + + if (!(mp->m_mode & mode)) // skip entries with wrong mode + { + mpp = &(mp->m_next); + continue; + } + if (!haskey) // 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 + { + 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)) + { + mpp = &(mp->m_next); + continue; + } + // 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 + { + showmap(mp, map_table != maphash); + did_it = TRUE; + } + else if (n != len) // new entry is ambiguous + { + mpp = &(mp->m_next); + continue; + } + else if (unique) + { + if (abbrev) + semsg(_("E226: abbreviation already exists for %s"), + p); + else + semsg(_("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 + { + newstr = vim_strsave(rhs); + if (newstr == NULL) + { + retval = 4; // no mem + goto theend; + } + vim_free(mp->m_str); + mp->m_str = newstr; + vim_free(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_mode = mode; +#ifdef FEAT_EVAL + mp->m_expr = expr; + mp->m_script_ctx = current_sctx; + mp->m_script_ctx.sc_lnum += sourcing_lnum; +#endif + did_it = TRUE; + } + } + if (mp->m_mode == 0) // entry can be deleted + { + map_free(mpp); + continue; // continue with *mpp + } + + // 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) + { + *mpp = mp->m_next; + mp->m_next = map_table[new_hash]; + map_table[new_hash] = mp; + + continue; // continue with *mpp + } + } + } + mpp = &(mp->m_next); + } + } + } + + if (maptype == 1) // delete entry + { + if (!did_it) + retval = 2; // no match + else if (*keys == Ctrl_C) + { + // If CTRL-C has been unmapped, reuse it for Interrupting. + if (map_table == curbuf->b_maphash) + curbuf->b_mapped_ctrl_c &= ~mode; + else + mapped_ctrl_c &= ~mode; + } + goto theend; + } + + if (!haskey || !hasarg) // print entries + { + if (!did_it && !did_local) + { + if (abbrev) + msg(_("No abbreviation found")); + else + msg(_("No mapping found")); + } + goto theend; // listing finished + } + + if (did_it) // have added the new entry already + goto theend; + + // Get here when adding a new entry to the maphash[] list or abbrlist. + mp = ALLOC_ONE(mapblock_T); + if (mp == NULL) + { + retval = 4; // no mem + goto theend; + } + + // 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; + else + mapped_ctrl_c |= mode; + } + + mp->m_keys = vim_strsave(keys); + mp->m_str = vim_strsave(rhs); + mp->m_orig_str = vim_strsave(orig_rhs); + if (mp->m_keys == NULL || mp->m_str == NULL) + { + vim_free(mp->m_keys); + vim_free(mp->m_str); + vim_free(mp->m_orig_str); + vim_free(mp); + retval = 4; // no mem + goto theend; + } + mp->m_keylen = (int)STRLEN(mp->m_keys); + mp->m_noremap = noremap; + mp->m_nowait = nowait; + mp->m_silent = silent; + mp->m_mode = mode; +#ifdef FEAT_EVAL + mp->m_expr = expr; + mp->m_script_ctx = current_sctx; + mp->m_script_ctx.sc_lnum += sourcing_lnum; +#endif + + // add the new entry in front of the abbrlist or maphash[] list + if (abbrev) + { + mp->m_next = *abbr_table; + *abbr_table = mp; + } + else + { + n = MAP_HASH(mp->m_mode, mp->m_keys[0]); + mp->m_next = map_table[n]; + map_table[n] = mp; + } + +theend: + vim_free(keys_buf); + vim_free(arg_buf); + return retval; +} + +/* + * Get the mapping mode from the command name. + */ + static int +get_map_mode(char_u **cmdp, int forceit) +{ + char_u *p; + int modec; + int mode; + + p = *cmdp; + modec = *p++; + if (modec == 'i') + mode = INSERT; // :imap + else if (modec == 'l') + mode = LANGMAP; // :lmap + else if (modec == 'c') + mode = CMDLINE; // :cmap + else if (modec == 'n' && *p != 'o') // avoid :noremap + mode = NORMAL; // :nmap + else if (modec == 'v') + mode = VISUAL + SELECTMODE; // :vmap + else if (modec == 'x') + mode = VISUAL; // :xmap + else if (modec == 's') + mode = SELECTMODE; // :smap + else if (modec == 'o') + mode = OP_PENDING; // :omap + else if (modec == 't') + mode = TERMINAL; // :tmap + else + { + --p; + if (forceit) + mode = INSERT + CMDLINE; // :map ! + else + mode = VISUAL + SELECTMODE + NORMAL + OP_PENDING;// :map + } + + *cmdp = p; + return mode; +} + +/* + * Clear all mappings or abbreviations. + * 'abbr' should be FALSE for mappings, TRUE for abbreviations. + */ + static void +map_clear( + char_u *cmdp, + char_u *arg UNUSED, + int forceit, + int abbr) +{ + int mode; + int local; + + local = (STRCMP(arg, "<buffer>") == 0); + if (!local && *arg != NUL) + { + emsg(_(e_invarg)); + return; + } + + mode = get_map_mode(&cmdp, forceit); + map_clear_int(curbuf, mode, local, abbr); +} + +/* + * Clear all mappings in "mode". + */ + void +map_clear_int( + buf_T *buf, // buffer for local mappings + int mode, // mode in which to delete + int local, // TRUE for buffer-local mappings + int abbr) // TRUE for abbreviations +{ + mapblock_T *mp, **mpp; + int hash; + int new_hash; + + validate_maphash(); + + for (hash = 0; hash < 256; ++hash) + { + if (abbr) + { + if (hash > 0) // there is only one abbrlist + break; + if (local) + mpp = &buf->b_first_abbr; + else + mpp = &first_abbr; + } + else + { + if (local) + mpp = &buf->b_maphash[hash]; + else + mpp = &maphash[hash]; + } + while (*mpp != NULL) + { + mp = *mpp; + if (mp->m_mode & mode) + { + mp->m_mode &= ~mode; + if (mp->m_mode == 0) // entry can be deleted + { + map_free(mpp); + continue; + } + // May need to put this entry into another hash list. + new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]); + if (!abbr && new_hash != hash) + { + *mpp = mp->m_next; + if (local) + { + mp->m_next = buf->b_maphash[new_hash]; + buf->b_maphash[new_hash] = mp; + } + else + { + mp->m_next = maphash[new_hash]; + maphash[new_hash] = mp; + } + continue; // continue with *mpp + } + } + mpp = &(mp->m_next); + } + } +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Return TRUE if a map exists that has "str" in the rhs for mode "modechars". + * Recognize termcap codes in "str". + * Also checks mappings local to the current buffer. + */ + int +map_to_exists(char_u *str, char_u *modechars, int abbr) +{ + int mode = 0; + char_u *rhs; + char_u *buf; + int retval; + + rhs = replace_termcodes(str, &buf, FALSE, TRUE, FALSE); + + if (vim_strchr(modechars, 'n') != NULL) + mode |= NORMAL; + if (vim_strchr(modechars, 'v') != NULL) + mode |= VISUAL + SELECTMODE; + if (vim_strchr(modechars, 'x') != NULL) + mode |= VISUAL; + if (vim_strchr(modechars, 's') != NULL) + mode |= SELECTMODE; + if (vim_strchr(modechars, 'o') != NULL) + mode |= OP_PENDING; + if (vim_strchr(modechars, 'i') != NULL) + mode |= INSERT; + if (vim_strchr(modechars, 'l') != NULL) + mode |= LANGMAP; + if (vim_strchr(modechars, 'c') != NULL) + mode |= CMDLINE; + + retval = map_to_exists_mode(rhs, mode, abbr); + vim_free(buf); + + return retval; +} +#endif + +/* + * Return TRUE if a map exists that has "str" in the rhs for mode "mode". + * Also checks mappings local to the current buffer. + */ + int +map_to_exists_mode(char_u *rhs, int mode, int abbr) +{ + mapblock_T *mp; + int hash; + int exp_buffer = FALSE; + + validate_maphash(); + + // Do it twice: once for global maps and once for local maps. + for (;;) + { + for (hash = 0; hash < 256; ++hash) + { + if (abbr) + { + if (hash > 0) // there is only one abbr list + break; + if (exp_buffer) + mp = curbuf->b_first_abbr; + else + mp = first_abbr; + } + else if (exp_buffer) + mp = curbuf->b_maphash[hash]; + else + mp = maphash[hash]; + for (; mp; mp = mp->m_next) + { + if ((mp->m_mode & mode) + && strstr((char *)mp->m_str, (char *)rhs) != NULL) + return TRUE; + } + } + if (exp_buffer) + break; + exp_buffer = TRUE; + } + + return FALSE; +} + +#if defined(FEAT_CMDL_COMPL) || defined(PROTO) +/* + * Used below when expanding mapping/abbreviation names. + */ +static int expand_mapmodes = 0; +static int expand_isabbrev = 0; +static int expand_buffer = FALSE; + +/* + * Work out what to complete when doing command line completion of mapping + * or abbreviation names. + */ + char_u * +set_context_in_map_cmd( + expand_T *xp, + char_u *cmd, + char_u *arg, + int forceit, // TRUE if '!' given + int isabbrev, // TRUE if abbreviation + int isunmap, // TRUE if unmap/unabbrev command + cmdidx_T cmdidx) +{ + if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) + xp->xp_context = EXPAND_NOTHING; + else + { + if (isunmap) + expand_mapmodes = get_map_mode(&cmd, forceit || isabbrev); + else + { + expand_mapmodes = INSERT + CMDLINE; + if (!isabbrev) + expand_mapmodes += VISUAL + SELECTMODE + NORMAL + OP_PENDING; + } + expand_isabbrev = isabbrev; + xp->xp_context = EXPAND_MAPPINGS; + expand_buffer = FALSE; + for (;;) + { + if (STRNCMP(arg, "<buffer>", 8) == 0) + { + expand_buffer = TRUE; + arg = skipwhite(arg + 8); + continue; + } + if (STRNCMP(arg, "<unique>", 8) == 0) + { + arg = skipwhite(arg + 8); + continue; + } + if (STRNCMP(arg, "<nowait>", 8) == 0) + { + arg = skipwhite(arg + 8); + continue; + } + if (STRNCMP(arg, "<silent>", 8) == 0) + { + arg = skipwhite(arg + 8); + continue; + } + if (STRNCMP(arg, "<special>", 9) == 0) + { + arg = skipwhite(arg + 9); + continue; + } +#ifdef FEAT_EVAL + if (STRNCMP(arg, "<script>", 8) == 0) + { + arg = skipwhite(arg + 8); + continue; + } + if (STRNCMP(arg, "<expr>", 6) == 0) + { + arg = skipwhite(arg + 6); + continue; + } +#endif + break; + } + xp->xp_pattern = arg; + } + + return NULL; +} + +/* + * Find all mapping/abbreviation names that match regexp "regmatch"'. + * For command line expansion of ":[un]map" and ":[un]abbrev" in all modes. + * Return OK if matches found, FAIL otherwise. + */ + int +ExpandMappings( + regmatch_T *regmatch, + int *num_file, + char_u ***file) +{ + mapblock_T *mp; + int hash; + int count; + int round; + char_u *p; + int i; + + validate_maphash(); + + *num_file = 0; // return values in case of FAIL + *file = NULL; + + // round == 1: Count the matches. + // round == 2: Build the array to keep the matches. + for (round = 1; round <= 2; ++round) + { + count = 0; + + for (i = 0; i < 7; ++i) + { + if (i == 0) + p = (char_u *)"<silent>"; + else if (i == 1) + p = (char_u *)"<unique>"; +#ifdef FEAT_EVAL + else if (i == 2) + p = (char_u *)"<script>"; + else if (i == 3) + p = (char_u *)"<expr>"; +#endif + else if (i == 4 && !expand_buffer) + p = (char_u *)"<buffer>"; + else if (i == 5) + p = (char_u *)"<nowait>"; + else if (i == 6) + p = (char_u *)"<special>"; + else + continue; + + if (vim_regexec(regmatch, p, (colnr_T)0)) + { + if (round == 1) + ++count; + else + (*file)[count++] = vim_strsave(p); + } + } + + for (hash = 0; hash < 256; ++hash) + { + if (expand_isabbrev) + { + if (hash > 0) // only one abbrev list + break; // for (hash) + mp = first_abbr; + } + else if (expand_buffer) + mp = curbuf->b_maphash[hash]; + else + mp = maphash[hash]; + for (; mp; mp = mp->m_next) + { + if (mp->m_mode & expand_mapmodes) + { + p = translate_mapping(mp->m_keys); + if (p != NULL && vim_regexec(regmatch, p, (colnr_T)0)) + { + if (round == 1) + ++count; + else + { + (*file)[count++] = p; + p = NULL; + } + } + vim_free(p); + } + } // for (mp) + } // for (hash) + + if (count == 0) // no match found + break; // for (round) + + if (round == 1) + { + *file = ALLOC_MULT(char_u *, count); + if (*file == NULL) + return FAIL; + } + } // for (round) + + if (count > 1) + { + char_u **ptr1; + char_u **ptr2; + char_u **ptr3; + + // Sort the matches + sort_strings(*file, count); + + // Remove multiple entries + ptr1 = *file; + ptr2 = ptr1 + 1; + ptr3 = ptr1 + count; + + while (ptr2 < ptr3) + { + if (STRCMP(*ptr1, *ptr2)) + *++ptr1 = *ptr2++; + else + { + vim_free(*ptr2++); + count--; + } + } + } + + *num_file = count; + return (count == 0 ? FAIL : OK); +} +#endif // FEAT_CMDL_COMPL + +/* + * Check for an abbreviation. + * Cursor is at ptr[col]. + * When inserting, mincol is where insert started. + * For the command line, mincol is what is to be skipped over. + * "c" is the character typed before check_abbr was called. It may have + * ABBR_OFF added to avoid prepending a CTRL-V to it. + * + * Historic vi practice: The last character of an abbreviation must be an id + * character ([a-zA-Z0-9_]). The characters in front of it must be all id + * characters or all non-id characters. This allows for abbr. "#i" to + * "#include". + * + * Vim addition: Allow for abbreviations that end in a non-keyword character. + * Then there must be white space before the abbr. + * + * return TRUE if there is an abbreviation, FALSE if not + */ + int +check_abbr( + int c, + char_u *ptr, + int col, + int mincol) +{ + int len; + int scol; // starting column of the abbr. + int j; + char_u *s; + char_u tb[MB_MAXBYTES + 4]; + mapblock_T *mp; + mapblock_T *mp2; + int clen = 0; // length in characters + int is_id = TRUE; + int vim_abbr; + + if (typebuf.tb_no_abbr_cnt) // abbrev. are not recursive + return FALSE; + + // no remapping implies no abbreviation, except for CTRL-] + if (noremap_keys() && c != Ctrl_RSB) + return FALSE; + + // Check for word before the cursor: If it ends in a keyword char all + // chars before it must be keyword chars or non-keyword chars, but not + // white space. If it ends in a non-keyword char we accept any characters + // before it except white space. + if (col == 0) // cannot be an abbr. + return FALSE; + + if (has_mbyte) + { + char_u *p; + + p = mb_prevptr(ptr, ptr + col); + if (!vim_iswordp(p)) + vim_abbr = TRUE; // Vim added abbr. + else + { + vim_abbr = FALSE; // vi compatible abbr. + if (p > ptr) + is_id = vim_iswordp(mb_prevptr(ptr, p)); + } + clen = 1; + while (p > ptr + mincol) + { + p = mb_prevptr(ptr, p); + if (vim_isspace(*p) || (!vim_abbr && is_id != vim_iswordp(p))) + { + p += (*mb_ptr2len)(p); + break; + } + ++clen; + } + scol = (int)(p - ptr); + } + else < |