aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/RunTests.cmake4
-rw-r--r--runtime/doc/options.txt8
-rw-r--r--runtime/doc/usr_40.txt11
-rw-r--r--runtime/doc/usr_41.txt32
-rw-r--r--runtime/indent/testdir/vim.in6
-rw-r--r--runtime/indent/testdir/vim.ok6
-rw-r--r--runtime/indent/vim.vim13
-rw-r--r--runtime/lua/vim/filetype.lua2
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim4
-rw-r--r--runtime/syntax/doxygen.vim16
-rwxr-xr-xscripts/vim-patch.sh4
-rw-r--r--src/nvim/api/buffer.c14
-rw-r--r--src/nvim/api/options.c6
-rw-r--r--src/nvim/api/private/helpers.c189
-rw-r--r--src/nvim/api/vim.c1
-rw-r--r--src/nvim/buffer.c1
-rw-r--r--src/nvim/digraph.c1
-rw-r--r--src/nvim/edit.c29
-rw-r--r--src/nvim/eval.c66
-rw-r--r--src/nvim/eval/funcs.c137
-rw-r--r--src/nvim/ex_cmds.c20
-rw-r--r--src/nvim/ex_docmd.c66
-rw-r--r--src/nvim/ex_getln.c1
-rw-r--r--src/nvim/ex_session.c2
-rw-r--r--src/nvim/getchar.c2037
-rw-r--r--src/nvim/getchar.h44
-rw-r--r--src/nvim/highlight_group.c19
-rw-r--r--src/nvim/indent.c6
-rw-r--r--src/nvim/keycodes.c88
-rw-r--r--src/nvim/main.c8
-rw-r--r--src/nvim/mapping.c2540
-rw-r--r--src/nvim/mapping.h54
-rw-r--r--src/nvim/memory.c9
-rw-r--r--src/nvim/menu.c1
-rw-r--r--src/nvim/normal.c1
-rw-r--r--src/nvim/ops.c8
-rw-r--r--src/nvim/option.c171
-rw-r--r--src/nvim/regexp.c64
-rw-r--r--src/nvim/syntax.c2
-rw-r--r--src/nvim/testdir/test_cmdline.vim32
-rw-r--r--src/nvim/testdir/test_lispwords.vim12
-rw-r--r--src/nvim/testdir/test_substitute.vim309
-rw-r--r--src/nvim/testdir/test_syntax.vim88
-rw-r--r--src/nvim/ui.c5
-rw-r--r--src/nvim/ui_compositor.c13
-rw-r--r--src/nvim/window.c1
-rw-r--r--test/functional/api/buffer_spec.lua75
-rw-r--r--test/functional/ex_cmds/map_spec.lua22
-rw-r--r--test/functional/fixtures/CMakeLists.txt2
-rw-r--r--test/functional/lua/vim_spec.lua4
-rw-r--r--test/functional/vimscript/screenchar_spec.lua69
-rw-r--r--test/helpers.lua3
52 files changed, 3515 insertions, 2811 deletions
diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake
index bf5049c5f0..e07c6dd174 100644
--- a/cmake/RunTests.cmake
+++ b/cmake/RunTests.cmake
@@ -51,8 +51,8 @@ if(DEFINED ENV{TEST_FILTER_OUT} AND NOT "$ENV{TEST_FILTER_OUT}" STREQUAL "")
list(APPEND BUSTED_ARGS --filter-out $ENV{TEST_FILTER_OUT})
endif()
-# TMPDIR: use relative test path (for parallel test runs / isolation).
-set(ENV{TMPDIR} "${BUILD_DIR}/Xtest_tmpdir/${TEST_PATH}")
+# TMPDIR: for helpers.tmpname() and Nvim tempname().
+set(ENV{TMPDIR} "${BUILD_DIR}/Xtest_tmpdir")
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory $ENV{TMPDIR})
# HISTFILE: do not write into user's ~/.bash_history
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 5d227a702c..20805377d8 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -2475,8 +2475,9 @@ A jump table for the options with a short description can be found at |Q_op|.
< This is similar to the default, except that these characters will also
be used when there is highlighting.
- For "stl" and "stlnc" single-byte and multibyte characters are
- supported. But double-width characters are not supported.
+ For the "stl", "stlnc", "foldopen", "foldclose" and "foldsep" items
+ single-byte and multibyte characters are supported. But double-width
+ characters are not supported.
The highlighting used for these items:
item highlight group ~
@@ -3667,6 +3668,9 @@ A jump table for the options with a short description can be found at |Q_op|.
executing macros, registers and other commands that have not been
typed. Also, updating the window title is postponed. To force an
update use |:redraw|.
+ This may occasionally cause display errors. It is only meant to be set
+ temporarily when performing an operation where redrawing may cause
+ flickering or cause a slow down.
*'linebreak'* *'lbr'* *'nolinebreak'* *'nolbr'*
'linebreak' 'lbr' boolean (default off)
diff --git a/runtime/doc/usr_40.txt b/runtime/doc/usr_40.txt
index 5b1254e2ae..f47c933124 100644
--- a/runtime/doc/usr_40.txt
+++ b/runtime/doc/usr_40.txt
@@ -463,6 +463,17 @@ separated) that trigger the command.
The optional [++nested] flag allows for nesting of autocommands (see below),
and finally, {command} is the command to be executed.
+When adding an autocommand the already existing ones remain. To avoid adding
+the autocommand several times you should use this form: >
+
+ :augroup updateDate
+ : autocmd!
+ : autocmd BufWritePre * call DateInsert()
+ :augroup END
+
+This will delete any previously defined autocommand with `:autocmd!` before
+defining the new one. Groups are explained later.
+
EVENTS
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 9af65343ef..5acef2f352 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1636,27 +1636,29 @@ manual: |exception-handling|.
Here is a summary of items that apply to Vim scripts. They are also mentioned
elsewhere, but form a nice checklist.
-The end-of-line character depends on the system. For Unix a single <NL>
-character is used. For Windows <CR><NL> is used. This is important when
-using mappings that end in a <CR>. See |:source_crnl|.
+The end-of-line character depends on the system. For Vim scripts it is
+recommended to always use the Unix fileformat. Lines are then separated with
+the Newline character. This also works on any other system. That way you can
+copy your Vim scripts from MS-Windows to Unix and they still work. See
+|:source_crnl|. To be sure it is set right, do this before writing the file:
+ >
+ :setlocal fileformat=unix
+When using "dos" fileformat, lines are separated with CR-NL, two characters.
+The CR character causes various problems, better avoid this.
-WHITE SPACE
-
-Blank lines are allowed and ignored.
-Leading whitespace characters (blanks and TABs) are always ignored. The
-whitespaces between parameters (e.g. between the "set" and the "cpoptions" in
-the example below) are reduced to one blank character and plays the role of a
-separator, the whitespaces after the last (visible) character may or may not
-be ignored depending on the situation, see below.
+WHITE SPACE
-For a ":set" command involving the "=" (equal) sign, such as in: >
+Blank lines are allowed in a script and ignored.
- :set cpoptions =aABceFst
+Leading whitespace characters (blanks and TABs) are ignored, except when using
+|:let-heredoc| without "trim".
-the whitespace immediately before the "=" sign is ignored. But there can be
-no whitespace after the "=" sign!
+Trailing whitespace is often ignored, but not always. One command that
+includes it is `map`. You have to watch out for that, it can cause hard to
+understand mistakes. A generic solution is to never use trailing white space,
+unless you really need it.
To include a whitespace character in the value of an option, it must be
escaped by a "\" (backslash) as in the following example: >
diff --git a/runtime/indent/testdir/vim.in b/runtime/indent/testdir/vim.in
index 699e4c243d..873045bc2c 100644
--- a/runtime/indent/testdir/vim.in
+++ b/runtime/indent/testdir/vim.in
@@ -30,6 +30,12 @@ for x in [
eval 0
endfor
+let t = [
+\ {
+\ 'k': 'val',
+\ },
+\ ]
+
" END_INDENT
" START_INDENT
diff --git a/runtime/indent/testdir/vim.ok b/runtime/indent/testdir/vim.ok
index f597d97e80..8e70abe619 100644
--- a/runtime/indent/testdir/vim.ok
+++ b/runtime/indent/testdir/vim.ok
@@ -30,6 +30,12 @@ for x in [
eval 0
endfor
+let t = [
+ \ {
+ \ 'k': 'val',
+ \ },
+ \ ]
+
" END_INDENT
" START_INDENT
diff --git a/runtime/indent/vim.vim b/runtime/indent/vim.vim
index cd2d4982d8..8076b2df07 100644
--- a/runtime/indent/vim.vim
+++ b/runtime/indent/vim.vim
@@ -1,7 +1,7 @@
" Vim indent file
" Language: Vim script
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Mar 01
+" Last Change: 2022 Jun 24
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
@@ -36,6 +36,14 @@ endfunc
let s:lineContPat = '^\s*\(\\\|"\\ \)'
function GetVimIndentIntern()
+ " If the current line has line continuation and the previous one too, use
+ " the same indent. This does not skip empty lines.
+ let cur_text = getline(v:lnum)
+ let cur_has_linecont = cur_text =~ s:lineContPat
+ if cur_has_linecont && v:lnum > 1 && getline(v:lnum - 1) =~ s:lineContPat
+ return indent(v:lnum - 1)
+ endif
+
" Find a non-blank line above the current line.
let lnum = prevnonblank(v:lnum - 1)
@@ -44,8 +52,7 @@ function GetVimIndentIntern()
" If the current line doesn't start with '\' or '"\ ' and below a line that
" starts with '\' or '"\ ', use the indent of the line above it.
- let cur_text = getline(v:lnum)
- if cur_text !~ s:lineContPat
+ if !cur_has_linecont
while lnum > 0 && getline(lnum) =~ s:lineContPat
let lnum = lnum - 1
endwhile
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 320d6a2a5b..6c4894208f 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -33,7 +33,7 @@ end
function M.getlines(bufnr, start_lnum, end_lnum)
if not end_lnum then
-- Return a single line as a string
- return api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1]
+ return api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1] or ''
end
return api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
end
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index 4cd4a200dd..f76bdebe9b 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -2,7 +2,7 @@
"
" Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license"
-" Last Change: 2022 May 23
+" Last Change: 2022 Jun 22
"
" WORK IN PROGRESS - The basics works stable, more to come
" Note: In general you need at least GDB 7.12 because this provides the
@@ -1406,7 +1406,7 @@ func s:HandleCursor(msg)
echomsg 'different fname: "' .. expand('%:p') .. '" vs "' .. fnamemodify(fname, ':p') .. '"'
augroup Termdebug
" Always open a file read-only instead of showing the ATTENTION
- " prompt, since we are unlikely to want to edit the file.
+ " prompt, since it is unlikely we want to edit the file.
" The file may be changed but not saved, warn for that.
au SwapExists * echohl WarningMsg
\ | echo 'Warning: file is being edited elsewhere'
diff --git a/runtime/syntax/doxygen.vim b/runtime/syntax/doxygen.vim
index 167b17cd0f..36d527b551 100644
--- a/runtime/syntax/doxygen.vim
+++ b/runtime/syntax/doxygen.vim
@@ -498,12 +498,16 @@ endif
syn match doxygenLeadingWhite +\(^\s*\*\)\@<=\s*+ contained
- " This is still a proposal, but won't do any harm.
- aug doxygengroup
- au!
- au Syntax UserColor_reset nested call s:Doxygen_Hilights_Base()
- au Syntax UserColor_{on,reset,enable} nested call s:Doxygen_Hilights()
- aug END
+ " This is still a proposal, but it is probably fine. However, it doesn't
+ " work when 'syntax' is set in a modeline, catch the security error.
+ try
+ aug doxygengroup
+ au!
+ au Syntax UserColor_reset nested call s:Doxygen_Hilights_Base()
+ au Syntax UserColor_{on,reset,enable} nested call s:Doxygen_Hilights()
+ aug END
+ catch /E12:/
+ endtry
SynLink doxygenBody Comment
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index 472b79cbf4..d5424f51ab 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -233,6 +233,10 @@ preprocess_patch() {
LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/userfunc\.c/\1\/eval\/userfunc\.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
+ # Rename map.c to mapping.c
+ LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/map\(\.[ch]\)/\1\/mapping\2/g' \
+ "$file" > "$file".tmp && mv "$file".tmp "$file"
+
# Rename session.c to ex_session.c
LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/session\(\.[ch]\)/\1\/ex_session\2/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 9dc95de243..1504004c6c 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -22,6 +22,7 @@
#include "nvim/ex_docmd.h"
#include "nvim/extmark.h"
#include "nvim/lua/executor.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -553,13 +554,13 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// check range is ordered and everything!
// start_row, end_row within buffer len (except add text past the end?)
start_row = normalize_index(buf, start_row, false, &oob);
- if (oob || start_row == buf->b_ml.ml_line_count + 1) {
+ if (oob) {
api_set_error(err, kErrorTypeValidation, "start_row out of bounds");
return;
}
end_row = normalize_index(buf, end_row, false, &oob);
- if (oob || end_row == buf->b_ml.ml_line_count + 1) {
+ if (oob) {
api_set_error(err, kErrorTypeValidation, "end_row out of bounds");
return;
}
@@ -1358,14 +1359,15 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
// Normalizes 0-based indexes to buffer line numbers
static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
{
- int64_t line_count = buf->b_ml.ml_line_count;
+ assert(buf->b_ml.ml_line_count > 0);
+ int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1;
// Fix if < 0
- index = index < 0 ? line_count + index + (int)end_exclusive : index;
+ index = index < 0 ? max_index + index + 1 : index;
// Check for oob
- if (index > line_count) {
+ if (index > max_index) {
*oob = true;
- index = line_count;
+ index = max_index;
} else if (index < 0) {
*oob = true;
index = 0;
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index 3766f47b20..8c174fc129 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -43,6 +43,9 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t
if (opts->win.type == kObjectTypeInteger) {
*opt_type = SREQ_WIN;
*from = find_window_by_handle((int)opts->win.data.integer, err);
+ if (ERROR_SET(err)) {
+ return FAIL;
+ }
} else if (HAS_KEY(opts->win)) {
api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
return FAIL;
@@ -52,6 +55,9 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t
*scope = OPT_LOCAL;
*opt_type = SREQ_BUF;
*from = find_buffer_by_handle((int)opts->buf.data.integer, err);
+ if (ERROR_SET(err)) {
+ return FAIL;
+ }
} else if (HAS_KEY(opts->buf)) {
api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
return FAIL;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 9cadef0385..436bcd5212 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -21,7 +21,6 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/fileio.h"
-#include "nvim/getchar.h"
#include "nvim/highlight_group.h"
#include "nvim/lib/kvec.h"
#include "nvim/lua/executor.h"
@@ -441,142 +440,6 @@ Array string_to_array(const String input, bool crlf)
return ret;
}
-/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
-/// functions like @ref nvim_buf_set_keymap.
-///
-/// Arguments are handled like @ref nvim_set_keymap unless noted.
-/// @param buffer Buffer handle for a specific buffer, or 0 for the current
-/// buffer, or -1 to signify global behavior ("all buffers")
-/// @param is_unmap When true, removes the mapping that matches {lhs}.
-void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mode, String lhs,
- String rhs, Dict(keymap) *opts, Error *err)
-{
- LuaRef lua_funcref = LUA_NOREF;
- bool global = (buffer == -1);
- if (global) {
- buffer = 0;
- }
- buf_T *target_buf = find_buffer_by_handle(buffer, err);
-
- if (!target_buf) {
- return;
- }
-
- const sctx_T save_current_sctx = api_set_sctx(channel_id);
-
- if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) {
- lua_funcref = opts->callback.data.luaref;
- opts->callback.data.luaref = LUA_NOREF;
- }
- MapArguments parsed_args = MAP_ARGUMENTS_INIT;
- if (opts) {
-#define KEY_TO_BOOL(name) \
- parsed_args.name = api_object_to_bool(opts->name, #name, false, err); \
- if (ERROR_SET(err)) { \
- goto fail_and_free; \
- }
-
- KEY_TO_BOOL(nowait);
- KEY_TO_BOOL(noremap);
- KEY_TO_BOOL(silent);
- KEY_TO_BOOL(script);
- KEY_TO_BOOL(expr);
- KEY_TO_BOOL(unique);
-#undef KEY_TO_BOOL
- }
- parsed_args.buffer = !global;
-
- set_maparg_lhs_rhs(lhs.data, lhs.size,
- rhs.data, rhs.size, lua_funcref,
- CPO_TO_CPO_FLAGS, &parsed_args);
- if (opts != NULL && opts->desc.type == kObjectTypeString) {
- parsed_args.desc = string_to_cstr(opts->desc.data.string);
- } else {
- parsed_args.desc = NULL;
- }
- if (parsed_args.lhs_len > MAXMAPLEN || parsed_args.alt_lhs_len > MAXMAPLEN) {
- api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data);
- goto fail_and_free;
- }
-
- if (mode.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Shortname is too long: %s", mode.data);
- goto fail_and_free;
- }
- int mode_val; // integer value of the mapping mode, to be passed to do_map()
- char *p = (mode.size) ? mode.data : "m";
- if (STRNCMP(p, "!", 2) == 0) {
- mode_val = get_map_mode(&p, true); // mapmode-ic
- } else {
- mode_val = get_map_mode(&p, false);
- if (mode_val == (MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING) && mode.size > 0) {
- // get_map_mode() treats unrecognized mode shortnames as ":map".
- // This is an error unless the given shortname was empty string "".
- api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", p);
- goto fail_and_free;
- }
- }
-
- if (parsed_args.lhs_len == 0) {
- api_set_error(err, kErrorTypeValidation, "Invalid (empty) LHS");
- goto fail_and_free;
- }
-
- bool is_noremap = parsed_args.noremap;
- assert(!(is_unmap && is_noremap));
-
- if (!is_unmap && lua_funcref == LUA_NOREF
- && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
- if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop>
- parsed_args.rhs_is_noop = true;
- } else {
- abort(); // should never happen
- }
- } else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) {
- if (parsed_args.rhs_len) {
- api_set_error(err, kErrorTypeValidation,
- "Gave nonempty RHS in unmap command: %s", parsed_args.rhs);
- } else {
- api_set_error(err, kErrorTypeValidation, "Gave nonempty RHS for unmap");
- }
- goto fail_and_free;
- }
-
- // buf_do_map() reads noremap/unmap as its own argument.
- int maptype_val = 0;
- if (is_unmap) {
- maptype_val = 1;
- } else if (is_noremap) {
- maptype_val = 2;
- }
-
- switch (buf_do_map(maptype_val, &parsed_args, mode_val, 0, target_buf)) {
- case 0:
- break;
- case 1:
- api_set_error(err, kErrorTypeException, (char *)e_invarg, 0);
- goto fail_and_free;
- case 2:
- api_set_error(err, kErrorTypeException, (char *)e_nomap, 0);
- goto fail_and_free;
- case 5:
- api_set_error(err, kErrorTypeException,
- "E227: mapping already exists for %s", parsed_args.lhs);
- goto fail_and_free;
- default:
- assert(false && "Unrecognized return code!");
- goto fail_and_free;
- } // switch
-
- parsed_args.rhs_lua = LUA_NOREF; // don't clear ref on success
-fail_and_free:
- current_sctx = save_current_sctx;
- NLUA_CLEAR_REF(parsed_args.rhs_lua);
- xfree(parsed_args.rhs);
- xfree(parsed_args.orig_rhs);
- XFREE_CLEAR(parsed_args.desc);
-}
-
/// Collects `n` buffer lines into array `l`, optionally replacing newlines
/// with NUL.
///
@@ -930,58 +793,6 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...)
err->type = errType;
}
-/// Get an array containing dictionaries describing mappings
-/// based on mode and buffer id
-///
-/// @param mode The abbreviation for the mode
-/// @param buf The buffer to get the mapping array. NULL for global
-/// @param from_lua Whether it is called from internal lua api.
-/// @returns Array of maparg()-like dictionaries describing mappings
-ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
-{
- Array mappings = ARRAY_DICT_INIT;
- dict_T *const dict = tv_dict_alloc();
-
- // Convert the string mode to the integer mode
- // that is stored within each mapblock
- char *p = mode.data;
- int int_mode = get_map_mode(&p, 0);
-
- // Determine the desired buffer value
- long buffer_value = (buf == NULL) ? 0 : buf->handle;
-
- for (int i = 0; i < MAX_MAPHASH; i++) {
- for (const mapblock_T *current_maphash = get_maphash(i, buf);
- current_maphash;
- current_maphash = current_maphash->m_next) {
- if (current_maphash->m_simplified) {
- continue;
- }
- // Check for correct mode
- if (int_mode & current_maphash->m_mode) {
- mapblock_fill_dict(dict, current_maphash, buffer_value, false);
- Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT,
- .vval.v_dict = dict } });
- if (from_lua) {
- Dictionary d = api_dict.data.dictionary;
- for (size_t j = 0; j < d.size; j++) {
- if (strequal("callback", d.items[j].key.data)) {
- d.items[j].value.type = kObjectTypeLuaRef;
- d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer);
- break;
- }
- }
- }
- ADD(mappings, api_dict);
- tv_dict_clear(dict);
- }
- }
- }
- tv_dict_free(dict);
-
- return mappings;
-}
-
/// Force obj to bool.
/// If it fails, returns false and sets err
/// @param obj The object to coerce to a boolean
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index b7df1398f5..c5881dbc5f 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -38,6 +38,7 @@
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index f13f6e35ea..85e34e910d 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -53,6 +53,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index b461082be0..99e99a8a9e 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -18,6 +18,7 @@
#include "nvim/ex_getln.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
+#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 8f7ba03b14..e0717ef8bc 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -31,6 +31,7 @@
#include "nvim/indent_c.h"
#include "nvim/keycodes.h"
#include "nvim/main.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -6768,34 +6769,6 @@ void free_last_insert(void)
#endif
-/// Add character "c" to buffer "s"
-///
-/// Escapes the special meaning of K_SPECIAL, handles multi-byte
-/// characters.
-///
-/// @param[in] c Character to add.
-/// @param[out] s Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes.
-///
-/// @return Pointer to after the added bytes.
-char_u *add_char2buf(int c, char_u *s)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
- char_u temp[MB_MAXBYTES + 1];
- const int len = utf_char2bytes(c, (char *)temp);
- for (int i = 0; i < len; i++) {
- c = (uint8_t)temp[i];
- // Need to escape K_SPECIAL like in the typeahead buffer.
- if (c == K_SPECIAL) {
- *s++ = K_SPECIAL;
- *s++ = KS_SPECIAL;
- *s++ = KE_FILLER;
- } else {
- *s++ = (char_u)c;
- }
- }
- return s;
-}
-
/*
* move cursor to start of line
* if flags & BL_WHITE move to first non-white
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index a4f56b47e6..be2df9488e 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -49,6 +49,7 @@
#include "nvim/sign.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
+#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
#include "nvim/version.h"
#include "nvim/window.h"
@@ -6790,57 +6791,6 @@ char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
return argv;
}
-/// Fill a dictionary with all applicable maparg() like dictionaries
-///
-/// @param dict The dictionary to be filled
-/// @param mp The maphash that contains the mapping information
-/// @param buffer_value The "buffer" value
-/// @param compatible True for compatible with old maparg() dict
-void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buffer_value,
- bool compatible)
- FUNC_ATTR_NONNULL_ALL
-{
- char *const lhs = str2special_save((const char *)mp->m_keys,
- compatible, !compatible);
- char *const mapmode = map_mode_to_chars(mp->m_mode);
- varnumber_T noremap_value;
-
- if (compatible) {
- // Keep old compatible behavior
- // This is unable to determine whether a mapping is a <script> mapping
- noremap_value = !!mp->m_noremap;
- } else {
- // Distinguish between <script> mapping
- // If it's not a <script> mapping, check if it's a noremap
- noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap;
- }
-
- if (mp->m_luaref != LUA_NOREF) {
- tv_dict_add_nr(dict, S_LEN("callback"), mp->m_luaref);
- } else {
- if (compatible) {
- tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str);
- } else {
- tv_dict_add_allocated_str(dict, S_LEN("rhs"),
- str2special_save((const char *)mp->m_str, false,
- true));
- }
- }
- if (mp->m_desc != NULL) {
- tv_dict_add_allocated_str(dict, S_LEN("desc"), xstrdup(mp->m_desc));
- }
- tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs);
- tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value);
- tv_dict_add_nr(dict, S_LEN("script"), mp->m_noremap == REMAP_SCRIPT ? 1 : 0);
- tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0);
- tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0);
- tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ctx.sc_sid);
- tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)mp->m_script_ctx.sc_lnum);
- tv_dict_add_nr(dict, S_LEN("buffer"), (varnumber_T)buffer_value);
- tv_dict_add_nr(dict, S_LEN("nowait"), mp->m_nowait ? 1 : 0);
- tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode);
-}
-
void return_register(int regname, typval_T *rettv)
{
char buf[2] = { (char)regname, 0 };
@@ -6849,19 +6799,19 @@ void return_register(int regname, typval_T *rettv)
rettv->vval.v_string = xstrdup(buf);
}
-void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col)
+void screenchar_adjust(ScreenGrid **grid, int *row, int *col)
{
// TODO(bfredl): this is a hack for legacy tests which use screenchar()
// to check printed messages on the screen (but not floats etc
// as these are not legacy features). If the compositor is refactored to
// have its own buffer, this should just read from it instead.
msg_scroll_flush();
- if (msg_grid.chars && msg_grid.comp_index > 0 && *row >= msg_grid.comp_row
- && *row < (msg_grid.rows + msg_grid.comp_row)
- && *col < msg_grid.cols) {
- *grid = &msg_grid;
- *row -= msg_grid.comp_row;
- }
+
+ *grid = ui_comp_get_grid_at_coord(*row, *col);
+
+ // Make `row` and `col` relative to the grid
+ *row -= (*grid)->comp_row;
+ *col -= (*grid)->comp_col;
}
/// Set line or list of lines in buffer "buf".
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 2225076a0a..f7d6b016fd 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -38,6 +38,7 @@
#include "nvim/input.h"
#include "nvim/lua/executor.h"
#include "nvim/macros.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/match.h"
#include "nvim/math.h"
@@ -5647,89 +5648,6 @@ static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = (varnumber_T)time(NULL);
}
-static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
-{
- char *keys_buf = NULL;
- char_u *alt_keys_buf = NULL;
- bool did_simplify = false;
- char_u *rhs;
- LuaRef rhs_lua;
- int mode;
- int abbr = FALSE;
- int get_dict = FALSE;
- mapblock_T *mp;
- 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;
-
- char *keys = (char *)tv_get_string(&argvars[0]);
- if (*keys == NUL) {
- return;
- }
-
- char buf[NUMBUFLEN];
- const char *which;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- which = tv_get_string_buf_chk(&argvars[1], buf);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- abbr = tv_get_number(&argvars[2]);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- get_dict = tv_get_number(&argvars[3]);
- }
- }
- } else {
- which = "";
- }
- if (which == NULL) {
- return;
- }
-
- mode = get_map_mode((char **)&which, 0);
-
- char_u *keys_simplified
- = (char_u *)replace_termcodes(keys,
- STRLEN(keys), &keys_buf, flags, &did_simplify,
- CPO_TO_CPO_FLAGS);
- rhs = check_map(keys_simplified, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
- 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,
- STRLEN(keys),
- (char **)&alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL,
- CPO_TO_CPO_FLAGS);
- rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
- }
-
- if (!get_dict) {
- // Return a string.
- if (rhs != NULL) {
- if (*rhs == NUL) {
- rettv->vval.v_string = xstrdup("<Nop>");
- } else {
- rettv->vval.v_string = str2special_save((char *)rhs, false, false);
- }
- } else if (rhs_lua != LUA_NOREF) {
- size_t msglen = 100;
- char *msg = (char *)xmalloc(msglen);
- snprintf(msg, msglen, "<Lua function %d>", mp->m_luaref);
- rettv->vval.v_string = msg;
- }
- } else {
- tv_dict_alloc_ret(rettv);
- if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) {
- // Return a dictionary.
- mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true);
- }
- }
-
- xfree(keys_buf);
- xfree(alt_keys_buf);
-}
-
/// luaeval() function implementation
static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
FUNC_ATTR_NONNULL_ALL
@@ -5748,18 +5666,6 @@ static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr)
filter_map(argvars, rettv, TRUE);
}
-/// "maparg()" function
-static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_maparg(argvars, rettv, TRUE);
-}
-
-/// "mapcheck()" function
-static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_maparg(argvars, rettv, FALSE);
-}
-
static void find_some_match(typval_T *const argvars, typval_T *const rettv,
const SomeMatchType type)
{
@@ -7766,6 +7672,9 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
break;
}
}
+
+ // clear the start flag to avoid getting stuck here
+ options &= ~SEARCH_START;
}
if (subpatnum != FAIL) {
@@ -8035,14 +7944,15 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int c;
+ ScreenGrid *grid;
int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
- if (row < 0 || row >= default_grid.rows
- || col < 0 || col >= default_grid.cols) {
+
+ screenchar_adjust(&grid, &row, &col);
+
+ if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
c = -1;
} else {
- ScreenGrid *grid = &default_grid;
- screenchar_adjust_grid(&grid, &row, &col);
c = grid->attrs[grid->line_offset[row] + col];
}
rettv->vval.v_number = c;
@@ -8053,14 +7963,15 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int c;
+ ScreenGrid *grid;
int row = tv_get_number_chk(&argvars[0], NULL) - 1;
int col = tv_get_number_chk(&argvars[1], NULL) - 1;
- if (row < 0 || row >= default_grid.rows
- || col < 0 || col >= default_grid.cols) {
+
+ screenchar_adjust(&grid, &row, &col);
+
+ if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
c = -1;
} else {
- ScreenGrid *grid = &default_grid;
- screenchar_adjust_grid(&grid, &row, &col);
c = utf_ptr2char((char *)grid->chars[grid->line_offset[row] + col]);
}
rettv->vval.v_number = c;
@@ -8069,15 +7980,16 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "screenchars()" function
static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
+ ScreenGrid *grid;
int row = tv_get_number_chk(&argvars[0], NULL) - 1;
int col = tv_get_number_chk(&argvars[1], NULL) - 1;
- if (row < 0 || row >= default_grid.rows
- || col < 0 || col >= default_grid.cols) {
+
+ screenchar_adjust(&grid, &row, &col);
+
+ if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
tv_list_alloc_ret(rettv, 0);
return;
}
- ScreenGrid *grid = &default_grid;
- screenchar_adjust_grid(&grid, &row, &col);
int pcc[MAX_MCO];
int c = utfc_ptr2char(grid->chars[grid->line_offset[row] + col], pcc);
int composing_len = 0;
@@ -8136,14 +8048,17 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
rettv->vval.v_string = NULL;
rettv->v_type = VAR_STRING;
+
+ ScreenGrid *grid;
int row = tv_get_number_chk(&argvars[0], NULL) - 1;
int col = tv_get_number_chk(&argvars[1], NULL) - 1;
- if (row < 0 || row >= default_grid.rows
- || col < 0 || col >= default_grid.cols) {
+
+ screenchar_adjust(&grid, &row, &col);
+
+ if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
return;
}
- ScreenGrid *grid = &default_grid;
- screenchar_adjust_grid(&grid, &row, &col);
+
rettv->vval.v_string = (char *)vim_strsave(grid->chars[grid->line_offset[row] + col]);
}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 73e6e4a62f..a1f0a123b1 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -3468,7 +3468,6 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
static int pre_hl_id = 0;
pos_T old_cursor = curwin->w_cursor;
int start_nsubs;
- int save_ma = 0;
bool did_save = false;
@@ -4060,7 +4059,8 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// there is a replace pattern.
if (!cmdpreview || has_second_delim) {
long lnum_start = lnum; // save the start lnum
- save_ma = curbuf->b_p_ma;
+ int save_ma = curbuf->b_p_ma;
+ int save_sandbox = sandbox;
if (subflags.do_count) {
// prevent accidentally changing the buffer by a function
curbuf->b_p_ma = false;
@@ -4069,20 +4069,24 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// Save flags for recursion. They can change for e.g.
// :s/^/\=execute("s#^##gn")
subflags_T subflags_save = subflags;
- // get length of substitution part
+
+ // Disallow changing text or switching window in an expression.
+ textlock++;
+ // Get length of substitution part, including the NUL.
+ // When it fails sublen is zero.
sublen = vim_regsub_multi(&regmatch,
sub_firstlnum - regmatch.startpos[0].lnum,
(char_u *)sub, (char_u *)sub_firstline, 0,
REGSUB_BACKSLASH | (p_magic ? REGSUB_MAGIC : 0));
+ textlock--;
+
// If getting the substitute string caused an error, don't do
// the replacement.
// Don't keep flags set by a recursive call
subflags = subflags_save;
- if (aborting() || subflags.do_count) {
+ if (sublen == 0 || aborting() || subflags.do_count) {
curbuf->b_p_ma = save_ma;
- if (sandbox > 0) {
- sandbox--;
- }
+ sandbox = save_sandbox;
goto skip;
}
@@ -4111,10 +4115,12 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
int start_col = new_end - new_start;
current_match.start.col = start_col;
+ textlock++;
(void)vim_regsub_multi(&regmatch,
sub_firstlnum - regmatch.startpos[0].lnum,
(char_u *)sub, (char_u *)new_end, sublen,
REGSUB_COPY | REGSUB_BACKSLASH | (p_magic ? REGSUB_MAGIC : 0));
+ textlock--;
sub_nsubs++;
did_sub = true;
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index a65e89a9f5..671e83def6 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -44,6 +44,7 @@
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/match.h"
#include "nvim/mbyte.h"
@@ -2826,10 +2827,13 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
curwin->w_cursor.lnum = eap->line2;
// Don't leave the cursor on an illegal line or column, but do
- // accept zero as address, so 0;/PATTERN/ works correctly.
+ // accept zero as address, so 0;/PATTERN/ works correctly
+ // (where zero usually means to use the first line).
// Check the cursor position before returning.
if (eap->line2 > 0) {
check_cursor();
+ } else {
+ check_cursor_col();
}
need_check_cursor = true;
}
@@ -2888,11 +2892,13 @@ static void append_command(char *cmd)
STRCAT(IObuff, ": ");
d = (char *)IObuff + STRLEN(IObuff);
- while (*s != NUL && (char_u *)d - IObuff < IOSIZE - 7) {
+ while (*s != NUL && (char_u *)d - IObuff + 5 < IOSIZE) {
if ((char_u)s[0] == 0xc2 && (char_u)s[1] == 0xa0) {
s += 2;
STRCPY(d, "<a0>");
d += 4;
+ } else if ((char_u *)d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) {
+ break;
} else {
mb_copy_char((const char_u **)&s, (char_u **)&d);
}
@@ -5253,45 +5259,6 @@ theend:
return tab_number;
}
-/// ":abbreviate" and friends.
-static void ex_abbreviate(exarg_T *eap)
-{
- do_exmap(eap, TRUE); // almost the same as mapping
-}
-
-/// ":map" and friends.
-static 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((char_u *)eap->cmd);
- msg_putchar('\n');
- }
- do_exmap(eap, FALSE);
-}
-
-/// ":unmap" and friends.
-static void ex_unmap(exarg_T *eap)
-{
- do_exmap(eap, FALSE);
-}
-
-/// ":mapclear" and friends.
-static void ex_mapclear(exarg_T *eap)
-{
- map_clear_mode((char_u *)eap->cmd, (char_u *)eap->arg, eap->forceit, false);
-}
-
-/// ":abclear" and friends.
-static void ex_abclear(exarg_T *eap)
-{
- map_clear_mode((char_u *)eap->cmd, (char_u *)eap->arg, true, true);
-}
-
static void ex_autocmd(exarg_T *eap)
{
// Disallow autocommands from .exrc and .vimrc in current
@@ -8378,23 +8345,6 @@ void do_sleep(long msec)
}
}
-static void do_exmap(exarg_T *eap, int isabbrev)
-{
- int mode;
- char *cmdp = eap->cmd;
- mode = get_map_mode(&cmdp, eap->forceit || isabbrev);
-
- switch (do_map((*cmdp == 'n') ? 2 : (*cmdp == 'u'),
- (char_u *)eap->arg, mode, isabbrev)) {
- case 1:
- emsg(_(e_invarg));
- break;
- case 2:
- emsg(isabbrev ? _(e_noabbr) : _(e_nomap));
- break;
- }
-}
-
/// ":winsize" command (obsolete).
static void ex_winsize(exarg_T *eap)
{
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 211eeff45e..e442c907b3 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -46,6 +46,7 @@
#include "nvim/log.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 3b6f7b90bf..e60be80923 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -25,9 +25,9 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
-#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/keycodes.h"
+#include "nvim/mapping.h"
#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/os/input.h"
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 7f783fd72f..d11088ed6b 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1,14 +1,8 @@
// 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
-/*
- * getchar.c
- *
- * functions related with getting a character from the user/mapping/redo/...
- *
- * manipulations with redo buffer and stuff buffer
- * mappings and abbreviations
- */
+// getchar.c: Code related to getting a character from the user or a script
+// file, manipulations with redo buffer and stuff buffer.
#include <assert.h>
#include <inttypes.h>
@@ -22,18 +16,15 @@
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
-#include "nvim/eval.h"
#include "nvim/event/loop.h"
-#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
-#include "nvim/ex_session.h"
-#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/input.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
+#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -46,7 +37,6 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/plines.h"
-#include "nvim/regexp.h"
#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/strings.h"
@@ -91,26 +81,6 @@ static int typeahead_char = 0; // typeahead char that's not flushed
*/
static int block_redo = FALSE;
-// Make a hash value for a mapping.
-// "mode" is the lower 4 bits of the State for the mapping.
-// "c1" is the first character of the "lhs".
-// Returns a value between 0 and 255, index in maphash.
-// Put Normal/Visual mode mappings mostly separately from Insert/Cmdline mode.
-#define MAP_HASH(mode, \
- c1) (((mode) & \
- (MODE_NORMAL | MODE_VISUAL | MODE_SELECT | \
- MODE_OP_PENDING | MODE_TERMINAL)) ? (c1) : ((c1) ^ 0x80))
-
-// Each mapping is put in one of the MAX_MAPHASH hash lists,
-// to speed up finding it.
-static mapblock_T *(maphash[MAX_MAPHASH]);
-static bool maphash_valid = false;
-
-/*
- * List used for abbreviations.
- */
-static mapblock_T *first_abbr = NULL; // first entry in abbrlist
-
static int KeyNoremap = 0; // remapping flags
/*
@@ -837,18 +807,10 @@ static void init_typebuf(void)
}
}
-void init_default_mappings(void)
+/// @return true when keys cannot be remapped.
+bool noremap_keys(void)
{
- add_map((char_u *)"Y y$", MODE_NORMAL, true);
-
- // Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O>
- // See https://github.com/neovim/neovim/issues/17473
- add_map((char_u *)"<C-L> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>",
- MODE_NORMAL, true);
- add_map((char_u *)"<C-U> <C-G>u<C-U>", MODE_INSERT, true);
- add_map((char_u *)"<C-W> <C-G>u<C-W>", MODE_INSERT, true);
- add_map((char_u *)"* y/\\\\V<C-R>\"<CR>", MODE_VISUAL, true);
- add_map((char_u *)"# y?\\\\V<C-R>\"<CR>", MODE_VISUAL, true);
+ return KeyNoremap & (RM_NONE|RM_SCRIPT);
}
// Insert a string in position 'offset' in the typeahead buffer (for "@r"
@@ -1860,7 +1822,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
// - waiting for a char with --more--
// - in Ctrl-X mode, and we get a valid char for that mode
tb_c1 = typebuf.tb_buf[typebuf.tb_off];
- if (no_mapping == 0 && maphash_valid
+ if (no_mapping == 0
&& (no_zero_mapping == 0 || tb_c1 != '0')
&& (typebuf.tb_maplen == 0 || is_plug_map
|| (!(typebuf.tb_noremap[typebuf.tb_off] & (RM_NONE|RM_ABBR))))
@@ -1877,8 +1839,8 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
nolmaplen = 0;
}
// First try buffer-local mappings.
- mp = curbuf->b_maphash[MAP_HASH(local_State, tb_c1)];
- mp2 = maphash[MAP_HASH(local_State, tb_c1)];
+ mp = get_buf_maphash_list(local_State, tb_c1);
+ mp2 = get_maphash_list(local_State, tb_c1);
if (mp == NULL) {
// There are no buffer-local mappings.
mp = mp2;
@@ -2122,6 +2084,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
const bool save_may_garbage_collect = may_garbage_collect;
const int save_cursor_row = ui_current_row();
const int save_cursor_col = ui_current_col();
+ const handle_T save_cursor_grid = ui_cursor_grid();
const int prev_did_emsg = did_emsg;
vgetc_busy = 0;
@@ -2135,7 +2098,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
// The mapping may do anything, but we expect it to take care of
// redrawing. Do put the cursor back where it was.
- ui_cursor_goto(save_cursor_row, save_cursor_col);
+ ui_grid_cursor_goto(save_cursor_grid, save_cursor_row, save_cursor_col);
ui_flush();
// If an error was displayed and the expression returns an empty
@@ -2815,1970 +2778,6 @@ int fix_input_buffer(char_u *buf, int len)
return len;
}
-/// 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 <Nop>, `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 maps.
-/// @param[in] orig_rhs_len `strlen` of orig_rhs.
-/// @param[in] cpo_flags See param docs for @ref replace_termcodes.
-/// @param[out] mapargs MapArguments struct holding the replaced strings.
-void set_maparg_lhs_rhs(const char *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 = NULL;
- char *alt_lhs_buf = NULL;
- char *rhs_buf = NULL;
-
- // If mapping has been given as ^V<C_UP> say, then replace the term codes
- // with the appropriate two bytes. If it is a shifted special key, unshift
- // it too, giving another two bytes.
- //
- // replace_termcodes() may move the result to allocated memory, which
- // needs to be freed later (*lhs_buf and *rhs_buf).
- // replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
- // If something like <C-H> is simplified to 0x08 then mark it as simplified.
- bool did_simplify = false;
- const int flags = REPTERM_FROM_PART | REPTERM_DO_LT;
- char *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &lhs_buf, flags, &did_simplify,
- cpo_flags);
- mapargs->lhs_len = STRLEN(replaced);
- STRLCPY(mapargs->lhs, replaced, sizeof(mapargs->lhs));
- if (did_simplify) {
- replaced = replace_termcodes(orig_lhs, orig_lhs_len, &alt_lhs_buf, flags | REPTERM_NO_SIMPLIFY,
- NULL, cpo_flags);
- mapargs->alt_lhs_len = STRLEN(replaced);
- STRLCPY(mapargs->alt_lhs, replaced, sizeof(mapargs->alt_lhs));
- } else {
- mapargs->alt_lhs_len = 0;
- }
-
- 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_u));
- STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1);
-
- if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing
- mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char
- mapargs->rhs_len = 0;
- mapargs->rhs_is_noop = true;
- } else {
- replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL,
- cpo_flags);
- mapargs->rhs_len = STRLEN(replaced);
- // XXX: 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 = xcalloc(mapargs->rhs_len + 1, sizeof(char_u));
- STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1);
- }
- } 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_u));
- mapargs->orig_rhs_len = 0;
- // stores <lua>ref_no<cr> 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 = vim_strsave((char_u *)tmp_buf);
- }
-
- xfree(lhs_buf);
- xfree(alt_lhs_buf);
- xfree(rhs_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. "<buffer> <expr><silent>".
-/// May contain leading or trailing whitespace.
-/// @param[in] is_unmap True, if strargs should be parsed like an |:unmap|
-/// command. |:unmap| commands interpret *all* text to the
-/// right of the last map argument as the {lhs} of the
-/// mapping, i.e. a literal ' ' character is treated like
-/// a "<space>", rather than separating the {lhs} from the
-/// {rhs}.
-/// @param[out] mapargs MapArguments struct holding all extracted argument
-/// values.
-/// @return 0 on success, 1 if invalid arguments are detected.
-int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs)
-{
- const char_u *to_parse = strargs;
- to_parse = (char_u *)skipwhite((char *)to_parse);
- MapArguments parsed_args; // copy these into mapargs "all at once" when done
- memset(&parsed_args, 0, sizeof(parsed_args));
-
- // Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in
- // any order.
- while (true) {
- if (STRNCMP(to_parse, "<buffer>", 8) == 0) {
- to_parse = (char_u *)skipwhite((char *)to_parse + 8);
- parsed_args.buffer = true;
- continue;
- }
-
- if (STRNCMP(to_parse, "<nowait>", 8) == 0) {
- to_parse = (char_u *)skipwhite((char *)to_parse + 8);
- parsed_args.nowait = true;
- continue;
- }
-
- if (STRNCMP(to_parse, "<silent>", 8) == 0) {
- to_parse = (char_u *)skipwhite((char *)to_parse + 8);
- parsed_args.silent = true;
- continue;
- }
-
- // Ignore obsolete "<special>" modifier.
- if (STRNCMP(to_parse, "<special>", 9) == 0) {
- to_parse = (char_u *)skipwhite((char *)to_parse + 9);
- continue;
- }
-
- if (STRNCMP(to_parse, "<script>", 8) == 0) {
- to_parse = (char_u *)skipwhite((char *)to_parse + 8);
- parsed_args.script = true;
- continue;
- }
-
- if (STRNCMP(to_parse, "<expr>", 6) == 0) {
- to_parse = (char_u *)skipwhite((char *)to_parse + 6);
- parsed_args.expr = true;
- continue;
- }
-
- if (STRNCMP(to_parse, "<unique>", 8) == 0) {
- to_parse = (char_u *)skipwhite((char *)to_parse + 8);
- parsed_args.unique = true;
- continue;
- }
- break;
- }
-
- // Find the next whitespace character, call that the end of {lhs}.
- //
- // If a character (e.g. whitespace) is immediately preceded by a CTRL-V,
- // "scan past" that character, i.e. don't "terminate" LHS with that character
- // if it's whitespace.
- //
- // Treat backslash like CTRL-V when 'cpoptions' does not contain 'B'.
- //
- // With :unmap, literal white space is included in the {lhs}; there is no
- // separate {rhs}.
- const char *lhs_end = (char *)to_parse;
- bool do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
- while (*lhs_end && (is_unmap || !ascii_iswhite(*lhs_end))) {
- if ((lhs_end[0] == Ctrl_V || (do_backslash && lhs_end[0] == '\\'))
- && lhs_end[1] != NUL) {
- lhs_end++; // skip CTRL-V or backslash
- }
- lhs_end++;
- }
-
- // {lhs_end} is a pointer to the "terminating whitespace" after {lhs}.
- // Use that to initialize {rhs_start}.
- const char_u *rhs_start = (char_u *)skipwhite((char *)lhs_end);
-
- // Given {lhs} might be larger than MAXMAPLEN before replace_termcodes
- // (e.g. "<Space>" is longer than ' '), so first copy into a buffer.
- size_t orig_lhs_len = (size_t)((char_u *)lhs_end - to_parse);
- char_u *lhs_to_replace = xcalloc(orig_lhs_len + 1, sizeof(char_u));
- STRLCPY(lhs_to_replace, to_parse, orig_lhs_len + 1);
-
- size_t orig_rhs_len = STRLEN(rhs_start);
- set_maparg_lhs_rhs((char *)lhs_to_replace, orig_lhs_len,
- (char *)rhs_start, orig_rhs_len, LUA_NOREF,
- CPO_TO_CPO_FLAGS, &parsed_args);
-
- xfree(lhs_to_replace);
-
- *mapargs = parsed_args;
-
- if (parsed_args.lhs_len > MAXMAPLEN) {
- return 1;
- }
- return 0;
-}
-
-/// Sets or removes a mapping or abbreviation in buffer `buf`.
-///
-/// @param maptype @see do_map
-/// @param args Fully parsed and "preprocessed" arguments for the
-/// (un)map/abbrev command. Termcodes should have already been
-/// replaced; whitespace, `<` and `>` signs, etc. in {lhs} and
-/// {rhs} are assumed to be literal components of the mapping.
-/// @param mode @see do_map
-/// @param is_abbrev @see do_map
-/// @param buf Target Buffer
-int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T *buf)
-{
- mapblock_T *mp, **mpp;
- const char_u *p;
- int n;
- int retval = 0;
- mapblock_T **abbr_table;
- mapblock_T **map_table;
- int noremap;
-
- map_table = maphash;
- abbr_table = &first_abbr;
-
- // For ":noremap" don't remap, otherwise do remap.
- if (maptype == 2) {
- noremap = REMAP_NONE;
- } else {
- noremap = REMAP_YES;
- }
-
- if (args->buffer) {
- // If <buffer> was given, we'll be searching through the buffer's
- // mappings/abbreviations, not the globals.
- map_table = buf->b_maphash;
- abbr_table = &buf->b_first_abbr;
- }
- if (args->script) {
- noremap = REMAP_SCRIPT;
- }
-
- validate_maphash();
-
- const bool has_lhs = (args->lhs[0] != NUL);
- const bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop;
- const bool do_print = !has_lhs || (maptype != 1 && !has_rhs);
-
- // check for :unmap without argument
- if (maptype == 1 && !has_lhs) {
- retval = 1;
- goto theend;
- }
-
- const char_u *lhs = (char_u *)&args->lhs;
- const char_u *const rhs = args->rhs;
- const char_u *const orig_rhs = args->orig_rhs;
- const bool did_simplify = args->alt_lhs_len != 0;
-
- // The following is done twice if we have two versions of keys
- for (int keyround = 1; keyround <= 2; keyround++) {
- bool did_it = false;
- bool did_local = false;
- bool keyround1_simplified = keyround == 1 && did_simplify;
- int len = (int)args->lhs_len;
-
- if (keyround == 2) {
- if (!did_simplify) {
- break;
- }
- lhs = (char_u *)&args->alt_lhs;
- len = (int)args->alt_lhs_len;
- } else if (did_simplify && do_print) {
- // when printing always use the not-simplified map
- lhs = (char_u *)&args->alt_lhs;
- len = (int)args->alt_lhs_len;
- }
-
- // check arguments and translate function keys
- if (has_lhs) {
- if (len > MAXMAPLEN) {
- retval = 1;
- goto theend;
- }
-
- if (is_abbrev && maptype != 1) {
- //
- // If an abbreviation ends in a keyword character, the
- // rest must be all keyword-char or all non-keyword-char.
- // Otherwise we won't be able to find the start of it in a
- // vi-compatible way.
- //
- int same = -1;
-
- const int first = vim_iswordp(lhs);
- int last = first;
- p = lhs + utfc_ptr2len((char *)lhs);
- n = 1;
- while (p < lhs + len) {
- n++; // nr of (multi-byte) chars
- last = vim_iswordp(p); // type of last char
- if (same == -1 && last != first) {
- same = n - 1; // count of same char type
- }
- p += utfc_ptr2len((char *)p);
- }
- if (last && n > 2 && same >= 0 && same < n - 1) {
- retval = 1;
- goto theend;
- }
- // An abbreviation cannot contain white space.
- for (n = 0; n < len; n++) {
- if (ascii_iswhite(lhs[n])) {
- retval = 1;
- goto theend;
- }
- } // for
- }
- }
-
- if (has_lhs && has_rhs && is_abbrev) { // if we will add an abbreviation,
- no_abbr = false; // reset flag that indicates there are no abbreviations
- }
-
- if (do_print) {
- msg_start();
- }
-
- // Check if a new local mapping wasn't already defined globally.
- if (map_table == buf->b_maphash && has_lhs && has_rhs && maptype != 1) {
- // need to loop over all global hash lists
- for (int hash = 0; hash < 256 && !got_int; hash++) {
- if (is_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
- && args->unique
- && STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) {
- if (is_abbrev) {
- semsg(_("E224: global abbreviation already exists for %s"),
- mp->m_keys);
- } else {
- semsg(_("E225: global mapping already exists for %s"), mp->m_keys);
- }
- retval = 5;
- goto theend;
- }
- }
- }
- }
-
- // When listing global mappings, also list buffer-local ones here.
- if (map_table != buf->b_maphash && !has_rhs && maptype != 1) {
- // need to loop over all global hash lists
- for (int hash = 0; hash < 256 && !got_int; hash++) {
- if (is_abbrev) {
- if (hash != 0) { // there is only one abbreviation list
- break;
- }
- mp = buf->b_first_abbr;
- } else {
- mp = buf->b_maphash[hash];
- }
- for (; mp != NULL && !got_int; mp = mp->m_next) {
- // check entries with the same mode
- if (!mp->m_simplified && (mp->m_mode & mode) != 0) {
- if (!has_lhs) { // show all entries
- showmap(mp, true);
- did_local = true;
- } else {
- n = mp->m_keylen;
- if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
- showmap(mp, true);
- did_local = true;
- }
- }
- }
- }
- }
- }
-
- // Find an entry in the maphash[] list that matches.
- // For :unmap we may loop two times: once to try to unmap an entry with a
- // matching 'from' part, a second time, if the first fails, to unmap an
- // entry with a matching 'to' part. This was done to allow ":ab foo bar"
- // to be unmapped by typing ":unab foo", where "foo" will be replaced by
- // "bar" because of the abbreviation.
- for (int round = 0; (round == 0 || maptype == 1) && round <= 1
- && !did_it && !got_int; round++) {
- // need to loop over all hash lists
- for (int hash = 0; hash < 256 && !got_int; hash++) {
- if (is_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 (!has_lhs) { // 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, lhs, (size_t)(n < len ? n : len)) == 0) {
- if (maptype == 1) {
- // Delete entry.
- // Only accept a full match. For abbreviations
- // we ignore trailing space when matching with
- // the "lhs", since an abbreviation can't have
- // trailing space.
- if (n != len && (!is_abbrev || round || n > len
- || *skipwhite((char *)lhs + 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 (!has_rhs) { // 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 (keyround1_simplified && !mp->m_simplified) {
- // In keyround for simplified keys, don't replace
- // a mapping without m_simplified flag.
- did_it = true;
- break;
- } else if (args->unique) {
- if (is_abbrev) {
- semsg(_("E226: abbreviation already exists for %s"), p);
- } else {
- semsg(_("E227: mapping already exists for %s"), p);
- }
- retval = 5;
- goto theend;
- } else {
- // new rhs for existing entry
- mp->m_mode &= ~mode; // remove mode bits
- if (mp->m_mode == 0 && !did_it) { // reuse entry
- XFREE_CLEAR(mp->m_str);
- XFREE_CLEAR(mp->m_orig_str);
- XFREE_CLEAR(mp->m_desc);
- if (!mp->m_simplified) {
- NLUA_CLEAR_REF(mp->m_luaref);
- }
-
- mp->m_str = vim_strsave(rhs);
- mp->m_orig_str = vim_strsave(orig_rhs);
- mp->m_luaref = args->rhs_lua;
- mp->m_noremap = noremap;
- mp->m_nowait = args->nowait;
- mp->m_silent = args->silent;
- mp->m_mode = mode;
- mp->m_simplified = keyround1_simplified;
- mp->m_expr = args->expr;
- mp->m_script_ctx = current_sctx;
- mp->m_script_ctx.sc_lnum += sourcing_lnum;
- nlua_set_sctx(&mp->m_script_ctx);
- if (args->desc != NULL) {
- mp->m_desc = xstrdup(args->desc);
- }
- did_it = true;
- }
- }
- if (mp->m_mode == 0) { // entry can be deleted
- mapblock_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 (!is_abbrev && new_hash != hash) {
- *mpp = mp->m_next;
- mp->m_next = map_table[new_hash];
- map_table[new_hash] = mp;
-
- continue; // continue with *mpp
- }
- }
- }
- mpp = &(mp->m_next);
- }
- }
- }
-
- if (maptype == 1) {
- // delete entry
- if (!did_it) {
- if (!keyround1_simplified) {
- retval = 2; // no match
- }
- } else if (*lhs == Ctrl_C) {
- // If CTRL-C has been unmapped, reuse it for Interrupting.
- if (map_table == buf->b_maphash) {
- buf->b_mapped_ctrl_c &= ~mode;
- } else {
- mapped_ctrl_c &= ~mode;
- }
- }
- continue;
- }
-
- if (!has_lhs || !has_rhs) {
- // print entries
- if (!did_it && !did_local) {
- if (is_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.
- mp = xmalloc(sizeof(mapblock_T));
-
- // If CTRL-C has been mapped, don't always use it for Interrupting.
- if (*lhs == Ctrl_C) {
- if (map_table == buf->b_maphash) {
- buf->b_mapped_ctrl_c |= mode;
- } else {
- mapped_ctrl_c |= mode;
- }
- }
-
- mp->m_keys = vim_strsave(lhs);
- mp->m_str = vim_strsave(rhs);
- mp->m_orig_str = vim_strsave(orig_rhs);
- mp->m_luaref = args->rhs_lua;
- mp->m_keylen = (int)STRLEN(mp->m_keys);
- mp->m_noremap = noremap;
- mp->m_nowait = args->nowait;
- mp->m_silent = args->silent;
- mp->m_mode = mode;
- mp->m_simplified = keyround1_simplified; // Notice this when porting patch 8.2.0807
- mp->m_expr = args->expr;
- mp->m_script_ctx = current_sctx;
- mp->m_script_ctx.sc_lnum += sourcing_lnum;
- nlua_set_sctx(&mp->m_script_ctx);
- mp->m_desc = NULL;
- if (args->desc != NULL) {
- mp->m_desc = xstrdup(args->desc);
- }
-
- // add the new entry in front of the abbrlist or maphash[] list
- if (is_abbrev) {
- mp->m_next = *abbr_table;
- *abbr_table = mp;
- } else {
- n = MAP_HASH(mp->m_mode, mp->m_keys[0]);
- mp->m_next = map_table[n];
- map_table[n] = mp;
- }
- }
-
-theend:
- return retval;
-}
-
-/// Set or remove a mapping or an abbreviation in the current buffer, OR
-/// display (matching) mappings/abbreviations.
-///
-/// ```vim
-/// map[!] " show all key mappings
-/// map[!] {lhs} " show key mapping for {lhs}
-/// map[!] {lhs} {rhs} " set key mapping for {lhs} to {rhs}
-/// noremap[!] {lhs} {rhs} " same, but no remapping for {rhs}
-/// unmap[!] {lhs} " remove key mapping for {lhs}
-/// abbr " show all abbreviations
-/// abbr {lhs} " show abbreviations for {lhs}
-/// abbr {lhs} {rhs} " set abbreviation for {lhs} to {rhs}
-/// noreabbr {lhs} {rhs} " same, but no remapping for {rhs}
-/// unabbr {lhs} " remove abbreviation for {lhs}
-///
-/// for :map mode is 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
-/// ```
-///
-/// @param maptype 0 for |:map|, 1 for |:unmap|, 2 for |noremap|.
-/// @param arg C-string containing the arguments of the map/abbrev
-/// command, i.e. everything except the initial `:[X][nore]map`.
-/// - Cannot be a read-only string; it will be modified.
-/// @param mode Bitflags representing the mode in which to set the mapping.
-/// See @ref get_map_mode.
-/// @param is_abbrev True if setting an abbreviation, false otherwise.
-///
-/// @return 0 on success. On failure, will return one of the following:
-/// - 1 for invalid arguments
-/// - 2 for no match
-/// - 4 for out of mem (deprecated, WON'T HAPPEN)
-/// - 5 for entry not unique
-///
-int do_map(int maptype, char_u *arg, int mode, bool is_abbrev)
-{
- MapArguments parsed_args;
- int result = str_to_mapargs(arg, maptype == 1, &parsed_args);
- switch (result) {
- case 0:
- break;
- case 1:
- // invalid arguments
- goto free_and_return;
- default:
- assert(false && "Unknown return code from str_to_mapargs!");
- result = -1;
- goto free_and_return;
- } // switch
-
- result = buf_do_map(maptype, &parsed_args, mode, is_abbrev, curbuf);
-
-free_and_return:
- xfree(parsed_args.rhs);
- xfree(parsed_args.orig_rhs);
- return result;
-}
-
-/*
- * Delete one entry from the abbrlist or maphash[].
- * "mpp" is a pointer to the m_next field of the PREVIOUS entry!
- */
-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_CLEAR(mp->m_str);
- XFREE_CLEAR(mp->m_orig_str);
- XFREE_CLEAR(mp->m_desc);
- *mpp = mp->m_next;
- xfree(mp);
-}
-
-/*
- * Initialize maphash[] for first use.
- */
-static void validate_maphash(void)
-{
- if (!maphash_valid) {
- memset(maphash, 0, sizeof(maphash));
- maphash_valid = TRUE;
- }
-}
-
-/*
- * Get the mapping mode from the command name.
- */
-int get_map_mode(char **cmdp, bool forceit)
-{
- char *p;
- int modec;
- int mode;
-
- p = *cmdp;
- modec = (uint8_t)(*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 or abbreviations.
- * 'abbr' should be FALSE for mappings, TRUE for abbreviations.
- */
-void map_clear_mode(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_invarg));
- return;
- }
-
- mode = get_map_mode((char **)&cmdp, forceit);
- map_clear_int(curbuf, mode,
- local,
- abbr);
-}
-
-/// Clear all mappings in "mode".
-///
-/// @param buf, buffer for local mappings
-/// @param mode mode in which to delete
-/// @param local true for buffer-local mappings
-/// @param abbr true for abbreviations
-void map_clear_int(buf_T *buf, int mode, bool local, bool abbr)
-{
- mapblock_T *mp, **mpp;
- int hash;
- int new_hash;
-
- validate_maphash();
-
- for (hash = 0; hash < 256; ++hash) {
- if (abbr) {
- if (hash > 0) { // there is only one abbrlist
- break;
- }
- if (local) {
- mpp = &buf->b_first_abbr;
- } else {
- mpp = &first_abbr;
- }
- } else {
- if (local) {
- mpp = &buf->b_maphash[hash];
- } else {
- mpp = &maphash[hash];
- }
- }
- while (*mpp != NULL) {
- mp = *mpp;
- if (mp->m_mode & mode) {
- mp->m_mode &= ~mode;
- if (mp->m_mode == 0) { // entry can be deleted
- mapblock_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);
- }
- }
-}
-
-/// Return characters to represent the map mode in an allocated string
-///
-/// @return [allocated] NUL-terminated string with characters.
-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((char_u *)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(' '); // padd with blanks
- len++;
- } while (len < 12);
-
- if (mp->m_noremap == REMAP_NONE) {
- msg_puts_attr("*", HL_ATTR(HLF_8));
- } else if (mp->m_noremap == REMAP_SCRIPT) {
- msg_puts_attr("&", HL_ATTR(HLF_8));
- } else {
- msg_putchar(' ');
- }
-
- if (local) {
- msg_putchar('@');
- } else {
- msg_putchar(' ');
- }
-
- /* Use FALSE below if we only want things like <Up> to show up as such on
- * the rhs, and not M-x etc, TRUE gets both -- webb */
- if (mp->m_luaref != LUA_NOREF) {
- char msg[100];
- snprintf(msg, sizeof(msg), "<Lua function %d>", mp->m_luaref);
- msg_puts_attr(msg, HL_ATTR(HLF_8));
- } else if (mp->m_str[0] == NUL) {
- msg_puts_attr("<Nop>", 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();
- ui_flush(); // show one line at a time
-}
-
-/// Check if a map exists that has given string in the rhs
-///
-/// Also checks mappings local to the current buffer.
-///
-/// @param[in] str String which mapping must have in the rhs. Termcap codes
-/// are recognized in this argument.
-/// @param[in] modechars Mode(s) in which mappings are checked.
-/// @param[in] abbr true if checking abbreviations in place of mappings.
-///
-/// @return true if there is at least one mapping with given parameters.
-bool map_to_exists(const char *const str, const char *const modechars, const bool abbr)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
-{
- int mode = 0;
- int retval;
-
- char_u *buf;
- const char_u *const rhs = (char_u *)replace_termcodes(str, strlen(str),
- (char **)&buf, REPTERM_DO_LT,
- NULL, CPO_TO_CPO_FLAGS);
-
-#define MAPMODE(mode, modechars, chr, modeflags) \
- do { \
- if (strchr(modechars, chr) != NULL) { \
- (mode) |= (modeflags); \
- } \
- } while (0)
- MAPMODE(mode, modechars, 'n', MODE_NORMAL);
- MAPMODE(mode, modechars, 'v', MODE_VISUAL | MODE_SELECT);
- MAPMODE(mode, modechars, 'x', MODE_VISUAL);
- MAPMODE(mode, modechars, 's', MODE_SELECT);
- MAPMODE(mode, modechars, 'o', MODE_OP_PENDING);
- MAPMODE(mode, modechars, 'i', MODE_INSERT);
- MAPMODE(mode, modechars, 'l', MODE_LANGMAP);
- MAPMODE(mode, modechars, 'c', MODE_CMDLINE);
-#undef MAPMODE
-
- retval = map_to_exists_mode((char *)rhs, mode, abbr);
- xfree(buf);
-
- return retval;
-}
-
-/// Check if a map exists that has given string in the rhs
-///
-/// Also checks mappings local to the current buffer.
-///
-/// @param[in] rhs String which mapping must have in the rhs. Termcap codes
-/// are recognized in this argument.
-/// @param[in] mode Mode(s) in which mappings are checked.
-/// @param[in] abbr true if checking abbreviations in place of mappings.
-///
-/// @return true if there is at least one mapping with given parameters.
-int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
-{
- mapblock_T *mp;
- int hash;
- bool 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, 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 bool expand_isabbrev = false;
-static bool expand_buffer = false;
-
-/// Work out what to complete when doing command line completion of mapping
-/// or abbreviation names.
-///
-/// @param forceit true if '!' given
-/// @param isabbrev true if abbreviation
-/// @param isunmap true if unmap/unabbrev command
-char_u *set_context_in_map_cmd(expand_T *xp, char_u *cmd, char_u *arg, bool forceit, bool isabbrev,
- bool isunmap, cmdidx_T cmdidx)
-{
- if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) {
- xp->xp_context = EXPAND_NOTHING;
- } else {
- if (isunmap) {
- expand_mapmodes = get_map_mode((char **)&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 = (char_u *)skipwhite((char *)arg + 8);
- continue;
- }
- if (STRNCMP(arg, "<unique>", 8) == 0) {
- arg = (char_u *)skipwhite((char *)arg + 8);
- continue;
- }
- if (STRNCMP(arg, "<nowait>", 8) == 0) {
- arg = (char_u *)skipwhite((char *)arg + 8);
- continue;
- }
- if (STRNCMP(arg, "<silent>", 8) == 0) {
- arg = (char_u *)skipwhite((char *)arg + 8);
- continue;
- }
- if (STRNCMP(arg, "<special>", 9) == 0) {
- arg = (char_u *)skipwhite((char *)arg + 9);
- continue;
- }
- if (STRNCMP(arg, "<script>", 8) == 0) {
- arg = (char_u *)skipwhite((char *)arg + 8);
- continue;
- }
- if (STRNCMP(arg, "<expr>", 6) == 0) {
- arg = (char_u *)skipwhite((char *)arg + 6);
- continue;
- }
- break;
- }
- xp->xp_pattern = (char *)arg;
- }
-
- return NULL;
-}
-
-// Find all mapping/abbreviation names that match regexp "regmatch".
-// For command line expansion of ":[un]map" and ":[un]abbrev" in all modes.
-// Return OK if matches found, FAIL otherwise.
-int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file)
-{
- mapblock_T *mp;
- int hash;
- int count;
- int round;
- char_u *p;
- int i;
-
- validate_maphash();
-
- *num_file = 0; // return values in case of FAIL
- *file = NULL;
-
- /*
- * round == 1: Count the matches.
- * round == 2: Build the array to keep the matches.
- */
- for (round = 1; round <= 2; ++round) {
- count = 0;
-
- for (i = 0; i < 7; i++) {
- if (i == 0) {
- p = (char_u *)"<silent>";
- } else if (i == 1) {
- p = (char_u *)"<unique>";
- } else if (i == 2) {
- p = (char_u *)"<script>";
- } else if (i == 3) {
- p = (char_u *)"<expr>";
- } else if (i == 4 && !expand_buffer) {
- p = (char_u *)"<buffer>";
- } else if (i == 5) {
- p = (char_u *)"<nowait>";
- } else if (i == 6) {
- p = (char_u *)"<special>";
- } else {
- continue;
- }
-
- if (vim_regexec(regmatch, p, (colnr_T)0)) {
- if (round == 1) {
- ++count;
- } else {
- (*file)[count++] = vim_strsave(p);
- }
- }
- }
-
- for (hash = 0; hash < 256; ++hash) {
- if (expand_isabbrev) {
- if (hash > 0) { // only one abbrev list
- break; // for (hash)
- }
- mp = first_abbr;
- } else if (expand_buffer) {
- mp = curbuf->b_maphash[hash];
- } else {
- mp = maphash[hash];
- }
- for (; mp; mp = mp->m_next) {
- if (mp->m_mode & expand_mapmodes) {
- p = translate_mapping(mp->m_keys, CPO_TO_CPO_FLAGS);
- if (p != NULL && vim_regexec(regmatch, p, (colnr_T)0)) {
- if (round == 1) {
- ++count;
- } else {
- (*file)[count++] = p;
- p = NULL;
- }
- }
- xfree(p);
- }
- } // for (mp)
- } // for (hash)
-
- if (count == 0) { // no match found
- break; // for (round)
- }
-
- if (round == 1) {
- *file = (char_u **)xmalloc((size_t)count * sizeof(char_u *));
- }
- } // for (round)
-
- if (count > 1) {
- char_u **ptr1;
- char_u **ptr2;
- char_u **ptr3;
-
- // Sort the matches
- sort_strings(*file, count);
-
- // Remove multiple entries
- ptr1 = *file;
- ptr2 = ptr1 + 1;
- ptr3 = ptr1 + count;
-
- while (ptr2 < ptr3) {
- if (STRCMP(*ptr1, *ptr2)) {
- *++ptr1 = *ptr2++;
- } else {
- xfree(*ptr2++);
- count--;
- }
- }
- }
-
- *num_file = 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.
-bool 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
- bool is_id = true;
-
- if (typebuf.tb_no_abbr_cnt) { // abbrev. are not recursive
- return false;
- }
-
- // no remapping implies no abbreviation, except for CTRL-]
- if ((KeyNoremap & (RM_NONE|RM_SCRIPT)) != 0 && 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;
- }
-
- {
- bool vim_abbr;
- char_u *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 (ascii_isspace(*p) || (!vim_abbr && is_id != vim_iswordp(p))) {
- p += utfc_ptr2len((char *)p);
- break;
- }
- ++clen;
- }
- scol = (int)(p - ptr);
- }
-
- 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 (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) {
- // Might have K_SPECIAL escaped mp->m_keys.
- q = vim_strsave(mp->m_keys);
- vim_unescape_ks(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) {
- xfree(q);
- }
- if (match) {
- break;
- }
- }
- if (mp != NULL) {
- /*
- * 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++] = (char_u)K_SECOND(c);
- tb[j++] = (char_u)K_THIRD(c);
- } else {
- if (c < ABBR_OFF && (c < ' ' || c > '~')) {
- tb[j++] = Ctrl_V; // special char needs CTRL-V
- }
- // if ABBR_OFF has been added, remove it here.
- if (c >= ABBR_OFF) {
- c -= ABBR_OFF;
- }
- int newlen = utf_char2bytes(c, (char *)tb + j);
- tb[j + newlen] = NUL;
- // Need to escape K_SPECIAL.
- char_u *escaped = (char_u *)vim_strsave_escape_ks((char *)tb + j);
- if (escaped != NULL) {
- newlen = (int)STRLEN(escaped);
- memmove(tb + j, escaped, (size_t)newlen);
- j += newlen;
- xfree(escaped);
- }
- }
- tb[j] = NUL;
- // insert the last typed char
- (void)ins_typebuf((char *)tb, 1, 0, true, mp->m_silent);
- }
- if (mp->m_expr) {
- s = eval_map_expr(mp, c);
- } else {
- s = mp->m_str;
- }
- if (s != NULL) {
- // insert the to string
- (void)ins_typebuf((char *)s, mp->m_noremap, 0, true, mp->m_silent);
- // no abbrev. for these chars
- typebuf.tb_no_abbr_cnt += (int)STRLEN(s) + j + 1;
- if (mp->m_expr) {
- xfree(s);
- }
- }
-
- tb[0] = Ctrl_H;
- tb[1] = NUL;
- len = clen; // Delete characters instead of bytes
- while (len-- > 0) { // delete the from string
- (void)ins_typebuf((char *)tb, 1, 0, true, mp->m_silent);
- }
- return true;
- }
- }
- return false;
-}
-
-/// Evaluate the RHS of a mapping or abbreviations and take care of escaping
-/// special characters.
-///
-/// @param c NUL or typed character for abbreviation
-static char_u *eval_map_expr(mapblock_T *mp, int c)
-{
- char_u *res;
- char_u *p = NULL;
- char_u *expr = NULL;
- pos_T save_cursor;
- int save_msg_col;
- int save_msg_row;
-
- // Remove escaping of K_SPECIAL, because "str" is in a format to be used as
- // typeahead.
- if (mp->m_luaref == LUA_NOREF) {
- expr = vim_strsave(mp->m_str);
- vim_unescape_ks(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_luaref != LUA_NOREF) {
- Error err = ERROR_INIT;
- Array args = ARRAY_DICT_INIT;
- Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err);
- if (ret.type == kObjectTypeString) {
- p = (char_u *)xstrndup(ret.data.string.data, ret.data.string.size);
- }
- api_free_object(ret);
- if (err.type != kErrorTypeNone) {
- semsg_multiline("E5108: %s", err.msg);
- api_clear_error(&err);
- }
- } else {
- p = (char_u *)eval_to_string((char *)expr, NULL, false);
- xfree(expr);
- }
- textlock--;
- ex_normal_lock--;
- curwin->w_cursor = save_cursor;
- msg_col = save_msg_col;
- msg_row = save_msg_row;
-
- if (p == NULL) {
- return NULL;
- }
- // Escape K_SPECIAL in the result to be able to use the string as typeahead.
- res = (char_u *)vim_strsave_escape_ks((char *)p);
- xfree(p);
-
- return res;
-}
-
-/// Copy "p" to allocated memory, escaping K_SPECIAL so that the result
-/// can be put in the typeahead buffer.
-char *vim_strsave_escape_ks(char *p)
-{
- // 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
- char_u *res = xmalloc(STRLEN(p) * 4 + 1);
- char_u *d = res;
- for (char_u *s = (char_u *)p; *s != NUL;) {
- if (s[0] == K_SPECIAL && 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
- // K_SPECIAL. Be careful, it can be an illegal byte!
- d = add_char2buf(utf_ptr2char((char *)s), d);
- s += utf_ptr2len((char *)s);
- }
- }
- *d = NUL;
-
- return (char *)res;
-}
-
-/// Remove escaping from K_SPECIAL characters. Reverse of
-/// vim_strsave_escape_ks(). Works in-place.
-void vim_unescape_ks(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 {
- *d++ = *s++;
- }
- }
- *d = NUL;
-}
-
-/// Write map commands for the current mappings to an .exrc file.
-/// Return FAIL on error, OK otherwise.
-///
-/// @param buf buffer for local mappings or NULL
-int makemap(FILE *fd, buf_T *buf)
-{
- mapblock_T *mp;
- char_u c1, c2, c3;
- char_u *p;
- char *cmd;
- int abbr;
- int hash;
- bool did_cpo = false;
-
- 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 lua mappings and mappings that contain a <SNR> (script-local thing),
- // they probably don't work when loaded again
- if (mp->m_luaref != LUA_NOREF) {
- continue;
- }
- for (p = mp->m_str; *p != NUL; p++) {
- if (p[0] == K_SPECIAL && p[1] == KS_EXTRA
- && p[2] == 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(_("E228: 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 {
- const char specials[] = { (char)(uint8_t)K_SPECIAL, NL, NUL };
- if (strpbrk((const char *)mp->m_str, specials) != NULL
- || strpbrk((const char *)mp->m_keys, specials) != NULL) {
- 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;
- }
- if (mp->m_expr && fputs(" <expr>", fd) < 0) {
- return FAIL;
- }
-
- 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;
-
- // :map xx <Nop>
- if (*str == NUL && what == 1) {
- if (fprintf(fd, "<Nop>") < 0) {
- return FAIL;
- }
- return OK;
- }
-
- for (; *str != NUL; str++) {
- // Check for a multi-byte character, which may contain escaped
- // K_SPECIAL bytes.
- const char *p = mb_unescape((const char **)&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) {
- int 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 && (ascii_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 the string "keys" against the lhs of all mappings.
-/// Return pointer to rhs of mapping (mapblock->m_str).
-/// NULL when no mapping found.
-///
-/// @param exact require exact match
-/// @param ign_mod ignore preceding modifier
-/// @param abbr do abbreviations
-/// @param mp_ptr return: pointer to mapblock or NULL
-/// @param local_ptr return: buffer-local mapping or NULL
-char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr,
- int *local_ptr, int *rhs_lua)
-{
- int len, minlen;
- mapblock_T *mp;
- *rhs_lua = LUA_NOREF;
-
- validate_maphash();
-
- len = (int)STRLEN(keys);
- for (int local = 1; local >= 0; local--) {
- // loop over all hash lists
- for (int 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)) {
- char_u *s = mp->m_keys;
- int keylen = mp->m_keylen;
- if (ign_mod && keylen >= 3
- && s[0] == K_SPECIAL && s[1] == KS_MODIFIER) {
- s += 3;
- keylen -= 3;
- }
- minlen = keylen < len ? keylen : len;
- if (STRNCMP(s, keys, minlen) == 0) {
- if (mp_ptr != NULL) {
- *mp_ptr = mp;
- }
- if (local_ptr != NULL) {
- *local_ptr = local;
- }
- *rhs_lua = mp->m_luaref;
- return mp->m_luaref == LUA_NOREF ? mp->m_str : NULL;
- }
- }
- }
- }
- }
-
- return NULL;
-}
-
-/// Add a mapping. Unlike @ref do_map this copies the {map} argument, so
-/// static or read-only strings can be used.
-///
-/// @param map C-string containing the arguments of the map/abbrev command,
-/// i.e. everything except the initial `:[X][nore]map`.
-/// @param mode Bitflags representing the mode in which to set the mapping.
-/// See @ref get_map_mode.
-/// @param nore If true, make a non-recursive mapping.
-void add_map(char_u *map, int mode, bool nore)
-{
- char_u *s;
- char *cpo_save = p_cpo;
-
- p_cpo = ""; // Allow <> notation
- // Need to put string in allocated memory, because do_map() will modify it.
- s = vim_strsave(map);
- (void)do_map(nore ? 2 : 0, s, mode, false);
- xfree(s);
- p_cpo = cpo_save;
-}
-
-/// Translate an internal mapping/abbreviation representation into the
-/// corresponding external one recognized by :map/:abbrev commands.
-///
-/// 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.
-///
-/// @param cpo_flags Value of various flags present in &cpo
-///
-/// @return NULL when there is a problem.
-static char_u *translate_mapping(char_u *str, int cpo_flags)
-{
- garray_T ga;
- ga_init(&ga, 1, 40);
-
- bool cpo_bslash = !(cpo_flags&FLAG_CPO_BSLASH);
-
- for (; *str; ++str) {
- int c = *str;
- if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) {
- int modifiers = 0;
- if (str[1] == KS_MODIFIER) {
- str++;
- modifiers = *++str;
- c = *++str;
- }
-
- if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) {
- 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
- ga_concat(&ga, (char *)get_special_key_name(c, modifiers));
- continue; // for (str)
- }
- }
-
- if (c == ' ' || c == '\t' || c == Ctrl_J || c == Ctrl_V
- || (c == '\\' && !cpo_bslash)) {
- ga_append(&ga, cpo_bslash ? Ctrl_V : '\\');
- }
-
- if (c) {
- ga_append(&ga, (char)c);
- }
- }
- ga_append(&ga, NUL);
- return (char_u *)(ga.ga_data);
-}
-
static bool typebuf_match_len(const uint8_t *str, int *mlen)
{
int i;
@@ -4791,20 +2790,6 @@ static bool typebuf_match_len(const uint8_t *str, int *mlen)
return str[i] == NUL; // matched the whole string
}
-/// 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];
-}
-
/// Get command argument for <Cmd> key
char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
{
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index ddd6d81aef..6996b00c6e 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -1,10 +1,7 @@
#ifndef NVIM_GETCHAR_H
#define NVIM_GETCHAR_H
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
#include "nvim/os/fileio.h"
-#include "nvim/types.h"
#include "nvim/vim.h"
/// Values for "noremap" argument of ins_typebuf()
@@ -24,47 +21,6 @@ typedef enum {
FLUSH_INPUT, // flush typebuf and inchar() input
} flush_buffers_T;
-/// All possible |:map-arguments| usable in a |:map| command.
-///
-/// The <special> argument has no effect on mappings and is excluded from this
-/// struct declaration. |noremap| is included, since it behaves like a map
-/// argument when used in a mapping.
-///
-/// @see mapblock_T
-struct map_arguments {
- bool buffer;
- bool expr;
- bool noremap;
- bool nowait;
- bool script;
- bool silent;
- bool unique;
-
- /// The {lhs} of the mapping.
- ///
- /// vim limits this to MAXMAPLEN characters, allowing us to use a static
- /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal
- /// that {lhs} was too long and truncated.
- char_u lhs[MAXMAPLEN + 1];
- size_t lhs_len;
-
- /// Unsimplifed {lhs} of the mapping. If no simplification has been done then alt_lhs_len is 0.
- char_u alt_lhs[MAXMAPLEN + 1];
- size_t alt_lhs_len;
-
- char_u *rhs; /// The {rhs} of the mapping.
- size_t rhs_len;
- LuaRef rhs_lua; /// lua function as {rhs}
- bool rhs_is_noop; /// True when the {rhs} should be <Nop>.
-
- char_u *orig_rhs; /// The original text of the {rhs}.
- size_t orig_rhs_len;
- char *desc; /// map description
-};
-typedef struct map_arguments MapArguments;
-#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \
- { 0 }, 0, { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL }
-
#define KEYLEN_PART_KEY (-1) // keylen value for incomplete key-code
#define KEYLEN_PART_MAP (-2) // keylen value for incomplete mapping
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 93d6e65040..ac7f6c4317 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -1751,6 +1751,8 @@ static int syn_add_group(const char *name, size_t len)
if (highlight_ga.ga_data == NULL) {
highlight_ga.ga_itemsize = sizeof(HlGroup);
ga_set_growsize(&highlight_ga, 10);
+ // 265 builtin groups, will always be used, plus some space
+ ga_grow(&highlight_ga, 300);
}
if (highlight_ga.ga_len >= MAX_HL_ID) {
@@ -2765,10 +2767,19 @@ RgbValue name_to_color(const char *name, int *idx)
return normal_fg;
}
- for (int i = 0; color_name_table[i].name != NULL; i++) {
- if (!STRICMP(name, color_name_table[i].name)) {
- *idx = i;
- return color_name_table[i].color;
+ int lo = 0;
+ int hi = ARRAY_SIZE(color_name_table) - 1; // don't count NULL element
+ while (lo < hi) {
+ int m = (lo + hi) / 2;
+ int cmp = STRICMP(name, color_name_table[m].name);
+ if (cmp < 0) {
+ hi = m;
+ } else if (cmp > 0) {
+ lo = m + 1;
+ } else { // found match
+ *idx = m;
+ return color_name_table[m].color;
+ break;
}
}
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index 010d2fe869..d71b3adb5c 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -697,8 +697,10 @@ int get_lisp_indent(void)
&& lisp_match(that + 1)) {
amount += 2;
} else {
- that++;
- amount++;
+ if (*that != NUL) {
+ that++;
+ amount++;
+ }
firsttry = amount;
while (ascii_iswhite(*that)) {
diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c
index 676ddcf8d4..dd78ebe722 100644
--- a/src/nvim/keycodes.c
+++ b/src/nvim/keycodes.c
@@ -861,6 +861,9 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag)
/// @param[in] from What characters to replace.
/// @param[in] from_len Length of the "from" argument.
/// @param[out] bufp Location where results were saved in case of success (allocated).
+/// if *bufp is non-NULL, it will be used directly. it is
+/// assumed to be 128 bytes long (enough for transcoding LHS
+/// of mapping)
/// Will be set to NULL in case of failure.
/// @param[in] flags REPTERM_FROM_PART see above
/// REPTERM_DO_LT also translate <lt>
@@ -885,10 +888,12 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
const bool do_backslash = !(cpo_flags & FLAG_CPO_BSLASH); // backslash is a special character
const bool do_special = !(flags & REPTERM_NO_SPECIAL);
+ bool allocated = (*bufp == NULL);
+
// Allocate space for the translation. Worst case a single character is
// replaced by 6 bytes (shifted special key), plus a NUL at the end.
- const size_t buf_len = from_len * 6 + 1;
- result = xmalloc(buf_len);
+ const size_t buf_len = allocated ? from_len * 6 + 1 : 128;
+ result = allocated ? xmalloc(buf_len) : *bufp;
src = (char_u *)from;
@@ -907,6 +912,9 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
// Copy each byte from *from to result[dlen]
while (src <= end) {
+ if (!allocated && dlen + 64 > buf_len) {
+ return NULL;
+ }
// Check for special <> keycodes, like "<C-S-LeftMouse>"
if (do_special && ((flags & REPTERM_DO_LT) || ((end - src) >= 3
&& STRNCMP(src, "<lt>", 4) != 0))) {
@@ -1000,11 +1008,85 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
}
result[dlen] = NUL;
- *bufp = xrealloc(result, dlen + 1);
+ if (allocated) {
+ *bufp = xrealloc(result, dlen + 1);
+ }
return *bufp;
}
+/// Add character "c" to buffer "s"
+///
+/// Escapes the special meaning of K_SPECIAL, handles multi-byte
+/// characters.
+///
+/// @param[in] c Character to add.
+/// @param[out] s Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes.
+///
+/// @return Pointer to after the added bytes.
+char_u *add_char2buf(int c, char_u *s)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char_u temp[MB_MAXBYTES + 1];
+ const int len = utf_char2bytes(c, (char *)temp);
+ for (int i = 0; i < len; i++) {
+ c = (uint8_t)temp[i];
+ // Need to escape K_SPECIAL like in the typeahead buffer.
+ if (c == K_SPECIAL) {
+ *s++ = K_SPECIAL;
+ *s++ = KS_SPECIAL;
+ *s++ = KE_FILLER;
+ } else {
+ *s++ = (char_u)c;
+ }
+ }
+ return s;
+}
+
+/// Copy "p" to allocated memory, escaping K_SPECIAL so that the result
+/// can be put in the typeahead buffer.
+char *vim_strsave_escape_ks(char *p)
+{
+ // 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
+ char_u *res = xmalloc(STRLEN(p) * 4 + 1);
+ char_u *d = res;
+ for (char_u *s = (char_u *)p; *s != NUL;) {
+ if (s[0] == K_SPECIAL && 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
+ // K_SPECIAL. Be careful, it can be an illegal byte!
+ d = add_char2buf(utf_ptr2char((char *)s), d);
+ s += utf_ptr2len((char *)s);
+ }
+ }
+ *d = NUL;
+
+ return (char *)res;
+}
+
+/// Remove escaping from K_SPECIAL characters. Reverse of
+/// vim_strsave_escape_ks(). Works in-place.
+void vim_unescape_ks(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 {
+ *d++ = *s++;
+ }
+ }
+ *d = NUL;
+}
+
/// Logs a single key as a human-readable keycode.
void log_key(int log_level, int key)
{
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 71c1ddfae1..124248508e 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -23,7 +23,6 @@
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
-#include "nvim/getchar.h"
#include "nvim/hashtab.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
@@ -31,6 +30,7 @@
#include "nvim/if_cscope.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
+#include "nvim/mapping.h"
#include "nvim/ui_client.h"
#include "nvim/vim.h"
#ifdef HAVE_LOCALE_H
@@ -128,6 +128,7 @@ void event_init(void)
channel_init();
terminal_init();
ui_init();
+ TIME_MSG("event init");
}
/// @returns false if main_loop could not be closed gracefully
@@ -172,6 +173,8 @@ void early_init(mparm_T *paramp)
(int)ovi.dwMajorVersion, (int)ovi.dwMinorVersion);
#endif
+ TIME_MSG("early init");
+
#if defined(HAVE_LOCALE_H)
// Setup to use the current locale (for ctype() and many other things).
// NOTE: Translated messages with encodings other than latin1 will not
@@ -184,8 +187,7 @@ void early_init(mparm_T *paramp)
if (!win_alloc_first()) {
os_exit(0);
}
-
- init_yank(); // init yank buffers
+ TIME_MSG("init first window");
alist_init(&global_alist); // Init the argument list to empty.
global_alist.id = 0;
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
new file mode 100644
index 0000000000..d0ecf7549a
--- /dev/null
+++ b/src/nvim/mapping.c
@@ -0,0 +1,2540 @@
+// 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 <assert.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "nvim/api/private/converter.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii.h"
+#include "nvim/assert.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/charset.h"
+#include "nvim/eval.h"
+#include "nvim/eval/typval.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_session.h"
+#include "nvim/func_attr.h"
+#include "nvim/garray.h"
+#include "nvim/getchar.h"
+#include "nvim/keycodes.h"
+#include "nvim/lua/executor.h"
+#include "nvim/mapping.h"
+#include "nvim/mbyte.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
+#include "nvim/option.h"
+#include "nvim/regexp.h"
+#include "nvim/ui.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((char_u *)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(' '); // padd with blanks
+ len++;
+ } while (len < 12);
+
+ if (mp->m_noremap == REMAP_NONE) {
+ msg_puts_attr("*", HL_ATTR(HLF_8));
+ } else if (mp->m_noremap == REMAP_SCRIPT) {
+ msg_puts_attr("&", HL_ATTR(HLF_8));
+ } else {
+ msg_putchar(' ');
+ }
+
+ if (local) {
+ msg_putchar('@');
+ } else {
+ msg_putchar(' ');
+ }
+
+ // Use false below if we only want things like <Up> to show up as such on
+ // the rhs, and not M-x etc, true gets both -- webb
+ if (mp->m_luaref != LUA_NOREF) {
+ char msg[100];
+ snprintf(msg, sizeof(msg), "<Lua function %d>", mp->m_luaref);
+ msg_puts_attr(msg, HL_ATTR(HLF_8));
+ } else if (mp->m_str[0] == NUL) {
+ msg_puts_attr("<Nop>", 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();
+ ui_flush(); // show one line at a time
+}
+
+/// 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 <Nop>, `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 maps.
+/// @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<C_UP> say, then replace the term codes
+ // with the appropriate two bytes. If it is a shifted special key, unshift
+ // it too, giving another two bytes.
+ //
+ // replace_termcodes() may move the result to allocated memory, which
+ // needs to be freed later (*lhs_buf and *rhs_buf).
+ // replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
+ // If something like <C-H> 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);
+ STRLCPY(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);
+ STRLCPY(mapargs->alt_lhs, replaced, sizeof(mapargs->alt_lhs));
+ } else {
+ mapargs->alt_lhs_len = 0;
+ }
+
+ 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_u));
+ STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1);
+
+ if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing
+ mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char
+ mapargs->rhs_len = 0;
+ mapargs->rhs_is_noop = true;
+ } else {
+ char *rhs_buf = NULL;
+ replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL,
+ cpo_flags);
+ mapargs->rhs_len = STRLEN(replaced);
+ // XXX: 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 = (char_u *)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_u));
+ mapargs->orig_rhs_len = 0;
+ // stores <lua>ref_no<cr> 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 = vim_strsave((char_u *)tmp_buf);
+ }
+ return true;
+}
+
+/// Parse a string of |:map-arguments| into a @ref MapArguments struct.
+///
+/// Termcodes, backslashes, CTRL-V's, etc. inside the extracted {lhs} and
+/// {rhs} are replaced by @ref set_maparg_lhs_rhs.
+///
+/// rhs and orig_rhs in the returned mapargs will be set to null or a pointer
+/// to allocated memory and should be freed even on error.
+///
+/// @param[in] strargs String of map args, e.g. "<buffer> <expr><silent>".
+/// May contain leading or trailing whitespace.
+/// @param[in] is_unmap True, if strargs should be parsed like an |:unmap|
+/// command. |:unmap| commands interpret *all* text to the
+/// right of the last map argument as the {lhs} of the
+/// mapping, i.e. a literal ' ' character is treated like
+/// a "<space>", rather than separating the {lhs} from the
+/// {rhs}.
+/// @param[out] mapargs MapArguments struct holding all extracted argument
+/// values.
+/// @return 0 on success, 1 if invalid arguments are detected.
+static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs)
+{
+ const char_u *to_parse = strargs;
+ to_parse = (char_u *)skipwhite((char *)to_parse);
+ memset(mapargs, 0, sizeof(*mapargs));
+
+ // Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in
+ // any order.
+ while (true) {
+ if (STRNCMP(to_parse, "<buffer>", 8) == 0) {
+ to_parse = (char_u *)skipwhite((char *)to_parse + 8);
+ mapargs->buffer = true;
+ continue;
+ }
+
+ if (STRNCMP(to_parse, "<nowait>", 8) == 0) {
+ to_parse = (char_u *)skipwhite((char *)to_parse + 8);
+ mapargs->nowait = true;
+ continue;
+ }
+
+ if (STRNCMP(to_parse, "<silent>", 8) == 0) {
+ to_parse = (char_u *)skipwhite((char *)to_parse + 8);
+ mapargs->silent = true;
+ continue;
+ }
+
+ // Ignore obsolete "<special>" modifier.
+ if (STRNCMP(to_parse, "<special>", 9) == 0) {
+ to_parse = (char_u *)skipwhite((char *)to_parse + 9);
+ continue;
+ }
+
+ if (STRNCMP(to_parse, "<script>", 8) == 0) {
+ to_parse = (char_u *)skipwhite((char *)to_parse + 8);
+ mapargs->script = true;
+ continue;
+ }
+
+ if (STRNCMP(to_parse, "<expr>", 6) == 0) {
+ to_parse = (char_u *)skipwhite((char *)to_parse + 6);
+ mapargs->expr = true;
+ continue;
+ }
+
+ if (STRNCMP(to_parse, "<unique>", 8) == 0) {
+ to_parse = (char_u *)skipwhite((char *)to_parse + 8);
+ mapargs->unique = true;
+ continue;
+ }
+ break;
+ }
+
+ // Find the next whitespace character, call that the end of {lhs}.
+ //
+ // If a character (e.g. whitespace) is immediately preceded by a CTRL-V,
+ // "scan past" that character, i.e. don't "terminate" LHS with that character
+ // if it's whitespace.
+ //
+ // Treat backslash like CTRL-V when 'cpoptions' does not contain 'B'.
+ //
+ // With :unmap, literal white space is included in the {lhs}; there is no
+ // separate {rhs}.
+ const char *lhs_end = (char *)to_parse;
+ bool do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
+ while (*lhs_end && (is_unmap || !ascii_iswhite(*lhs_end))) {
+ if ((lhs_end[0] == Ctrl_V || (do_backslash && lhs_end[0] == '\\'))
+ && lhs_end[1] != NUL) {
+ lhs_end++; // skip CTRL-V or backslash
+ }
+ lhs_end++;
+ }
+
+ // {lhs_end} is a pointer to the "terminating whitespace" after {lhs}.
+ // Use that to initialize {rhs_start}.
+ const char_u *rhs_start = (char_u *)skipwhite((char *)lhs_end);
+
+ // Given {lhs} might be larger than MAXMAPLEN before replace_termcodes
+ // (e.g. "<Space>" is longer than ' '), so first copy into a buffer.
+ size_t orig_lhs_len = (size_t)((char_u *)lhs_end - to_parse);
+ if (orig_lhs_len >= 256) {
+ return 1;
+ }
+ char_u lhs_to_replace[256];
+ STRLCPY(lhs_to_replace, to_parse, orig_lhs_len + 1);
+
+ size_t orig_rhs_len = STRLEN(rhs_start);
+ if (!set_maparg_lhs_rhs((char *)lhs_to_replace, orig_lhs_len,
+ (char *)rhs_start, orig_rhs_len, LUA_NOREF,
+ CPO_TO_CPO_FLAGS, mapargs)) {
+ return 1;
+ }
+
+ if (mapargs->lhs_len > MAXMAPLEN) {
+ return 1;
+ }
+ return 0;
+}
+
+/// Sets or removes a mapping or abbreviation in buffer `buf`.
+///
+/// @param maptype @see do_map
+/// @param args Fully parsed and "preprocessed" arguments for the
+/// (un)map/abbrev command. Termcodes should have already been
+/// replaced; whitespace, `<` and `>` signs, etc. in {lhs} and
+/// {rhs} are assumed to be literal components of the mapping.
+/// @param mode @see do_map
+/// @param is_abbrev @see do_map
+/// @param buf Target Buffer
+static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T *buf)
+{
+ mapblock_T *mp, **mpp;
+ const char_u *p;
+ int n;
+ int retval = 0;
+ mapblock_T **abbr_table;
+ mapblock_T **map_table;
+ int noremap;
+
+ map_table = maphash;
+ abbr_table = &first_abbr;
+
+ // For ":noremap" don't remap, otherwise do remap.
+ if (maptype == 2) {
+ noremap = REMAP_NONE;
+ } else {
+ noremap = REMAP_YES;
+ }
+
+ if (args->buffer) {
+ // If <buffer> was given, we'll be searching through the buffer's
+ // mappings/abbreviations, not the globals.
+ map_table = buf->b_maphash;
+ abbr_table = &buf->b_first_abbr;
+ }
+ if (args->script) {
+ noremap = REMAP_SCRIPT;
+ }
+
+ const bool has_lhs = (args->lhs[0] != NUL);
+ const bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop;
+ const bool do_print = !has_lhs || (maptype != 1 && !has_rhs);
+
+ // check for :unmap without argument
+ if (maptype == 1 && !has_lhs) {
+ retval = 1;
+ goto theend;
+ }
+
+ const char_u *lhs = (char_u *)&args->lhs;
+ const bool did_simplify = args->alt_lhs_len != 0;
+
+ // The following is done twice if we have two versions of keys
+ for (int keyround = 1; keyround <= 2; keyround++) {
+ bool did_it = false;
+ bool did_local = false;
+ bool keyround1_simplified = keyround == 1 && did_simplify;
+ int len = (int)args->lhs_len;
+
+ if (keyround == 2) {
+ if (!did_simplify) {
+ break;
+ }
+ lhs = (char_u *)&args->alt_lhs;
+ len = (int)args->alt_lhs_len;
+ } else if (did_simplify && do_print) {
+ // when printing always use the not-simplified map
+ lhs = (char_u *)&args->alt_lhs;
+ len = (int)args->alt_lhs_len;
+ }
+
+ // check arguments and translate function keys
+ if (has_lhs) {
+ if (len > MAXMAPLEN) {
+ retval = 1;
+ goto theend;
+ }
+
+ if (is_abbrev && maptype != 1) {
+ //
+ // If an abbreviation ends in a keyword character, the
+ // rest must be all keyword-char or all non-keyword-char.
+ // Otherwise we won't be able to find the start of it in a
+ // vi-compatible way.
+ //
+ int same = -1;
+
+ const int first = vim_iswordp(lhs);
+ int last = first;
+ p = lhs + utfc_ptr2len((char *)lhs);
+ n = 1;
+ while (p < lhs + len) {
+ n++; // nr of (multi-byte) chars
+ last = vim_iswordp(p); // type of last char
+ if (same == -1 && last != first) {
+ same = n - 1; // count of same char type
+ }
+ p += utfc_ptr2len((char *)p);
+ }
+ if (last && n > 2 && same >= 0 && same < n - 1) {
+ retval = 1;
+ goto theend;
+ }
+ // An abbreviation cannot contain white space.
+ for (n = 0; n < len; n++) {
+ if (ascii_iswhite(lhs[n])) {
+ retval = 1;
+ goto theend;
+ }
+ } // for
+ }
+ }
+
+ if (has_lhs && has_rhs && is_abbrev) { // if we will add an abbreviation,
+ no_abbr = false; // reset flag that indicates there are no abbreviations
+ }
+
+ if (do_print) {
+ msg_start();
+ }
+
+ // Check if a new local mapping wasn't already defined globally.
+ if (args->unique && map_table == buf->b_maphash && has_lhs && has_rhs && maptype != 1) {
+ // need to loop over all global hash lists
+ for (int hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_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, lhs, (size_t)len) == 0) {
+ if (is_abbrev) {
+ semsg(_("E224: global abbreviation already exists for %s"),
+ mp->m_keys);
+ } else {
+ semsg(_("E225: global mapping already exists for %s"), mp->m_keys);
+ }
+ retval = 5;
+ goto theend;
+ }
+ }
+ }
+ }
+
+ // When listing global mappings, also list buffer-local ones here.
+ if (map_table != buf->b_maphash && !has_rhs && maptype != 1) {
+ // need to loop over all global hash lists
+ for (int hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash != 0) { // there is only one abbreviation list
+ break;
+ }
+ mp = buf->b_first_abbr;
+ } else {
+ mp = buf->b_maphash[hash];
+ }
+ for (; mp != NULL && !got_int; mp = mp->m_next) {
+ // check entries with the same mode
+ if (!mp->m_simplified && (mp->m_mode & mode) != 0) {
+ if (!has_lhs) { // show all entries
+ showmap(mp, true);
+ did_local = true;
+ } else {
+ n = mp->m_keylen;
+ if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
+ showmap(mp, true);
+ did_local = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Find an entry in the maphash[] list that matches.
+ // For :unmap we may loop two times: once to try to unmap an entry with a
+ // matching 'from' part, a second time, if the first fails, to unmap an
+ // entry with a matching 'to' part. This was done to allow ":ab foo bar"
+ // to be unmapped by typing ":unab foo", where "foo" will be replaced by
+ // "bar" because of the abbreviation.
+ for (int round = 0; (round == 0 || maptype == 1) && round <= 1
+ && !did_it && !got_int; round++) {
+ int hash_start, hash_end;
+ if (has_lhs || is_abbrev) {
+ // just use one hash
+ hash_start = is_abbrev ? 0 : MAP_HASH(mode, lhs[0]);
+ hash_end = hash_start + 1;
+ } else {
+ // need to loop over all hash lists
+ hash_start = 0;
+ hash_end = 256;
+ }
+ for (int hash = hash_start; hash < hash_end && !got_int; hash++) {
+ mpp = is_abbrev ? abbr_table : &(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 (!has_lhs) { // 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, lhs, (size_t)(n < len ? n : len)) == 0) {
+ if (maptype == 1) {
+ // Delete entry.
+ // Only accept a full match. For abbreviations
+ // we ignore trailing space when matching with
+ // the "lhs", since an abbreviation can't have
+ // trailing space.
+ if (n != len && (!is_abbrev || round || n > len
+ || *skipwhite((char *)lhs + 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 (!has_rhs) { // 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 (keyround1_simplified && !mp->m_simplified) {
+ // In keyround for simplified keys, don't replace
+ // a mapping without m_simplified flag.
+ did_it = true;
+ break;
+ } else if (args->unique) {
+ if (is_abbrev) {
+ semsg(_("E226: abbreviation already exists for %s"), p);
+ } else {
+ semsg(_("E227: mapping already exists for %s"), p);
+ }
+ retval = 5;
+ goto theend;
+ } else {
+ // new rhs for existing entry
+ mp->m_mode &= ~mode; // remove mode bits
+ if (mp->m_mode == 0 && !did_it) { // reuse entry
+ XFREE_CLEAR(mp->m_desc);
+ if (!mp->m_simplified) {
+ NLUA_CLEAR_REF(mp->m_luaref);
+ XFREE_CLEAR(mp->m_str);
+ XFREE_CLEAR(mp->m_orig_str);
+ }
+ mp->m_str = args->rhs;
+ mp->m_orig_str = args->orig_rhs;
+ mp->m_luaref = args->rhs_lua;
+ if (!keyround1_simplified) {
+ args->rhs = NULL;
+ args->orig_rhs = NULL;
+ args->rhs_lua = LUA_NOREF;
+ }
+ mp->m_noremap = noremap;
+ mp->m_nowait = args->nowait;
+ mp->m_silent = args->silent;
+ mp->m_mode = mode;
+ mp->m_simplified = keyround1_simplified;
+ mp->m_expr = args->expr;
+ mp->m_script_ctx = current_sctx;
+ mp->m_script_ctx.sc_lnum += sourcing_lnum;
+ nlua_set_sctx(&mp->m_script_ctx);
+ if (args->desc != NULL) {
+ mp->m_desc = xstrdup(args->desc);
+ }
+ did_it = true;
+ }
+ }
+ if (mp->m_mode == 0) { // entry can be deleted
+ mapblock_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 (!is_abbrev && new_hash != hash) {
+ *mpp = mp->m_next;
+ mp->m_next = map_table[new_hash];
+ map_table[new_hash] = mp;
+
+ continue; // continue with *mpp
+ }
+ }
+ }
+ mpp = &(mp->m_next);
+ }
+ }
+ }
+
+ if (maptype == 1) {
+ // delete entry
+ if (!did_it) {
+ if (!keyround1_simplified) {
+ retval = 2; // no match
+ }
+ } else if (*lhs == Ctrl_C) {
+ // If CTRL-C has been unmapped, reuse it for Interrupting.
+ if (map_table == buf->b_maphash) {
+ buf->b_mapped_ctrl_c &= ~mode;
+ } else {
+ mapped_ctrl_c &= ~mode;
+ }
+ }
+ continue;
+ }
+
+ if (!has_lhs || !has_rhs) {
+ // print entries
+ if (!did_it && !did_local) {
+ if (is_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.
+ mp = xmalloc(sizeof(mapblock_T));
+
+ // If CTRL-C has been mapped, don't always use it for Interrupting.
+ if (*lhs == Ctrl_C) {
+ if (map_table == buf->b_maphash) {
+ buf->b_mapped_ctrl_c |= mode;
+ } else {
+ mapped_ctrl_c |= mode;
+ }
+ }
+
+ mp->m_keys = vim_strsave(lhs);
+ mp->m_str = args->rhs;
+ mp->m_orig_str = args->orig_rhs;
+ mp->m_luaref = args->rhs_lua;
+ if (!keyround1_simplified) {
+ args->rhs = NULL;
+ args->orig_rhs = NULL;
+ args->rhs_lua = LUA_NOREF;
+ }
+ mp->m_keylen = (int)STRLEN(mp->m_keys);
+ mp->m_noremap = noremap;
+ mp->m_nowait = args->nowait;
+ mp->m_silent = args->silent;
+ mp->m_mode = mode;
+ mp->m_simplified = keyround1_simplified; // Notice this when porting patch 8.2.0807
+ mp->m_expr = args->expr;
+ mp->m_script_ctx = current_sctx;
+ mp->m_script_ctx.sc_lnum += sourcing_lnum;
+ nlua_set_sctx(&mp->m_script_ctx);
+ mp->m_desc = NULL;
+ if (args->desc != NULL) {
+ mp->m_desc = xstrdup(args->desc);
+ }
+
+ // add the new entry in front of the abbrlist or maphash[] list
+ if (is_abbrev) {
+ mp->m_next = *abbr_table;
+ *abbr_table = mp;
+ } else {
+ n = MAP_HASH(mp->m_mode, mp->m_keys[0]);
+ mp->m_next = map_table[n];
+ map_table[n] = mp;
+ }
+ }
+
+theend:
+ return retval;
+}
+
+/// Set or remove a mapping or an abbreviation in the current buffer, OR
+/// display (matching) mappings/abbreviations.
+///
+/// ```vim
+/// map[!] " show all key mappings
+/// map[!] {lhs} " show key mapping for {lhs}
+/// map[!] {lhs} {rhs} " set key mapping for {lhs} to {rhs}
+/// noremap[!] {lhs} {rhs} " same, but no remapping for {rhs}
+/// unmap[!] {lhs} " remove key mapping for {lhs}
+/// abbr " show all abbreviations
+/// abbr {lhs} " show abbreviations for {lhs}
+/// abbr {lhs} {rhs} " set abbreviation for {lhs} to {rhs}
+/// noreabbr {lhs} {rhs} " same, but no remapping for {rhs}
+/// unabbr {lhs} " remove abbreviation for {lhs}
+///
+/// for :map mode is 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
+/// ```
+///
+/// @param maptype 0 for |:map|, 1 for |:unmap|, 2 for |noremap|.
+/// @param arg C-string containing the arguments of the map/abbrev
+/// command, i.e. everything except the initial `:[X][nore]map`.
+/// - Cannot be a read-only string; it will be modified.
+/// @param mode Bitflags representing the mode in which to set the mapping.
+/// See @ref get_map_mode.
+/// @param is_abbrev True if setting an abbreviation, false otherwise.
+///
+/// @return 0 on success. On failure, will return one of the following:
+/// - 1 for invalid arguments
+/// - 2 for no match
+/// - 4 for out of mem (deprecated, WON'T HAPPEN)
+/// - 5 for entry not unique
+///
+int do_map(int maptype, char_u *arg, int mode, bool is_abbrev)
+{
+ MapArguments parsed_args;
+ int result = str_to_mapargs(arg, maptype == 1, &parsed_args);
+ switch (result) {
+ case 0:
+ break;
+ case 1:
+ // invalid arguments
+ goto free_and_return;
+ default:
+ assert(false && "Unknown return code from str_to_mapargs!");
+ result = -1;
+ goto free_and_return;
+ } // switch
+
+ result = buf_do_map(maptype, &parsed_args, mode, is_abbrev, curbuf);
+
+free_and_return:
+ xfree(parsed_args.rhs);
+ xfree(parsed_args.orig_rhs);
+ return result;
+}
+
+/// Get the mapping mode from the command name.
+static int get_map_mode(char **cmdp, bool forceit)
+{
+ char *p;
+ int modec;
+ int mode;
+
+ p = *cmdp;
+ modec = (uint8_t)(*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 or abbreviations.
+/// 'abbr' should be false for mappings, true for abbreviations.
+/// This function used to be called map_clear().
+static void do_mapclear(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_invarg));
+ return;
+ }
+
+ mode = get_map_mode((char **)&cmdp, forceit);
+ map_clear_int(curbuf, mode, local, abbr);
+}
+
+/// Clear all mappings in "mode".
+///
+/// @param buf, buffer for local mappings
+/// @param mode mode in which to delete
+/// @param local true for buffer-local mappings
+/// @param abbr true for abbreviations
+void map_clear_int(buf_T *buf, int mode, bool local, bool abbr)
+{
+ mapblock_T *mp, **mpp;
+ int hash;
+ int new_hash;
+
+ 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
+ mapblock_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);
+ }
+ }
+}
+
+/// Check if a map exists that has given string in the rhs
+///
+/// Also checks mappings local to the current buffer.
+///
+/// @param[in] str String which mapping must have in the rhs. Termcap codes
+/// are recognized in this argument.
+/// @param[in] modechars Mode(s) in which mappings are checked.
+/// @param[in] abbr true if checking abbreviations in place of mappings.
+///
+/// @return true if there is at least one mapping with given parameters.
+bool map_to_exists(const char *const str, const char *const modechars, const bool abbr)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ int mode = 0;
+ int retval;
+
+ char_u *buf = NULL;
+ const char_u *const rhs = (char_u *)replace_termcodes(str, strlen(str),
+ (char **)&buf, REPTERM_DO_LT,
+ NULL, CPO_TO_CPO_FLAGS);
+
+#define MAPMODE(mode, modechars, chr, modeflags) \
+ do { \
+ if (strchr(modechars, chr) != NULL) { \
+ (mode) |= (modeflags); \
+ } \
+ } while (0)
+ MAPMODE(mode, modechars, 'n', MODE_NORMAL);
+ MAPMODE(mode, modechars, 'v', MODE_VISUAL | MODE_SELECT);
+ MAPMODE(mode, modechars, 'x', MODE_VISUAL);
+ MAPMODE(mode, modechars, 's', MODE_SELECT);
+ MAPMODE(mode, modechars, 'o', MODE_OP_PENDING);
+ MAPMODE(mode, modechars, 'i', MODE_INSERT);
+ MAPMODE(mode, modechars, 'l', MODE_LANGMAP);
+ MAPMODE(mode, modechars, 'c', MODE_CMDLINE);
+#undef MAPMODE
+
+ retval = map_to_exists_mode((char *)rhs, mode, abbr);
+ xfree(buf);
+
+ return retval;
+}
+
+/// Check if a map exists that has given string in the rhs
+///
+/// Also checks mappings local to the current buffer.
+///
+/// @param[in] rhs String which mapping must have in the rhs. Termcap codes
+/// are recognized in this argument.
+/// @param[in] mode Mode(s) in which mappings are checked.
+/// @param[in] abbr true if checking abbreviations in place of mappings.
+///
+/// @return true if there is at least one mapping with given parameters.
+int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
+{
+ mapblock_T *mp;
+ int hash;
+ bool exp_buffer = false;
+
+ // 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, 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 bool expand_isabbrev = false;
+static bool expand_buffer = false;
+
+/// Translate an internal mapping/abbreviation representation into the
+/// corresponding external one recognized by :map/:abbrev commands.
+///
+/// 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.
+///
+/// @param cpo_flags Value of various flags present in &cpo
+///
+/// @return NULL when there is a problem.
+static char_u *translate_mapping(char_u *str, int cpo_flags)
+{
+ garray_T ga;
+ ga_init(&ga, 1, 40);
+
+ bool cpo_bslash = !(cpo_flags&FLAG_CPO_BSLASH);
+
+ for (; *str; str++) {
+ int c = *str;
+ if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) {
+ int modifiers = 0;
+ if (str[1] == KS_MODIFIER) {
+ str++;
+ modifiers = *++str;
+ c = *++str;
+ }
+
+ if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) {
+ 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
+ ga_concat(&ga, (char *)get_special_key_name(c, modifiers));
+ continue; // for (str)
+ }
+ }
+
+ if (c == ' ' || c == '\t' || c == Ctrl_J || c == Ctrl_V
+ || (c == '\\' && !cpo_bslash)) {
+ ga_append(&ga, cpo_bslash ? Ctrl_V : '\\');
+ }
+
+ if (c) {
+ ga_append(&ga, (char)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.
+///
+/// @param forceit true if '!' given
+/// @param isabbrev true if abbreviation
+/// @param isunmap true if unmap/unabbrev command
+char_u *set_context_in_map_cmd(expand_T *xp, char_u *cmd, char_u *arg, bool forceit, bool isabbrev,
+ bool isunmap, cmdidx_T cmdidx)
+{
+ if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) {
+ xp->xp_context = EXPAND_NOTHING;
+ } else {
+ if (isunmap) {
+ expand_mapmodes = get_map_mode((char **)&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 = (char_u *)skipwhite((char *)arg + 8);
+ continue;
+ }
+ if (STRNCMP(arg, "<unique>", 8) == 0) {
+ arg = (char_u *)skipwhite((char *)arg + 8);
+ continue;
+ }
+ if (STRNCMP(arg, "<nowait>", 8) == 0) {
+ arg = (char_u *)skipwhite((char *)arg + 8);
+ continue;
+ }
+ if (STRNCMP(arg, "<silent>", 8) == 0) {
+ arg = (char_u *)skipwhite((char *)arg + 8);
+ continue;
+ }
+ if (STRNCMP(arg, "<special>", 9) == 0) {
+ arg = (char_u *)skipwhite((char *)arg + 9);
+ continue;
+ }
+ if (STRNCMP(arg, "<script>", 8) == 0) {
+ arg = (char_u *)skipwhite((char *)arg + 8);
+ continue;
+ }
+ if (STRNCMP(arg, "<expr>", 6) == 0) {
+ arg = (char_u *)skipwhite((char *)arg + 6);
+ continue;
+ }
+ break;
+ }
+ xp->xp_pattern = (char *)arg;
+ }
+
+ return NULL;
+}
+
+/// Find all mapping/abbreviation names that match regexp "regmatch".
+/// For command line expansion of ":[un]map" and ":[un]abbrev" in all modes.
+/// @return OK if matches found, FAIL otherwise.
+int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file)
+{
+ mapblock_T *mp;
+ int hash;
+ int count;
+ int round;
+ char_u *p;
+ int i;
+
+ *num_file = 0; // return values in case of FAIL
+ *file = NULL;
+
+ // round == 1: Count the matches.
+ // round == 2: Build the array to keep the matches.
+ for (round = 1; round <= 2; round++) {
+ count = 0;
+
+ for (i = 0; i < 7; i++) {
+ if (i == 0) {
+ p = (char_u *)"<silent>";
+ } else if (i == 1) {
+ p = (char_u *)"<unique>";
+ } else if (i == 2) {
+ p = (char_u *)"<script>";
+ } else if (i == 3) {
+ p = (char_u *)"<expr>";
+ } else if (i == 4 && !expand_buffer) {
+ p = (char_u *)"<buffer>";
+ } else if (i == 5) {
+ p = (char_u *)"<nowait>";
+ } else if (i == 6) {
+ p = (char_u *)"<special>";
+ } else {
+ continue;
+ }
+
+ if (vim_regexec(regmatch, p, (colnr_T)0)) {
+ if (round == 1) {
+ count++;
+ } else {
+ (*file)[count++] = vim_strsave(p);
+ }
+ }
+ }
+
+ for (hash = 0; hash < 256; hash++) {
+ if (expand_isabbrev) {
+ if (hash > 0) { // only one abbrev list
+ break; // for (hash)
+ }
+ mp = first_abbr;
+ } else if (expand_buffer) {
+ mp = curbuf->b_maphash[hash];
+ } else {
+ mp = maphash[hash];
+ }
+ for (; mp; mp = mp->m_next) {
+ if (mp->m_mode & expand_mapmodes) {
+ p = translate_mapping(mp->m_keys, CPO_TO_CPO_FLAGS);
+ if (p != NULL && vim_regexec(regmatch, p, (colnr_T)0)) {
+ if (round == 1) {
+ count++;
+ } else {
+ (*file)[count++] = p;
+ p = NULL;
+ }
+ }
+ xfree(p);
+ }
+ } // for (mp)
+ } // for (hash)
+
+ if (count == 0) { // no match found
+ break; // for (round)
+ }
+
+ if (round == 1) {
+ *file = (char_u **)xmalloc((size_t)count * sizeof(char_u *));
+ }
+ } // for (round)
+
+ if (count > 1) {
+ char_u **ptr1;
+ char_u **ptr2;
+ char_u **ptr3;
+
+ // Sort the matches
+ sort_strings(*file, count);
+
+ // Remove multiple entries
+ ptr1 = *file;
+ ptr2 = ptr1 + 1;
+ ptr3 = ptr1 + count;
+
+ while (ptr2 < ptr3) {
+ if (STRCMP(*ptr1, *ptr2)) {
+ *++ptr1 = *ptr2++;
+ } else {
+ xfree(*ptr2++);
+ count--;
+ }
+ }
+ }
+
+ *num_file = 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.
+bool 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
+ bool is_id = true;
+
+ 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;
+ }
+
+ {
+ bool vim_abbr;
+ char_u *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 (ascii_isspace(*p) || (!vim_abbr && is_id != vim_iswordp(p))) {
+ p += utfc_ptr2len((char *)p);
+ break;
+ }
+ clen++;
+ }
+ scol = (int)(p - ptr);
+ }
+
+ 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 (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) {
+ // Might have K_SPECIAL escaped mp->m_keys.
+ q = vim_strsave(mp->m_keys);
+ vim_unescape_ks(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) {
+ xfree(q);
+ }
+ if (match) {
+ break;
+ }
+ }
+ if (mp != NULL) {
+ // 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++] = (char_u)K_SECOND(c);
+ tb[j++] = (char_u)K_THIRD(c);
+ } else {
+ if (c < ABBR_OFF && (c < ' ' || c > '~')) {
+ tb[j++] = Ctrl_V; // special char needs CTRL-V
+ }
+ // if ABBR_OFF has been added, remove it here.
+ if (c >= ABBR_OFF) {
+ c -= ABBR_OFF;
+ }
+ int newlen = utf_char2bytes(c, (char *)tb + j);
+ tb[j + newlen] = NUL;
+ // Need to escape K_SPECIAL.
+ char_u *escaped = (char_u *)vim_strsave_escape_ks((char *)tb + j);
+ if (escaped != NULL) {
+ newlen = (int)STRLEN(escaped);
+ memmove(tb + j, escaped, (size_t)newlen);
+ j += newlen;
+ xfree(escaped);
+ }
+ }
+ tb[j] = NUL;
+ // insert the last typed char
+ (void)ins_typebuf((char *)tb, 1, 0, true, mp->m_silent);
+ }
+ if (mp->m_expr) {
+ s = eval_map_expr(mp, c);
+ } else {
+ s = mp->m_str;
+ }
+ if (s != NULL) {
+ // insert the to string
+ (void)ins_typebuf((char *)s, mp->m_noremap, 0, true, mp->m_silent);
+ // no abbrev. for these chars
+ typebuf.tb_no_abbr_cnt += (int)STRLEN(s) + j + 1;
+ if (mp->m_expr) {
+ xfree(s);
+ }
+ }
+
+ tb[0] = Ctrl_H;
+ tb[1] = NUL;
+ len = clen; // Delete characters instead of bytes
+ while (len-- > 0) { // delete the from string
+ (void)ins_typebuf((char *)tb, 1, 0, true, mp->m_silent);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Evaluate the RHS of a mapping or abbreviations and take care of escaping
+/// special characters.
+///
+/// @param c NUL or typed character for abbreviation
+char_u *eval_map_expr(mapblock_T *mp, int c)
+{
+ char_u *res;
+ char_u *p = NULL;
+ char_u *expr = NULL;
+ pos_T save_cursor;
+ int save_msg_col;
+ int save_msg_row;
+
+ // Remove escaping of K_SPECIAL, because "str" is in a format to be used as
+ // typeahead.
+ if (mp->m_luaref == LUA_NOREF) {
+ expr = vim_strsave(mp->m_str);
+ vim_unescape_ks(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_luaref != LUA_NOREF) {
+ Error err = ERROR_INIT;
+ Array args = ARRAY_DICT_INIT;
+ Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err);
+ if (ret.type == kObjectTypeString) {
+ p = (char_u *)xstrndup(ret.data.string.data, ret.data.string.size);
+ }
+ api_free_object(ret);
+ if (err.type != kErrorTypeNone) {
+ semsg_multiline("E5108: %s", err.msg);
+ api_clear_error(&err);
+ }
+ } else {
+ p = (char_u *)eval_to_string((char *)expr, NULL, false);
+ xfree(expr);
+ }
+ textlock--;
+ ex_normal_lock--;
+ curwin->w_cursor = save_cursor;
+ msg_col = save_msg_col;
+ msg_row = save_msg_row;
+
+ if (p == NULL) {
+ return NULL;
+ }
+ // Escape K_SPECIAL in the result to be able to use the string as typeahead.
+ res = (char_u *)vim_strsave_escape_ks((char *)p);
+ xfree(p);
+
+ return res;
+}
+
+/// Write map commands for the current mappings to an .exrc file.
+/// Return FAIL on error, OK otherwise.
+///
+/// @param buf buffer for local mappings or NULL
+int makemap(FILE *fd, buf_T *buf)
+{
+ mapblock_T *mp;
+ char_u c1, c2, c3;
+ char_u *p;
+ char *cmd;
+ int abbr;
+ int hash;
+ bool did_cpo = false;
+
+ // 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 lua mappings and mappings that contain a <SNR> (script-local thing),
+ // they probably don't work when loaded again
+ if (mp->m_luaref != LUA_NOREF) {
+ continue;
+ }
+ for (p = mp->m_str; *p != NUL; p++) {
+ if (p[0] == K_SPECIAL && p[1] == KS_EXTRA
+ && p[2] == 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(_("E228: 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 {
+ const char specials[] = { (char)(uint8_t)K_SPECIAL, NL, NUL };
+ if (strpbrk((const char *)mp->m_str, specials) != NULL
+ || strpbrk((const char *)mp->m_keys, specials) != NULL) {
+ 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;
+ }
+ if (mp->m_expr && fputs(" <expr>", fd) < 0) {
+ return FAIL;
+ }
+
+ 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;
+
+ // :map xx <Nop>
+ if (*str == NUL && what == 1) {
+ if (fprintf(fd, "<Nop>") < 0) {
+ return FAIL;
+ }
+ return OK;
+ }
+
+ for (; *str != NUL; str++) {
+ // Check for a multi-byte character, which may contain escaped
+ // K_SPECIAL bytes.
+ const char *p = mb_unescape((const char **)&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) {
+ int 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 && (ascii_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 the string "keys" against the lhs of all mappings.
+/// Return pointer to rhs of mapping (mapblock->m_str).
+/// NULL when no mapping found.
+///
+/// @param exact require exact match
+/// @param ign_mod ignore preceding modifier
+/// @param abbr do abbreviations
+/// @param mp_ptr return: pointer to mapblock or NULL
+/// @param local_ptr return: buffer-local mapping or NULL
+char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr,
+ int *local_ptr, int *rhs_lua)
+{
+ int len, minlen;
+ mapblock_T *mp;
+ *rhs_lua = LUA_NOREF;
+
+ len = (int)STRLEN(keys);
+ for (int local = 1; local >= 0; local--) {
+ // loop over all hash lists
+ for (int 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)) {
+ char_u *s = mp->m_keys;
+ int keylen = mp->m_keylen;
+ if (ign_mod && keylen >= 3
+ && s[0] == K_SPECIAL && s[1] == KS_MODIFIER) {
+ s += 3;
+ keylen -= 3;
+ }
+ minlen = keylen < len ? keylen : len;
+ if (STRNCMP(s, keys, minlen) == 0) {
+ if (mp_ptr != NULL) {
+ *mp_ptr = mp;
+ }
+ if (local_ptr != NULL) {
+ *local_ptr = local;
+ }
+ *rhs_lua = mp->m_luaref;
+ return mp->m_luaref == LUA_NOREF ? mp->m_str : NULL;
+ }
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/// Fill a dictionary with all applicable maparg() like dictionaries
+///
+/// @param dict The dictionary to be filled
+/// @param mp The maphash that contains the mapping information
+/// @param buffer_value The "buffer" value
+/// @param compatible True for compatible with old maparg() dict
+static void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buffer_value,
+ bool compatible)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char *const lhs = str2special_save((const char *)mp->m_keys,
+ compatible, !compatible);
+ char *const mapmode = map_mode_to_chars(mp->m_mode);
+ varnumber_T noremap_value;
+
+ if (compatible) {
+ // Keep old compatible behavior
+ // This is unable to determine whether a mapping is a <script> mapping
+ noremap_value = !!mp->m_noremap;
+ } else {
+ // Distinguish between <script> mapping
+ // If it's not a <script> mapping, check if it's a noremap
+ noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap;
+ }
+
+ if (mp->m_luaref != LUA_NOREF) {
+ tv_dict_add_nr(dict, S_LEN("callback"), mp->m_luaref);
+ } else {
+ if (compatible) {
+ tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str);
+ } else {
+ tv_dict_add_allocated_str(dict, S_LEN("rhs"),
+ str2special_save((const char *)mp->m_str, false,
+ true));
+ }
+ }
+ if (mp->m_desc != NULL) {
+ tv_dict_add_allocated_str(dict, S_LEN("desc"), xstrdup(mp->m_desc));
+ }
+ tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs);
+ tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value);
+ tv_dict_add_nr(dict, S_LEN("script"), mp->m_noremap == REMAP_SCRIPT ? 1 : 0);
+ tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0);
+ tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0);
+ tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ctx.sc_sid);
+ tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)mp->m_script_ctx.sc_lnum);
+ tv_dict_add_nr(dict, S_LEN("buffer"), (varnumber_T)buffer_value);
+ tv_dict_add_nr(dict, S_LEN("nowait"), mp->m_nowait ? 1 : 0);
+ tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode);
+}
+
+static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
+{
+ char *keys_buf = NULL;
+ char_u *alt_keys_buf = NULL;
+ bool did_simplify = false;
+ char_u *rhs;
+ LuaRef rhs_lua;
+ int mode;
+ bool abbr = false;
+ bool get_dict = false;
+ mapblock_T *mp;
+ 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;
+
+ char *keys = (char *)tv_get_string(&argvars[0]);
+ if (*keys == NUL) {
+ return;
+ }
+
+ char buf[NUMBUFLEN];
+ const char *which;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ which = tv_get_string_buf_chk(&argvars[1], buf);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ abbr = (bool)tv_get_number(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ get_dict = (bool)tv_get_number(&argvars[3]);
+ }
+ }
+ } else {
+ which = "";
+ }
+ if (which == NULL) {
+ return;
+ }
+
+ mode = get_map_mode((char **)&which, 0);
+
+ char_u *keys_simplified
+ = (char_u *)replace_termcodes(keys,
+ STRLEN(keys), &keys_buf, flags, &did_simplify,
+ CPO_TO_CPO_FLAGS);
+ rhs = check_map(keys_simplified, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
+ 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,
+ STRLEN(keys),
+ (char **)&alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL,
+ CPO_TO_CPO_FLAGS);
+ rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
+ }
+
+ if (!get_dict) {
+ // Return a string.
+ if (rhs != NULL) {
+ if (*rhs == NUL) {
+ rettv->vval.v_string = xstrdup("<Nop>");
+ } else {
+ rettv->vval.v_string = str2special_save((char *)rhs, false, false);
+ }
+ } else if (rhs_lua != LUA_NOREF) {
+ size_t msglen = 100;
+ char *msg = (char *)xmalloc(msglen);
+ snprintf(msg, msglen, "<Lua function %d>", mp->m_luaref);
+ rettv->vval.v_string = msg;
+ }
+ } else {
+ tv_dict_alloc_ret(rettv);
+ if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) {
+ // Return a dictionary.
+ mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true);
+ }
+ }
+
+ xfree(keys_buf);
+ xfree(alt_keys_buf);
+}
+
+/// "maparg()" function
+void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_maparg(argvars, rettv, true);
+}
+
+/// "mapcheck()" function
+void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_maparg(argvars, rettv, false);
+}
+
+void init_default_mappings(void)
+{
+ add_map((char_u *)"Y y$", MODE_NORMAL, true);
+
+ // Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O>
+ // See https://github.com/neovim/neovim/issues/17473
+ add_map((char_u *)"<C-L> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>",
+ MODE_NORMAL, true);
+ add_map((char_u *)"<C-U> <C-G>u<C-U>", MODE_INSERT, true);
+ add_map((char_u *)"<C-W> <C-G>u<C-W>", MODE_INSERT, true);
+ add_map((char_u *)"* y/\\\\V<C-R>\"<CR>", MODE_VISUAL, true);
+ add_map((char_u *)"# y?\\\\V<C-R>\"<CR>", MODE_VISUAL, true);
+}
+
+/// Add a mapping. Unlike @ref do_map this copies the {map} argument, so
+/// static or read-only strings can be used.
+///
+/// @param map C-string containing the arguments of the map/abbrev command,
+/// i.e. everything except the initial `:[X][nore]map`.
+/// @param mode Bitflags representing the mode in which to set the mapping.
+/// See @ref get_map_mode.
+/// @param nore If true, make a non-recursive mapping.
+void add_map(char_u *map, int mode, bool nore)
+{
+ char_u *s;
+ char *cpo_save = p_cpo;
+
+ p_cpo = ""; // Allow <> notation
+ // Need to put string in allocated memory, because do_map() will modify it.
+ s = vim_strsave(map);
+ (void)do_map(nore ? 2 : 0, s, mode, false);
+ xfree(s);
+ p_cpo = cpo_save;
+}
+
+/// 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.
+///
+/// With multi-byte support use growarray for 'langmap' chars >= 256
+typedef struct {
+ int from;
+ int to;
+} langmap_entry_T;
+
+static garray_T langmap_mapga = GA_EMPTY_INIT_VALUE;
+
+/// 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);
+ unsigned int a = 0;
+ assert(langmap_mapga.ga_len >= 0);
+ unsigned int b = (unsigned int)langmap_mapga.ga_len;
+
+ // Do a binary search for an existing entry.
+ while (a != b) {
+ unsigned 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;
+ }
+ }
+
+ ga_grow(&langmap_mapga, 1);
+
+ // insert new entry at position "a"
+ entries = (langmap_entry_T *)(langmap_mapga.ga_data) + a;
+ memmove(entries + 1, entries,
+ ((unsigned int)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)
+{
+ for (int i = 0; i < 256; i++) {
+ langmap_mapchar[i] = (char_u)i; // we init with a one-to-one map
+ }
+ ga_init(&langmap_mapga, sizeof(langmap_entry_T), 8);
+}
+
+/// Called when langmap option is set; the language map can be
+/// changed at any time!
+void langmap_set(void)
+{
+ 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 = utf_ptr2char((char *)p);
+ to = NUL;
+ if (p2 == NULL) {
+ MB_PTR_ADV(p);
+ if (p[0] != ',') {
+ if (p[0] == '\\') {
+ p++;
+ }
+ to = utf_ptr2char((char *)p);
+ }
+ } else {
+ if (p2[0] != ',') {
+ if (p2[0] == '\\') {
+ p2++;
+ }
+ to = utf_ptr2char((char *)p2);
+ }
+ }
+ if (to == NUL) {
+ semsg(_("E357: 'langmap': Matching character missing for %s"),
+ transchar(from));
+ return;
+ }
+
+ if (from >= 256) {
+ langmap_set_entry(from, to);
+ } else {
+ assert(to <= UCHAR_MAX);
+ langmap_mapchar[from & 255] = (char_u)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] != ',') {
+ semsg(_("E358: 'langmap': Extra characters after semicolon: %s"),
+ p);
+ return;
+ }
+ p++;
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void do_exmap(exarg_T *eap, int isabbrev)
+{
+ int mode;
+ char *cmdp = eap->cmd;
+ mode = get_map_mode(&cmdp, eap->forceit || isabbrev);
+
+ switch (do_map((*cmdp == 'n') ? 2 : (*cmdp == 'u'),
+ (char_u *)eap->arg, mode, isabbrev)) {
+ case 1:
+ emsg(_(e_invarg));
+ break;
+ case 2:
+ emsg(isabbrev ? _(e_noabbr) : _(e_nomap));
+ 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((char_u *)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)
+{
+ do_mapclear((char_u *)eap->cmd, (char_u *)eap->arg, eap->forceit, false);
+}
+
+/// ":abclear" and friends.
+void ex_abclear(exarg_T *eap)
+{
+ do_mapclear((char_u *)eap->cmd, (char_u *)eap->arg, true, true);
+}
+
+/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
+/// functions like @ref nvim_buf_set_keymap.
+///
+/// Arguments are handled like @ref nvim_set_keymap unless noted.
+/// @param buffer Buffer handle for a specific buffer, or 0 for the current
+/// buffer, or -1 to signify global behavior ("all buffers")
+/// @param is_unmap When true, removes the mapping that matches {lhs}.
+void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mode, String lhs,
+ String rhs, Dict(keymap) *opts, Error *err)
+{
+ LuaRef lua_funcref = LUA_NOREF;
+ bool global = (buffer == -1);
+ if (global) {
+ buffer = 0;
+ }
+ buf_T *target_buf = find_buffer_by_handle(buffer, err);
+
+ if (!target_buf) {
+ return;
+ }
+
+ const sctx_T save_current_sctx = api_set_sctx(channel_id);
+
+ if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) {
+ lua_funcref = opts->callback.data.luaref;
+ opts->callback.data.luaref = LUA_NOREF;
+ }
+ MapArguments parsed_args = MAP_ARGUMENTS_INIT;
+ if (opts) {
+#define KEY_TO_BOOL(name) \
+ parsed_args.name = api_object_to_bool(opts->name, #name, false, err); \
+ if (ERROR_SET(err)) { \
+ goto fail_and_free; \
+ }
+
+ KEY_TO_BOOL(nowait);
+ KEY_TO_BOOL(noremap);
+ KEY_TO_BOOL(silent);
+ KEY_TO_BOOL(script);
+ KEY_TO_BOOL(expr);
+ KEY_TO_BOOL(unique);
+#undef KEY_TO_BOOL
+ }
+ parsed_args.buffer = !global;
+
+ if (!set_maparg_lhs_rhs(lhs.data, lhs.size,
+ rhs.data, rhs.size, lua_funcref,
+ CPO_TO_CPO_FLAGS, &parsed_args)) {
+ api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data);
+ goto fail_and_free;
+ }
+
+ if (opts != NULL && opts->desc.type == kObjectTypeString) {
+ parsed_args.desc = string_to_cstr(opts->desc.data.string);
+ } else {
+ parsed_args.desc = NULL;
+ }
+ if (parsed_args.lhs_len > MAXMAPLEN || parsed_args.alt_lhs_len > MAXMAPLEN) {
+ api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data);
+ goto fail_and_free;
+ }
+
+ if (mode.size > 1) {
+ api_set_error(err, kErrorTypeValidation, "Shortname is too long: %s", mode.data);
+ goto fail_and_free;
+ }
+ int mode_val; // integer value of the mapping mode, to be passed to do_map()
+ char *p = (mode.size) ? mode.data : "m";
+ if (STRNCMP(p, "!", 2) == 0) {
+ mode_val = get_map_mode(&p, true); // mapmode-ic
+ } else {
+ mode_val = get_map_mode(&p, false);
+ if (mode_val == (MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING) && mode.size > 0) {
+ // get_map_mode() treats unrecognized mode shortnames as ":map".
+ // This is an error unless the given shortname was empty string "".
+ api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", p);
+ goto fail_and_free;
+ }
+ }
+
+ if (parsed_args.lhs_len == 0) {
+ api_set_error(err, kErrorTypeValidation, "Invalid (empty) LHS");
+ goto fail_and_free;
+ }
+
+ bool is_noremap = parsed_args.noremap;
+ assert(!(is_unmap && is_noremap));
+
+ if (!is_unmap && lua_funcref == LUA_NOREF
+ && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
+ if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop>
+ parsed_args.rhs_is_noop = true;
+ } else {
+ abort(); // should never happen
+ }
+ } else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) {
+ if (parsed_args.rhs_len) {
+ api_set_error(err, kErrorTypeValidation,
+ "Gave nonempty RHS in unmap command: %s", parsed_args.rhs);
+ } else {
+ api_set_error(err, kErrorTypeValidation, "Gave nonempty RHS for unmap");
+ }
+ goto fail_and_free;
+ }
+
+ // buf_do_map() reads noremap/unmap as its own argument.
+ int maptype_val = 0;
+ if (is_unmap) {
+ maptype_val = 1;
+ } else if (is_noremap) {
+ maptype_val = 2;
+ }
+
+ switch (buf_do_map(maptype_val, &parsed_args, mode_val, 0, target_buf)) {
+ case 0:
+ break;
+ case 1:
+ api_set_error(err, kErrorTypeException, (char *)e_invarg, 0);
+ goto fail_and_free;
+ case 2:
+ api_set_error(err, kErrorTypeException, (char *)e_nomap, 0);
+ goto fail_and_free;
+ case 5:
+ api_set_error(err, kErrorTypeException,
+ "E227: mapping already exists for %s", parsed_args.lhs);
+ goto fail_and_free;
+ default:
+ assert(false && "Unrecognized return code!");
+ goto fail_and_free;
+ } // switch
+
+fail_and_free:
+ current_sctx = save_current_sctx;
+ NLUA_CLEAR_REF(parsed_args.rhs_lua);
+ xfree(parsed_args.rhs);
+ xfree(parsed_args.orig_rhs);
+ xfree(parsed_args.desc);
+}
+
+/// Get an array containing dictionaries describing mappings
+/// based on mode and buffer id
+///
+/// @param mode The abbreviation for the mode
+/// @param buf The buffer to get the mapping array. NULL for global
+/// @param from_lua Whether it is called from internal lua api.
+/// @returns Array of maparg()-like dictionaries describing mappings
+ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
+{
+ Array mappings = ARRAY_DICT_INIT;
+ dict_T *const dict = tv_dict_alloc();
+
+ // Convert the string mode to the integer mode
+ // that is stored within each mapblock
+ char *p = mode.data;
+ int int_mode = get_map_mode(&p, 0);
+
+ // Determine the desired buffer value
+ long buffer_value = (buf == NULL) ? 0 : buf->handle;
+
+ for (int i = 0; i < MAX_MAPHASH; i++) {
+ for (const mapblock_T *current_maphash = get_maphash(i, buf);
+ current_maphash;
+ current_maphash = current_maphash->m_next) {
+ if (current_maphash->m_simplified) {
+ continue;
+ }
+ // Check for correct mode
+ if (int_mode & current_maphash->m_mode) {
+ mapblock_fill_dict(dict, current_maphash, buffer_value, false);
+ Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT,
+ .vval.v_dict = dict } });
+ if (from_lua) {
+ Dictionary d = api_dict.data.dictionary;
+ for (size_t j = 0; j < d.size; j++) {
+ if (strequal("callback", d.items[j].key.data)) {
+ d.items[j].value.type = kObjectTypeLuaRef;
+ d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer);
+ break;
+ }
+ }
+ }
+ ADD(mappings, api_dict);
+ tv_dict_clear(dict);
+ }
+ }
+ }
+ tv_dict_free(dict);
+
+ return mappings;
+}
diff --git a/src/nvim/mapping.h b/src/nvim/mapping.h
new file mode 100644
index 0000000000..4b0622ffa1
--- /dev/null
+++ b/src/nvim/mapping.h
@@ -0,0 +1,54 @@
+#ifndef NVIM_MAPPING_H
+#define NVIM_MAPPING_H
+
+#include "nvim/buffer_defs.h"
+#include "nvim/eval/funcs.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/types.h"
+#include "nvim/vim.h"
+
+/// All possible |:map-arguments| usable in a |:map| command.
+///
+/// The <special> argument has no effect on mappings and is excluded from this
+/// struct declaration. |noremap| is included, since it behaves like a map
+/// argument when used in a mapping.
+///
+/// @see mapblock_T
+struct map_arguments {
+ bool buffer;
+ bool expr;
+ bool noremap;
+ bool nowait;
+ bool script;
+ bool silent;
+ bool unique;
+
+ /// The {lhs} of the mapping.
+ ///
+ /// vim limits this to MAXMAPLEN characters, allowing us to use a static
+ /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal
+ /// that {lhs} was too long and truncated.
+ char_u lhs[MAXMAPLEN + 1];
+ size_t lhs_len;
+
+ /// Unsimplifed {lhs} of the mapping. If no simplification has been done then alt_lhs_len is 0.
+ char_u alt_lhs[MAXMAPLEN + 1];
+ size_t alt_lhs_len;
+
+ char_u *rhs; /// The {rhs} of the mapping.
+ size_t rhs_len;
+ LuaRef rhs_lua; /// lua function as {rhs}
+ bool rhs_is_noop; /// True when the {rhs} should be <Nop>.
+
+ char_u *orig_rhs; /// The original text of the {rhs}.
+ size_t orig_rhs_len;
+ char *desc; /// map description
+};
+typedef struct map_arguments MapArguments;
+#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \
+ { 0 }, 0, { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL }
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "mapping.h.generated.h"
+#endif
+#endif // NVIM_MAPPING_H
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index a615802b36..29a1191bd6 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -15,6 +15,7 @@
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
+#include "nvim/mapping.h"
#include "nvim/memfile.h"
#include "nvim/memory.h"
#include "nvim/message.h"
@@ -689,11 +690,9 @@ void free_all_mem(void)
do_cmdline_cmd("menutranslate clear");
// Clear mappings, abbreviations, breakpoints.
- do_cmdline_cmd("lmapclear");
- do_cmdline_cmd("xmapclear");
- do_cmdline_cmd("mapclear");
- do_cmdline_cmd("mapclear!");
- do_cmdline_cmd("abclear");
+ // NB: curbuf not used with local=false arg
+ map_clear_int(curbuf, MAP_ALL_MODES, false, false);
+ map_clear_int(curbuf, MAP_ALL_MODES, false, true);
do_cmdline_cmd("breakdel *");
do_cmdline_cmd("profdel *");
do_cmdline_cmd("set keymap=");
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index f4943c4031..80f8406ab0 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -232,6 +232,7 @@ void ex_menu(exarg_T *eap)
} else if (modes & MENU_TIP_MODE) {
map_buf = NULL; // Menu tips are plain text.
} else {
+ map_buf = NULL;
map_to = replace_termcodes(map_to, STRLEN(map_to), &map_buf,
REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 8e4f78818b..5ed1a61b54 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -37,6 +37,7 @@
#include "nvim/keycodes.h"
#include "nvim/log.h"
#include "nvim/main.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 510aa1c8ac..20bbc5b440 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -57,7 +57,7 @@
#include "nvim/vim.h"
#include "nvim/window.h"
-static yankreg_T y_regs[NUM_REGISTERS];
+static yankreg_T y_regs[NUM_REGISTERS] = { 0 };
static yankreg_T *y_previous = NULL; // ptr to last written yankreg
@@ -2571,12 +2571,6 @@ int op_change(oparg_T *oap)
return retval;
}
-/// set all the yank registers to empty (called from main())
-void init_yank(void)
-{
- memset(&(y_regs[0]), 0, sizeof(y_regs));
-}
-
#if defined(EXITFREE)
void clear_registers(void)
{
diff --git a/src/nvim/option.c b/src/nvim/option.c
index c97c027740..633fbe0517 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -51,6 +51,7 @@
#include "nvim/indent_c.h"
#include "nvim/keycodes.h"
#include "nvim/macros.h"
+#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
#include "nvim/memline.h"
@@ -3024,6 +3025,7 @@ ambw_end:
} else if (varp == &p_pt) {
// 'pastetoggle': translate key codes like in a mapping
if (*p_pt) {
+ p = NULL;
(void)replace_termcodes((char *)p_pt,
STRLEN(p_pt),
(char **)&p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL,
@@ -7062,175 +7064,6 @@ static int wc_use_keyname(char_u *varp, long *wcp)
return false;
}
-/// 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.
-///
-/// With multi-byte support use growarray for 'langmap' chars >= 256
-typedef struct {
- int from;
- int to;
-} langmap_entry_T;
-
-static garray_T langmap_mapga = GA_EMPTY_INIT_VALUE;
-
-/// 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);
- unsigned int a = 0;
- assert(langmap_mapga.ga_len >= 0);
- unsigned int b = (unsigned int)langmap_mapga.ga_len;
-
- // Do a binary search for an existing entry.
- while (a != b) {
- unsigned 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;
- }
- }
-
- ga_grow(&langmap_mapga, 1);
-
- // insert new entry at position "a"
- entries = (langmap_entry_T *)(langmap_mapga.ga_data) + a;
- memmove(entries + 1, entries,
- ((unsigned int)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
-}
-
-static void langmap_init(void)
-{
- for (int i = 0; i < 256; i++) {
- langmap_mapchar[i] = (char_u)i; // we init with a one-to-one map
- }
- ga_init(&langmap_mapga, sizeof(langmap_entry_T), 8);
-}
-
-/// Called when langmap option is set; the language map can be
-/// changed at any time!
-static void langmap_set(void)
-{
- 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 = utf_ptr2char((char *)p);
- to = NUL;
- if (p2 == NULL) {
- MB_PTR_ADV(p);
- if (p[0] != ',') {
- if (p[0] == '\\') {
- p++;
- }
- to = utf_ptr2char((char *)p);
- }
- } else {
- if (p2[0] != ',') {
- if (p2[0] == '\\') {
- p2++;
- }
- to = utf_ptr2char((char *)p2);
- }
- }
- if (to == NUL) {
- semsg(_("E357: 'langmap': Matching character missing for %s"),
- transchar(from));
- return;
- }
-
- if (from >= 256) {
- langmap_set_entry(from, to);
- } else {
- assert(to <= UCHAR_MAX);
- langmap_mapchar[from & 255] = (char_u)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] != ',') {
- semsg(_("E358: 'langmap': Extra characters after semicolon: %s"),
- p);
- return;
- }
- p++;
- }
- break;
- }
- }
- }
- }
-}
-
/// Return true if format option 'x' is in effect.
/// Take care of no formatting when 'paste' is set.
bool has_format_option(int x)
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 352f4dfe39..45f2cf0e1d 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -110,6 +110,7 @@ static char_u e_empty_sb[] = N_("E70: Empty %s%%[]");
static char_u e_recursive[] = N_("E956: Cannot use pattern recursively");
static char_u e_regexp_number_after_dot_pos_search[]
= N_("E1204: No Number allowed after .: '\\%%%c'");
+static char_u e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep");
#define NOT_MULTI 0
#define MULTI_ONE 1
@@ -1722,6 +1723,19 @@ int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *de
return result;
}
+// When nesting more than a couple levels it's probably a mistake.
+#define MAX_REGSUB_NESTING 4
+static char_u *eval_result[MAX_REGSUB_NESTING] = { NULL, NULL, NULL, NULL };
+
+#if defined(EXITFREE)
+void free_resub_eval_result(void)
+{
+ for (int i = 0; i < MAX_REGSUB_NESTING; i++) {
+ XFREE_CLEAR(eval_result[i]);
+ }
+}
+#endif
+
static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int destlen, int flags)
{
char_u *src;
@@ -1734,7 +1748,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des
fptr_T func_one = (fptr_T)NULL;
linenr_T clnum = 0; // init for GCC
int len = 0; // init for GCC
- static char_u *eval_result = NULL;
+ static int nesting = 0;
bool copy = flags & REGSUB_COPY;
// Be paranoid...
@@ -1745,6 +1759,11 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des
if (prog_magic_wrong()) {
return 0;
}
+ if (nesting == MAX_REGSUB_NESTING) {
+ emsg(_(e_substitute_nesting_too_deep));
+ return 0;
+ }
+ int nested = nesting;
src = source;
dst = dest;
@@ -1752,19 +1771,20 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des
if (expr != NULL || (source[0] == '\\' && source[1] == '=')) {
// To make sure that the length doesn't change between checking the
// length and copying the string, and to speed up things, the
- // resulting string is saved from the call with "flags & REGSUB_COPY"
- // == 0 to the call with "flags & REGSUB_COPY" != 0.
+ // resulting string is saved from the call with
+ // "flags & REGSUB_COPY" == 0 to the call with
+ // "flags & REGSUB_COPY" != 0.
if (copy) {
- if (eval_result != NULL) {
- STRCPY(dest, eval_result);
- dst += STRLEN(eval_result);
- XFREE_CLEAR(eval_result);
+ if (eval_result[nested] != NULL) {
+ STRCPY(dest, eval_result[nested]);
+ dst += STRLEN(eval_result[nested]);
+ XFREE_CLEAR(eval_result[nested]);
}
} else {
const bool prev_can_f_submatch = can_f_submatch;
regsubmatch_T rsm_save;
- xfree(eval_result);
+ XFREE_CLEAR(eval_result[nested]);
// The expression may contain substitute(), which calls us
// recursively. Make sure submatch() gets the text from the first
@@ -1779,6 +1799,11 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des
rsm.sm_maxline = rex.reg_maxline;
rsm.sm_line_lbr = rex.reg_line_lbr;
+ // Although unlikely, it is possible that the expression invokes a
+ // substitute command (it might fail, but still). Therefore keep
+ // an array of eval results.
+ nesting++;
+
if (expr != NULL) {
typval_T argv[2];
typval_T rettv;
@@ -1806,23 +1831,24 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des
}
if (rettv.v_type == VAR_UNKNOWN) {
// something failed, no need to report another error
- eval_result = NULL;
+ eval_result[nested] = NULL;
} else {
char buf[NUMBUFLEN];
- eval_result = (char_u *)tv_get_string_buf_chk(&rettv, buf);
- if (eval_result != NULL) {
- eval_result = vim_strsave(eval_result);
+ eval_result[nested] = (char_u *)tv_get_string_buf_chk(&rettv, buf);
+ if (eval_result[nested] != NULL) {
+ eval_result[nested] = vim_strsave(eval_result[nested]);
}
}
tv_clear(&rettv);
} else {
- eval_result = (char_u *)eval_to_string((char *)source + 2, NULL, true);
+ eval_result[nested] = (char_u *)eval_to_string((char *)source + 2, NULL, true);
}
+ nesting--;
- if (eval_result != NULL) {
+ if (eval_result[nested] != NULL) {
int had_backslash = false;
- for (s = eval_result; *s != NUL; MB_PTR_ADV(s)) {
+ for (s = eval_result[nested]; *s != NUL; MB_PTR_ADV(s)) {
// Change NL to CR, so that it becomes a line break,
// unless called from vim_regexec_nl().
// Skip over a backslashed character.
@@ -1844,12 +1870,12 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des
}
if (had_backslash && (flags & REGSUB_BACKSLASH)) {
// Backslashes will be consumed, need to double them.
- s = vim_strsave_escaped(eval_result, (char_u *)"\\");
- xfree(eval_result);
- eval_result = s;
+ s = vim_strsave_escaped(eval_result[nested], (char_u *)"\\");
+ xfree(eval_result[nested]);
+ eval_result[nested] = s;
}
- dst += STRLEN(eval_result);
+ dst += STRLEN(eval_result[nested]);
}
can_f_submatch = prev_can_f_submatch;
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 47305b6709..0d43458b5b 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -2381,8 +2381,6 @@ static void update_si_attr(int idx)
} else {
sip->si_attr = CUR_STATE(idx - 1).si_attr;
sip->si_trans_id = CUR_STATE(idx - 1).si_trans_id;
- sip->si_h_startpos = CUR_STATE(idx - 1).si_h_startpos;
- sip->si_h_endpos = CUR_STATE(idx - 1).si_h_endpos;
if (sip->si_cont_list == NULL) {
sip->si_flags |= HL_TRANS_CONT;
sip->si_cont_list = CUR_STATE(idx - 1).si_cont_list;
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index b2c752376f..887c8e1593 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -636,6 +636,14 @@ func Test_illegal_address2()
call delete('Xtest.vim')
endfunc
+func Test_mark_from_line_zero()
+ " this was reading past the end of the first (empty) line
+ new
+ norm oxxxx
+ call assert_fails("0;'(", 'E20:')
+ bwipe!
+endfunc
+
func Test_cmdline_complete_wildoptions()
help
call feedkeys(":tag /\<c-a>\<c-b>\"\<cr>", 'tx')
@@ -1212,6 +1220,30 @@ func Test_recalling_cmdline()
cunmap <Plug>(save-cmdline)
endfunc
+" this was going over the end of IObuff
+func Test_report_error_with_composing()
+ let caught = 'no'
+ try
+ exe repeat('0', 987) .. "0\xdd\x80\xdd\x80\xdd\x80\xdd\x80"
+ catch /E492:/
+ let caught = 'yes'
+ endtry
+ call assert_equal('yes', caught)
+endfunc
+
+" Test for expanding 2-letter and 3-letter :substitute command arguments.
+" These commands don't accept an argument.
+func Test_cmdline_complete_substitute_short()
+ for cmd in ['sc', 'sce', 'scg', 'sci', 'scI', 'scn', 'scp', 'scl',
+ \ 'sgc', 'sge', 'sg', 'sgi', 'sgI', 'sgn', 'sgp', 'sgl', 'sgr',
+ \ 'sic', 'sie', 'si', 'siI', 'sin', 'sip', 'sir',
+ \ 'sIc', 'sIe', 'sIg', 'sIi', 'sI', 'sIn', 'sIp', 'sIl', 'sIr',
+ \ 'src', 'srg', 'sri', 'srI', 'srn', 'srp', 'srl', 'sr']
+ call feedkeys(':' .. cmd .. " \<Tab>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"' .. cmd .. " \<Tab>", @:)
+ endfor
+endfunc
+
func Check_completion()
call assert_equal('let a', getcmdline())
call assert_equal(6, getcmdpos())
diff --git a/src/nvim/testdir/test_lispwords.vim b/src/nvim/testdir/test_lispwords.vim
index ff710b2716..4144fb0521 100644
--- a/src/nvim/testdir/test_lispwords.vim
+++ b/src/nvim/testdir/test_lispwords.vim
@@ -1,4 +1,5 @@
-" Tests for 'lispwords' settings being global-local
+" Tests for 'lispwords' settings being global-local.
+" And other lisp indent stuff.
set nocompatible viminfo+=nviminfo
@@ -85,4 +86,13 @@ func Test_lisp_indent()
set nolisp
endfunc
+func Test_lisp_indent_works()
+ " This was reading beyond the end of the line
+ new
+ exe "norm a\tü(\<CR>="
+ set lisp
+ norm ==
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index 979c4a70bc..8483435062 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -1,4 +1,4 @@
-" Tests for multi-line regexps with ":s".
+" Tests for the substitute (:s) command
func Test_multiline_subst()
enew!
@@ -831,12 +831,317 @@ func Test_using_old_sub()
~
s/
endfunc
- silent! s/\%')/\=Repl()
+ silent! s/\%')/\=Repl()
delfunc Repl
bwipe!
set nocompatible
endfunc
+" This was switching windows in between computing the length and using it.
+func Test_sub_change_window()
+ silent! lfile
+ sil! norm o0000000000000000000000000000000000000000000000000000
+ func Repl()
+ lopen
+ endfunc
+ silent! s/\%')/\=Repl()
+ bwipe!
+ bwipe!
+ delfunc Repl
+endfunc
+
+" Test for the 2-letter and 3-letter :substitute commands
+func Test_substitute_short_cmd()
+ new
+ call setline(1, ['one', 'one one one'])
+ s/one/two
+ call cursor(2, 1)
+
+ " :sc
+ call feedkeys(":sc\<CR>y", 'xt')
+ call assert_equal('two one one', getline(2))
+
+ " :scg
+ call setline(2, 'one one one')
+ call feedkeys(":scg\<CR>nyq", 'xt')
+ call assert_equal('one two one', getline(2))
+
+ " :sci
+ call setline(2, 'ONE One onE')
+ call feedkeys(":sci\<CR>y", 'xt')
+ call assert_equal('two One onE', getline(2))
+
+ " :scI
+ set ignorecase
+ call setline(2, 'ONE One one')
+ call feedkeys(":scI\<CR>y", 'xt')
+ call assert_equal('ONE One two', getline(2))
+ set ignorecase&
+
+ " :scn
+ call setline(2, 'one one one')
+ let t = execute('scn')->split("\n")
+ call assert_equal(['1 match on 1 line'], t)
+ call assert_equal('one one one', getline(2))
+
+ " :scp
+ call setline(2, "\tone one one")
+ redir => output
+ call feedkeys(":scp\<CR>y", 'xt')
+ redir END
+ call assert_equal(' two one one', output->split("\n")[-1])
+ call assert_equal("\ttwo one one", getline(2))
+
+ " :scl
+ call setline(2, "\tone one one")
+ redir => output
+ call feedkeys(":scl\<CR>y", 'xt')
+ redir END
+ call assert_equal("^Itwo one one$", output->split("\n")[-1])
+ call assert_equal("\ttwo one one", getline(2))
+
+ " :sgc
+ call setline(2, 'one one one one one')
+ call feedkeys(":sgc\<CR>nyyq", 'xt')
+ call assert_equal('one two two one one', getline(2))
+
+ " :sg
+ call setline(2, 'one one one')
+ sg
+ call assert_equal('two two two', getline(2))
+
+ " :sgi
+ call setline(2, 'ONE One onE')
+ sgi
+ call assert_equal('two two two', getline(2))
+
+ " :sgI
+ set ignorecase
+ call setline(2, 'ONE One one')
+ sgI
+ call assert_equal('ONE One two', getline(2))
+ set ignorecase&
+
+ " :sgn
+ call setline(2, 'one one one')
+ let t = execute('sgn')->split("\n")
+ call assert_equal(['3 matches on 1 line'], t)
+ call assert_equal('one one one', getline(2))
+
+ " :sgp
+ call setline(2, "\tone one one")
+ redir => output
+ sgp
+ redir END
+ call assert_equal(' two two two', output->split("\n")[-1])
+ call assert_equal("\ttwo two two", getline(2))
+
+ " :sgl
+ call setline(2, "\tone one one")
+ redir => output
+ sgl
+ redir END
+ call assert_equal("^Itwo two two$", output->split("\n")[-1])
+ call assert_equal("\ttwo two two", getline(2))
+
+ " :sgr
+ call setline(2, "one one one")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sgr
+ call assert_equal('xyz xyz xyz', getline(2))
+
+ " :sic
+ call cursor(1, 1)
+ s/one/two/e
+ call setline(2, "ONE One one")
+ call cursor(2, 1)
+ call feedkeys(":sic\<CR>y", 'xt')
+ call assert_equal('two One one', getline(2))
+
+ " :si
+ call setline(2, "ONE One one")
+ si
+ call assert_equal('two One one', getline(2))
+
+ " :siI
+ call setline(2, "ONE One one")
+ siI
+ call assert_equal('ONE One two', getline(2))
+
+ " :sin
+ call setline(2, 'ONE One onE')
+ let t = execute('sin')->split("\n")
+ call assert_equal(['1 match on 1 line'], t)
+ call assert_equal('ONE One onE', getline(2))
+
+ " :sip
+ call setline(2, "\tONE One onE")
+ redir => output
+ sip
+ redir END
+ call assert_equal(' two One onE', output->split("\n")[-1])
+ call assert_equal("\ttwo One onE", getline(2))
+
+ " :sir
+ call setline(2, "ONE One onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sir
+ call assert_equal('xyz One onE', getline(2))
+
+ " :sIc
+ call cursor(1, 1)
+ s/one/two/e
+ call setline(2, "ONE One one")
+ call cursor(2, 1)
+ call feedkeys(":sIc\<CR>y", 'xt')
+ call assert_equal('ONE One two', getline(2))
+
+ " :sIg
+ call setline(2, "ONE one onE one")
+ sIg
+ call assert_equal('ONE two onE two', getline(2))
+
+ " :sIi
+ call setline(2, "ONE One one")
+ sIi
+ call assert_equal('two One one', getline(2))
+
+ " :sI
+ call setline(2, "ONE One one")
+ sI
+ call assert_equal('ONE One two', getline(2))
+
+ " :sIn
+ call setline(2, 'ONE One one')
+ let t = execute('sIn')->split("\n")
+ call assert_equal(['1 match on 1 line'], t)
+ call assert_equal('ONE One one', getline(2))
+
+ " :sIp
+ call setline(2, "\tONE One one")
+ redir => output
+ sIp
+ redir END
+ call assert_equal(' ONE One two', output->split("\n")[-1])
+ call assert_equal("\tONE One two", getline(2))
+
+ " :sIl
+ call setline(2, "\tONE onE one")
+ redir => output
+ sIl
+ redir END
+ call assert_equal("^IONE onE two$", output->split("\n")[-1])
+ call assert_equal("\tONE onE two", getline(2))
+
+ " :sIr
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sIr
+ call assert_equal('ONE xyz onE', getline(2))
+
+ " :src
+ call setline(2, "ONE one one")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ call feedkeys(":src\<CR>y", 'xt')
+ call assert_equal('ONE xyz one', getline(2))
+
+ " :srg
+ call setline(2, "one one one")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ srg
+ call assert_equal('xyz xyz xyz', getline(2))
+
+ " :sri
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sri
+ call assert_equal('xyz one onE', getline(2))
+
+ " :srI
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ srI
+ call assert_equal('ONE xyz onE', getline(2))
+
+ " :srn
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ let t = execute('srn')->split("\n")
+ call assert_equal(['1 match on 1 line'], t)
+ call assert_equal('ONE one onE', getline(2))
+
+ " :srp
+ call setline(2, "\tONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ redir => output
+ srp
+ redir END
+ call assert_equal(' ONE xyz onE', output->split("\n")[-1])
+ call assert_equal("\tONE xyz onE", getline(2))
+
+ " :srl
+ call setline(2, "\tONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ redir => output
+ srl
+ redir END
+ call assert_equal("^IONE xyz onE$", output->split("\n")[-1])
+ call assert_equal("\tONE xyz onE", getline(2))
+
+ " :sr
+ call setline(2, "ONE one onE")
+ call cursor(2, 1)
+ s/abc/xyz/e
+ let @/ = 'one'
+ sr
+ call assert_equal('ONE xyz onE', getline(2))
+
+ " :sce
+ s/abc/xyz/e
+ call assert_fails("sc", 'E486:')
+ sce
+ " :sge
+ call assert_fails("sg", 'E486:')
+ sge
+ " :sie
+ call assert_fails("si", 'E486:')
+ sie
+ " :sIe
+ call assert_fails("sI", 'E486:')
+ sIe
+
+ bw!
+endfunc
+
+" This should be done last to reveal a memory leak when vim_regsub_both() is
+" called to evaluate an expression but it is not used in a second call.
+func Test_z_substitute_expr_leak()
+ func SubExpr()
+ ~n
+ endfunc
+ silent! s/\%')/\=SubExpr()
+ delfunc SubExpr
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 6bef61ae8f..9f50b3c241 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -745,8 +745,9 @@ func Test_search_syntax_skip()
1
call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"')
call assert_equal('Another Text for VIM', getline('.'))
+
1
- call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") !~? "string"')
+ call search('VIM', 'cw', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") !~? "string"')
call assert_equal(' let a = "VIM"', getline('.'))
" Skip argument using Lambda.
@@ -755,29 +756,106 @@ func Test_search_syntax_skip()
call assert_equal('Another Text for VIM', getline('.'))
1
- call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") !~? "string"})
+ call search('VIM', 'cw', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") !~? "string"})
call assert_equal(' let a = "VIM"', getline('.'))
" Skip argument using funcref.
func InComment()
return synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"
endfunc
- func InString()
+ func NotInString()
return synIDattr(synID(line("."), col("."), 1), "name") !~? "string"
endfunc
+
1
call search('VIM', 'w', '', 0, function('InComment'))
call assert_equal('Another Text for VIM', getline('.'))
1
- call search('VIM', 'w', '', 0, function('InString'))
+ call search('VIM', 'cw', '', 0, function('NotInString'))
call assert_equal(' let a = "VIM"', getline('.'))
delfunc InComment
- delfunc InString
+ delfunc NotInString
bwipe!
endfunc
+func Test_syn_contained_transparent()
+ " Comments starting with "Regression:" show the result when the highlighting
+ " span of the containing item is assigned to the contained region.
+ syntax on
+
+ let l:case = "Transparent region contained in region"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax region Y start=/(/ end=/)/ contains=X
+
+ call setline(1, "==(--[~~]--)==")
+ let l:expected = " YYYYYYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Transparent region extends region"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax region Y start=/(/ end=/)/ end=/e/ contains=X
+
+ call setline(1, "==(--[~~e~~]--)==")
+ let l:expected = " YYYYYYYYYYYYY "
+ " Regression: " YYYYYYY YYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Nested transparent regions extend region"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax region Y start=/(/ end=/)/ end=/e/ contains=X
+
+ call setline(1, "==(--[~~e~~[~~e~~]~~e~~]--)==")
+ let l:expected = " YYYYYYYYYYYYYYYYYYYYYYYYY "
+ " Regression: " YYYYYYY YYYYYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Transparent region contained in match"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax match Y /(.\{-})/ contains=X
+
+ call setline(1, "==(--[~~]--)==")
+ let l:expected = " YYYYYYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Transparent region extends match"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax match Y /(.\{-}[e)]/ contains=X
+
+ call setline(1, "==(--[~~e~~]--)==")
+ let l:expected = " YYYYYYYYYY "
+ " Regression: " YYYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+
+ let l:case = "Nested transparent regions extend match"
+ new
+ syntax region X start=/\[/ end=/\]/ contained transparent
+ syntax match Y /(.\{-}[e)]/ contains=X
+
+ call setline(1, "==(--[~~e~~[~~e~~]~~e~~]--)==")
+ let l:expected = " YYYYYYYYYYYYYYYYYYYYYY "
+ " Regression: " YYYYYYY YYYYYY "
+ eval AssertHighlightGroups(1, 1, l:expected, 1, l:case)
+ syntax clear Y X
+ bw!
+endfunc
+
func Test_syn_include_contains_TOP()
let l:case = "TOP in included syntax means its group list name"
new
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 96ce2c4cb1..a49e9df9ee 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -495,6 +495,11 @@ int ui_current_col(void)
return cursor_col;
}
+handle_T ui_cursor_grid(void)
+{
+ return cursor_grid_handle;
+}
+
void ui_flush(void)
{
cmdline_ui_flush();
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 3ce2b80ea9..5df70d0d8e 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -300,6 +300,19 @@ ScreenGrid *ui_comp_mouse_focus(int row, int col)
return NULL;
}
+/// Compute which grid is on top at supplied screen coordinates
+ScreenGrid *ui_comp_get_grid_at_coord(int row, int col)
+{
+ for (ssize_t i = (ssize_t)kv_size(layers) - 1; i > 0; i--) {
+ ScreenGrid *grid = kv_A(layers, i);
+ if (row >= grid->comp_row && row < grid->comp_row + grid->rows
+ && col >= grid->comp_col && col < grid->comp_col + grid->cols) {
+ return grid;
+ }
+ }
+ return &default_grid;
+}
+
/// Baseline implementation. This is always correct, but we can sometimes
/// do something more efficient (where efficiency means smaller deltas to
/// the downstream UI.)
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 1a1f62f2c0..9b9a2126ff 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -26,6 +26,7 @@
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/main.h"
+#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/match.h"
#include "nvim/memline.h"
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua
index dd3af8c28f..dc668e7201 100644
--- a/test/functional/api/buffer_spec.lua
+++ b/test/functional/api/buffer_spec.lua
@@ -227,8 +227,8 @@ describe('api/buf', function()
it('can get a single line with strict indexing', function()
set_lines(0, 1, true, {'line1.a'})
eq(1, line_count()) -- sanity
- eq(false, pcall(get_lines, 1, 2, true))
- eq(false, pcall(get_lines, -3, -2, true))
+ eq('Index out of bounds', pcall_err(get_lines, 1, 2, true))
+ eq('Index out of bounds', pcall_err(get_lines, -3, -2, true))
end)
it('can get a single line with non-strict indexing', function()
@@ -240,11 +240,11 @@ describe('api/buf', function()
it('can set and delete a single line with strict indexing', function()
set_lines(0, 1, true, {'line1.a'})
- eq(false, pcall(set_lines, 1, 2, true, {'line1.b'}))
- eq(false, pcall(set_lines, -3, -2, true, {'line1.c'}))
+ eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, {'line1.b'}))
+ eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, {'line1.c'}))
eq({'line1.a'}, get_lines(0, -1, true))
- eq(false, pcall(set_lines, 1, 2, true, {}))
- eq(false, pcall(set_lines, -3, -2, true, {}))
+ eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, {}))
+ eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, {}))
eq({'line1.a'}, get_lines(0, -1, true))
end)
@@ -302,9 +302,9 @@ describe('api/buf', function()
set_lines(0, -1, true, {'a', 'b', 'c'})
eq({'a', 'b', 'c'}, get_lines(0, -1, true)) --sanity
- eq(false, pcall(get_lines, 3, 4, true))
- eq(false, pcall(get_lines, 3, 10, true))
- eq(false, pcall(get_lines, -5, -5, true))
+ eq('Index out of bounds', pcall_err(get_lines, 3, 4, true))
+ eq('Index out of bounds', pcall_err(get_lines, 3, 10, true))
+ eq('Index out of bounds', pcall_err(get_lines, -5, -5, true))
-- empty or inverted ranges are not errors
eq({}, get_lines(3, -1, true))
eq({}, get_lines(-3, -4, true))
@@ -316,10 +316,10 @@ describe('api/buf', function()
eq({'c'}, get_lines(-2, 5, false))
eq({'a', 'b', 'c'}, get_lines(0, 6, false))
- eq(false, pcall(set_lines, 4, 6, true, {'d'}))
+ eq('Index out of bounds', pcall_err(set_lines, 4, 6, true, {'d'}))
set_lines(4, 6, false, {'d'})
eq({'a', 'b', 'c', 'd'}, get_lines(0, -1, true))
- eq(false, pcall(set_lines, -6, -6, true, {'e'}))
+ eq('Index out of bounds', pcall_err(set_lines, -6, -6, true, {'e'}))
set_lines(-6, -6, false, {'e'})
eq({'e', 'a', 'b', 'c', 'd'}, get_lines(0, -1, true))
end)
@@ -392,7 +392,7 @@ describe('api/buf', function()
end)
end)
- describe('nvim_buf_get_lines, nvim_buf_set_text', function()
+ describe('nvim_buf_set_text', function()
local get_lines, set_text = curbufmeths.get_lines, curbufmeths.set_text
it('works', function()
@@ -430,6 +430,10 @@ describe('api/buf', function()
set_text(-1, 0, -1, 0, {'text'})
eq({'goodbye bar', 'text'}, get_lines(0, 2, true))
+
+ -- can append to a line
+ set_text(1, 4, -1, 4, {' and', 'more'})
+ eq({'goodbye bar', 'text and', 'more'}, get_lines(0, 3, true))
end)
it('works with undo', function()
@@ -513,12 +517,12 @@ describe('api/buf', function()
eq({0, 6}, curbufmeths.get_extmark_by_id(ns, id2, {}))
eq({0, 6}, curbufmeths.get_extmark_by_id(ns, id3, {}))
- -- marks should be shifted over by the correct number of bytes for multibyte
- -- chars
- set_text(0, 0, 0, 0, {'Ø'})
- eq({0, 3}, curbufmeths.get_extmark_by_id(ns, id1, {}))
- eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id2, {}))
- eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id3, {}))
+ -- marks should be shifted over by the correct number of bytes for multibyte
+ -- chars
+ set_text(0, 0, 0, 0, {'Ø'})
+ eq({0, 3}, curbufmeths.get_extmark_by_id(ns, id1, {}))
+ eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id2, {}))
+ eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id3, {}))
end)
it("correctly marks changed region for redraw #13890", function()
@@ -540,18 +544,39 @@ describe('api/buf', function()
|
]])
+ end)
+ it('errors on out-of-range', function()
+ insert([[
+ hello foo!
+ text]])
+ eq('start_row out of bounds', pcall_err(set_text, 2, 0, 3, 0, {}))
+ eq('start_row out of bounds', pcall_err(set_text, -3, 0, 0, 0, {}))
+ eq('end_row out of bounds', pcall_err(set_text, 0, 0, 2, 0, {}))
+ eq('end_row out of bounds', pcall_err(set_text, 0, 0, -3, 0, {}))
+ eq('start_col out of bounds', pcall_err(set_text, 1, 5, 1, 5, {}))
+ eq('end_col out of bounds', pcall_err(set_text, 1, 0, 1, 5, {}))
+ end)
+
+ it('errors when start is greater than end', function()
+ insert([[
+ hello foo!
+ text]])
+ eq('start is higher than end', pcall_err(set_text, 1, 0, 0, 0, {}))
+ eq('start is higher than end', pcall_err(set_text, 0, 1, 0, 0, {}))
end)
end)
describe('nvim_buf_get_text', function()
local get_text = curbufmeths.get_text
- it('works', function()
+ before_each(function()
insert([[
hello foo!
text]])
+ end)
+ it('works', function()
eq({'hello'}, get_text(0, 0, 0, 5, {}))
eq({'hello foo!'}, get_text(0, 0, 0, 42, {}))
eq({'foo!'}, get_text(0, 6, 0, 10, {}))
@@ -562,13 +587,17 @@ describe('api/buf', function()
end)
it('errors on out-of-range', function()
- eq(false, pcall(get_text, 2, 0, 3, 0, {}))
- eq(false, pcall(get_text, 0, 0, 4, 0, {}))
+ eq('Index out of bounds', pcall_err(get_text, 2, 0, 3, 0, {}))
+ eq('Index out of bounds', pcall_err(get_text, -3, 0, 0, 0, {}))
+ eq('Index out of bounds', pcall_err(get_text, 0, 0, 2, 0, {}))
+ eq('Index out of bounds', pcall_err(get_text, 0, 0, -3, 0, {}))
+ -- no ml_get errors should happen #19017
+ eq('', meths.get_vvar('errmsg'))
end)
it('errors when start is greater than end', function()
- eq(false, pcall(get_text, 1, 0, 0, 0, {}))
- eq(false, pcall(get_text, 0, 1, 0, 0, {}))
+ eq('start is higher than end', pcall_err(get_text, 1, 0, 0, 0, {}))
+ eq('start_col must be less than end_col', pcall_err(get_text, 0, 1, 0, 0, {}))
end)
end)
diff --git a/test/functional/ex_cmds/map_spec.lua b/test/functional/ex_cmds/map_spec.lua
index eae36b9ae9..c6bdd017bd 100644
--- a/test/functional/ex_cmds/map_spec.lua
+++ b/test/functional/ex_cmds/map_spec.lua
@@ -86,7 +86,7 @@ n asdf1 qwert
end)
end)
-describe(':*map cursor and redrawing', function()
+describe('Screen', function()
local screen
before_each(function()
clear()
@@ -149,6 +149,18 @@ describe(':*map cursor and redrawing', function()
]])
end)
+ it('cursor position does not move after empty-string :cmap <expr> #19046', function()
+ command([[cnoremap <expr> <F2> '']])
+ feed(':<F2>')
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ :^ |
+ ]])
+ end)
+
it('cursor is restored after :map <expr> which redraws statusline vim-patch:8.1.2336', function()
exec([[
call setline(1, ['one', 'two', 'three'])
@@ -157,12 +169,12 @@ describe(':*map cursor and redrawing', function()
hi! link StatusLine ErrorMsg
noremap <expr> <C-B> Func()
func Func()
- let g:on = !get(g:, 'on', 0)
- redraws
- return ''
+ let g:on = !get(g:, 'on', 0)
+ redraws
+ return ''
endfunc
func Status()
- return get(g:, 'on', 0) ? '[on]' : ''
+ return get(g:, 'on', 0) ? '[on]' : ''
endfunc
set stl=%{Status()}
]])
diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt
index 270540de2e..6010fcaf1e 100644
--- a/test/functional/fixtures/CMakeLists.txt
+++ b/test/functional/fixtures/CMakeLists.txt
@@ -4,7 +4,7 @@ target_link_libraries(tty-test ${LIBUV_LIBRARIES})
add_executable(shell-test EXCLUDE_FROM_ALL shell-test.c)
add_executable(printargs-test EXCLUDE_FROM_ALL printargs-test.c)
add_executable(printenv-test EXCLUDE_FROM_ALL printenv-test.c)
-if(WIN32)
+if(MINGW)
set_target_properties(printenv-test PROPERTIES LINK_FLAGS -municode)
endif()
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index dfbcbbe688..883e0e373b 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -1400,6 +1400,8 @@ describe('lua stdlib', function()
pcall_err(exec_lua, 'return vim.bo.nosuchopt'))
matches("Expected lua string$",
pcall_err(exec_lua, 'return vim.bo[0][0].autoread'))
+ matches("Invalid buffer id: %-1$",
+ pcall_err(exec_lua, 'return vim.bo[-1].filetype'))
end)
it('vim.wo', function()
@@ -1419,6 +1421,8 @@ describe('lua stdlib', function()
pcall_err(exec_lua, 'return vim.wo.notanopt'))
matches("Expected lua string$",
pcall_err(exec_lua, 'return vim.wo[0][0].list'))
+ matches("Invalid window id: %-1$",
+ pcall_err(exec_lua, 'return vim.wo[-1].list'))
eq(2, funcs.luaeval "vim.wo[1000].cole")
exec_lua [[
vim.wo[1000].cole = 0
diff --git a/test/functional/vimscript/screenchar_spec.lua b/test/functional/vimscript/screenchar_spec.lua
new file mode 100644
index 0000000000..767e3c57ef
--- /dev/null
+++ b/test/functional/vimscript/screenchar_spec.lua
@@ -0,0 +1,69 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq
+local command, meths, funcs = helpers.command, helpers.meths, helpers.funcs
+local tbl_deep_extend = helpers.tbl_deep_extend
+
+-- Set up two overlapping floating windows
+local setup_floating_windows = function()
+ local base_opts = {
+ relative = 'editor',
+ height = 1,
+ width = 2,
+ anchor = 'NW',
+ style = 'minimal',
+ border = 'none',
+ }
+
+ local bufnr_1 = meths.create_buf(false, true)
+ meths.buf_set_lines(bufnr_1, 0, -1, true, { 'aa' })
+ local opts_1 = tbl_deep_extend('force', { row = 0, col = 0, zindex = 11 }, base_opts)
+ meths.open_win(bufnr_1, false, opts_1)
+
+ local bufnr_2 = meths.create_buf(false, true)
+ meths.buf_set_lines(bufnr_2, 0, -1, true, { 'bb' })
+ local opts_2 = tbl_deep_extend('force', { row = 0, col = 1, zindex = 10 }, base_opts)
+ meths.open_win(bufnr_2, false, opts_2)
+
+ command('redraw')
+end
+
+describe('screenchar() and family respect floating windows', function()
+ before_each(function()
+ clear()
+ -- These commands result into visible text `aabc`.
+ -- `aab` - from floating windows, `c` - from text in regular window.
+ meths.buf_set_lines(0, 0, -1, true, { 'cccc' })
+ setup_floating_windows()
+ end)
+
+ it('screenattr()', function()
+ local attr_1 = funcs.screenattr(1, 1)
+ local attr_2 = funcs.screenattr(1, 2)
+ local attr_3 = funcs.screenattr(1, 3)
+ local attr_4 = funcs.screenattr(1, 4)
+ eq(attr_1, attr_2)
+ eq(attr_1, attr_3)
+ neq(attr_1, attr_4)
+ end)
+
+ it('screenchar()', function()
+ eq(97, funcs.screenchar(1, 1))
+ eq(97, funcs.screenchar(1, 2))
+ eq(98, funcs.screenchar(1, 3))
+ eq(99, funcs.screenchar(1, 4))
+ end)
+
+ it('screenchars()', function()
+ eq({ 97 }, funcs.screenchars(1, 1))
+ eq({ 97 }, funcs.screenchars(1, 2))
+ eq({ 98 }, funcs.screenchars(1, 3))
+ eq({ 99 }, funcs.screenchars(1, 4))
+ end)
+
+ it('screenstring()', function()
+ eq('a', funcs.screenstring(1, 1))
+ eq('a', funcs.screenstring(1, 2))
+ eq('b', funcs.screenstring(1, 3))
+ eq('c', funcs.screenstring(1, 4))
+ end)
+end)
diff --git a/test/helpers.lua b/test/helpers.lua
index a3482d1b46..a1c3dfacd2 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -291,7 +291,8 @@ module.tmpname = (function()
if tmpdir_is_local(tmpdir) then
-- Cannot control os.tmpname() dir, so hack our own tmpname() impl.
seq = seq + 1
- local fname = tmpdir..'/nvim-test-lua-'..seq
+ -- "…/Xtest_tmpdir/T42.7"
+ local fname = ('%s/%s.%d'):format(tmpdir, (_G._nvim_test_id or 'nvim-test'), seq)
io.open(fname, 'w'):close()
return fname
else