/* 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: Code for mappings 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; // When non-zero then no mappings can be added or removed. Prevents mappings // to change while listing them. static int map_locked = 0; /* * 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) & (MODE_NORMAL | MODE_VISUAL | MODE_SELECT | MODE_OP_PENDING | MODE_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) return; CLEAR_FIELD(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; #ifdef FEAT_EVAL reset_last_used_map(mp); #endif 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 & (MODE_INSERT | MODE_CMDLINE)) == (MODE_INSERT | MODE_CMDLINE)) ga_append(&mapmode, '!'); // :map! else if (mode & MODE_INSERT) ga_append(&mapmode, 'i'); // :imap else if (mode & MODE_LANGMAP) ga_append(&mapmode, 'l'); // :lmap else if (mode & MODE_CMDLINE) ga_append(&mapmode, 'c'); // :cmap else if ((mode & (MODE_NORMAL | MODE_VISUAL | MODE_SELECT | MODE_OP_PENDING)) == (MODE_NORMAL | MODE_VISUAL | MODE_SELECT | MODE_OP_PENDING)) ga_append(&mapmode, ' '); // :map else { if (mode & MODE_NORMAL) ga_append(&mapmode, 'n'); // :nmap if (mode & MODE_OP_PENDING) ga_append(&mapmode, 'o'); // :omap if (mode & MODE_TERMINAL) ga_append(&mapmode, 't'); // :tmap if ((mode & (MODE_VISUAL | MODE_SELECT)) == (MODE_VISUAL | MODE_SELECT)) ga_append(&mapmode, 'v'); // :vmap else { if (mode & MODE_VISUAL) ga_append(&mapmode, 'x'); // :xmap if (mode & MODE_SELECT) ga_append(&mapmode, 's'); // :smap } } ga_append(&mapmode, NUL); return (char_u *)mapmode.ga_data; } /* * Output a line for one mapping. */ 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; // Prevent mappings to be cleared while at the more prompt. // Must jump to "theend" instead of returning. ++map_locked; if (msg_didout || msg_silent != 0) { msg_putchar('\n'); if (got_int) // 'q' typed at MORE prompt goto theend; } 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(' '); // pad 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 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("", HL_ATTR(HLF_8)); else msg_outtrans_special(mp->m_str, FALSE, 0); #ifdef FEAT_EVAL if (p_verbose > 0) last_set_msg(mp->m_script_ctx); #endif msg_clr_eos(); out_flush(); // show one line at a time theend: --map_locked; } static int map_add( mapblock_T **map_table, mapblock_T **abbr_table, char_u *keys, char_u *rhs, char_u *orig_rhs, int noremap, int nowait, int silent, int mode, int is_abbr, #ifdef FEAT_EVAL int expr, scid_T sid, // -1 to use current_sctx int scriptversion, linenr_T lnum, #endif int simplified) { mapblock_T *mp = ALLOC_CLEAR_ONE(mapblock_T); if (mp == NULL) return FAIL; // 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); return FAIL; } mp->m_keylen = (int)STRLEN(mp->m_keys); mp->m_noremap = noremap; mp->m_nowait = nowait; mp->m_silent = silent; mp->m_mode = mode; mp->m_simplified = simplified; #ifdef FEAT_EVAL mp->m_expr = expr; if (sid > 0) { mp->m_script_ctx.sc_sid = sid; mp->m_script_ctx.sc_lnum = lnum; mp->m_script_ctx.sc_version = scriptversion; } else { 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 (is_abbr) { mp->m_next = *abbr_table; *abbr_table = mp; } else { int n = MAP_HASH(mp->m_mode, mp->m_keys[0]); mp->m_next = map_table[n]; map_table[n] = mp; } return OK; } /* * List mappings. When "haskey" is FALSE all mappings, otherwise mappings that * match "keys[keys_len]". */ static void list_mappings( int keyround, int abbrev, int haskey, char_u *keys, int keys_len, int mode, int *did_local) { // Prevent mappings to be cleared while at the more prompt. ++map_locked; if (p_verbose > 0 && keyround == 1) { if (seenModifyOtherKeys) msg_puts(_("Seen modifyOtherKeys: true\n")); if (modify_otherkeys_state != MOKS_INITIAL) { char *name = _("Unknown"); switch (modify_otherkeys_state) { case MOKS_INITIAL: break; case MOKS_OFF: name = _("Off"); break; case MOKS_ENABLED: name = _("On"); break; case MOKS_DISABLED: name = _("Disabled"); break; case MOKS_AFTER_T_TE: name = _("Cleared"); break; } char buf[200]; vim_snprintf(buf, sizeof(buf), _("modifyOtherKeys detected: %s\n"), name); msg_puts(buf); } if (kitty_protocol_state != KKPS_INITIAL) { char *name = _("Unknown"); switch (kitty_protocol_state) { case KKPS_INITIAL: break; case KKPS_OFF: name = _("Off"); break; case KKPS_ENABLED: name = _("On"); break; case KKPS_DISABLED: name = _("Disabled"); break; case KKPS_AFTER_T_TE: name = _("Cleared"); break; } char buf[200]; vim_snprintf(buf, sizeof(buf), _("Kitty keyboard protocol: %s\n"), name); msg_puts(buf); } } // need to loop over all global hash lists for (int hash = 0; hash < 256 && !got_int; ++hash) { mapblock_T *mp; 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_simplified && (mp->m_mode & mode) != 0) { if (!haskey) // show all entries { showmap(mp, TRUE); *did_local = TRUE; } else { int n = mp->m_keylen; if (STRNCMP(mp->m_keys, keys, (size_t)(n < keys_len ? n : keys_len)) == 0) { showmap(mp, TRUE); *did_local = TRUE; } } } } } --map_locked; } /* * 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: MAPTYPE_MAP for :map * MAPTYPE_UNMAP for :unmap * MAPTYPE_NOREMAP for noremap * * arg is pointer to any arguments. Note: arg cannot be a read-only string, * it will be modified. * * for :map mode is MODE_NORMAL | MODE_VISUAL | MODE_SELECT | MODE_OP_PENDING * for :map! mode is MODE_INSERT | MODE_CMDLINE * for :cmap mode is MODE_CMDLINE * for :imap mode is MODE_INSERT * for :lmap mode is MODE_LANGMAP * for :nmap mode is MODE_NORMAL * for :vmap mode is MODE_VISUAL | MODE_SELECT * for :xmap mode is MODE_VISUAL * for :smap mode is MODE_SELECT * for :omap mode is MODE_OP_PENDING * for :tmap mode is MODE_TERMINAL * * for :abbr mode is MODE_INSERT | MODE_CMDLINE * for :iabbr mode is MODE_INSERT * for :cabbr mode is MODE_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 int hasarg; int haskey; int do_print; int keyround; char_u *keys_buf = NULL; char_u *alt_keys_buf = NULL; char_u *arg_buf = NULL; int retval = 0; int do_backslash; 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 did_simplify = FALSE; 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 == MAPTYPE_NOREMAP) noremap = REMAP_NONE; else noremap = REMAP_YES; // Accept , , ,