// This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // mapping.c: Code for mappings and abbreviations. #include #include #include #include #include #include #include #include #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_session.h" #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/highlight_defs.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option_defs.h" #include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/vim.h" /// List used for abbreviations. static mapblock_T *first_abbr = NULL; // first entry in abbrlist // Each mapping is put in one of the MAX_MAPHASH hash lists, // to speed up finding it. static mapblock_T *(maphash[MAX_MAPHASH]) = { 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)) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mapping.c.generated.h" #endif /// 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)]; } /// Retrieve the mapblock at the index either globally or for a certain buffer /// /// @param index The index in the maphash[] /// @param buf The buffer to get the maphash from. NULL for global mapblock_T *get_maphash(int index, buf_T *buf) FUNC_ATTR_PURE { if (index >= MAX_MAPHASH) { return NULL; } return (buf == NULL) ? maphash[index] : buf->b_maphash[index]; } /// Delete one entry from the abbrlist or maphash[]. /// "mpp" is a pointer to the m_next field of the PREVIOUS entry! static void mapblock_free(mapblock_T **mpp) { mapblock_T *mp; mp = *mpp; xfree(mp->m_keys); if (!mp->m_simplified) { NLUA_CLEAR_REF(mp->m_luaref); xfree(mp->m_str); xfree(mp->m_orig_str); } xfree(mp->m_desc); *mpp = mp->m_next; xfree(mp); } /// Return characters to represent the map mode in an allocated string /// /// @return [allocated] NUL-terminated string with characters. static char *map_mode_to_chars(int mode) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET { garray_T mapmode; ga_init(&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 *)mapmode.ga_data; } /// @param local true for buffer-local map static void showmap(mapblock_T *mp, bool local) { size_t len = 1; if (message_filtered(mp->m_keys) && message_filtered(mp->m_str) && (mp->m_desc == NULL || message_filtered(mp->m_desc))) { return; } if (msg_didout || msg_silent != 0) { msg_putchar('\n'); if (got_int) { // 'q' typed at MORE prompt return; } } { char *const mapchars = map_mode_to_chars(mp->m_mode); msg_puts(mapchars); len = strlen(mapchars); xfree(mapchars); } while (++len <= 3) { msg_putchar(' '); } // Display the LHS. Get length of what we write. len = (size_t)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_luaref != LUA_NOREF) { char *str = nlua_funcref_str(mp->m_luaref); msg_puts_attr(str, HL_ATTR(HLF_8)); xfree(str); } else if (mp->m_str[0] == NUL) { msg_puts_attr("", HL_ATTR(HLF_8)); } else { msg_outtrans_special(mp->m_str, false, 0); } if (mp->m_desc != NULL) { msg_puts("\n "); // Shift line to same level as rhs. msg_puts(mp->m_desc); } if (p_verbose > 0) { last_set_msg(mp->m_script_ctx); } msg_clr_eos(); } /// 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 should be , `rhs` will be an 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] rhs_lua Lua reference for Lua mappings. /// @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. static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs_len, const char *const orig_rhs, const size_t orig_rhs_len, const LuaRef rhs_lua, const int cpo_flags, MapArguments *const mapargs) { char lhs_buf[128]; // If mapping has been given as ^V 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. // If something like is simplified to 0x08 then mark it as simplified. bool did_simplify = false; const int flags = REPTERM_FROM_PART | REPTERM_DO_LT; char *bufarg = lhs_buf; char *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags, &did_simplify, cpo_flags); if (replaced == NULL) { return false; } mapargs->lhs_len = strlen(replaced); xstrlcpy(mapargs->lhs, replaced, sizeof(mapargs->lhs)); if (did_simplify) { replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags | REPTERM_NO_SIMPLIFY, NULL, cpo_flags); if (replaced == NULL) { return false; } mapargs->alt_lhs_len = strlen(replaced); xstrlcpy(mapargs->alt_lhs, replaced, sizeof(mapargs->alt_lhs)); } else { mapargs->alt_lhs_len = 0; } set_maparg_rhs(orig_rhs, orig_rhs_len, rhs_lua, cpo_flags, mapargs); return true; } /// @see set_maparg_lhs_rhs static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len, const LuaRef rhs_lua, const int cpo_flags, MapArguments *const mapargs) { mapargs->rhs_lua = rhs_lua; if (rhs_lua == LUA_NOREF) { mapargs->orig_rhs_len = orig_rhs_len; mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char)); xstrlcpy(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1); if (STRICMP(orig_rhs, "") == 0) { // "" means nothing mapargs->rhs = xcalloc(1, sizeof(char)); // single NUL-char mapargs->rhs_len = 0; mapargs->rhs_is_noop = true; } else { char *rhs_buf = NULL; char *replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL, cpo_flags); mapargs->rhs_len = strlen(replaced); // NB: replace_termcodes may produce an empty string even if orig_rhs is non-empty // (e.g. a single ^V, see :h map-empty-rhs) mapargs->rhs_is_noop = orig_rhs_len != 0 && mapargs->rhs_len == 0; mapargs->rhs = replaced; } } else { char tmp_buf[64]; // orig_rhs is not used for Lua mappings, but still needs to be a string. mapargs->orig_rhs = xcalloc(1, sizeof(char)); mapargs->orig_rhs_len = 0; // stores ref_no in map_str mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL, (char_u)KS_EXTRA, KE_LUA, rhs_lua); mapargs->rhs = xstrdup(tmp_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. " ". /// 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 "", 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. static int str_to_mapargs(const char *strargs, bool is_unmap, MapArguments *mapargs) { const char *to_parse = strargs; to_parse = skipwhite(to_parse); CLEAR_POINTER(mapargs); // Accept , , , ,