Mercurial > vim
view src/map.c @ 32936:c517845bd10e v9.0.1776
patch 9.0.1776: No support for stable Python 3 ABI
Commit: https://github.com/vim/vim/commit/c13b3d1350b60b94fe87f0761ea31c0e7fb6ebf3
Author: Yee Cheng Chin <ychin.git@gmail.com>
Date: Sun Aug 20 21:18:38 2023 +0200
patch 9.0.1776: No support for stable Python 3 ABI
Problem: No support for stable Python 3 ABI
Solution: Support Python 3 stable ABI
Commits:
1) Support Python 3 stable ABI to allow mixed version interoperatbility
Vim currently supports embedding Python for use with plugins, and the
"dynamic" linking option allows the user to specify a locally installed
version of Python by setting `pythonthreedll`. However, one caveat is
that the Python 3 libs are not binary compatible across minor versions,
and mixing versions can potentially be dangerous (e.g. let's say Vim was
linked against the Python 3.10 SDK, but the user sets `pythonthreedll`
to a 3.11 lib). Usually, nothing bad happens, but in theory this could
lead to crashes, memory corruption, and other unpredictable behaviors.
It's also difficult for the user to tell something is wrong because Vim
has no way of reporting what Python 3 version Vim was linked with.
For Vim installed via a package manager, this usually isn't an issue
because all the dependencies would already be figured out. For prebuilt
Vim binaries like MacVim (my motivation for working on this), AppImage,
and Win32 installer this could potentially be an issue as usually a
single binary is distributed. This is more tricky when a new Python
version is released, as there's a chicken-and-egg issue with deciding
what Python version to build against and hard to keep in sync when a new
Python version just drops and we have a mix of users of different Python
versions, and a user just blindly upgrading to a new Python could lead to
bad interactions with Vim.
Python 3 does have a solution for this problem: stable ABI / limited API
(see https://docs.python.org/3/c-api/stable.html). The C SDK limits the
API to a set of functions that are promised to be stable across
versions. This pull request adds an ifdef config that allows us to turn
it on when building Vim. Vim binaries built with this option should be
safe to freely link with any Python 3 libraies without having the
constraint of having to use the same minor version.
Note: Python 2 has no such concept and this doesn't change how Python 2
integration works (not that there is going to be a new version of Python
2 that would cause compatibility issues in the future anyway).
---
Technical details:
======
The stable ABI can be accessed when we compile with the Python 3 limited
API (by defining `Py_LIMITED_API`). The Python 3 code (in `if_python3.c`
and `if_py_both.h`) would now handle this and switch to limited API
mode. Without it set, Vim will still use the full API as before so this
is an opt-in change.
The main difference is that `PyType_Object` is now an opaque struct that
we can't directly create "static types" out of, and we have to create
type objects as "heap types" instead. This is because the struct is not
stable and changes from version to version (e.g. 3.8 added a
`tp_vectorcall` field to it). I had to change all the types to be
allocated on the heap instead with just a pointer to them.
Other functions are also simply missing in limited API, or they are
introduced too late (e.g. `PyUnicode_AsUTF8AndSize` in 3.10) to it that
we need some other ways to do the same thing, so I had to abstract a few
things into macros, and sometimes re-implement functions like
`PyObject_NEW`.
One caveat is that in limited API, `OutputType` (used for replacing
`sys.stdout`) no longer inherits from `PyStdPrinter_Type` which I don't
think has any real issue other than minor differences in how they
convert to a string and missing a couple functions like `mode()` and
`fileno()`.
Also fixed an existing bug where `tp_basicsize` was set incorrectly for
`BufferObject`, `TabListObject, `WinListObject`.
Technically, there could be a small performance drop, there is a little
more indirection with accessing type objects, and some APIs like
`PyUnicode_AsUTF8AndSize` are missing, but in practice I didn't see any
difference, and any well-written Python plugin should try to avoid
excessing callbacks to the `vim` module in Python anyway.
I only tested limited API mode down to Python 3.7, which seemes to
compile and work fine. I haven't tried earlier Python versions.
2) Fix PyIter_Check on older Python vers / type##Ptr unused warning
For PyIter_Check, older versions exposed them as either macros (used in
full API), or a function (for use in limited API). A previous change
exposed PyIter_Check to the dynamic build because Python just moved it
to function-only in 3.10 anyway. Because of that, just make sure we
always grab the function in dynamic builds in earlier versions since
that's what Python eventually did anyway.
3) Move Py_LIMITED_API define to configure script
Can now use --with-python-stable-abi flag to customize what stable ABI
version to target. Can also use an env var to do so as well.
4) Show +python/dyn-stable in :version, and allow has() feature query
Not sure if the "/dyn-stable" suffix would break things, or whether we
should do it another way. Or just don't show it in version and rely on
has() feature checking.
5) Documentation first draft. Still need to implement v:python3_version
6) Fix PyIter_Check build breaks when compiling against Python 3.8
7) Add CI coverage stable ABI on Linux/Windows / make configurable on Windows
This adds configurable options for Windows make files (both MinGW and
MSVC). CI will also now exercise both traditional full API and stable
ABI for Linux and Windows in the matrix for coverage.
Also added a "dynamic" option to Linux matrix as a drive-by change to
make other scripting languages like Ruby / Perl testable under both
static and dynamic builds.
8) Fix inaccuracy in Windows docs
Python's own docs are confusing but you don't actually want to use
`python3.dll` for the dynamic linkage.
9) Add generated autoconf file
10) Add v:python3_version support
This variable indicates the version of Python3 that Vim was built
against (PY_VERSION_HEX), and will be useful to check whether the Python
library you are loading in dynamically actually fits it. When built with
stable ABI, it will be the limited ABI version instead
(`Py_LIMITED_API`), which indicates the minimum version of Python 3 the
user should have, rather than the exact match. When stable ABI is used,
we won't be exposing PY_VERSION_HEX in this var because it just doesn't
seem necessary to do so (the whole point of stable ABI is the promise
that it will work across versions), and I don't want to confuse the user
with too many variables.
Also, cleaned up some documentation, and added help tags.
11) Fix Python 3.7 compat issues
Fix a couple issues when using limited API < 3.8
- Crash on exit: In Python 3.7, if a heap-allocated type is destroyed
before all instances are, it would cause a crash later. This happens
when we destroyed `OptionsType` before calling `Py_Finalize` when
using the limited API. To make it worse, later versions changed the
semantics and now each instance has a strong reference to its own type
and the recommendation has changed to have each instance de-ref its
own type and have its type in GC traversal. To avoid dealing with
these cross-version variations, we just don't free the heap type. They
are static types in non-limited-API anyway and are designed to last
through the entirety of the app, and we also don't restart the Python
runtime and therefore do not need it to have absolutely 0 leaks.
See:
- https://docs.python.org/3/whatsnew/3.8.html#changes-in-the-c-api
- https://docs.python.org/3/whatsnew/3.9.html#changes-in-the-c-api
- PyIter_Check: This function is not provided in limited APIs older than
3.8. Previously I was trying to mock it out using manual
PyType_GetSlot() but it was brittle and also does not actually work
properly for static types (it will generate a Python error). Just
return false. It does mean using limited API < 3.8 is not recommended
as you lose the functionality to handle iterators, but from playing
with plugins I couldn't find it to be an issue.
- Fix loading of PyIter_Check so it will be done when limited API < 3.8.
Otherwise loading a 3.7 Python lib will fail even if limited API was
specified to use it.
12) Make sure to only load `PyUnicode_AsUTF8AndSize` in needed in limited API
We don't use this function unless limited API >= 3.10, but we were
loading it regardless. Usually it's ok in Unix-like systems where Python
just has a single lib that we load from, but in Windows where there is a
separate python3.dll this would not work as the symbol would not have
been exposed in this more limited DLL file. This makes it much clearer
under what condition is this function needed.
closes: #12032
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Sun, 20 Aug 2023 21:30:04 +0200 |
parents | 161ae1985d81 |
children | 6e4c686b6b5b |
line wrap: on
line source
/* 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 <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 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, // 0 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 <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 == MAPTYPE_UNMAP || !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); do_print = !haskey || (maptype != MAPTYPE_UNMAP && !hasarg); // check for :unmap without argument if (maptype == MAPTYPE_UNMAP && !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 something like <C-H> is simplified to 0x08 then mark it as simplified // and also add an entry with a modifier, which will work when using a key // protocol. if (haskey) { char_u *new_keys; int flags = REPTERM_FROM_PART | REPTERM_DO_LT; if (special) flags |= REPTERM_SPECIAL; new_keys = replace_termcodes(keys, &keys_buf, 0, flags, &did_simplify); if (did_simplify) (void)replace_termcodes(keys, &alt_keys_buf, 0, flags | REPTERM_NO_SIMPLIFY, NULL); keys = new_keys; } orig_rhs = rhs; if (hasarg) { if (STRICMP(rhs, "<nop>") == 0) // "<Nop>" means nothing rhs = (char_u *)""; else rhs = replace_termcodes(rhs, &arg_buf, 0, REPTERM_DO_LT | (special ? REPTERM_SPECIAL : 0), NULL); } /* * The following is done twice if we have two versions of keys: * "alt_keys_buf" is not NULL. */ for (keyround = 1; keyround <= 2; ++keyround) { int did_it = FALSE; int did_local = FALSE; int keyround1_simplified = keyround == 1 && did_simplify; int round; if (keyround == 2) { if (alt_keys_buf == NULL) break; keys = alt_keys_buf; } else if (alt_keys_buf != NULL && do_print) // when printing always use the not-simplified map keys = alt_keys_buf; // 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 != MAPTYPE_UNMAP) { // 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 (do_print) msg_start(); // Check if a new local mapping wasn't already defined globally. if (unique && map_table == curbuf->b_maphash && haskey && hasarg && maptype != MAPTYPE_UNMAP) { // need to loop over all global hash lists for (int 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 && STRNCMP(mp->m_keys, keys, (size_t)len) == 0) { if (abbrev) semsg( _(e_global_abbreviation_already_exists_for_str), mp->m_keys); else semsg(_(e_global_mapping_already_exists_for_str), 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 != MAPTYPE_UNMAP) list_mappings(keyround, abbrev, haskey, keys, len, mode, &did_local); // 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 == MAPTYPE_UNMAP) && round <= 1 && !did_it && !got_int; ++round) { // need to loop over all hash lists for (int 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) == 0) { // skip entries with wrong mode mpp = &(mp->m_next); continue; } if (!haskey) // show all entries { if (!mp->m_simplified) { 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 == MAPTYPE_UNMAP) { // 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; } // In keyround for simplified keys, don't unmap // a mapping without m_simplified flag. if (keyround1_simplified && !mp->m_simplified) break; // 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 { if (!mp->m_simplified) { 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( _(e_abbreviation_already_exists_for_str), p); else semsg(_(e_mapping_already_exists_for_str), 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 { char_u *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; mp->m_simplified = keyround1_simplified; #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. int 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 == MAPTYPE_UNMAP) { // delete entry if (!did_it) { if (!keyround1_simplified) 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; } continue; } 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) continue; // have added the new entry already // Get here when adding a new entry to the maphash[] list or abbrlist. if (map_add(map_table, abbr_table, keys, rhs, orig_rhs, noremap, nowait, silent, mode, abbrev, #ifdef FEAT_EVAL expr, /* sid */ 0, /* scriptversion */ 0, /* lnum */ 0, #endif keyround1_simplified) == FAIL) { retval = 4; // no mem goto theend; } } theend: vim_free(keys_buf); vim_free(alt_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 = MODE_INSERT; // :imap else if (modec == 'l') mode = MODE_LANGMAP; // :lmap else if (modec == 'c') mode = MODE_CMDLINE; // :cmap else if (modec == 'n' && *p != 'o') // avoid :noremap mode = MODE_NORMAL; // :nmap else if (modec == 'v') mode = MODE_VISUAL | MODE_SELECT; // :vmap else if (modec == 'x') mode = MODE_VISUAL; // :xmap else if (modec == 's') mode = MODE_SELECT; // :smap else if (modec == 'o') mode = MODE_OP_PENDING; // :omap else if (modec == 't') mode = MODE_TERMINAL; // :tmap else { --p; if (forceit) mode = MODE_INSERT | MODE_CMDLINE; // :map ! else mode = MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING; // :map } *cmdp = p; return mode; } /* * Clear all mappings (":mapclear") or abbreviations (":abclear"). * "abbr" should be FALSE for mappings, TRUE for abbreviations. */ static void map_clear( char_u *cmdp, char_u *arg, int forceit, int abbr) { int mode; int local; local = (STRCMP(arg, "<buffer>") == 0); if (!local && *arg != NUL) { emsg(_(e_invalid_argument)); return; } mode = get_map_mode(&cmdp, forceit); map_clear_mode(curbuf, mode, local, abbr); } /* * If "map_locked" is set then give an error and return TRUE. * Otherwise return FALSE. */ static int is_map_locked(void) { if (map_locked > 0) { emsg(_(e_cannot_change_mappings_while_listing)); return TRUE; } return FALSE; } /* * Clear all mappings in "mode". */ void map_clear_mode( 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; if (is_map_locked()) return; 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) int mode_str2flags(char_u *modechars) { int mode = 0; if (vim_strchr(modechars, 'n') != NULL) mode |= MODE_NORMAL; if (vim_strchr(modechars, 'v') != NULL) mode |= MODE_VISUAL | MODE_SELECT; if (vim_strchr(modechars, 'x') != NULL) mode |= MODE_VISUAL; if (vim_strchr(modechars, 's') != NULL) mode |= MODE_SELECT; if (vim_strchr(modechars, 'o') != NULL) mode |= MODE_OP_PENDING; if (vim_strchr(modechars, 'i') != NULL) mode |= MODE_INSERT; if (vim_strchr(modechars, 'l') != NULL) mode |= MODE_LANGMAP; if (vim_strchr(modechars, 'c') != NULL) mode |= MODE_CMDLINE; return mode; } /* * 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) { char_u *rhs; char_u *buf; int retval; rhs = replace_termcodes(str, &buf, 0, REPTERM_DO_LT, NULL); retval = map_to_exists_mode(rhs, mode_str2flags(modechars), 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; } /* * Used below when expanding mapping/abbreviation names. */ static int expand_mapmodes = 0; static int expand_isabbrev = 0; static int expand_buffer = FALSE; /* * Translate an internal mapping/abbreviation representation into the * corresponding external one recognized by :map/:abbrev commands. * Respects the current B/k/< settings of 'cpoption'. * * This function is called when expanding mappings/abbreviations on the * command-line. * * It uses a growarray to build the translation string since the latter can be * wider than the original description. The caller has to free the string * afterwards. * * Returns NULL when there is a problem. */ static char_u * translate_mapping(char_u *str) { garray_T ga; int c; int modifiers; int cpo_bslash; int cpo_special; ga_init(&ga); ga.ga_itemsize = 1; ga.ga_growsize = 40; cpo_bslash = (vim_strchr(p_cpo, CPO_BSLASH) != NULL); cpo_special = (vim_strchr(p_cpo, CPO_SPECI) != NULL); for (; *str; ++str) { c = *str; if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) { modifiers = 0; if (str[1] == KS_MODIFIER) { str++; modifiers = *++str; c = *++str; } if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) { if (cpo_special) { ga_clear(&ga); return NULL; } c = TO_SPECIAL(str[1], str[2]); if (c == K_ZERO) // display <Nul> as ^@ c = NUL; str += 2; } if (IS_SPECIAL(c) || modifiers) // special key { if (cpo_special) { ga_clear(&ga); return NULL; } ga_concat(&ga, get_special_key_name(c, modifiers)); continue; // for (str) } } if (c == ' ' || c == '\t' || c == Ctrl_J || c == Ctrl_V || (c == '<' && !cpo_special) || (c == '\\' && !cpo_bslash)) ga_append(&ga, cpo_bslash ? Ctrl_V : '\\'); if (c) ga_append(&ga, c); } ga_append(&ga, NUL); return (char_u *)(ga.ga_data); } /* * 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 = MODE_INSERT | MODE_CMDLINE; if (!isabbrev) expand_mapmodes += MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_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( char_u *pat, regmatch_T *regmatch, int *numMatches, char_u ***matches) { mapblock_T *mp; garray_T ga; int hash; int count; char_u *p; int i; int fuzzy; int match; int score = 0; fuzmatch_str_T *fuzmatch; fuzzy = cmdline_fuzzy_complete(pat); validate_maphash(); *numMatches = 0; // return values in case of FAIL *matches = NULL; if (!fuzzy) ga_init2(&ga, sizeof(char *), 3); else ga_init2(&ga, sizeof(fuzmatch_str_T), 3); // First search in map modifier arguments 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 (!fuzzy) match = vim_regexec(regmatch, p, (colnr_T)0); else { score = fuzzy_match_str(p, pat); match = (score != 0); } if (!match) continue; if (ga_grow(&ga, 1) == FAIL) break; if (fuzzy) { fuzmatch = &((fuzmatch_str_T *)ga.ga_data)[ga.ga_len]; fuzmatch->idx = ga.ga_len; fuzmatch->str = vim_strsave(p); fuzmatch->score = score; } else ((char_u **)ga.ga_data)[ga.ga_len] = vim_strsave(p); ++ga.ga_len; } 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_simplified || !(mp->m_mode & expand_mapmodes)) continue; p = translate_mapping(mp->m_keys); if (p == NULL) continue; if (!fuzzy) match = vim_regexec(regmatch, p, (colnr_T)0); else { score = fuzzy_match_str(p, pat); match = (score != 0); } if (!match) { vim_free(p); continue; } if (ga_grow(&ga, 1) == FAIL) { vim_free(p); break; } if (fuzzy) { fuzmatch = &((fuzmatch_str_T *)ga.ga_data)[ga.ga_len]; fuzmatch->idx = ga.ga_len; fuzmatch->str = p; fuzmatch->score = score; } else ((char_u **)ga.ga_data)[ga.ga_len] = p; ++ga.ga_len; } // for (mp) } // for (hash) if (ga.ga_len == 0) return FAIL; if (!fuzzy) { *matches = ga.ga_data; *numMatches = ga.ga_len; } else { if (fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, FALSE) == FAIL) return FAIL; *numMatches = ga.ga_len; } count = *numMatches; if (count > 1) { char_u **ptr1; char_u **ptr2; char_u **ptr3; // Sort the matches // Fuzzy matching already sorts the matches if (!fuzzy) sort_strings(*matches, count); // Remove multiple entries ptr1 = *matches; ptr2 = ptr1 + 1; ptr3 = ptr1 + count; while (ptr2 < ptr3) { if (STRCMP(*ptr1, *ptr2)) *++ptr1 = *ptr2++; else { vim_free(*ptr2++); count--; } } } *numMatches = count; return (count == 0 ? FAIL : OK); } /* * 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 { if (!vim_iswordc(ptr[col - 1])) vim_abbr = TRUE; // Vim added abbr. else { vim_abbr = FALSE; // vi compatible abbr. if (col > 1) is_id = vim_iswordc(ptr[col - 2]); } for (scol = col - 1; scol > 0 && !vim_isspace(ptr[scol - 1]) && (vim_abbr || is_id == vim_iswordc(ptr[scol - 1])); --scol) ; } if (scol < mincol) scol = mincol; if (scol < col) // there is a word in front of the cursor { ptr += scol; len = col - scol; mp = curbuf->b_first_abbr; mp2 = first_abbr; if (mp == NULL) { mp = mp2; mp2 = NULL; } for ( ; mp; mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : (mp = mp->m_next)) { int qlen = mp->m_keylen; char_u *q = mp->m_keys; int match; if (vim_strbyte(mp->m_keys, K_SPECIAL) != NULL) { char_u *qe = vim_strsave(mp->m_keys); // might have CSI escaped mp->m_keys if (qe != NULL) { q = qe; vim_unescape_csi(q); qlen = (int)STRLEN(q); } } // find entries with right mode and keys match = (mp->m_mode & State) && qlen == len && !STRNCMP(q, ptr, (size_t)len); if (q != mp->m_keys) vim_free(q); if (match) break; } if (mp != NULL) { int noremap; int silent; #ifdef FEAT_EVAL int expr; #endif // Found a match: // Insert the rest of the abbreviation in typebuf.tb_buf[]. // This goes from end to start. // // Characters 0x000 - 0x100: normal chars, may need CTRL-V, // except K_SPECIAL: Becomes K_SPECIAL KS_SPECIAL KE_FILLER // Characters where IS_SPECIAL() == TRUE: key codes, need // K_SPECIAL. Other characters (with ABBR_OFF): don't use CTRL-V. // // Character CTRL-] is treated specially - it completes the // abbreviation, but is not inserted into the input stream. j = 0; if (c != Ctrl_RSB) { // special key code, split up if (IS_SPECIAL(c) || c == K_SPECIAL) { tb[j++] = K_SPECIAL; tb[j++] = K_SECOND(c); tb[j++] = K_THIRD(c); } else { if (c < ABBR_OFF && (c < ' ' || c > '~')) tb[j++] = Ctrl_V; // special char needs CTRL-V if (has_mbyte) { int newlen; char_u *escaped; // if ABBR_OFF has been added, remove it here if (c >= ABBR_OFF) c -= ABBR_OFF; newlen = (*mb_char2bytes)(c, tb + j); tb[j + newlen] = NUL; // Need to escape K_SPECIAL. escaped = vim_strsave_escape_csi(tb + j); if (escaped != NULL) { newlen = (int)STRLEN(escaped); mch_memmove(tb + j, escaped, newlen); j += newlen; vim_free(escaped); } } else tb[j++] = c; } tb[j] = NUL; // insert the last typed char (void)ins_typebuf(tb, 1, 0, TRUE, mp->m_silent); } // copy values here, calling eval_map_expr() may make "mp" invalid! noremap = mp->m_noremap; silent = mp->m_silent; #ifdef FEAT_EVAL expr = mp->m_expr; if (expr) s = eval_map_expr(mp, c); else #endif s = mp->m_str; if (s != NULL) { // insert the to string (void)ins_typebuf(s, noremap, 0, TRUE, silent); // no abbrev. for these chars typebuf.tb_no_abbr_cnt += (int)STRLEN(s) + j + 1; #ifdef FEAT_EVAL if (expr) vim_free(s); #endif } tb[0] = Ctrl_H; tb[1] = NUL; if (has_mbyte) len = clen; // Delete characters instead of bytes while (len-- > 0) // delete the from string (void)ins_typebuf(tb, 1, 0, TRUE, silent); return TRUE; } } return FALSE; } #ifdef FEAT_EVAL /* * Evaluate the RHS of a mapping or abbreviations and take care of escaping * special characters. * Careful: after this "mp" will be invalid if the mapping was deleted. */ char_u * eval_map_expr( mapblock_T *mp, int c) // NUL or typed character for abbreviation { char_u *res; char_u *p; char_u *expr; pos_T save_cursor; int save_msg_col; int save_msg_row; scid_T save_sctx_sid = current_sctx.sc_sid; int save_sctx_version = current_sctx.sc_version; // Remove escaping of CSI, because "str" is in a format to be used as // typeahead. expr = vim_strsave(mp->m_str); if (expr == NULL) return NULL; vim_unescape_csi(expr); // Forbid changing text or using ":normal" to avoid most of the bad side // effects. Also restore the cursor position. ++textlock; ++ex_normal_lock; set_vim_var_char(c); // set v:char to the typed character save_cursor = curwin->w_cursor; save_msg_col = msg_col; save_msg_row = msg_row; if (mp->m_script_ctx.sc_version == SCRIPT_VERSION_VIM9) { current_sctx.sc_sid = mp->m_script_ctx.sc_sid; current_sctx.sc_version = SCRIPT_VERSION_VIM9; } // Note: the evaluation may make "mp" invalid. p = eval_to_string(expr, FALSE, FALSE); --textlock; --ex_normal_lock; curwin->w_cursor = save_cursor; msg_col = save_msg_col; msg_row = save_msg_row; current_sctx.sc_sid = save_sctx_sid; current_sctx.sc_version = save_sctx_version; vim_free(expr); if (p == NULL) return NULL; // Escape CSI in the result to be able to use the string as typeahead. res = vim_strsave_escape_csi(p); vim_free(p); return res; } #endif /* * Copy "p" to allocated memory, escaping K_SPECIAL and CSI so that the result * can be put in the typeahead buffer. * Returns NULL when out of memory. */ char_u * vim_strsave_escape_csi(char_u *p) { char_u *res; char_u *s, *d; // Need a buffer to hold up to three times as much. Four in case of an // illegal utf-8 byte: // 0xc0 -> 0xc3 0x80 -> 0xc3 K_SPECIAL KS_SPECIAL KE_FILLER res = alloc(STRLEN(p) * 4 + 1); if (res == NULL) return NULL; d = res; for (s = p; *s != NUL; ) { if ((s[0] == K_SPECIAL #ifdef FEAT_GUI || (gui.in_use && s[0] == CSI) #endif ) && s[1] != NUL && s[2] != NUL) { // Copy special key unmodified. *d++ = *s++; *d++ = *s++; *d++ = *s++; } else { // Add character, possibly multi-byte to destination, escaping // CSI and K_SPECIAL. Be careful, it can be an illegal byte! d = add_char2buf(PTR2CHAR(s), d); s += MB_CPTR2LEN(s); } } *d = NUL; return res; } /* * Remove escaping from CSI and K_SPECIAL characters. Reverse of * vim_strsave_escape_csi(). Works in-place. */ void vim_unescape_csi(char_u *p) { char_u *s = p, *d = p; while (*s != NUL) { if (s[0] == K_SPECIAL && s[1] == KS_SPECIAL && s[2] == KE_FILLER) { *d++ = K_SPECIAL; s += 3; } else if ((s[0] == K_SPECIAL || s[0] == CSI) && s[1] == KS_EXTRA && s[2] == (int)KE_CSI) { *d++ = CSI; s += 3; } else *d++ = *s++; } *d = NUL; } /* * Write map commands for the current mappings to an .exrc file. * Return FAIL on error, OK otherwise. */ int makemap( FILE *fd, buf_T *buf) // buffer for local mappings or NULL { mapblock_T *mp; char_u c1, c2, c3; char_u *p; char *cmd; int abbr; int hash; int did_cpo = FALSE; int i; validate_maphash(); // Do the loop twice: Once for mappings, once for abbreviations. // Then loop over all map hash lists. for (abbr = 0; abbr < 2; ++abbr) for (hash = 0; hash < 256; ++hash) { if (abbr) { if (hash > 0) // there is only one abbr list break; if (buf != NULL) mp = buf->b_first_abbr; else mp = first_abbr; } else { if (buf != NULL) mp = buf->b_maphash[hash]; else mp = maphash[hash]; } for ( ; mp; mp = mp->m_next) { // skip script-local mappings if (mp->m_noremap == REMAP_SCRIPT) continue; // skip mappings that contain a <SNR> (script-local thing), // they probably don't work when loaded again for (p = mp->m_str; *p != NUL; ++p) if (p[0] == K_SPECIAL && p[1] == KS_EXTRA && p[2] == (int)KE_SNR) break; if (*p != NUL) continue; // It's possible to create a mapping and then ":unmap" certain // modes. We recreate this here by mapping the individual // modes, which requires up to three of them. c1 = NUL; c2 = NUL; c3 = NUL; if (abbr) cmd = "abbr"; else cmd = "map"; switch (mp->m_mode) { case MODE_NORMAL | MODE_VISUAL | MODE_SELECT | MODE_OP_PENDING: break; case MODE_NORMAL: c1 = 'n'; break; case MODE_VISUAL: c1 = 'x'; break; case MODE_SELECT: c1 = 's'; break; case MODE_OP_PENDING: c1 = 'o'; break; case MODE_NORMAL | MODE_VISUAL: c1 = 'n'; c2 = 'x'; break; case MODE_NORMAL | MODE_SELECT: c1 = 'n'; c2 = 's'; break; case MODE_NORMAL | MODE_OP_PENDING: c1 = 'n'; c2 = 'o'; break; case MODE_VISUAL | MODE_SELECT: c1 = 'v'; break; case MODE_VISUAL | MODE_OP_PENDING: c1 = 'x'; c2 = 'o'; break; case MODE_SELECT | MODE_OP_PENDING: c1 = 's'; c2 = 'o'; break; case MODE_NORMAL | MODE_VISUAL | MODE_SELECT: c1 = 'n'; c2 = 'v'; break; case MODE_NORMAL | MODE_VISUAL | MODE_OP_PENDING: c1 = 'n'; c2 = 'x'; c3 = 'o'; break; case MODE_NORMAL | MODE_SELECT | MODE_OP_PENDING: c1 = 'n'; c2 = 's'; c3 = 'o'; break; case MODE_VISUAL | MODE_SELECT | MODE_OP_PENDING: c1 = 'v'; c2 = 'o'; break; case MODE_CMDLINE | MODE_INSERT: if (!abbr) cmd = "map!"; break; case MODE_CMDLINE: c1 = 'c'; break; case MODE_INSERT: c1 = 'i'; break; case MODE_LANGMAP: c1 = 'l'; break; case MODE_TERMINAL: c1 = 't'; break; default: iemsg(e_makemap_illegal_mode); return FAIL; } do // do this twice if c2 is set, 3 times with c3 { // When outputting <> form, need to make sure that 'cpo' // is set to the Vim default. if (!did_cpo) { if (*mp->m_str == NUL) // will use <Nop> did_cpo = TRUE; else for (i = 0; i < 2; ++i) for (p = (i ? mp->m_str : mp->m_keys); *p; ++p) if (*p == K_SPECIAL || *p == NL) did_cpo = TRUE; if (did_cpo) { if (fprintf(fd, "let s:cpo_save=&cpo") < 0 || put_eol(fd) < 0 || fprintf(fd, "set cpo&vim") < 0 || put_eol(fd) < 0) return FAIL; } } if (c1 && putc(c1, fd) < 0) return FAIL; if (mp->m_noremap != REMAP_YES && fprintf(fd, "nore") < 0) return FAIL; if (fputs(cmd, fd) < 0) return FAIL; if (buf != NULL && fputs(" <buffer>", fd) < 0) return FAIL; if (mp->m_nowait && fputs(" <nowait>", fd) < 0) return FAIL; if (mp->m_silent && fputs(" <silent>", fd) < 0) return FAIL; #ifdef FEAT_EVAL if (mp->m_noremap == REMAP_SCRIPT && fputs("<script>", fd) < 0) return FAIL; if (mp->m_expr && fputs(" <expr>", fd) < 0) return FAIL; #endif if ( putc(' ', fd) < 0 || put_escstr(fd, mp->m_keys, 0) == FAIL || putc(' ', fd) < 0 || put_escstr(fd, mp->m_str, 1) == FAIL || put_eol(fd) < 0) return FAIL; c1 = c2; c2 = c3; c3 = NUL; } while (c1 != NUL); } } if (did_cpo) if (fprintf(fd, "let &cpo=s:cpo_save") < 0 || put_eol(fd) < 0 || fprintf(fd, "unlet s:cpo_save") < 0 || put_eol(fd) < 0) return FAIL; return OK; } /* * write escape string to file * "what": 0 for :map lhs, 1 for :map rhs, 2 for :set * * return FAIL for failure, OK otherwise */ int put_escstr(FILE *fd, char_u *strstart, int what) { char_u *str = strstart; int c; int modifiers; // :map xx <Nop> if (*str == NUL && what == 1) { if (fprintf(fd, "<Nop>") < 0) return FAIL; return OK; } for ( ; *str != NUL; ++str) { char_u *p; // Check for a multi-byte character, which may contain escaped // K_SPECIAL and CSI bytes p = mb_unescape(&str); if (p != NULL) { while (*p != NUL) if (fputc(*p++, fd) < 0) return FAIL; --str; continue; } c = *str; // Special key codes have to be translated to be able to make sense // when they are read back. if (c == K_SPECIAL && what != 2) { modifiers = 0; if (str[1] == KS_MODIFIER) { modifiers = str[2]; str += 3; c = *str; } if (c == K_SPECIAL) { c = TO_SPECIAL(str[1], str[2]); str += 2; } if (IS_SPECIAL(c) || modifiers) // special key { if (fputs((char *)get_special_key_name(c, modifiers), fd) < 0) return FAIL; continue; } } // A '\n' in a map command should be written as <NL>. // A '\n' in a set command should be written as \^V^J. if (c == NL) { if (what == 2) { if (fprintf(fd, "\\\026\n") < 0) return FAIL; } else { if (fprintf(fd, "<NL>") < 0) return FAIL; } continue; } // Some characters have to be escaped with CTRL-V to // prevent them from misinterpreted in DoOneCmd(). // A space, Tab and '"' has to be escaped with a backslash to // prevent it to be misinterpreted in do_set(). // A space has to be escaped with a CTRL-V when it's at the start of a // ":map" rhs. // A '<' has to be escaped with a CTRL-V to prevent it being // interpreted as the start of a special key name. // A space in the lhs of a :map needs a CTRL-V. if (what == 2 && (VIM_ISWHITE(c) || c == '"' || c == '\\')) { if (putc('\\', fd) < 0) return FAIL; } else if (c < ' ' || c > '~' || c == '|' || (what == 0 && c == ' ') || (what == 1 && str == strstart && c == ' ') || (what != 2 && c == '<')) { if (putc(Ctrl_V, fd) < 0) return FAIL; } if (putc(c, fd) < 0) return FAIL; } return OK; } /* * Check all mappings for the presence of special key codes. * Used after ":set term=xxx". */ void check_map_keycodes(void) { mapblock_T *mp; char_u *p; int i; char_u buf[3]; int abbr; int hash; buf_T *bp; ESTACK_CHECK_DECLARATION; validate_maphash(); // avoids giving error messages estack_push(ETYPE_INTERNAL, (char_u *)"mappings", 0); ESTACK_CHECK_SETUP; // Do this once for each buffer, and then once for global // mappings/abbreviations with bp == NULL for (bp = firstbuf; ; bp = bp->b_next) { // Do the loop twice: Once for mappings, once for abbreviations. // Then loop over all map hash lists. for (abbr = 0; abbr <= 1; ++abbr) for (hash = 0; hash < 256; ++hash) { if (abbr) { if (hash) // there is only one abbr list break; if (bp != NULL) mp = bp->b_first_abbr; else mp = first_abbr; } else { if (bp != NULL) mp = bp->b_maphash[hash]; else mp = maphash[hash]; } for ( ; mp != NULL; mp = mp->m_next) { for (i = 0; i <= 1; ++i) // do this twice { if (i == 0) p = mp->m_keys; // once for the "from" part else p = mp->m_str; // and once for the "to" part while (*p) { if (*p == K_SPECIAL) { ++p; if (*p < 128) // for "normal" tcap entries { buf[0] = p[0]; buf[1] = p[1]; buf[2] = NUL; (void)add_termcap_entry(buf, FALSE); } ++p; } ++p; } } } } if (bp == NULL) break; } ESTACK_CHECK_NOW; estack_pop(); } #if defined(FEAT_EVAL) || defined(PROTO) /* * Check the string "keys" against the lhs of all mappings. * Return pointer to rhs of mapping (mapblock->m_str). * NULL when no mapping found. */ char_u * check_map( char_u *keys, int mode, int exact, // require exact match int ign_mod, // ignore preceding modifier int abbr, // do abbreviations mapblock_T **mp_ptr, // return: pointer to mapblock or NULL int *local_ptr) // return: buffer-local mapping or NULL { int hash; int len, minlen; mapblock_T *mp; char_u *s; int local; validate_maphash(); len = (int)STRLEN(keys); for (local = 1; local >= 0; --local) // loop over all hash lists for (hash = 0; hash < 256; ++hash) { if (abbr) { if (hash > 0) // there is only one list. break; if (local) mp = curbuf->b_first_abbr; else mp = first_abbr; } else if (local) mp = curbuf->b_maphash[hash]; else mp = maphash[hash]; for ( ; mp != NULL; mp = mp->m_next) { // skip entries with wrong mode, wrong length and not matching // ones if ((mp->m_mode & mode) && (!exact || mp->m_keylen == len)) { if (len > mp->m_keylen) minlen = mp->m_keylen; else minlen = len; s = mp->m_keys; if (ign_mod && s[0] == K_SPECIAL && s[1] == KS_MODIFIER && s[2] != NUL) { s += 3; if (len > mp->m_keylen - 3) minlen = mp->m_keylen - 3; } if (STRNCMP(s, keys, minlen) == 0) { if (mp_ptr != NULL) *mp_ptr = mp; if (local_ptr != NULL) *local_ptr = local; return mp->m_str; } } } } return NULL; } /* * "hasmapto()" function */ void f_hasmapto(typval_T *argvars, typval_T *rettv) { char_u *name; char_u *mode; char_u buf[NUMBUFLEN]; int abbr = FALSE; if (in_vim9script() && (check_for_string_arg(argvars, 0) == FAIL || check_for_opt_string_arg(argvars, 1) == FAIL || (argvars[1].v_type != VAR_UNKNOWN && check_for_opt_bool_arg(argvars, 2) == FAIL))) return; name = tv_get_string(&argvars[0]); if (argvars[1].v_type == VAR_UNKNOWN) mode = (char_u *)"nvo"; else { mode = tv_get_string_buf(&argvars[1], buf); if (argvars[2].v_type != VAR_UNKNOWN) abbr = (int)tv_get_bool(&argvars[2]); } if (map_to_exists(name, mode, abbr)) rettv->vval.v_number = TRUE; else rettv->vval.v_number = FALSE; } /* * Fill in the empty dictionary with items as defined by maparg builtin. */ static void mapblock2dict( mapblock_T *mp, dict_T *dict, char_u *lhsrawalt, // may be NULL int buffer_local, // false if not buffer local mapping int abbr) // true if abbreviation { char_u *lhs = str2special_save(mp->m_keys, TRUE, FALSE); char_u *mapmode = map_mode_to_chars(mp->m_mode); dict_add_string(dict, "lhs", lhs); vim_free(lhs); dict_add_string(dict, "lhsraw", mp->m_keys); if (lhsrawalt) // Also add the value for the simplified entry. dict_add_string(dict, "lhsrawalt", lhsrawalt); dict_add_string(dict, "rhs", mp->m_orig_str); dict_add_number(dict, "noremap", mp->m_noremap ? 1L : 0L); dict_add_number(dict, "script", mp->m_noremap == REMAP_SCRIPT ? 1L : 0L); dict_add_number(dict, "expr", mp->m_expr ? 1L : 0L); dict_add_number(dict, "silent", mp->m_silent ? 1L : 0L); dict_add_number(dict, "sid", (long)mp->m_script_ctx.sc_sid); dict_add_number(dict, "scriptversion", (long)mp->m_script_ctx.sc_version); dict_add_number(dict, "lnum", (long)mp->m_script_ctx.sc_lnum); dict_add_number(dict, "buffer", (long)buffer_local); dict_add_number(dict, "nowait", mp->m_nowait ? 1L : 0L); dict_add_string(dict, "mode", mapmode); dict_add_number(dict, "abbr", abbr ? 1L : 0L); dict_add_number(dict, "mode_bits", mp->m_mode); vim_free(mapmode); } static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) { char_u *keys; char_u *keys_simplified; char_u *which; char_u buf[NUMBUFLEN]; char_u *keys_buf = NULL; char_u *alt_keys_buf = NULL; int did_simplify = FALSE; char_u *rhs; int mode; int abbr = FALSE; int get_dict = FALSE; mapblock_T *mp = NULL; int buffer_local; int flags = REPTERM_FROM_PART | REPTERM_DO_LT; // return empty string for failure rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; keys = tv_get_string(&argvars[0]); if (*keys == NUL) return; if (argvars[1].v_type != VAR_UNKNOWN) { which = tv_get_string_buf_chk(&argvars[1], buf); if (argvars[2].v_type != VAR_UNKNOWN) { abbr = (int)tv_get_bool(&argvars[2]); if (argvars[3].v_type != VAR_UNKNOWN) get_dict = (int)tv_get_bool(&argvars[3]); } } else which = (char_u *)""; if (which == NULL) return; mode = get_map_mode(&which, 0); keys_simplified = replace_termcodes(keys, &keys_buf, 0, flags, &did_simplify); rhs = check_map(keys_simplified, mode, exact, FALSE, abbr, &mp, &buffer_local); if (did_simplify) { // When the lhs is being simplified the not-simplified keys are // preferred for printing, like in do_map(). (void)replace_termcodes(keys, &alt_keys_buf, 0, flags | REPTERM_NO_SIMPLIFY, NULL); rhs = check_map(alt_keys_buf, mode, exact, FALSE, abbr, &mp, &buffer_local); } if (!get_dict) { // Return a string. if (rhs != NULL) { if (*rhs == NUL) rettv->vval.v_string = vim_strsave((char_u *)"<Nop>"); else rettv->vval.v_string = str2special_save(rhs, FALSE, FALSE); } } else if (rettv_dict_alloc(rettv) == OK && rhs != NULL) mapblock2dict(mp, rettv->vval.v_dict, did_simplify ? keys_simplified : NULL, buffer_local, abbr); vim_free(keys_buf); vim_free(alt_keys_buf); } /* * "maplist()" function */ void f_maplist(typval_T *argvars UNUSED, typval_T *rettv) { dict_T *d; mapblock_T *mp; int buffer_local; char_u *keys_buf; int did_simplify; int hash; char_u *lhs; const int flags = REPTERM_FROM_PART | REPTERM_DO_LT; int abbr = FALSE; if (in_vim9script() && check_for_opt_bool_arg(argvars, 0) == FAIL) return; if (argvars[0].v_type != VAR_UNKNOWN) abbr = tv_get_bool(&argvars[0]); if (rettv_list_alloc(rettv) == FAIL) return; validate_maphash(); // Do it twice: once for global maps and once for local maps. for (buffer_local = 0; buffer_local <= 1; ++buffer_local) { for (hash = 0; hash < 256; ++hash) { if (abbr) { if (hash > 0) // there is only one abbr list break; if (buffer_local) mp = curbuf->b_first_abbr; else mp = first_abbr; } else if (buffer_local) mp = curbuf->b_maphash[hash]; else mp = maphash[hash]; for (; mp; mp = mp->m_next) { if (mp->m_simplified) continue; if ((d = dict_alloc()) == NULL) return; if (list_append_dict(rettv->vval.v_list, d) == FAIL) return; keys_buf = NULL; did_simplify = FALSE; lhs = str2special_save(mp->m_keys, TRUE, FALSE); (void)replace_termcodes(lhs, &keys_buf, 0, flags, &did_simplify); vim_free(lhs); mapblock2dict(mp, d, did_simplify ? keys_buf : NULL, buffer_local, abbr); vim_free(keys_buf); } } } } /* * "maparg()" function */ void f_maparg(typval_T *argvars, typval_T *rettv) { if (in_vim9script() && (check_for_string_arg(argvars, 0) == FAIL || check_for_opt_string_arg(argvars, 1) == FAIL || (argvars[1].v_type != VAR_UNKNOWN && (check_for_opt_bool_arg(argvars, 2) == FAIL || (argvars[2].v_type != VAR_UNKNOWN && check_for_opt_bool_arg(argvars, 3) == FAIL))))) return; get_maparg(argvars, rettv, TRUE); } /* * "mapcheck()" function */ void f_mapcheck(typval_T *argvars, typval_T *rettv) { if (in_vim9script() && (check_for_string_arg(argvars, 0) == FAIL || check_for_opt_string_arg(argvars, 1) == FAIL || (argvars[1].v_type != VAR_UNKNOWN && check_for_opt_bool_arg(argvars, 2) == FAIL))) return; get_maparg(argvars, rettv, FALSE); } /* * Get the mapping mode from the mode string. * It may contain multiple characters, eg "nox", or "!", or ' ' * Return 0 if there is an error. */ static int get_map_mode_string(char_u *mode_string, int abbr) { char_u *p = mode_string; int mode = 0; int tmode; int modec; const int MASK_V = MODE_VISUAL | MODE_SELECT; const int MASK_MAP = MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING; const int MASK_BANG = MODE_INSERT | MODE_CMDLINE; if (*p == NUL) p = (char_u *)" "; // compatibility while ((modec = *p++)) { switch (modec) { case 'i': tmode = MODE_INSERT; break; case 'l': tmode = MODE_LANGMAP; break; case 'c': tmode = MODE_CMDLINE; break; case 'n': tmode = MODE_NORMAL; break; case 'x': tmode = MODE_VISUAL; break; case 's': tmode = MODE_SELECT; break; case 'o': tmode = MODE_OP_PENDING; break; case 't': tmode = MODE_TERMINAL; break; case 'v': tmode = MASK_V; break; case '!': tmode = MASK_BANG; break; case ' ': tmode = MASK_MAP; break; default: return 0; // error, unknown mode character } mode |= tmode; } if ((abbr && (mode & ~MASK_BANG) != 0) || (!abbr && (mode & (mode-1)) != 0 // more than one bit set && ( // false if multiple bits set in mode and mode is fully // contained in one mask !(((mode & MASK_BANG) != 0 && (mode & ~MASK_BANG) == 0) || ((mode & MASK_MAP) != 0 && (mode & ~MASK_MAP) == 0))))) return 0; return mode; } /* * "mapset()" function */ void f_mapset(typval_T *argvars, typval_T *rettv UNUSED) { char_u *which; int mode; char_u buf[NUMBUFLEN]; int is_abbr; dict_T *d; char_u *lhs; char_u *lhsraw; char_u *lhsrawalt; char_u *rhs; char_u *orig_rhs; char_u *arg_buf = NULL; int noremap; int expr; int silent; int buffer; scid_T sid; int scriptversion; linenr_T lnum; mapblock_T **map_table = maphash; mapblock_T **abbr_table = &first_abbr; int nowait; char_u *arg; int dict_only; // If first arg is a dict, then that's the only arg permitted. dict_only = argvars[0].v_type == VAR_DICT; if (in_vim9script() && (check_for_string_or_dict_arg(argvars, 0) == FAIL || (dict_only && check_for_unknown_arg(argvars, 1) == FAIL) || (!dict_only && (check_for_string_arg(argvars, 0) == FAIL || check_for_bool_arg(argvars, 1) == FAIL || check_for_dict_arg(argvars, 2) == FAIL)))) return; if (dict_only) { d = argvars[0].vval.v_dict; which = dict_get_string(d, "mode", FALSE); is_abbr = dict_get_bool(d, "abbr", -1); if (which == NULL || is_abbr < 0) { emsg(_(e_entries_missing_in_mapset_dict_argument)); return; } } else { which = tv_get_string_buf_chk(&argvars[0], buf); if (which == NULL) return; is_abbr = (int)tv_get_bool(&argvars[1]); if (check_for_dict_arg(argvars, 2) == FAIL) return; d = argvars[2].vval.v_dict; } mode = get_map_mode_string(which, is_abbr); if (mode == 0) { semsg(_(e_illegal_map_mode_string_str), which); return; } // Get the values in the same order as above in get_maparg(). lhs = dict_get_string(d, "lhs", FALSE); lhsraw = dict_get_string(d, "lhsraw", FALSE); lhsrawalt = dict_get_string(d, "lhsrawalt", FALSE); rhs = dict_get_string(d, "rhs", FALSE); if (lhs == NULL || lhsraw == NULL || rhs == NULL) { emsg(_(e_entries_missing_in_mapset_dict_argument)); return; } orig_rhs = rhs; noremap = dict_get_number(d, "noremap") ? REMAP_NONE: 0; if (dict_get_number(d, "script") != 0) noremap = REMAP_SCRIPT; expr = dict_get_number(d, "expr") != 0; silent = dict_get_number(d, "silent") != 0; sid = dict_get_number(d, "sid"); scriptversion = dict_get_number(d, "scriptversion"); lnum = dict_get_number(d, "lnum"); buffer = dict_get_number(d, "buffer"); nowait = dict_get_number(d, "nowait") != 0; // mode from the dict is not used if (STRICMP(rhs, "<nop>") == 0) // "<Nop>" means nothing rhs = (char_u *)""; else rhs = replace_termcodes(rhs, &arg_buf, sid, REPTERM_DO_LT | REPTERM_SPECIAL, NULL); if (buffer) { map_table = curbuf->b_maphash; abbr_table = &curbuf->b_first_abbr; } // Delete any existing mapping for this lhs and mode. if (buffer) { arg = alloc(STRLEN(lhs) + STRLEN("<buffer>") + 1); if (arg == NULL) return; STRCPY(arg, "<buffer>"); STRCPY(arg + 8, lhs); } else { arg = vim_strsave(lhs); if (arg == NULL) return; } do_map(MAPTYPE_UNMAP, arg, mode, is_abbr); vim_free(arg); (void)map_add(map_table, abbr_table, lhsraw, rhs, orig_rhs, noremap, nowait, silent, mode, is_abbr, expr, sid, scriptversion, lnum, 0); if (lhsrawalt != NULL) (void)map_add(map_table, abbr_table, lhsrawalt, rhs, orig_rhs, noremap, nowait, silent, mode, is_abbr, expr, sid, scriptversion, lnum, 1); vim_free(arg_buf); } #endif #if defined(MSWIN) || defined(MACOS_X) # define VIS_SEL (MODE_VISUAL | MODE_SELECT) // abbreviation /* * Default mappings for some often used keys. */ struct initmap { char_u *arg; int mode; }; # ifdef FEAT_GUI_MSWIN // Use the Windows (CUA) keybindings. (GUI) static struct initmap initmappings[] = { // paste, copy and cut {(char_u *)"<S-Insert> \"*P", MODE_NORMAL}, {(char_u *)"<S-Insert> \"-d\"*P", VIS_SEL}, {(char_u *)"<S-Insert> <C-R><C-O>*", MODE_INSERT | MODE_CMDLINE}, {(char_u *)"<C-Insert> \"*y", VIS_SEL}, {(char_u *)"<S-Del> \"*d", VIS_SEL}, {(char_u *)"<C-Del> \"*d", VIS_SEL}, {(char_u *)"<C-X> \"*d", VIS_SEL}, // Missing: CTRL-C (cancel) and CTRL-V (block selection) }; # endif # if defined(MSWIN) && (!defined(FEAT_GUI) || defined(VIMDLL)) // Use the Windows (CUA) keybindings. (Console) static struct initmap cinitmappings[] = { {(char_u *)"\316w <C-Home>", MODE_NORMAL | VIS_SEL}, {(char_u *)"\316w <C-Home>", MODE_INSERT | MODE_CMDLINE}, {(char_u *)"\316u <C-End>", MODE_NORMAL | VIS_SEL}, {(char_u *)"\316u <C-End>", MODE_INSERT | MODE_CMDLINE}, // paste, copy and cut # ifdef FEAT_CLIPBOARD {(char_u *)"\316\324 \"*P", MODE_NORMAL}, // SHIFT-Insert is "*P {(char_u *)"\316\324 \"-d\"*P", VIS_SEL}, // SHIFT-Insert is "-d"*P {(char_u *)"\316\324 \022\017*", MODE_INSERT}, // SHIFT-Insert is ^R^O* {(char_u *)"\316\325 \"*y", VIS_SEL}, // CTRL-Insert is "*y {(char_u *)"\316\327 \"*d", VIS_SEL}, // SHIFT-Del is "*d {(char_u *)"\316\330 \"*d", VIS_SEL}, // CTRL-Del is "*d {(char_u *)"\030 \"*d", VIS_SEL}, // CTRL-X is "*d # else {(char_u *)"\316\324 P", MODE_NORMAL}, // SHIFT-Insert is P {(char_u *)"\316\324 \"-dP", VIS_SEL}, // SHIFT-Insert is "-dP {(char_u *)"\316\324 \022\017\"", MODE_INSERT}, // SHIFT-Insert is ^R^O" {(char_u *)"\316\325 y", VIS_SEL}, // CTRL-Insert is y {(char_u *)"\316\327 d", VIS_SEL}, // SHIFT-Del is d {(char_u *)"\316\330 d", VIS_SEL}, // CTRL-Del is d # endif }; # endif # if defined(MACOS_X) static struct initmap initmappings[] = { // Use the Standard MacOS binding. // paste, copy and cut {(char_u *)"<D-v> \"*P", MODE_NORMAL}, {(char_u *)"<D-v> \"-d\"*P", VIS_SEL}, {(char_u *)"<D-v> <C-R>*", MODE_INSERT | MODE_CMDLINE}, {(char_u *)"<D-c> \"*y", VIS_SEL}, {(char_u *)"<D-x> \"*d", VIS_SEL}, {(char_u *)"<Backspace> \"-d", VIS_SEL}, }; # endif # undef VIS_SEL #endif /* * Set up default mappings. */ void init_mappings(void) { #if defined(MSWIN) || defined(MACOS_X) int i; # if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL)) # ifdef VIMDLL if (!gui.starting) # endif { for (i = 0; i < (int)ARRAY_LENGTH(cinitmappings); ++i) add_map(cinitmappings[i].arg, cinitmappings[i].mode, FALSE); } # endif # if defined(FEAT_GUI_MSWIN) || defined(MACOS_X) for (i = 0; i < (int)ARRAY_LENGTH(initmappings); ++i) add_map(initmappings[i].arg, initmappings[i].mode, FALSE); # endif #endif } /* * Add a mapping "map" for mode "mode". * When "nore" is TRUE use MAPTYPE_NOREMAP. * Need to put string in allocated memory, because do_map() will modify it. */ void add_map(char_u *map, int mode, int nore) { char_u *s; char_u *cpo_save = p_cpo; p_cpo = empty_option; // Allow <> notation s = vim_strsave(map); if (s != NULL) { (void)do_map(nore ? MAPTYPE_NOREMAP : MAPTYPE_MAP, s, mode, FALSE); vim_free(s); } p_cpo = cpo_save; } #if defined(FEAT_LANGMAP) || defined(PROTO) /* * Any character has an equivalent 'langmap' character. This is used for * keyboards that have a special language mode that sends characters above * 128 (although other characters can be translated too). The "to" field is a * Vim command character. This avoids having to switch the keyboard back to * ASCII mode when leaving Insert mode. * * langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim * commands. * langmap_mapga.ga_data is a sorted table of langmap_entry_T. This does the * same as langmap_mapchar[] for characters >= 256. * * Use growarray for 'langmap' chars >= 256 */ typedef struct { int from; int to; } langmap_entry_T; static garray_T langmap_mapga; /* * Search for an entry in "langmap_mapga" for "from". If found set the "to" * field. If not found insert a new entry at the appropriate location. */ static void langmap_set_entry(int from, int to) { langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data); int a = 0; int b = langmap_mapga.ga_len; // Do a binary search for an existing entry. while (a != b) { int i = (a + b) / 2; int d = entries[i].from - from; if (d == 0) { entries[i].to = to; return; } if (d < 0) a = i + 1; else b = i; } if (ga_grow(&langmap_mapga, 1) == FAIL) return; // out of memory // insert new entry at position "a" entries = (langmap_entry_T *)(langmap_mapga.ga_data) + a; mch_memmove(entries + 1, entries, (langmap_mapga.ga_len - a) * sizeof(langmap_entry_T)); ++langmap_mapga.ga_len; entries[0].from = from; entries[0].to = to; } /* * Apply 'langmap' to multi-byte character "c" and return the result. */ int langmap_adjust_mb(int c) { langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data); int a = 0; int b = langmap_mapga.ga_len; while (a != b) { int i = (a + b) / 2; int d = entries[i].from - c; if (d == 0) return entries[i].to; // found matching entry if (d < 0) a = i + 1; else b = i; } return c; // no entry found, return "c" unmodified } void langmap_init(void) { int i; for (i = 0; i < 256; i++) langmap_mapchar[i] = i; // we init with a one-to-one map ga_init2(&langmap_mapga, sizeof(langmap_entry_T), 8); } /* * Called when langmap option is set; the language map can be * changed at any time! */ char * did_set_langmap(optset_T *args UNUSED) { char_u *p; char_u *p2; int from, to; ga_clear(&langmap_mapga); // clear the previous map first langmap_init(); // back to one-to-one map for (p = p_langmap; p[0] != NUL; ) { for (p2 = p; p2[0] != NUL && p2[0] != ',' && p2[0] != ';'; MB_PTR_ADV(p2)) { if (p2[0] == '\\' && p2[1] != NUL) ++p2; } if (p2[0] == ';') ++p2; // abcd;ABCD form, p2 points to A else p2 = NULL; // aAbBcCdD form, p2 is NULL while (p[0]) { if (p[0] == ',') { ++p; break; } if (p[0] == '\\' && p[1] != NUL) ++p; from = (*mb_ptr2char)(p); to = NUL; if (p2 == NULL) { MB_PTR_ADV(p); if (p[0] != ',') { if (p[0] == '\\') ++p; to = (*mb_ptr2char)(p); } } else { if (p2[0] != ',') { if (p2[0] == '\\') ++p2; to = (*mb_ptr2char)(p2); } } if (to == NUL) { sprintf(args->os_errbuf, _(e_langmap_matching_character_missing_for_str), transchar(from)); return args->os_errbuf; } if (from >= 256) langmap_set_entry(from, to); else langmap_mapchar[from & 255] = to; // Advance to next pair MB_PTR_ADV(p); if (p2 != NULL) { MB_PTR_ADV(p2); if (*p == ';') { p = p2; if (p[0] != NUL) { if (p[0] != ',') { sprintf(args->os_errbuf, _(e_langmap_extra_characters_after_semicolon_str), p); return args->os_errbuf; } ++p; } break; } } } } return NULL; } #endif static void do_exmap(exarg_T *eap, int isabbrev) { int mode; char_u *cmdp; cmdp = eap->cmd; mode = get_map_mode(&cmdp, eap->forceit || isabbrev); switch (do_map(*cmdp == 'n' ? MAPTYPE_NOREMAP : *cmdp == 'u' ? MAPTYPE_UNMAP : MAPTYPE_MAP, eap->arg, mode, isabbrev)) { case 1: emsg(_(e_invalid_argument)); break; case 2: emsg((isabbrev ? _(e_no_such_abbreviation) : _(e_no_such_mapping))); break; } } /* * ":abbreviate" and friends. */ void ex_abbreviate(exarg_T *eap) { do_exmap(eap, TRUE); // almost the same as mapping } /* * ":map" and friends. */ void ex_map(exarg_T *eap) { // If we are sourcing .exrc or .vimrc in current directory we // print the mappings for security reasons. if (secure) { secure = 2; msg_outtrans(eap->cmd); msg_putchar('\n'); } do_exmap(eap, FALSE); } /* * ":unmap" and friends. */ void ex_unmap(exarg_T *eap) { do_exmap(eap, FALSE); } /* * ":mapclear" and friends. */ void ex_mapclear(exarg_T *eap) { map_clear(eap->cmd, eap->arg, eap->forceit, FALSE); } /* * ":abclear" and friends. */ void ex_abclear(exarg_T *eap) { map_clear(eap->cmd, eap->arg, TRUE, TRUE); }