aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt12
-rw-r--r--src/nvim/api/buffer.c319
-rw-r--r--src/nvim/api/private/helpers.c126
-rw-r--r--src/nvim/api/vim.c148
-rw-r--r--src/nvim/buffer.c103
-rw-r--r--src/nvim/buffer_defs.h29
-rw-r--r--src/nvim/change.c42
-rw-r--r--src/nvim/channel.c30
-rw-r--r--src/nvim/context.c2
-rw-r--r--src/nvim/diff.c32
-rw-r--r--src/nvim/diff.h7
-rw-r--r--src/nvim/edit.c37
-rw-r--r--src/nvim/eval.c262
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/typval.c2
-rw-r--r--src/nvim/eval/typval.h2
-rw-r--r--src/nvim/event/libuv_process.c2
-rw-r--r--src/nvim/event/process.h1
-rw-r--r--src/nvim/ex_cmds.c297
-rw-r--r--src/nvim/ex_cmds2.c71
-rw-r--r--src/nvim/ex_cmds_defs.h91
-rw-r--r--src/nvim/ex_docmd.c820
-rw-r--r--src/nvim/ex_eval.c35
-rw-r--r--src/nvim/ex_eval.h44
-rw-r--r--src/nvim/ex_getln.c62
-rw-r--r--src/nvim/fileio.c58
-rw-r--r--src/nvim/fold.c5
-rw-r--r--src/nvim/generators/gen_char_blob.lua70
-rwxr-xr-xsrc/nvim/generators/gen_declarations.lua4
-rw-r--r--src/nvim/getchar.c1
-rw-r--r--src/nvim/globals.h7
-rw-r--r--src/nvim/lib/kbtree.h6
-rw-r--r--src/nvim/log.c1
-rw-r--r--src/nvim/lua/converter.c42
-rw-r--r--src/nvim/lua/executor.c226
-rw-r--r--src/nvim/lua/executor.h2
-rw-r--r--src/nvim/lua/vim.lua253
-rw-r--r--src/nvim/main.c5
-rw-r--r--src/nvim/mark.c58
-rw-r--r--src/nvim/mark_extended.c1139
-rw-r--r--src/nvim/mark_extended.h282
-rw-r--r--src/nvim/mark_extended_defs.h54
-rw-r--r--src/nvim/memline.c7
-rw-r--r--src/nvim/message.c4
-rw-r--r--src/nvim/misc1.c3
-rw-r--r--src/nvim/mouse.c29
-rw-r--r--src/nvim/normal.c30
-rw-r--r--src/nvim/ops.c128
-rw-r--r--src/nvim/option.c80
-rw-r--r--src/nvim/option_defs.h11
-rw-r--r--src/nvim/options.lua20
-rw-r--r--src/nvim/os/dl.c8
-rw-r--r--src/nvim/os/env.c148
-rw-r--r--src/nvim/os/tty.c1
-rw-r--r--src/nvim/os_unix.c2
-rw-r--r--src/nvim/po/af.po2
-rw-r--r--src/nvim/po/check.vim11
-rw-r--r--src/nvim/po/fi.po2
-rw-r--r--src/nvim/po/uk.po4
-rw-r--r--src/nvim/pos.h4
-rw-r--r--src/nvim/quickfix.c556
-rw-r--r--src/nvim/screen.c16
-rw-r--r--src/nvim/search.c75
-rw-r--r--src/nvim/spell.c11
-rw-r--r--src/nvim/spellfile.c17
-rw-r--r--src/nvim/state.c2
-rw-r--r--src/nvim/syntax.c2
-rw-r--r--src/nvim/tag.c72
-rw-r--r--src/nvim/testdir/Makefile24
-rwxr-xr-xsrc/nvim/testdir/runnvim.sh5
-rw-r--r--src/nvim/testdir/runtest.vim2
-rw-r--r--src/nvim/testdir/setup.vim1
-rw-r--r--src/nvim/testdir/shared.vim24
-rw-r--r--src/nvim/testdir/test48.in82
-rw-r--r--src/nvim/testdir/test48.ok23
-rw-r--r--src/nvim/testdir/test49.vim2
-rw-r--r--src/nvim/testdir/test64.in654
-rw-r--r--src/nvim/testdir/test64.ok1107
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_backup.vim58
-rw-r--r--src/nvim/testdir/test_bufwintabinfo.vim10
-rw-r--r--src/nvim/testdir/test_cmdline.vim89
-rw-r--r--src/nvim/testdir/test_const.vim20
-rw-r--r--src/nvim/testdir/test_cursor_func.vim1
-rw-r--r--src/nvim/testdir/test_diffmode.vim25
-rw-r--r--src/nvim/testdir/test_display.vim1
-rw-r--r--src/nvim/testdir/test_filetype.vim3
-rw-r--r--src/nvim/testdir/test_fold.vim25
-rw-r--r--src/nvim/testdir/test_functions.vim7
-rw-r--r--src/nvim/testdir/test_gf.vim25
-rw-r--r--src/nvim/testdir/test_gn.vim3
-rw-r--r--src/nvim/testdir/test_ins_complete.vim18
-rw-r--r--src/nvim/testdir/test_join.vim406
-rw-r--r--src/nvim/testdir/test_let.vim17
-rw-r--r--src/nvim/testdir/test_modeline.vim117
-rw-r--r--src/nvim/testdir/test_normal.vim7
-rw-r--r--src/nvim/testdir/test_options.vim6
-rw-r--r--src/nvim/testdir/test_popup.vim42
-rw-r--r--src/nvim/testdir/test_quickfix.vim835
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim668
-rw-r--r--src/nvim/testdir/test_registers.vim86
-rw-r--r--src/nvim/testdir/test_search.vim4
-rw-r--r--src/nvim/testdir/test_spell.vim37
-rw-r--r--src/nvim/testdir/test_statusline.vim22
-rw-r--r--src/nvim/testdir/test_substitute.vim2
-rw-r--r--src/nvim/testdir/test_tagjump.vim47
-rw-r--r--src/nvim/testdir/test_textobjects.vim10
-rw-r--r--src/nvim/testdir/test_undo.vim253
-rw-r--r--src/nvim/testdir/test_vimscript.vim2
-rw-r--r--src/nvim/testdir/test_virtualedit.vim135
-rw-r--r--src/nvim/tui/input.c8
-rw-r--r--src/nvim/tui/terminfo.c3
-rw-r--r--src/nvim/tui/tui.c21
-rw-r--r--src/nvim/ui_compositor.c2
-rw-r--r--src/nvim/undo.c77
-rw-r--r--src/nvim/undo_defs.h16
-rw-r--r--src/nvim/version.c41
-rw-r--r--src/nvim/window.c30
119 files changed, 7430 insertions, 3710 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index b00ac866b7..bc8e64dd41 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -52,7 +52,8 @@ set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua)
set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode)
set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h)
-set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
+set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
+set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua)
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json)
set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint")
@@ -317,11 +318,13 @@ add_custom_command(
add_custom_command(
OUTPUT ${VIM_MODULE_FILE}
- COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_SOURCE}
- ${VIM_MODULE_FILE} vim_module
+ COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE}
+ ${LUA_VIM_MODULE_SOURCE} vim_module
+ ${LUA_SHARED_MODULE_SOURCE} shared_module
DEPENDS
${CHAR_BLOB_GENERATOR}
- ${VIM_MODULE_SOURCE}
+ ${LUA_VIM_MODULE_SOURCE}
+ ${LUA_SHARED_MODULE_SOURCE}
)
list(APPEND NVIM_GENERATED_SOURCES
@@ -462,6 +465,7 @@ install_helper(TARGETS nvim)
set_property(TARGET nvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
+set_property(TARGET nvim PROPERTY ENABLE_EXPORTS TRUE)
if(ENABLE_LTO AND (POLICY CMP0069))
include(CheckIPOSupported)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index cb74c4227b..8f5718d97e 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -23,7 +23,10 @@
#include "nvim/memory.h"
#include "nvim/misc1.h"
#include "nvim/ex_cmds.h"
+#include "nvim/map_defs.h"
+#include "nvim/map.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
#include "nvim/syntax.h"
@@ -103,6 +106,14 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// Activates buffer-update events on a channel, or as Lua callbacks.
///
+/// Example (Lua): capture buffer updates in a global `events` variable
+/// (use "print(vim.inspect(events))" to see its contents):
+/// <pre>
+/// events = {}
+/// vim.api.nvim_buf_attach(0, false, {
+/// on_lines=function(...) table.insert(events, {...}) end})
+/// </pre>
+///
/// @see |nvim_buf_detach()|
/// @see |api-buffer-updates-lua|
///
@@ -544,7 +555,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
(linenr_T)(end - 1),
MAXLNUM,
(long)extra,
- false);
+ false,
+ kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
@@ -999,6 +1011,254 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+/// Returns position for a given extmark id
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return (row, col) tuple or empty list () if extmark id was absent
+ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
+ Integer id, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ return rv;
+ }
+
+ Extmark *extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ if (!extmark) {
+ return rv;
+ }
+ ADD(rv, INTEGER_OBJ((Integer)extmark->line->lnum-1));
+ ADD(rv, INTEGER_OBJ((Integer)extmark->col-1));
+ return rv;
+}
+
+/// Gets extmarks in "traversal order" from a |charwise| region defined by
+/// buffer positions (inclusive, 0-indexed |api-indexing|).
+///
+/// Region can be given as (row,col) tuples, or valid extmark ids (whose
+/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
+/// respectively, thus the following are equivalent:
+///
+/// <pre>
+/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})
+/// </pre>
+///
+/// If `end` is less than `start`, traversal works backwards. (Useful
+/// with `limit`, to get the first marks prior to a given position.)
+///
+/// Example:
+///
+/// <pre>
+/// local a = vim.api
+/// local pos = a.nvim_win_get_cursor(0)
+/// local ns = a.nvim_create_namespace('my-plugin')
+/// -- Create new extmark at line 1, column 1.
+/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+/// -- Create new extmark at line 3, column 1.
+/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {})
+/// -- Get extmarks only from line 3.
+/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+/// -- Get all marks in this buffer + namespace.
+/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+/// print(vim.inspect(ms))
+/// </pre>
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param start Start of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param end End of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param opts Optional parameters. Keys:
+/// - limit: Maximum number of marks to return
+/// @param[out] err Error details, if any
+/// @return List of [extmark_id, row, col] tuples in "traversal order".
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
+ Object end, Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ return rv;
+ }
+ Integer limit = -1;
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("limit", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation, "limit is not an integer");
+ return rv;
+ }
+ limit = v->data.integer;
+ v->data.integer = LUA_NOREF;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return rv;
+ }
+ }
+
+ if (limit == 0) {
+ return rv;
+ }
+
+
+ bool reverse = false;
+
+ linenr_T l_lnum;
+ colnr_T l_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) {
+ return rv;
+ }
+
+ linenr_T u_lnum;
+ colnr_T u_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) {
+ return rv;
+ }
+
+ if (l_lnum > u_lnum || (l_lnum == u_lnum && l_col > u_col)) {
+ reverse = true;
+ linenr_T tmp_lnum = l_lnum;
+ l_lnum = u_lnum;
+ u_lnum = tmp_lnum;
+ colnr_T tmp_col = l_col;
+ l_col = u_col;
+ u_col = tmp_col;
+ }
+
+
+ ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, u_lnum,
+ u_col, (int64_t)limit, reverse);
+
+ for (size_t i = 0; i < kv_size(marks); i++) {
+ Array mark = ARRAY_DICT_INIT;
+ Extmark *extmark = kv_A(marks, i);
+ ADD(mark, INTEGER_OBJ((Integer)extmark->mark_id));
+ ADD(mark, INTEGER_OBJ(extmark->line->lnum-1));
+ ADD(mark, INTEGER_OBJ(extmark->col-1));
+ ADD(rv, ARRAY_OBJ(mark));
+ }
+
+ kv_destroy(marks);
+ return rv;
+}
+
+/// Creates or updates an extmark.
+///
+/// To create a new extmark, pass id=0. The extmark id will be returned.
+// To move an existing mark, pass its id.
+///
+/// It is also allowed to create a new mark by passing in a previously unused
+/// id, but the caller must then keep track of existing and unused ids itself.
+/// (Useful over RPC, to avoid waiting for the return value.)
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id, or 0 to create new
+/// @param line Line number where to place the mark
+/// @param col Column where to place the mark
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any
+/// @return Id of the created/updated extmark
+Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
+ Integer line, Integer col,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return 0;
+ }
+
+ size_t len = 0;
+ if (line < 0 || line > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ return 0;
+ } else if (line < buf->b_ml.ml_line_count) {
+ len = STRLEN(ml_get_buf(buf, (linenr_T)line+1, false));
+ }
+
+ if (col == -1) {
+ col = (Integer)len;
+ } else if (col < -1 || col > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ return 0;
+ }
+
+ uint64_t id_num;
+ if (id == 0) {
+ id_num = extmark_free_id_get(buf, (uint64_t)ns_id);
+ } else if (id > 0) {
+ id_num = (uint64_t)id;
+ } else {
+ api_set_error(err, kErrorTypeValidation, _("Invalid mark id"));
+ return 0;
+ }
+
+ extmark_set(buf, (uint64_t)ns_id, id_num,
+ (linenr_T)line+1, (colnr_T)col+1, kExtmarkUndo);
+
+ return (Integer)id_num;
+}
+
+/// Removes an extmark.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return true if the extmark was found, else false
+Boolean nvim_buf_del_extmark(Buffer buffer,
+ Integer ns_id,
+ Integer id,
+ Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return false;
+ }
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ return false;
+ }
+
+ return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id, kExtmarkUndo);
+}
+
/// Adds a highlight to buffer.
///
/// Useful for plugins that dynamically generate highlights to a buffer
@@ -1065,7 +1325,8 @@ Integer nvim_buf_add_highlight(Buffer buffer,
return ns_id;
}
-/// Clears namespaced objects, highlights and virtual text, from a line range
+/// Clears namespaced objects (highlights, extmarks, virtual text) from
+/// a region.
///
/// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire
/// buffer, specify line_start=0 and line_end=-1.
@@ -1097,6 +1358,10 @@ void nvim_buf_clear_namespace(Buffer buffer,
}
bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end);
+ extmark_clear(buf, ns_id == -1 ? 0 : (uint64_t)ns_id,
+ (linenr_T)line_start+1,
+ (linenr_T)line_end,
+ kExtmarkUndo);
}
/// Clears highlights and virtual text from namespace and range of lines
@@ -1207,6 +1472,56 @@ free_exit:
return 0;
}
+/// Get the virtual text (annotation) for a buffer line.
+///
+/// The virtual text is returned as list of lists, whereas the inner lists have
+/// either one or two elements. The first element is the actual text, the
+/// optional second element is the highlight group.
+///
+/// The format is exactly the same as given to nvim_buf_set_virtual_text().
+///
+/// If there is no virtual text associated with the given line, an empty list
+/// is returned.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param line Line to get the virtual text from (zero-indexed)
+/// @param[out] err Error details, if any
+/// @return List of virtual text chunks
+Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array chunks = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return chunks;
+ }
+
+ if (lnum < 0 || lnum >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return chunks;
+ }
+
+ BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, (linenr_T)(lnum + 1),
+ false);
+ if (!lineinfo) {
+ return chunks;
+ }
+
+ for (size_t i = 0; i < lineinfo->virt_text.size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &lineinfo->virt_text.items[i];
+ ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
+ if (vtc->hl_id > 0) {
+ ADD(chunk, STRING_OBJ(cstr_to_string(
+ (const char *)syn_id2name(vtc->hl_id))));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+
+ return chunks;
+}
+
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
{
Dictionary rv = ARRAY_DICT_INIT;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 2056cb07e3..b8d62e42a1 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -10,6 +10,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/handle.h"
+#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/lua/executor.h"
#include "nvim/ascii.h"
@@ -23,6 +24,7 @@
#include "nvim/eval/typval.h"
#include "nvim/map_defs.h"
#include "nvim/map.h"
+#include "nvim/mark_extended.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
@@ -1505,3 +1507,127 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
return mappings;
}
+
+// Returns an extmark given an id or a positional index
+// If throw == true then an error will be raised if nothing
+// was found
+// Returns NULL if something went wrong
+Extmark *extmark_from_id_or_pos(Buffer buffer, Integer ns, Object id,
+ Error *err, bool throw)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return NULL;
+ }
+
+ Extmark *extmark = NULL;
+ if (id.type == kObjectTypeArray) {
+ if (id.data.array.size != 2) {
+ api_set_error(err, kErrorTypeValidation,
+ _("Position must have 2 elements"));
+ return NULL;
+ }
+ linenr_T row = (linenr_T)id.data.array.items[0].data.integer;
+ colnr_T col = (colnr_T)id.data.array.items[1].data.integer;
+ if (row < 1 || col < 1) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation, _("Row and column MUST be > 0"));
+ }
+ return NULL;
+ }
+ extmark = extmark_from_pos(buf, (uint64_t)ns, row, col);
+ } else if (id.type != kObjectTypeInteger) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation,
+ _("Mark id must be an int or [row, col]"));
+ }
+ return NULL;
+ } else if (id.data.integer < 0) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation, _("Mark id must be positive"));
+ }
+ return NULL;
+ } else {
+ extmark = extmark_from_id(buf,
+ (uint64_t)ns,
+ (uint64_t)id.data.integer);
+ }
+
+ if (!extmark) {
+ if (throw) {
+ api_set_error(err, kErrorTypeValidation, _("Mark doesn't exist"));
+ }
+ return NULL;
+ }
+ return extmark;
+}
+
+// Is the Namespace in use?
+bool ns_initialized(uint64_t ns)
+{
+ if (ns < 1) {
+ return false;
+ }
+ return ns < (uint64_t)next_namespace_id;
+}
+
+/// Gets the line and column of an extmark.
+///
+/// Extmarks may be queried by position, name or even special names
+/// in the future such as "cursor".
+///
+/// @param[out] lnum extmark line
+/// @param[out] colnr extmark column
+///
+/// @return true if the extmark was found, else false
+bool extmark_get_index_from_obj(buf_T *buf, Integer ns, Object obj, linenr_T
+ *lnum, colnr_T *colnr, Error *err)
+{
+ // Check if it is mark id
+ if (obj.type == kObjectTypeInteger) {
+ Integer id = obj.data.integer;
+ if (id == 0) {
+ *lnum = 1;
+ *colnr = 1;
+ return true;
+ } else if (id == -1) {
+ *lnum = MAXLNUM;
+ *colnr = MAXCOL;
+ return true;
+ } else if (id < 0) {
+ api_set_error(err, kErrorTypeValidation, _("Mark id must be positive"));
+ return false;
+ }
+
+ Extmark *extmark = extmark_from_id(buf, (uint64_t)ns, (uint64_t)id);
+ if (extmark) {
+ *lnum = extmark->line->lnum;
+ *colnr = extmark->col;
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation, _("No mark with requested id"));
+ return false;
+ }
+
+ // Check if it is a position
+ } else if (obj.type == kObjectTypeArray) {
+ Array pos = obj.data.array;
+ if (pos.size != 2
+ || pos.items[0].type != kObjectTypeInteger
+ || pos.items[1].type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ _("Position must have 2 integer elements"));
+ return false;
+ }
+ Integer line = pos.items[0].data.integer;
+ Integer col = pos.items[1].data.integer;
+ *lnum = (linenr_T)(line >= 0 ? line + 1 : MAXLNUM);
+ *colnr = (colnr_T)(col >= 0 ? col + 1 : MAXCOL);
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ _("Position must be a mark id Integer or position Array"));
+ return false;
+ }
+}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 0e64658f36..19601b6539 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -25,6 +25,7 @@
#include "nvim/highlight.h"
#include "nvim/window.h"
#include "nvim/types.h"
+#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/screen.h"
#include "nvim/memline.h"
@@ -39,6 +40,7 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
+#include "nvim/mark_extended.h"
#include "nvim/syntax.h"
#include "nvim/getchar.h"
#include "nvim/os/input.h"
@@ -71,10 +73,70 @@ void api_vim_free_all_mem(void)
map_free(String, handle_T)(namespace_ids);
}
+/// Executes Vimscript (multiline block of Ex-commands), like anonymous
+/// |:source|.
+///
+/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
+/// etc.
+///
+/// On execution error: fails with VimL error, does not update v:errmsg.
+///
+/// @see |execute()|
+/// @see |nvim_command()|
+///
+/// @param src Vimscript code
+/// @param output Capture and return all (non-error, non-shell |:!|) output
+/// @param[out] err Error details (Vim error), if any
+/// @return Output (non-error, non-shell |:!|) if `output` is true,
+/// else empty string.
+String nvim_exec(String src, Boolean output, Error *err)
+ FUNC_API_SINCE(7)
+{
+ const int save_msg_silent = msg_silent;
+ garray_T *const save_capture_ga = capture_ga;
+ garray_T capture_local;
+ if (output) {
+ ga_init(&capture_local, 1, 80);
+ capture_ga = &capture_local;
+ }
+
+ try_start();
+ msg_silent++;
+ do_source_str(src.data, "nvim_exec()");
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ try_end(err);
+
+ if (ERROR_SET(err)) {
+ goto theend;
+ }
+
+ if (output && capture_local.ga_len > 1) {
+ String s = (String){
+ .data = capture_local.ga_data,
+ .size = (size_t)capture_local.ga_len,
+ };
+ // redir usually (except :echon) prepends a newline.
+ if (s.data[0] == '\n') {
+ memmove(s.data, s.data + 1, s.size - 1);
+ s.data[s.size - 1] = '\0';
+ s.size = s.size - 1;
+ }
+ return s; // Caller will free the memory.
+ }
+theend:
+ if (output) {
+ ga_clear(&capture_local);
+ }
+ return (String)STRING_INIT;
+}
+
/// Executes an ex-command.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
///
+/// @see |nvim_exec()|
+///
/// @param command Ex-command string
/// @param[out] err Error details (Vim error), if any
void nvim_command(String command, Error *err)
@@ -331,53 +393,16 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,
return cstr_as_string(ptr);
}
-/// Executes an ex-command and returns its (non-error) output.
-/// Shell |:!| output is not captured.
-///
-/// On execution error: fails with VimL error, does not update v:errmsg.
-///
-/// @param command Ex-command string
-/// @param[out] err Error details (Vim error), if any
+/// @deprecated
+/// @see nvim_exec
String nvim_command_output(String command, Error *err)
FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(7)
{
- const int save_msg_silent = msg_silent;
- garray_T *const save_capture_ga = capture_ga;
- garray_T capture_local;
- ga_init(&capture_local, 1, 80);
-
- try_start();
- msg_silent++;
- capture_ga = &capture_local;
- do_cmdline_cmd(command.data);
- capture_ga = save_capture_ga;
- msg_silent = save_msg_silent;
- try_end(err);
-
- if (ERROR_SET(err)) {
- goto theend;
- }
-
- if (capture_local.ga_len > 1) {
- String s = (String){
- .data = capture_local.ga_data,
- .size = (size_t)capture_local.ga_len,
- };
- // redir usually (except :echon) prepends a newline.
- if (s.data[0] == '\n') {
- memmove(s.data, s.data + 1, s.size - 1);
- s.data[s.size - 1] = '\0';
- s.size = s.size - 1;
- }
- return s; // Caller will free the memory.
- }
-
-theend:
- ga_clear(&capture_local);
- return (String)STRING_INIT;
+ return nvim_exec(command, true, err);
}
-/// Evaluates a VimL expression (:help expression).
+/// Evaluates a VimL |expression|.
/// Dictionaries and Lists are recursively expanded.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
@@ -422,6 +447,15 @@ Object nvim_eval(String expr, Error *err)
return rv;
}
+/// @deprecated Use nvim_exec_lua() instead.
+Object nvim_execute_lua(String code, Array args, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(7)
+ FUNC_API_REMOTE_ONLY
+{
+ return executor_exec_lua_api(code, args, err);
+}
+
/// Execute Lua code. Parameters (if any) are available as `...` inside the
/// chunk. The chunk can return a value.
///
@@ -434,8 +468,9 @@ Object nvim_eval(String expr, Error *err)
/// or executing the Lua code.
///
/// @return Return value of Lua code if present or NIL.
-Object nvim_execute_lua(String code, Array args, Error *err)
- FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY
+Object nvim_exec_lua(String code, Array args, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_REMOTE_ONLY
{
return executor_exec_lua_api(code, args, err);
}
@@ -1073,9 +1108,10 @@ fail:
/// float where the text should not be edited. Disables
/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn',
/// 'foldcolumn', 'spell' and 'list' options. 'signcolumn'
-/// is changed to `auto`. The end-of-buffer region is hidden
-/// by setting `eob` flag of 'fillchars' to a space char,
-/// and clearing the |EndOfBuffer| region in 'winhighlight'.
+/// is changed to `auto` and 'colorcolumn' is cleared. The
+/// end-of-buffer region is hidden by setting `eob` flag of
+/// 'fillchars' to a space char, and clearing the
+/// |EndOfBuffer| region in 'winhighlight'.
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
@@ -1094,6 +1130,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config,
if (enter) {
win_enter(wp, false);
}
+ if (!win_valid(wp)) {
+ api_set_error(err, kErrorTypeException, "Window was closed immediately");
+ return 0;
+ }
if (buffer > 0) {
nvim_win_set_buf(wp->handle, buffer, err);
}
@@ -1245,13 +1285,13 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
Array lines = string_to_array(data, crlf);
ADD(args, ARRAY_OBJ(lines));
ADD(args, INTEGER_OBJ(phase));
- rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
- err);
+ rv = nvim_exec_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
+ err);
if (ERROR_SET(err)) {
draining = true;
goto theend;
}
- if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) {
+ if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 1)) {
ResetRedobuff();
AppendCharToRedobuff('a'); // Dot-repeat.
}
@@ -1269,7 +1309,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
}
}
}
- if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) {
+ if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 3)) {
AppendCharToRedobuff(ESC); // Dot-repeat.
}
theend:
@@ -1289,7 +1329,7 @@ theend:
/// @param lines |readfile()|-style list of lines. |channel-lines|
/// @param type Edit behavior: any |getregtype()| result, or:
/// - "b" |blockwise-visual| mode (may include width, e.g. "b3")
-/// - "c" |characterwise| mode
+/// - "c" |charwise| mode
/// - "l" |linewise| mode
/// - "" guess by contents, see |setreg()|
/// @param after Insert after cursor (like |p|), or before (like |P|).
@@ -2380,7 +2420,7 @@ Array nvim_get_proc_children(Integer pid, Error *err)
Array a = ARRAY_DICT_INIT;
ADD(a, INTEGER_OBJ(pid));
String s = cstr_to_string("return vim._os_proc_children(select(1, ...))");
- Object o = nvim_execute_lua(s, a, err);
+ Object o = nvim_exec_lua(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray) {
@@ -2426,7 +2466,7 @@ Object nvim_get_proc(Integer pid, Error *err)
Array a = ARRAY_DICT_INIT;
ADD(a, INTEGER_OBJ(pid));
String s = cstr_to_string("return vim._os_proc_info(select(1, ...))");
- Object o = nvim_execute_lua(s, a, err);
+ Object o = nvim_exec_lua(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray && o.data.array.size == 0) {
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 1d5aa8ba9b..33ffff39f6 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -53,6 +53,7 @@
#include "nvim/indent_c.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -410,11 +411,11 @@ bool buf_valid(buf_T *buf)
/// caller should get a new buffer very soon!
/// The 'bufhidden' option can force freeing and deleting.
/// @param abort_if_last
-/// If TRUE, do not close the buffer if autocommands cause
+/// If true, do not close the buffer if autocommands cause
/// there to be only one window with this buffer. e.g. when
/// ":quit" is supposed to close the window but autocommands
/// close all other windows.
-void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
+void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
{
bool unload_buf = (action != 0);
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
@@ -816,6 +817,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
}
uc_clear(&buf->b_ucmds); // clear local user commands
buf_delete_signs(buf, (char_u *)"*"); // delete any signs
+ extmark_free_all(buf); // delete any extmarks
bufhl_clear_all(buf); // delete any highligts
map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
@@ -1236,7 +1238,7 @@ do_buffer(
return FAIL;
}
} else {
- EMSG2(_("E89: %s will be killed(add ! to override)"),
+ EMSG2(_("E89: %s will be killed (add ! to override)"),
(char *)buf->b_fname);
return FAIL;
}
@@ -1583,10 +1585,12 @@ void enter_buffer(buf_T *buf)
open_buffer(false, NULL, 0);
} else {
- if (!msg_silent) {
+ if (!msg_silent && !shortmess(SHM_FILEINFO)) {
need_fileinfo = true; // display file info after redraw
}
- (void)buf_check_timestamp(curbuf, false); // check if file changed
+ // check if file changed
+ (void)buf_check_timestamp(curbuf, false);
+
curwin->w_topline = 1;
curwin->w_topfill = 0;
apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
@@ -2690,7 +2694,7 @@ setfname(
buf_T *buf,
char_u *ffname,
char_u *sfname,
- int message // give message when buffer already exists
+ bool message // give message when buffer already exists
)
{
buf_T *obuf = NULL;
@@ -3797,14 +3801,20 @@ int build_stl_str_hl(
buf_T *const save_curbuf = curbuf;
win_T *const save_curwin = curwin;
+ const int save_VIsual_active = VIsual_active;
curwin = wp;
curbuf = wp->w_buffer;
+ // Visual mode is only valid in the current window.
+ if (curwin != save_curwin) {
+ VIsual_active = false;
+ }
// Note: The result stored in `t` is unused.
str = eval_to_string_safe(out_p, &t, use_sandbox);
curwin = save_curwin;
curbuf = save_curbuf;
+ VIsual_active = save_VIsual_active;
// Remove the variable we just stored
do_unlet(S_LEN("g:actual_curbuf"), true);
@@ -5496,6 +5506,7 @@ void bufhl_clear_line_range(buf_T *buf,
linenr_T line_start,
linenr_T line_end)
{
+ // TODO(bfredl): implement kb_itr_interval to jump directly to the first line
kbitr_t(bufhl) itr;
BufhlLine *l, t = BUFHLLINE_INIT(line_start);
if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) {
@@ -5623,6 +5634,86 @@ void bufhl_mark_adjust(buf_T* buf,
}
}
+/// Adjust a placed highlight for column changes and joined/broken lines
+bool bufhl_mark_col_adjust(buf_T *buf,
+ linenr_T lnum,
+ colnr_T mincol,
+ long lnum_amount,
+ long col_amount)
+{
+ bool moved = false;
+ BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false);
+ if (!lineinfo) {
+ // Old line empty, nothing to do
+ return false;
+ }
+ // Create the new line below only if needed
+ BufhlLine *lineinfo2 = NULL;
+
+ colnr_T delcol = MAXCOL;
+ if (lnum_amount == 0 && col_amount < 0) {
+ delcol = mincol+(int)col_amount;
+ }
+
+ size_t newidx = 0;
+ for (size_t i = 0; i < kv_size(lineinfo->items); i++) {
+ BufhlItem *item = &kv_A(lineinfo->items, i);
+ bool delete = false;
+ if (item->start >= mincol) {
+ moved = true;
+ item->start += (int)col_amount;
+ if (item->stop < MAXCOL) {
+ item->stop += (int)col_amount;
+ }
+ if (lnum_amount != 0) {
+ if (lineinfo2 == NULL) {
+ lineinfo2 = bufhl_tree_ref(&buf->b_bufhl_info,
+ lnum+lnum_amount, true);
+ }
+ kv_push(lineinfo2->items, *item);
+ delete = true;
+ }
+ } else {
+ if (item->start >= delcol) {
+ moved = true;
+ item->start = delcol;
+ }
+ if (item->stop == MAXCOL || item->stop+1 >= mincol) {
+ if (item->stop == MAXCOL) {
+ if (delcol < MAXCOL
+ && delcol > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
+ delete = true;
+ }
+ } else {
+ moved = true;
+ item->stop += (int)col_amount;
+ }
+ assert(lnum_amount >= 0);
+ if (lnum_amount > 0) {
+ item->stop = MAXCOL;
+ }
+ } else if (item->stop+1 >= delcol) {
+ moved = true;
+ item->stop = delcol-1;
+ }
+ // we covered the entire range with a visual delete or something
+ if (item->stop < item->start) {
+ delete = true;
+ }
+ }
+
+ if (!delete) {
+ if (i != newidx) {
+ kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i);
+ }
+ newidx++;
+ }
+ }
+ kv_size(lineinfo->items) = newidx;
+
+ return moved;
+}
+
/// Get highlights to display at a specific line
///
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index ca740dea21..700d8b82e6 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -115,6 +115,9 @@ typedef uint16_t disptick_T; // display tick type
#include "nvim/os/fs_defs.h" // for FileID
#include "nvim/terminal.h" // for Terminal
+#include "nvim/lib/kbtree.h"
+#include "nvim/mark_extended.h"
+
/*
* The taggy struct is used to store the information about a :tag command.
*/
@@ -805,6 +808,10 @@ struct file_buffer {
kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights
+ PMap(uint64_t) *b_extmark_ns; // extmark namespaces
+ kbtree_t(extmarklines) b_extlines; // extmarks
+ kvec_t(ExtmarkLine *) b_extmark_move_space; // temp space for extmarks
+
// array of channel_id:s which have asked to receive updates for this
// buffer.
kvec_t(uint64_t) update_channels;
@@ -911,19 +918,19 @@ typedef struct w_line {
* or row (FR_ROW) layout or is a leaf, which has a window.
*/
struct frame_S {
- char fr_layout; /* FR_LEAF, FR_COL or FR_ROW */
+ char fr_layout; // FR_LEAF, FR_COL or FR_ROW
int fr_width;
- int fr_newwidth; /* new width used in win_equal_rec() */
+ int fr_newwidth; // new width used in win_equal_rec()
int fr_height;
- int fr_newheight; /* new height used in win_equal_rec() */
- frame_T *fr_parent; /* containing frame or NULL */
- frame_T *fr_next; /* frame right or below in same parent, NULL
- for first */
- frame_T *fr_prev; /* frame left or above in same parent, NULL
- for last */
- /* fr_child and fr_win are mutually exclusive */
- frame_T *fr_child; /* first contained frame */
- win_T *fr_win; /* window that fills this frame */
+ int fr_newheight; // new height used in win_equal_rec()
+ frame_T *fr_parent; // containing frame or NULL
+ frame_T *fr_next; // frame right or below in same parent, NULL
+ // for last
+ frame_T *fr_prev; // frame left or above in same parent, NULL
+ // for first
+ // fr_child and fr_win are mutually exclusive
+ frame_T *fr_child; // first contained frame
+ win_T *fr_win; // window that fills this frame
};
#define FR_LEAF 0 /* frame is a leaf */
diff --git a/src/nvim/change.c b/src/nvim/change.c
index ba80e71ae6..8a782c2b20 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -17,6 +17,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/memline.h"
#include "nvim/misc1.h"
#include "nvim/move.h"
@@ -358,6 +359,24 @@ void changed_bytes(linenr_T lnum, colnr_T col)
}
}
+/// insert/delete bytes at column
+///
+/// Like changed_bytes() but also adjust extmark for "added" bytes.
+/// When "added" is negative text was deleted.
+static void inserted_bytes(linenr_T lnum, colnr_T col, int added)
+{
+ if (added > 0) {
+ extmark_col_adjust(curbuf, lnum, col+1, 0, added, kExtmarkUndo);
+ } else if (added < 0) {
+ // TODO(bfredl): next revision of extmarks should handle both these
+ // with the same entry point. Also with more sane params..
+ extmark_col_adjust_delete(curbuf, lnum, col+2,
+ col+(-added)+1, kExtmarkUndo, 0);
+ }
+
+ changed_bytes(lnum, col);
+}
+
/// Appended "count" lines below line "lnum" in the current buffer.
/// Must be called AFTER the change and after mark_adjust().
/// Takes care of marking the buffer to be redrawn and sets the changed flag.
@@ -372,7 +391,7 @@ void appended_lines_mark(linenr_T lnum, long count)
// Skip mark_adjust when adding a line after the last one, there can't
// be marks there. But it's still needed in diff mode.
if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false, kExtmarkUndo);
}
changed_lines(lnum + 1, 0, lnum + 1, count, true);
}
@@ -390,7 +409,8 @@ void deleted_lines(linenr_T lnum, long count)
/// be triggered to display the cursor.
void deleted_lines_mark(linenr_T lnum, long count)
{
- mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false);
+ mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false,
+ kExtmarkUndo);
changed_lines(lnum, 0, lnum + count, -count, true);
}
@@ -628,7 +648,7 @@ void ins_char_bytes(char_u *buf, size_t charlen)
ml_replace(lnum, newp, false);
// mark the buffer as changed and prepare for displaying
- changed_bytes(lnum, (colnr_T)col);
+ inserted_bytes(lnum, (colnr_T)col, (int)(newlen - oldlen));
// If we're in Insert or Replace mode and 'showmatch' is set, then briefly
// show the match for right parens and braces.
@@ -674,7 +694,7 @@ void ins_str(char_u *s)
assert(bytes >= 0);
memmove(newp + col + newlen, oldp + col, (size_t)bytes);
ml_replace(lnum, newp, false);
- changed_bytes(lnum, col);
+ inserted_bytes(lnum, col, newlen);
curwin->w_cursor.col += newlen;
}
@@ -795,7 +815,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
}
// mark the buffer as changed and prepare for displaying
- changed_bytes(lnum, curwin->w_cursor.col);
+ inserted_bytes(lnum, col, -count);
return OK;
}
@@ -951,6 +971,9 @@ int open_line(
bool did_append; // appended a new line
int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting
+ linenr_T lnum = curwin->w_cursor.lnum;
+ colnr_T mincol = curwin->w_cursor.col + 1;
+
// make a copy of the current line so we can mess with it
char_u *saved_line = vim_strsave(get_cursor_line_ptr());
@@ -1574,7 +1597,8 @@ int open_line(
// be marks there. But still needed in diff mode.
if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
|| curwin->w_p_diff) {
- mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
+ mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false,
+ kExtmarkUndo);
}
did_append = true;
} else {
@@ -1663,8 +1687,12 @@ int open_line(
if (flags & OPENLINE_MARKFIX) {
mark_col_adjust(curwin->w_cursor.lnum,
curwin->w_cursor.col + less_cols_off,
- 1L, (long)-less_cols, 0);
+ 1L, (long)-less_cols, 0, kExtmarkNOOP);
}
+ // Always move extmarks - Here we move only the line where the
+ // cursor is, the previous mark_adjust takes care of the lines after
+ extmark_col_adjust(curbuf, lnum, mincol, 1L, (long)-less_cols,
+ kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index f9102fa0e2..2bb568f025 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -272,11 +272,35 @@ static void close_cb(Stream *stream, void *data)
channel_decref(data);
}
+
+/// Starts a job and returns the associated channel
+///
+/// @param[in] argv Arguments vector specifying the command to run,
+/// NULL-terminated
+/// @param[in] on_stdout Callback to read the job's stdout
+/// @param[in] on_stderr Callback to read the job's stderr
+/// @param[in] on_exit Callback to receive the job's exit status
+/// @param[in] pty True if the job should run attached to a pty
+/// @param[in] rpc True to communicate with the job using msgpack-rpc,
+/// `on_stdout` is ignored
+/// @param[in] detach True if the job should not be killed when nvim exits,
+/// ignored if `pty` is true
+/// @param[in] cwd Initial working directory for the job. Nvim's working
+/// directory if `cwd` is NULL
+/// @param[in] pty_width Width of the pty, ignored if `pty` is false
+/// @param[in] pty_height Height of the pty, ignored if `pty` is false
+/// @param[in] term_name `$TERM` for the pty
+/// @param[in] env Nvim's configured environment is used if this is NULL,
+/// otherwise defines all environment variables
+/// @param[out] status_out 0 for invalid arguments, > 0 for the channel id,
+/// < 0 if the job can't start
+///
+/// @returns [allocated] channel
Channel *channel_job_start(char **argv, CallbackReader on_stdout,
CallbackReader on_stderr, Callback on_exit,
bool pty, bool rpc, bool detach, const char *cwd,
uint16_t pty_width, uint16_t pty_height,
- char *term_name, varnumber_T *status_out)
+ char *term_name, char **env, varnumber_T *status_out)
{
assert(cwd == NULL || os_isdir_executable(cwd));
@@ -314,6 +338,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
proc->events = chan->events;
proc->detach = detach;
proc->cwd = cwd;
+ proc->env = env;
char *cmd = xstrdup(proc->argv[0]);
bool has_out, has_err;
@@ -328,6 +353,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
if (status) {
EMSG3(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd);
+ os_free_fullenv(proc->env);
if (proc->type == kProcessTypePty) {
xfree(chan->stream.pty.term_name);
}
@@ -336,6 +362,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
return NULL;
}
xfree(cmd);
+ os_free_fullenv(proc->env);
+
wstream_init(&proc->in, 0);
if (has_out) {
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 2f872ff2bf..1ae0510762 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -254,7 +254,7 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
size_t cmd_len = sizeof("func! ") + STRLEN(name);
char *cmd = xmalloc(cmd_len);
snprintf(cmd, cmd_len, "func! %s", name);
- String func_body = nvim_command_output(cstr_as_string(cmd), &err);
+ String func_body = nvim_exec(cstr_as_string(cmd), true, &err);
xfree(cmd);
if (!ERROR_SET(&err)) {
ADD(ctx->funcs, STRING_OBJ(func_body));
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 31552929dc..dccde01d29 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -44,7 +44,7 @@
#include "nvim/os/shell.h"
static int diff_busy = false; // using diff structs, don't change them
-static int diff_need_update = false; // ex_diffupdate needs to be called
+static bool diff_need_update = false; // ex_diffupdate needs to be called
// Flags obtained from the 'diffopt' option
#define DIFF_FILLER 0x001 // display filler lines
@@ -57,8 +57,9 @@ static int diff_need_update = false; // ex_diffupdate needs to be called
#define DIFF_VERTICAL 0x080 // vertical splits
#define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden
#define DIFF_INTERNAL 0x200 // use internal xdiff algorithm
+#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
-static int diff_flags = DIFF_INTERNAL | DIFF_FILLER;
+static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static long diff_algorithm = 0;
@@ -490,7 +491,8 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1,
}
if (tp == curtab) {
- diff_redraw(true);
+ // Don't redraw right away, this updates the diffs, which can be slow.
+ need_diff_redraw = true;
// Need to recompute the scroll binding, may remove or add filler
// lines (e.g., when adding lines above w_topline). But it's slow when
@@ -634,8 +636,9 @@ static int diff_check_sanity(tabpage_T *tp, diff_T *dp)
/// Mark all diff buffers in the current tab page for redraw.
///
/// @param dofold Also recompute the folds
-static void diff_redraw(int dofold)
+void diff_redraw(bool dofold)
{
+ need_diff_redraw = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (!wp->w_p_diff) {
continue;
@@ -1472,6 +1475,13 @@ void ex_diffoff(exarg_T *eap)
diff_buf_clear();
}
+ if (!diffwin) {
+ diff_need_update = false;
+ curtab->tp_diff_invalid = false;
+ curtab->tp_diff_update = false;
+ diff_clear(curtab);
+ }
+
// Remove "hor" from from 'scrollopt' if there are no diff windows left.
if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) {
do_cmdline_cmd("set sbo-=hor");
@@ -1712,6 +1722,7 @@ static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig,
///
/// @param tp
void diff_clear(tabpage_T *tp)
+ FUNC_ATTR_NONNULL_ALL
{
diff_T *p;
diff_T *next_p;
@@ -2141,6 +2152,9 @@ int diffopt_changed(void)
} else if (STRNCMP(p, "hiddenoff", 9) == 0) {
p += 9;
diff_flags_new |= DIFF_HIDDEN_OFF;
+ } else if (STRNCMP(p, "closeoff", 8) == 0) {
+ p += 8;
+ diff_flags_new |= DIFF_CLOSE_OFF;
} else if (STRNCMP(p, "indent-heuristic", 16) == 0) {
p += 16;
diff_indent_heuristic = XDF_INDENT_HEURISTIC;
@@ -2216,6 +2230,13 @@ bool diffopt_hiddenoff(void)
return (diff_flags & DIFF_HIDDEN_OFF) != 0;
}
+// Return true if 'diffopt' contains "closeoff".
+bool diffopt_closeoff(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return (diff_flags & DIFF_CLOSE_OFF) != 0;
+}
+
/// Find the difference within a changed line.
///
/// @param wp window whose current buffer to check
@@ -2690,7 +2711,8 @@ void ex_diffgetput(exarg_T *eap)
// Adjust marks. This will change the following entries!
if (added != 0) {
- mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false);
+ mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false,
+ kExtmarkUndo);
if (curwin->w_cursor.lnum >= lnum) {
// Adjust the cursor position if it's in/after the changed
// lines.
diff --git a/src/nvim/diff.h b/src/nvim/diff.h
index 3624ce29bb..99a60381bd 100644
--- a/src/nvim/diff.h
+++ b/src/nvim/diff.h
@@ -4,6 +4,13 @@
#include "nvim/pos.h"
#include "nvim/ex_cmds_defs.h"
+// Value set from 'diffopt'.
+EXTERN int diff_context INIT(= 6); // context for folds
+EXTERN int diff_foldcolumn INIT(= 2); // 'foldcolumn' for diff mode
+EXTERN bool diff_need_scrollbind INIT(= false);
+
+EXTERN bool need_diff_redraw INIT(= false); // need to call diff_redraw()
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "diff.h.generated.h"
#endif
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 93f13c8d3f..eecea03a19 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -28,6 +28,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -1837,6 +1838,13 @@ change_indent (
xfree(new_line);
}
+
+ // change_indent seems to bec called twice, this combination only triggers
+ // once for both calls
+ if (new_cursor_col - vcol != 0) {
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum, 0, 0, amount,
+ kExtmarkUndo);
+ }
}
/*
@@ -3048,7 +3056,9 @@ static void ins_compl_clear(void)
XFREE_CLEAR(compl_orig_text);
compl_enter_selects = false;
// clear v:completed_item
- set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc());
+ dict_T *const d = tv_dict_alloc();
+ d->dv_lock = VAR_FIXED;
+ set_vim_var_dict(VV_COMPLETED_ITEM, d);
}
/// Check that Insert completion is active.
@@ -4305,7 +4315,9 @@ static void ins_compl_delete(void)
// causes flicker, thus we can't do that.
changed_cline_bef_curs();
// clear v:completed_item
- set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc());
+ dict_T *const d = tv_dict_alloc();
+ d->dv_lock = VAR_FIXED;
+ set_vim_var_dict(VV_COMPLETED_ITEM, d);
}
// Insert the new text being completed.
@@ -4327,6 +4339,7 @@ static dict_T *ins_compl_dict_alloc(compl_T *match)
{
// { word, abbr, menu, kind, info }
dict_T *dict = tv_dict_alloc();
+ dict->dv_lock = VAR_FIXED;
tv_dict_add_str(
dict, S_LEN("word"),
(const char *)EMPTY_IF_NULL(match->cp_str));
@@ -5606,10 +5619,11 @@ insertchar (
AppendCharToRedobuff(c);
} else {
ins_char(c);
- if (flags & INSCHAR_CTRLV)
+ if (flags & INSCHAR_CTRLV) {
redo_literal(c);
- else
+ } else {
AppendCharToRedobuff(c);
+ }
}
}
}
@@ -6891,8 +6905,9 @@ static void mb_replace_pop_ins(int cc)
for (i = 1; i < n; ++i)
buf[i] = replace_pop();
ins_bytes_len(buf, n);
- } else
+ } else {
ins_char(cc);
+ }
if (enc_utf8)
/* Handle composing chars. */
@@ -8002,9 +8017,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
Insstart_orig.col = curwin->w_cursor.col;
}
- if (State & VREPLACE_FLAG)
+ if (State & VREPLACE_FLAG) {
ins_char(' ');
- else {
+ } else {
ins_str((char_u *)" ");
if ((State & REPLACE_FLAG))
replace_push(NUL);
@@ -8482,6 +8497,7 @@ static bool ins_tab(void)
} else { // otherwise use "tabstop"
temp = (int)curbuf->b_p_ts;
}
+
temp -= get_nolist_virtcol() % temp;
/*
@@ -8491,12 +8507,13 @@ static bool ins_tab(void)
*/
ins_char(' ');
while (--temp > 0) {
- if (State & VREPLACE_FLAG)
+ if (State & VREPLACE_FLAG) {
ins_char(' ');
- else {
+ } else {
ins_str((char_u *)" ");
- if (State & REPLACE_FLAG) /* no char replaced */
+ if (State & REPLACE_FLAG) { // no char replaced
replace_push(NUL);
+ }
}
}
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index fd83bc846b..04899f2c99 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -422,6 +422,7 @@ static struct vimvar {
VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
+ VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
};
#undef VV
@@ -433,11 +434,14 @@ static struct vimvar {
#define vv_str vv_di.di_tv.vval.v_string
#define vv_list vv_di.di_tv.vval.v_list
#define vv_dict vv_di.di_tv.vval.v_dict
+#define vv_partial vv_di.di_tv.vval.v_partial
#define vv_tv vv_di.di_tv
/// Variable used for v:
static ScopeDictDictItem vimvars_var;
+static partial_T *vvlua_partial;
+
/// v: hashtab
#define vimvarht vimvardict.dv_hashtab
@@ -639,6 +643,13 @@ void eval_init(void)
set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
+ vimvars[VV_LUA].vv_type = VAR_PARTIAL;
+ vvlua_partial = xcalloc(1, sizeof(partial_T));
+ vimvars[VV_LUA].vv_partial = vvlua_partial;
+ // this value shouldn't be printed, but if it is, do not crash
+ vvlua_partial->pt_name = xmallocz(0);
+ vvlua_partial->pt_refcount++;
+
set_reg_var(0); // default for v:register is not 0 but '"'
}
@@ -1313,12 +1324,25 @@ int call_vim_function(
{
int doesrange;
int ret;
+ int len = (int)STRLEN(func);
+ partial_T *pt = NULL;
+
+ if (len >= 6 && !memcmp(func, "v:lua.", 6)) {
+ func += 6;
+ len = check_luafunc_name((const char *)func, false);
+ if (len == 0) {
+ ret = FAIL;
+ goto fail;
+ }
+ pt = vvlua_partial;
+ }
rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this.
- ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL,
+ ret = call_func(func, len, rettv, argc, argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &doesrange, true, NULL, NULL);
+ &doesrange, true, pt, NULL);
+fail:
if (ret == FAIL) {
tv_clear(rettv);
}
@@ -2462,6 +2486,13 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
}
}
+ if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv)
+ && len == -1 && rettv == NULL) {
+ tv_clear(&var1);
+ EMSG2(e_illvar, "v:['lua']");
+ return NULL;
+ }
+
if (lp->ll_di == NULL) {
// Can't add "v:" or "a:" variable.
if (lp->ll_dict == &vimvardict
@@ -2841,7 +2872,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
int c;
char_u *p;
- if (cmdidx == CMD_let) {
+ if (cmdidx == CMD_let || cmdidx == CMD_const) {
xp->xp_context = EXPAND_USER_VARS;
if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) {
/* ":let var1 var2 ...": find last space. */
@@ -4699,7 +4730,7 @@ eval_index(
if (evaluate) {
n1 = 0;
- if (!empty1 && rettv->v_type != VAR_DICT) {
+ if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) {
n1 = tv_get_number(&var1);
tv_clear(&var1);
}
@@ -4823,7 +4854,7 @@ eval_index(
if (len == -1) {
tv_clear(&var1);
}
- if (item == NULL) {
+ if (item == NULL || tv_is_luafunc(&item->di_tv)) {
return FAIL;
}
@@ -6334,7 +6365,7 @@ static char_u *deref_func_name(const char *name, int *lenp,
*/
static int
get_func_tv(
- char_u *name, // name of the function
+ const char_u *name, // name of the function
int len, // length of "name"
typval_T *rettv,
char_u **arg, // argument, pointing to the '('
@@ -6590,7 +6621,15 @@ call_func(
rettv->vval.v_number = 0;
error = ERROR_UNKNOWN;
- if (!builtin_function((const char *)rfname, -1)) {
+ if (partial == vvlua_partial) {
+ if (len > 0) {
+ error = ERROR_NONE;
+ executor_call_lua((const char *)funcname, len,
+ argvars, argcount, rettv);
+ } else {
+ error = ERROR_UNKNOWN;
+ }
+ } else if (!builtin_function((const char *)rfname, -1)) {
// User defined function.
if (partial != NULL && partial->pt_func != NULL) {
fp = partial->pt_func;
@@ -6707,14 +6746,14 @@ call_func(
///
/// @param ermsg must be passed without translation (use N_() instead of _()).
/// @param name function name
-static void emsg_funcname(char *ermsg, char_u *name)
+static void emsg_funcname(char *ermsg, const char_u *name)
{
char_u *p;
if (*name == K_SPECIAL) {
p = concat_str((char_u *)"<SNR>", name + 3);
} else {
- p = name;
+ p = (char_u *)name;
}
EMSG2(_(ermsg), p);
@@ -8677,18 +8716,25 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
tv_dict_alloc_ret(rettv);
- for (int i = 0; ; i++) {
- // TODO(justinmk): use os_copyfullenv from #7202 ?
- char *envname = os_getenvname_at_index((size_t)i);
- if (envname == NULL) {
- break;
- }
- const char *value = os_getenv(envname);
+ size_t env_size = os_get_fullenv_size();
+ char **env = xmalloc(sizeof(*env) * (env_size + 1));
+ env[env_size] = NULL;
+
+ os_copy_fullenv(env, env_size);
+
+ for (size_t i = 0; i < env_size; i++) {
+ const char * str = env[i];
+ const char * const end = strchr(str + (str[0] == '=' ? 1 : 0),
+ '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - str;
+ assert(len > 0);
+ const char * value = str + len + 1;
tv_dict_add_str(rettv->vval.v_dict,
- (char *)envname, STRLEN((char *)envname),
- value == NULL ? "" : value);
- xfree(envname);
+ str, len,
+ value);
}
+ os_free_fullenv(env);
}
/*
@@ -8711,7 +8757,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (p == NULL) {
rettv->v_type = VAR_SPECIAL;
- rettv->vval.v_number = kSpecialVarNull;
+ rettv->vval.v_special = kSpecialVarNull;
return;
}
rettv->vval.v_string = p;
@@ -9876,6 +9922,7 @@ static dict_T *get_buffer_info(buf_T *buf)
buf->b_ffname != NULL ? (const char *)buf->b_ffname : "");
tv_dict_add_nr(dict, S_LEN("lnum"),
buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf));
+ tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count);
tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL);
tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl);
tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf));
@@ -11464,6 +11511,9 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
static const char *const has_list[] = {
+#if defined(BSD) && !defined(__APPLE__)
+ "bsd",
+#endif
#ifdef UNIX
"unix",
#endif
@@ -12535,6 +12585,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool executable = true;
char **argv = tv_to_argv(&argvars[0], NULL, &executable);
+ char **env = NULL;
if (!argv) {
rettv->vval.v_number = executable ? 0 : -1;
return; // Did error message in tv_to_argv.
@@ -12552,6 +12603,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool detach = false;
bool rpc = false;
bool pty = false;
+ bool clear_env = false;
CallbackReader on_stdout = CALLBACK_READER_INIT,
on_stderr = CALLBACK_READER_INIT;
Callback on_exit = CALLBACK_NONE;
@@ -12562,6 +12614,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
detach = tv_dict_get_number(job_opts, "detach") != 0;
rpc = tv_dict_get_number(job_opts, "rpc") != 0;
pty = tv_dict_get_number(job_opts, "pty") != 0;
+ clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
if (pty && rpc) {
EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
shell_free_argv(argv);
@@ -12578,6 +12631,45 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
}
+ dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env"));
+ if (job_env) {
+ if (job_env->di_tv.v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "env");
+ shell_free_argv(argv);
+ return;
+ }
+
+ size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict);
+ size_t i = 0;
+ size_t env_size = 0;
+
+ if (clear_env) {
+ // + 1 for last null entry
+ env = xmalloc((custom_env_size + 1) * sizeof(*env));
+ env_size = 0;
+ } else {
+ env_size = os_get_fullenv_size();
+
+ env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env));
+
+ os_copy_fullenv(env, env_size);
+ i = env_size;
+ }
+ assert(env); // env must be allocated at this point
+
+ TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
+ const char *str = tv_get_string(&var->di_tv);
+ assert(str);
+ size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1;
+ env[i] = xmalloc(len);
+ snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
+ i++;
+ });
+
+ // must be null terminated
+ env[env_size + custom_env_size] = NULL;
+ }
+
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv);
@@ -12595,8 +12687,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
- rpc, detach, cwd, width, height, term_name,
- &rettv->vval.v_number);
+ rpc, detach, cwd, width, height,
+ term_name, env, &rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
}
@@ -12870,7 +12962,7 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
const char *libname = (char *) argvars[0].vval.v_string;
const char *funcname = (char *) argvars[1].vval.v_string;
- int in_type = argvars[2].v_type;
+ VarType in_type = argvars[2].v_type;
// input variables
char *str_in = (in_type == VAR_STRING)
@@ -12879,8 +12971,8 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
// output variables
char **str_out = (out_type == VAR_STRING)
- ? (char **) &rettv->vval.v_string : NULL;
- int64_t int_out = 0;
+ ? (char **)&rettv->vval.v_string : NULL;
+ int int_out = 0;
bool success = os_libcall(libname, funcname,
str_in, int_in,
@@ -12892,7 +12984,7 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
}
if (out_type == VAR_NUMBER) {
- rettv->vval.v_number = (int) int_out;
+ rettv->vval.v_number = (varnumber_T)int_out;
}
}
@@ -13881,6 +13973,13 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+// "pum_getpos()" function
+static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+ pum_set_event_info(rettv->vval.v_dict);
+}
+
/*
* "pumvisible()" function
*/
@@ -14891,7 +14990,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
CALLBACK_READER_INIT, CALLBACK_NONE,
- false, true, false, NULL, 0, 0, NULL,
+ false, true, false, NULL, 0, 0, NULL, NULL,
&rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
@@ -15669,7 +15768,7 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char *name = tv_get_string_buf(&argvars[0], namebuf);
if (argvars[1].v_type == VAR_SPECIAL
- && argvars[1].vval.v_number == kSpecialVarNull) {
+ && argvars[1].vval.v_special == kSpecialVarNull) {
os_unsetenv(name);
} else {
os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1);
@@ -18278,7 +18377,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
true, false, false, cwd,
term_width, curwin->w_height_inner,
- xstrdup("xterm-256color"),
+ xstrdup("xterm-256color"), NULL,
&rettv->vval.v_number);
if (rettv->vval.v_number <= 0) {
return;
@@ -20168,6 +20267,26 @@ static void check_vars(const char *name, size_t len)
}
}
+/// check if special v:lua value for calling lua functions
+static bool tv_is_luafunc(typval_T *tv)
+{
+ return tv->v_type == VAR_PARTIAL && tv->vval.v_partial == vvlua_partial;
+}
+
+/// check the function name after "v:lua."
+static int check_luafunc_name(const char *str, bool paren)
+{
+ const char *p = str;
+ while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') {
+ p++;
+ }
+ if (*p != (paren ? '(' : NUL)) {
+ return 0;
+ } else {
+ return (int)(p-str);
+ }
+}
+
/// Handle expr[expr], expr[expr:expr] subscript and .name lookup.
/// Also handle function call with Funcref variable: func(expr)
/// Can all be combined: dict.func(expr)[idx]['func'](expr)
@@ -20181,9 +20300,30 @@ handle_subscript(
{
int ret = OK;
dict_T *selfdict = NULL;
- char_u *s;
+ const char_u *s;
int len;
typval_T functv;
+ int slen = 0;
+ bool lua = false;
+
+ if (tv_is_luafunc(rettv)) {
+ if (**arg != '.') {
+ tv_clear(rettv);
+ ret = FAIL;
+ } else {
+ (*arg)++;
+
+ lua = true;
+ s = (char_u *)(*arg);
+ slen = check_luafunc_name(*arg, true);
+ if (slen == 0) {
+ tv_clear(rettv);
+ ret = FAIL;
+ }
+ (*arg) += slen;
+ }
+ }
+
while (ret == OK
&& (**arg == '['
@@ -20200,14 +20340,16 @@ handle_subscript(
// Invoke the function. Recursive!
if (functv.v_type == VAR_PARTIAL) {
pt = functv.vval.v_partial;
- s = partial_name(pt);
+ if (!lua) {
+ s = partial_name(pt);
+ }
} else {
s = functv.vval.v_string;
}
} else {
s = (char_u *)"";
}
- ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg,
+ ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&len, evaluate, pt, selfdict);
@@ -20442,7 +20584,7 @@ static hashtab_T *get_funccal_local_ht(void)
return &get_funccal()->l_vars.dv_hashtab;
}
-/// Find the dict and hashtable used for a variable
+/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable.
///
/// @param[in] name Variable name, possibly with scope prefix.
/// @param[in] name_len Variable name length.
@@ -21742,22 +21884,31 @@ void ex_function(exarg_T *eap)
}
// Check for ":let v =<< [trim] EOF"
+ // and ":let [a, b] =<< [trim] EOF"
arg = skipwhite(skiptowhite(p));
- arg = skipwhite(skiptowhite(arg));
- if (arg[0] == '=' && arg[1] == '<' && arg[2] =='<'
- && ((p[0] == 'l' && p[1] == 'e'
- && (!ASCII_ISALNUM(p[2])
- || (p[2] == 't' && !ASCII_ISALNUM(p[3])))))) {
- p = skipwhite(arg + 3);
- if (STRNCMP(p, "trim", 4) == 0) {
- // Ignore leading white space.
- p = skipwhite(p + 4);
- heredoc_trimmed = vim_strnsave(theline,
- (int)(skipwhite(theline) - theline));
+ if (*arg == '[') {
+ arg = vim_strchr(arg, ']');
+ }
+ if (arg != NULL) {
+ arg = skipwhite(skiptowhite(arg));
+ if (arg[0] == '='
+ && arg[1] == '<'
+ && arg[2] =='<'
+ && (p[0] == 'l'
+ && p[1] == 'e'
+ && (!ASCII_ISALNUM(p[2])
+ || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) {
+ p = skipwhite(arg + 3);
+ if (STRNCMP(p, "trim", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed =
+ vim_strnsave(theline, (int)(skipwhite(theline) - theline));
+ }
+ skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p));
+ do_concat = false;
+ is_heredoc = true;
}
- skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p));
- do_concat = false;
- is_heredoc = true;
}
}
@@ -22030,8 +22181,19 @@ trans_function_name(
*pp = (char_u *)end;
} else if (lv.ll_tv->v_type == VAR_PARTIAL
&& lv.ll_tv->vval.v_partial != NULL) {
- name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
- *pp = (char_u *)end;
+ if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') {
+ len = check_luafunc_name((const char *)end+1, true);
+ if (len == 0) {
+ EMSG2(e_invexpr2, "v:lua");
+ goto theend;
+ }
+ name = xmallocz(len);
+ memcpy(name, end+1, len);
+ *pp = (char_u *)end+1+len;
+ } else {
+ name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
+ *pp = (char_u *)end;
+ }
if (partial != NULL) {
*partial = lv.ll_tv->vval.v_partial;
}
@@ -23491,7 +23653,7 @@ void ex_return(exarg_T *eap)
int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
{
int idx;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (reanimate)
/* Undo the return. */
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index e099de831a..2aa08e2074 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -117,6 +117,7 @@ typedef enum {
VV_TYPE_BOOL,
VV_ECHOSPACE,
VV_EXITING,
+ VV_LUA,
} VimVarIndex;
/// All recognized msgpack types
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 0ae250e626..efeac70816 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -243,6 +243,7 @@ return {
pow={args=2},
prevnonblank={args=1},
printf={args=varargs(1)},
+ pum_getpos={},
pumvisible={},
py3eval={args=1},
pyeval={args=1},
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 106b8f6eed..72ee45a03a 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1200,6 +1200,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
typval_T rettv;
+ dict->dv_refcount++;
QUEUE *w;
QUEUE_FOREACH(w, &dict->watchers) {
DictWatcher *watcher = tv_dict_watcher_node_data(w);
@@ -1211,6 +1212,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
tv_clear(&rettv);
}
}
+ tv_dict_unref(dict);
for (size_t i = 1; i < ARRAY_SIZE(argv); i++) {
tv_clear(argv + i);
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 0b04170cac..02d241f6f5 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -246,7 +246,7 @@ typedef int scid_T;
/// Format argument for scid_T
#define PRIdSCID "d"
-// SCript ConteXt (SCTX): identifies a script script line.
+// SCript ConteXt (SCTX): identifies a script line.
// When sourcing a script "sc_lnum" is zero, "sourcing_lnum" is the current
// line number. When executing a user function "sc_lnum" is the line where the
// function was defined, "sourcing_lnum" is the line number inside the
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index 63efee59a8..37b9f73ba4 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -41,7 +41,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
#endif
uvproc->uvopts.exit_cb = exit_cb;
uvproc->uvopts.cwd = proc->cwd;
- uvproc->uvopts.env = NULL; // Inherits the parent (nvim) env.
+ uvproc->uvopts.env = proc->env;
uvproc->uvopts.stdio = uvproc->uvstdio;
uvproc->uvopts.stdio_count = 3;
uvproc->uvstdio[0].flags = UV_IGNORE;
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index ef9d953ab7..b677b80bfe 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -23,6 +23,7 @@ struct process {
uint64_t stopped_time; // process_stop() timestamp
const char *cwd;
char **argv;
+ char **env;
Stream in, out, err;
process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 1b6d9b50e9..0c3b467612 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -39,6 +39,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/message.h"
@@ -658,10 +659,10 @@ void ex_sort(exarg_T *eap)
deleted = (long)(count - (lnum - eap->line2));
if (deleted > 0) {
mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted,
- false);
+ false, kExtmarkUndo);
msgmore(-deleted);
} else if (deleted < 0) {
- mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false);
+ mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false, kExtmarkUndo);
}
if (change_occurred || deleted != 0) {
changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true);
@@ -874,10 +875,12 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
* their final destination at the new text position -- webb
*/
last_line = curbuf->b_ml.ml_line_count;
- mark_adjust_nofold(line1, line2, last_line - line2, 0L, true);
+ mark_adjust_nofold(line1, line2, last_line - line2, 0L, true, kExtmarkNoUndo);
+ extmark_adjust(curbuf, line1, line2, last_line - line2, 0L, kExtmarkNoUndo,
+ true);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
if (dest >= line2) {
- mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false);
+ mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false, kExtmarkNoUndo);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, line1, line2, dest);
@@ -886,7 +889,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
curbuf->b_op_start.lnum = dest - num_lines + 1;
curbuf->b_op_end.lnum = dest;
} else {
- mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false);
+ mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false,
+ kExtmarkNoUndo);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2);
@@ -897,7 +901,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
}
curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
mark_adjust_nofold(last_line - num_lines + 1, last_line,
- -(last_line - dest - extra), 0L, true);
+ -(last_line - dest - extra), 0L, true, kExtmarkNoUndo);
+
+ u_extmark_move(curbuf, line1, line2, last_line, dest, num_lines, extra);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
// send update regarding the new lines that were added
@@ -1281,12 +1287,14 @@ static void do_filter(
if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) {
if (read_linecount >= linecount) {
// move all marks from old lines to new lines
- mark_adjust(line1, line2, linecount, 0L, false);
+ mark_adjust(line1, line2, linecount, 0L, false, kExtmarkUndo);
} else {
// move marks from old lines to new lines, delete marks
// that are in deleted lines
- mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false);
- mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false);
+ mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false,
+ kExtmarkUndo);
+ mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false,
+ kExtmarkUndo);
}
}
@@ -1579,7 +1587,7 @@ int rename_buffer(char_u *new_fname)
xfname = curbuf->b_fname;
curbuf->b_ffname = NULL;
curbuf->b_sfname = NULL;
- if (setfname(curbuf, new_fname, NULL, TRUE) == FAIL) {
+ if (setfname(curbuf, new_fname, NULL, true) == FAIL) {
curbuf->b_ffname = fname;
curbuf->b_sfname = sfname;
return FAIL;
@@ -3214,6 +3222,189 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags,
return cmd;
}
+static void extmark_move_regmatch_single(lpos_T startpos,
+ lpos_T endpos,
+ linenr_T lnum,
+ int sublen)
+{
+ colnr_T mincol;
+ colnr_T endcol;
+ colnr_T col_amount;
+
+ mincol = startpos.col + 1;
+ endcol = endpos.col + 1;
+
+ // There are cases such as :s/^/x/ where this happens
+ // a delete is simply not required.
+ if (mincol + 1 <= endcol) {
+ extmark_col_adjust_delete(curbuf,
+ lnum, mincol + 1, endcol, kExtmarkUndo, 0);
+ }
+
+ // Insert, sublen seems to be the value we need but + 1...
+ col_amount = sublen - 1;
+ extmark_col_adjust(curbuf, lnum, mincol, 0, col_amount, kExtmarkUndo);
+}
+
+static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i)
+{
+ colnr_T mincol;
+ linenr_T u_lnum;
+ mincol = s.startpos.col + 1;
+
+ linenr_T n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum;
+ colnr_T n_after_newline_in_pat = s.endpos.col;
+ colnr_T n_before_newline_in_pat = mincol - s.cm_start.col;
+ long n_after_newline_in_sub;
+ if (!s.newline_in_sub) {
+ n_after_newline_in_sub = s.cm_end.col - s.cm_start.col;
+ } else {
+ n_after_newline_in_sub = s.cm_end.col;
+ }
+
+ if (s.newline_in_pat && !s.newline_in_sub) {
+ // -- Delete Pattern --
+ // 1. Move marks in the pattern
+ mincol = s.startpos.col + 1;
+ u_lnum = n_u_lnum;
+ assert(n_u_lnum == u_lnum);
+ extmark_copy_and_place(curbuf,
+ s.lnum, mincol,
+ u_lnum, n_after_newline_in_pat,
+ s.lnum, mincol,
+ kExtmarkUndo, true, NULL);
+ // 2. Move marks on last newline
+ mincol = mincol - n_before_newline_in_pat;
+ extmark_col_adjust(curbuf,
+ u_lnum,
+ n_after_newline_in_pat + 1,
+ -s.newline_in_pat,
+ mincol - n_after_newline_in_pat,
+ kExtmarkUndo);
+ // Take care of the lines after
+ extmark_adjust(curbuf,
+ u_lnum,
+ u_lnum,
+ MAXLNUM,
+ -s.newline_in_pat,
+ kExtmarkUndo,
+ false);
+ // 1. first insert the text in the substitutaion
+ extmark_col_adjust(curbuf,
+ s.lnum,
+ mincol + 1,
+ s.newline_in_sub,
+ n_after_newline_in_sub,
+ kExtmarkUndo);
+
+ } else {
+ // The data in sub_obj is as if the substituons above had already taken
+ // place. For our extmarks they haven't as we work from the bottom of the
+ // buffer up. Readjust the data.
+ n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum;
+ n_u_lnum = n_u_lnum - s.lnum_added;
+
+ // adjusted = L - (i-1)N
+ // where L = lnum value, N= lnum_added and i = iteration
+ linenr_T a_l_lnum = s.cm_start.lnum - ((i -1) * s.lnum_added);
+ linenr_T a_u_lnum = a_l_lnum + s.endpos.lnum;
+ assert(s.startpos.lnum == 0);
+
+ mincol = s.startpos.col + 1;
+ u_lnum = n_u_lnum;
+
+ if (!s.newline_in_pat && s.newline_in_sub) {
+ // -- Delete Pattern --
+ // 1. Move marks in the pattern
+ extmark_col_adjust_delete(curbuf,
+ a_l_lnum,
+ mincol + 1,
+ s.endpos.col + 1,
+ kExtmarkUndo,
+ s.eol);
+
+ extmark_adjust(curbuf,
+ a_u_lnum + 1,
+ MAXLNUM,
+ (long)s.newline_in_sub,
+ 0,
+ kExtmarkUndo,
+ false);
+ // 3. Insert
+ extmark_col_adjust(curbuf,
+ a_l_lnum,
+ mincol,
+ s.newline_in_sub,
+ (long)-mincol + 1 + n_after_newline_in_sub,
+ kExtmarkUndo);
+ } else if (s.newline_in_pat && s.newline_in_sub) {
+ if (s.lnum_added >= 0) {
+ linenr_T u_col = n_after_newline_in_pat == 0
+ ? 1 : n_after_newline_in_pat;
+ extmark_copy_and_place(curbuf,
+ a_l_lnum, mincol,
+ a_u_lnum, u_col,
+ a_l_lnum, mincol,
+ kExtmarkUndo, true, NULL);
+ // 2. Move marks on last newline
+ mincol = mincol - (colnr_T)n_before_newline_in_pat;
+ extmark_col_adjust(curbuf,
+ a_u_lnum,
+ (colnr_T)(n_after_newline_in_pat + 1),
+ -s.newline_in_pat,
+ mincol - n_after_newline_in_pat,
+ kExtmarkUndo);
+ // TODO(timeyyy): nothing to do here if lnum_added = 0
+ extmark_adjust(curbuf,
+ a_u_lnum + 1,
+ MAXLNUM,
+ (long)s.lnum_added,
+ 0,
+ kExtmarkUndo,
+ false);
+
+ extmark_col_adjust(curbuf,
+ a_l_lnum,
+ mincol + 1,
+ s.newline_in_sub,
+ (long)-mincol + n_after_newline_in_sub,
+ kExtmarkUndo);
+ } else {
+ mincol = s.startpos.col + 1;
+ a_l_lnum = s.startpos.lnum + 1;
+ a_u_lnum = s.endpos.lnum + 1;
+ extmark_copy_and_place(curbuf,
+ a_l_lnum, mincol,
+ a_u_lnum, n_after_newline_in_pat,
+ a_l_lnum, mincol,
+ kExtmarkUndo, true, NULL);
+ // 2. Move marks on last newline
+ mincol = mincol - (colnr_T)n_before_newline_in_pat;
+ extmark_col_adjust(curbuf,
+ a_u_lnum,
+ (colnr_T)(n_after_newline_in_pat + 1),
+ -s.newline_in_pat,
+ mincol - n_after_newline_in_pat,
+ kExtmarkUndo);
+ extmark_adjust(curbuf,
+ a_u_lnum,
+ a_u_lnum,
+ MAXLNUM,
+ s.lnum_added,
+ kExtmarkUndo,
+ false);
+ // 3. Insert
+ extmark_col_adjust(curbuf,
+ a_l_lnum,
+ mincol + 1,
+ s.newline_in_sub,
+ (long)-mincol + n_after_newline_in_sub,
+ kExtmarkUndo);
+ }
+ }
+ }
+}
+
/// Perform a substitution from line eap->line1 to line eap->line2 using the
/// command pointed to by eap->arg which should be of the form:
///
@@ -3260,6 +3451,17 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
int save_ma = 0;
int save_b_changed = curbuf->b_changed;
bool preview = (State & CMDPREVIEW);
+ extmark_sub_multi_vec_t extmark_sub_multi = KV_INITIAL_VALUE;
+ extmark_sub_single_vec_t extmark_sub_single = KV_INITIAL_VALUE;
+ linenr_T no_of_lines_changed = 0;
+ linenr_T newline_in_pat = 0;
+ linenr_T newline_in_sub = 0;
+
+ // inccommand tests fail without this check
+ if (!preview) {
+ // Required for Undo to work for extmarks.
+ u_save_cursor();
+ }
if (!global_busy) {
sub_nsubs = 0;
@@ -3418,6 +3620,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
// Check for a match on each line.
// If preview: limit to max('cmdwinheight', viewport).
linenr_T line2 = eap->line2;
+
for (linenr_T lnum = eap->line1;
lnum <= line2 && !got_quit && !aborting()
&& (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh
@@ -3876,6 +4079,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
ADJUST_SUB_FIRSTLNUM();
+
// Now the trick is to replace CTRL-M chars with a real line
// break. This would make it impossible to insert a CTRL-M in
// the text. The line break can be avoided by preceding the
@@ -3890,7 +4094,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
*p1 = NUL; // truncate up to the CR
ml_append(lnum - 1, new_start,
(colnr_T)(p1 - new_start + 1), false);
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false,
+ kExtmarkNOOP);
+
if (subflags.do_ask) {
appended_lines(lnum - 1, 1L);
} else {
@@ -3917,6 +4123,44 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
current_match.end.lnum = lnum;
}
+ // Adjust extmarks, by delete and then insert
+ if (!preview) {
+ newline_in_pat = (regmatch.endpos[0].lnum
+ - regmatch.startpos[0].lnum);
+ newline_in_sub = current_match.end.lnum - current_match.start.lnum;
+ if (newline_in_pat || newline_in_sub) {
+ ExtmarkSubMulti sub_multi;
+ no_of_lines_changed = newline_in_sub - newline_in_pat;
+
+ sub_multi.newline_in_pat = newline_in_pat;
+ sub_multi.newline_in_sub = newline_in_sub;
+ sub_multi.lnum = lnum;
+ sub_multi.lnum_added = no_of_lines_changed;
+ sub_multi.cm_start = current_match.start;
+ sub_multi.cm_end = current_match.end;
+
+ sub_multi.startpos = regmatch.startpos[0];
+ sub_multi.endpos = regmatch.endpos[0];
+ sub_multi.eol = extmark_eol_col(curbuf, lnum);
+
+ kv_push(extmark_sub_multi, sub_multi);
+ // Collect information required for moving extmarks WITHOUT \n, \r
+ } else {
+ no_of_lines_changed = 0;
+
+ if (regmatch.startpos[0].col != -1) {
+ ExtmarkSubSingle sub_single;
+ sub_single.sublen = sublen;
+ sub_single.lnum = lnum;
+ sub_single.startpos = regmatch.startpos[0];
+ sub_single.endpos = regmatch.endpos[0];
+
+ kv_push(extmark_sub_single, sub_single);
+ }
+ }
+ }
+
+
// 4. If subflags.do_all is set, find next match.
// Prevent endless loop with patterns that match empty
// strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g.
@@ -3983,7 +4227,7 @@ skip:
ml_delete(lnum, false);
}
mark_adjust(lnum, lnum + nmatch_tl - 1,
- (long)MAXLNUM, -nmatch_tl, false);
+ (long)MAXLNUM, -nmatch_tl, false, kExtmarkNOOP);
if (subflags.do_ask) {
deleted_lines(lnum, nmatch_tl);
}
@@ -4159,6 +4403,35 @@ skip:
}
}
}
+ if (newline_in_pat || newline_in_sub) {
+ long n = (long)kv_size(extmark_sub_multi);
+ ExtmarkSubMulti sub_multi;
+ if (no_of_lines_changed < 0) {
+ for (i = 0; i < n; i++) {
+ sub_multi = kv_A(extmark_sub_multi, i);
+ extmark_move_regmatch_multi(sub_multi, i);
+ }
+ } else {
+ // Move extmarks in reverse order to avoid moving marks we just moved...
+ for (i = 0; i < n; i++) {
+ sub_multi = kv_Z(extmark_sub_multi, i);
+ extmark_move_regmatch_multi(sub_multi, n - i);
+ }
+ }
+ kv_destroy(extmark_sub_multi);
+ } else {
+ long n = (long)kv_size(extmark_sub_single);
+ ExtmarkSubSingle sub_single;
+ for (i = 0; i < n; i++) {
+ sub_single = kv_Z(extmark_sub_single, i);
+ extmark_move_regmatch_single(sub_single.startpos,
+ sub_single.endpos,
+ sub_single.lnum,
+ sub_single.sublen);
+ }
+
+ kv_destroy(extmark_sub_single);
+ }
kv_destroy(preview_lines.subresults);
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 84291b3637..496aecfb27 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -3015,11 +3015,78 @@ static FILE *fopen_noinh_readbin(char *filename)
return fdopen(fd_tmp, READBIN);
}
+typedef struct {
+ char_u *buf;
+ size_t offset;
+} GetStrLineCookie;
-/// Read the file "fname" and execute its lines as EX commands.
+/// Get one full line from a sourced string (in-memory, no file).
+/// Called by do_cmdline() when it's called from do_source_str().
+///
+/// @return pointer to allocated line, or NULL for end-of-file or
+/// some error.
+static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
+{
+ GetStrLineCookie *p = cookie;
+ size_t i = p->offset;
+ if (strlen((char *)p->buf) <= p->offset) {
+ return NULL;
+ }
+ while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) {
+ i++;
+ }
+ char buf[2046];
+ char *dst;
+ dst = xstpncpy(buf, (char *)p->buf + p->offset, i - p->offset);
+ if ((uint32_t)(dst - buf) != i - p->offset) {
+ smsg(_(":source error parsing command %s"), p->buf);
+ return NULL;
+ }
+ buf[i - p->offset] = '\0';
+ p->offset = i + 1;
+ return (char_u *)xstrdup(buf);
+}
+
+/// Executes lines in `src` as Ex commands.
+///
+/// @see do_source()
+int do_source_str(const char *cmd, const char *traceback_name)
+{
+ char_u *save_sourcing_name = sourcing_name;
+ linenr_T save_sourcing_lnum = sourcing_lnum;
+ char_u sourcing_name_buf[256];
+ if (save_sourcing_name == NULL) {
+ sourcing_name = (char_u *)traceback_name;
+ } else {
+ snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf),
+ "%s called at %s:%"PRIdLINENR, traceback_name, save_sourcing_name,
+ save_sourcing_lnum);
+ sourcing_name = sourcing_name_buf;
+ }
+ sourcing_lnum = 0;
+
+ GetStrLineCookie cookie = {
+ .buf = (char_u *)cmd,
+ .offset = 0,
+ };
+ const sctx_T save_current_sctx = current_sctx;
+ current_sctx.sc_sid = SID_STR;
+ current_sctx.sc_seq = 0;
+ current_sctx.sc_lnum = save_sourcing_lnum;
+ int retval = do_cmdline(NULL, get_str_line, (void *)&cookie,
+ DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT);
+ current_sctx = save_current_sctx;
+ sourcing_lnum = save_sourcing_lnum;
+ sourcing_name = save_sourcing_name;
+ return retval;
+}
+
+/// Reads the file `fname` and executes its lines as Ex commands.
///
/// This function may be called recursively!
///
+/// @see do_source_str
+///
/// @param fname
/// @param check_other check for .vimrc and _vimrc
/// @param is_vimrc DOSO_ value
@@ -3360,6 +3427,8 @@ char_u *get_scriptname(LastSet last_set, bool *should_free)
_("API client (channel id %" PRIu64 ")"),
last_set.channel_id);
return IObuff;
+ case SID_STR:
+ return (char_u *)_("anonymous :source");
default:
*should_free = true;
return home_replace_save(NULL,
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 3a9fd01dd9..4a40cc54b4 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -12,31 +12,29 @@
# include "ex_cmds_enum.generated.h"
#endif
-/*
- * When adding an Ex command:
- * 1. Add an entry to the table in src/nvim/ex_cmds.lua. Keep it sorted on the
- * shortest version of the command name that works. If it doesn't start with
- * a lower case letter, add it at the end.
- *
- * Each table entry is a table with the following keys:
- *
- * Key | Description
- * ------- | -------------------------------------------------------------
- * command | Name of the command. Required.
- * enum | Name of the enum entry. If not set defaults to CMD_{command}.
- * flags | A set of the flags from below list joined by bitwise or.
- * func | Name of the function containing the implementation.
- *
- * Referenced function should be either non-static one or defined in
- * ex_docmd.c and be coercible to ex_func_T type from below.
- *
- * All keys not described in the above table are reserved for future use.
- *
- * 2. Add a "case: CMD_xxx" in the big switch in ex_docmd.c.
- * 3. Add an entry in the index for Ex commands at ":help ex-cmd-index".
- * 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and
- * long name of the command.
- */
+// When adding an Ex command:
+// 1. Add an entry to the table in src/nvim/ex_cmds.lua. Keep it sorted on the
+// shortest version of the command name that works. If it doesn't start with
+// a lower case letter, add it at the end.
+//
+// Each table entry is a table with the following keys:
+//
+// Key | Description
+// ------- | -------------------------------------------------------------
+// command | Name of the command. Required.
+// enum | Name of the enum entry. If not set defaults to CMD_{command}.
+// flags | A set of the flags from below list joined by bitwise or.
+// func | Name of the function containing the implementation.
+//
+// Referenced function should be either non-static one or defined in
+// ex_docmd.c and be coercible to ex_func_T type from below.
+//
+// All keys not described in the above table are reserved for future use.
+//
+// 2. Add a "case: CMD_xxx" in the big switch in ex_docmd.c.
+// 3. Add an entry in the index for Ex commands at ":help ex-cmd-index".
+// 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and
+// long name of the command.
#define RANGE 0x001 /* allow a linespecs */
#define BANG 0x002 /* allow a ! after the command name */
@@ -98,6 +96,47 @@ typedef struct cmdname {
int cmd_addr_type; ///< Flag for address type
} CommandDefinition;
+// A list used for saving values of "emsg_silent". Used by ex_try() to save the
+// value of "emsg_silent" if it was non-zero. When this is done, the CSF_SILENT
+// flag below is set.
+typedef struct eslist_elem eslist_T;
+struct eslist_elem {
+ int saved_emsg_silent; // saved value of "emsg_silent"
+ eslist_T *next; // next element on the list
+};
+
+// For conditional commands a stack is kept of nested conditionals.
+// When cs_idx < 0, there is no conditional command.
+enum {
+ CSTACK_LEN = 50,
+};
+
+typedef struct {
+ int cs_flags[CSTACK_LEN]; // CSF_ flags
+ char cs_pending[CSTACK_LEN]; // CSTP_: what's pending in ":finally"
+ union {
+ void *csp_rv[CSTACK_LEN]; // return typeval for pending return
+ void *csp_ex[CSTACK_LEN]; // exception for pending throw
+ } cs_pend;
+ void *cs_forinfo[CSTACK_LEN]; // info used by ":for"
+ int cs_line[CSTACK_LEN]; // line nr of ":while"/":for" line
+ int cs_idx; // current entry, or -1 if none
+ int cs_looplevel; // nr of nested ":while"s and ":for"s
+ int cs_trylevel; // nr of nested ":try"s
+ eslist_T *cs_emsg_silent_list; // saved values of "emsg_silent"
+ int cs_lflags; // loop flags: CSL_ flags
+} cstack_T;
+# define cs_rettv cs_pend.csp_rv
+# define cs_exception cs_pend.csp_ex
+
+// Flags for the cs_lflags item in cstack_T.
+enum {
+ CSL_HAD_LOOP = 1, // just found ":while" or ":for"
+ CSL_HAD_ENDLOOP = 2, // just found ":endwhile" or ":endfor"
+ CSL_HAD_CONT = 4, // just found ":continue"
+ CSL_HAD_FINA = 8, // just found ":finally"
+};
+
/// Arguments used for Ex commands.
struct exarg {
char_u *arg; ///< argument of the command
@@ -128,7 +167,7 @@ struct exarg {
char_u *errmsg; ///< returned error message
LineGetter getline; ///< Function used to get the next line
void *cookie; ///< argument for getline()
- struct condstack *cstack; ///< condition stack for ":if" etc.
+ cstack_T *cstack; ///< condition stack for ":if" etc.
};
#define FORCE_BIN 1 // ":edit ++bin file"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index d3e2120721..d16ad9db2c 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -140,6 +140,31 @@ struct dbg_stuff {
except_T *current_exception;
};
+typedef struct {
+ // parsed results
+ exarg_T *eap;
+ char_u *parsed_upto; // local we've parsed up to so far
+ char_u *cmd; // start of command
+ char_u *after_modifier;
+
+ // errors
+ char_u *errormsg;
+
+ // globals that need to be updated
+ cmdmod_T cmdmod;
+ int sandbox;
+ int msg_silent;
+ int emsg_silent;
+ bool ex_pressedreturn;
+ long p_verbose;
+
+ // other side-effects
+ bool set_eventignore;
+ long verbose_save;
+ int save_msg_silent;
+ int did_esilent;
+ bool did_sandbox;
+} parse_state_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.c.generated.h"
@@ -271,25 +296,23 @@ int do_cmdline_cmd(const char *cmd)
DOCMD_NOWAIT|DOCMD_KEYTYPED);
}
-/*
- * do_cmdline(): execute one Ex command line
- *
- * 1. Execute "cmdline" when it is not NULL.
- * If "cmdline" is NULL, or more lines are needed, fgetline() is used.
- * 2. Split up in parts separated with '|'.
- *
- * This function can be called recursively!
- *
- * flags:
- * DOCMD_VERBOSE - The command will be included in the error message.
- * DOCMD_NOWAIT - Don't call wait_return() and friends.
- * DOCMD_REPEAT - Repeat execution until fgetline() returns NULL.
- * DOCMD_KEYTYPED - Don't reset KeyTyped.
- * DOCMD_EXCRESET - Reset the exception environment (used for debugging).
- * DOCMD_KEEPLINE - Store first typed line (for repeating with ".").
- *
- * return FAIL if cmdline could not be executed, OK otherwise
- */
+/// do_cmdline(): execute one Ex command line
+///
+/// 1. Execute "cmdline" when it is not NULL.
+/// If "cmdline" is NULL, or more lines are needed, fgetline() is used.
+/// 2. Split up in parts separated with '|'.
+///
+/// This function can be called recursively!
+///
+/// flags:
+/// DOCMD_VERBOSE - The command will be included in the error message.
+/// DOCMD_NOWAIT - Don't call wait_return() and friends.
+/// DOCMD_REPEAT - Repeat execution until fgetline() returns NULL.
+/// DOCMD_KEYTYPED - Don't reset KeyTyped.
+/// DOCMD_EXCRESET - Reset the exception environment (used for debugging).
+/// DOCMD_KEEPLINE - Store first typed line (for repeating with ".").
+///
+/// @return FAIL if cmdline could not be executed, OK otherwise
int do_cmdline(char_u *cmdline, LineGetter fgetline,
void *cookie, /* argument for fgetline() */
int flags)
@@ -302,13 +325,13 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
int count = 0; /* line number count */
int did_inc = FALSE; /* incremented RedrawingDisabled */
int retval = OK;
- struct condstack cstack; /* conditional stack */
- garray_T lines_ga; /* keep lines for ":while"/":for" */
- int current_line = 0; /* active line in lines_ga */
- char_u *fname = NULL; /* function or script name */
- linenr_T *breakpoint = NULL; /* ptr to breakpoint field in cookie */
- int *dbg_tick = NULL; /* ptr to dbg_tick field in cookie */
- struct dbg_stuff debug_saved; /* saved things for debug mode */
+ cstack_T cstack; // conditional stack
+ garray_T lines_ga; // keep lines for ":while"/":for"
+ int current_line = 0; // active line in lines_ga
+ char_u *fname = NULL; // function or script name
+ linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie
+ int *dbg_tick = NULL; // ptr to dbg_tick field in cookie
+ struct dbg_stuff debug_saved; // saved things for debug mode
int initial_trylevel;
struct msglist **saved_msg_list = NULL;
struct msglist *private_msg_list;
@@ -338,7 +361,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
EMSG(_("E169: Command too recursive"));
// When converting to an exception, we do not include the command name
// since this is not an error of the specific command.
- do_errthrow((struct condstack *)NULL, (char_u *)NULL);
+ do_errthrow((cstack_T *)NULL, (char_u *)NULL);
msg_list = saved_msg_list;
return FAIL;
}
@@ -396,13 +419,12 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
// If force_abort is set, we cancel everything.
did_emsg = false;
- /*
- * KeyTyped is only set when calling vgetc(). Reset it here when not
- * calling vgetc() (sourced command lines).
- */
+ // KeyTyped is only set when calling vgetc(). Reset it here when not
+ // calling vgetc() (sourced command lines).
if (!(flags & DOCMD_KEYTYPED)
- && !getline_equal(fgetline, cookie, getexline))
+ && !getline_equal(fgetline, cookie, getexline)) {
KeyTyped = false;
+ }
/*
* Continue executing command lines:
@@ -1218,69 +1240,57 @@ static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite)
return (char_u *)p;
}
-/*
- * Execute one Ex command.
- *
- * If 'sourcing' is TRUE, the command will be included in the error message.
- *
- * 1. skip comment lines and leading space
- * 2. handle command modifiers
- * 3. skip over the range to find the command
- * 4. parse the range
- * 5. parse the command
- * 6. parse arguments
- * 7. switch on command name
- *
- * Note: "fgetline" can be NULL.
- *
- * This function may be called recursively!
- */
-static char_u * do_one_cmd(char_u **cmdlinep,
- int flags,
- struct condstack *cstack,
- LineGetter fgetline,
- void *cookie /* argument for fgetline() */
- )
+static void parse_state_to_global(const parse_state_T *parse_state)
{
- char_u *p;
- linenr_T lnum;
- long n;
- char_u *errormsg = NULL; /* error message */
- exarg_T ea; /* Ex command arguments */
- long verbose_save = -1;
- int save_msg_scroll = msg_scroll;
- int save_msg_silent = -1;
- int did_esilent = 0;
- int did_sandbox = FALSE;
- cmdmod_T save_cmdmod;
- const int save_reg_executing = reg_executing;
- char_u *cmd;
- int address_count = 1;
+ cmdmod = parse_state->cmdmod;
+ sandbox = parse_state->sandbox;
+ msg_silent = parse_state->msg_silent;
+ emsg_silent = parse_state->emsg_silent;
+ ex_pressedreturn = parse_state->ex_pressedreturn;
+ p_verbose = parse_state->p_verbose;
- memset(&ea, 0, sizeof(ea));
- ea.line1 = 1;
- ea.line2 = 1;
- ex_nesting_level++;
+ if (parse_state->set_eventignore) {
+ set_string_option_direct(
+ (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
+ }
+}
- /* When the last file has not been edited :q has to be typed twice. */
- if (quitmore
- /* avoid that a function call in 'statusline' does this */
- && !getline_equal(fgetline, cookie, get_func_line)
- /* avoid that an autocommand, e.g. QuitPre, does this */
- && !getline_equal(fgetline, cookie, getnextac)
- )
- --quitmore;
+static void parse_state_from_global(parse_state_T *parse_state)
+{
+ memset(parse_state, 0, sizeof(*parse_state));
+ parse_state->cmdmod = cmdmod;
+ parse_state->sandbox = sandbox;
+ parse_state->msg_silent = msg_silent;
+ parse_state->emsg_silent = emsg_silent;
+ parse_state->ex_pressedreturn = ex_pressedreturn;
+ parse_state->p_verbose = p_verbose;
+}
- /*
- * Reset browse, confirm, etc.. They are restored when returning, for
- * recursive calls.
- */
- save_cmdmod = cmdmod;
- memset(&cmdmod, 0, sizeof(cmdmod));
+//
+// Parse one Ex command.
+//
+// This has no side-effects, except for modifying parameters
+// passed in by pointer.
+//
+// The `out` should be zeroed, and its `ea` member initialised,
+// before calling this function.
+//
+static bool parse_one_cmd(
+ char_u **cmdlinep,
+ parse_state_T *const out,
+ LineGetter fgetline,
+ void *fgetline_cookie)
+{
+ exarg_T ea = {
+ .line1 = 1,
+ .line2 = 1,
+ };
+ *out->eap = ea;
- /* "#!anything" is handled like a comment. */
- if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!')
- goto doend;
+ // "#!anything" is handled like a comment.
+ if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') {
+ return false;
+ }
/*
* Repeat until no more command modifiers are found.
@@ -1290,70 +1300,76 @@ static char_u * do_one_cmd(char_u **cmdlinep,
/*
* 1. Skip comment lines and leading white space and colons.
*/
- while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':')
- ++ea.cmd;
+ while (*ea.cmd == ' '
+ || *ea.cmd == '\t'
+ || *ea.cmd == ':') {
+ ea.cmd++;
+ }
- /* in ex mode, an empty line works like :+ */
+ // in ex mode, an empty line works like :+
if (*ea.cmd == NUL && exmode_active
- && (getline_equal(fgetline, cookie, getexmodeline)
- || getline_equal(fgetline, cookie, getexline))
+ && (getline_equal(fgetline, fgetline_cookie, getexmodeline)
+ || getline_equal(fgetline, fgetline_cookie, getexline))
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
ea.cmd = (char_u *)"+";
- ex_pressedreturn = true;
+ out->ex_pressedreturn = true;
}
- /* ignore comment and empty lines */
- if (*ea.cmd == '"')
- goto doend;
+ // ignore comment and empty lines
+ if (*ea.cmd == '"') {
+ return false;
+ }
if (*ea.cmd == NUL) {
- ex_pressedreturn = true;
- goto doend;
+ out->ex_pressedreturn = true;
+ return false;
}
/*
* 2. Handle command modifiers.
*/
- p = skip_range(ea.cmd, NULL);
+ char_u *p = skip_range(ea.cmd, NULL);
switch (*p) {
- /* When adding an entry, also modify cmd_exists(). */
+ // When adding an entry, also modify cmd_exists().
case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3))
break;
- cmdmod.split |= WSP_ABOVE;
+ out->cmdmod.split |= WSP_ABOVE;
continue;
case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) {
- cmdmod.split |= WSP_BELOW;
+ out->cmdmod.split |= WSP_BELOW;
continue;
}
if (checkforcmd(&ea.cmd, "browse", 3)) {
- cmdmod.browse = true;
+ out->cmdmod.browse = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "botright", 2))
+ if (!checkforcmd(&ea.cmd, "botright", 2)) {
break;
- cmdmod.split |= WSP_BOT;
+ }
+ out->cmdmod.split |= WSP_BOT;
continue;
case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4))
break;
- cmdmod.confirm = true;
+ out->cmdmod.confirm = true;
continue;
case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) {
- cmdmod.keepmarks = true;
+ out->cmdmod.keepmarks = true;
continue;
}
if (checkforcmd(&ea.cmd, "keepalt", 5)) {
- cmdmod.keepalt = true;
+ out->cmdmod.keepalt = true;
continue;
}
if (checkforcmd(&ea.cmd, "keeppatterns", 5)) {
- cmdmod.keeppatterns = true;
+ out->cmdmod.keeppatterns = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "keepjumps", 5))
+ if (!checkforcmd(&ea.cmd, "keepjumps", 5)) {
break;
- cmdmod.keepjumps = true;
+ }
+ out->cmdmod.keepjumps = true;
continue;
case 'f': { // only accept ":filter {pat} cmd"
@@ -1363,7 +1379,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
break;
}
if (*p == '!') {
- cmdmod.filter_force = true;
+ out->cmdmod.filter_force = true;
p = skipwhite(p + 1);
if (*p == NUL || ends_excmd(*p)) {
break;
@@ -1373,134 +1389,217 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (p == NULL || *p == NUL) {
break;
}
- cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
- if (cmdmod.filter_regmatch.regprog == NULL) {
+ out->cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
+ if (out->cmdmod.filter_regmatch.regprog == NULL) {
break;
}
ea.cmd = p;
continue;
}
- /* ":hide" and ":hide | cmd" are not modifiers */
+ // ":hide" and ":hide | cmd" are not modifiers
case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3)
|| *p == NUL || ends_excmd(*p))
break;
ea.cmd = p;
- cmdmod.hide = true;
+ out->cmdmod.hide = true;
continue;
case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) {
- cmdmod.lockmarks = true;
+ out->cmdmod.lockmarks = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "leftabove", 5))
+ if (!checkforcmd(&ea.cmd, "leftabove", 5)) {
break;
- cmdmod.split |= WSP_ABOVE;
+ }
+ out->cmdmod.split |= WSP_ABOVE;
continue;
case 'n':
if (checkforcmd(&ea.cmd, "noautocmd", 3)) {
- if (cmdmod.save_ei == NULL) {
- /* Set 'eventignore' to "all". Restore the
- * existing option value later. */
- cmdmod.save_ei = vim_strsave(p_ei);
- set_string_option_direct(
- (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
+ if (out->cmdmod.save_ei == NULL) {
+ // Set 'eventignore' to "all". Restore the
+ // existing option value later.
+ out->cmdmod.save_ei = vim_strsave(p_ei);
+ out->set_eventignore = true;
}
continue;
}
if (!checkforcmd(&ea.cmd, "noswapfile", 3)) {
break;
}
- cmdmod.noswapfile = true;
+ out->cmdmod.noswapfile = true;
continue;
case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6))
break;
- cmdmod.split |= WSP_BELOW;
+ out->cmdmod.split |= WSP_BELOW;
continue;
case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) {
- if (!did_sandbox)
- ++sandbox;
- did_sandbox = TRUE;
+ if (!out->did_sandbox) {
+ out->sandbox++;
+ }
+ out->did_sandbox = true;
continue;
}
- if (!checkforcmd(&ea.cmd, "silent", 3))
+ if (!checkforcmd(&ea.cmd, "silent", 3)) {
break;
- if (save_msg_silent == -1)
- save_msg_silent = msg_silent;
- ++msg_silent;
+ }
+ if (out->save_msg_silent == -1) {
+ out->save_msg_silent = out->msg_silent;
+ }
+ out->msg_silent++;
if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) {
- /* ":silent!", but not "silent !cmd" */
+ // ":silent!", but not "silent !cmd"
ea.cmd = skipwhite(ea.cmd + 1);
- ++emsg_silent;
- ++did_esilent;
+ out->emsg_silent++;
+ out->did_esilent++;
}
continue;
case 't': if (checkforcmd(&p, "tab", 3)) {
- long tabnr = get_address(&ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1);
+ long tabnr = get_address(
+ &ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1);
+
if (tabnr == MAXLNUM) {
- cmdmod.tab = tabpage_index(curtab) + 1;
+ out->cmdmod.tab = tabpage_index(curtab) + 1;
} else {
if (tabnr < 0 || tabnr > LAST_TAB_NR) {
- errormsg = (char_u *)_(e_invrange);
- goto doend;
+ out->errormsg = (char_u *)_(e_invrange);
+ return false;
}
- cmdmod.tab = tabnr + 1;
+ out->cmdmod.tab = tabnr + 1;
}
ea.cmd = p;
continue;
}
- if (!checkforcmd(&ea.cmd, "topleft", 2))
+ if (!checkforcmd(&ea.cmd, "topleft", 2)) {
break;
- cmdmod.split |= WSP_TOP;
+ }
+ out->cmdmod.split |= WSP_TOP;
continue;
case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3))
break;
- if (save_msg_silent == -1)
- save_msg_silent = msg_silent;
- msg_silent = 0;
+ if (out->save_msg_silent == -1) {
+ out->save_msg_silent = out->msg_silent;
+ }
+ out->msg_silent = 0;
continue;
case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) {
- cmdmod.split |= WSP_VERT;
+ out->cmdmod.split |= WSP_VERT;
continue;
}
if (!checkforcmd(&p, "verbose", 4))
break;
- if (verbose_save < 0)
- verbose_save = p_verbose;
- if (ascii_isdigit(*ea.cmd))
- p_verbose = atoi((char *)ea.cmd);
- else
- p_verbose = 1;
+ if (out->verbose_save < 0) {
+ out->verbose_save = out->p_verbose;
+ }
+ if (ascii_isdigit(*ea.cmd)) {
+ out->p_verbose = atoi((char *)ea.cmd);
+ } else {
+ out->p_verbose = 1;
+ }
ea.cmd = p;
continue;
}
break;
}
- char_u *after_modifier = ea.cmd;
-
- ea.skip = (did_emsg
- || got_int
- || current_exception
- || (cstack->cs_idx >= 0
- && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)));
+ out->after_modifier = ea.cmd;
// 3. Skip over the range to find the command. Let "p" point to after it.
//
// We need the command to know what kind of range it uses.
- cmd = ea.cmd;
+ out->cmd = ea.cmd;
ea.cmd = skip_range(ea.cmd, NULL);
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
- p = find_command(&ea, NULL);
+ out->parsed_upto = find_command(&ea, NULL);
+
+ *out->eap = ea;
+
+ return true;
+}
+
+/*
+ * Execute one Ex command.
+ *
+ * If 'sourcing' is TRUE, the command will be included in the error message.
+ *
+ * 1. skip comment lines and leading space
+ * 2. handle command modifiers
+ * 3. skip over the range to find the command
+ * 4. parse the range
+ * 5. parse the command
+ * 6. parse arguments
+ * 7. switch on command name
+ *
+ * Note: "fgetline" can be NULL.
+ *
+ * This function may be called recursively!
+ */
+static char_u * do_one_cmd(char_u **cmdlinep,
+ int flags,
+ cstack_T *cstack,
+ LineGetter fgetline,
+ void *cookie /* argument for fgetline() */
+ )
+{
+ char_u *p;
+ linenr_T lnum;
+ long n;
+ char_u *errormsg = NULL; // error message
+ exarg_T ea;
+ int save_msg_scroll = msg_scroll;
+ parse_state_T parsed;
+ cmdmod_T save_cmdmod;
+ const int save_reg_executing = reg_executing;
+
+ ex_nesting_level++;
+
+ /* When the last file has not been edited :q has to be typed twice. */
+ if (quitmore
+ /* avoid that a function call in 'statusline' does this */
+ && !getline_equal(fgetline, cookie, get_func_line)
+ /* avoid that an autocommand, e.g. QuitPre, does this */
+ && !getline_equal(fgetline, cookie, getnextac)
+ )
+ --quitmore;
+
+ /*
+ * Reset browse, confirm, etc.. They are restored when returning, for
+ * recursive calls.
+ */
+ save_cmdmod = cmdmod;
+ memset(&cmdmod, 0, sizeof(cmdmod));
+
+ parse_state_from_global(&parsed);
+ parsed.eap = &ea;
+ parsed.verbose_save = -1;
+ parsed.save_msg_silent = -1;
+ parsed.did_esilent = 0;
+ parsed.did_sandbox = false;
+ bool parse_success = parse_one_cmd(cmdlinep, &parsed, fgetline, cookie);
+ parse_state_to_global(&parsed);
+
+ // Update locals from parse_one_cmd()
+ errormsg = parsed.errormsg;
+ p = parsed.parsed_upto;
+
+ if (!parse_success) {
+ goto doend;
+ }
+
+ ea.skip = (did_emsg
+ || got_int
+ || current_exception
+ || (cstack->cs_idx >= 0
+ && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)));
// Count this line for profiling if skip is TRUE.
if (do_profiling == PROF_YES
@@ -1571,148 +1670,9 @@ static char_u * do_one_cmd(char_u **cmdlinep,
}
}
- /* repeat for all ',' or ';' separated addresses */
- ea.cmd = cmd;
- for (;; ) {
- ea.line1 = ea.line2;
- switch (ea.addr_type) {
- case ADDR_LINES:
- // default is current line number
- ea.line2 = curwin->w_cursor.lnum;
- break;
- case ADDR_WINDOWS:
- ea.line2 = CURRENT_WIN_NR;
- break;
- case ADDR_ARGUMENTS:
- ea.line2 = curwin->w_arg_idx + 1;
- if (ea.line2 > ARGCOUNT) {
- ea.line2 = ARGCOUNT;
- }
- break;
- case ADDR_LOADED_BUFFERS:
- case ADDR_BUFFERS:
- ea.line2 = curbuf->b_fnum;
- break;
- case ADDR_TABS:
- ea.line2 = CURRENT_TAB_NR;
- break;
- case ADDR_TABS_RELATIVE:
- ea.line2 = 1;
- break;
- case ADDR_QUICKFIX:
- ea.line2 = qf_get_cur_valid_idx(&ea);
- break;
- }
- ea.cmd = skipwhite(ea.cmd);
- lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip,
- ea.addr_count == 0, address_count++);
- if (ea.cmd == NULL) { // error detected
- goto doend;
- }
- if (lnum == MAXLNUM) {
- if (*ea.cmd == '%') { /* '%' - all lines */
- ++ea.cmd;
- switch (ea.addr_type) {
- case ADDR_LINES:
- ea.line1 = 1;
- ea.line2 = curbuf->b_ml.ml_line_count;
- break;
- case ADDR_LOADED_BUFFERS: {
- buf_T *buf = firstbuf;
- while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) {
- buf = buf->b_next;
- }
- ea.line1 = buf->b_fnum;
- buf = lastbuf;
- while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) {
- buf = buf->b_prev;
- }
- ea.line2 = buf->b_fnum;
- break;
- }
- case ADDR_BUFFERS:
- ea.line1 = firstbuf->b_fnum;
- ea.line2 = lastbuf->b_fnum;
- break;
- case ADDR_WINDOWS:
- case ADDR_TABS:
- if (IS_USER_CMDIDX(ea.cmdidx)) {
- ea.line1 = 1;
- ea.line2 =
- ea.addr_type == ADDR_WINDOWS ? LAST_WIN_NR : LAST_TAB_NR;
- } else {
- // there is no Vim command which uses '%' and
- // ADDR_WINDOWS or ADDR_TABS
- errormsg = (char_u *)_(e_invrange);
- goto doend;
- }
- break;
- case ADDR_TABS_RELATIVE:
- errormsg = (char_u *)_(e_invrange);
- goto doend;
- break;
- case ADDR_ARGUMENTS:
- if (ARGCOUNT == 0) {
- ea.line1 = ea.line2 = 0;
- } else {
- ea.line1 = 1;
- ea.line2 = ARGCOUNT;
- }
- break;
- case ADDR_QUICKFIX:
- ea.line1 = 1;
- ea.line2 = qf_get_size(&ea);
- if (ea.line2 == 0) {
- ea.line2 = 1;
- }
- break;
- }
- ++ea.addr_count;
- }
- /* '*' - visual area */
- else if (*ea.cmd == '*') {
- pos_T *fp;
-
- if (ea.addr_type != ADDR_LINES) {
- errormsg = (char_u *)_(e_invrange);
- goto doend;
- }
-
- ++ea.cmd;
- if (!ea.skip) {
- fp = getmark('<', FALSE);
- if (check_mark(fp) == FAIL)
- goto doend;
- ea.line1 = fp->lnum;
- fp = getmark('>', FALSE);
- if (check_mark(fp) == FAIL)
- goto doend;
- ea.line2 = fp->lnum;
- ++ea.addr_count;
- }
- }
- } else
- ea.line2 = lnum;
- ea.addr_count++;
-
- if (*ea.cmd == ';') {
- if (!ea.skip) {
- curwin->w_cursor.lnum = ea.line2;
- // don't leave the cursor on an illegal line or column
- check_cursor();
- }
- } else if (*ea.cmd != ',') {
- break;
- }
- ea.cmd++;
- }
-
- /* One address given: set start and end lines */
- if (ea.addr_count == 1) {
- ea.line1 = ea.line2;
- /* ... but only implicit: really no address given */
- if (lnum == MAXLNUM)
- ea.addr_count = 0;
+ ea.cmd = parsed.cmd;
+ if (parse_cmd_address(&ea, &errormsg) == FAIL) {
+ goto doend;
}
/*
@@ -1791,8 +1751,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (!(flags & DOCMD_VERBOSE)) {
// If the modifier was parsed OK the error must be in the following
// command
- if (after_modifier != NULL) {
- append_command(after_modifier);
+ if (parsed.after_modifier != NULL) {
+ append_command(parsed.after_modifier);
} else {
append_command(*cmdlinep);
}
@@ -2163,6 +2123,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_browse:
case CMD_call:
case CMD_confirm:
+ case CMD_const:
case CMD_delfunction:
case CMD_djump:
case CMD_dlist:
@@ -2187,6 +2148,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_leftabove:
case CMD_let:
case CMD_lockmarks:
+ case CMD_lockvar:
case CMD_lua:
case CMD_match:
case CMD_mzscheme:
@@ -2215,6 +2177,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
case CMD_tilde:
case CMD_topleft:
case CMD_unlet:
+ case CMD_unlockvar:
case CMD_verbose:
case CMD_vertical:
case CMD_wincmd:
@@ -2260,12 +2223,12 @@ static char_u * do_one_cmd(char_u **cmdlinep,
// The :try command saves the emsg_silent flag, reset it here when
// ":silent! try" was used, it should only apply to :try itself.
- if (ea.cmdidx == CMD_try && did_esilent > 0) {
- emsg_silent -= did_esilent;
+ if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) {
+ emsg_silent -= parsed.did_esilent;
if (emsg_silent < 0) {
emsg_silent = 0;
}
- did_esilent = 0;
+ parsed.did_esilent = 0;
}
// 7. Execute the command.
@@ -2331,8 +2294,9 @@ doend:
? cmdnames[(int)ea.cmdidx].cmd_name
: (char_u *)NULL);
- if (verbose_save >= 0)
- p_verbose = verbose_save;
+ if (parsed.verbose_save >= 0) {
+ p_verbose = parsed.verbose_save;
+ }
if (cmdmod.save_ei != NULL) {
/* Restore 'eventignore' to the value before ":noautocmd". */
set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei,
@@ -2347,16 +2311,18 @@ doend:
cmdmod = save_cmdmod;
reg_executing = save_reg_executing;
- if (save_msg_silent != -1) {
- /* messages could be enabled for a serious error, need to check if the
- * counters don't become negative */
- if (!did_emsg || msg_silent > save_msg_silent)
- msg_silent = save_msg_silent;
- emsg_silent -= did_esilent;
- if (emsg_silent < 0)
+ if (parsed.save_msg_silent != -1) {
+ // messages could be enabled for a serious error, need to check if the
+ // counters don't become negative
+ if (!did_emsg || msg_silent > parsed.save_msg_silent) {
+ msg_silent = parsed.save_msg_silent;
+ }
+ emsg_silent -= parsed.did_esilent;
+ if (emsg_silent < 0) {
emsg_silent = 0;
- /* Restore msg_scroll, it's set by file I/O commands, even when no
- * message is actually displayed. */
+ }
+ // Restore msg_scroll, it's set by file I/O commands, even when no
+ // message is actually displayed.
msg_scroll = save_msg_scroll;
/* "silent reg" or "silent echo x" inside "redir" leaves msg_col
@@ -2365,8 +2331,9 @@ doend:
msg_col = 0;
}
- if (did_sandbox)
- --sandbox;
+ if (parsed.did_sandbox) {
+ sandbox--;
+ }
if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */
ea.nextcmd = NULL;
@@ -2376,6 +2343,160 @@ doend:
return ea.nextcmd;
}
+// Parse the address range, if any, in "eap".
+// Return FAIL and set "errormsg" or return OK.
+int parse_cmd_address(exarg_T *eap, char_u **errormsg)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int address_count = 1;
+ linenr_T lnum;
+
+ // Repeat for all ',' or ';' separated addresses.
+ for (;;) {
+ eap->line1 = eap->line2;
+ switch (eap->addr_type) {
+ case ADDR_LINES:
+ // default is current line number
+ eap->line2 = curwin->w_cursor.lnum;
+ break;
+ case ADDR_WINDOWS:
+ eap->line2 = CURRENT_WIN_NR;
+ break;
+ case ADDR_ARGUMENTS:
+ eap->line2 = curwin->w_arg_idx + 1;
+ if (eap->line2 > ARGCOUNT) {
+ eap->line2 = ARGCOUNT;
+ }
+ break;
+ case ADDR_LOADED_BUFFERS:
+ case ADDR_BUFFERS:
+ eap->line2 = curbuf->b_fnum;
+ break;
+ case ADDR_TABS:
+ eap->line2 = CURRENT_TAB_NR;
+ break;
+ case ADDR_TABS_RELATIVE:
+ eap->line2 = 1;
+ break;
+ case ADDR_QUICKFIX:
+ eap->line2 = qf_get_cur_valid_idx(eap);
+ break;
+ }
+ eap->cmd = skipwhite(eap->cmd);
+ lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip,
+ eap->addr_count == 0, address_count++);
+ if (eap->cmd == NULL) { // error detected
+ return FAIL;
+ }
+ if (lnum == MAXLNUM) {
+ if (*eap->cmd == '%') { // '%' - all lines
+ eap->cmd++;
+ switch (eap->addr_type) {
+ case ADDR_LINES:
+ eap->line1 = 1;
+ eap->line2 = curbuf->b_ml.ml_line_count;
+ break;
+ case ADDR_LOADED_BUFFERS: {
+ buf_T *buf = firstbuf;
+
+ while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) {
+ buf = buf->b_next;
+ }
+ eap->line1 = buf->b_fnum;
+ buf = lastbuf;
+ while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) {
+ buf = buf->b_prev;
+ }
+ eap->line2 = buf->b_fnum;
+ break;
+ }
+ case ADDR_BUFFERS:
+ eap->line1 = firstbuf->b_fnum;
+ eap->line2 = lastbuf->b_fnum;
+ break;
+ case ADDR_WINDOWS:
+ case ADDR_TABS:
+ if (IS_USER_CMDIDX(eap->cmdidx)) {
+ eap->line1 = 1;
+ eap->line2 = eap->addr_type == ADDR_WINDOWS
+ ? LAST_WIN_NR : LAST_TAB_NR;
+ } else {
+ // there is no Vim command which uses '%' and
+ // ADDR_WINDOWS or ADDR_TABS
+ *errormsg = (char_u *)_(e_invrange);
+ return FAIL;
+ }
+ break;
+ case ADDR_TABS_RELATIVE:
+ *errormsg = (char_u *)_(e_invrange);
+ return FAIL;
+ case ADDR_ARGUMENTS:
+ if (ARGCOUNT == 0) {
+ eap->line1 = eap->line2 = 0;
+ } else {
+ eap->line1 = 1;
+ eap->line2 = ARGCOUNT;
+ }
+ break;
+ case ADDR_QUICKFIX:
+ eap->line1 = 1;
+ eap->line2 = qf_get_size(eap);
+ if (eap->line2 == 0) {
+ eap->line2 = 1;
+ }
+ break;
+ }
+ eap->addr_count++;
+ } else if (*eap->cmd == '*') {
+ // '*' - visual area
+ if (eap->addr_type != ADDR_LINES) {
+ *errormsg = (char_u *)_(e_invrange);
+ return FAIL;
+ }
+
+ eap->cmd++;
+ if (!eap->skip) {
+ pos_T *fp = getmark('<', false);
+ if (check_mark(fp) == FAIL) {
+ return FAIL;
+ }
+ eap->line1 = fp->lnum;
+ fp = getmark('>', false);
+ if (check_mark(fp) == FAIL) {
+ return FAIL;
+ }
+ eap->line2 = fp->lnum;
+ eap->addr_count++;
+ }
+ }
+ } else {
+ eap->line2 = lnum;
+ }
+ eap->addr_count++;
+
+ if (*eap->cmd == ';') {
+ if (!eap->skip) {
+ curwin->w_cursor.lnum = eap->line2;
+ // don't leave the cursor on an illegal line or column
+ check_cursor();
+ }
+ } else if (*eap->cmd != ',') {
+ break;
+ }
+ eap->cmd++;
+ }
+
+ // One address given: set start and end lines.
+ if (eap->addr_count == 1) {
+ eap->line1 = eap->line2;
+ // ... but only implicit: really no address given
+ if (lnum == MAXLNUM) {
+ eap->addr_count = 0;
+ }
+ }
+ return OK;
+}
+
/*
* Check for an Ex command with optional tail.
* If there is a match advance "pp" to the argument and return TRUE.
@@ -3316,6 +3437,7 @@ const char * set_one_cmd_context(
case CMD_syntax:
set_context_in_syntax_cmd(xp, arg);
break;
+ case CMD_const:
case CMD_let:
case CMD_if:
case CMD_elseif:
@@ -3556,15 +3678,13 @@ const char * set_one_cmd_context(
return NULL;
}
-/*
- * skip a range specifier of the form: addr [,addr] [;addr] ..
- *
- * Backslashed delimiters after / or ? will be skipped, and commands will
- * not be expanded between /'s and ?'s or after "'".
- *
- * Also skip white space and ":" characters.
- * Returns the "cmd" pointer advanced to beyond the range.
- */
+// Skip a range specifier of the form: addr [,addr] [;addr] ..
+//
+// Backslashed delimiters after / or ? will be skipped, and commands will
+// not be expanded between /'s and ?'s or after "'".
+//
+// Also skip white space and ":" characters.
+// Returns the "cmd" pointer advanced to beyond the range.
char_u *skip_range(
const char_u *cmd,
int *ctx // pointer to xp_context or NULL
@@ -5014,9 +5134,11 @@ static void uc_list(char_u *name, size_t name_len)
ucmd_T *cmd;
int len;
uint32_t a;
- garray_T *gap;
- gap = &curbuf->b_ucmds;
+ // In cmdwin, the alternative buffer should be used.
+ garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? &prevwin->w_buffer->b_ucmds
+ : &curbuf->b_ucmds;
for (;; ) {
for (i = 0; i < gap->ga_len; ++i) {
cmd = USER_CMD_GA(gap, i);
@@ -5865,13 +5987,21 @@ char_u *get_user_cmd_addr_type(expand_T *xp, int idx)
/*
* Function given to ExpandGeneric() to obtain the list of user command names.
*/
-char_u *get_user_commands(expand_T *xp, int idx)
+char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (idx < curbuf->b_ucmds.ga_len)
- return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name;
- idx -= curbuf->b_ucmds.ga_len;
- if (idx < ucmds.ga_len)
+ // In cmdwin, the alternative buffer should be used.
+ const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? prevwin->w_buffer
+ : curbuf;
+
+ if (idx < buf->b_ucmds.ga_len) {
+ return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
+ }
+ idx -= buf->b_ucmds.ga_len;
+ if (idx < ucmds.ga_len) {
return USER_CMD(idx)->uc_name;
+ }
return NULL;
}
@@ -8207,6 +8337,7 @@ static void ex_normal(exarg_T *eap)
int save_insertmode = p_im;
int save_finish_op = finish_op;
long save_opcount = opcount;
+ const int save_reg_executing = reg_executing;
char_u *arg = NULL;
int l;
char_u *p;
@@ -8301,7 +8432,8 @@ static void ex_normal(exarg_T *eap)
p_im = save_insertmode;
finish_op = save_finish_op;
opcount = save_opcount;
- msg_didout |= save_msg_didout; /* don't reset msg_didout now */
+ reg_executing = save_reg_executing;
+ msg_didout |= save_msg_didout; // don't reset msg_didout now
/* Restore the state (needed when called from a function executed for
* 'indentexpr'). Update the mouse and cursor, they may have changed. */
@@ -10156,7 +10288,7 @@ static void ex_folddo(exarg_T *eap)
bool is_loclist_cmd(int cmdidx)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (cmdidx < 0 || cmdidx > CMD_SIZE) {
+ if (cmdidx < 0 || cmdidx >= CMD_SIZE) {
return false;
}
return cmdnames[cmdidx].cmd_name[0] == 'l';
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 28bc222827..f70a568e4a 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -307,7 +307,7 @@ void free_global_msglist(void)
* error exception. If cstack is NULL, postpone the throw until do_cmdline()
* has returned (see do_one_cmd()).
*/
-void do_errthrow(struct condstack *cstack, char_u *cmdname)
+void do_errthrow(cstack_T *cstack, char_u *cmdname)
{
/*
* Ensure that all commands in nested function calls and sourced files
@@ -339,7 +339,7 @@ void do_errthrow(struct condstack *cstack, char_u *cmdname)
* exception if appropriate. Return TRUE if the current exception is discarded,
* FALSE otherwise.
*/
-int do_intthrow(struct condstack *cstack)
+int do_intthrow(cstack_T *cstack)
{
// If no interrupt occurred or no try conditional is active and no exception
// is being thrown, do nothing (for compatibility of non-EH scripts).
@@ -795,7 +795,7 @@ void ex_if(exarg_T *eap)
{
int skip;
int result;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1)
eap->errmsg = (char_u *)N_("E579: :if nesting too deep");
@@ -852,7 +852,7 @@ void ex_else(exarg_T *eap)
{
int skip;
int result;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
skip = CHECK_SKIP;
@@ -926,7 +926,7 @@ void ex_while(exarg_T *eap)
bool error;
int skip;
int result;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1)
eap->errmsg = (char_u *)N_("E585: :while/:for nesting too deep");
@@ -1005,7 +1005,7 @@ void ex_while(exarg_T *eap)
void ex_continue(exarg_T *eap)
{
int idx;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0)
eap->errmsg = (char_u *)N_("E586: :continue without :while or :for");
@@ -1039,7 +1039,7 @@ void ex_continue(exarg_T *eap)
void ex_break(exarg_T *eap)
{
int idx;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0)
eap->errmsg = (char_u *)N_("E587: :break without :while or :for");
@@ -1061,7 +1061,7 @@ void ex_break(exarg_T *eap)
*/
void ex_endwhile(exarg_T *eap)
{
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
int idx;
char_u *err;
int csf;
@@ -1164,7 +1164,7 @@ void ex_throw(exarg_T *eap)
* for ":throw" (user exception) and error and interrupt exceptions. Also
* used for rethrowing an uncaught exception.
*/
-void do_throw(struct condstack *cstack)
+void do_throw(cstack_T *cstack)
{
int idx;
int inactivate_try = FALSE;
@@ -1225,7 +1225,7 @@ void do_throw(struct condstack *cstack)
void ex_try(exarg_T *eap)
{
int skip;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1)
eap->errmsg = (char_u *)N_("E601: :try nesting too deep");
@@ -1260,7 +1260,7 @@ void ex_try(exarg_T *eap)
* to save the value.
*/
if (emsg_silent) {
- eslist_T *elem = xmalloc(sizeof(struct eslist_elem));
+ eslist_T *elem = xmalloc(sizeof(*elem));
elem->saved_emsg_silent = emsg_silent;
elem->next = cstack->cs_emsg_silent_list;
cstack->cs_emsg_silent_list = elem;
@@ -1286,7 +1286,7 @@ void ex_catch(exarg_T *eap)
char_u *save_cpo;
regmatch_T regmatch;
int prev_got_int;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
char_u *pat;
if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) {
@@ -1432,7 +1432,7 @@ void ex_finally(exarg_T *eap)
int idx;
int skip = FALSE;
int pending = CSTP_NONE;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0)
eap->errmsg = (char_u *)N_("E606: :finally without :try");
@@ -1555,7 +1555,7 @@ void ex_endtry(exarg_T *eap)
int rethrow = FALSE;
int pending = CSTP_NONE;
void *rettv = NULL;
- struct condstack *cstack = eap->cstack;
+ cstack_T *const cstack = eap->cstack;
if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) {
eap->errmsg = (char_u *)N_("E602: :endtry without :try");
@@ -1882,7 +1882,7 @@ void leave_cleanup(cleanup_T *csp)
* entered, is restored (used by ex_endtry()). This is normally done only
* when such a try conditional is left.
*/
-int cleanup_conditionals(struct condstack *cstack, int searched_cond, int inclusive)
+int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive)
{
int idx;
int stop = FALSE;
@@ -1990,7 +1990,7 @@ int cleanup_conditionals(struct condstack *cstack, int searched_cond, int inclus
/*
* Return an appropriate error message for a missing endwhile/endfor/endif.
*/
-static char_u *get_end_emsg(struct condstack *cstack)
+static char_u *get_end_emsg(cstack_T *cstack)
{
if (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE)
return e_endwhile;
@@ -2007,7 +2007,8 @@ static char_u *get_end_emsg(struct condstack *cstack)
* type.
* Also free "for info" structures where needed.
*/
-void rewind_conditionals(struct condstack *cstack, int idx, int cond_type, int *cond_level)
+void rewind_conditionals(cstack_T *cstack, int idx, int cond_type,
+ int *cond_level)
{
while (cstack->cs_idx > idx) {
if (cstack->cs_flags[cstack->cs_idx] & cond_type)
diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h
index 2237b6aca3..d8388c9156 100644
--- a/src/nvim/ex_eval.h
+++ b/src/nvim/ex_eval.h
@@ -4,42 +4,6 @@
#include "nvim/pos.h" // for linenr_T
#include "nvim/ex_cmds_defs.h" // for exarg_T
-/*
- * A list used for saving values of "emsg_silent". Used by ex_try() to save the
- * value of "emsg_silent" if it was non-zero. When this is done, the CSF_SILENT
- * flag below is set.
- */
-
-typedef struct eslist_elem eslist_T;
-struct eslist_elem {
- int saved_emsg_silent; /* saved value of "emsg_silent" */
- eslist_T *next; /* next element on the list */
-};
-
-/*
- * For conditional commands a stack is kept of nested conditionals.
- * When cs_idx < 0, there is no conditional command.
- */
-#define CSTACK_LEN 50
-
-struct condstack {
- int cs_flags[CSTACK_LEN]; // CSF_ flags
- char cs_pending[CSTACK_LEN]; // CSTP_: what's pending in ":finally"
- union {
- void *csp_rv[CSTACK_LEN]; // return typeval for pending return
- void *csp_ex[CSTACK_LEN]; // exception for pending throw
- } cs_pend;
- void *cs_forinfo[CSTACK_LEN]; // info used by ":for"
- int cs_line[CSTACK_LEN]; // line nr of ":while"/":for" line
- int cs_idx; // current entry, or -1 if none
- int cs_looplevel; // nr of nested ":while"s and ":for"s
- int cs_trylevel; // nr of nested ":try"s
- eslist_T *cs_emsg_silent_list; // saved values of "emsg_silent"
- int cs_lflags; // loop flags: CSL_ flags
-};
-# define cs_rettv cs_pend.csp_rv
-# define cs_exception cs_pend.csp_ex
-
/* There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if"
* was used. */
# define CSF_TRUE 0x0001 /* condition was TRUE */
@@ -70,14 +34,6 @@ struct condstack {
# define CSTP_FINISH 32 /* ":finish" is pending */
/*
- * Flags for the cs_lflags item in struct condstack.
- */
-# define CSL_HAD_LOOP 1 /* just found ":while" or ":for" */
-# define CSL_HAD_ENDLOOP 2 /* just found ":endwhile" or ":endfor" */
-# define CSL_HAD_CONT 4 /* just found ":continue" */
-# define CSL_HAD_FINA 8 /* just found ":finally" */
-
-/*
* A list of error messages that can be converted to an exception. "throw_msg"
* is only set in the first element of the list. Usually, it points to the
* original message stored in that element, but sometimes it points to a later
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 9e2671ca5e..8065d764b9 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -878,7 +878,9 @@ static int command_line_execute(VimState *state, int key)
}
if (s->c == cedit_key || s->c == K_CMDWIN) {
- if (ex_normal_busy == 0 && got_int == false) {
+ // TODO(vim): why is ex_normal_busy checked here?
+ if ((s->c == K_CMDWIN || ex_normal_busy == 0)
+ && got_int == false) {
// Open a window to edit the command line (and history).
s->c = open_cmdwin();
s->some_key_typed = true;
@@ -5008,19 +5010,24 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
hashtab_T found_ht;
hash_init(&found_ht);
for (s = path; ; s = e) {
+ e = vim_strchr(s, ENV_SEPCHAR);
+ if (e == NULL) {
+ e = s + STRLEN(s);
+ }
+
if (*s == NUL) {
if (did_curdir) {
break;
}
// Find directories in the current directory, path is empty.
did_curdir = true;
- } else if (*s == '.') {
+ flags |= EW_DIR;
+ } else if (STRNCMP(s, ".", e - s) == 0) {
did_curdir = true;
- }
-
- e = vim_strchr(s, ENV_SEPCHAR);
- if (e == NULL) {
- e = s + STRLEN(s);
+ flags |= EW_DIR;
+ } else {
+ // Do not match directories inside a $PATH item.
+ flags &= ~EW_DIR;
}
l = (size_t)(e - s);
@@ -6073,12 +6080,9 @@ static int open_cmdwin(void)
set_bufref(&old_curbuf, curbuf);
- /* Save current window sizes. */
+ // Save current window sizes.
win_size_save(&winsizes);
- /* Don't execute autocommands while creating the window. */
- block_autocmds();
-
// When using completion in Insert mode with <C-R>=<C-F> one can open the
// command line window, but we don't want the popup menu then.
pum_undisplay(true);
@@ -6087,10 +6091,9 @@ static int open_cmdwin(void)
cmdmod.tab = 0;
cmdmod.noswapfile = 1;
- /* Create a window for the command-line buffer. */
+ // Create a window for the command-line buffer.
if (win_split((int)p_cwh, WSP_BOT) == FAIL) {
beep_flush();
- unblock_autocmds();
return K_IGNORE;
}
cmdwin_type = get_cmdline_type();
@@ -6105,13 +6108,11 @@ static int open_cmdwin(void)
curbuf->b_p_ma = true;
curwin->w_p_fen = false;
- // Do execute autocommands for setting the filetype (load syntax).
- unblock_autocmds();
- // But don't allow switching to another buffer.
+ // Don't allow switching to another buffer.
curbuf_lock++;
- /* Showing the prompt may have set need_wait_return, reset it. */
- need_wait_return = FALSE;
+ // Showing the prompt may have set need_wait_return, reset it.
+ need_wait_return = false;
const int histtype = hist_char2type(cmdwin_type);
if (histtype == HIST_CMD || histtype == HIST_DEBUG) {
@@ -6123,11 +6124,11 @@ static int open_cmdwin(void)
}
curbuf_lock--;
- /* Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin
- * sets 'textwidth' to 78). */
+ // Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin
+ // sets 'textwidth' to 78).
curbuf->b_p_tw = 0;
- /* Fill the buffer with the history. */
+ // Fill the buffer with the history.
init_history();
if (hislen > 0 && histtype != HIST_INVALID) {
i = hisidx[histtype];
@@ -6168,9 +6169,10 @@ static int open_cmdwin(void)
// Trigger CmdwinEnter autocommands.
typestr[0] = (char_u)cmdwin_type;
typestr[1] = NUL;
- apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, FALSE, curbuf);
- if (restart_edit != 0) /* autocmd with ":startinsert" */
+ apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, false, curbuf);
+ if (restart_edit != 0) { // autocmd with ":startinsert"
stuffcharReadbuff(K_NOP);
+ }
i = RedrawingDisabled;
RedrawingDisabled = 0;
@@ -6187,10 +6189,10 @@ static int open_cmdwin(void)
const bool save_KeyTyped = KeyTyped;
- /* Trigger CmdwinLeave autocommands. */
- apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, FALSE, curbuf);
+ // Trigger CmdwinLeave autocommands.
+ apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, false, curbuf);
- /* Restore KeyTyped in case it is modified by autocommands */
+ // Restore KeyTyped in case it is modified by autocommands
KeyTyped = save_KeyTyped;
// Restore the command line info.
@@ -6249,9 +6251,7 @@ static int open_cmdwin(void)
}
}
- /* Don't execute autocommands while deleting the window. */
- block_autocmds();
- // Avoid command-line window first character being concealed
+ // Avoid command-line window first character being concealed.
curwin->w_p_cole = 0;
wp = curwin;
set_bufref(&bufref, curbuf);
@@ -6264,10 +6264,8 @@ static int open_cmdwin(void)
close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false);
}
- /* Restore window sizes. */
+ // Restore window sizes.
win_size_restore(&winsizes);
-
- unblock_autocmds();
}
ga_clear(&winsizes);
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 58e6b2ae92..865da25009 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -2650,6 +2650,7 @@ buf_write(
*/
if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) {
FileInfo file_info;
+ const bool no_prepend_dot = false;
if ((bkc & BKC_YES) || append) { /* "yes" */
backup_copy = TRUE;
@@ -2737,6 +2738,7 @@ buf_write(
int some_error = false;
char_u *dirp;
char_u *rootname;
+ char_u *p;
/*
* Try to make the backup in each directory in the 'bdir' option.
@@ -2756,6 +2758,17 @@ buf_write(
* Isolate one directory name, using an entry in 'bdir'.
*/
(void)copy_option_part(&dirp, IObuff, IOSIZE, ",");
+ p = IObuff + STRLEN(IObuff);
+ if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) {
+ // Ends with '//', Use Full path
+ if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname))
+ != NULL) {
+ backup = (char_u *)modname((char *)p, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(p);
+ }
+ }
+
rootname = get_file_in_dir(fname, IObuff);
if (rootname == NULL) {
some_error = TRUE; /* out of memory */
@@ -2764,10 +2777,14 @@ buf_write(
FileInfo file_info_new;
{
- /*
- * Make backup file name.
- */
- backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE);
+ //
+ // Make the backup file name.
+ //
+ if (backup == NULL) {
+ backup = (char_u *)modname((char *)rootname, (char *)backup_ext,
+ no_prepend_dot);
+ }
+
if (backup == NULL) {
xfree(rootname);
some_error = TRUE; /* out of memory */
@@ -2893,12 +2910,26 @@ nobackup:
* Isolate one directory name and make the backup file name.
*/
(void)copy_option_part(&dirp, IObuff, IOSIZE, ",");
- rootname = get_file_in_dir(fname, IObuff);
- if (rootname == NULL)
- backup = NULL;
- else {
- backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE);
- xfree(rootname);
+ p = IObuff + STRLEN(IObuff);
+ if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) {
+ // path ends with '//', use full path
+ if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname))
+ != NULL) {
+ backup = (char_u *)modname((char *)p, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(p);
+ }
+ }
+
+ if (backup == NULL) {
+ rootname = get_file_in_dir(fname, IObuff);
+ if (rootname == NULL) {
+ backup = NULL;
+ } else {
+ backup = (char_u *)modname((char *)rootname, (char *)backup_ext,
+ no_prepend_dot);
+ xfree(rootname);
+ }
}
if (backup != NULL) {
@@ -3700,8 +3731,9 @@ static int set_rw_fname(char_u *fname, char_u *sfname)
return FAIL;
}
- if (setfname(curbuf, fname, sfname, FALSE) == OK)
+ if (setfname(curbuf, fname, sfname, false) == OK) {
curbuf->b_flags |= BF_NOTEDITED;
+ }
/* ....and a new named one is created */
apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, curbuf);
@@ -5328,7 +5360,7 @@ static bool vim_settempdir(char *tempdir)
char_u *vim_tempname(void)
{
// Temp filename counter.
- static uint32_t temp_count;
+ static uint64_t temp_count;
char_u *tempdir = vim_gettempdir();
if (!tempdir) {
@@ -5339,7 +5371,7 @@ char_u *vim_tempname(void)
// and nobody else creates a file in it.
char_u template[TEMP_FILE_PATH_MAXLEN];
snprintf((char *)template, TEMP_FILE_PATH_MAXLEN,
- "%s%" PRIu32, tempdir, temp_count++);
+ "%s%" PRIu64, tempdir, temp_count++);
return vim_strsave(template);
}
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 5ce953e626..b193b4005c 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -771,6 +771,11 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
return;
}
+ if (need_diff_redraw) {
+ // will update later
+ return;
+ }
+
// Mark all folds from top to bot as maybe-small.
fold_T *fp;
(void)foldFind(&wp->w_folds, top, &fp);
diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua
index 1702add2e4..a7dad50d48 100644
--- a/src/nvim/generators/gen_char_blob.lua
+++ b/src/nvim/generators/gen_char_blob.lua
@@ -1,49 +1,59 @@
if arg[1] == '--help' then
print('Usage:')
- print(' gencharblob.lua source target varname')
+ print(' '..arg[0]..' target source varname [source varname]...')
print('')
print('Generates C file with big uint8_t blob.')
print('Blob will be stored in a static const array named varname.')
os.exit()
end
-assert(#arg == 3)
+assert(#arg >= 3 and (#arg - 1) % 2 == 0)
-local source_file = arg[1]
-local target_file = arg[2]
-local varname = arg[3]
-
-local source = io.open(source_file, 'r')
+local target_file = arg[1] or error('Need a target file')
local target = io.open(target_file, 'w')
target:write('#include <stdint.h>\n\n')
-target:write(('static const uint8_t %s[] = {\n'):format(varname))
-
-local num_bytes = 0
-local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
-target:write(' ')
-
-local increase_num_bytes
-increase_num_bytes = function()
- num_bytes = num_bytes + 1
- if num_bytes == MAX_NUM_BYTES then
- num_bytes = 0
- target:write('\n ')
+
+local varnames = {}
+for argi = 2, #arg, 2 do
+ local source_file = arg[argi]
+ local varname = arg[argi + 1]
+ if varnames[varname] then
+ error(string.format("varname %q is already specified for file %q", varname, varnames[varname]))
end
-end
+ varnames[varname] = source_file
+
+ local source = io.open(source_file, 'r')
+ or error(string.format("source_file %q doesn't exist", source_file))
+
+ target:write(('static const uint8_t %s[] = {\n'):format(varname))
-for line in source:lines() do
- for i = 1,string.len(line) do
- local byte = string.byte(line, i)
- assert(byte ~= 0)
- target:write(string.format(' %3u,', byte))
+ local num_bytes = 0
+ local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
+ target:write(' ')
+
+ local increase_num_bytes
+ increase_num_bytes = function()
+ num_bytes = num_bytes + 1
+ if num_bytes == MAX_NUM_BYTES then
+ num_bytes = 0
+ target:write('\n ')
+ end
+ end
+
+ for line in source:lines() do
+ for i = 1, string.len(line) do
+ local byte = line:byte(i)
+ assert(byte ~= 0)
+ target:write(string.format(' %3u,', byte))
+ increase_num_bytes()
+ end
+ target:write(string.format(' %3u,', string.byte('\n', 1)))
increase_num_bytes()
end
- target:write(string.format(' %3u,', string.byte('\n', 1)))
- increase_num_bytes()
-end
-target:write(' 0};\n')
+ target:write(' 0};\n')
+ source:close()
+end
-source:close()
target:close()
diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua
index ad44613f42..0782c8115d 100755
--- a/src/nvim/generators/gen_declarations.lua
+++ b/src/nvim/generators/gen_declarations.lua
@@ -207,9 +207,7 @@ preproc_f:close()
local header = [[
-#ifndef DEFINE_FUNC_ATTRIBUTES
-# define DEFINE_FUNC_ATTRIBUTES
-#endif
+#define DEFINE_FUNC_ATTRIBUTES
#include "nvim/func_attr.h"
#undef DEFINE_FUNC_ATTRIBUTES
]]
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 399f0671b4..c038977127 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -2409,7 +2409,6 @@ int inchar(
did_outofmem_msg = FALSE; /* display out of memory message (again) */
did_swapwrite_msg = FALSE; /* display swap file write error again */
}
- undo_off = FALSE; /* restart undo now */
// Get a character from a script file if there is one.
// If interrupted: Stop reading script files, close them all.
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index c3d1a4d40b..172c190df2 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -331,6 +331,7 @@ EXTERN int garbage_collect_at_exit INIT(= false);
#define SID_NONE -6 // don't set scriptID
#define SID_LUA -7 // for Lua scripts/chunks
#define SID_API_CLIENT -8 // for API clients
+#define SID_STR -9 // for sourcing a string
// Script CTX being sourced or was sourced to define the current function.
EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 });
@@ -400,11 +401,6 @@ EXTERN bool mouse_past_eol INIT(= false); /* mouse right of line */
EXTERN int mouse_dragging INIT(= 0); /* extending Visual area with
mouse dragging */
-/* Value set from 'diffopt'. */
-EXTERN int diff_context INIT(= 6); /* context for folds */
-EXTERN int diff_foldcolumn INIT(= 2); /* 'foldcolumn' for diff mode */
-EXTERN int diff_need_scrollbind INIT(= FALSE);
-
/* The root of the menu hierarchy. */
EXTERN vimmenu_T *root_menu INIT(= NULL);
/*
@@ -768,7 +764,6 @@ EXTERN int did_outofmem_msg INIT(= false);
// set after out of memory msg
EXTERN int did_swapwrite_msg INIT(= false);
// set after swap write error msg
-EXTERN int undo_off INIT(= false); // undo switched off for now
EXTERN int global_busy INIT(= 0); // set when :global is executing
EXTERN int listcmd_busy INIT(= false); // set when :argdo, :windo or
// :bufdo is executing
diff --git a/src/nvim/lib/kbtree.h b/src/nvim/lib/kbtree.h
index 33aeff1d89..bef37f8ba9 100644
--- a/src/nvim/lib/kbtree.h
+++ b/src/nvim/lib/kbtree.h
@@ -25,6 +25,12 @@
* SUCH DAMAGE.
*/
+// Gotchas
+// -------
+//
+// if you delete from a kbtree while iterating over it you must use
+// kb_del_itr and not kb_del otherwise the iterator might point to freed memory.
+
#ifndef NVIM_LIB_KBTREE_H
#define NVIM_LIB_KBTREE_H
diff --git a/src/nvim/log.c b/src/nvim/log.c
index db36611933..225e40cdb4 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -93,6 +93,7 @@ static bool log_path_init(void)
void log_init(void)
{
uv_mutex_init(&mutex);
+ log_path_init();
}
void log_lock(void)
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 844232c64a..09d1a68898 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -377,6 +377,19 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
nlua_pop_typval_table_processing_end:
break;
}
+ case LUA_TUSERDATA: {
+ nlua_pushref(lstate, nlua_nil_ref);
+ bool is_nil = lua_rawequal(lstate, -2, -1);
+ lua_pop(lstate, 1);
+ if (is_nil) {
+ cur.tv->v_type = VAR_SPECIAL;
+ cur.tv->vval.v_special = kSpecialVarNull;
+ } else {
+ EMSG(_("E5101: Cannot convert given lua type"));
+ ret = false;
+ }
+ break;
+ }
default: {
EMSG(_("E5101: Cannot convert given lua type"));
ret = false;
@@ -406,7 +419,13 @@ static bool typval_conv_special = false;
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
#define TYPVAL_ENCODE_CONV_NIL(tv) \
- lua_pushnil(lstate)
+ do { \
+ if (typval_conv_special) { \
+ lua_pushnil(lstate); \
+ } else { \
+ nlua_pushref(lstate, nlua_nil_ref); \
+ } \
+ } while (0)
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
lua_pushboolean(lstate, (bool)(num))
@@ -718,7 +737,11 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
{
switch (obj.type) {
case kObjectTypeNil: {
- lua_pushnil(lstate);
+ if (special) {
+ lua_pushnil(lstate);
+ } else {
+ nlua_pushref(lstate, nlua_nil_ref);
+ }
break;
}
case kObjectTypeLuaRef: {
@@ -1152,6 +1175,19 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
break;
}
+ case LUA_TUSERDATA: {
+ nlua_pushref(lstate, nlua_nil_ref);
+ bool is_nil = lua_rawequal(lstate, -2, -1);
+ lua_pop(lstate, 1);
+ if (is_nil) {
+ *cur.obj = NIL;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Cannot convert userdata");
+ }
+ break;
+ }
+
default: {
type_error:
api_set_error(err, kErrorTypeValidation,
@@ -1189,7 +1225,7 @@ GENERATE_INDEX_FUNCTION(Tabpage)
#undef GENERATE_INDEX_FUNCTION
-/// Record some auxilary values in vim module
+/// Record some auxiliary values in vim module
///
/// Assumes that module table is on top of the stack.
///
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index c7ff163f83..25f4be1c4d 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -12,6 +12,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
+#include "nvim/msgpack_rpc/channel.h"
#include "nvim/vim.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_cmds2.h"
@@ -47,9 +48,6 @@ typedef struct {
# include "lua/executor.c.generated.h"
#endif
-/// Name of the run code for use in messages
-#define NLUA_EVAL_NAME "<VimL compiled string>"
-
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
@@ -270,12 +268,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
#endif
// vim
- const char *code = (char *)&vim_module[0];
- if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
- || lua_pcall(lstate, 0, LUA_MULTRET, 0)) {
- nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
- return 1;
- }
+ lua_newtable(lstate);
// vim.api
nlua_add_api_functions(lstate);
// vim.types, vim.type_idx, vim.val_idx
@@ -299,6 +292,14 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, &nlua_call);
lua_setfield(lstate, -2, "call");
+ // rpcrequest
+ lua_pushcfunction(lstate, &nlua_rpcrequest);
+ lua_setfield(lstate, -2, "rpcrequest");
+
+ // rpcnotify
+ lua_pushcfunction(lstate, &nlua_rpcnotify);
+ lua_setfield(lstate, -2, "rpcnotify");
+
// vim.loop
luv_set_loop(lstate, &main_loop.uv);
luv_set_callback(lstate, nlua_luv_cfpcall);
@@ -314,11 +315,38 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setfield(lstate, -2, "luv");
lua_pop(lstate, 3);
+ // vim.NIL
+ lua_newuserdata(lstate, 0);
+ lua_createtable(lstate, 0, 0);
+ lua_pushcfunction(lstate, &nlua_nil_tostring);
+ lua_setfield(lstate, -2, "__tostring");
+ lua_setmetatable(lstate, -2);
+ nlua_nil_ref = nlua_ref(lstate, -1);
+ lua_setfield(lstate, -2, "NIL");
+
// internal vim._treesitter... API
nlua_add_treesitter(lstate);
lua_setglobal(lstate, "vim");
+ {
+ const char *code = (char *)&shared_module[0];
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua")
+ || lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating shared module: %.*s"));
+ return 1;
+ }
+ }
+
+ {
+ const char *code = (char *)&vim_module[0];
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
+ || lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
+ return 1;
+ }
+ }
+
return 0;
}
@@ -379,29 +407,6 @@ static lua_State *nlua_enter(void)
return lstate;
}
-/// Execute lua string
-///
-/// @param[in] str String to execute.
-/// @param[out] ret_tv Location where result will be saved.
-///
-/// @return Result of the execution.
-void executor_exec_lua(const String str, typval_T *const ret_tv)
- FUNC_ATTR_NONNULL_ALL
-{
- lua_State *const lstate = nlua_enter();
-
- if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) {
- nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s"));
- return;
- }
- if (lua_pcall(lstate, 0, 1, 0)) {
- nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s"));
- return;
- }
-
- nlua_pop_typval(lstate, ret_tv);
-}
-
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@@ -547,6 +552,10 @@ int nlua_call(lua_State *lstate)
Error err = ERROR_INIT;
size_t name_len;
const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len);
+ if (!nlua_is_deferred_safe(lstate)) {
+ return luaL_error(lstate, e_luv_api_disabled, "vimL function");
+ }
+
int nargs = lua_gettop(lstate)-1;
if (nargs > MAX_FUNC_ARGS) {
return luaL_error(lstate, "Function called with too many arguments");
@@ -596,6 +605,67 @@ free_vim_args:
return 1;
}
+static int nlua_rpcrequest(lua_State *lstate)
+{
+ if (!nlua_is_deferred_safe(lstate)) {
+ return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
+ }
+ return nlua_rpc(lstate, true);
+}
+
+static int nlua_rpcnotify(lua_State *lstate)
+{
+ return nlua_rpc(lstate, false);
+}
+
+static int nlua_rpc(lua_State *lstate, bool request)
+{
+ size_t name_len;
+ uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1);
+ const char *name = luaL_checklstring(lstate, 2, &name_len);
+ int nargs = lua_gettop(lstate)-2;
+ Error err = ERROR_INIT;
+ Array args = ARRAY_DICT_INIT;
+
+ for (int i = 0; i < nargs; i++) {
+ lua_pushvalue(lstate, (int)i+3);
+ ADD(args, nlua_pop_Object(lstate, false, &err));
+ if (ERROR_SET(&err)) {
+ api_free_array(args);
+ goto check_err;
+ }
+ }
+
+ if (request) {
+ Object result = rpc_send_call(chan_id, name, args, &err);
+ if (!ERROR_SET(&err)) {
+ nlua_push_Object(lstate, result, false);
+ api_free_object(result);
+ }
+ } else {
+ if (!rpc_send_event(chan_id, name, args)) {
+ api_set_error(&err, kErrorTypeValidation,
+ "Invalid channel: %"PRIu64, chan_id);
+ }
+ }
+
+check_err:
+ if (ERROR_SET(&err)) {
+ lua_pushstring(lstate, err.msg);
+ api_clear_error(&err);
+ return lua_error(lstate);
+ }
+
+ return request ? 1 : 0;
+}
+
+static int nlua_nil_tostring(lua_State *lstate)
+{
+ lua_pushstring(lstate, "vim.NIL");
+ return 1;
+}
+
+
#ifdef WIN32
/// os.getenv: override os.getenv to maintain coherency. #9681
///
@@ -649,10 +719,6 @@ void executor_eval_lua(const String str, typval_T *const arg,
typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
- lua_State *const lstate = nlua_enter();
-
- garray_T str_ga;
- ga_init(&str_ga, 1, 80);
#define EVALHEADER "local _A=select(1,...) return ("
const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1;
char *lcmd;
@@ -665,35 +731,76 @@ void executor_eval_lua(const String str, typval_T *const arg,
memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size);
lcmd[lcmd_len - 1] = ')';
#undef EVALHEADER
- if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
- nlua_error(lstate,
- _("E5107: Error while creating lua chunk for luaeval(): %.*s"));
- if (lcmd != (char *)IObuff) {
- xfree(lcmd);
- }
- return;
- }
+ typval_exec_lua(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv);
+
if (lcmd != (char *)IObuff) {
xfree(lcmd);
}
+}
- if (arg->v_type == VAR_UNKNOWN) {
- lua_pushnil(lstate);
+void executor_call_lua(const char *str, size_t len, typval_T *const args,
+ int argcount, typval_T *ret_tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+#define CALLHEADER "return "
+#define CALLSUFFIX "(...)"
+ const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1;
+ char *lcmd;
+ if (lcmd_len < IOSIZE) {
+ lcmd = (char *)IObuff;
} else {
- nlua_push_typval(lstate, arg, true);
+ lcmd = xmalloc(lcmd_len);
+ }
+ memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1);
+ memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len);
+ memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX,
+ sizeof(CALLSUFFIX) - 1);
+#undef CALLHEADER
+#undef CALLSUFFIX
+
+ typval_exec_lua(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv);
+
+ if (lcmd != (char *)IObuff) {
+ xfree(lcmd);
}
- if (lua_pcall(lstate, 1, 1, 0)) {
- nlua_error(lstate,
- _("E5108: Error while calling lua chunk for luaeval(): %.*s"));
+}
+
+static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
+ typval_T *const args, int argcount, bool special,
+ typval_T *ret_tv)
+{
+ if (check_restricted() || check_secure()) {
+ ret_tv->v_type = VAR_NUMBER;
+ ret_tv->vval.v_number = 0;
return;
}
- nlua_pop_typval(lstate, ret_tv);
+ lua_State *const lstate = nlua_enter();
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) {
+ nlua_error(lstate, _("E5107: Error loading lua %.*s"));
+ return;
+ }
+
+ for (int i = 0; i < argcount; i++) {
+ if (args[i].v_type == VAR_UNKNOWN) {
+ lua_pushnil(lstate);
+ } else {
+ nlua_push_typval(lstate, &args[i], special);
+ }
+ }
+ if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) {
+ nlua_error(lstate, _("E5108: Error executing lua %.*s"));
+ return;
+ }
+
+ if (ret_tv) {
+ nlua_pop_typval(lstate, ret_tv);
+ }
}
-/// Execute lua string
+/// Execute Lua string
///
-/// Used for nvim_execute_lua().
+/// Used for nvim_exec_lua().
///
/// @param[in] str String to execute.
/// @param[in] args array of ... args
@@ -774,9 +881,8 @@ void ex_lua(exarg_T *const eap)
xfree(code);
return;
}
- typval_T tv = { .v_type = VAR_UNKNOWN };
- executor_exec_lua((String) { .data = code, .size = len }, &tv);
- tv_clear(&tv);
+ typval_exec_lua(code, len, ":lua", NULL, 0, false, NULL);
+
xfree(code);
}
@@ -814,8 +920,8 @@ void ex_luado(exarg_T *const eap)
#undef DOSTART
#undef DOEND
- if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) {
- nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s"));
+ if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) {
+ nlua_error(lstate, _("E5109: Error loading lua: %.*s"));
if (lcmd_len >= IOSIZE) {
xfree(lcmd);
}
@@ -825,7 +931,7 @@ void ex_luado(exarg_T *const eap)
xfree(lcmd);
}
if (lua_pcall(lstate, 0, 1, 0)) {
- nlua_error(lstate, _("E5110: Error while creating lua function: %.*s"));
+ nlua_error(lstate, _("E5110: Error executing lua: %.*s"));
return;
}
for (linenr_T l = eap->line1; l <= eap->line2; l++) {
@@ -836,7 +942,7 @@ void ex_luado(exarg_T *const eap)
lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false));
lua_pushnumber(lstate, (lua_Number)l);
if (lua_pcall(lstate, 2, 1, 0)) {
- nlua_error(lstate, _("E5111: Error while calling lua function: %.*s"));
+ nlua_error(lstate, _("E5111: Error calling lua: %.*s"));
break;
}
if (lua_isstring(lstate, -1)) {
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 8d356a5600..32f66b629c 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -12,6 +12,8 @@
// Generated by msgpack-gen.lua
void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL;
+EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF);
+
#define set_api_error(s, err) \
do { \
Error *err_ = (err); \
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index adb90084db..e7c5458102 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -33,35 +33,35 @@
-- - https://github.com/bakpakin/Fennel (pretty print, repl)
-- - https://github.com/howl-editor/howl/tree/master/lib/howl/util
+local vim = vim
+assert(vim)
-- Internal-only until comments in #8107 are addressed.
-- Returns:
-- {errcode}, {output}
-local function _system(cmd)
- local out = vim.api.nvim_call_function('system', { cmd })
- local err = vim.api.nvim_get_vvar('shell_error')
+function vim._system(cmd)
+ local out = vim.fn.system(cmd)
+ local err = vim.v.shell_error
return err, out
end
-- Gets process info from the `ps` command.
-- Used by nvim_get_proc() as a fallback.
-local function _os_proc_info(pid)
+function vim._os_proc_info(pid)
if pid == nil or pid <= 0 or type(pid) ~= 'number' then
error('invalid pid')
end
local cmd = { 'ps', '-p', pid, '-o', 'comm=', }
- local err, name = _system(cmd)
- if 1 == err and string.gsub(name, '%s*', '') == '' then
+ local err, name = vim._system(cmd)
+ if 1 == err and vim.trim(name) == '' then
return {} -- Process not found.
elseif 0 ~= err then
- local args_str = vim.api.nvim_call_function('string', { cmd })
- error('command failed: '..args_str)
+ error('command failed: '..vim.fn.string(cmd))
end
- local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', })
+ local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', })
-- Remove trailing whitespace.
- name = string.gsub(string.gsub(name, '%s+$', ''), '^.*/', '')
- ppid = string.gsub(ppid, '%s+$', '')
- ppid = tonumber(ppid) == nil and -1 or tonumber(ppid)
+ name = vim.trim(name):gsub('^.*/', '')
+ ppid = tonumber(ppid) or -1
return {
name = name,
pid = pid,
@@ -71,20 +71,19 @@ end
-- Gets process children from the `pgrep` command.
-- Used by nvim_get_proc_children() as a fallback.
-local function _os_proc_children(ppid)
+function vim._os_proc_children(ppid)
if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then
error('invalid ppid')
end
local cmd = { 'pgrep', '-P', ppid, }
- local err, rv = _system(cmd)
- if 1 == err and string.gsub(rv, '%s*', '') == '' then
+ local err, rv = vim._system(cmd)
+ if 1 == err and vim.trim(rv) == '' then
return {} -- Process not found.
elseif 0 ~= err then
- local args_str = vim.api.nvim_call_function('string', { cmd })
- error('command failed: '..args_str)
+ error('command failed: '..vim.fn.string(cmd))
end
local children = {}
- for s in string.gmatch(rv, '%S+') do
+ for s in rv:gmatch('%S+') do
local i = tonumber(s)
if i ~= nil then
table.insert(children, i)
@@ -98,7 +97,7 @@ end
-- Last inserted paths. Used to clear out items from package.[c]path when they
-- are no longer in &runtimepath.
local last_nvim_paths = {}
-local function _update_package_paths()
+function vim._update_package_paths()
local cur_nvim_paths = {}
local rtps = vim.api.nvim_list_runtime_paths()
local sep = package.config:sub(1, 1)
@@ -162,36 +161,35 @@ local function inspect(object, options) -- luacheck: no unused
error(object, options) -- Stub for gen_vimdoc.py
end
---- Paste handler, invoked by |nvim_paste()| when a conforming UI
---- (such as the |TUI|) pastes text into the editor.
----
---- Example: To remove ANSI color codes when pasting:
---- <pre>
---- vim.paste = (function()
---- local overridden = vim.paste
---- return function(lines, phase)
---- for i,line in ipairs(lines) do
---- -- Scrub ANSI color codes from paste input.
---- lines[i] = line:gsub('\27%[[0-9;mK]+', '')
---- end
---- overridden(lines, phase)
---- end
---- end)()
---- </pre>
----
---@see |paste|
----
---@param lines |readfile()|-style list of lines to paste. |channel-lines|
---@param phase -1: "non-streaming" paste: the call contains all lines.
---- If paste is "streamed", `phase` indicates the stream state:
---- - 1: starts the paste (exactly once)
---- - 2: continues the paste (zero or more times)
---- - 3: ends the paste (exactly once)
---@returns false if client should cancel the paste.
-local function paste(lines, phase) end -- luacheck: no unused
-paste = (function()
+do
local tdots, tick, got_line1 = 0, 0, false
- return function(lines, phase)
+
+ --- Paste handler, invoked by |nvim_paste()| when a conforming UI
+ --- (such as the |TUI|) pastes text into the editor.
+ ---
+ --- Example: To remove ANSI color codes when pasting:
+ --- <pre>
+ --- vim.paste = (function(overridden)
+ --- return function(lines, phase)
+ --- for i,line in ipairs(lines) do
+ --- -- Scrub ANSI color codes from paste input.
+ --- lines[i] = line:gsub('\27%[[0-9;mK]+', '')
+ --- end
+ --- overridden(lines, phase)
+ --- end
+ --- end)(vim.paste)
+ --- </pre>
+ ---
+ --@see |paste|
+ ---
+ --@param lines |readfile()|-style list of lines to paste. |channel-lines|
+ --@param phase -1: "non-streaming" paste: the call contains all lines.
+ --- If paste is "streamed", `phase` indicates the stream state:
+ --- - 1: starts the paste (exactly once)
+ --- - 2: continues the paste (zero or more times)
+ --- - 3: ends the paste (exactly once)
+ --@returns false if client should cancel the paste.
+ function vim.paste(lines, phase)
local call = vim.api.nvim_call_function
local now = vim.loop.now()
local mode = call('mode', {}):sub(1,1)
@@ -206,8 +204,11 @@ paste = (function()
local line1 = lines[1]:gsub('<', '<lt>'):gsub('[\r\n\012\027]', ' ') -- Scrub.
vim.api.nvim_input(line1)
vim.api.nvim_set_option('paste', false)
- elseif mode ~= 'c' then -- Else: discard remaining cmdline-mode chunks.
- if phase < 2 and mode ~= 'i' and mode ~= 'R' and mode ~= 't' then
+ elseif mode ~= 'c' then
+ if phase < 2 and mode:find('^[vV\22sS\19]') then
+ vim.api.nvim_command([[exe "normal! \<Del>"]])
+ vim.api.nvim_put(lines, 'c', false, true)
+ elseif phase < 2 and not mode:find('^[iRt]') then
vim.api.nvim_put(lines, 'c', true, true)
-- XXX: Normal-mode: workaround bad cursor-placement after first chunk.
vim.api.nvim_command('normal! a')
@@ -228,20 +229,33 @@ paste = (function()
end
return true -- Paste will not continue if not returning `true`.
end
-end)()
+end
--- Defers callback `cb` until the Nvim API is safe to call.
---
---@see |lua-loop-callbacks|
---@see |vim.schedule()|
---@see |vim.in_fast_event()|
-local function schedule_wrap(cb)
+function vim.schedule_wrap(cb)
return (function (...)
local args = {...}
vim.schedule(function() cb(unpack(args)) end)
end)
end
+-- vim.fn.{func}(...)
+vim.fn = setmetatable({}, {
+ __index = function(t, key)
+ local function _fn(...)
+ return vim.call(key, ...)
+ end
+ t[key] = _fn
+ return _fn
+ end
+})
+
+-- These are for loading runtime modules lazily since they aren't available in
+-- the nvim binary as specified in executor.c
local function __index(t, key)
if key == 'inspect' then
t.inspect = require('vim.inspect')
@@ -249,36 +263,123 @@ local function __index(t, key)
elseif key == 'treesitter' then
t.treesitter = require('vim.treesitter')
return t.treesitter
- elseif require('vim.shared')[key] ~= nil then
- -- Expose all `vim.shared` functions on the `vim` module.
- t[key] = require('vim.shared')[key]
+ elseif require('vim.uri')[key] ~= nil then
+ -- Expose all `vim.uri` functions on the `vim` module.
+ t[key] = require('vim.uri')[key]
return t[key]
+ elseif key == 'lsp' then
+ t.lsp = require('vim.lsp')
+ return t.lsp
end
end
+setmetatable(vim, {
+ __index = __index
+})
--- vim.fn.{func}(...)
-local function fn_index(t, key)
- local function func(...)
- return vim.call(key, ...)
+-- An easier alias for commands.
+vim.cmd = vim.api.nvim_command
+
+-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
+do
+ local a = vim.api
+ local validate = vim.validate
+ local function make_meta_accessor(get, set, del)
+ validate {
+ get = {get, 'f'};
+ set = {set, 'f'};
+ del = {del, 'f', true};
+ }
+ local mt = {}
+ if del then
+ function mt:__newindex(k, v)
+ if v == nil then
+ return del(k)
+ end
+ return set(k, v)
+ end
+ else
+ function mt:__newindex(k, v)
+ return set(k, v)
+ end
+ end
+ function mt:__index(k)
+ return get(k)
+ end
+ return setmetatable({}, mt)
+ end
+ local function pcall_ret(status, ...)
+ if status then return ... end
+ end
+ local function nil_wrap(fn)
+ return function(...)
+ return pcall_ret(pcall(fn, ...))
+ end
+ end
+ vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var)
+ vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar)
+ vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
+ local function getenv(k)
+ local v = vim.fn.getenv(k)
+ if v == vim.NIL then
+ return nil
+ end
+ return v
+ end
+ vim.env = make_meta_accessor(getenv, vim.fn.setenv)
+ -- TODO(ashkan) if/when these are available from an API, generate them
+ -- instead of hardcoding.
+ local window_options = {
+ arab = true; arabic = true; breakindent = true; breakindentopt = true;
+ bri = true; briopt = true; cc = true; cocu = true;
+ cole = true; colorcolumn = true; concealcursor = true; conceallevel = true;
+ crb = true; cuc = true; cul = true; cursorbind = true;
+ cursorcolumn = true; cursorline = true; diff = true; fcs = true;
+ fdc = true; fde = true; fdi = true; fdl = true;
+ fdm = true; fdn = true; fdt = true; fen = true;
+ fillchars = true; fml = true; fmr = true; foldcolumn = true;
+ foldenable = true; foldexpr = true; foldignore = true; foldlevel = true;
+ foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true;
+ foldtext = true; lbr = true; lcs = true; linebreak = true;
+ list = true; listchars = true; nu = true; number = true;
+ numberwidth = true; nuw = true; previewwindow = true; pvw = true;
+ relativenumber = true; rightleft = true; rightleftcmd = true; rl = true;
+ rlc = true; rnu = true; scb = true; scl = true;
+ scr = true; scroll = true; scrollbind = true; signcolumn = true;
+ spell = true; statusline = true; stl = true; wfh = true;
+ wfw = true; winbl = true; winblend = true; winfixheight = true;
+ winfixwidth = true; winhighlight = true; winhl = true; wrap = true;
+ }
+ local function new_buf_opt_accessor(bufnr)
+ local function get(k)
+ if window_options[k] then
+ return a.nvim_err_writeln(k.." is a window option, not a buffer option")
+ end
+ if bufnr == nil and type(k) == "number" then
+ return new_buf_opt_accessor(k)
+ end
+ return a.nvim_buf_get_option(bufnr or 0, k)
+ end
+ local function set(k, v)
+ if window_options[k] then
+ return a.nvim_err_writeln(k.." is a window option, not a buffer option")
+ end
+ return a.nvim_buf_set_option(bufnr or 0, k, v)
+ end
+ return make_meta_accessor(get, set)
+ end
+ vim.bo = new_buf_opt_accessor(nil)
+ local function new_win_opt_accessor(winnr)
+ local function get(k)
+ if winnr == nil and type(k) == "number" then
+ return new_win_opt_accessor(k)
+ end
+ return a.nvim_win_get_option(winnr or 0, k)
+ end
+ local function set(k, v) return a.nvim_win_set_option(winnr or 0, k, v) end
+ return make_meta_accessor(get, set)
end
- t[key] = func
- return func
+ vim.wo = new_win_opt_accessor(nil)
end
-local fn = setmetatable({}, {__index=fn_index})
-
-local module = {
- _update_package_paths = _update_package_paths,
- _os_proc_children = _os_proc_children,
- _os_proc_info = _os_proc_info,
- _system = _system,
- paste = paste,
- schedule_wrap = schedule_wrap,
- fn=fn,
-}
-
-setmetatable(module, {
- __index = __index
-})
return module
diff --git a/src/nvim/main.c b/src/nvim/main.c
index e0a1e60fc0..c7011f4f4e 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -27,6 +27,7 @@
#include "nvim/highlight.h"
#include "nvim/iconv.h"
#include "nvim/if_cscope.h"
+#include "nvim/lua/executor.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
@@ -143,7 +144,6 @@ static const char *err_extra_cmd =
void event_init(void)
{
- log_init();
loop_init(&main_loop, NULL);
resize_events = multiqueue_new_child(main_loop.events);
@@ -219,6 +219,7 @@ void early_init(void)
// First find out the home directory, needed to expand "~" in options.
init_homedir(); // find real value of $HOME
set_init_1();
+ log_init();
TIME_MSG("inits 1");
set_lang_var(); // set v:lang and v:ctype
@@ -1515,7 +1516,7 @@ static void create_windows(mparm_T *parmp)
/* We can't close the window, it would disturb what
* happens next. Clear the file name and set the arg
* index to -1 to delete it later. */
- setfname(curbuf, NULL, NULL, FALSE);
+ setfname(curbuf, NULL, NULL, false);
curwin->w_arg_idx = -1;
swap_exists_action = SEA_NONE;
} else
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index e8f1651a6e..93bc497cf0 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -178,6 +178,16 @@ void setpcmark(void)
curwin->w_pcmark.lnum = 1;
}
+ if (jop_flags & JOP_STACK) {
+ // If we're somewhere in the middle of the jumplist discard everything
+ // after the current index.
+ if (curwin->w_jumplistidx < curwin->w_jumplistlen - 1) {
+ // Discard the rest of the jumplist by cutting the length down to
+ // contain nothing beyond the current index.
+ curwin->w_jumplistlen = curwin->w_jumplistidx + 1;
+ }
+ }
+
/* If jumplist is full: remove oldest entry */
if (++curwin->w_jumplistlen > JUMPLISTSIZE) {
curwin->w_jumplistlen = JUMPLISTSIZE;
@@ -296,17 +306,17 @@ pos_T *movechangelist(int count)
* - NULL if there is no mark called 'c'.
* - -1 if mark is in other file and jumped there (only if changefile is TRUE)
*/
-pos_T *getmark_buf(buf_T *buf, int c, int changefile)
+pos_T *getmark_buf(buf_T *buf, int c, bool changefile)
{
return getmark_buf_fnum(buf, c, changefile, NULL);
}
-pos_T *getmark(int c, int changefile)
+pos_T *getmark(int c, bool changefile)
{
return getmark_buf_fnum(curbuf, c, changefile, NULL);
}
-pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum)
+pos_T *getmark_buf_fnum(buf_T *buf, int c, bool changefile, int *fnum)
{
pos_T *posp;
pos_T *startp, *endp;
@@ -905,9 +915,10 @@ void mark_adjust(linenr_T line1,
linenr_T line2,
long amount,
long amount_after,
- bool end_temp)
+ bool end_temp,
+ ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp);
+ mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp, op);
}
// mark_adjust_nofold() does the same as mark_adjust() but without adjusting
@@ -916,14 +927,16 @@ void mark_adjust(linenr_T line1,
// calling foldMarkAdjust() with arguments line1, line2, amount, amount_after,
// for an example of why this may be necessary, see do_move().
void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount,
- long amount_after, bool end_temp)
+ long amount_after, bool end_temp,
+ ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp);
+ mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp, op);
}
static void mark_adjust_internal(linenr_T line1, linenr_T line2,
long amount, long amount_after,
- bool adjust_folds, bool end_temp)
+ bool adjust_folds, bool end_temp,
+ ExtmarkOp op)
{
int i;
int fnum = curbuf->b_fnum;
@@ -979,6 +992,9 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2,
sign_mark_adjust(line1, line2, amount, amount_after);
bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after, end_temp);
+ if (op != kExtmarkNOOP) {
+ extmark_adjust(curbuf, line1, line2, amount, amount_after, op, end_temp);
+ }
}
/* previous context mark */
@@ -1090,7 +1106,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2,
// cursor is inside them.
void mark_col_adjust(
linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount,
- int spaces_removed)
+ int spaces_removed, ExtmarkOp op)
{
int i;
int fnum = curbuf->b_fnum;
@@ -1110,6 +1126,13 @@ void mark_col_adjust(
col_adjust(&(namedfm[i].fmark.mark));
}
+ // Extmarks
+ if (op != kExtmarkNOOP) {
+ // TODO(timeyyy): consider spaces_removed? (behave like a delete)
+ extmark_col_adjust(curbuf, lnum, mincol, lnum_amount, col_amount,
+ kExtmarkUndo);
+ }
+
/* last Insert position */
col_adjust(&(curbuf->b_last_insert.mark));
@@ -1191,7 +1214,20 @@ void cleanup_jumplist(win_T *wp, bool checktail)
break;
}
}
- if (i >= wp->w_jumplistlen) { // no duplicate
+ bool mustfree;
+ if (i >= wp->w_jumplistlen) { // not duplicate
+ mustfree = false;
+ } else if (i > from + 1) { // non-adjacent duplicate
+ // When the jump options include "stack", duplicates are only removed from
+ // the jumplist when they are adjacent.
+ mustfree = !(jop_flags & JOP_STACK);
+ } else { // adjacent duplicate
+ mustfree = true;
+ }
+
+ if (mustfree) {
+ xfree(wp->w_jumplist[from].fname);
+ } else {
if (to != from) {
// Not using wp->w_jumplist[to++] = wp->w_jumplist[from] because
// this way valgrind complains about overlapping source and destination
@@ -1199,8 +1235,6 @@ void cleanup_jumplist(win_T *wp, bool checktail)
wp->w_jumplist[to] = wp->w_jumplist[from];
}
to++;
- } else {
- xfree(wp->w_jumplist[from].fname);
}
}
if (wp->w_jumplistidx == wp->w_jumplistlen) {
diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c
new file mode 100644
index 0000000000..17776d438a
--- /dev/null
+++ b/src/nvim/mark_extended.c
@@ -0,0 +1,1139 @@
+// 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
+
+// Implements extended marks for plugins. Each mark exists in a btree of
+// lines containing btrees of columns.
+//
+// The btree provides efficient range lookups.
+// A map of pointers to the marks is used for fast lookup by mark id.
+//
+// Marks are moved by calls to: extmark_col_adjust, extmark_adjust, or
+// extmark_col_adjust_delete which are based on col_adjust and mark_adjust from
+// mark.c
+//
+// Undo/Redo of marks is implemented by storing the call arguments to
+// extmark_col_adjust or extmark_adjust. The list of arguments
+// is applied in extmark_apply_undo. The only case where we have to
+// copy extmarks is for the area being effected by a delete.
+//
+// Marks live in namespaces that allow plugins/users to segregate marks
+// from other users.
+//
+// For possible ideas for efficency improvements see:
+// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html
+// TODO(bfredl): These ideas could be used for an enhanced btree, which
+// wouldn't need separate line and column layers.
+// Other implementations exist in gtk and tk toolkits.
+//
+// Deleting marks only happens when explicitly calling extmark_del, deleteing
+// over a range of marks will only move the marks. Deleting on a mark will
+// leave it in same position unless it is on the EOL of a line.
+
+#include <assert.h>
+#include "nvim/vim.h"
+#include "charset.h"
+#include "nvim/mark_extended.h"
+#include "nvim/memline.h"
+#include "nvim/pos.h"
+#include "nvim/globals.h"
+#include "nvim/map.h"
+#include "nvim/lib/kbtree.h"
+#include "nvim/undo.h"
+#include "nvim/buffer.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "mark_extended.c.generated.h"
+#endif
+
+
+/// Create or update an extmark
+///
+/// must not be used during iteration!
+/// @returns whether a new mark was created
+int extmark_set(buf_T *buf, uint64_t ns, uint64_t id,
+ linenr_T lnum, colnr_T col, ExtmarkOp op)
+{
+ Extmark *extmark = extmark_from_id(buf, ns, id);
+ if (!extmark) {
+ extmark_create(buf, ns, id, lnum, col, op);
+ return true;
+ } else {
+ ExtmarkLine *extmarkline = extmark->line;
+ extmark_update(extmark, buf, ns, id, lnum, col, op, NULL);
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del(extmarklines, &buf->b_extlines, extmarkline);
+ extmarkline_free(extmarkline);
+ }
+ return false;
+ }
+}
+
+// Remove an extmark
+// Returns 0 on missing id
+int extmark_del(buf_T *buf, uint64_t ns, uint64_t id, ExtmarkOp op)
+{
+ Extmark *extmark = extmark_from_id(buf, ns, id);
+ if (!extmark) {
+ return 0;
+ }
+ return extmark_delete(extmark, buf, ns, id, op);
+}
+
+// Free extmarks in a ns between lines
+// if ns = 0, it means clear all namespaces
+void extmark_clear(buf_T *buf, uint64_t ns,
+ linenr_T l_lnum, linenr_T u_lnum, ExtmarkOp undo)
+{
+ if (!buf->b_extmark_ns) {
+ return;
+ }
+
+ bool marks_cleared = false;
+ if (undo == kExtmarkUndo) {
+ // Copy marks that would be effected by clear
+ u_extmark_copy(buf, ns, l_lnum, 0, u_lnum, MAXCOL);
+ }
+
+ bool all_ns = ns == 0 ? true : false;
+ ExtmarkNs *ns_obj;
+ if (!all_ns) {
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ if (!ns_obj) {
+ // nothing to do
+ return;
+ }
+ }
+
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, {
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, {
+ if (extmark->ns_id == ns || all_ns) {
+ marks_cleared = true;
+ if (all_ns) {
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, extmark->ns_id);
+ } else {
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ }
+ pmap_del(uint64_t)(ns_obj->map, extmark->mark_id);
+ kb_del_itr(markitems, &extmarkline->items, &mitr);
+ }
+ });
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del_itr(extmarklines, &buf->b_extlines, &itr);
+ extmarkline_free(extmarkline);
+ }
+ });
+
+ // Record the undo for the actual move
+ if (marks_cleared && undo == kExtmarkUndo) {
+ u_extmark_clear(buf, ns, l_lnum, u_lnum);
+ }
+}
+
+// Returns the position of marks between a range,
+// marks found at the start or end index will be included,
+// if upper_lnum or upper_col are negative the buffer
+// will be searched to the start, or end
+// dir can be set to control the order of the array
+// amount = amount of marks to find or -1 for all
+ExtmarkArray extmark_get(buf_T *buf, uint64_t ns,
+ linenr_T l_lnum, colnr_T l_col,
+ linenr_T u_lnum, colnr_T u_col,
+ int64_t amount, bool reverse)
+{
+ ExtmarkArray array = KV_INITIAL_VALUE;
+ // Find all the marks
+ if (!reverse) {
+ FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, {
+ if (extmark->ns_id == ns) {
+ kv_push(array, extmark);
+ if (kv_size(array) == (size_t)amount) {
+ return array;
+ }
+ }
+ })
+ } else {
+ FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, {
+ if (extmark->ns_id == ns) {
+ kv_push(array, extmark);
+ if (kv_size(array) == (size_t)amount) {
+ return array;
+ }
+ }
+ })
+ }
+ return array;
+}
+
+static void extmark_create(buf_T *buf, uint64_t ns, uint64_t id,
+ linenr_T lnum, colnr_T col, ExtmarkOp op)
+{
+ if (!buf->b_extmark_ns) {
+ buf->b_extmark_ns = pmap_new(uint64_t)();
+ }
+ ExtmarkNs *ns_obj = NULL;
+ ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ // Initialize a new namespace for this buffer
+ if (!ns_obj) {
+ ns_obj = xmalloc(sizeof(ExtmarkNs));
+ ns_obj->map = pmap_new(uint64_t)();
+ pmap_put(uint64_t)(buf->b_extmark_ns, ns, ns_obj);
+ }
+
+ // Create or get a line
+ ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, true);
+ // Create and put mark on the line
+ extmark_put(col, id, extmarkline, ns);
+
+ // Marks do not have stable address so we have to look them up
+ // by using the line instead of the mark
+ pmap_put(uint64_t)(ns_obj->map, id, extmarkline);
+ if (op != kExtmarkNoUndo) {
+ u_extmark_set(buf, ns, id, lnum, col, kExtmarkSet);
+ }
+
+ // Set a free id so extmark_free_id_get works
+ extmark_free_id_set(ns_obj, id);
+}
+
+// update the position of an extmark
+// to update while iterating pass the markitems itr
+static void extmark_update(Extmark *extmark, buf_T *buf,
+ uint64_t ns, uint64_t id,
+ linenr_T lnum, colnr_T col,
+ ExtmarkOp op, kbitr_t(markitems) *mitr)
+{
+ assert(op != kExtmarkNOOP);
+ if (op != kExtmarkNoUndo) {
+ u_extmark_update(buf, ns, id, extmark->line->lnum, extmark->col,
+ lnum, col);
+ }
+ ExtmarkLine *old_line = extmark->line;
+ // Move the mark to a new line and update column
+ if (old_line->lnum != lnum) {
+ ExtmarkLine *ref_line = extmarkline_ref(buf, lnum, true);
+ extmark_put(col, id, ref_line, ns);
+ // Update the hashmap
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ pmap_put(uint64_t)(ns_obj->map, id, ref_line);
+ // Delete old mark
+ if (mitr != NULL) {
+ kb_del_itr(markitems, &(old_line->items), mitr);
+ } else {
+ kb_del(markitems, &old_line->items, *extmark);
+ }
+ // Just update the column
+ } else {
+ if (mitr != NULL) {
+ // The btree stays organized during iteration with kbitr_t
+ extmark->col = col;
+ } else {
+ // Keep the btree in order
+ kb_del(markitems, &old_line->items, *extmark);
+ extmark_put(col, id, old_line, ns);
+ }
+ }
+}
+
+static int extmark_delete(Extmark *extmark,
+ buf_T *buf,
+ uint64_t ns,
+ uint64_t id,
+ ExtmarkOp op)
+{
+ if (op != kExtmarkNoUndo) {
+ u_extmark_set(buf, ns, id, extmark->line->lnum, extmark->col,
+ kExtmarkDel);
+ }
+
+ // Remove our key from the namespace
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ pmap_del(uint64_t)(ns_obj->map, id);
+
+ // Remove the mark mark from the line
+ ExtmarkLine *extmarkline = extmark->line;
+ kb_del(markitems, &extmarkline->items, *extmark);
+ // Remove the line if there are no more marks in the line
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del(extmarklines, &buf->b_extlines, extmarkline);
+ extmarkline_free(extmarkline);
+ }
+ return true;
+}
+
+// Lookup an extmark by id
+Extmark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id)
+{
+ if (!buf->b_extmark_ns) {
+ return NULL;
+ }
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ if (!ns_obj || !kh_size(ns_obj->map->table)) {
+ return NULL;
+ }
+ ExtmarkLine *extmarkline = pmap_get(uint64_t)(ns_obj->map, id);
+ if (!extmarkline) {
+ return NULL;
+ }
+
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, {
+ if (extmark->ns_id == ns
+ && extmark->mark_id == id) {
+ return extmark;
+ }
+ })
+ return NULL;
+}
+
+// Lookup an extmark by position
+Extmark *extmark_from_pos(buf_T *buf, uint64_t ns, linenr_T lnum, colnr_T col)
+{
+ if (!buf->b_extmark_ns) {
+ return NULL;
+ }
+ FOR_ALL_EXTMARKS(buf, ns, lnum, col, lnum, col, {
+ if (extmark->ns_id == ns) {
+ if (extmark->col == col) {
+ return extmark;
+ }
+ }
+ })
+ return NULL;
+}
+
+// Returns an available id in a namespace
+uint64_t extmark_free_id_get(buf_T *buf, uint64_t ns)
+{
+ if (!buf->b_extmark_ns) {
+ return 1;
+ }
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns);
+ if (!ns_obj) {
+ return 1;
+ }
+ return ns_obj->free_id;
+}
+
+// Set the next free id in a namesapce
+static void extmark_free_id_set(ExtmarkNs *ns_obj, uint64_t id)
+{
+ // Simply Heurstic, the largest id + 1
+ ns_obj->free_id = id + 1;
+}
+
+// free extmarks from the buffer
+void extmark_free_all(buf_T *buf)
+{
+ if (!buf->b_extmark_ns) {
+ return;
+ }
+
+ uint64_t ns;
+ ExtmarkNs *ns_obj;
+
+ FOR_ALL_EXTMARKLINES(buf, 1, MAXLNUM, {
+ kb_del_itr(extmarklines, &buf->b_extlines, &itr);
+ extmarkline_free(extmarkline);
+ })
+
+ map_foreach(buf->b_extmark_ns, ns, ns_obj, {
+ (void)ns;
+ pmap_free(uint64_t)(ns_obj->map);
+ xfree(ns_obj);
+ });
+
+ pmap_free(uint64_t)(buf->b_extmark_ns);
+ buf->b_extmark_ns = NULL;
+
+ // k?_init called to set pointers to NULL
+ kb_destroy(extmarklines, (&buf->b_extlines));
+ kb_init(&buf->b_extlines);
+
+ kv_destroy(buf->b_extmark_move_space);
+ kv_init(buf->b_extmark_move_space);
+}
+
+
+// Save info for undo/redo of set marks
+static void u_extmark_set(buf_T *buf, uint64_t ns, uint64_t id,
+ linenr_T lnum, colnr_T col, UndoObjectType undo_type)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkSet set;
+ set.ns_id = ns;
+ set.mark_id = id;
+ set.lnum = lnum;
+ set.col = col;
+
+ ExtmarkUndoObject undo = { .type = undo_type,
+ .data.set = set };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Save info for undo/redo of deleted marks
+static void u_extmark_update(buf_T *buf, uint64_t ns, uint64_t id,
+ linenr_T old_lnum, colnr_T old_col,
+ linenr_T lnum, colnr_T col)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkUpdate update;
+ update.ns_id = ns;
+ update.mark_id = id;
+ update.old_lnum = old_lnum;
+ update.old_col = old_col;
+ update.lnum = lnum;
+ update.col = col;
+
+ ExtmarkUndoObject undo = { .type = kExtmarkUpdate,
+ .data.update = update };
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Hueristic works only for when the user is typing in insert mode
+// - Instead of 1 undo object for each char inserted,
+// we create 1 undo objet for all text inserted before the user hits esc
+// Return True if we compacted else False
+static bool u_compact_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol,
+ long lnum_amount, long col_amount)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return false;
+ }
+
+ if (kv_size(uhp->uh_extmark) < 1) {
+ return false;
+ }
+ // Check the last action
+ ExtmarkUndoObject object = kv_last(uhp->uh_extmark);
+
+ if (object.type != kColAdjust) {
+ return false;
+ }
+ ColAdjust undo = object.data.col_adjust;
+ bool compactable = false;
+
+ if (!undo.lnum_amount && !lnum_amount) {
+ if (undo.lnum == lnum) {
+ if ((undo.mincol + undo.col_amount) >= mincol) {
+ compactable = true;
+ } } }
+
+ if (!compactable) {
+ return false;
+ }
+
+ undo.col_amount = undo.col_amount + col_amount;
+ ExtmarkUndoObject new_undo = { .type = kColAdjust,
+ .data.col_adjust = undo };
+ kv_last(uhp->uh_extmark) = new_undo;
+ return true;
+}
+
+// Save col_adjust info so we can undo/redo
+void u_extmark_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol,
+ long lnum_amount, long col_amount)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ if (!u_compact_col_adjust(buf, lnum, mincol, lnum_amount, col_amount)) {
+ ColAdjust col_adjust;
+ col_adjust.lnum = lnum;
+ col_adjust.mincol = mincol;
+ col_adjust.lnum_amount = lnum_amount;
+ col_adjust.col_amount = col_amount;
+
+ ExtmarkUndoObject undo = { .type = kColAdjust,
+ .data.col_adjust = col_adjust };
+
+ kv_push(uhp->uh_extmark, undo);
+ }
+}
+
+// Save col_adjust_delete info so we can undo/redo
+void u_extmark_col_adjust_delete(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, colnr_T endcol, int eol)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ColAdjustDelete col_adjust_delete;
+ col_adjust_delete.lnum = lnum;
+ col_adjust_delete.mincol = mincol;
+ col_adjust_delete.endcol = endcol;
+ col_adjust_delete.eol = eol;
+
+ ExtmarkUndoObject undo = { .type = kColAdjustDelete,
+ .data.col_adjust_delete = col_adjust_delete };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Save adjust info so we can undo/redo
+static void u_extmark_adjust(buf_T * buf, linenr_T line1, linenr_T line2,
+ long amount, long amount_after)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ Adjust adjust;
+ adjust.line1 = line1;
+ adjust.line2 = line2;
+ adjust.amount = amount;
+ adjust.amount_after = amount_after;
+
+ ExtmarkUndoObject undo = { .type = kLineAdjust,
+ .data.adjust = adjust };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// save info to undo/redo a :move
+void u_extmark_move(buf_T *buf, linenr_T line1, linenr_T line2,
+ linenr_T last_line, linenr_T dest, linenr_T num_lines,
+ linenr_T extra)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ AdjustMove move;
+ move.line1 = line1;
+ move.line2 = line2;
+ move.last_line = last_line;
+ move.dest = dest;
+ move.num_lines = num_lines;
+ move.extra = extra;
+
+ ExtmarkUndoObject undo = { .type = kAdjustMove,
+ .data.move = move };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// copy extmarks data between range, useful when we cannot simply reverse
+// the operation. This will do nothing on redo, enforces correct position when
+// undo.
+// if ns = 0, it means copy all namespaces
+void u_extmark_copy(buf_T *buf, uint64_t ns,
+ linenr_T l_lnum, colnr_T l_col,
+ linenr_T u_lnum, colnr_T u_col)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ bool all_ns = ns == 0 ? true : false;
+
+ ExtmarkCopy copy;
+ ExtmarkUndoObject undo;
+ FOR_ALL_EXTMARKS(buf, 1, l_lnum, l_col, u_lnum, u_col, {
+ if (all_ns || extmark->ns_id == ns) {
+ copy.ns_id = extmark->ns_id;
+ copy.mark_id = extmark->mark_id;
+ copy.lnum = extmark->line->lnum;
+ copy.col = extmark->col;
+
+ undo.data.copy = copy;
+ undo.type = kExtmarkCopy;
+ kv_push(uhp->uh_extmark, undo);
+ }
+ });
+}
+
+void u_extmark_copy_place(buf_T *buf,
+ linenr_T l_lnum, colnr_T l_col,
+ linenr_T u_lnum, colnr_T u_col,
+ linenr_T p_lnum, colnr_T p_col)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkCopyPlace copy_place;
+ copy_place.l_lnum = l_lnum;
+ copy_place.l_col = l_col;
+ copy_place.u_lnum = u_lnum;
+ copy_place.u_col = u_col;
+ copy_place.p_lnum = p_lnum;
+ copy_place.p_col = p_col;
+
+ ExtmarkUndoObject undo = { .type = kExtmarkCopyPlace,
+ .data.copy_place = copy_place };
+
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// Save info for undo/redo of extmark_clear
+static void u_extmark_clear(buf_T *buf, uint64_t ns,
+ linenr_T l_lnum, linenr_T u_lnum)
+{
+ u_header_T *uhp = u_force_get_undo_header(buf);
+ if (!uhp) {
+ return;
+ }
+
+ ExtmarkClear clear;
+ clear.ns_id = ns;
+ clear.l_lnum = l_lnum;
+ clear.u_lnum = u_lnum;
+
+ ExtmarkUndoObject undo = { .type = kExtmarkClear,
+ .data.clear = clear };
+ kv_push(uhp->uh_extmark, undo);
+}
+
+// undo or redo an extmark operation
+void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
+{
+ linenr_T lnum;
+ colnr_T mincol;
+ long lnum_amount;
+ long col_amount;
+ linenr_T line1;
+ linenr_T line2;
+ long amount;
+ long amount_after;
+
+ // use extmark_col_adjust
+ if (undo_info.type == kColAdjust) {
+ // Undo
+ if (undo) {
+ lnum = (undo_info.data.col_adjust.lnum
+ + undo_info.data.col_adjust.lnum_amount);
+ lnum_amount = -undo_info.data.col_adjust.lnum_amount;
+ col_amount = -undo_info.data.col_adjust.col_amount;
+ mincol = (undo_info.data.col_adjust.mincol
+ + (colnr_T)undo_info.data.col_adjust.col_amount);
+ // Redo
+ } else {
+ lnum = undo_info.data.col_adjust.lnum;
+ col_amount = undo_info.data.col_adjust.col_amount;
+ lnum_amount = undo_info.data.col_adjust.lnum_amount;
+ mincol = undo_info.data.col_adjust.mincol;
+ }
+ extmark_col_adjust(curbuf,
+ lnum, mincol, lnum_amount, col_amount, kExtmarkNoUndo);
+ // use extmark_col_adjust_delete
+ } else if (undo_info.type == kColAdjustDelete) {
+ if (undo) {
+ mincol = undo_info.data.col_adjust_delete.mincol;
+ col_amount = (undo_info.data.col_adjust_delete.endcol
+ - undo_info.data.col_adjust_delete.mincol) + 1;
+ extmark_col_adjust(curbuf,
+ undo_info.data.col_adjust_delete.lnum,
+ mincol,
+ 0,
+ col_amount,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_col_adjust_delete(curbuf,
+ undo_info.data.col_adjust_delete.lnum,
+ undo_info.data.col_adjust_delete.mincol,
+ undo_info.data.col_adjust_delete.endcol,
+ kExtmarkNoUndo,
+ undo_info.data.col_adjust_delete.eol);
+ }
+ // use extmark_adjust
+ } else if (undo_info.type == kLineAdjust) {
+ if (undo) {
+ // Undo - call signature type one - insert now
+ if (undo_info.data.adjust.amount == MAXLNUM) {
+ line1 = undo_info.data.adjust.line1;
+ line2 = MAXLNUM;
+ amount = -undo_info.data.adjust.amount_after;
+ amount_after = 0;
+ // Undo - call singature type two - delete now
+ } else if (undo_info.data.adjust.line2 == MAXLNUM) {
+ line1 = undo_info.data.adjust.line1;
+ line2 = undo_info.data.adjust.line2;
+ amount = -undo_info.data.adjust.amount;
+ amount_after = undo_info.data.adjust.amount_after;
+ // Undo - call signature three - move lines
+ } else {
+ line1 = (undo_info.data.adjust.line1
+ + undo_info.data.adjust.amount);
+ line2 = (undo_info.data.adjust.line2
+ + undo_info.data.adjust.amount);
+ amount = -undo_info.data.adjust.amount;
+ amount_after = -undo_info.data.adjust.amount_after;
+ }
+ // redo
+ } else {
+ line1 = undo_info.data.adjust.line1;
+ line2 = undo_info.data.adjust.line2;
+ amount = undo_info.data.adjust.amount;
+ amount_after = undo_info.data.adjust.amount_after;
+ }
+ extmark_adjust(curbuf,
+ line1, line2, amount, amount_after, kExtmarkNoUndo, false);
+ // kExtmarkCopy
+ } else if (undo_info.type == kExtmarkCopy) {
+ // Redo should be handled by kColAdjustDelete or kExtmarkCopyPlace
+ if (undo) {
+ extmark_set(curbuf,
+ undo_info.data.copy.ns_id,
+ undo_info.data.copy.mark_id,
+ undo_info.data.copy.lnum,
+ undo_info.data.copy.col,
+ kExtmarkNoUndo);
+ }
+ // uses extmark_copy_and_place
+ } else if (undo_info.type == kExtmarkCopyPlace) {
+ // Redo, undo is handle by kExtmarkCopy
+ if (!undo) {
+ extmark_copy_and_place(curbuf,
+ undo_info.data.copy_place.l_lnum,
+ undo_info.data.copy_place.l_col,
+ undo_info.data.copy_place.u_lnum,
+ undo_info.data.copy_place.u_col,
+ undo_info.data.copy_place.p_lnum,
+ undo_info.data.copy_place.p_col,
+ kExtmarkNoUndo, true, NULL);
+ }
+ // kExtmarkClear
+ } else if (undo_info.type == kExtmarkClear) {
+ // Redo, undo is handle by kExtmarkCopy
+ if (!undo) {
+ extmark_clear(curbuf,
+ undo_info.data.clear.ns_id,
+ undo_info.data.clear.l_lnum,
+ undo_info.data.clear.u_lnum,
+ kExtmarkNoUndo);
+ }
+ // kAdjustMove
+ } else if (undo_info.type == kAdjustMove) {
+ apply_undo_move(undo_info, undo);
+ // extmark_set
+ } else if (undo_info.type == kExtmarkSet) {
+ if (undo) {
+ extmark_del(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_set(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ undo_info.data.set.lnum,
+ undo_info.data.set.col,
+ kExtmarkNoUndo);
+ }
+ // extmark_set into update
+ } else if (undo_info.type == kExtmarkUpdate) {
+ if (undo) {
+ extmark_set(curbuf,
+ undo_info.data.update.ns_id,
+ undo_info.data.update.mark_id,
+ undo_info.data.update.old_lnum,
+ undo_info.data.update.old_col,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_set(curbuf,
+ undo_info.data.update.ns_id,
+ undo_info.data.update.mark_id,
+ undo_info.data.update.lnum,
+ undo_info.data.update.col,
+ kExtmarkNoUndo);
+ }
+ // extmark_del
+ } else if (undo_info.type == kExtmarkDel) {
+ if (undo) {
+ extmark_set(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ undo_info.data.set.lnum,
+ undo_info.data.set.col,
+ kExtmarkNoUndo);
+ // Redo
+ } else {
+ extmark_del(curbuf,
+ undo_info.data.set.ns_id,
+ undo_info.data.set.mark_id,
+ kExtmarkNoUndo);
+ }
+ }
+}
+
+// undo/redo an kExtmarkMove operation
+static void apply_undo_move(ExtmarkUndoObject undo_info, bool undo)
+{
+ // 3 calls are required , see comment in function do_move (ex_cmds.c)
+ linenr_T line1 = undo_info.data.move.line1;
+ linenr_T line2 = undo_info.data.move.line2;
+ linenr_T last_line = undo_info.data.move.last_line;
+ linenr_T dest = undo_info.data.move.dest;
+ linenr_T num_lines = undo_info.data.move.num_lines;
+ linenr_T extra = undo_info.data.move.extra;
+
+ if (undo) {
+ if (dest >= line2) {
+ extmark_adjust(curbuf, dest - num_lines + 1, dest,
+ last_line - dest + num_lines - 1, 0L, kExtmarkNoUndo,
+ true);
+ extmark_adjust(curbuf, dest - line2, dest - line1,
+ dest - line2, 0L, kExtmarkNoUndo, false);
+ } else {
+ extmark_adjust(curbuf, line1-num_lines, line2-num_lines,
+ last_line - (line1-num_lines), 0L, kExtmarkNoUndo, true);
+ extmark_adjust(curbuf, (line1-num_lines) + 1, (line2-num_lines) + 1,
+ -num_lines, 0L, kExtmarkNoUndo, false);
+ }
+ extmark_adjust(curbuf, last_line, last_line + num_lines - 1,
+ line1 - last_line, 0L, kExtmarkNoUndo, true);
+ // redo
+ } else {
+ extmark_adjust(curbuf, line1, line2,
+ last_line - line2, 0L, kExtmarkNoUndo, true);
+ if (dest >= line2) {
+ extmark_adjust(curbuf, line2 + 1, dest,
+ -num_lines, 0L, kExtmarkNoUndo, false);
+ } else {
+ extmark_adjust(curbuf, dest + 1, line1 - 1,
+ num_lines, 0L, kExtmarkNoUndo, false);
+ }
+ extmark_adjust(curbuf, last_line - num_lines + 1, last_line,
+ -(last_line - dest - extra), 0L, kExtmarkNoUndo, true);
+ }
+}
+
+
+/// Get the column position for EOL on a line
+///
+/// If the lnum doesn't exist, returns 0
+colnr_T extmark_eol_col(buf_T *buf, linenr_T lnum)
+{
+ if (lnum > buf->b_ml.ml_line_count) {
+ return 0;
+ }
+ return (colnr_T)STRLEN(ml_get_buf(buf, lnum, false)) + 1;
+}
+
+
+// Adjust columns and rows for extmarks
+// based off mark_col_adjust in mark.c
+// returns true if something was moved otherwise false
+static bool extmark_col_adjust_impl(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, long lnum_amount,
+ bool for_delete,
+ long update_col)
+{
+ bool marks_exist = false;
+
+ ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, false);
+ if (!extmarkline) {
+ return false;
+ }
+
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, mincol, MAXCOL, {
+ marks_exist = true;
+
+ // Calculate desired col amount where the adjustment should take place
+ // (not taking) eol into account
+ long col_amount;
+ if (for_delete) {
+ if (extmark->col < update_col) {
+ // When mark inside range
+ colnr_T start_effected_range = mincol - 1;
+ col_amount = -(extmark->col - start_effected_range);
+ } else {
+ // Mark outside of range
+ // -1 because a delete of width 0 should still move marks
+ col_amount = -(update_col - mincol) - 1;
+ }
+ } else {
+ // for anything other than deletes
+ col_amount = update_col;
+ }
+
+ // No update required for this guy
+ if (col_amount == 0 && lnum_amount == 0) {
+ continue;
+ }
+
+ // Set mark to start of line
+ if (col_amount < 0
+ && extmark->col <= (colnr_T)-col_amount) {
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extmarkline->lnum + lnum_amount,
+ 1, kExtmarkNoUndo, &mitr);
+ // Update the mark
+ } else {
+ // Note: The undo is handled by u_extmark_col_adjust, NoUndo here
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extmarkline->lnum + lnum_amount,
+ extmark->col + (colnr_T)col_amount, kExtmarkNoUndo, &mitr);
+ }
+ })
+
+ if (kb_size(&extmarkline->items) == 0) {
+ kb_del(extmarklines, &buf->b_extlines, extmarkline);
+ extmarkline_free(extmarkline);
+ }
+
+ return marks_exist;
+}
+
+// Adjust columns and rows for extmarks
+//
+// based off mark_col_adjust in mark.c
+// use extmark_col_adjust_impl to move columns by inserting
+// Doesn't take the eol into consideration (possible to put marks in invalid
+// positions)
+void extmark_col_adjust(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, long lnum_amount,
+ long col_amount, ExtmarkOp undo)
+{
+ assert(col_amount > INT_MIN && col_amount <= INT_MAX);
+
+ bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount,
+ false, col_amount);
+
+ marks_moved |= bufhl_mark_col_adjust(buf, lnum, mincol,
+ lnum_amount, col_amount);
+
+ if (undo == kExtmarkUndo && marks_moved) {
+ u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount);
+ }
+}
+
+// Adjust marks after a delete on a line
+//
+// Automatically readjusts to take the eol into account
+// TODO(timeyyy): change mincol to be for the mark to be copied, not moved
+//
+// @param mincol First column that needs to be moved (start of delete range) + 1
+// @param endcol Last column which needs to be copied (end of delete range + 1)
+void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum,
+ colnr_T mincol, colnr_T endcol,
+ ExtmarkOp undo, int _eol)
+{
+ colnr_T start_effected_range = mincol;
+
+ bool marks_moved;
+ if (undo == kExtmarkUndo) {
+ // Copy marks that would be effected by delete
+ // -1 because we need to restore if a mark existed at the start pos
+ u_extmark_copy(buf, 0, lnum, start_effected_range, lnum, endcol);
+ }
+
+ marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, 0,
+ true, (long)endcol);
+
+ marks_moved |= bufhl_mark_col_adjust(buf, lnum, endcol, 0, mincol-(endcol+1));
+ // Deletes at the end of the line have different behaviour than the normal
+ // case when deleted.
+ // Cleanup any marks that are floating beyond the end of line.
+ // we allow this to be passed in as well because the buffer may have already
+ // been mutated.
+ int eol = _eol;
+ if (!eol) {
+ eol = extmark_eol_col(buf, lnum);
+ }
+ FOR_ALL_EXTMARKS(buf, 1, lnum, eol, lnum, -1, {
+ extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id,
+ extmarkline->lnum, (colnr_T)eol, kExtmarkNoUndo, &mitr);
+ })
+
+ // Record the undo for the actual move
+ if (marks_moved && undo == kExtmarkUndo) {
+ u_extmark_col_adjust_delete(buf, lnum, mincol, endcol, eol);
+ }
+}
+
+// Adjust extmark row for inserted/deleted rows (columns stay fixed).
+void extmark_adjust(buf_T *buf,
+ linenr_T line1,
+ linenr_T line2,
+ long amount,
+ long amount_after,
+ ExtmarkOp undo,
+ bool end_temp)
+{
+ ExtmarkLine *_extline;
+
+ // btree needs to be kept ordered to work, so far only :move requires this
+ // 2nd call with end_temp = true unpack the lines from the temp position
+ if (end_temp && amount < 0) {
+ for (size_t i = 0; i < kv_size(buf->b_extmark_move_space); i++) {
+ _extline = kv_A(buf->b_extmark_move_space, i);
+ _extline->lnum += amount;
+ kb_put(extmarklines, &buf->b_extlines, _extline);
+ }
+ kv_size(buf->b_extmark_move_space) = 0;
+ return;
+ }
+
+ bool marks_exist = false;
+ linenr_T *lp;
+
+ linenr_T adj_start = line1;
+ if (amount == MAXLNUM) {
+ // Careful! marks from deleted region can end up on en extisting extmarkline
+ // that is goinig to be adjusted to the target position.
+ linenr_T join_num = line1 - amount_after;
+ ExtmarkLine *joinline = (join_num > line2
+ ? extmarkline_ref(buf, join_num, false) : NULL);
+
+ // extmark_adjust is already redoable, the copy should only be for undo
+ marks_exist = extmark_copy_and_place(curbuf,
+ line1, 1,
+ line2, MAXCOL,
+ line1, 1,
+ kExtmarkUndoNoRedo, true, joinline);
+ adj_start = line2+1;
+ }
+ FOR_ALL_EXTMARKLINES(buf, adj_start, MAXLNUM, {
+ marks_exist = true;
+ lp = &(extmarkline->lnum);
+ if (*lp <= line2) {
+ // 1st call with end_temp = true, store the lines in a temp position
+ if (end_temp && amount > 0) {
+ kb_del_itr_extmarklines(&buf->b_extlines, &itr);
+ kv_push(buf->b_extmark_move_space, extmarkline);
+ }
+
+ *lp += amount;
+ } else if (amount_after && *lp > line2) {
+ *lp += amount_after;
+ }
+ })
+
+ if (undo == kExtmarkUndo && marks_exist) {
+ u_extmark_adjust(buf, line1, line2, amount, amount_after);
+ }
+}
+
+/// Range points to copy
+///
+/// if part of a larger iteration we can't delete, then the caller
+/// must check for empty lines.
+bool extmark_copy_and_place(buf_T *buf,
+ linenr_T l_lnum, colnr_T l_col,
+ linenr_T u_lnum, colnr_T u_col,
+ linenr_T p_lnum, colnr_T p_col,
+ ExtmarkOp undo, bool delete,
+ ExtmarkLine *destline)
+
+{
+ bool marks_moved = false;
+ if (undo == kExtmarkUndo || undo == kExtmarkUndoNoRedo) {
+ // Copy marks that would be effected by delete
+ u_extmark_copy(buf, 0, l_lnum, l_col, u_lnum, u_col);
+ }
+
+ // Move extmarks to their final position
+ // Careful: if we move items within the same line, we might change order of
+ // marks within the same extmarkline. Too keep it simple, first delete all
+ // items from the extmarkline and put them back in the right order.
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, {
+ kvec_t(Extmark) temp_space = KV_INITIAL_VALUE;
+ bool same_line = extmarkline == destline;
+ FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items,
+ (extmarkline->lnum > l_lnum) ? 0 : l_col,
+ (extmarkline->lnum < u_lnum) ? MAXCOL : u_col, {
+ if (!destline) {
+ destline = extmarkline_ref(buf, p_lnum, true);
+ same_line = extmarkline == destline;
+ }
+ marks_moved = true;
+ if (!same_line) {
+ extmark_put(p_col, extmark->mark_id, destline, extmark->ns_id);
+ ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns,
+ extmark->ns_id);
+ pmap_put(uint64_t)(ns_obj->map, extmark->mark_id, destline);
+ } else {
+ kv_push(temp_space, *extmark);
+ }
+ // Delete old mark
+ kb_del_itr(markitems, &extmarkline->items, &mitr);
+ })
+ if (same_line) {
+ for (size_t i = 0; i < kv_size(temp_space); i++) {
+ Extmark mark = kv_A(temp_space, i);
+ extmark_put(p_col, mark.mark_id, extmarkline, mark.ns_id);
+ }
+ kv_destroy(temp_space);
+ } else if (delete && kb_size(&extmarkline->items) == 0) {
+ kb_del_itr(extmarklines, &buf->b_extlines, &itr);
+ extmarkline_free(extmarkline);
+ }
+ })
+
+ // Record the undo for the actual move
+ if (marks_moved && undo == kExtmarkUndo) {
+ u_extmark_copy_place(buf, l_lnum, l_col, u_lnum, u_col, p_lnum, p_col);
+ }
+
+ return marks_moved;
+}
+
+// Get reference to line in kbtree_t, allocating it if neccessary.
+ExtmarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put)
+{
+ kbtree_t(extmarklines) *b = &buf->b_extlines;
+ ExtmarkLine t, **pp;
+ t.lnum = lnum;
+
+ pp = kb_get(extmarklines, b, &t);
+ if (!pp) {
+ if (!put) {
+ return NULL;
+ }
+ ExtmarkLine *p = xcalloc(sizeof(ExtmarkLine), 1);
+ p->lnum = lnum;
+ // p->items zero initialized
+ kb_put(extmarklines, b, p);
+ return p;
+ }
+ // Return existing
+ return *pp;
+}
+
+void extmarkline_free(ExtmarkLine *extmarkline)
+{
+ kb_destroy(markitems, (&extmarkline->items));
+ xfree(extmarkline);
+}
+
+/// Put an extmark into a line,
+///
+/// caller must ensure combination of id and ns_id isn't in use.
+void extmark_put(colnr_T col, uint64_t id,
+ ExtmarkLine *extmarkline, uint64_t ns)
+{
+ Extmark t;
+ t.col = col;
+ t.mark_id = id;
+ t.line = extmarkline;
+ t.ns_id = ns;
+
+ kbtree_t(markitems) *b = &(extmarkline->items);
+ // kb_put requries the key to not be there
+ assert(!kb_getp(markitems, b, &t));
+
+ kb_put(markitems, b, t);
+}
+
+
diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h
new file mode 100644
index 0000000000..ee1da26875
--- /dev/null
+++ b/src/nvim/mark_extended.h
@@ -0,0 +1,282 @@
+#ifndef NVIM_MARK_EXTENDED_H
+#define NVIM_MARK_EXTENDED_H
+
+#include "nvim/mark_extended_defs.h"
+#include "nvim/buffer_defs.h" // for buf_T
+
+
+// Macro Documentation: FOR_ALL_?
+// Search exclusively using the range values given.
+// Use MAXCOL/MAXLNUM for the start and end of the line/col.
+// The ns parameter: Unless otherwise stated, this is only a starting point
+// for the btree to searched in, the results being itterated over will
+// still contain extmarks from other namespaces.
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, code)\
+ kbitr_t(extmarklines) itr;\
+ ExtmarkLine t;\
+ t.lnum = l_lnum;\
+ if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \
+ kb_itr_next(extmarklines, &buf->b_extlines, &itr);\
+ }\
+ ExtmarkLine *extmarkline;\
+ for (; kb_itr_valid(&itr); kb_itr_next(extmarklines, \
+ &buf->b_extlines, &itr)) { \
+ extmarkline = kb_itr_key(&itr);\
+ if (extmarkline->lnum > u_lnum) { \
+ break;\
+ }\
+ code;\
+ }
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\
+ kbitr_t(extmarklines) itr;\
+ ExtmarkLine t;\
+ t.lnum = u_lnum;\
+ if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \
+ kb_itr_prev(extmarklines, &buf->b_extlines, &itr);\
+ }\
+ ExtmarkLine *extmarkline;\
+ for (; kb_itr_valid(&itr); kb_itr_prev(extmarklines, \
+ &buf->b_extlines, &itr)) { \
+ extmarkline = kb_itr_key(&itr);\
+ if (extmarkline->lnum < l_lnum) { \
+ break;\
+ }\
+ code;\
+ }
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\
+ kbitr_t(markitems) mitr;\
+ Extmark mt;\
+ mt.ns_id = ns;\
+ mt.mark_id = 0;\
+ mt.line = NULL;\
+ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { \
+ mt.col = (extmarkline->lnum != l_lnum) ? MINCOL : l_col;\
+ if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \
+ kb_itr_next(markitems, &extmarkline->items, &mitr);\
+ } \
+ Extmark *extmark;\
+ for (; \
+ kb_itr_valid(&mitr); \
+ kb_itr_next(markitems, &extmarkline->items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->line->lnum == u_lnum \
+ && extmark->col > u_col) { \
+ break;\
+ }\
+ code;\
+ }\
+ })
+
+
+// see FOR_ALL_? for documentation
+#define FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\
+ kbitr_t(markitems) mitr;\
+ Extmark mt;\
+ mt.mark_id = sizeof(uint64_t);\
+ mt.ns_id = ns;\
+ FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \
+ mt.col = (extmarkline->lnum != u_lnum) ? MAXCOL : u_col;\
+ if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \
+ kb_itr_prev(markitems, &extmarkline->items, &mitr);\
+ } \
+ Extmark *extmark;\
+ for (; \
+ kb_itr_valid(&mitr); \
+ kb_itr_prev(markitems, &extmarkline->items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->line->lnum == l_lnum \
+ && extmark->col < l_col) { \
+ break;\
+ }\
+ code;\
+ }\
+ })
+
+
+#define FOR_ALL_EXTMARKS_IN_LINE(items, l_col, u_col, code)\
+ kbitr_t(markitems) mitr;\
+ Extmark mt;\
+ mt.ns_id = 0;\
+ mt.mark_id = 0;\
+ mt.line = NULL;\
+ mt.col = l_col;\
+ colnr_T extmarkline_u_col = u_col;\
+ if (!kb_itr_get(markitems, &items, mt, &mitr)) { \
+ kb_itr_next(markitems, &items, &mitr);\
+ } \
+ Extmark *extmark;\
+ for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \
+ extmark = &kb_itr_key(&mitr);\
+ if (extmark->col > extmarkline_u_col) { \
+ break;\
+ }\
+ code;\
+ }
+
+
+typedef struct ExtmarkNs { // For namespacing extmarks
+ PMap(uint64_t) *map; // For fast lookup
+ uint64_t free_id; // For automatically assigning id's
+} ExtmarkNs;
+
+
+typedef kvec_t(Extmark *) ExtmarkArray;
+
+
+// Undo/redo extmarks
+
+typedef enum {
+ kExtmarkNOOP, // Extmarks shouldn't be moved
+ kExtmarkUndo, // Operation should be reversable/undoable
+ kExtmarkNoUndo, // Operation should not be reversable
+ kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
+} ExtmarkOp;
+
+
+// adjust line numbers only, corresponding to mark_adjust call
+typedef struct {
+ linenr_T line1;
+ linenr_T line2;
+ long amount;
+ long amount_after;
+} Adjust;
+
+// adjust columns after split/join line, like mark_col_adjust
+typedef struct {
+ linenr_T lnum;
+ colnr_T mincol;
+ long col_amount;
+ long lnum_amount;
+} ColAdjust;
+
+// delete the columns between mincol and endcol
+typedef struct {
+ linenr_T lnum;
+ colnr_T mincol;
+ colnr_T endcol;
+ int eol;
+} ColAdjustDelete;
+
+// adjust linenumbers after :move operation
+typedef struct {
+ linenr_T line1;
+ linenr_T line2;
+ linenr_T last_line;
+ linenr_T dest;
+ linenr_T num_lines;
+ linenr_T extra;
+} AdjustMove;
+
+// TODO(bfredl): reconsider if we really should track mark creation/updating
+// itself, these are not really "edit" operation.
+// extmark was created
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkSet;
+
+// extmark was updated
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T old_lnum;
+ colnr_T old_col;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkUpdate;
+
+// copied mark before deletion (as operation is destructive)
+typedef struct {
+ uint64_t ns_id;
+ uint64_t mark_id;
+ linenr_T lnum;
+ colnr_T col;
+} ExtmarkCopy;
+
+// also used as part of :move operation? probably can be simplified to one
+// event.
+typedef struct {
+ linenr_T l_lnum;
+ colnr_T l_col;
+ linenr_T u_lnum;
+ colnr_T u_col;
+ linenr_T p_lnum;
+ colnr_T p_col;
+} ExtmarkCopyPlace;
+
+// extmark was cleared.
+// TODO(bfredl): same reconsideration as for ExtmarkSet/ExtmarkUpdate
+typedef struct {
+ uint64_t ns_id;
+ linenr_T l_lnum;
+ linenr_T u_lnum;
+} ExtmarkClear;
+
+
+typedef enum {
+ kLineAdjust,
+ kColAdjust,
+ kColAdjustDelete,
+ kAdjustMove,
+ kExtmarkSet,
+ kExtmarkDel,
+ kExtmarkUpdate,
+ kExtmarkCopy,
+ kExtmarkCopyPlace,
+ kExtmarkClear,
+} UndoObjectType;
+
+// TODO(bfredl): reduce the number of undo action types
+struct undo_object {
+ UndoObjectType type;
+ union {
+ Adjust adjust;
+ ColAdjust col_adjust;
+ ColAdjustDelete col_adjust_delete;
+ AdjustMove move;
+ ExtmarkSet set;
+ ExtmarkUpdate update;
+ ExtmarkCopy copy;
+ ExtmarkCopyPlace copy_place;
+ ExtmarkClear clear;
+ } data;
+};
+
+
+// For doing move of extmarks in substitutions
+typedef struct {
+ lpos_T startpos;
+ lpos_T endpos;
+ linenr_T lnum;
+ int sublen;
+} ExtmarkSubSingle;
+
+// For doing move of extmarks in substitutions
+typedef struct {
+ lpos_T startpos;
+ lpos_T endpos;
+ linenr_T lnum;
+ linenr_T newline_in_pat;
+ linenr_T newline_in_sub;
+ linenr_T lnum_added;
+ lpos_T cm_start; // start of the match
+ lpos_T cm_end; // end of the match
+ int eol; // end of the match
+} ExtmarkSubMulti;
+
+typedef kvec_t(ExtmarkSubSingle) extmark_sub_single_vec_t;
+typedef kvec_t(ExtmarkSubMulti) extmark_sub_multi_vec_t;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "mark_extended.h.generated.h"
+#endif
+
+#endif // NVIM_MARK_EXTENDED_H
diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h
new file mode 100644
index 0000000000..565c599d06
--- /dev/null
+++ b/src/nvim/mark_extended_defs.h
@@ -0,0 +1,54 @@
+#ifndef NVIM_MARK_EXTENDED_DEFS_H
+#define NVIM_MARK_EXTENDED_DEFS_H
+
+#include "nvim/pos.h" // for colnr_T
+#include "nvim/map.h" // for uint64_t
+#include "nvim/lib/kbtree.h"
+#include "nvim/lib/kvec.h"
+
+struct ExtmarkLine;
+
+typedef struct Extmark
+{
+ uint64_t ns_id;
+ uint64_t mark_id;
+ struct ExtmarkLine *line;
+ colnr_T col;
+} Extmark;
+
+
+// We only need to compare columns as rows are stored in a different tree.
+// Marks are ordered by: position, namespace, mark_id
+// This improves moving marks but slows down all other use cases (searches)
+static inline int extmark_cmp(Extmark a, Extmark b)
+{
+ int cmp = kb_generic_cmp(a.col, b.col);
+ if (cmp != 0) {
+ return cmp;
+ }
+ cmp = kb_generic_cmp(a.ns_id, b.ns_id);
+ if (cmp != 0) {
+ return cmp;
+ }
+ return kb_generic_cmp(a.mark_id, b.mark_id);
+}
+
+
+#define markitems_cmp(a, b) (extmark_cmp((a), (b)))
+KBTREE_INIT(markitems, Extmark, markitems_cmp, 10)
+
+typedef struct ExtmarkLine
+{
+ linenr_T lnum;
+ kbtree_t(markitems) items;
+} ExtmarkLine;
+
+#define EXTMARKLINE_CMP(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum))
+KBTREE_INIT(extmarklines, ExtmarkLine *, EXTMARKLINE_CMP, 10)
+
+
+typedef struct undo_object ExtmarkUndoObject;
+typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
+
+
+#endif // NVIM_MARK_EXTENDED_DEFS_H
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 05cc62bb33..e5ba17a0a7 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -540,7 +540,7 @@ void ml_open_file(buf_T *buf)
/// file, or reading into an existing buffer, create a swap file now.
///
/// @param newfile reading file into new buffer
-void check_need_swap(int newfile)
+void check_need_swap(bool newfile)
{
int old_msg_silent = msg_silent; // might be reset by an E325 message
msg_silent = 0; // If swap dialog prompts for input, user needs to see it!
@@ -937,8 +937,9 @@ void ml_recover(bool checkext)
*/
if (directly) {
expand_env(b0p->b0_fname, NameBuff, MAXPATHL);
- if (setfname(curbuf, NameBuff, NULL, TRUE) == FAIL)
+ if (setfname(curbuf, NameBuff, NULL, true) == FAIL) {
goto theend;
+ }
}
home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, TRUE);
@@ -1437,7 +1438,7 @@ recover_names (
* Append the full path to name with path separators made into percent
* signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"")
*/
-static char *make_percent_swname(const char *dir, char *name)
+char *make_percent_swname(const char *dir, char *name)
FUNC_ATTR_NONNULL_ARG(1)
{
char *d = NULL;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 03cbe8ec18..067f1c3283 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1378,7 +1378,7 @@ static void msg_home_replace_attr(char_u *fname, int attr)
/*
* Output 'len' characters in 'str' (including NULs) with translation
- * if 'len' is -1, output upto a NUL character.
+ * if 'len' is -1, output up to a NUL character.
* Use attributes 'attr'.
* Return the number of characters it takes on the screen.
*/
@@ -1501,7 +1501,7 @@ void msg_make(char_u *arg)
}
}
-/// Output the string 'str' upto a NUL character.
+/// Output the string 'str' up to a NUL character.
/// Return the number of characters it takes on the screen.
///
/// If K_SPECIAL is encountered, then it is taken in conjunction with the
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 1db8a1fa11..a871d424c6 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -30,7 +30,6 @@
#include "nvim/indent_c.h"
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
-#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -792,6 +791,8 @@ int prompt_for_number(int *mouse_used)
cmdline_row = msg_row - 1;
}
need_wait_return = false;
+ msg_didany = false;
+ msg_didout = false;
} else {
cmdline_row = save_cmdline_row;
}
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index d0aa0653cb..deb7ee6342 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -508,31 +508,30 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
return NULL;
}
-/*
- * setmouse() - switch mouse on/off depending on current mode and 'mouse'
- */
+/// Set UI mouse depending on current mode and 'mouse'.
+///
+/// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty).
void setmouse(void)
{
- int checkfor;
-
ui_cursor_shape();
- /* be quick when mouse is off */
- if (*p_mouse == NUL)
+ // Be quick when mouse is off.
+ if (*p_mouse == NUL) {
return;
+ }
- if (VIsual_active)
+ int checkfor = MOUSE_NORMAL; // assume normal mode
+ if (VIsual_active) {
checkfor = MOUSE_VISUAL;
- else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE)
+ } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) {
checkfor = MOUSE_RETURN;
- else if (State & INSERT)
+ } else if (State & INSERT) {
checkfor = MOUSE_INSERT;
- else if (State & CMDLINE)
+ } else if (State & CMDLINE) {
checkfor = MOUSE_COMMAND;
- else if (State == CONFIRM || State == EXTERNCMD)
- checkfor = ' '; /* don't use mouse for ":confirm" or ":!cmd" */
- else
- checkfor = MOUSE_NORMAL; /* assume normal mode */
+ } else if (State == CONFIRM || State == EXTERNCMD) {
+ checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd"
+ }
if (mouse_has(checkfor)) {
ui_call_mouse_on();
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 2ef2c3101f..9c5434a0dd 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1422,12 +1422,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (oap->motion_type == kMTLineWise) {
oap->inclusive = false;
} else if (oap->motion_type == kMTCharWise) {
- // If the motion already was characterwise, toggle "inclusive"
+ // If the motion already was charwise, toggle "inclusive"
oap->inclusive = !oap->inclusive;
}
oap->motion_type = kMTCharWise;
} else if (oap->motion_force == Ctrl_V) {
- // Change line- or characterwise motion into Visual block mode.
+ // Change line- or charwise motion into Visual block mode.
if (!VIsual_active) {
VIsual_active = true;
VIsual = oap->start;
@@ -1516,7 +1516,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
}
// In Select mode, a linewise selection is operated upon like a
- // characterwise selection.
+ // charwise selection.
// Special case: gH<Del> deletes the last line.
if (VIsual_select && VIsual_mode == 'V'
&& cap->oap->op_type != OP_DELETE) {
@@ -1553,7 +1553,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (!VIsual_active) {
if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL))
oap->start.col = 0;
- if ((curwin->w_cursor.col > 0 || oap->inclusive)
+ if ((curwin->w_cursor.col > 0
+ || oap->inclusive
+ || oap->motion_type == kMTLineWise)
&& hasFolding(curwin->w_cursor.lnum, NULL,
&curwin->w_cursor.lnum)) {
curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr());
@@ -4588,7 +4590,7 @@ static void nv_colon(cmdarg_T *cap)
nv_operator(cap);
} else {
if (cap->oap->op_type != OP_NOP) {
- // Using ":" as a movement is characterwise exclusive.
+ // Using ":" as a movement is charwise exclusive.
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
} else if (cap->count0 && !is_cmdkey) {
@@ -6372,8 +6374,8 @@ static void nv_visual(cmdarg_T *cap)
if (cap->cmdchar == Ctrl_Q)
cap->cmdchar = Ctrl_V;
- /* 'v', 'V' and CTRL-V can be used while an operator is pending to make it
- * characterwise, linewise, or blockwise. */
+ // 'v', 'V' and CTRL-V can be used while an operator is pending to make it
+ // charwise, linewise, or blockwise.
if (cap->oap->op_type != OP_NOP) {
motion_force = cap->oap->motion_force = cap->cmdchar;
finish_op = false; // operator doesn't finish now but later
@@ -7887,15 +7889,17 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
cap->oap->regname = regname;
}
- /* When deleted a linewise Visual area, put the register as
- * lines to avoid it joined with the next line. When deletion was
- * characterwise, split a line when putting lines. */
- if (VIsual_mode == 'V')
+ // When deleted a linewise Visual area, put the register as
+ // lines to avoid it joined with the next line. When deletion was
+ // charwise, split a line when putting lines.
+ if (VIsual_mode == 'V') {
flags |= PUT_LINE;
- else if (VIsual_mode == 'v')
+ } else if (VIsual_mode == 'v') {
flags |= PUT_LINE_SPLIT;
- if (VIsual_mode == Ctrl_V && dir == FORWARD)
+ }
+ if (VIsual_mode == Ctrl_V && dir == FORWARD) {
flags |= PUT_LINE_FORWARD;
+ }
dir = BACKWARD;
if ((VIsual_mode != 'V'
&& curwin->w_cursor.col < curbuf->b_op_start.col)
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index fbbdfdcd82..0ca16e2c25 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -49,6 +49,7 @@
#include "nvim/undo.h"
#include "nvim/macros.h"
#include "nvim/window.h"
+#include "nvim/lib/kvec.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
@@ -306,6 +307,15 @@ void shift_line(
change_indent(INDENT_SET, count, false, NUL, call_changed_bytes);
} else {
(void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
+
+ colnr_T mincol = (curwin->w_cursor.col + 1) -p_sw;
+ colnr_T col_amount = left ? -p_sw : p_sw;
+ extmark_col_adjust(curbuf,
+ curwin->w_cursor.lnum,
+ mincol,
+ 0,
+ col_amount,
+ kExtmarkUndo);
}
}
@@ -479,6 +489,10 @@ static void shift_block(oparg_T *oap, int amount)
State = oldstate;
curwin->w_cursor.col = oldcol;
p_ri = old_p_ri;
+
+ colnr_T col_amount = left ? -p_sw : p_sw;
+ extmark_col_adjust(curbuf, curwin->w_cursor.lnum,
+ curwin->w_cursor.col, 0, col_amount, kExtmarkUndo);
}
/*
@@ -623,10 +637,19 @@ void op_reindent(oparg_T *oap, Indenter how)
amount = how(); /* get the indent for this line */
if (amount >= 0 && set_indent(amount, SIN_UNDO)) {
- /* did change the indent, call changed_lines() later */
- if (first_changed == 0)
+ // did change the indent, call changed_lines() later
+ if (first_changed == 0) {
first_changed = curwin->w_cursor.lnum;
+ }
last_changed = curwin->w_cursor.lnum;
+
+ // Adjust extmarks
+ extmark_col_adjust(curbuf,
+ curwin->w_cursor.lnum,
+ 0, // mincol
+ 0, // lnum_amount
+ amount, // col_amount
+ kExtmarkUndo);
}
}
++curwin->w_cursor.lnum;
@@ -1632,10 +1655,35 @@ setmarks:
if (oap->motion_type == kMTBlockWise) {
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = oap->start.col;
- } else
+ } else {
curbuf->b_op_end = oap->start;
+ }
curbuf->b_op_start = oap->start;
+ // TODO(timeyyy): refactor: Move extended marks
+ // + 1 to change to buf mode,
+ // and + 1 because we only move marks after the deleted col
+ colnr_T mincol = oap->start.col + 1 + 1;
+ colnr_T endcol;
+ if (oap->motion_type == kMTBlockWise) {
+ // TODO(timeyyy): refactor extmark_col_adjust to take lnumstart, lnum_end ?
+ endcol = bd.end_vcol + 1;
+ for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) {
+ extmark_col_adjust_delete(curbuf, lnum, mincol, endcol,
+ kExtmarkUndo, 0);
+ }
+
+ // Delete characters within one line,
+ // The case with multiple lines is handled by do_join
+ } else if (oap->motion_type == kMTCharWise && oap->line_count == 1) {
+ // + 1 to change to buf mode, then plus 1 to fit function requirements
+ endcol = oap->end.col + 1 + 1;
+
+ lnum = curwin->w_cursor.lnum;
+ if (oap->is_VIsual == false) {
+ endcol = MAX(endcol - 1, mincol);
+ }
+ }
return OK;
}
@@ -2031,8 +2079,8 @@ bool swapchar(int op_type, pos_T *pos)
pos_T sp = curwin->w_cursor;
curwin->w_cursor = *pos;
- /* don't use del_char(), it also removes composing chars */
- del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE);
+ // don't use del_char(), it also removes composing chars
+ del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false);
ins_char(nc);
curwin->w_cursor = sp;
} else {
@@ -2105,8 +2153,9 @@ void op_insert(oparg_T *oap, long count1)
* values in "bd". */
if (u_save_cursor() == FAIL)
return;
- for (i = 0; i < bd.endspaces; i++)
+ for (i = 0; i < bd.endspaces; i++) {
ins_char(' ');
+ }
bd.textlen += bd.endspaces;
}
} else {
@@ -2224,6 +2273,10 @@ void op_insert(oparg_T *oap, long count1)
xfree(ins_text);
}
}
+ colnr_T col = oap->start.col;
+ for (linenr_T lnum = oap->start.lnum; lnum <= oap->end.lnum; lnum++) {
+ extmark_col_adjust(curbuf, lnum, col, 0, 1, kExtmarkUndo);
+ }
}
/*
@@ -2694,6 +2747,27 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
}
+static void extmarks_do_put(int dir,
+ size_t totlen,
+ MotionType y_type,
+ linenr_T lnum,
+ colnr_T col)
+{
+ // adjust extmarks
+ colnr_T col_amount = (colnr_T)(dir == FORWARD ? totlen-1 : totlen);
+ // Move extmark with char put
+ if (y_type == kMTCharWise) {
+ extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo);
+ // Move extmark with blockwise put
+ } else if (y_type == kMTBlockWise) {
+ for (lnum = curbuf->b_op_start.lnum;
+ lnum <= curbuf->b_op_end.lnum;
+ lnum++) {
+ extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo);
+ }
+ }
+}
+
/*
* Put contents of register "regname" into the text.
* Caller must check "regname" to be valid!
@@ -2708,8 +2782,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
char_u *oldp;
int yanklen;
size_t totlen = 0; // init for gcc
- linenr_T lnum;
- colnr_T col;
+ linenr_T lnum = 0;
+ colnr_T col = 0;
size_t i; // index in y_array[]
MotionType y_type;
size_t y_size;
@@ -3286,11 +3360,11 @@ error:
curbuf->b_op_start.lnum++;
}
// Skip mark_adjust when adding lines after the last one, there
- // can't be marks there. But still needed in diff mode.
+ // can't be marks there.
if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines
- < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
+ < curbuf->b_ml.ml_line_count) {
mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise),
- (linenr_T)MAXLNUM, nr_lines, 0L, false);
+ (linenr_T)MAXLNUM, nr_lines, 0L, false, kExtmarkUndo);
}
// note changed text for displaying and folding
@@ -3352,6 +3426,8 @@ end:
/* If the cursor is past the end of the line put it at the end. */
adjust_cursor_eol();
+
+ extmarks_do_put(dir, totlen, y_type, lnum, col);
}
/*
@@ -3694,7 +3770,10 @@ int do_join(size_t count,
if (insert_space && t > 0) {
curr = skipwhite(curr);
- if (*curr != ')' && currsize != 0 && endcurr1 != TAB
+ if (*curr != NUL
+ && *curr != ')'
+ && sumsize != 0
+ && endcurr1 != TAB
&& (!has_format_option(FO_MBYTE_JOIN)
|| (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100))
&& (!has_format_option(FO_MBYTE_JOIN2)
@@ -3745,6 +3824,7 @@ int do_join(size_t count,
* column. This is not Vi compatible, but Vi deletes the marks, thus that
* should not really be a problem.
*/
+
for (t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
@@ -3756,12 +3836,18 @@ int do_join(size_t count,
// If deleting more spaces than adding, the cursor moves no more than
// what is added if it is inside these spaces.
const int spaces_removed = (int)((curr - curr_start) - spaces[t]);
+ linenr_T lnum = curwin->w_cursor.lnum + t;
+ colnr_T mincol = (colnr_T)0;
+ long lnum_amount = (linenr_T)-t;
+ long col_amount = (long)(cend - newp - spaces_removed);
+
+ mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed,
+ kExtmarkUndo);
- mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t,
- (long)(cend - newp - spaces_removed), spaces_removed);
if (t == 0) {
break;
}
+
curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1));
if (remove_comments)
curr += comments[t - 1];
@@ -3769,6 +3855,7 @@ int do_join(size_t count,
curr = skipwhite(curr);
currsize = (int)STRLEN(curr);
}
+
ml_replace(curwin->w_cursor.lnum, newp, false);
if (setmark) {
@@ -4189,14 +4276,14 @@ format_lines(
if (next_leader_len > 0) {
(void)del_bytes(next_leader_len, false, false);
mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L,
- (long)-next_leader_len, 0);
+ (long)-next_leader_len, 0, kExtmarkNOOP);
} else if (second_indent > 0) { // the "leader" for FO_Q_SECOND
int indent = (int)getwhitecols_curline();
if (indent > 0) {
- (void)del_bytes(indent, FALSE, FALSE);
+ (void)del_bytes(indent, false, false);
mark_col_adjust(curwin->w_cursor.lnum,
- (colnr_T)0, 0L, (long)-indent, 0);
+ (colnr_T)0, 0L, (long)-indent, 0, kExtmarkNOOP);
}
}
curwin->w_cursor.lnum--;
@@ -4539,7 +4626,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
{
int col;
- char_u *buf1;
+ char_u *buf1 = NULL;
char_u buf2[NUMBUFLEN];
int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin
static bool hexupper = false; // 0xABC
@@ -4848,7 +4935,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
*ptr = NUL;
STRCAT(buf1, buf2);
ins_str(buf1); // insert the new number
- xfree(buf1);
endpos = curwin->w_cursor;
if (curwin->w_cursor.col) {
curwin->w_cursor.col--;
@@ -4863,6 +4949,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
theend:
+ xfree(buf1);
if (visual) {
curwin->w_cursor = save_cursor;
} else if (did_change) {
@@ -5117,8 +5204,7 @@ void write_reg_contents_lst(int name, char_u **strings,
/// write_reg_contents_ex - store `str` in register `name`
///
-/// If `str` ends in '\n' or '\r', use linewise, otherwise use
-/// characterwise.
+/// If `str` ends in '\n' or '\r', use linewise, otherwise use charwise.
///
/// @warning when `name` is '/', `len` and `must_append` are ignored. This
/// means that `str` MUST be NUL-terminated.
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 20351d3908..e48ed201e8 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -2184,6 +2184,7 @@ static void didset_options(void)
(void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false);
(void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true);
(void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true);
+ (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true);
(void)spell_check_msm();
(void)spell_check_sps();
(void)compile_cap_prog(curwin->w_s);
@@ -2205,10 +2206,10 @@ static void didset_options2(void)
(void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true);
// Parse default for 'fillchars'.
- (void)set_chars_option(curwin, &curwin->w_p_fcs);
+ (void)set_chars_option(curwin, &curwin->w_p_fcs, true);
// Parse default for 'listchars'.
- (void)set_chars_option(curwin, &curwin->w_p_lcs);
+ (void)set_chars_option(curwin, &curwin->w_p_lcs, true);
// Parse default for 'wildmode'.
check_opt_wim();
@@ -2632,6 +2633,10 @@ did_set_string_option(
if (strcmp((char *)(*varp), HIGHLIGHT_INIT) != 0) {
errmsg = e_unsupportedoption;
}
+ } else if (varp == &p_jop) { // 'jumpoptions'
+ if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) {
+ errmsg = e_invarg;
+ }
} else if (gvarp == &p_nf) { // 'nrformats'
if (check_opt_strings(*varp, p_nf_values, true) != OK) {
errmsg = e_invarg;
@@ -2663,11 +2668,11 @@ did_set_string_option(
errmsg = e_invarg;
} else {
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (set_chars_option(wp, &wp->w_p_lcs) != NULL) {
+ if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) {
errmsg = (char_u *)_("E834: Conflicts with value of 'listchars'");
goto ambw_end;
}
- if (set_chars_option(wp, &wp->w_p_fcs) != NULL) {
+ if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) {
errmsg = (char_u *)_("E835: Conflicts with value of 'fillchars'");
goto ambw_end;
}
@@ -2868,10 +2873,26 @@ ambw_end:
}
s = skip_to_option_part(s);
}
- } else if (varp == &curwin->w_p_lcs) { // 'listchars'
- errmsg = set_chars_option(curwin, varp);
- } else if (varp == &curwin->w_p_fcs) { // 'fillchars'
- errmsg = set_chars_option(curwin, varp);
+ } else if (varp == &p_lcs) { // 'listchars'
+ errmsg = set_chars_option(curwin, varp, false);
+ if (!errmsg) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ set_chars_option(wp, &wp->w_p_lcs, true);
+ }
+ }
+ redraw_all_later(NOT_VALID);
+ } else if (varp == &curwin->w_p_lcs) { // local 'listchars'
+ errmsg = set_chars_option(curwin, varp, true);
+ } else if (varp == &p_fcs) { // 'fillchars'
+ errmsg = set_chars_option(curwin, varp, false);
+ if (!errmsg) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ set_chars_option(wp, &wp->w_p_fcs, true);
+ }
+ }
+ redraw_all_later(NOT_VALID);
+ } else if (varp == &curwin->w_p_fcs) { // local 'fillchars'
+ errmsg = set_chars_option(curwin, varp, true);
} else if (varp == &p_cedit) { // 'cedit'
errmsg = check_cedit();
} else if (varp == &p_vfile) { // 'verbosefile'
@@ -3501,7 +3522,7 @@ skip:
///
/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs
/// @return error message, NULL if it's OK.
-static char_u *set_chars_option(win_T *wp, char_u **varp)
+static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
{
int round, i, len, entries;
char_u *p, *s;
@@ -3536,12 +3557,18 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
{ &wp->w_p_lcs_chars.conceal, "conceal", NUL },
};
- if (varp == &wp->w_p_lcs) {
+ if (varp == &p_lcs || varp == &wp->w_p_lcs) {
tab = lcs_tab;
entries = ARRAY_SIZE(lcs_tab);
+ if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) {
+ varp = &p_lcs;
+ }
} else {
tab = fcs_tab;
entries = ARRAY_SIZE(fcs_tab);
+ if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) {
+ varp = &p_fcs;
+ }
if (*p_ambw == 'd') {
// XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is
// forbidden (TUI limitation?). Set old defaults.
@@ -3554,7 +3581,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
}
// first round: check for valid value, second round: assign values
- for (round = 0; round <= 1; round++) {
+ for (round = 0; round <= set ? 1 : 0; round++) {
if (round > 0) {
// After checking that the value is valid: set defaults
for (i = 0; i < entries; i++) {
@@ -3562,7 +3589,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
*(tab[i].cp) = tab[i].def;
}
}
- if (varp == &wp->w_p_lcs) {
+ if (varp == &p_lcs || varp == &wp->w_p_lcs) {
wp->w_p_lcs_chars.tab1 = NUL;
wp->w_p_lcs_chars.tab3 = NUL;
}
@@ -5173,6 +5200,13 @@ void ui_refresh_options(void)
}
ui_call_option_set(name, value);
}
+ if (p_mouse != NULL) {
+ if (*p_mouse == NUL) {
+ ui_call_mouse_off();
+ } else {
+ setmouse();
+ }
+ }
}
/*
@@ -5562,6 +5596,16 @@ void unset_global_local_option(char *name, void *from)
case PV_MENC:
clear_string_option(&buf->b_p_menc);
break;
+ case PV_LCS:
+ clear_string_option(&((win_T *)from)->w_p_lcs);
+ set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true);
+ redraw_win_later((win_T *)from, NOT_VALID);
+ break;
+ case PV_FCS:
+ clear_string_option(&((win_T *)from)->w_p_fcs);
+ set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true);
+ redraw_win_later((win_T *)from, NOT_VALID);
+ break;
}
}
@@ -5598,6 +5642,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
case PV_LW: return (char_u *)&(curbuf->b_p_lw);
case PV_BKC: return (char_u *)&(curbuf->b_p_bkc);
case PV_MENC: return (char_u *)&(curbuf->b_p_menc);
+ case PV_FCS: return (char_u *)&(curwin->w_p_fcs);
+ case PV_LCS: return (char_u *)&(curwin->w_p_lcs);
}
return NULL; // "cannot happen"
}
@@ -5656,6 +5702,10 @@ static char_u *get_varp(vimoption_T *p)
? (char_u *)&(curbuf->b_p_lw) : p->var;
case PV_MENC: return *curbuf->b_p_menc != NUL
? (char_u *)&(curbuf->b_p_menc) : p->var;
+ case PV_FCS: return *curwin->w_p_fcs != NUL
+ ? (char_u *)&(curwin->w_p_fcs) : p->var;
+ case PV_LCS: return *curwin->w_p_lcs != NUL
+ ? (char_u *)&(curwin->w_p_lcs) : p->var;
case PV_ARAB: return (char_u *)&(curwin->w_p_arab);
case PV_LIST: return (char_u *)&(curwin->w_p_list);
@@ -5753,8 +5803,6 @@ static char_u *get_varp(vimoption_T *p)
case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap);
case PV_SCL: return (char_u *)&(curwin->w_p_scl);
case PV_WINHL: return (char_u *)&(curwin->w_p_winhl);
- case PV_FCS: return (char_u *)&(curwin->w_p_fcs);
- case PV_LCS: return (char_u *)&(curwin->w_p_lcs);
case PV_WINBL: return (char_u *)&(curwin->w_p_winbl);
default: IEMSG(_("E356: get_varp ERROR"));
}
@@ -5896,8 +5944,8 @@ void didset_window_options(win_T *wp)
{
check_colorcolumn(wp);
briopt_check(wp);
- set_chars_option(wp, &wp->w_p_fcs);
- set_chars_option(wp, &wp->w_p_lcs);
+ set_chars_option(wp, &wp->w_p_fcs, true);
+ set_chars_option(wp, &wp->w_p_lcs, true);
parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl
wp->w_grid.blending = wp->w_p_winbl > 0;
}
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index e5a3c0bd95..4f9f32794b 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -473,6 +473,12 @@ EXTERN char_u *p_isf; // 'isfname'
EXTERN char_u *p_isi; // 'isident'
EXTERN char_u *p_isp; // 'isprint'
EXTERN int p_js; // 'joinspaces'
+EXTERN char_u *p_jop; // 'jumpooptions'
+EXTERN unsigned jop_flags;
+#ifdef IN_OPTION_C
+static char *(p_jop_values[]) = { "stack", NULL };
+#endif
+#define JOP_STACK 0x01
EXTERN char_u *p_kp; // 'keywordprg'
EXTERN char_u *p_km; // 'keymodel'
EXTERN char_u *p_langmap; // 'langmap'
@@ -484,6 +490,7 @@ EXTERN long p_linespace; // 'linespace'
EXTERN char_u *p_lispwords; // 'lispwords'
EXTERN long p_ls; // 'laststatus'
EXTERN long p_stal; // 'showtabline'
+EXTERN char_u *p_lcs; // 'listchars'
EXTERN int p_lz; // 'lazyredraw'
EXTERN int p_lpl; // 'loadplugins'
@@ -611,13 +618,14 @@ EXTERN char_u *p_swb; // 'switchbuf'
EXTERN unsigned swb_flags;
#ifdef IN_OPTION_C
static char *(p_swb_values[]) =
- { "useopen", "usetab", "split", "newtab", "vsplit", NULL };
+ { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL };
#endif
#define SWB_USEOPEN 0x001
#define SWB_USETAB 0x002
#define SWB_SPLIT 0x004
#define SWB_NEWTAB 0x008
#define SWB_VSPLIT 0x010
+#define SWB_USELAST 0x020
EXTERN int p_tbs; ///< 'tagbsearch'
EXTERN char_u *p_tc; ///< 'tagcase'
EXTERN unsigned tc_flags; ///< flags from 'tagcase'
@@ -652,6 +660,7 @@ EXTERN long p_ul; ///< 'undolevels'
EXTERN long p_ur; ///< 'undoreload'
EXTERN long p_uc; ///< 'updatecount'
EXTERN long p_ut; ///< 'updatetime'
+EXTERN char_u *p_fcs; ///< 'fillchar'
EXTERN char_u *p_shada; ///< 'shada'
EXTERN char *p_shadafile; ///< 'shadafile'
EXTERN char_u *p_vdir; ///< 'viewdir'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index e96b3f8e02..93bfc1c0b1 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -612,7 +612,7 @@ return {
alloced=true,
redraw={'current_window'},
varname='p_dip',
- defaults={if_true={vi="internal,filler"}}
+ defaults={if_true={vi="internal,filler,closeoff"}}
},
{
full_name='digraph', abbreviation='dg',
@@ -804,11 +804,12 @@ return {
},
{
full_name='fillchars', abbreviation='fcs',
- type='string', list='onecomma', scope={'window'},
+ type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
vi_def=true,
alloced=true,
redraw={'current_window'},
+ varname='p_fcs',
defaults={if_true={vi=''}}
},
{
@@ -1299,6 +1300,14 @@ return {
defaults={if_true={vi=true}}
},
{
+ full_name='jumpoptions', abbreviation='jop',
+ type='string', list='onecomma', scope={'global'},
+ deny_duplicates=true,
+ varname='p_jop',
+ vim=true,
+ defaults={if_true={vim=''}}
+ },
+ {
full_name='keymap', abbreviation='kmp',
type='string', scope={'buffer'},
normal_fname_chars=true,
@@ -1420,11 +1429,12 @@ return {
},
{
full_name='listchars', abbreviation='lcs',
- type='string', list='onecomma', scope={'window'},
+ type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
vim=true,
alloced=true,
redraw={'current_window'},
+ varname='p_lcs',
defaults={if_true={vi="eol:$", vim="tab:> ,trail:-,nbsp:+"}}
},
{
@@ -2323,9 +2333,9 @@ return {
full_name='startofline', abbreviation='sol',
type='bool', scope={'global'},
vi_def=true,
- vim=true,
+ vim=false,
varname='p_sol',
- defaults={if_true={vi=true}}
+ defaults={if_true={vi=false}}
},
{
full_name='statusline', abbreviation='stl',
diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c
index f0fadb16f2..2783411574 100644
--- a/src/nvim/os/dl.c
+++ b/src/nvim/os/dl.c
@@ -19,15 +19,15 @@
/// string -> int
typedef void (*gen_fn)(void);
typedef const char *(*str_str_fn)(const char *str);
-typedef int64_t (*str_int_fn)(const char *str);
+typedef int (*str_int_fn)(const char *str);
typedef const char *(*int_str_fn)(int64_t i);
-typedef int64_t (*int_int_fn)(int64_t i);
+typedef int (*int_int_fn)(int64_t i);
/// os_libcall - call a function in a dynamic loadable library
///
/// an example of calling a function that takes a string and returns an int:
///
-/// int64_t int_out = 0;
+/// int int_out = 0;
/// os_libcall("mylib.so", "somefn", "string-argument", 0, NULL, &int_out);
///
/// @param libname the name of the library to load (e.g.: libsomething.so)
@@ -43,7 +43,7 @@ bool os_libcall(const char *libname,
const char *argv,
int64_t argi,
char **str_out,
- int64_t *int_out)
+ int *int_out)
{
if (!libname || !funcname) {
return false;
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index eb86cb8ac7..360609c50d 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -184,39 +184,142 @@ int os_unsetenv(const char *name)
return r == 0 ? 0 : -1;
}
+/// Returns number of variables in the current environment variables block
+size_t os_get_fullenv_size(void)
+{
+ size_t len = 0;
+#ifdef _WIN32
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ wchar_t *p = envstrings;
+ size_t l;
+ if (!envstrings) {
+ return len;
+ }
+ // GetEnvironmentStringsW() result has this format:
+ // var1=value1\0var2=value2\0...varN=valueN\0\0
+ while ((l = wcslen(p)) != 0) {
+ p += l + 1;
+ len++;
+ }
+
+ FreeEnvironmentStringsW(envstrings);
+#else
+# if defined(HAVE__NSGETENVIRON)
+ char **environ = *_NSGetEnviron();
+# else
+ extern char **environ;
+# endif
+
+ while (environ[len] != NULL) {
+ len++;
+ }
+
+#endif
+ return len;
+}
+
+void os_free_fullenv(char **env)
+{
+ if (!env) { return; }
+ for (char **it = env; *it; it++) {
+ XFREE_CLEAR(*it);
+ }
+ xfree(env);
+}
+
+/// Copies the current environment variables into the given array, `env`. Each
+/// array element is of the form "NAME=VALUE".
+/// Result must be freed by the caller.
+///
+/// @param[out] env array to populate with environment variables
+/// @param env_size size of `env`, @see os_fullenv_size
+void os_copy_fullenv(char **env, size_t env_size)
+{
+#ifdef _WIN32
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ if (!envstrings) {
+ return;
+ }
+ wchar_t *p = envstrings;
+ size_t i = 0;
+ size_t l;
+ // GetEnvironmentStringsW() result has this format:
+ // var1=value1\0var2=value2\0...varN=valueN\0\0
+ while ((l = wcslen(p)) != 0 && i < env_size) {
+ char *utf8_str;
+ int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
+ if (conversion_result != 0) {
+ EMSG2("utf16_to_utf8 failed: %d", conversion_result);
+ break;
+ }
+ p += l + 1;
+
+ env[i] = utf8_str;
+ i++;
+ }
+
+ FreeEnvironmentStringsW(envstrings);
+#else
+# if defined(HAVE__NSGETENVIRON)
+ char **environ = *_NSGetEnviron();
+# else
+ extern char **environ;
+# endif
+
+ size_t i = 0;
+ while (environ[i] != NULL && i < env_size) {
+ env[i] = xstrdup(environ[i]);
+ i++;
+ }
+#endif
+}
+
+/// Copy value of the environment variable at `index` in the current
+/// environment variables block.
+/// Result must be freed by the caller.
+///
+/// @param index nth item in environment variables block
+/// @return [allocated] environment variable's value, or NULL
char *os_getenvname_at_index(size_t index)
{
#ifdef _WIN32
- wchar_t *env = GetEnvironmentStringsW();
- if (!env) {
+ wchar_t *envstrings = GetEnvironmentStringsW();
+ if (!envstrings) {
return NULL;
}
+ wchar_t *p = envstrings;
char *name = NULL;
- size_t current_index = 0;
+ size_t i = 0;
+ size_t l;
// GetEnvironmentStringsW() result has this format:
// var1=value1\0var2=value2\0...varN=valueN\0\0
- for (wchar_t *it = env; *it != L'\0' || *(it + 1) != L'\0'; it++) {
- if (index == current_index) {
+ while ((l = wcslen(p)) != 0 && i <= index) {
+ if (i == index) {
char *utf8_str;
- int conversion_result = utf16_to_utf8(it, -1, &utf8_str);
+ int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
if (conversion_result != 0) {
EMSG2("utf16_to_utf8 failed: %d", conversion_result);
break;
}
- size_t namesize = 0;
- while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) {
- namesize++;
- }
- name = (char *)vim_strnsave((char_u *)utf8_str, namesize);
+
+ // Some Windows env vars start with =, so skip over that to find the
+ // separator between name/value
+ const char * const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0),
+ '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - utf8_str;
+ assert(len > 0);
+ name = xstrndup(utf8_str, (size_t)len);
xfree(utf8_str);
break;
}
- if (*it == L'\0') {
- current_index++;
- }
+
+ // Advance past the name and NUL
+ p += l + 1;
+ i++;
}
- FreeEnvironmentStringsW(env);
+ FreeEnvironmentStringsW(envstrings);
return name;
#else
# if defined(HAVE__NSGETENVIRON)
@@ -224,19 +327,20 @@ char *os_getenvname_at_index(size_t index)
# else
extern char **environ;
# endif
- // Check if index is inside the environ array and is not the last element.
+
+ // check if index is inside the environ array
for (size_t i = 0; i <= index; i++) {
if (environ[i] == NULL) {
return NULL;
}
}
char *str = environ[index];
- size_t namesize = 0;
- while (str[namesize] != '=' && str[namesize] != NUL) {
- namesize++;
- }
- char *name = (char *)vim_strnsave((char_u *)str, namesize);
- return name;
+ assert(str != NULL);
+ const char * const end = strchr(str, '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - str;
+ assert(len > 0);
+ return xstrndup(str, (size_t)len);
#endif
}
diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c
index bd5b9b4506..4f525bed9a 100644
--- a/src/nvim/os/tty.c
+++ b/src/nvim/os/tty.c
@@ -6,6 +6,7 @@
//
#include "nvim/os/os.h"
+#include "nvim/os/tty.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/tty.c.generated.h"
diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c
index ded575529f..0f44df2188 100644
--- a/src/nvim/os_unix.c
+++ b/src/nvim/os_unix.c
@@ -412,7 +412,7 @@ int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file,
fseek(fd, 0L, SEEK_SET);
buffer = xmalloc(len + 1);
// fread() doesn't terminate buffer with NUL;
- // appropiate termination (not always NUL) is done below.
+ // appropriate termination (not always NUL) is done below.
size_t readlen = fread((char *)buffer, 1, len, fd);
fclose(fd);
os_remove((char *)tempname);
diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po
index 61ad72d7e0..79048eac39 100644
--- a/src/nvim/po/af.po
+++ b/src/nvim/po/af.po
@@ -100,7 +100,7 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Kan nie vóór eerste buffer gaan nie"
#, fuzzy, c-format
-#~ msgid "E89: %s will be killed(add ! to override)"
+#~ msgid "E89: %s will be killed (add ! to override)"
#~ msgstr "E189: \"%s\" bestaan (gebruik ! om te dwing)"
#, c-format
diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim
index eae27ef74d..650c6155e2 100644
--- a/src/nvim/po/check.vim
+++ b/src/nvim/po/check.vim
@@ -47,6 +47,17 @@ let wsv = winsaveview()
let error = 0
while 1
+ let lnum = line('.')
+ if getline(lnum) =~ 'msgid "Text;.*;"'
+ if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"'
+ echomsg 'Mismatching ; in line ' . (lnum + 1)
+ echomsg 'Did you forget the trailing semicolon?'
+ if error == 0
+ let error = lnum + 1
+ endif
+ endif
+ endif
+
if getline(line('.') - 1) !~ "no-c-format"
" go over the "msgid" and "msgid_plural" lines
let prevfromline = 'foobar'
diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po
index 4612988c95..f568a34b3c 100644
--- a/src/nvim/po/fi.po
+++ b/src/nvim/po/fi.po
@@ -323,7 +323,7 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Ensimmäisen puskurin ohi ei voi edetä"
#, fuzzy, c-format
-#~ msgid "E89: %s will be killed(add ! to override)"
+#~ msgid "E89: %s will be killed (add ! to override)"
#~ msgstr "E189: %s on jo olemassa (lisää komentoon ! ohittaaksesi)"
#, fuzzy, c-format
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 211d38e53a..19ea8e897a 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -87,8 +87,8 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Це вже найперший буфер"
#, c-format
-msgid "E89: %s will be killed(add ! to override)"
-msgstr "E89: «%s» буде вбито(! щоб не зважати)"
+msgid "E89: %s will be killed (add ! to override)"
+msgstr "E89: «%s» буде вбито (! щоб не зважати)"
#, c-format
msgid ""
diff --git a/src/nvim/pos.h b/src/nvim/pos.h
index 47d253e083..8e86ea08c5 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos.h
@@ -14,6 +14,10 @@ typedef int colnr_T;
enum { MAXLNUM = 0x7fffffff };
/// Maximal column number, 31 bits
enum { MAXCOL = 0x7fffffff };
+// Minimum line number
+enum { MINLNUM = 1 };
+// minimum column number
+enum { MINCOL = 1 };
/*
* position in file or buffer
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index cf5194d16f..5d30ca624f 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -1,9 +1,7 @@
// 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
-/*
- * quickfix.c: functions for quickfix mode, using a file with error messages
- */
+// quickfix.c: functions for quickfix mode, using a file with error messages
#include <assert.h>
#include <inttypes.h>
@@ -53,9 +51,7 @@ struct dir_stack_T {
char_u *dirname;
};
-/*
- * For each error the next struct is allocated and linked in a list.
- */
+// For each error the next struct is allocated and linked in a list.
typedef struct qfline_S qfline_T;
struct qfline_S {
qfline_T *qf_next; ///< pointer to next error in the list
@@ -74,9 +70,7 @@ struct qfline_S {
char_u qf_valid; ///< valid error message detected
};
-/*
- * There is a stack of error lists.
- */
+// There is a stack of error lists.
#define LISTCOUNT 10
#define INVALID_QFIDX (-1)
@@ -120,15 +114,13 @@ typedef struct qf_list_S {
/// Quickfix/Location list stack definition
/// Contains a list of quickfix/location lists (qf_list_T)
struct qf_info_S {
- /*
- * Count of references to this list. Used only for location lists.
- * When a location list window reference this list, qf_refcount
- * will be 2. Otherwise, qf_refcount will be 1. When qf_refcount
- * reaches 0, the list is freed.
- */
+ // Count of references to this list. Used only for location lists.
+ // When a location list window reference this list, qf_refcount
+ // will be 2. Otherwise, qf_refcount will be 1. When qf_refcount
+ // reaches 0, the list is freed.
int qf_refcount;
- int qf_listcount; /* current number of lists */
- int qf_curlist; /* current error list */
+ int qf_listcount; // current number of lists
+ int qf_curlist; // current error list
qf_list_T qf_lists[LISTCOUNT];
qfltype_T qfl_type; // type of list
};
@@ -138,31 +130,29 @@ static unsigned last_qf_id = 0; // Last Used quickfix list id
#define FMT_PATTERNS 11 // maximum number of % recognized
-/*
- * Structure used to hold the info of one part of 'errorformat'
- */
+// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
struct efm_S {
- regprog_T *prog; /* pre-formatted part of 'errorformat' */
- efm_T *next; /* pointer to next (NULL if last) */
- char_u addr[FMT_PATTERNS]; /* indices of used % patterns */
- char_u prefix; /* prefix of this format line: */
- /* 'D' enter directory */
- /* 'X' leave directory */
- /* 'A' start of multi-line message */
- /* 'E' error message */
- /* 'W' warning message */
- /* 'I' informational message */
- /* 'C' continuation line */
- /* 'Z' end of multi-line message */
- /* 'G' general, unspecific message */
- /* 'P' push file (partial) message */
- /* 'Q' pop/quit file (partial) message */
- /* 'O' overread (partial) message */
- char_u flags; /* additional flags given in prefix */
- /* '-' do not include this line */
- /* '+' include whole line in message */
- int conthere; /* %> used */
+ regprog_T *prog; // pre-formatted part of 'errorformat'
+ efm_T *next; // pointer to next (NULL if last)
+ char_u addr[FMT_PATTERNS]; // indices of used % patterns
+ char_u prefix; // prefix of this format line:
+ // 'D' enter directory
+ // 'X' leave directory
+ // 'A' start of multi-line message
+ // 'E' error message
+ // 'W' warning message
+ // 'I' informational message
+ // 'C' continuation line
+ // 'Z' end of multi-line message
+ // 'G' general, unspecific message
+ // 'P' push file (partial) message
+ // 'Q' pop/quit file (partial) message
+ // 'O' overread (partial) message
+ char_u flags; // additional flags given in prefix
+ // '-' do not include this line
+ // '+' include whole line in message
+ int conthere; // %> used
};
/// List of location lists to be deleted.
@@ -221,7 +211,7 @@ static char_u *e_no_more_items = (char_u *)N_("E553: No more items");
// Quickfix window check helper macro
#define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL)
-/* Location list window check helper macro */
+// Location list window check helper macro
#define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)
// Quickfix and location list stack check helper macros
@@ -1156,16 +1146,12 @@ qf_init_ext(
goto error2;
}
- /*
- * got_int is reset here, because it was probably set when killing the
- * ":make" command, but we still want to read the errorfile then.
- */
- got_int = FALSE;
+ // got_int is reset here, because it was probably set when killing the
+ // ":make" command, but we still want to read the errorfile then.
+ got_int = false;
- /*
- * Read the lines in the error file one by one.
- * Try to recognize one of the error formats in each line.
- */
+ // Read the lines in the error file one by one.
+ // Try to recognize one of the error formats in each line.
while (!got_int) {
status = qf_init_process_nextline(qfl, fmt_first, &state, &fields);
if (status == QF_END_OF_INPUT) { // end of input
@@ -1263,10 +1249,8 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title)
qf_free(&qi->qf_lists[--qi->qf_listcount]);
}
- /*
- * When the stack is full, remove to oldest entry
- * Otherwise, add a new entry.
- */
+ // When the stack is full, remove to oldest entry
+ // Otherwise, add a new entry.
if (qi->qf_listcount == LISTCOUNT) {
qf_free(&qi->qf_lists[0]);
for (i = 1; i < LISTCOUNT; i++) {
@@ -1714,7 +1698,7 @@ static void ll_free_all(qf_info_T **pqi)
qi = *pqi;
if (qi == NULL)
return;
- *pqi = NULL; /* Remove reference to this list */
+ *pqi = NULL; // Remove reference to this list
qi->qf_refcount--;
if (qi->qf_refcount < 1) {
@@ -1739,7 +1723,7 @@ void qf_free_all(win_T *wp)
qf_info_T *qi = &ql_info;
if (wp != NULL) {
- /* location list */
+ // location list
ll_free_all(&wp->w_llist);
ll_free_all(&wp->w_llist_ref);
} else {
@@ -1890,15 +1874,15 @@ static qf_info_T *qf_alloc_stack(qfltype_T qfltype)
/// Return the location list stack for window 'wp'.
/// If not present, allocate a location list stack
static qf_info_T *ll_get_or_alloc_list(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- if (IS_LL_WINDOW(wp))
- /* For a location list window, use the referenced location list */
+ if (IS_LL_WINDOW(wp)) {
+ // For a location list window, use the referenced location list
return wp->w_llist_ref;
+ }
- /*
- * For a non-location list window, w_llist_ref should not point to a
- * location list.
- */
+ // For a non-location list window, w_llist_ref should not point to a
+ // location list.
ll_free_all(&wp->w_llist_ref);
if (wp->w_llist == NULL) {
@@ -1931,17 +1915,14 @@ static qf_info_T * qf_cmd_get_stack(exarg_T *eap, int print_emsg)
/// Get the quickfix/location list stack to use for the specified Ex command.
/// For a location list command, returns the stack for the current window.
/// If the location list is not present, then allocates a new one.
-/// Returns NULL if the allocation fails. For a location list command, sets
-/// 'pwinp' to curwin.
-static qf_info_T * qf_cmd_get_or_alloc_stack(exarg_T *eap, win_T **pwinp)
+/// For a location list command, sets 'pwinp' to curwin.
+static qf_info_T *qf_cmd_get_or_alloc_stack(const exarg_T *eap, win_T **pwinp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
qf_info_T *qi = &ql_info;
if (is_loclist_cmd(eap->cmdidx)) {
qi = ll_get_or_alloc_list(curwin);
- if (qi == NULL) {
- return NULL;
- }
*pwinp = curwin;
}
@@ -2135,22 +2116,21 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
{
struct dir_stack_T *ds_ptr;
- /* allocate new stack element and hook it in */
+ // allocate new stack element and hook it in
struct dir_stack_T *ds_new = xmalloc(sizeof(struct dir_stack_T));
ds_new->next = *stackptr;
*stackptr = ds_new;
- /* store directory on the stack */
+ // store directory on the stack
if (vim_isAbsName(dirbuf)
|| (*stackptr)->next == NULL
- || (*stackptr && is_file_stack))
+ || (*stackptr && is_file_stack)) {
(*stackptr)->dirname = vim_strsave(dirbuf);
- else {
- /* Okay we don't have an absolute path.
- * dirbuf must be a subdir of one of the directories on the stack.
- * Let's search...
- */
+ } else {
+ // Okay we don't have an absolute path.
+ // dirbuf must be a subdir of one of the directories on the stack.
+ // Let's search...
ds_new = (*stackptr)->next;
(*stackptr)->dirname = NULL;
while (ds_new) {
@@ -2163,7 +2143,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
ds_new = ds_new->next;
}
- /* clean up all dirs we already left */
+ // clean up all dirs we already left
while ((*stackptr)->next != ds_new) {
ds_ptr = (*stackptr)->next;
(*stackptr)->next = (*stackptr)->next->next;
@@ -2171,7 +2151,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
xfree(ds_ptr);
}
- /* Nothing found -> it must be on top level */
+ // Nothing found -> it must be on top level
if (ds_new == NULL) {
xfree((*stackptr)->dirname);
(*stackptr)->dirname = vim_strsave(dirbuf);
@@ -2189,18 +2169,16 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
}
-/*
- * pop dirbuf from the directory stack and return previous directory or NULL if
- * stack is empty
- */
+// pop dirbuf from the directory stack and return previous directory or NULL if
+// stack is empty
static char_u *qf_pop_dir(struct dir_stack_T **stackptr)
{
struct dir_stack_T *ds_ptr;
- /* TODO: Should we check if dirbuf is the directory on top of the stack?
- * What to do if it isn't? */
+ // TODO(vim): Should we check if dirbuf is the directory on top of the stack?
+ // What to do if it isn't?
- /* pop top element and free it */
+ // pop top element and free it
if (*stackptr != NULL) {
ds_ptr = *stackptr;
*stackptr = (*stackptr)->next;
@@ -2208,13 +2186,11 @@ static char_u *qf_pop_dir(struct dir_stack_T **stackptr)
xfree(ds_ptr);
}
- /* return NEW top element as current dir or NULL if stack is empty*/
+ // return NEW top element as current dir or NULL if stack is empty
return *stackptr ? (*stackptr)->dirname : NULL;
}
-/*
- * clean up directory stack
- */
+// clean up directory stack
static void qf_clean_dir_stack(struct dir_stack_T **stackptr)
{
struct dir_stack_T *ds_ptr;
@@ -2619,8 +2595,11 @@ static void qf_goto_win_with_qfl_file(int qf_fnum)
if (IS_QF_WINDOW(win)) {
// Didn't find it, go to the window before the quickfix
- // window.
- if (altwin != NULL) {
+ // window, unless 'switchbuf' contains 'uselast': in this case we
+ // try to jump to the previously used window first.
+ if ((swb_flags & SWB_USELAST) && win_valid(prevwin)) {
+ win = prevwin;
+ } else if (altwin != NULL) {
win = altwin;
} else if (curwin->w_prev != NULL) {
win = curwin->w_prev;
@@ -2889,10 +2868,8 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
}
}
- /*
- * If there is a file name,
- * read the wanted file if needed, and check autowrite etc.
- */
+ // If there is a file name,
+ // read the wanted file if needed, and check autowrite etc.
old_curbuf = curbuf;
old_lnum = curwin->w_cursor.lnum;
@@ -2940,8 +2917,8 @@ theend:
qfl->qf_index = qf_index;
}
if (p_swb != old_swb && opened_window) {
- /* Restore old 'switchbuf' value, but not when an autocommand or
- * modeline has changed the value. */
+ // Restore old 'switchbuf' value, but not when an autocommand or
+ // modeline has changed the value.
if (p_swb == empty_option) {
p_swb = old_swb;
swb_flags = old_swb_flags;
@@ -3040,10 +3017,8 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel)
ui_flush(); // show one line at a time
}
-/*
- * ":clist": list all errors
- * ":llist": list all locations
- */
+// ":clist": list all errors
+// ":llist": list all locations
void qf_list(exarg_T *eap)
{
qf_list_T *qfl;
@@ -3118,10 +3093,8 @@ void qf_list(exarg_T *eap)
}
}
-/*
- * Remove newlines and leading whitespace from an error message.
- * Put the result in "buf[bufsize]".
- */
+// Remove newlines and leading whitespace from an error message.
+// Put the result in "buf[bufsize]".
static void qf_fmt_text(const char_u *restrict text, char_u *restrict buf,
int bufsize)
FUNC_ATTR_NONNULL_ALL
@@ -3279,9 +3252,7 @@ static void qf_free(qf_list_T *qfl)
qfl->qf_changedtick = 0L;
}
-/*
- * qf_mark_adjust: adjust marks
- */
+// qf_mark_adjust: adjust marks
bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
long amount_after)
{
@@ -3323,21 +3294,19 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
return found_one;
}
-/*
- * Make a nice message out of the error character and the error number:
- * char number message
- * e or E 0 " error"
- * w or W 0 " warning"
- * i or I 0 " info"
- * 0 0 ""
- * other 0 " c"
- * e or E n " error n"
- * w or W n " warning n"
- * i or I n " info n"
- * 0 n " error n"
- * other n " c n"
- * 1 x "" :helpgrep
- */
+// Make a nice message out of the error character and the error number:
+// char number message
+// e or E 0 " error"
+// w or W 0 " warning"
+// i or I 0 " info"
+// 0 0 ""
+// other 0 " c"
+// e or E n " error n"
+// w or W n " warning n"
+// i or I n " info n"
+// 0 n " error n"
+// other n " c n"
+// 1 x "" :helpgrep
static char_u *qf_types(int c, int nr)
{
static char_u buf[20];
@@ -3398,12 +3367,10 @@ void qf_view_result(bool split)
do_cmdline_cmd((IS_LL_WINDOW(curwin) ? ".ll" : ".cc"));
}
-/*
- * ":cwindow": open the quickfix window if we have errors to display,
- * close it if not.
- * ":lwindow": open the location list window if we have locations to display,
- * close it if not.
- */
+// ":cwindow": open the quickfix window if we have errors to display,
+// close it if not.
+// ":lwindow": open the location list window if we have locations to display,
+// close it if not.
void ex_cwindow(exarg_T *eap)
{
qf_info_T *qi;
@@ -3416,14 +3383,12 @@ void ex_cwindow(exarg_T *eap)
qfl = qf_get_curlist(qi);
- /* Look for an existing quickfix window. */
+ // Look for an existing quickfix window.
win = qf_find_win(qi);
- /*
- * If a quickfix window is open but we have no errors to display,
- * close the window. If a quickfix window is not open, then open
- * it if we have errors; otherwise, leave it closed.
- */
+ // If a quickfix window is open but we have no errors to display,
+ // close the window. If a quickfix window is not open, then open
+ // it if we have errors; otherwise, leave it closed.
if (qf_stack_empty(qi)
|| qfl->qf_nonevalid
|| qf_list_empty(qf_get_curlist(qi))) {
@@ -3435,10 +3400,8 @@ void ex_cwindow(exarg_T *eap)
}
}
-/*
- * ":cclose": close the window showing the list of errors.
- * ":lclose": close the window showing the location list
- */
+// ":cclose": close the window showing the list of errors.
+// ":lclose": close the window showing the location list
void ex_cclose(exarg_T *eap)
{
win_T *win = NULL;
@@ -3448,7 +3411,7 @@ void ex_cclose(exarg_T *eap)
return;
}
- /* Find existing quickfix window and close it. */
+ // Find existing quickfix window and close it.
win = qf_find_win(qi);
if (win != NULL) {
win_close(win, false);
@@ -3644,38 +3607,32 @@ void ex_cbottom(exarg_T *eap)
}
}
-/*
- * Return the number of the current entry (line number in the quickfix
- * window).
- */
+// Return the number of the current entry (line number in the quickfix
+// window).
linenr_T qf_current_entry(win_T *wp)
{
qf_info_T *qi = &ql_info;
- if (IS_LL_WINDOW(wp))
- /* In the location list window, use the referenced location list */
+ if (IS_LL_WINDOW(wp)) {
+ // In the location list window, use the referenced location list
qi = wp->w_llist_ref;
+ }
return qf_get_curlist(qi)->qf_index;
}
-/*
- * Update the cursor position in the quickfix window to the current error.
- * Return TRUE if there is a quickfix window.
- */
-static int
-qf_win_pos_update (
+// Update the cursor position in the quickfix window to the current error.
+// Return TRUE if there is a quickfix window.
+static int qf_win_pos_update(
qf_info_T *qi,
- int old_qf_index /* previous qf_index or zero */
+ int old_qf_index // previous qf_index or zero
)
{
win_T *win;
int qf_index = qf_get_curlist(qi)->qf_index;
- /*
- * Put the cursor on the current error in the quickfix window, so that
- * it's viewable.
- */
+ // Put the cursor on the current error in the quickfix window, so that
+ // it's viewable.
win = qf_find_win(qi);
if (win != NULL
&& qf_index <= win->w_buffer->b_ml.ml_line_count
@@ -3727,10 +3684,8 @@ static win_T *qf_find_win(const qf_info_T *qi)
return NULL;
}
-/*
- * Find a quickfix buffer.
- * Searches in windows opened in all the tabs.
- */
+// Find a quickfix buffer.
+// Searches in windows opened in all the tabs.
static buf_T *qf_find_buf(const qf_info_T *qi)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -3756,16 +3711,14 @@ static void qf_update_win_titlevar(qf_info_T *qi)
}
}
-/*
- * Find the quickfix buffer. If it exists, update the contents.
- */
+// Find the quickfix buffer. If it exists, update the contents.
static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
{
buf_T *buf;
win_T *win;
aco_save_T aco;
- /* Check if a buffer for the quickfix list exists. Update it. */
+ // Check if a buffer for the quickfix list exists. Update it.
buf = qf_find_buf(qi);
if (buf != NULL) {
linenr_T old_line_count = buf->b_ml.ml_line_count;
@@ -3938,7 +3891,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
redraw_curbuf_later(NOT_VALID);
}
- /* Restore KeyTyped, setting 'filetype' may reset it. */
+ // Restore KeyTyped, setting 'filetype' may reset it.
KeyTyped = old_KeyTyped;
}
@@ -3992,9 +3945,7 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit)
}
}
-/*
- * Return TRUE when using ":vimgrep" for ":grep".
- */
+// Return TRUE when using ":vimgrep" for ":grep".
int grep_internal(cmdidx_T cmdidx)
{
return (cmdidx == CMD_grep
@@ -4057,9 +4008,7 @@ static char *make_get_fullcmd(const char_u *makecmd, const char_u *fname)
return cmd;
}
-/*
- * Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd"
- */
+// Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd"
void ex_make(exarg_T *eap)
{
char_u *fname;
@@ -4130,11 +4079,9 @@ cleanup:
xfree(cmd);
}
-/*
- * Return the name for the errorfile, in allocated memory.
- * Find a new unique name when 'makeef' contains "##".
- * Returns NULL for error.
- */
+// Return the name for the errorfile, in allocated memory.
+// Find a new unique name when 'makeef' contains "##".
+// Returns NULL for error.
static char_u *get_mef_name(void)
{
char_u *p;
@@ -4156,7 +4103,7 @@ static char_u *get_mef_name(void)
if (*p == NUL)
return vim_strsave(p_mef);
- /* Keep trying until the name doesn't exist yet. */
+ // Keep trying until the name doesn't exist yet.
for (;; ) {
if (start == -1) {
start = (int)os_get_pid();
@@ -4561,9 +4508,9 @@ static qfline_T *qf_find_closest_entry(qf_list_T *qfl,
/// Get the nth quickfix entry below the specified entry treating multiple
/// entries on a single line as one. Searches forward in the list.
-static qfline_T *qf_get_nth_below_entry(qfline_T *entry,
- int *errornr,
- linenr_T n)
+static void qf_get_nth_below_entry(qfline_T *entry,
+ int *errornr,
+ linenr_T n)
{
while (n-- > 0 && !got_int) {
qfline_T *first_entry = entry;
@@ -4584,15 +4531,13 @@ static qfline_T *qf_get_nth_below_entry(qfline_T *entry,
entry = entry->qf_next;
(*errornr)++;
}
-
- return entry;
}
/// Get the nth quickfix entry above the specified entry treating multiple
/// entries on a single line as one. Searches backwards in the list.
-static qfline_T *qf_get_nth_above_entry(qfline_T *entry,
- int *errornr,
- linenr_T n)
+static void qf_get_nth_above_entry(qfline_T *entry,
+ int *errornr,
+ linenr_T n)
{
while (n-- > 0 && !got_int) {
if (entry->qf_prev == NULL
@@ -4606,8 +4551,6 @@ static qfline_T *qf_get_nth_above_entry(qfline_T *entry,
// If multiple entries are on the same line, then use the first entry
entry = qf_find_first_entry_on_line(entry, errornr);
}
-
- return entry;
}
/// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the
@@ -4631,9 +4574,9 @@ static int qf_find_nth_adj_entry(qf_list_T *qfl,
if (--n > 0) {
// Go to the n'th entry in the current buffer
if (dir == FORWARD) {
- adj_entry = qf_get_nth_below_entry(adj_entry, &errornr, n);
+ qf_get_nth_below_entry(adj_entry, &errornr, n);
} else {
- adj_entry = qf_get_nth_above_entry(adj_entry, &errornr, n);
+ qf_get_nth_above_entry(adj_entry, &errornr, n);
}
}
@@ -4709,10 +4652,8 @@ static char_u * cfile_get_auname(cmdidx_T cmdidx)
}
-/*
- * ":cfile"/":cgetfile"/":caddfile" commands.
- * ":lfile"/":lgetfile"/":laddfile" commands.
- */
+// ":cfile"/":cgetfile"/":caddfile" commands.
+// ":lfile"/":lgetfile"/":laddfile" commands.
void ex_cfile(exarg_T *eap)
{
win_T *wp = NULL;
@@ -4953,12 +4894,10 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
}
}
-/*
- * ":vimgrep {pattern} file(s)"
- * ":vimgrepadd {pattern} file(s)"
- * ":lvimgrep {pattern} file(s)"
- * ":lvimgrepadd {pattern} file(s)"
- */
+// ":vimgrep {pattern} file(s)"
+// ":vimgrepadd {pattern} file(s)"
+// ":lvimgrep {pattern} file(s)"
+// ":lvimgrepadd {pattern} file(s)"
void ex_vimgrep(exarg_T *eap)
{
regmmatch_T regmatch;
@@ -4968,7 +4907,6 @@ void ex_vimgrep(exarg_T *eap)
char_u *s;
char_u *p;
int fi;
- qf_info_T *qi;
qf_list_T *qfl;
win_T *wp = NULL;
buf_T *buf;
@@ -4994,17 +4932,14 @@ void ex_vimgrep(exarg_T *eap)
}
}
- qi = qf_cmd_get_or_alloc_stack(eap, &wp);
- if (qi == NULL) {
- return;
- }
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
if (eap->addr_count > 0)
tomatch = eap->line2;
else
tomatch = MAXLNUM;
- /* Get the search pattern: either white-separated or enclosed in // */
+ // Get the search pattern: either white-separated or enclosed in //
regmatch.regprog = NULL;
char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
p = skip_vimgrep_pat(eap->arg, &s, &flags);
@@ -5031,9 +4966,10 @@ void ex_vimgrep(exarg_T *eap)
qf_new_list(qi, title);
}
- /* parse the list of arguments */
- if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL)
+ // parse the list of arguments
+ if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) {
goto theend;
+ }
if (fcount == 0) {
EMSG(_(e_nomatch));
goto theend;
@@ -5042,8 +4978,8 @@ void ex_vimgrep(exarg_T *eap)
dirname_start = xmalloc(MAXPATHL);
dirname_now = xmalloc(MAXPATHL);
- /* Remember the current directory, because a BufRead autocommand that does
- * ":lcd %:p:h" changes the meaning of short path names. */
+ // Remember the current directory, because a BufRead autocommand that does
+ // ":lcd %:p:h" changes the meaning of short path names.
os_dirname(dirname_start, MAXPATHL);
incr_quickfix_busy();
@@ -5056,15 +4992,15 @@ void ex_vimgrep(exarg_T *eap)
for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) {
fname = path_try_shorten_fname(fnames[fi]);
if (time(NULL) > seconds) {
- /* Display the file name every second or so, show the user we are
- * working on it. */
+ // Display the file name every second or so, show the user we are
+ // working on it.
seconds = time(NULL);
vgr_display_fname(fname);
}
buf = buflist_findname_exp(fnames[fi]);
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
- /* Remember that a buffer with this name already exists. */
+ // Remember that a buffer with this name already exists.
duplicate_name = (buf != NULL);
using_dummy = TRUE;
redraw_for_dummy = TRUE;
@@ -5097,20 +5033,20 @@ void ex_vimgrep(exarg_T *eap)
if (found_match && first_match_buf == NULL)
first_match_buf = buf;
if (duplicate_name) {
- /* Never keep a dummy buffer if there is another buffer
- * with the same name. */
+ // Never keep a dummy buffer if there is another buffer
+ // with the same name.
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
} else if (!cmdmod.hide
- || buf->b_p_bh[0] == 'u' /* "unload" */
- || buf->b_p_bh[0] == 'w' /* "wipe" */
- || buf->b_p_bh[0] == 'd') { /* "delete" */
- /* When no match was found we don't need to remember the
- * buffer, wipe it out. If there was a match and it
- * wasn't the first one or we won't jump there: only
- * unload the buffer.
- * Ignore 'hidden' here, because it may lead to having too
- * many swap files. */
+ || buf->b_p_bh[0] == 'u' // "unload"
+ || buf->b_p_bh[0] == 'w' // "wipe"
+ || buf->b_p_bh[0] == 'd') { // "delete"
+ // When no match was found we don't need to remember the
+ // buffer, wipe it out. If there was a match and it
+ // wasn't the first one or we won't jump there: only
+ // unload the buffer.
+ // Ignore 'hidden' here, because it may lead to having too
+ // many swap files.
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
@@ -5134,10 +5070,10 @@ void ex_vimgrep(exarg_T *eap)
target_dir = vim_strsave(dirname_now);
}
- /* The buffer is still loaded, the Filetype autocommands
- * need to be done now, in that buffer. And the modelines
- * need to be done (again). But not the window-local
- * options! */
+ // The buffer is still loaded, the Filetype autocommands
+ // need to be done now, in that buffer. And the modelines
+ // need to be done (again). But not the window-local
+ // options!
aucmd_prepbuf(&aco, buf);
apply_autocmds(EVENT_FILETYPE, buf->b_p_ft,
buf->b_fname, TRUE, buf);
@@ -5181,8 +5117,8 @@ void ex_vimgrep(exarg_T *eap)
decr_quickfix_busy();
- /* If we loaded a dummy buffer into the current window, the autocommands
- * may have messed up things, need to redraw and recompute folds. */
+ // If we loaded a dummy buffer into the current window, the autocommands
+ // may have messed up things, need to redraw and recompute folds.
if (redraw_for_dummy) {
foldUpdateAll(curwin);
}
@@ -5195,18 +5131,16 @@ theend:
vim_regfree(regmatch.regprog);
}
-/*
- * Restore current working directory to "dirname_start" if they differ, taking
- * into account whether it is set locally or globally.
- */
+// Restore current working directory to "dirname_start" if they differ, taking
+// into account whether it is set locally or globally.
static void restore_start_dir(char_u *dirname_start)
{
char_u *dirname_now = xmalloc(MAXPATHL);
os_dirname(dirname_now, MAXPATHL);
if (STRCMP(dirname_start, dirname_now) != 0) {
- /* If the directory has changed, change it back by building up an
- * appropriate ex command and executing it. */
+ // If the directory has changed, change it back by building up an
+ // appropriate ex command and executing it.
exarg_T ea = {
.arg = dirname_start,
.cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd,
@@ -5216,23 +5150,21 @@ static void restore_start_dir(char_u *dirname_start)
xfree(dirname_now);
}
-/*
- * Load file "fname" into a dummy buffer and return the buffer pointer,
- * placing the directory resulting from the buffer load into the
- * "resulting_dir" pointer. "resulting_dir" must be allocated by the caller
- * prior to calling this function. Restores directory to "dirname_start" prior
- * to returning, if autocmds or the 'autochdir' option have changed it.
- *
- * If creating the dummy buffer does not fail, must call unload_dummy_buffer()
- * or wipe_dummy_buffer() later!
- *
- * Returns NULL if it fails.
- */
+// Load file "fname" into a dummy buffer and return the buffer pointer,
+// placing the directory resulting from the buffer load into the
+// "resulting_dir" pointer. "resulting_dir" must be allocated by the caller
+// prior to calling this function. Restores directory to "dirname_start" prior
+// to returning, if autocmds or the 'autochdir' option have changed it.
+//
+// If creating the dummy buffer does not fail, must call unload_dummy_buffer()
+// or wipe_dummy_buffer() later!
+//
+// Returns NULL if it fails.
static buf_T *
load_dummy_buffer (
char_u *fname,
- char_u *dirname_start, /* in: old directory */
- char_u *resulting_dir /* out: new directory */
+ char_u *dirname_start, // in: old directory
+ char_u *resulting_dir // out: new directory
)
{
buf_T *newbuf;
@@ -5249,24 +5181,24 @@ load_dummy_buffer (
}
set_bufref(&newbufref, newbuf);
- /* Init the options. */
+ // Init the options.
buf_copy_options(newbuf, BCO_ENTER | BCO_NOHELP);
- /* need to open the memfile before putting the buffer in a window */
+ // need to open the memfile before putting the buffer in a window
if (ml_open(newbuf) == OK) {
// Make sure this buffer isn't wiped out by autocommands.
newbuf->b_locked++;
// set curwin/curbuf to buf and save a few things
aucmd_prepbuf(&aco, newbuf);
- /* Need to set the filename for autocommands. */
- (void)setfname(curbuf, fname, NULL, FALSE);
+ // Need to set the filename for autocommands.
+ (void)setfname(curbuf, fname, NULL, false);
- /* Create swap file now to avoid the ATTENTION message. */
- check_need_swap(TRUE);
+ // Create swap file now to avoid the ATTENTION message.
+ check_need_swap(true);
- /* Remove the "dummy" flag, otherwise autocommands may not
- * work. */
+ // Remove the "dummy" flag, otherwise autocommands may not
+ // work.
curbuf->b_flags &= ~BF_DUMMY;
newbuf_to_wipe.br_buf = NULL;
@@ -5299,11 +5231,9 @@ load_dummy_buffer (
newbuf->b_flags |= BF_DUMMY;
}
- /*
- * When autocommands/'autochdir' option changed directory: go back.
- * Let the caller know what the resulting dir was first, in case it is
- * important.
- */
+ // When autocommands/'autochdir' option changed directory: go back.
+ // Let the caller know what the resulting dir was first, in case it is
+ // important.
os_dirname(resulting_dir, MAXPATHL);
restore_start_dir(dirname_start);
@@ -5317,42 +5247,38 @@ load_dummy_buffer (
return newbuf;
}
-/*
- * Wipe out the dummy buffer that load_dummy_buffer() created. Restores
- * directory to "dirname_start" prior to returning, if autocmds or the
- * 'autochdir' option have changed it.
- */
+// Wipe out the dummy buffer that load_dummy_buffer() created. Restores
+// directory to "dirname_start" prior to returning, if autocmds or the
+// 'autochdir' option have changed it.
static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
{
- if (curbuf != buf) { /* safety check */
+ if (curbuf != buf) { // safety check
cleanup_T cs;
- /* Reset the error/interrupt/exception state here so that aborting()
- * returns FALSE when wiping out the buffer. Otherwise it doesn't
- * work when got_int is set. */
+ // Reset the error/interrupt/exception state here so that aborting()
+ // returns FALSE when wiping out the buffer. Otherwise it doesn't
+ // work when got_int is set.
enter_cleanup(&cs);
wipe_buffer(buf, FALSE);
- /* Restore the error/interrupt/exception state if not discarded by a
- * new aborting error, interrupt, or uncaught exception. */
+ // Restore the error/interrupt/exception state if not discarded by a
+ // new aborting error, interrupt, or uncaught exception.
leave_cleanup(&cs);
- /* When autocommands/'autochdir' option changed directory: go back. */
+ // When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
}
}
-/*
- * Unload the dummy buffer that load_dummy_buffer() created. Restores
- * directory to "dirname_start" prior to returning, if autocmds or the
- * 'autochdir' option have changed it.
- */
+// Unload the dummy buffer that load_dummy_buffer() created. Restores
+// directory to "dirname_start" prior to returning, if autocmds or the
+// 'autochdir' option have changed it.
static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start)
{
- if (curbuf != buf) { /* safety check */
- close_buffer(NULL, buf, DOBUF_UNLOAD, FALSE);
+ if (curbuf != buf) { // safety check
+ close_buffer(NULL, buf, DOBUF_UNLOAD, false);
- /* When autocommands/'autochdir' option changed directory: go back. */
+ // When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
}
}
@@ -5785,11 +5711,13 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
}
/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the
-/// items in the dict 'd'.
+/// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry'
+/// to true.
static int qf_add_entry_from_dict(
qf_list_T *qfl,
const dict_T *d,
- bool first_entry)
+ bool first_entry,
+ bool *valid_entry)
FUNC_ATTR_NONNULL_ALL
{
static bool did_bufnr_emsg;
@@ -5852,6 +5780,10 @@ static int qf_add_entry_from_dict(
xfree(pattern);
xfree(text);
+ if (valid) {
+ *valid_entry = true;
+ }
+
return status;
}
@@ -5863,6 +5795,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
qf_list_T *qfl = qf_get_list(qi, qf_idx);
qfline_T *old_last = NULL;
int retval = OK;
+ bool valid_entry = false;
if (action == ' ' || qf_idx == qi->qf_listcount) {
// make place for a new list
@@ -5887,23 +5820,30 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
continue;
}
- retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list));
+ retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list),
+ &valid_entry);
if (retval == QF_FAIL) {
break;
}
});
- if (qfl->qf_index == 0) {
- // no valid entry
- qfl->qf_nonevalid = true;
- } else {
+ // Check if any valid error entries are added to the list.
+ if (valid_entry) {
qfl->qf_nonevalid = false;
+ } else if (qfl->qf_index == 0) {
+ qfl->qf_nonevalid = true;
}
+
+ // If not appending to the list, set the current error to the first entry
if (action != 'a') {
qfl->qf_ptr = qfl->qf_start;
- if (!qf_list_empty(qfl)) {
- qfl->qf_index = 1;
- }
+ }
+
+ // Update the current error index if not appending to the list or if the
+ // list was empty before and it is not empty now.
+ if ((action != 'a' || qfl->qf_index == 0)
+ && !qf_list_empty(qfl)) {
+ qfl->qf_index = 1;
}
// Don't update the cursor in quickfix window when appending entries
@@ -6293,18 +6233,15 @@ static int cbuffer_process_args(exarg_T *eap,
return OK;
}
-/*
- * ":[range]cbuffer [bufnr]" command.
- * ":[range]caddbuffer [bufnr]" command.
- * ":[range]cgetbuffer [bufnr]" command.
- * ":[range]lbuffer [bufnr]" command.
- * ":[range]laddbuffer [bufnr]" command.
- * ":[range]lgetbuffer [bufnr]" command.
- */
+// ":[range]cbuffer [bufnr]" command.
+// ":[range]caddbuffer [bufnr]" command.
+// ":[range]cgetbuffer [bufnr]" command.
+// ":[range]lbuffer [bufnr]" command.
+// ":[range]laddbuffer [bufnr]" command.
+// ":[range]lgetbuffer [bufnr]" command.
void ex_cbuffer(exarg_T *eap)
{
buf_T *buf = NULL;
- qf_info_T *qi;
char_u *au_name = NULL;
win_T *wp = NULL;
char_u *qf_title;
@@ -6320,10 +6257,7 @@ void ex_cbuffer(exarg_T *eap)
}
// Must come after autocommands.
- qi = qf_cmd_get_or_alloc_stack(eap, &wp);
- if (qi == NULL) {
- return;
- }
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
if (cbuffer_process_args(eap, &buf, &line1, &line2) == FAIL) {
return;
@@ -6392,7 +6326,6 @@ static char_u * cexpr_get_auname(cmdidx_T cmdidx)
/// ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command.
void ex_cexpr(exarg_T *eap)
{
- qf_info_T *qi;
char_u *au_name = NULL;
win_T *wp = NULL;
@@ -6404,13 +6337,10 @@ void ex_cexpr(exarg_T *eap)
}
}
- qi = qf_cmd_get_or_alloc_stack(eap, &wp);
- if (qi == NULL) {
- return;
- }
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
- /* Evaluate the expression. When the result is a string or a list we can
- * use it to fill the errorlist. */
+ // Evaluate the expression. When the result is a string or a list we can
+ // use it to fill the errorlist.
typval_T tv;
if (eval0(eap->arg, &tv, NULL, true) != FAIL) {
if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL)
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 1ce0b5217e..1d29ae064e 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -124,7 +124,7 @@
// temporary buffer for rendering a single screenline, so it can be
-// comparared with previous contents to calulate smallest delta.
+// comparared with previous contents to calculate smallest delta.
static size_t linebuf_size = 0;
static schar_T *linebuf_char = NULL;
static sattr_T *linebuf_attr = NULL;
@@ -286,6 +286,11 @@ int update_screen(int type)
return FAIL;
}
+ // May have postponed updating diffs.
+ if (need_diff_redraw) {
+ diff_redraw(true);
+ }
+
if (must_redraw) {
if (type < must_redraw) /* use maximal type */
type = must_redraw;
@@ -4016,10 +4021,13 @@ win_line (
if (wp->w_buffer->terminal) {
// terminal buffers may need to highlight beyond the end of the
// logical line
- while (col < grid->Columns) {
+ int n = wp->w_p_rl ? -1 : 1;
+ while (col >= 0 && col < grid->Columns) {
schar_from_ascii(linebuf_char[off], ' ');
- linebuf_attr[off++] = term_attrs[vcol++];
- col++;
+ linebuf_attr[off] = term_attrs[vcol];
+ off += n;
+ vcol += n;
+ col += n;
}
}
grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp,
diff --git a/src/nvim/search.c b/src/nvim/search.c
index c4c8633ed9..5e32715e49 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -3777,30 +3777,31 @@ find_prev_quote(
return col_start;
}
-/*
- * Find quote under the cursor, cursor at end.
- * Returns TRUE if found, else FALSE.
- */
-int
-current_quote(
+// Find quote under the cursor, cursor at end.
+// Returns true if found, else false.
+bool current_quote(
oparg_T *oap,
long count,
- int include, /* TRUE == include quote char */
- int quotechar /* Quote character */
+ bool include, // true == include quote char
+ int quotechar // Quote character
)
+ FUNC_ATTR_NONNULL_ALL
{
char_u *line = get_cursor_line_ptr();
int col_end;
int col_start = curwin->w_cursor.col;
bool inclusive = false;
- int vis_empty = true; // Visual selection <= 1 char
- int vis_bef_curs = false; // Visual starts before cursor
- int inside_quotes = false; // Looks like "i'" done before
- int selected_quote = false; // Has quote inside selection
+ bool vis_empty = true; // Visual selection <= 1 char
+ bool vis_bef_curs = false; // Visual starts before cursor
+ bool did_exclusive_adj = false; // adjusted pos for 'selection'
+ bool inside_quotes = false; // Looks like "i'" done before
+ bool selected_quote = false; // Has quote inside selection
int i;
- int restore_vis_bef = false; // resotre VIsual on abort
+ bool restore_vis_bef = false; // resotre VIsual on abort
- // Correct cursor when 'selection' is "exclusive".
+ // When 'selection' is "exclusive" move the cursor to where it would be
+ // with 'selection' "inclusive", so that the logic is the same for both.
+ // The cursor then is moved forward after adjusting the area.
if (VIsual_active) {
// this only works within one line
if (VIsual.lnum != curwin->w_cursor.lnum) {
@@ -3808,8 +3809,17 @@ current_quote(
}
vis_bef_curs = lt(VIsual, curwin->w_cursor);
+ vis_empty = equalpos(VIsual, curwin->w_cursor);
if (*p_sel == 'e') {
- if (!vis_bef_curs) {
+ if (vis_bef_curs) {
+ dec_cursor();
+ did_exclusive_adj = true;
+ } else if (!vis_empty) {
+ dec(&VIsual);
+ did_exclusive_adj = true;
+ }
+ vis_empty = equalpos(VIsual, curwin->w_cursor);
+ if (!vis_bef_curs && !vis_empty) {
// VIsual needs to be start of Visual selection.
pos_T t = curwin->w_cursor;
@@ -3818,9 +3828,7 @@ current_quote(
vis_bef_curs = true;
restore_vis_bef = true;
}
- dec_cursor();
}
- vis_empty = equalpos(VIsual, curwin->w_cursor);
}
if (!vis_empty) {
@@ -3845,7 +3853,7 @@ current_quote(
/* Find out if we have a quote in the selection. */
while (i <= col_end)
if (line[i++] == quotechar) {
- selected_quote = TRUE;
+ selected_quote = true;
break;
}
}
@@ -3934,8 +3942,8 @@ current_quote(
}
}
- /* When "include" is TRUE, include spaces after closing quote or before
- * the starting quote. */
+ // When "include" is true, include spaces after closing quote or before
+ // the starting quote.
if (include) {
if (ascii_iswhite(line[col_end + 1]))
while (ascii_iswhite(line[col_end + 1]))
@@ -3981,9 +3989,10 @@ current_quote(
inclusive = true;
if (VIsual_active) {
if (vis_empty || vis_bef_curs) {
- /* decrement cursor when 'selection' is not exclusive */
- if (*p_sel != 'e')
+ // decrement cursor when 'selection' is not exclusive
+ if (*p_sel != 'e') {
dec_cursor();
+ }
} else {
/* Cursor is at start of Visual area. Set the end of the Visual
* area when it was just inside quotes or it didn't end at a
@@ -4007,11 +4016,13 @@ current_quote(
oap->inclusive = inclusive;
}
- return OK;
+ return true;
abort_search:
if (VIsual_active && *p_sel == 'e') {
- inc_cursor();
+ if (did_exclusive_adj) {
+ inc_cursor();
+ }
if (restore_vis_bef) {
pos_T t = curwin->w_cursor;
@@ -4037,9 +4048,6 @@ current_search(
bool old_p_ws = p_ws;
pos_T save_VIsual = VIsual;
- /* wrapping should not occur */
- p_ws = false;
-
/* Correct cursor when 'selection' is exclusive */
if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor))
dec_cursor();
@@ -4063,8 +4071,7 @@ current_search(
int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor,
FORWARD);
if (zero_width == -1) {
- p_ws = old_p_ws;
- return FAIL; /* pattern not found */
+ return FAIL; // pattern not found
}
/*
@@ -4081,11 +4088,18 @@ current_search(
}
end_pos = pos;
+ // wrapping should not occur in the first round
+ if (i == 0) {
+ p_ws = false;
+ }
+
result = searchit(curwin, curbuf, &pos, &end_pos,
(dir ? FORWARD : BACKWARD),
spats[last_idx].pat, i ? count : 1,
SEARCH_KEEP | flags, RE_SEARCH, NULL);
+ p_ws = old_p_ws;
+
// First search may fail, but then start searching from the
// beginning of the file (cursor might be on the search match)
// except when Visual mode is active, so that extending the visual
@@ -4094,7 +4108,6 @@ current_search(
curwin->w_cursor = orig_pos;
if (VIsual_active)
VIsual = save_VIsual;
- p_ws = old_p_ws;
return FAIL;
} else if (i == 0 && !result) {
if (forward) { // try again from start of buffer
@@ -4110,8 +4123,6 @@ current_search(
pos_T start_pos = pos;
- p_ws = old_p_ws;
-
if (!VIsual_active) {
VIsual = start_pos;
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 687c86b4a8..5feb7efda9 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -1910,11 +1910,11 @@ int init_syl_tab(slang_T *slang)
// Count the number of syllables in "word".
// When "word" contains spaces the syllables after the last space are counted.
// Returns zero if syllables are not defines.
-static int count_syllables(slang_T *slang, char_u *word)
+static int count_syllables(slang_T *slang, const char_u *word)
+ FUNC_ATTR_NONNULL_ALL
{
int cnt = 0;
bool skip = false;
- char_u *p;
int len;
syl_item_T *syl;
int c;
@@ -1922,7 +1922,7 @@ static int count_syllables(slang_T *slang, char_u *word)
if (slang->sl_syllable == NULL)
return 0;
- for (p = word; *p != NUL; p += len) {
+ for (const char_u *p = word; *p != NUL; p += len) {
// When running into a space reset counter.
if (*p == ' ') {
len = 1;
@@ -2625,9 +2625,10 @@ static bool spell_mb_isword_class(int cl, const win_T *wp)
// Returns true if "p" points to a word character.
// Wide version of spell_iswordp().
-static bool spell_iswordp_w(int *p, win_T *wp)
+static bool spell_iswordp_w(const int *p, const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- int *s;
+ const int *s;
if (*p < 256 ? wp->w_s->b_spell_ismw[*p]
: (wp->w_s->b_spell_ismw_mb != NULL
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index eeec5be120..4fac001bc5 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -265,6 +265,8 @@
// follow; never used in prefix tree
#define BY_SPECIAL BY_FLAGS2 // highest special byte value
+#define ZERO_FLAG 65009 // used when flag is zero: "0"
+
// Flags used in .spl file for soundsalike flags.
#define SAL_F0LLOWUP 1
#define SAL_COLLAPSE 2
@@ -2783,6 +2785,7 @@ static unsigned affitem2flag(int flagtype, char_u *item, char_u *fname, int lnum
}
// Get one affix name from "*pp" and advance the pointer.
+// Returns ZERO_FLAG for "0".
// Returns zero for an error, still advances the pointer then.
static unsigned get_affitem(int flagtype, char_u **pp)
{
@@ -2794,6 +2797,9 @@ static unsigned get_affitem(int flagtype, char_u **pp)
return 0;
}
res = getdigits_int(pp, true, 0);
+ if (res == 0) {
+ res = ZERO_FLAG;
+ }
} else {
res = mb_ptr2char_adv((const char_u **)pp);
if (flagtype == AFT_LONG || (flagtype == AFT_CAPLONG
@@ -2915,10 +2921,15 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag)
int digits = getdigits_int(&p, true, 0);
assert(digits >= 0);
n = (unsigned int)digits;
- if (n == flag)
+ if (n == 0) {
+ n = ZERO_FLAG;
+ }
+ if (n == flag) {
return true;
- if (*p != NUL) // skip over comma
- ++p;
+ }
+ if (*p != NUL) { // skip over comma
+ p++;
+ }
}
break;
}
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 81bc078a88..b195c1d96b 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -156,7 +156,7 @@ char *get_mode(void)
buf[0] = 'n';
if (finish_op) {
buf[1] = 'o';
- // to be able to detect force-linewise/blockwise/characterwise operations
+ // to be able to detect force-linewise/blockwise/charwise operations
buf[2] = (char)motion_force;
} else if (restart_edit == 'I' || restart_edit == 'R'
|| restart_edit == 'V') {
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index bdbc09a87a..61ee225eba 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -7316,7 +7316,7 @@ static void set_hl_attr(int idx)
sgp->sg_attr = hl_get_syn_attr(idx+1, at_en);
- // a cursor style uses this syn_id, make sure its atribute is updated.
+ // a cursor style uses this syn_id, make sure its attribute is updated.
if (cursor_mode_uses_syn_id(idx+1)) {
ui_mode_info_set();
}
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index a3967c70b5..3629b37c32 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -65,6 +65,7 @@ typedef struct tag_pointers {
char_u *tagkind_end; // end of tagkind
char_u *user_data; // user_data string
char_u *user_data_end; // end of user_data
+ linenr_T tagline; // "line:" value
} tagptrs_T;
/*
@@ -1193,12 +1194,10 @@ static int find_tagfunc_tags(
taglist = rettv.vval.v_list;
TV_LIST_ITER_CONST(taglist, li, {
- char_u *mfp;
char_u *res_name;
char_u *res_fname;
char_u *res_cmd;
char_u *res_kind;
- int len;
int has_extra = 0;
int name_only = flags & TAG_NAMES;
@@ -1207,7 +1206,7 @@ static int find_tagfunc_tags(
break;
}
- len = 2;
+ size_t len = 2;
res_name = NULL;
res_fname = NULL;
res_cmd = NULL;
@@ -1253,15 +1252,7 @@ static int find_tagfunc_tags(
break;
}
- if (name_only) {
- mfp = vim_strsave(res_name);
- } else {
- mfp = (char_u *)xmalloc((int)sizeof(char_u) + len + 1);
- }
-
- if (mfp == NULL) {
- continue;
- }
+ char_u *const mfp = name_only ? vim_strsave(res_name) : xmalloc(len + 2);
if (!name_only) {
char_u *p = mfp;
@@ -1668,12 +1659,9 @@ find_tags(
break; /* End the binary search without a match. */
else
search_info.curr_offset = offset;
- }
- /*
- * Skipping back (after a match during binary search).
- */
- else if (state == TS_SKIP_BACK) {
- search_info.curr_offset -= LSIZE * 2;
+ } else if (state == TS_SKIP_BACK) {
+ // Skipping back (after a match during binary search).
+ search_info.curr_offset -= lbuf_size * 2;
if (search_info.curr_offset < 0) {
search_info.curr_offset = 0;
rewind(fp);
@@ -1689,7 +1677,7 @@ find_tags(
/* Adjust the search file offset to the correct position */
search_info.curr_offset_used = search_info.curr_offset;
vim_fseek(fp, search_info.curr_offset, SEEK_SET);
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = vim_fgets(lbuf, lbuf_size, fp);
if (!eof && search_info.curr_offset != 0) {
/* The explicit cast is to work around a bug in gcc 3.4.2
* (repeated below). */
@@ -1699,12 +1687,12 @@ find_tags(
vim_fseek(fp, search_info.low_offset, SEEK_SET);
search_info.curr_offset = search_info.low_offset;
}
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = vim_fgets(lbuf, lbuf_size, fp);
}
/* skip empty and blank lines */
while (!eof && vim_isblankline(lbuf)) {
search_info.curr_offset = vim_ftell(fp);
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = vim_fgets(lbuf, lbuf_size, fp);
}
if (eof) {
/* Hit end of file. Skip backwards. */
@@ -1720,10 +1708,9 @@ find_tags(
else {
/* skip empty and blank lines */
do {
- if (use_cscope)
- eof = cs_fgets(lbuf, LSIZE);
- else
- eof = vim_fgets(lbuf, LSIZE, fp);
+ eof = use_cscope
+ ? cs_fgets(lbuf, lbuf_size)
+ : vim_fgets(lbuf, lbuf_size, fp);
} while (!eof && vim_isblankline(lbuf));
if (eof) {
@@ -1848,19 +1835,14 @@ parse_line:
// When the line is too long the NUL will not be in the
// last-but-one byte (see vim_fgets()).
// Has been reported for Mozilla JS with extremely long names.
- // In that case we can't parse it and we ignore the line.
- if (lbuf[LSIZE - 2] != NUL && !use_cscope) {
- if (p_verbose >= 5) {
- verbose_enter();
- MSG(_("Ignoring long line in tags file"));
- verbose_leave();
- }
- if (state != TS_LINEAR) {
- // Avoid getting stuck.
- linear = true;
- state = TS_LINEAR;
- vim_fseek(fp, search_info.low_offset, SEEK_SET);
- }
+ // In that case we need to increase lbuf_size.
+ if (lbuf[lbuf_size - 2] != NUL && !use_cscope) {
+ lbuf_size *= 2;
+ xfree(lbuf);
+ lbuf = xmalloc(lbuf_size);
+ // this will try the same thing again, make sure the offset is
+ // different
+ search_info.curr_offset = 0;
continue;
}
@@ -2545,6 +2527,7 @@ parse_match(
tagp->tagkind = NULL;
tagp->user_data = NULL;
+ tagp->tagline = 0;
tagp->command_end = NULL;
if (retval == OK) {
@@ -2564,6 +2547,8 @@ parse_match(
tagp->tagkind = p + 5;
} else if (STRNCMP(p, "user_data:", 10) == 0) {
tagp->user_data = p + 10;
+ } else if (STRNCMP(p, "line:", 5) == 0) {
+ tagp->tagline = atoi((char *)p + 5);
}
if (tagp->tagkind != NULL && tagp->user_data != NULL) {
break;
@@ -2660,6 +2645,9 @@ static int jumpto_tag(
str = tagp.command;
for (pbuf_end = pbuf; *str && *str != '\n' && *str != '\r'; ) {
*pbuf_end++ = *str++;
+ if (pbuf_end - pbuf + 1 >= LSIZE) {
+ break;
+ }
}
*pbuf_end = NUL;
@@ -2811,7 +2799,13 @@ static int jumpto_tag(
p_ic = FALSE; /* don't ignore case now */
p_scs = FALSE;
save_lnum = curwin->w_cursor.lnum;
- curwin->w_cursor.lnum = 0; /* start search before first line */
+ if (tagp.tagline > 0) {
+ // start search before line from "line:" field
+ curwin->w_cursor.lnum = tagp.tagline - 1;
+ } else {
+ // start search before first line
+ curwin->w_cursor.lnum = 0;
+ }
if (do_search(NULL, pbuf[0], pbuf + 1, (long)1,
search_options, NULL)) {
retval = OK;
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index 08353509af..c36458930f 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -15,8 +15,6 @@ export TMPDIR := $(abspath Xtest-tmpdir)
SCRIPTS_DEFAULT = \
test42.out \
- test48.out \
- test64.out \
ifneq ($(OS),Windows_NT)
SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \
@@ -34,21 +32,23 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT)
# Tests using runtest.vim.
NEW_TESTS_ALOT := test_alot_utf8 test_alot
-NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim)
+NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT)))
+NEW_TESTS_IN_ALOT_LATIN := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' test_alot_latin.vim)
# Ignored tests.
# test_alot_latin: Nvim does not allow setting encoding.
# test_autochdir: ported to Lua, but kept for easier merging.
# test_eval_func: used as include in old-style test (test_eval.in).
# test_listlbr: Nvim does not allow setting encoding.
# test_largefile: uses too much resources to run on CI.
-NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \
- test_alot_latin \
+NEW_TESTS_IGNORE := \
+ test_alot_latin $(NEW_TESTS_IN_ALOT_LATIN) \
test_autochdir \
test_eval_func \
test_listlbr \
test_largefile \
-NEW_TESTS ?= $(addsuffix .res,$(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT))
+NEW_TESTS := $(sort $(basename $(notdir $(wildcard test_*.vim))))
+NEW_TESTS_RES := $(addsuffix .res,$(filter-out $(NEW_TESTS_ALOT) $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_IGNORE),$(NEW_TESTS)) $(NEW_TESTS_ALOT))
ifdef VALGRIND_GDB
@@ -112,6 +112,16 @@ fixff:
-$(NVIM_PRG) $(NO_INITS) -u unix.vim "+argdo set ff=dos|upd" +q \
dotest.in
+# Execute an individual new style test, e.g.:
+# make test_largefile
+$(NEW_TESTS):
+ rm -f $@.res test.log messages
+ @MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile $@.res
+ @cat messages
+ @if test -f test.log; then \
+ exit 1; \
+ fi
+
RM_ON_RUN := test.out X* viminfo
RM_ON_START := test.ok
RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in
@@ -172,7 +182,7 @@ newtests: newtestssilent
cat messages && cat test.log; \
fi"
-newtestssilent: $(NEW_TESTS)
+newtestssilent: $(NEW_TESTS_RES)
%.res: %.vim .gdbinit
@echo "[OLDTEST] Running" $*
diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh
index 2dcd9150be..72f9254635 100755
--- a/src/nvim/testdir/runnvim.sh
+++ b/src/nvim/testdir/runnvim.sh
@@ -82,6 +82,11 @@ main() {(
fi
if test "$FAILED" = 1 ; then
echo "Test $test_name failed, see output above and summary for more details" >> test.log
+ # When Neovim crashed/aborted it might not have created messages.
+ # test.log itself is used as an indicator to exit non-zero in the Makefile.
+ if ! test -f message; then
+ cp -a test.log messages
+ fi
fi
)}
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 5c2e570adf..2d4134a644 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -297,6 +297,8 @@ let s:flaky_tests = [
\ 'Test_repeat_three()',
\ 'Test_state()',
\ 'Test_stop_all_in_callback()',
+ \ 'Test_term_mouse_double_click_to_create_tab',
+ \ 'Test_term_mouse_multiple_clicks_to_visually_select()',
\ 'Test_terminal_composing_unicode()',
\ 'Test_terminal_redir_file()',
\ 'Test_terminal_tmap()',
diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim
index ea28f328ae..d032c9a739 100644
--- a/src/nvim/testdir/setup.vim
+++ b/src/nvim/testdir/setup.vim
@@ -19,6 +19,7 @@ set sidescroll=0
set tags=./tags,tags
set undodir^=.
set wildoptions=
+set startofline
" Prevent Nvim log from writing to stderr.
let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index 84f636077d..3875ffc056 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -69,7 +69,8 @@ endfunc
" Read the port number from the Xportnr file.
func GetPort()
let l = []
- for i in range(200)
+ " with 200 it sometimes failed
+ for i in range(400)
try
let l = readfile("Xportnr")
catch
@@ -252,6 +253,8 @@ func GetVimProg()
endif
endfunc
+let g:valgrind_cnt = 1
+
" Get the command to run Vim, with -u NONE and --headless arguments.
" If there is an argument use it instead of "NONE".
func GetVimCommand(...)
@@ -267,14 +270,25 @@ func GetVimCommand(...)
endif
let cmd .= ' --headless -i NONE'
let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '')
+
+ " If using valgrind, make sure every run uses a different log file.
+ if cmd =~ 'valgrind.*--log-file='
+ let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '')
+ let g:valgrind_cnt += 1
+ endif
+
return cmd
endfunc
-" Get the command to run Vim, with --clean.
+" Get the command to run Vim, with --clean instead of "-u NONE".
func GetVimCommandClean()
let cmd = GetVimCommand()
let cmd = substitute(cmd, '-u NONE', '--clean', '')
let cmd = substitute(cmd, '--headless', '', '')
+
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
return cmd
endfunc
@@ -290,9 +304,6 @@ endfunc
func RunVimPiped(before, after, arguments, pipecmd)
let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
let cmd = GetVimCommand()
- if cmd == ''
- return 0
- endif
let args = ''
if len(a:before) > 0
call writefile(a:before, 'Xbefore.vim')
@@ -303,6 +314,9 @@ func RunVimPiped(before, after, arguments, pipecmd)
let args .= ' -S Xafter.vim'
endif
+ " Optionally run Vim under valgrind
+ " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
+
exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments
if len(a:before) > 0
diff --git a/src/nvim/testdir/test48.in b/src/nvim/testdir/test48.in
deleted file mode 100644
index 1df5a3c46a..0000000000
--- a/src/nvim/testdir/test48.in
+++ /dev/null
@@ -1,82 +0,0 @@
-This is a test of 'virtualedit'.
-
-STARTTEST
-:set noswf
-:set ve=all
-j-dgg
-:"
-:" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
-:" Repeating CTRL-N fixes it. (Mary Ellen Foster)
-2/w
-C
-:"
-:" Using "C" then then <CR> moves the last remaining character to the next
-:" line. (Mary Ellen Foster)
-j^/are
-C are belong to vim
-:"
-:" When past the end of a line that ends in a single character "b" skips
-:" that word.
-^$15lbC7
-:"
-:" Make sure 'i' works
-$4li<-- should be 3 ' '
-:"
-:" Make sure 'C' works
-$4lC<-- should be 3 ' '
-:"
-:" Make sure 'a' works
-$4la<-- should be 4 ' '
-:"
-:" Make sure 'A' works
-$4lA<-- should be 0 ' '
-:"
-:" Make sure 'D' works
-$4lDi<-- 'D' should be intact
-:"
-:" Test for yank bug reported by Mark Waggoner.
-:set ve=block
-^2w3jyGp
-:"
-:" Test "r" beyond the end of the line
-:set ve=all
-/^"r"
-$5lrxa<-- should be 'x'
-:"
-:" Test "r" on a tab
-:" Note that for this test, 'ts' must be 8 (the default).
-^5lrxA<-- should be ' x '
-:"
-:" Test to make sure 'x' can delete control characters
-:set display=uhex
-^xxxxxxi[This line should contain only the text between the brackets.]
-:set display=
-:"
-:" Test for ^Y/^E due to bad w_virtcol value, reported by
-:" Roy <royl@netropolis.net>.
-^O3li4li4li <-- should show the name of a noted text editor
-^o4li4li4li <-- and its version number-dd
-:"
-:" Test for yanking and pasting using the small delete register
-gg/^foo
-dewve"-p
-:wq! test.out
-ENDTEST
-foo, bar
-keyword keyw
-all your base are belong to us
-1 2 3 4 5 6
-'i'
-'C'
-'a'
-'A'
-'D'
-this is a test
-this is a test
-this is a test
-"r"
-"r"
-ab sd
-abcv6efi.him0kl
-
-
diff --git a/src/nvim/testdir/test48.ok b/src/nvim/testdir/test48.ok
deleted file mode 100644
index 14cd9b12ec..0000000000
--- a/src/nvim/testdir/test48.ok
+++ /dev/null
@@ -1,23 +0,0 @@
-, foo
-keyword keyword
-all your base
-are belong to vim
-1 2 3 4 5 7
-'i' <-- should be 3 ' '
-'C' <-- should be 3 ' '
-'a' <-- should be 4 ' '
-'A'<-- should be 0 ' '
-'D' <-- 'D' should be intact
-this is a test
-this is a test
-this is a test
-"r" x<-- should be 'x'
-"r" x <-- should be ' x '
-[This line should contain only the text between the brackets.]
- v i m <-- should show the name of a noted text editor
- 6 . 0 <-- and its version number
-
-a
-a
-a
-
diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim
index 837e55ebca..fc79f57d2e 100644
--- a/src/nvim/testdir/test49.vim
+++ b/src/nvim/testdir/test49.vim
@@ -178,7 +178,7 @@ endif
" next Xpath value. No new Xnext value is prepared. The argument
" should be 2^(n-1) for the nth Xloop command inside the loop.
" If the loop has only one Xloop command, the argument can be
-" ommitted (default: 1).
+" omitted (default: 1).
"
" - Use XloopNEXT before ":continue" and ":endwhile". This computes a new
" Xnext value for the next execution of the loop by multiplying the old
diff --git a/src/nvim/testdir/test64.in b/src/nvim/testdir/test64.in
deleted file mode 100644
index ec11e15e35..0000000000
--- a/src/nvim/testdir/test64.in
+++ /dev/null
@@ -1,654 +0,0 @@
-Test for regexp patterns without multi-byte support.
-See test95 for multi-byte tests.
-
-A pattern that gives the expected result produces OK, so that we know it was
-actually tried.
-
-STARTTEST
-:" tl is a List of Lists with:
-:" regexp engine
-:" regexp pattern
-:" text to test the pattern on
-:" expected match (optional)
-:" expected submatch 1 (optional)
-:" expected submatch 2 (optional)
-:" etc.
-:" When there is no match use only the first two items.
-:let tl = []
-:"
-:""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-:"""" Previously written tests """"""""""""""""""""""""""""""""
-:""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-:"
-:set noautoindent
-:call add(tl, [2, 'ab', 'aab', 'ab'])
-:call add(tl, [2, 'b', 'abcdef', 'b'])
-:call add(tl, [2, 'bc*', 'abccccdef', 'bcccc'])
-:call add(tl, [2, 'bc\{-}', 'abccccdef', 'b'])
-:call add(tl, [2, 'bc\{-}\(d\)', 'abccccdef', 'bccccd', 'd'])
-:call add(tl, [2, 'bc*', 'abbdef', 'b'])
-:call add(tl, [2, 'c*', 'ccc', 'ccc'])
-:call add(tl, [2, 'bc*', 'abdef', 'b'])
-:call add(tl, [2, 'c*', 'abdef', ''])
-:call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc'])
-:call add(tl, [2, 'bc\+', 'abdef']) "no match
-:"
-:"operator \|
-:call add(tl, [2, 'a\|ab', 'cabd', 'a']) "alternation is ordered
-:"
-:call add(tl, [2, 'c\?', 'ccb', 'c'])
-:call add(tl, [2, 'bc\?', 'abd', 'b'])
-:call add(tl, [2, 'bc\?', 'abccd', 'bc'])
-:"
-:call add(tl, [2, '\va{1}', 'ab', 'a'])
-:"
-:call add(tl, [2, '\va{2}', 'aa', 'aa'])
-:call add(tl, [2, '\va{2}', 'caad', 'aa'])
-:call add(tl, [2, '\va{2}', 'aba'])
-:call add(tl, [2, '\va{2}', 'ab'])
-:call add(tl, [2, '\va{2}', 'abaa', 'aa'])
-:call add(tl, [2, '\va{2}', 'aaa', 'aa'])
-:"
-:call add(tl, [2, '\vb{1}', 'abca', 'b'])
-:call add(tl, [2, '\vba{2}', 'abaa', 'baa'])
-:call add(tl, [2, '\vba{3}', 'aabaac'])
-:"
-:call add(tl, [2, '\v(ab){1}', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(ab){1}', 'dabc', 'ab', 'ab'])
-:call add(tl, [2, '\v(ab){1}', 'acb'])
-:"
-:call add(tl, [2, '\v(ab){0,2}', 'acb', "", ""])
-:call add(tl, [2, '\v(ab){0,2}', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(ab){1,2}', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(ab){1,2}', 'ababc', 'abab', 'ab'])
-:call add(tl, [2, '\v(ab){2,4}', 'ababcab', 'abab', 'ab'])
-:call add(tl, [2, '\v(ab){2,4}', 'abcababa', 'abab', 'ab'])
-:"
-:call add(tl, [2, '\v(ab){2}', 'abab', 'abab', 'ab'])
-:call add(tl, [2, '\v(ab){2}', 'cdababe', 'abab', 'ab'])
-:call add(tl, [2, '\v(ab){2}', 'abac'])
-:call add(tl, [2, '\v(ab){2}', 'abacabab', 'abab', 'ab'])
-:call add(tl, [2, '\v((ab){2}){2}', 'abababab', 'abababab', 'abab', 'ab'])
-:call add(tl, [2, '\v((ab){2}){2}', 'abacabababab', 'abababab', 'abab', 'ab'])
-:"
-:call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
-:call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{2}){1}', 'aaac', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{2}){1}', 'daaac', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{1}){2}', 'daaac', 'aa', 'a'])
-:call add(tl, [2, '\v(a{1}){2}', 'aaa', 'aa', 'a'])
-:call add(tl, [2, '\v(a{2})+', 'adaac', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{2})+', 'aa', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
-:call add(tl, [2, '\v(a{1}){2}', 'aa', 'aa', 'a'])
-:call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
-:call add(tl, [2, '\v(a{2}){2}', 'aaaa', 'aaaa', 'aa'])
-:call add(tl, [2, '\v(a{2}){2}', 'aaabaaaa', 'aaaa', 'aa'])
-:"
-:call add(tl, [2, '\v(a+){2}', 'dadaac', 'aa', 'a'])
-:call add(tl, [2, '\v(a{3}){2}', 'aaaaaaa', 'aaaaaa', 'aaa'])
-:"
-:call add(tl, [2, '\v(a{1,2}){2}', 'daaac', 'aaa', 'a'])
-:call add(tl, [2, '\v(a{1,3}){2}', 'daaaac', 'aaaa', 'a'])
-:call add(tl, [2, '\v(a{1,3}){2}', 'daaaaac', 'aaaaa', 'aa'])
-:call add(tl, [2, '\v(a{1,3}){3}', 'daac'])
-:call add(tl, [2, '\v(a{1,2}){2}', 'dac'])
-:call add(tl, [2, '\v(a+)+', 'daac', 'aa', 'aa'])
-:call add(tl, [2, '\v(a+)+', 'aaa', 'aaa', 'aaa'])
-:call add(tl, [2, '\v(a+){1,2}', 'aaa', 'aaa', 'aaa'])
-:call add(tl, [2, '\v(a+)(a+)', 'aaa', 'aaa', 'aa', 'a'])
-:call add(tl, [2, '\v(a{3})+', 'daaaac', 'aaa', 'aaa'])
-:call add(tl, [2, '\v(a|b|c)+', 'aacb', 'aacb', 'b'])
-:call add(tl, [2, '\v(a|b|c){2}', 'abcb', 'ab', 'b'])
-:call add(tl, [2, '\v(abc){2}', 'abcabd', ])
-:call add(tl, [2, '\v(abc){2}', 'abdabcabc','abcabc', 'abc'])
-:"
-:call add(tl, [2, 'a*', 'cc', ''])
-:call add(tl, [2, '\v(a*)+', 'cc', ''])
-:call add(tl, [2, '\v((ab)+)+', 'ab', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(((ab)+)+)+', 'ab', 'ab', 'ab', 'ab', 'ab'])
-:call add(tl, [2, '\v(((ab)+)+)+', 'dababc', 'abab', 'abab', 'abab', 'ab'])
-:call add(tl, [2, '\v(a{0,2})+', 'cc', ''])
-:call add(tl, [2, '\v(a*)+', '', ''])
-:call add(tl, [2, '\v((a*)+)+', '', ''])
-:call add(tl, [2, '\v((ab)*)+', '', ''])
-:call add(tl, [2, '\va{1,3}', 'aab', 'aa'])
-:call add(tl, [2, '\va{2,3}', 'abaa', 'aa'])
-:"
-:call add(tl, [2, '\v((ab)+|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
-:call add(tl, [2, '\v(a{2})|(b{3})', 'bbabbbb', 'bbb', '', 'bbb'])
-:call add(tl, [2, '\va{2}|b{2}', 'abab'])
-:call add(tl, [2, '\v(a)+|(c)+', 'bbacbaacbbb', 'a', 'a'])
-:call add(tl, [2, '\vab{2,3}c', 'aabbccccccccccccc', 'abbc'])
-:call add(tl, [2, '\vab{2,3}c', 'aabbbccccccccccccc', 'abbbc'])
-:call add(tl, [2, '\vab{2,3}cd{2,3}e', 'aabbbcddee', 'abbbcdde'])
-:call add(tl, [2, '\va(bc){2}d', 'aabcbfbc' ])
-:call add(tl, [2, '\va*a{2}', 'a', ])
-:call add(tl, [2, '\va*a{2}', 'aa', 'aa' ])
-:call add(tl, [2, '\va*a{2}', 'aaa', 'aaa' ])
-:call add(tl, [2, '\va*a{2}', 'bbbabcc', ])
-:call add(tl, [2, '\va*b*|a*c*', 'a', 'a'])
-:call add(tl, [2, '\va{1}b{1}|a{1}b{1}', ''])
-:"
-:"submatches
-:call add(tl, [2, '\v(a)', 'ab', 'a', 'a'])
-:call add(tl, [2, '\v(a)(b)', 'ab', 'ab', 'a', 'b'])
-:call add(tl, [2, '\v(ab)(b)(c)', 'abbc', 'abbc', 'ab', 'b', 'c'])
-:call add(tl, [2, '\v((a)(b))', 'ab', 'ab', 'ab', 'a', 'b'])
-:call add(tl, [2, '\v(a)|(b)', 'ab', 'a', 'a'])
-:"
-:call add(tl, [2, '\v(a*)+', 'aaaa', 'aaaa', ''])
-:call add(tl, [2, 'x', 'abcdef'])
-:"
-:""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-:""""" Simple tests """""""""""""""""""""""""""""""""""""""""""
-:""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-:"
-:" Search single groups
-:call add(tl, [2, 'ab', 'aab', 'ab'])
-:call add(tl, [2, 'ab', 'baced'])
-:call add(tl, [2, 'ab', ' ab ', 'ab'])
-:"
-:" Search multi-modifiers
-:call add(tl, [2, 'x*', 'xcd', 'x'])
-:call add(tl, [2, 'x*', 'xxxxxxxxxxxxxxxxsofijiojgf', 'xxxxxxxxxxxxxxxx'])
-:" empty match is good
-:call add(tl, [2, 'x*', 'abcdoij', ''])
-:" no match here
-:call add(tl, [2, 'x\+', 'abcdoin'])
-:call add(tl, [2, 'x\+', 'abcdeoijdfxxiuhfij', 'xx'])
-:call add(tl, [2, 'x\+', 'xxxxx', 'xxxxx'])
-:call add(tl, [2, 'x\+', 'abc x siufhiush xxxxxxxxx', 'x'])
-:call add(tl, [2, 'x\=', 'x sdfoij', 'x'])
-:call add(tl, [2, 'x\=', 'abc sfoij', '']) " empty match is good
-:call add(tl, [2, 'x\=', 'xxxxxxxxx c', 'x'])
-:call add(tl, [2, 'x\?', 'x sdfoij', 'x'])
-:" empty match is good
-:call add(tl, [2, 'x\?', 'abc sfoij', ''])
-:call add(tl, [2, 'x\?', 'xxxxxxxxxx c', 'x'])
-:"
-:call add(tl, [2, 'a\{0,0}', 'abcdfdoij', ''])
-:" same thing as 'a?'
-:call add(tl, [2, 'a\{0,1}', 'asiubid axxxaaa', 'a'])
-:" same thing as 'a\{0,1}'
-:call add(tl, [2, 'a\{1,0}', 'asiubid axxxaaa', 'a'])
-:call add(tl, [2, 'a\{3,6}', 'aa siofuh'])
-:call add(tl, [2, 'a\{3,6}', 'aaaaa asfoij afaa', 'aaaaa'])
-:call add(tl, [2, 'a\{3,6}', 'aaaaaaaa', 'aaaaaa'])
-:call add(tl, [2, 'a\{0}', 'asoiuj', ''])
-:call add(tl, [2, 'a\{2}', 'aaaa', 'aa'])
-:call add(tl, [2, 'a\{2}', 'iuash fiusahfliusah fiushfilushfi uhsaifuh askfj nasfvius afg aaaa sfiuhuhiushf', 'aa'])
-:call add(tl, [2, 'a\{2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
-:" same thing as 'a*'
-:call add(tl, [2, 'a\{0,}', 'oij sdigfusnf', ''])
-:call add(tl, [2, 'a\{0,}', 'aaaaa aa', 'aaaaa'])
-:call add(tl, [2, 'a\{2,}', 'sdfiougjdsafg'])
-:call add(tl, [2, 'a\{2,}', 'aaaaasfoij ', 'aaaaa'])
-:call add(tl, [2, 'a\{5,}', 'xxaaaaxxx '])
-:call add(tl, [2, 'a\{5,}', 'xxaaaaaxxx ', 'aaaaa'])
-:call add(tl, [2, 'a\{,0}', 'oidfguih iuhi hiu aaaa', ''])
-:call add(tl, [2, 'a\{,5}', 'abcd', 'a'])
-:call add(tl, [2, 'a\{,5}', 'aaaaaaaaaa', 'aaaaa'])
-:" leading star as normal char when \{} follows
-:call add(tl, [2, '^*\{4,}$', '***'])
-:call add(tl, [2, '^*\{4,}$', '****', '****'])
-:call add(tl, [2, '^*\{4,}$', '*****', '*****'])
-:" same thing as 'a*'
-:call add(tl, [2, 'a\{}', 'bbbcddiuhfcd', ''])
-:call add(tl, [2, 'a\{}', 'aaaaioudfh coisf jda', 'aaaa'])
-:"
-:call add(tl, [2, 'a\{-0,0}', 'abcdfdoij', ''])
-:" anti-greedy version of 'a?'
-:call add(tl, [2, 'a\{-0,1}', 'asiubid axxxaaa', ''])
-:call add(tl, [2, 'a\{-3,6}', 'aa siofuh'])
-:call add(tl, [2, 'a\{-3,6}', 'aaaaa asfoij afaa', 'aaa'])
-:call add(tl, [2, 'a\{-3,6}', 'aaaaaaaa', 'aaa'])
-:call add(tl, [2, 'a\{-0}', 'asoiuj', ''])
-:call add(tl, [2, 'a\{-2}', 'aaaa', 'aa'])
-:call add(tl, [2, 'a\{-2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
-:call add(tl, [2, 'a\{-0,}', 'oij sdigfusnf', ''])
-:call add(tl, [2, 'a\{-0,}', 'aaaaa aa', ''])
-:call add(tl, [2, 'a\{-2,}', 'sdfiougjdsafg'])
-:call add(tl, [2, 'a\{-2,}', 'aaaaasfoij ', 'aa'])
-:call add(tl, [2, 'a\{-,0}', 'oidfguih iuhi hiu aaaa', ''])
-:call add(tl, [2, 'a\{-,5}', 'abcd', ''])
-:call add(tl, [2, 'a\{-,5}', 'aaaaaaaaaa', ''])
-:" anti-greedy version of 'a*'
-:call add(tl, [2, 'a\{-}', 'bbbcddiuhfcd', ''])
-:call add(tl, [2, 'a\{-}', 'aaaaioudfh coisf jda', ''])
-:"
-:" Test groups of characters and submatches
-:call add(tl, [2, '\(abc\)*', 'abcabcabc', 'abcabcabc', 'abc'])
-:call add(tl, [2, '\(ab\)\+', 'abababaaaaa', 'ababab', 'ab'])
-:call add(tl, [2, '\(abaaaaa\)*cd', 'cd', 'cd', ''])
-:call add(tl, [2, '\(test1\)\? \(test2\)\?', 'test1 test3', 'test1 ', 'test1', ''])
-:call add(tl, [2, '\(test1\)\= \(test2\) \(test4443\)\=', ' test2 test4443 yupiiiiiiiiiii', ' test2 test4443', '', 'test2', 'test4443'])
-:call add(tl, [2, '\(\(sub1\) hello \(sub 2\)\)', 'asterix sub1 hello sub 2 obelix', 'sub1 hello sub 2', 'sub1 hello sub 2', 'sub1', 'sub 2'])
-:call add(tl, [2, '\(\(\(yyxxzz\)\)\)', 'abcdddsfiusfyyzzxxyyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz'])
-:call add(tl, [2, '\v((ab)+|c+)+', 'abcccaba', 'abcccab', 'ab', 'ab'])
-:call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
-:call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', ''])
-:call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', ''])
-:call add(tl, [2, '\p*', 'aá ', 'aá '])
-:"
-:" Test greedy-ness and lazy-ness
-:call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa'])
-:call add(tl, [2, 'a\{-2,7}x','aaaaaaaaax', 'aaaaaaax'])
-:call add(tl, [2, 'a\{2,7}','aaaaaaaaaaaaaaaaaaaa', 'aaaaaaa'])
-:call add(tl, [2, 'a\{2,7}x','aaaaaaaaax', 'aaaaaaax'])
-:call add(tl, [2, '\vx(.{-,8})yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz','ayxa','xayzxayz'])
-:call add(tl, [2, '\vx(.*)yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz', 'ayxayzxayzxa',''])
-:call add(tl, [2, '\v(a{1,2}){-2,3}','aaaaaaa','aaaa','aa'])
-:call add(tl, [2, '\v(a{-1,3})+', 'aa', 'aa', 'a'])
-:call add(tl, [2, '^\s\{-}\zs\( x\|x$\)', ' x', ' x', ' x'])
-:call add(tl, [2, '^\s\{-}\zs\(x\| x$\)', ' x', ' x', ' x'])
-:call add(tl, [2, '^\s\{-}\ze\(x\| x$\)', ' x', '', ' x'])
-:call add(tl, [2, '^\(\s\{-}\)\(x\| x$\)', ' x', ' x', '', ' x'])
-:"
-:" Test Character classes
-:call add(tl, [2, '\d\+e\d\d','test 10e23 fd','10e23'])
-:"
-:" Test collections and character range []
-:call add(tl, [2, '\v[a]', 'abcd', 'a'])
-:call add(tl, [2, 'a[bcd]', 'abcd', 'ab'])
-:call add(tl, [2, 'a[b-d]', 'acbd', 'ac'])
-:call add(tl, [2, '[a-d][e-f][x-x]d', 'cexdxx', 'cexd'])
-:call add(tl, [2, '\v[[:alpha:]]+', 'abcdefghijklmnopqrstuvwxyz6','abcdefghijklmnopqrstuvwxyz'])
-:call add(tl, [2, '[[:alpha:]\+]', '6x8','x'])
-:call add(tl, [2, '[^abc]\+','abcabcabc'])
-:call add(tl, [2, '[^abc]','defghiasijvoinasoiunbvb','d'])
-:call add(tl, [2, '[^abc]\+','ddddddda','ddddddd'])
-:call add(tl, [2, '[^a-d]\+','aaaAAAZIHFNCddd','AAAZIHFNC'])
-:call add(tl, [2, '[a-f]*','iiiiiiii',''])
-:call add(tl, [2, '[a-f]*','abcdefgh','abcdef'])
-:call add(tl, [2, '[^a-f]\+','abcdefgh','gh'])
-:call add(tl, [2, '[a-c]\{-3,6}','abcabc','abc'])
-:call add(tl, [2, '[^[:alpha:]]\+','abcccadfoij7787ysf287yrnccdu','7787'])
-:call add(tl, [2, '[-a]', '-', '-'])
-:call add(tl, [2, '[a-]', '-', '-'])
-:call add(tl, [2, '[a-f]*\c','ABCDEFGH','ABCDEF'])
-:call add(tl, [2, '[abc][xyz]\c','-af-AF-BY--','BY'])
-:" filename regexp
-:call add(tl, [2, '[-./[:alnum:]_~]\+', 'log13.file', 'log13.file'])
-:" special chars
-:call add(tl, [2, '[\]\^\-\\]\+', '\^\\\-\---^', '\^\\\-\---^'])
-:" collation elem
-:call add(tl, [2, '[[.a.]]\+', 'aa', 'aa'])
-:" middle of regexp
-:call add(tl, [2, 'abc[0-9]*ddd', 'siuhabc ii'])
-:call add(tl, [2, 'abc[0-9]*ddd', 'adf abc44482ddd oijs', 'abc44482ddd'])
-:call add(tl, [2, '\_[0-9]\+', 'asfi9888u', '9888'])
-:call add(tl, [2, '[0-9\n]\+', 'asfi9888u', '9888'])
-:call add(tl, [2, '\_[0-9]\+', "asfi\n9888u", "\n9888"])
-:call add(tl, [2, '\_f', " \na ", "\n"])
-:call add(tl, [2, '\_f\+', " \na ", "\na"])
-:call add(tl, [2, '[0-9A-Za-z-_.]\+', " @0_a.A-{ ", "0_a.A-"])
-:"
-:"""" Test start/end of line, start/end of file
-:call add(tl, [2, '^a.', "a_\nb ", "a_"])
-:call add(tl, [2, '^a.', "b a \na_"])
-:call add(tl, [2, '.a$', " a\n "])
-:call add(tl, [2, '.a$', " a b\n_a", "_a"])
-:call add(tl, [2, '\%^a.', "a a\na", "a "])
-:call add(tl, [2, '\%^a', " a \na "])
-:call add(tl, [2, '.a\%$', " a\n "])
-:call add(tl, [2, '.a\%$', " a\n_a", "_a"])
-:"
-:"""" Test recognition of character classes
-:call add(tl, [2, '[0-7]\+', 'x0123456789x', '01234567'])
-:call add(tl, [2, '[^0-7]\+', '0a;X+% 897', 'a;X+% 89'])
-:call add(tl, [2, '[0-9]\+', 'x0123456789x', '0123456789'])
-:call add(tl, [2, '[^0-9]\+', '0a;X+% 9', 'a;X+% '])
-:call add(tl, [2, '[0-9a-fA-F]\+', 'x0189abcdefg', '0189abcdef'])
-:call add(tl, [2, '[^0-9A-Fa-f]\+', '0189g;X+% ab', 'g;X+% '])
-:call add(tl, [2, '[a-z_A-Z0-9]\+', ';+aso_SfOij ', 'aso_SfOij'])
-:call add(tl, [2, '[^a-z_A-Z0-9]\+', 'aSo_;+% sfOij', ';+% '])
-:call add(tl, [2, '[a-z_A-Z]\+', '0abyz_ABYZ;', 'abyz_ABYZ'])
-:call add(tl, [2, '[^a-z_A-Z]\+', 'abAB_09;+% yzYZ', '09;+% '])
-:call add(tl, [2, '[a-z]\+', '0abcxyz1', 'abcxyz'])
-:call add(tl, [2, '[a-z]\+', 'AabxyzZ', 'abxyz'])
-:call add(tl, [2, '[^a-z]\+', 'a;X09+% x', ';X09+% '])
-:call add(tl, [2, '[^a-z]\+', 'abX0;%yz', 'X0;%'])
-:call add(tl, [2, '[a-zA-Z]\+', '0abABxzXZ9', 'abABxzXZ'])
-:call add(tl, [2, '[^a-zA-Z]\+', 'ab09_;+ XZ', '09_;+ '])
-:call add(tl, [2, '[A-Z]\+', 'aABXYZz', 'ABXYZ'])
-:call add(tl, [2, '[^A-Z]\+', 'ABx0;%YZ', 'x0;%'])
-:call add(tl, [2, '[a-z]\+\c', '0abxyzABXYZ;', 'abxyzABXYZ'])
-:call add(tl, [2, '[A-Z]\+\c', '0abABxzXZ9', 'abABxzXZ'])
-:call add(tl, [2, '\c[^a-z]\+', 'ab09_;+ XZ', '09_;+ '])
-:call add(tl, [2, '\c[^A-Z]\+', 'ab09_;+ XZ', '09_;+ '])
-:call add(tl, [2, '\C[^A-Z]\+', 'ABCOIJDEOIFNSD jsfoij sa', ' jsfoij sa'])
-:"
-:"""" Tests for \z features
-:" match ends at \ze
-:call add(tl, [2, 'xx \ze test', 'xx '])
-:call add(tl, [2, 'abc\zeend', 'oij abcend', 'abc'])
-:call add(tl, [2, 'aa\zebb\|aaxx', ' aabb ', 'aa'])
-:call add(tl, [2, 'aa\zebb\|aaxx', ' aaxx ', 'aaxx'])
-:call add(tl, [2, 'aabb\|aa\zebb', ' aabb ', 'aabb'])
-:call add(tl, [2, 'aa\zebb\|aaebb', ' aabb ', 'aa'])
-:" match starts at \zs
-:call add(tl, [2, 'abc\zsdd', 'ddabcddxyzt', 'dd'])
-:call add(tl, [2, 'aa \zsax', ' ax'])
-:call add(tl, [2, 'abc \zsmatch\ze abc', 'abc abc abc match abc abc', 'match'])
-:call add(tl, [2, '\v(a \zsif .*){2}', 'a if then a if last', 'if last', 'a if last'])
-:call add(tl, [2, '\>\zs.', 'aword. ', '.'])
-:call add(tl, [2, '\s\+\ze\[/\|\s\zs\s\+', 'is [a t', ' '])
-:"
-:"""" Tests for \@= and \& features
-:call add(tl, [2, 'abc\@=', 'abc', 'ab'])
-:call add(tl, [2, 'abc\@=cd', 'abcd', 'abcd'])
-:call add(tl, [2, 'abc\@=', 'ababc', 'ab'])
-:" will never match, no matter the input text
-:call add(tl, [2, 'abcd\@=e', 'abcd'])
-:" will never match
-:call add(tl, [2, 'abcd\@=e', 'any text in here ... '])
-:call add(tl, [2, '\v(abc)@=..', 'xabcd', 'ab', 'abc'])
-:call add(tl, [2, '\(.*John\)\@=.*Bob', 'here is John, and here is B'])
-:call add(tl, [2, '\(John.*\)\@=.*Bob', 'John is Bobs friend', 'John is Bob', 'John is Bobs friend'])
-:call add(tl, [2, '\<\S\+\())\)\@=', '$((i=i+1))', 'i=i+1', '))'])
-:call add(tl, [2, '.*John\&.*Bob', 'here is John, and here is B'])
-:call add(tl, [2, '.*John\&.*Bob', 'John is Bobs friend', 'John is Bob'])
-:call add(tl, [2, '\v(test1)@=.*yep', 'this is a test1, yep it is', 'test1, yep', 'test1'])
-:call add(tl, [2, 'foo\(bar\)\@!', 'foobar'])
-:call add(tl, [2, 'foo\(bar\)\@!', 'foo bar', 'foo'])
-:call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if then else'])
-:call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if else ', 'if else ', ' '])
-:call add(tl, [2, '\(foo\)\@!bar', 'foobar', 'bar'])
-:call add(tl, [2, '\(foo\)\@!...bar', 'foobar'])
-:call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' bar foo '])
-:call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo bar '])
-:call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo xxx ', 'foo'])
-:call add(tl, [2, '[ ]\@!\p\%([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:'])
-:call add(tl, [2, '[ ]\@!\p\([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:', 's'])
-:call add(tl, [2, 'm\k\+_\@=\%(_\@!\k\)\@<=\k\+e', 'mx__xe', 'mx__xe'])
-:call add(tl, [2, '\%(\U\@<=S\k*\|S\l\)R', 'SuR', 'SuR'])
-:"
-:"""" Combining different tests and features
-:call add(tl, [2, '[[:alpha:]]\{-2,6}', '787abcdiuhsasiuhb4', 'ab'])
-:call add(tl, [2, '', 'abcd', ''])
-:call add(tl, [2, '\v(())', 'any possible text', ''])
-:call add(tl, [2, '\v%(ab(xyz)c)', ' abxyzc ', 'abxyzc', 'xyz'])
-:call add(tl, [2, '\v(test|)empty', 'tesempty', 'empty', ''])
-:call add(tl, [2, '\v(a|aa)(a|aa)', 'aaa', 'aa', 'a', 'a'])
-:"
-:"""" \%u and friends
-:call add(tl, [2, '\%d32', 'yes no', ' '])
-:call add(tl, [2, '\%o40', 'yes no', ' '])
-:call add(tl, [2, '\%x20', 'yes no', ' '])
-:call add(tl, [2, '\%u0020', 'yes no', ' '])
-:call add(tl, [2, '\%U00000020', 'yes no', ' '])
-:call add(tl, [2, '\%d0', "yes\x0ano", "\x0a"])
-:"
-:""""" \%[abc]
-:call add(tl, [2, 'foo\%[bar]', 'fobar'])
-:call add(tl, [2, 'foo\%[bar]', 'foobar', 'foobar'])
-:call add(tl, [2, 'foo\%[bar]', 'fooxx', 'foo'])
-:call add(tl, [2, 'foo\%[bar]', 'foobxx', 'foob'])
-:call add(tl, [2, 'foo\%[bar]', 'foobaxx', 'fooba'])
-:call add(tl, [2, 'foo\%[bar]', 'foobarxx', 'foobar'])
-:call add(tl, [2, 'foo\%[bar]x', 'foobxx', 'foobx'])
-:call add(tl, [2, 'foo\%[bar]x', 'foobarxx', 'foobarx'])
-:call add(tl, [2, '\%[bar]x', 'barxx', 'barx'])
-:call add(tl, [2, '\%[bar]x', 'bxx', 'bx'])
-:call add(tl, [2, '\%[bar]x', 'xxx', 'x'])
-:call add(tl, [2, 'b\%[[ao]r]', 'bar bor', 'bar'])
-:call add(tl, [2, 'b\%[[]]r]', 'b]r bor', 'b]r'])
-:call add(tl, [2, '@\%[\w\-]*', '<http://john.net/pandoc/>[@pandoc]', '@pandoc'])
-:"
-:"""" Alternatives, must use first longest match
-:call add(tl, [2, 'goo\|go', 'google', 'goo'])
-:call add(tl, [2, '\<goo\|\<go', 'google', 'goo'])
-:call add(tl, [2, '\<goo\|go', 'google', 'goo'])
-:"
-:"""" Back references
-:call add(tl, [2, '\(\i\+\) \1', ' abc abc', 'abc abc', 'abc'])
-:call add(tl, [2, '\(\i\+\) \1', 'xgoo goox', 'goo goo', 'goo'])
-:call add(tl, [2, '\(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9', 'xabcddefghiabcddefghix', 'abcddefghiabcddefghi', 'a', 'b', 'c', 'dd', 'e', 'f', 'g', 'h', 'i'])
-:call add(tl, [2, '\(\d*\)a \1b', ' a b ', 'a b', ''])
-:call add(tl, [2, '^.\(.\).\_..\1.', "aaa\naaa\nb", "aaa\naaa", 'a'])
-:call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.com', 'foo.bat/foo.com', 'bat'])
-:call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.bat'])
-:call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<=$', 'foo.bat/foo.bat', 'foo.bat/foo.bat', 'bat', 'bat'])
-:call add(tl, [2, '\\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}', '2013-06-27${0}', '${0}', '0'])
-:call add(tl, [2, '^\(a*\)\1$', 'aaaaaaaa', 'aaaaaaaa', 'aaaa'])
-:call add(tl, [2, '^\(a\{-2,}\)\1\+$', 'aaaaaaaaa', 'aaaaaaaaa', 'aaa'])
-:"
-:"""" Look-behind with limit
-:call add(tl, [2, '<\@<=span.', 'xxspanxx<spanyyy', 'spany'])
-:call add(tl, [2, '<\@1<=span.', 'xxspanxx<spanyyy', 'spany'])
-:call add(tl, [2, '<\@2<=span.', 'xxspanxx<spanyyy', 'spany'])
-:call add(tl, [2, '\(<<\)\@<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
-:call add(tl, [2, '\(<<\)\@1<=span.', 'xxspanxxxx<spanxx<<spanyyy'])
-:call add(tl, [2, '\(<<\)\@2<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
-:call add(tl, [2, '\(foo\)\@<!bar.', 'xx foobar1 xbar2 xx', 'bar2'])
-:"
-:" look-behind match in front of a zero-width item
-:call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" test header'])
-:call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" Last Changed: 1970', '1970'])
-:call add(tl, [2, '\(foo\)\@<=\>', 'foobar'])
-:call add(tl, [2, '\(foo\)\@<=\>', 'barfoo', '', 'foo'])
-:call add(tl, [2, '\(foo\)\@<=.*', 'foobar', 'bar', 'foo'])
-:"
-:" complicated look-behind match
-:call add(tl, [2, '\(r\@<=\|\w\@<!\)\/', 'x = /word/;', '/'])
-:call add(tl, [2, '^[a-z]\+\ze \&\(asdf\)\@<!', 'foo bar', 'foo'])
-:"
-:""""" \@>
-:call add(tl, [2, '\(a*\)\@>a', 'aaaa'])
-:call add(tl, [2, '\(a*\)\@>b', 'aaab', 'aaab', 'aaa'])
-:call add(tl, [2, '^\(.\{-}b\)\@>.', ' abcbd', ' abc', ' ab'])
-:call add(tl, [2, '\(.\{-}\)\(\)\@>$', 'abc', 'abc', 'abc', ''])
-:" TODO: BT engine does not restore submatch after failure
-:call add(tl, [1, '\(a*\)\@>a\|a\+', 'aaaa', 'aaaa'])
-:"
-:"""" "\_" prepended negated collection matches EOL
-:call add(tl, [2, '\_[^8-9]\+', "asfi\n9888", "asfi\n"])
-:call add(tl, [2, '\_[^a]\+', "asfi\n9888", "sfi\n9888"])
-:"
-:"""" Requiring lots of states.
-:call add(tl, [2, '[0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}', " 12345678-1234-1234-1234-123456789012 ", "12345678-1234-1234-1234-123456789012", "1234-"])
-:"
-:"""" Skip adding state twice
-:call add(tl, [2, '^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=', "#if FOO", "#if", ' FOO'])
-:"
-:""" Test \%V atom
-:call add(tl, [2, '\%>70vGesamt', 'Jean-Michel Charlier & Victor Hubinon\Gesamtausgabe [Salleck] Buck Danny {Jean-Michel Charlier & Victor Hubinon}\Gesamtausgabe', 'Gesamt'])
-:"
-:"""" Run the tests
-:"
-:for t in tl
-: let re = t[0]
-: let pat = t[1]
-: let text = t[2]
-: let matchidx = 3
-: for engine in [0, 1, 2]
-: if engine == 2 && re == 0 || engine == 1 && re == 1
-: continue
-: endif
-: let &regexpengine = engine
-: try
-: let l = matchlist(text, pat)
-: catch
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", caused an exception: \"' . v:exception . '\"'
-: endtry
-:" check the match itself
-: if len(l) == 0 && len(t) > matchidx
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", did not match, expected: \"' . t[matchidx] . '\"'
-: elseif len(l) > 0 && len(t) == matchidx
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", match: \"' . l[0] . '\", expected no match'
-: elseif len(t) > matchidx && l[0] != t[matchidx]
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", match: \"' . l[0] . '\", expected: \"' . t[matchidx] . '\"'
-: else
-: $put ='OK ' . engine . ' - ' . pat
-: endif
-: if len(l) > 0
-:" check all the nine submatches
-: for i in range(1, 9)
-: if len(t) <= matchidx + i
-: let e = ''
-: else
-: let e = t[matchidx + i]
-: endif
-: if l[i] != e
-: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", submatch ' . i . ': \"' . l[i] . '\", expected: \"' . e . '\"'
-: endif
-: endfor
-: unlet i
-: endif
-: endfor
-:endfor
-:unlet t tl e l
-:"
-:"""""" multi-line tests """"""""""""""""""""
-:let tl = []
-:"
-:"""" back references
-:call add(tl, [2, '^.\(.\).\_..\1.', ['aaa', 'aaa', 'b'], ['XX', 'b']])
-:call add(tl, [2, '\v.*\/(.*)\n.*\/\1$', ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', './Dir1/Dir2/file1.txt', './OtherDir1/OtherDir2/file1.txt'], ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', 'XX']])
-:"
-:"""" line breaks
-:call add(tl, [2, '\S.*\nx', ['abc', 'def', 'ghi', 'xjk', 'lmn'], ['abc', 'def', 'XXjk', 'lmn']])
-:"
-:" Check that \_[0-9] matching EOL does not break a following \>
-:call add(tl, [2, '\<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>', ['', 'localnet/192.168.0.1', ''], ['', 'localnet/XX', '']])
-:"
-:" Check a pattern with a line break and ^ and $
-:call add(tl, [2, 'a\n^b$\n^c', ['a', 'b', 'c'], ['XX']])
-:"
-:call add(tl, [2, '\(^.\+\n\)\1', [' dog', ' dog', 'asdf'], ['XXasdf']])
-:"
-:"""" Run the multi-line tests
-:"
-:$put ='multi-line tests'
-:for t in tl
-: let re = t[0]
-: let pat = t[1]
-: let before = t[2]
-: let after = t[3]
-: for engine in [0, 1, 2]
-: if engine == 2 && re == 0 || engine == 1 && re ==1
-: continue
-: endif
-: let &regexpengine = engine
-: new
-: call setline(1, before)
-: exe '%s/' . pat . '/XX/'
-: let result = getline(1, '$')
-: q!
-: if result != after
-: $put ='ERROR: pat: \"' . pat . '\", text: \"' . string(before) . '\", expected: \"' . string(after) . '\", got: \"' . string(result) . '\"'
-: else
-: $put ='OK ' . engine . ' - ' . pat
-: endif
-: endfor
-:endfor
-:unlet t tl
-:"
-:" Check that using a pattern on two lines doesn't get messed up by using
-:" matchstr() with \ze in between.
-:set re=0
-/^Substitute here
-:.+1,.+2s/""/\='"'.matchstr(getline("."), '\d\+\ze<').'"'
-/^Substitute here
-:.+1,.+2yank
-Gop:"
-:"
-:" Check a pattern with a look beind crossing a line boundary
-/^Behind:
-/\(<\_[xy]\+\)\@3<=start
-:.yank
-Gop:"
-:"
-:" Check matching Visual area
-/^Visual:
-jfxvfx:s/\%Ve/E/g
-jV:s/\%Va/A/g
-jfxfxj:s/\%Vo/O/g
-:/^Visual/+1,/^Visual/+4yank
-Gop:"
-:"
-:" Check matching marks
-/^Marks:
-jfSmsfEme:.-4,.+6s/.\%>'s.*\%<'e../here/
-jfSmsj0fEme:.-4,.+6s/.\%>'s\_.*\%<'e../again/
-:/^Marks:/+1,/^Marks:/+3yank
-Gop:"
-:"
-:" Check patterns matching cursor position.
-:func! Postest()
- new
- call setline(1, ['ffooooo', 'boboooo', 'zoooooo', 'koooooo', 'moooooo', "\t\t\tfoo", 'abababababababfoo', 'bababababababafoo', '********_', ' xxxxxxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx'])
- call setpos('.', [0, 1, 0, 0])
- s/\%>3c.//g
- call setpos('.', [0, 2, 4, 0])
- s/\%#.*$//g
- call setpos('.', [0, 3, 0, 0])
- s/\%<3c./_/g
- %s/\%4l\%>5c./_/g
- %s/\%6l\%>25v./_/g
- %s/\%>6l\%3c./!/g
- %s/\%>7l\%12c./?/g
- %s/\%>7l\%<9l\%>5v\%<8v./#/g
- $s/\%(|\u.*\)\@<=[^|\t]\+$//ge
- 1,$yank
- quit!
-endfunc
-Go-0-:set re=0
-:call Postest()
-:put
-o-1-:set re=1
-:call Postest()
-:put
-o-2-:set re=2
-:call Postest()
-:put
-:"
-:" start and end of buffer
-/\%^
-yeGop:"
-50%/\%^..
-yeGopA END:"
-50%/\%$
-"ayb20gg/..\%$
-"bybGo"apo"bp:"
-:"
-:" Check for detecting error
-:set regexpengine=2
-:for pat in [' \ze*', ' \zs*']
-: try
-: let l = matchlist('x x', pat)
-: $put ='E888 NOT detected for ' . pat
-: catch
-: $put ='E888 detected for ' . pat
-: endtry
-:endfor
-:"
-:""""" Write the results """""""""""""
-:/\%#=1^Results/,$wq! test.out
-ENDTEST
-
-Substitute here:
-<T="">Ta 5</Title>
-<T="">Ac 7</Title>
-
-Behind:
-asdfasd<yyy
-xxstart1
-asdfasd<yy
-xxxstart2
-asdfasd<yy
-xxstart3
-
-Visual:
-thexe the thexethe
-andaxand andaxand
-oooxofor foroxooo
-oooxofor foroxooo
-
-Marks:
-asdfSasdfsadfEasdf
-asdfSas
-dfsadfEasdf
-
-Results of test64:
diff --git a/src/nvim/testdir/test64.ok b/src/nvim/testdir/test64.ok
deleted file mode 100644
index c218f8ea17..0000000000
--- a/src/nvim/testdir/test64.ok
+++ /dev/null
@@ -1,1107 +0,0 @@
-Results of test64:
-OK 0 - ab
-OK 1 - ab
-OK 2 - ab
-OK 0 - b
-OK 1 - b
-OK 2 - b
-OK 0 - bc*
-OK 1 - bc*
-OK 2 - bc*
-OK 0 - bc\{-}
-OK 1 - bc\{-}
-OK 2 - bc\{-}
-OK 0 - bc\{-}\(d\)
-OK 1 - bc\{-}\(d\)
-OK 2 - bc\{-}\(d\)
-OK 0 - bc*
-OK 1 - bc*
-OK 2 - bc*
-OK 0 - c*
-OK 1 - c*
-OK 2 - c*
-OK 0 - bc*
-OK 1 - bc*
-OK 2 - bc*
-OK 0 - c*
-OK 1 - c*
-OK 2 - c*
-OK 0 - bc\+
-OK 1 - bc\+
-OK 2 - bc\+
-OK 0 - bc\+
-OK 1 - bc\+
-OK 2 - bc\+
-OK 0 - a\|ab
-OK 1 - a\|ab
-OK 2 - a\|ab
-OK 0 - c\?
-OK 1 - c\?
-OK 2 - c\?
-OK 0 - bc\?
-OK 1 - bc\?
-OK 2 - bc\?
-OK 0 - bc\?
-OK 1 - bc\?
-OK 2 - bc\?
-OK 0 - \va{1}
-OK 1 - \va{1}
-OK 2 - \va{1}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \va{2}
-OK 1 - \va{2}
-OK 2 - \va{2}
-OK 0 - \vb{1}
-OK 1 - \vb{1}
-OK 2 - \vb{1}
-OK 0 - \vba{2}
-OK 1 - \vba{2}
-OK 2 - \vba{2}
-OK 0 - \vba{3}
-OK 1 - \vba{3}
-OK 2 - \vba{3}
-OK 0 - \v(ab){1}
-OK 1 - \v(ab){1}
-OK 2 - \v(ab){1}
-OK 0 - \v(ab){1}
-OK 1 - \v(ab){1}
-OK 2 - \v(ab){1}
-OK 0 - \v(ab){1}
-OK 1 - \v(ab){1}
-OK 2 - \v(ab){1}
-OK 0 - \v(ab){0,2}
-OK 1 - \v(ab){0,2}
-OK 2 - \v(ab){0,2}
-OK 0 - \v(ab){0,2}
-OK 1 - \v(ab){0,2}
-OK 2 - \v(ab){0,2}
-OK 0 - \v(ab){1,2}
-OK 1 - \v(ab){1,2}
-OK 2 - \v(ab){1,2}
-OK 0 - \v(ab){1,2}
-OK 1 - \v(ab){1,2}
-OK 2 - \v(ab){1,2}
-OK 0 - \v(ab){2,4}
-OK 1 - \v(ab){2,4}
-OK 2 - \v(ab){2,4}
-OK 0 - \v(ab){2,4}
-OK 1 - \v(ab){2,4}
-OK 2 - \v(ab){2,4}
-OK 0 - \v(ab){2}
-OK 1 - \v(ab){2}
-OK 2 - \v(ab){2}
-OK 0 - \v(ab){2}
-OK 1 - \v(ab){2}
-OK 2 - \v(ab){2}
-OK 0 - \v(ab){2}
-OK 1 - \v(ab){2}
-OK 2 - \v(ab){2}
-OK 0 - \v(ab){2}
-OK 1 - \v(ab){2}
-OK 2 - \v(ab){2}
-OK 0 - \v((ab){2}){2}
-OK 1 - \v((ab){2}){2}
-OK 2 - \v((ab){2}){2}
-OK 0 - \v((ab){2}){2}
-OK 1 - \v((ab){2}){2}
-OK 2 - \v((ab){2}){2}
-OK 0 - \v(a{1}){1}
-OK 1 - \v(a{1}){1}
-OK 2 - \v(a{1}){1}
-OK 0 - \v(a{2}){1}
-OK 1 - \v(a{2}){1}
-OK 2 - \v(a{2}){1}
-OK 0 - \v(a{2}){1}
-OK 1 - \v(a{2}){1}
-OK 2 - \v(a{2}){1}
-OK 0 - \v(a{2}){1}
-OK 1 - \v(a{2}){1}
-OK 2 - \v(a{2}){1}
-OK 0 - \v(a{1}){2}
-OK 1 - \v(a{1}){2}
-OK 2 - \v(a{1}){2}
-OK 0 - \v(a{1}){2}
-OK 1 - \v(a{1}){2}
-OK 2 - \v(a{1}){2}
-OK 0 - \v(a{2})+
-OK 1 - \v(a{2})+
-OK 2 - \v(a{2})+
-OK 0 - \v(a{2})+
-OK 1 - \v(a{2})+
-OK 2 - \v(a{2})+
-OK 0 - \v(a{2}){1}
-OK 1 - \v(a{2}){1}
-OK 2 - \v(a{2}){1}
-OK 0 - \v(a{1}){2}
-OK 1 - \v(a{1}){2}
-OK 2 - \v(a{1}){2}
-OK 0 - \v(a{1}){1}
-OK 1 - \v(a{1}){1}
-OK 2 - \v(a{1}){1}
-OK 0 - \v(a{2}){2}
-OK 1 - \v(a{2}){2}
-OK 2 - \v(a{2}){2}
-OK 0 - \v(a{2}){2}
-OK 1 - \v(a{2}){2}
-OK 2 - \v(a{2}){2}
-OK 0 - \v(a+){2}
-OK 1 - \v(a+){2}
-OK 2 - \v(a+){2}
-OK 0 - \v(a{3}){2}
-OK 1 - \v(a{3}){2}
-OK 2 - \v(a{3}){2}
-OK 0 - \v(a{1,2}){2}
-OK 1 - \v(a{1,2}){2}
-OK 2 - \v(a{1,2}){2}
-OK 0 - \v(a{1,3}){2}
-OK 1 - \v(a{1,3}){2}
-OK 2 - \v(a{1,3}){2}
-OK 0 - \v(a{1,3}){2}
-OK 1 - \v(a{1,3}){2}
-OK 2 - \v(a{1,3}){2}
-OK 0 - \v(a{1,3}){3}
-OK 1 - \v(a{1,3}){3}
-OK 2 - \v(a{1,3}){3}
-OK 0 - \v(a{1,2}){2}
-OK 1 - \v(a{1,2}){2}
-OK 2 - \v(a{1,2}){2}
-OK 0 - \v(a+)+
-OK 1 - \v(a+)+
-OK 2 - \v(a+)+
-OK 0 - \v(a+)+
-OK 1 - \v(a+)+
-OK 2 - \v(a+)+
-OK 0 - \v(a+){1,2}
-OK 1 - \v(a+){1,2}
-OK 2 - \v(a+){1,2}
-OK 0 - \v(a+)(a+)
-OK 1 - \v(a+)(a+)
-OK 2 - \v(a+)(a+)
-OK 0 - \v(a{3})+
-OK 1 - \v(a{3})+
-OK 2 - \v(a{3})+
-OK 0 - \v(a|b|c)+
-OK 1 - \v(a|b|c)+
-OK 2 - \v(a|b|c)+
-OK 0 - \v(a|b|c){2}
-OK 1 - \v(a|b|c){2}
-OK 2 - \v(a|b|c){2}
-OK 0 - \v(abc){2}
-OK 1 - \v(abc){2}
-OK 2 - \v(abc){2}
-OK 0 - \v(abc){2}
-OK 1 - \v(abc){2}
-OK 2 - \v(abc){2}
-OK 0 - a*
-OK 1 - a*
-OK 2 - a*
-OK 0 - \v(a*)+
-OK 1 - \v(a*)+
-OK 2 - \v(a*)+
-OK 0 - \v((ab)+)+
-OK 1 - \v((ab)+)+
-OK 2 - \v((ab)+)+
-OK 0 - \v(((ab)+)+)+
-OK 1 - \v(((ab)+)+)+
-OK 2 - \v(((ab)+)+)+
-OK 0 - \v(((ab)+)+)+
-OK 1 - \v(((ab)+)+)+
-OK 2 - \v(((ab)+)+)+
-OK 0 - \v(a{0,2})+
-OK 1 - \v(a{0,2})+
-OK 2 - \v(a{0,2})+
-OK 0 - \v(a*)+
-OK 1 - \v(a*)+
-OK 2 - \v(a*)+
-OK 0 - \v((a*)+)+
-OK 1 - \v((a*)+)+
-OK 2 - \v((a*)+)+
-OK 0 - \v((ab)*)+
-OK 1 - \v((ab)*)+
-OK 2 - \v((ab)*)+
-OK 0 - \va{1,3}
-OK 1 - \va{1,3}
-OK 2 - \va{1,3}
-OK 0 - \va{2,3}
-OK 1 - \va{2,3}
-OK 2 - \va{2,3}
-OK 0 - \v((ab)+|c*)+
-OK 1 - \v((ab)+|c*)+
-OK 2 - \v((ab)+|c*)+
-OK 0 - \v(a{2})|(b{3})
-OK 1 - \v(a{2})|(b{3})
-OK 2 - \v(a{2})|(b{3})
-OK 0 - \va{2}|b{2}
-OK 1 - \va{2}|b{2}
-OK 2 - \va{2}|b{2}
-OK 0 - \v(a)+|(c)+
-OK 1 - \v(a)+|(c)+
-OK 2 - \v(a)+|(c)+
-OK 0 - \vab{2,3}c
-OK 1 - \vab{2,3}c
-OK 2 - \vab{2,3}c
-OK 0 - \vab{2,3}c
-OK 1 - \vab{2,3}c
-OK 2 - \vab{2,3}c
-OK 0 - \vab{2,3}cd{2,3}e
-OK 1 - \vab{2,3}cd{2,3}e
-OK 2 - \vab{2,3}cd{2,3}e
-OK 0 - \va(bc){2}d
-OK 1 - \va(bc){2}d
-OK 2 - \va(bc){2}d
-OK 0 - \va*a{2}
-OK 1 - \va*a{2}
-OK 2 - \va*a{2}
-OK 0 - \va*a{2}
-OK 1 - \va*a{2}
-OK 2 - \va*a{2}
-OK 0 - \va*a{2}
-OK 1 - \va*a{2}
-OK 2 - \va*a{2}
-OK 0 - \va*a{2}
-OK 1 - \va*a{2}
-OK 2 - \va*a{2}
-OK 0 - \va*b*|a*c*
-OK 1 - \va*b*|a*c*
-OK 2 - \va*b*|a*c*
-OK 0 - \va{1}b{1}|a{1}b{1}
-OK 1 - \va{1}b{1}|a{1}b{1}
-OK 2 - \va{1}b{1}|a{1}b{1}
-OK 0 - \v(a)
-OK 1 - \v(a)
-OK 2 - \v(a)
-OK 0 - \v(a)(b)
-OK 1 - \v(a)(b)
-OK 2 - \v(a)(b)
-OK 0 - \v(ab)(b)(c)
-OK 1 - \v(ab)(b)(c)
-OK 2 - \v(ab)(b)(c)
-OK 0 - \v((a)(b))
-OK 1 - \v((a)(b))
-OK 2 - \v((a)(b))
-OK 0 - \v(a)|(b)
-OK 1 - \v(a)|(b)
-OK 2 - \v(a)|(b)
-OK 0 - \v(a*)+
-OK 1 - \v(a*)+
-OK 2 - \v(a*)+
-OK 0 - x
-OK 1 - x
-OK 2 - x
-OK 0 - ab
-OK 1 - ab
-OK 2 - ab
-OK 0 - ab
-OK 1 - ab
-OK 2 - ab
-OK 0 - ab
-OK 1 - ab
-OK 2 - ab
-OK 0 - x*
-OK 1 - x*
-OK 2 - x*
-OK 0 - x*
-OK 1 - x*
-OK 2 - x*
-OK 0 - x*
-OK 1 - x*
-OK 2 - x*
-OK 0 - x\+
-OK 1 - x\+
-OK 2 - x\+
-OK 0 - x\+
-OK 1 - x\+
-OK 2 - x\+
-OK 0 - x\+
-OK 1 - x\+
-OK 2 - x\+
-OK 0 - x\+
-OK 1 - x\+
-OK 2 - x\+
-OK 0 - x\=
-OK 1 - x\=
-OK 2 - x\=
-OK 0 - x\=
-OK 1 - x\=
-OK 2 - x\=
-OK 0 - x\=
-OK 1 - x\=
-OK 2 - x\=
-OK 0 - x\?
-OK 1 - x\?
-OK 2 - x\?
-OK 0 - x\?
-OK 1 - x\?
-OK 2 - x\?
-OK 0 - x\?
-OK 1 - x\?
-OK 2 - x\?
-OK 0 - a\{0,0}
-OK 1 - a\{0,0}
-OK 2 - a\{0,0}
-OK 0 - a\{0,1}
-OK 1 - a\{0,1}
-OK 2 - a\{0,1}
-OK 0 - a\{1,0}
-OK 1 - a\{1,0}
-OK 2 - a\{1,0}
-OK 0 - a\{3,6}
-OK 1 - a\{3,6}
-OK 2 - a\{3,6}
-OK 0 - a\{3,6}
-OK 1 - a\{3,6}
-OK 2 - a\{3,6}
-OK 0 - a\{3,6}
-OK 1 - a\{3,6}
-OK 2 - a\{3,6}
-OK 0 - a\{0}
-OK 1 - a\{0}
-OK 2 - a\{0}
-OK 0 - a\{2}
-OK 1 - a\{2}
-OK 2 - a\{2}
-OK 0 - a\{2}
-OK 1 - a\{2}
-OK 2 - a\{2}
-OK 0 - a\{2}
-OK 1 - a\{2}
-OK 2 - a\{2}
-OK 0 - a\{0,}
-OK 1 - a\{0,}
-OK 2 - a\{0,}
-OK 0 - a\{0,}
-OK 1 - a\{0,}
-OK 2 - a\{0,}
-OK 0 - a\{2,}
-OK 1 - a\{2,}
-OK 2 - a\{2,}
-OK 0 - a\{2,}
-OK 1 - a\{2,}
-OK 2 - a\{2,}
-OK 0 - a\{5,}
-OK 1 - a\{5,}
-OK 2 - a\{5,}
-OK 0 - a\{5,}
-OK 1 - a\{5,}
-OK 2 - a\{5,}
-OK 0 - a\{,0}
-OK 1 - a\{,0}
-OK 2 - a\{,0}
-OK 0 - a\{,5}
-OK 1 - a\{,5}
-OK 2 - a\{,5}
-OK 0 - a\{,5}
-OK 1 - a\{,5}
-OK 2 - a\{,5}
-OK 0 - ^*\{4,}$
-OK 1 - ^*\{4,}$
-OK 2 - ^*\{4,}$
-OK 0 - ^*\{4,}$
-OK 1 - ^*\{4,}$
-OK 2 - ^*\{4,}$
-OK 0 - ^*\{4,}$
-OK 1 - ^*\{4,}$
-OK 2 - ^*\{4,}$
-OK 0 - a\{}
-OK 1 - a\{}
-OK 2 - a\{}
-OK 0 - a\{}
-OK 1 - a\{}
-OK 2 - a\{}
-OK 0 - a\{-0,0}
-OK 1 - a\{-0,0}
-OK 2 - a\{-0,0}
-OK 0 - a\{-0,1}
-OK 1 - a\{-0,1}
-OK 2 - a\{-0,1}
-OK 0 - a\{-3,6}
-OK 1 - a\{-3,6}
-OK 2 - a\{-3,6}
-OK 0 - a\{-3,6}
-OK 1 - a\{-3,6}
-OK 2 - a\{-3,6}
-OK 0 - a\{-3,6}
-OK 1 - a\{-3,6}
-OK 2 - a\{-3,6}
-OK 0 - a\{-0}
-OK 1 - a\{-0}
-OK 2 - a\{-0}
-OK 0 - a\{-2}
-OK 1 - a\{-2}
-OK 2 - a\{-2}
-OK 0 - a\{-2}
-OK 1 - a\{-2}
-OK 2 - a\{-2}
-OK 0 - a\{-0,}
-OK 1 - a\{-0,}
-OK 2 - a\{-0,}
-OK 0 - a\{-0,}
-OK 1 - a\{-0,}
-OK 2 - a\{-0,}
-OK 0 - a\{-2,}
-OK 1 - a\{-2,}
-OK 2 - a\{-2,}
-OK 0 - a\{-2,}
-OK 1 - a\{-2,}
-OK 2 - a\{-2,}
-OK 0 - a\{-,0}
-OK 1 - a\{-,0}
-OK 2 - a\{-,0}
-OK 0 - a\{-,5}
-OK 1 - a\{-,5}
-OK 2 - a\{-,5}
-OK 0 - a\{-,5}
-OK 1 - a\{-,5}
-OK 2 - a\{-,5}
-OK 0 - a\{-}
-OK 1 - a\{-}
-OK 2 - a\{-}
-OK 0 - a\{-}
-OK 1 - a\{-}
-OK 2 - a\{-}
-OK 0 - \(abc\)*
-OK 1 - \(abc\)*
-OK 2 - \(abc\)*
-OK 0 - \(ab\)\+
-OK 1 - \(ab\)\+
-OK 2 - \(ab\)\+
-OK 0 - \(abaaaaa\)*cd
-OK 1 - \(abaaaaa\)*cd
-OK 2 - \(abaaaaa\)*cd
-OK 0 - \(test1\)\? \(test2\)\?
-OK 1 - \(test1\)\? \(test2\)\?
-OK 2 - \(test1\)\? \(test2\)\?
-OK 0 - \(test1\)\= \(test2\) \(test4443\)\=
-OK 1 - \(test1\)\= \(test2\) \(test4443\)\=
-OK 2 - \(test1\)\= \(test2\) \(test4443\)\=
-OK 0 - \(\(sub1\) hello \(sub 2\)\)
-OK 1 - \(\(sub1\) hello \(sub 2\)\)
-OK 2 - \(\(sub1\) hello \(sub 2\)\)
-OK 0 - \(\(\(yyxxzz\)\)\)
-OK 1 - \(\(\(yyxxzz\)\)\)
-OK 2 - \(\(\(yyxxzz\)\)\)
-OK 0 - \v((ab)+|c+)+
-OK 1 - \v((ab)+|c+)+
-OK 2 - \v((ab)+|c+)+
-OK 0 - \v((ab)|c*)+
-OK 1 - \v((ab)|c*)+
-OK 2 - \v((ab)|c*)+
-OK 0 - \v(a(c*)+b)+
-OK 1 - \v(a(c*)+b)+
-OK 2 - \v(a(c*)+b)+
-OK 0 - \v(a|b*)+
-OK 1 - \v(a|b*)+
-OK 2 - \v(a|b*)+
-OK 0 - \p*
-OK 1 - \p*
-OK 2 - \p*
-OK 0 - a\{-2,7}
-OK 1 - a\{-2,7}
-OK 2 - a\{-2,7}
-OK 0 - a\{-2,7}x
-OK 1 - a\{-2,7}x
-OK 2 - a\{-2,7}x
-OK 0 - a\{2,7}
-OK 1 - a\{2,7}
-OK 2 - a\{2,7}
-OK 0 - a\{2,7}x
-OK 1 - a\{2,7}x
-OK 2 - a\{2,7}x
-OK 0 - \vx(.{-,8})yz(.*)
-OK 1 - \vx(.{-,8})yz(.*)
-OK 2 - \vx(.{-,8})yz(.*)
-OK 0 - \vx(.*)yz(.*)
-OK 1 - \vx(.*)yz(.*)
-OK 2 - \vx(.*)yz(.*)
-OK 0 - \v(a{1,2}){-2,3}
-OK 1 - \v(a{1,2}){-2,3}
-OK 2 - \v(a{1,2}){-2,3}
-OK 0 - \v(a{-1,3})+
-OK 1 - \v(a{-1,3})+
-OK 2 - \v(a{-1,3})+
-OK 0 - ^\s\{-}\zs\( x\|x$\)
-OK 1 - ^\s\{-}\zs\( x\|x$\)
-OK 2 - ^\s\{-}\zs\( x\|x$\)
-OK 0 - ^\s\{-}\zs\(x\| x$\)
-OK 1 - ^\s\{-}\zs\(x\| x$\)
-OK 2 - ^\s\{-}\zs\(x\| x$\)
-OK 0 - ^\s\{-}\ze\(x\| x$\)
-OK 1 - ^\s\{-}\ze\(x\| x$\)
-OK 2 - ^\s\{-}\ze\(x\| x$\)
-OK 0 - ^\(\s\{-}\)\(x\| x$\)
-OK 1 - ^\(\s\{-}\)\(x\| x$\)
-OK 2 - ^\(\s\{-}\)\(x\| x$\)
-OK 0 - \d\+e\d\d
-OK 1 - \d\+e\d\d
-OK 2 - \d\+e\d\d
-OK 0 - \v[a]
-OK 1 - \v[a]
-OK 2 - \v[a]
-OK 0 - a[bcd]
-OK 1 - a[bcd]
-OK 2 - a[bcd]
-OK 0 - a[b-d]
-OK 1 - a[b-d]
-OK 2 - a[b-d]
-OK 0 - [a-d][e-f][x-x]d
-OK 1 - [a-d][e-f][x-x]d
-OK 2 - [a-d][e-f][x-x]d
-OK 0 - \v[[:alpha:]]+
-OK 1 - \v[[:alpha:]]+
-OK 2 - \v[[:alpha:]]+
-OK 0 - [[:alpha:]\+]
-OK 1 - [[:alpha:]\+]
-OK 2 - [[:alpha:]\+]
-OK 0 - [^abc]\+
-OK 1 - [^abc]\+
-OK 2 - [^abc]\+
-OK 0 - [^abc]
-OK 1 - [^abc]
-OK 2 - [^abc]
-OK 0 - [^abc]\+
-OK 1 - [^abc]\+
-OK 2 - [^abc]\+
-OK 0 - [^a-d]\+
-OK 1 - [^a-d]\+
-OK 2 - [^a-d]\+
-OK 0 - [a-f]*
-OK 1 - [a-f]*
-OK 2 - [a-f]*
-OK 0 - [a-f]*
-OK 1 - [a-f]*
-OK 2 - [a-f]*
-OK 0 - [^a-f]\+
-OK 1 - [^a-f]\+
-OK 2 - [^a-f]\+
-OK 0 - [a-c]\{-3,6}
-OK 1 - [a-c]\{-3,6}
-OK 2 - [a-c]\{-3,6}
-OK 0 - [^[:alpha:]]\+
-OK 1 - [^[:alpha:]]\+
-OK 2 - [^[:alpha:]]\+
-OK 0 - [-a]
-OK 1 - [-a]
-OK 2 - [-a]
-OK 0 - [a-]
-OK 1 - [a-]
-OK 2 - [a-]
-OK 0 - [a-f]*\c
-OK 1 - [a-f]*\c
-OK 2 - [a-f]*\c
-OK 0 - [abc][xyz]\c
-OK 1 - [abc][xyz]\c
-OK 2 - [abc][xyz]\c
-OK 0 - [-./[:alnum:]_~]\+
-OK 1 - [-./[:alnum:]_~]\+
-OK 2 - [-./[:alnum:]_~]\+
-OK 0 - [\]\^\-\\]\+
-OK 1 - [\]\^\-\\]\+
-OK 2 - [\]\^\-\\]\+
-OK 0 - [[.a.]]\+
-OK 1 - [[.a.]]\+
-OK 2 - [[.a.]]\+
-OK 0 - abc[0-9]*ddd
-OK 1 - abc[0-9]*ddd
-OK 2 - abc[0-9]*ddd
-OK 0 - abc[0-9]*ddd
-OK 1 - abc[0-9]*ddd
-OK 2 - abc[0-9]*ddd
-OK 0 - \_[0-9]\+
-OK 1 - \_[0-9]\+
-OK 2 - \_[0-9]\+
-OK 0 - [0-9\n]\+
-OK 1 - [0-9\n]\+
-OK 2 - [0-9\n]\+
-OK 0 - \_[0-9]\+
-OK 1 - \_[0-9]\+
-OK 2 - \_[0-9]\+
-OK 0 - \_f
-OK 1 - \_f
-OK 2 - \_f
-OK 0 - \_f\+
-OK 1 - \_f\+
-OK 2 - \_f\+
-OK 0 - [0-9A-Za-z-_.]\+
-OK 1 - [0-9A-Za-z-_.]\+
-OK 2 - [0-9A-Za-z-_.]\+
-OK 0 - ^a.
-OK 1 - ^a.
-OK 2 - ^a.
-OK 0 - ^a.
-OK 1 - ^a.
-OK 2 - ^a.
-OK 0 - .a$
-OK 1 - .a$
-OK 2 - .a$
-OK 0 - .a$
-OK 1 - .a$
-OK 2 - .a$
-OK 0 - \%^a.
-OK 1 - \%^a.
-OK 2 - \%^a.
-OK 0 - \%^a
-OK 1 - \%^a
-OK 2 - \%^a
-OK 0 - .a\%$
-OK 1 - .a\%$
-OK 2 - .a\%$
-OK 0 - .a\%$
-OK 1 - .a\%$
-OK 2 - .a\%$
-OK 0 - [0-7]\+
-OK 1 - [0-7]\+
-OK 2 - [0-7]\+
-OK 0 - [^0-7]\+
-OK 1 - [^0-7]\+
-OK 2 - [^0-7]\+
-OK 0 - [0-9]\+
-OK 1 - [0-9]\+
-OK 2 - [0-9]\+
-OK 0 - [^0-9]\+
-OK 1 - [^0-9]\+
-OK 2 - [^0-9]\+
-OK 0 - [0-9a-fA-F]\+
-OK 1 - [0-9a-fA-F]\+
-OK 2 - [0-9a-fA-F]\+
-OK 0 - [^0-9A-Fa-f]\+
-OK 1 - [^0-9A-Fa-f]\+
-OK 2 - [^0-9A-Fa-f]\+
-OK 0 - [a-z_A-Z0-9]\+
-OK 1 - [a-z_A-Z0-9]\+
-OK 2 - [a-z_A-Z0-9]\+
-OK 0 - [^a-z_A-Z0-9]\+
-OK 1 - [^a-z_A-Z0-9]\+
-OK 2 - [^a-z_A-Z0-9]\+
-OK 0 - [a-z_A-Z]\+
-OK 1 - [a-z_A-Z]\+
-OK 2 - [a-z_A-Z]\+
-OK 0 - [^a-z_A-Z]\+
-OK 1 - [^a-z_A-Z]\+
-OK 2 - [^a-z_A-Z]\+
-OK 0 - [a-z]\+
-OK 1 - [a-z]\+
-OK 2 - [a-z]\+
-OK 0 - [a-z]\+
-OK 1 - [a-z]\+
-OK 2 - [a-z]\+
-OK 0 - [^a-z]\+
-OK 1 - [^a-z]\+
-OK 2 - [^a-z]\+
-OK 0 - [^a-z]\+
-OK 1 - [^a-z]\+
-OK 2 - [^a-z]\+
-OK 0 - [a-zA-Z]\+
-OK 1 - [a-zA-Z]\+
-OK 2 - [a-zA-Z]\+
-OK 0 - [^a-zA-Z]\+
-OK 1 - [^a-zA-Z]\+
-OK 2 - [^a-zA-Z]\+
-OK 0 - [A-Z]\+
-OK 1 - [A-Z]\+
-OK 2 - [A-Z]\+
-OK 0 - [^A-Z]\+
-OK 1 - [^A-Z]\+
-OK 2 - [^A-Z]\+
-OK 0 - [a-z]\+\c
-OK 1 - [a-z]\+\c
-OK 2 - [a-z]\+\c
-OK 0 - [A-Z]\+\c
-OK 1 - [A-Z]\+\c
-OK 2 - [A-Z]\+\c
-OK 0 - \c[^a-z]\+
-OK 1 - \c[^a-z]\+
-OK 2 - \c[^a-z]\+
-OK 0 - \c[^A-Z]\+
-OK 1 - \c[^A-Z]\+
-OK 2 - \c[^A-Z]\+
-OK 0 - \C[^A-Z]\+
-OK 1 - \C[^A-Z]\+
-OK 2 - \C[^A-Z]\+
-OK 0 - xx \ze test
-OK 1 - xx \ze test
-OK 2 - xx \ze test
-OK 0 - abc\zeend
-OK 1 - abc\zeend
-OK 2 - abc\zeend
-OK 0 - aa\zebb\|aaxx
-OK 1 - aa\zebb\|aaxx
-OK 2 - aa\zebb\|aaxx
-OK 0 - aa\zebb\|aaxx
-OK 1 - aa\zebb\|aaxx
-OK 2 - aa\zebb\|aaxx
-OK 0 - aabb\|aa\zebb
-OK 1 - aabb\|aa\zebb
-OK 2 - aabb\|aa\zebb
-OK 0 - aa\zebb\|aaebb
-OK 1 - aa\zebb\|aaebb
-OK 2 - aa\zebb\|aaebb
-OK 0 - abc\zsdd
-OK 1 - abc\zsdd
-OK 2 - abc\zsdd
-OK 0 - aa \zsax
-OK 1 - aa \zsax
-OK 2 - aa \zsax
-OK 0 - abc \zsmatch\ze abc
-OK 1 - abc \zsmatch\ze abc
-OK 2 - abc \zsmatch\ze abc
-OK 0 - \v(a \zsif .*){2}
-OK 1 - \v(a \zsif .*){2}
-OK 2 - \v(a \zsif .*){2}
-OK 0 - \>\zs.
-OK 1 - \>\zs.
-OK 2 - \>\zs.
-OK 0 - \s\+\ze\[/\|\s\zs\s\+
-OK 1 - \s\+\ze\[/\|\s\zs\s\+
-OK 2 - \s\+\ze\[/\|\s\zs\s\+
-OK 0 - abc\@=
-OK 1 - abc\@=
-OK 2 - abc\@=
-OK 0 - abc\@=cd
-OK 1 - abc\@=cd
-OK 2 - abc\@=cd
-OK 0 - abc\@=
-OK 1 - abc\@=
-OK 2 - abc\@=
-OK 0 - abcd\@=e
-OK 1 - abcd\@=e
-OK 2 - abcd\@=e
-OK 0 - abcd\@=e
-OK 1 - abcd\@=e
-OK 2 - abcd\@=e
-OK 0 - \v(abc)@=..
-OK 1 - \v(abc)@=..
-OK 2 - \v(abc)@=..
-OK 0 - \(.*John\)\@=.*Bob
-OK 1 - \(.*John\)\@=.*Bob
-OK 2 - \(.*John\)\@=.*Bob
-OK 0 - \(John.*\)\@=.*Bob
-OK 1 - \(John.*\)\@=.*Bob
-OK 2 - \(John.*\)\@=.*Bob
-OK 0 - \<\S\+\())\)\@=
-OK 1 - \<\S\+\())\)\@=
-OK 2 - \<\S\+\())\)\@=
-OK 0 - .*John\&.*Bob
-OK 1 - .*John\&.*Bob
-OK 2 - .*John\&.*Bob
-OK 0 - .*John\&.*Bob
-OK 1 - .*John\&.*Bob
-OK 2 - .*John\&.*Bob
-OK 0 - \v(test1)@=.*yep
-OK 1 - \v(test1)@=.*yep
-OK 2 - \v(test1)@=.*yep
-OK 0 - foo\(bar\)\@!
-OK 1 - foo\(bar\)\@!
-OK 2 - foo\(bar\)\@!
-OK 0 - foo\(bar\)\@!
-OK 1 - foo\(bar\)\@!
-OK 2 - foo\(bar\)\@!
-OK 0 - if \(\(then\)\@!.\)*$
-OK 1 - if \(\(then\)\@!.\)*$
-OK 2 - if \(\(then\)\@!.\)*$
-OK 0 - if \(\(then\)\@!.\)*$
-OK 1 - if \(\(then\)\@!.\)*$
-OK 2 - if \(\(then\)\@!.\)*$
-OK 0 - \(foo\)\@!bar
-OK 1 - \(foo\)\@!bar
-OK 2 - \(foo\)\@!bar
-OK 0 - \(foo\)\@!...bar
-OK 1 - \(foo\)\@!...bar
-OK 2 - \(foo\)\@!...bar
-OK 0 - ^\%(.*bar\)\@!.*\zsfoo
-OK 1 - ^\%(.*bar\)\@!.*\zsfoo
-OK 2 - ^\%(.*bar\)\@!.*\zsfoo
-OK 0 - ^\%(.*bar\)\@!.*\zsfoo
-OK 1 - ^\%(.*bar\)\@!.*\zsfoo
-OK 2 - ^\%(.*bar\)\@!.*\zsfoo
-OK 0 - ^\%(.*bar\)\@!.*\zsfoo
-OK 1 - ^\%(.*bar\)\@!.*\zsfoo
-OK 2 - ^\%(.*bar\)\@!.*\zsfoo
-OK 0 - [ ]\@!\p\%([ ]\@!\p\)*:
-OK 1 - [ ]\@!\p\%([ ]\@!\p\)*:
-OK 2 - [ ]\@!\p\%([ ]\@!\p\)*:
-OK 0 - [ ]\@!\p\([ ]\@!\p\)*:
-OK 1 - [ ]\@!\p\([ ]\@!\p\)*:
-OK 2 - [ ]\@!\p\([ ]\@!\p\)*:
-OK 0 - m\k\+_\@=\%(_\@!\k\)\@<=\k\+e
-OK 1 - m\k\+_\@=\%(_\@!\k\)\@<=\k\+e
-OK 2 - m\k\+_\@=\%(_\@!\k\)\@<=\k\+e
-OK 0 - \%(\U\@<=S\k*\|S\l\)R
-OK 1 - \%(\U\@<=S\k*\|S\l\)R
-OK 2 - \%(\U\@<=S\k*\|S\l\)R
-OK 0 - [[:alpha:]]\{-2,6}
-OK 1 - [[:alpha:]]\{-2,6}
-OK 2 - [[:alpha:]]\{-2,6}
-OK 0 -
-OK 1 -
-OK 2 -
-OK 0 - \v(())
-OK 1 - \v(())
-OK 2 - \v(())
-OK 0 - \v%(ab(xyz)c)
-OK 1 - \v%(ab(xyz)c)
-OK 2 - \v%(ab(xyz)c)
-OK 0 - \v(test|)empty
-OK 1 - \v(test|)empty
-OK 2 - \v(test|)empty
-OK 0 - \v(a|aa)(a|aa)
-OK 1 - \v(a|aa)(a|aa)
-OK 2 - \v(a|aa)(a|aa)
-OK 0 - \%d32
-OK 1 - \%d32
-OK 2 - \%d32
-OK 0 - \%o40
-OK 1 - \%o40
-OK 2 - \%o40
-OK 0 - \%x20
-OK 1 - \%x20
-OK 2 - \%x20
-OK 0 - \%u0020
-OK 1 - \%u0020
-OK 2 - \%u0020
-OK 0 - \%U00000020
-OK 1 - \%U00000020
-OK 2 - \%U00000020
-OK 0 - \%d0
-OK 1 - \%d0
-OK 2 - \%d0
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]
-OK 1 - foo\%[bar]
-OK 2 - foo\%[bar]
-OK 0 - foo\%[bar]x
-OK 1 - foo\%[bar]x
-OK 2 - foo\%[bar]x
-OK 0 - foo\%[bar]x
-OK 1 - foo\%[bar]x
-OK 2 - foo\%[bar]x
-OK 0 - \%[bar]x
-OK 1 - \%[bar]x
-OK 2 - \%[bar]x
-OK 0 - \%[bar]x
-OK 1 - \%[bar]x
-OK 2 - \%[bar]x
-OK 0 - \%[bar]x
-OK 1 - \%[bar]x
-OK 2 - \%[bar]x
-OK 0 - b\%[[ao]r]
-OK 1 - b\%[[ao]r]
-OK 2 - b\%[[ao]r]
-OK 0 - b\%[[]]r]
-OK 1 - b\%[[]]r]
-OK 2 - b\%[[]]r]
-OK 0 - @\%[\w\-]*
-OK 1 - @\%[\w\-]*
-OK 2 - @\%[\w\-]*
-OK 0 - goo\|go
-OK 1 - goo\|go
-OK 2 - goo\|go
-OK 0 - \<goo\|\<go
-OK 1 - \<goo\|\<go
-OK 2 - \<goo\|\<go
-OK 0 - \<goo\|go
-OK 1 - \<goo\|go
-OK 2 - \<goo\|go
-OK 0 - \(\i\+\) \1
-OK 1 - \(\i\+\) \1
-OK 2 - \(\i\+\) \1
-OK 0 - \(\i\+\) \1
-OK 1 - \(\i\+\) \1
-OK 2 - \(\i\+\) \1
-OK 0 - \(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9
-OK 1 - \(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9
-OK 2 - \(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9
-OK 0 - \(\d*\)a \1b
-OK 1 - \(\d*\)a \1b
-OK 2 - \(\d*\)a \1b
-OK 0 - ^.\(.\).\_..\1.
-OK 1 - ^.\(.\).\_..\1.
-OK 2 - ^.\(.\).\_..\1.
-OK 0 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 1 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 2 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 0 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 1 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 2 - ^.*\.\(.*\)/.\+\(\1\)\@<!$
-OK 0 - ^.*\.\(.*\)/.\+\(\1\)\@<=$
-OK 1 - ^.*\.\(.*\)/.\+\(\1\)\@<=$
-OK 2 - ^.*\.\(.*\)/.\+\(\1\)\@<=$
-OK 0 - \\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}
-OK 1 - \\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}
-OK 2 - \\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}
-OK 0 - ^\(a*\)\1$
-OK 1 - ^\(a*\)\1$
-OK 2 - ^\(a*\)\1$
-OK 0 - ^\(a\{-2,}\)\1\+$
-OK 1 - ^\(a\{-2,}\)\1\+$
-OK 2 - ^\(a\{-2,}\)\1\+$
-OK 0 - <\@<=span.
-OK 1 - <\@<=span.
-OK 2 - <\@<=span.
-OK 0 - <\@1<=span.
-OK 1 - <\@1<=span.
-OK 2 - <\@1<=span.
-OK 0 - <\@2<=span.
-OK 1 - <\@2<=span.
-OK 2 - <\@2<=span.
-OK 0 - \(<<\)\@<=span.
-OK 1 - \(<<\)\@<=span.
-OK 2 - \(<<\)\@<=span.
-OK 0 - \(<<\)\@1<=span.
-OK 1 - \(<<\)\@1<=span.
-OK 2 - \(<<\)\@1<=span.
-OK 0 - \(<<\)\@2<=span.
-OK 1 - \(<<\)\@2<=span.
-OK 2 - \(<<\)\@2<=span.
-OK 0 - \(foo\)\@<!bar.
-OK 1 - \(foo\)\@<!bar.
-OK 2 - \(foo\)\@<!bar.
-OK 0 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 1 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 2 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 0 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 1 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 2 - \v\C%(<Last Changed:\s+)@<=.*$
-OK 0 - \(foo\)\@<=\>
-OK 1 - \(foo\)\@<=\>
-OK 2 - \(foo\)\@<=\>
-OK 0 - \(foo\)\@<=\>
-OK 1 - \(foo\)\@<=\>
-OK 2 - \(foo\)\@<=\>
-OK 0 - \(foo\)\@<=.*
-OK 1 - \(foo\)\@<=.*
-OK 2 - \(foo\)\@<=.*
-OK 0 - \(r\@<=\|\w\@<!\)\/
-OK 1 - \(r\@<=\|\w\@<!\)\/
-OK 2 - \(r\@<=\|\w\@<!\)\/
-OK 0 - ^[a-z]\+\ze \&\(asdf\)\@<!
-OK 1 - ^[a-z]\+\ze \&\(asdf\)\@<!
-OK 2 - ^[a-z]\+\ze \&\(asdf\)\@<!
-OK 0 - \(a*\)\@>a
-OK 1 - \(a*\)\@>a
-OK 2 - \(a*\)\@>a
-OK 0 - \(a*\)\@>b
-OK 1 - \(a*\)\@>b
-OK 2 - \(a*\)\@>b
-OK 0 - ^\(.\{-}b\)\@>.
-OK 1 - ^\(.\{-}b\)\@>.
-OK 2 - ^\(.\{-}b\)\@>.
-OK 0 - \(.\{-}\)\(\)\@>$
-OK 1 - \(.\{-}\)\(\)\@>$
-OK 2 - \(.\{-}\)\(\)\@>$
-OK 0 - \(a*\)\@>a\|a\+
-OK 2 - \(a*\)\@>a\|a\+
-OK 0 - \_[^8-9]\+
-OK 1 - \_[^8-9]\+
-OK 2 - \_[^8-9]\+
-OK 0 - \_[^a]\+
-OK 1 - \_[^a]\+
-OK 2 - \_[^a]\+
-OK 0 - [0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}
-OK 1 - [0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}
-OK 2 - [0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}
-OK 0 - ^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=
-OK 1 - ^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=
-OK 2 - ^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=
-OK 0 - \%>70vGesamt
-OK 1 - \%>70vGesamt
-OK 2 - \%>70vGesamt
-multi-line tests
-OK 0 - ^.\(.\).\_..\1.
-OK 1 - ^.\(.\).\_..\1.
-OK 2 - ^.\(.\).\_..\1.
-OK 0 - \v.*\/(.*)\n.*\/\1$
-OK 1 - \v.*\/(.*)\n.*\/\1$
-OK 2 - \v.*\/(.*)\n.*\/\1$
-OK 0 - \S.*\nx
-OK 1 - \S.*\nx
-OK 2 - \S.*\nx
-OK 0 - \<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>
-OK 1 - \<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>
-OK 2 - \<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>
-OK 0 - a\n^b$\n^c
-OK 1 - a\n^b$\n^c
-OK 2 - a\n^b$\n^c
-OK 0 - \(^.\+\n\)\1
-OK 1 - \(^.\+\n\)\1
-OK 2 - \(^.\+\n\)\1
-
-<T="5">Ta 5</Title>
-<T="7">Ac 7</Title>
-
-xxstart3
-
-thexE thE thExethe
-AndAxAnd AndAxAnd
-oooxOfOr fOrOxooo
-oooxOfOr fOrOxooo
-
-asdfhereasdf
-asdfagainasdf
-
--0-
-ffo
-bob
-__ooooo
-koooo__
-moooooo
- f__
-ab!babababababfoo
-ba!ab##abab?bafoo
-**!*****_
- ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx
--1-
-ffo
-bob
-__ooooo
-koooo__
-moooooo
- f__
-ab!babababababfoo
-ba!ab##abab?bafoo
-**!*****_
- ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx
--2-
-ffo
-bob
-__ooooo
-koooo__
-moooooo
- f__
-ab!babababababfoo
-ba!ab##abab?bafoo
-**!*****_
- ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx
-Test
-Test END
-EN
-E
-E888 detected for \ze*
-E888 detected for \zs*
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index f1274b01c8..5668f45dea 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -2,6 +2,7 @@
" This makes testing go faster, since Vim doesn't need to restart.
source test_assign.vim
+source test_backup.vim
source test_behave.vim
source test_cd.vim
source test_changedtick.vim
diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim
new file mode 100644
index 0000000000..fa10430613
--- /dev/null
+++ b/src/nvim/testdir/test_backup.vim
@@ -0,0 +1,58 @@
+" Tests for the backup function
+
+func Test_backup()
+ set backup backupdir=.
+ new
+ call setline(1, ['line1', 'line2'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ let l = readfile('Xbackup.txt~')
+ call assert_equal(['line1', 'line2'], l)
+ bw!
+ set backup&vim backupdir&vim
+ call delete('Xbackup.txt')
+ call delete('Xbackup.txt~')
+endfunc
+
+func Test_backup2()
+ set backup backupdir=.//
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f=expand('%')
+ call assert_match('src%nvim%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim
+endfunc
+
+func Test_backup2_backupcopy()
+ set backup backupdir=.// backupcopy=yes
+ new
+ call setline(1, ['line1', 'line2', 'line3'])
+ :f Xbackup.txt
+ :w! Xbackup.txt
+ " backup file is only created after
+ " writing a second time (before overwriting)
+ :w! Xbackup.txt
+ sp *Xbackup.txt~
+ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
+ let f=expand('%')
+ call assert_match('src%nvim%testdir%Xbackup.txt\~', f)
+ bw!
+ bw!
+ call delete('Xbackup.txt')
+ call delete(f)
+ set backup&vim backupdir&vim backupcopy&vim
+endfunc
diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim
index 0e8c7d1dc1..176d49d28e 100644
--- a/src/nvim/testdir/test_bufwintabinfo.vim
+++ b/src/nvim/testdir/test_bufwintabinfo.vim
@@ -139,3 +139,13 @@ function Test_get_win_options()
set foldlevel=0
endif
endfunc
+
+func Test_getbufinfo_lines()
+ new Xfoo
+ call setline(1, ['a', 'bc', 'd'])
+ let bn = bufnr('%')
+ hide
+ call assert_equal(3, getbufinfo(bn)[0]["linecount"])
+ edit Xfoo
+ bw!
+endfunc
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 0a3e6ae625..9c3c33a943 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -78,26 +78,45 @@ func Test_map_completion()
call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <silent> <special>', getreg(':'))
+ map <Middle>x middle
+
map ,f commaf
map ,g commaf
+ map <Left> left
+ map <A-Left>x shiftleft
call feedkeys(":map ,\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map ,f', getreg(':'))
call feedkeys(":map ,\<Tab>\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map ,g', getreg(':'))
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ call feedkeys(":map <A-Left>\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map <A-Left>\<Tab>", getreg(':'))
unmap ,f
unmap ,g
+ unmap <Left>
+ unmap <A-Left>x
set cpo-=< cpo-=B cpo-=k
map <Left> left
call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <Left>', getreg(':'))
+ call feedkeys(":map <M\<Tab>\<Home>\"\<CR>", 'xt')
+ " call assert_equal("\"map <M\<Tab>", getreg(':'))
unmap <Left>
" set cpo+=<
map <Left> left
+ exe "set t_k6=\<Esc>[17~"
+ call feedkeys(":map \<Esc>[17~x f6x\<CR>", 'xt')
call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <Left>', getreg(':'))
+ if !has('gui_running')
+ call feedkeys(":map \<Esc>[17~\<Tab>\<Home>\"\<CR>", 'xt')
+ " call assert_equal("\"map <F6>x", getreg(':'))
+ endif
unmap <Left>
+ call feedkeys(":unmap \<Esc>[17~x\<CR>", 'xt')
set cpo-=<
set cpo+=B
@@ -113,6 +132,9 @@ func Test_map_completion()
call assert_equal('"map <Left>', getreg(':'))
unmap <Left>
" set cpo-=k
+
+ unmap <Middle>x
+ set cpo&vim
endfunc
func Test_match_completion()
@@ -159,6 +181,7 @@ func Test_expr_completion()
endif
for cmd in [
\ 'let a = ',
+ \ 'const a = ',
\ 'if',
\ 'elseif',
\ 'while',
@@ -301,7 +324,7 @@ func Test_getcompletion()
call assert_equal([], l)
let l = getcompletion('.', 'shellcmd')
- call assert_equal(['./', '../'], l[0:1])
+ call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"'))
call assert_equal(-1, match(l[2:], '^\.\.\?/$'))
let root = has('win32') ? 'C:\\' : '/'
let l = getcompletion(root, 'shellcmd')
@@ -375,6 +398,29 @@ func Test_getcompletion()
call assert_fails('call getcompletion("", "burp")', 'E475:')
endfunc
+func Test_shellcmd_completion()
+ let save_path = $PATH
+
+ call mkdir('Xpathdir/Xpathsubdir', 'p')
+ call writefile([''], 'Xpathdir/Xfile.exe')
+ call setfperm('Xpathdir/Xfile.exe', 'rwx------')
+
+ " Set PATH to example directory without trailing slash.
+ let $PATH = getcwd() . '/Xpathdir'
+
+ " Test for the ":!<TAB>" case. Previously, this would include subdirs of
+ " dirs in the PATH, even though they won't be executed. We check that only
+ " subdirs of the PWD and executables from the PATH are included in the
+ " suggestions.
+ let actual = getcompletion('X', 'shellcmd')
+ let expected = map(filter(glob('*', 0, 1), 'isdirectory(v:val) && v:val[0] == "X"'), 'v:val . "/"')
+ call insert(expected, 'Xfile.exe')
+ call assert_equal(expected, actual)
+
+ call delete('Xpathdir', 'rf')
+ let $PATH = save_path
+endfunc
+
func Test_expand_star_star()
call mkdir('a/b', 'p')
call writefile(['asdfasdf'], 'a/b/fileXname')
@@ -492,8 +538,9 @@ func Test_cmdline_complete_user_names()
let names = system('net user')
if names =~ 'Administrator'
" Trying completion of :e ~A should complete to Administrator.
+ " There could be other names starting with "A" before Administrator.
call feedkeys(':e ~A' . "\<c-a>\<c-B>\"\<cr>", 'tx')
- call assert_match('^"e \~Administrator', @:)
+ call assert_match('^"e \~.*Administrator', @:)
endif
endif
endfunc
@@ -572,6 +619,8 @@ func Check_cmdline(cmdtype)
return ''
endfunc
+set cpo&
+
func Test_getcmdtype()
call feedkeys(":MyCmd a\<C-R>=Check_cmdline(':')\<CR>\<Esc>", "xt")
@@ -612,6 +661,37 @@ func Test_getcmdwintype()
call assert_equal('', getcmdwintype())
endfunc
+func Test_getcmdwin_autocmd()
+ let s:seq = []
+ augroup CmdWin
+ au WinEnter * call add(s:seq, 'WinEnter ' .. win_getid())
+ au WinLeave * call add(s:seq, 'WinLeave ' .. win_getid())
+ au BufEnter * call add(s:seq, 'BufEnter ' .. bufnr())
+ au BufLeave * call add(s:seq, 'BufLeave ' .. bufnr())
+ au CmdWinEnter * call add(s:seq, 'CmdWinEnter ' .. win_getid())
+ au CmdWinLeave * call add(s:seq, 'CmdWinLeave ' .. win_getid())
+
+ let org_winid = win_getid()
+ let org_bufnr = bufnr()
+ call feedkeys("q::let a = getcmdwintype()\<CR>:let s:cmd_winid = win_getid()\<CR>:let s:cmd_bufnr = bufnr()\<CR>:q\<CR>", 'x!')
+ call assert_equal(':', a)
+ call assert_equal([
+ \ 'WinLeave ' .. org_winid,
+ \ 'WinEnter ' .. s:cmd_winid,
+ \ 'BufLeave ' .. org_bufnr,
+ \ 'BufEnter ' .. s:cmd_bufnr,
+ \ 'CmdWinEnter ' .. s:cmd_winid,
+ \ 'CmdWinLeave ' .. s:cmd_winid,
+ \ 'BufLeave ' .. s:cmd_bufnr,
+ \ 'WinLeave ' .. s:cmd_winid,
+ \ 'WinEnter ' .. org_winid,
+ \ 'BufEnter ' .. org_bufnr,
+ \ ], s:seq)
+
+ au!
+ augroup END
+endfunc
+
func Test_verbosefile()
set verbosefile=Xlog
echomsg 'foo'
@@ -671,4 +751,7 @@ func Test_cmdline_overstrike()
let &encoding = encoding_save
endfunc
-set cpo&
+func Test_cmdwin_feedkeys()
+ " This should not generate E488
+ call feedkeys("q:\<CR>", 'x')
+endfunc
diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim
index 06062c5e58..eaf200e9bb 100644
--- a/src/nvim/testdir/test_const.vim
+++ b/src/nvim/testdir/test_const.vim
@@ -176,6 +176,26 @@ func Test_cannot_modify_existing_variable()
call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:')
endfunc
+func Test_const_with_condition()
+ const x = 0
+ if 0 | const x = 1 | endif
+ call assert_equal(0, x)
+endfunc
+
+func Test_lockvar()
+ let x = 'hello'
+ lockvar x
+ call assert_fails('let x = "there"', 'E741')
+ if 0 | unlockvar x | endif
+ call assert_fails('let x = "there"', 'E741')
+ unlockvar x
+ let x = 'there'
+
+ if 0 | lockvar x | endif
+ let x = 'again'
+endfunc
+
+
func Test_const_with_index_access()
let l = [1, 2, 3]
call assert_fails('const l[0] = 4', 'E996:')
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index 037918fa31..e8e561dfd8 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -66,6 +66,7 @@ func Test_curswant_with_cursorline()
endfunc
func Test_screenpos()
+ throw 'skipped: TODO: '
rightbelow new
rightbelow 20vsplit
call setline(1, ["\tsome text", "long wrapping line here", "next line"])
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 57b19aa817..21e0271bda 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -773,3 +773,28 @@ func Test_diff_of_diff()
call StopVimInTerminal(buf)
call delete('Xtest_diff_diff')
endfunc
+
+func CloseoffSetup()
+ enew
+ call setline(1, ['one', 'two', 'three'])
+ diffthis
+ new
+ call setline(1, ['one', 'tow', 'three'])
+ diffthis
+ call assert_equal(1, &diff)
+ only!
+endfunc
+
+func Test_diff_closeoff()
+ " "closeoff" included by default: last diff win gets 'diff' reset'
+ call CloseoffSetup()
+ call assert_equal(0, &diff)
+ enew!
+
+ " "closeoff" excluded: last diff win keeps 'diff' set'
+ set diffopt-=closeoff
+ call CloseoffSetup()
+ call assert_equal(1, &diff)
+ diffoff!
+ enew!
+endfunc
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
index 66c13ded82..1c2f5a05ff 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/src/nvim/testdir/test_display.vim
@@ -71,6 +71,7 @@ func! Test_display_foldtext_mbyte()
endfunc
func Test_display_listchars_precedes()
+ set fillchars+=vert:\|
call NewWindow(10, 10)
" Need a physical line that wraps over the complete
" window size
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 4053746c82..cc0037b4cf 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -223,7 +223,7 @@ let s:filename_checks = {
\ 'jam': ['file.jpl', 'file.jpr'],
\ 'java': ['file.java', 'file.jav'],
\ 'javacc': ['file.jj', 'file.jjt'],
- \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs'],
+ \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'],
\ 'javascriptreact': ['file.jsx'],
\ 'jess': ['file.clp'],
\ 'jgraph': ['file.jgr'],
@@ -364,6 +364,7 @@ let s:filename_checks = {
\ 'rcs': ['file,v'],
\ 'readline': ['.inputrc', 'inputrc'],
\ 'remind': ['.reminders', 'file.remind', 'file.rem'],
+ \ 'rego': ['file.rego'],
\ 'resolv': ['resolv.conf'],
\ 'reva': ['file.frt'],
\ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'],
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
index 324f3f8cf2..56ed543d4b 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/src/nvim/testdir/test_fold.vim
@@ -769,3 +769,28 @@ func Test_fold_delete_with_marker_and_whichwrap()
set fdm& ww&
bwipe!
endfunc
+
+func Test_fold_delete_first_line()
+ new
+ call setline(1, [
+ \ '" x {{{1',
+ \ '" a',
+ \ '" aa',
+ \ '" x {{{1',
+ \ '" b',
+ \ '" bb',
+ \ '" x {{{1',
+ \ '" c',
+ \ '" cc',
+ \ ])
+ set foldmethod=marker
+ 1
+ normal dj
+ call assert_equal([
+ \ '" x {{{1',
+ \ '" c',
+ \ '" cc',
+ \ ], getline(1,'$'))
+ bwipe!
+ set foldmethod&
+endfunc
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index a36c51f56f..7822507f86 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1132,6 +1132,13 @@ func Test_reg_executing_and_recording()
" :normal command saves and restores reg_executing
let s:reg_stat = ''
+ let @q = ":call TestFunc()\<CR>:call s:save_reg_stat()\<CR>"
+ func TestFunc() abort
+ normal! ia
+ endfunc
+ call feedkeys("@q", 'xt')
+ call assert_equal(':q', s:reg_stat)
+ delfunc TestFunc
" getchar() command saves and restores reg_executing
map W :call TestFunc()<CR>
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index accd21e9a3..d301874891 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -99,3 +99,28 @@ func Test_gf()
call delete('Xtest1')
call delete('Xtestgf')
endfunc
+
+func Test_gf_visual()
+ call writefile([], "Xtest_gf_visual")
+ new
+ call setline(1, 'XXXtest_gf_visualXXX')
+ set hidden
+
+ " Visually select Xtest_gf_visual and use gf to go to that file
+ norm! ttvtXgf
+ call assert_equal('Xtest_gf_visual', bufname('%'))
+
+ bwipe!
+ call delete('Xtest_gf_visual')
+ set hidden&
+endfunc
+
+func Test_gf_error()
+ new
+ call assert_fails('normal gf', 'E446:')
+ call assert_fails('normal gF', 'E446:')
+ call setline(1, '/doesnotexist')
+ call assert_fails('normal gf', 'E447:')
+ call assert_fails('normal gF', 'E447:')
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim
index 834397126f..d41675be0c 100644
--- a/src/nvim/testdir/test_gn.vim
+++ b/src/nvim/testdir/test_gn.vim
@@ -136,8 +136,9 @@ func Test_gn_command()
call assert_equal(['ABCDEFGHi'], getline(1,'$'))
call setline('.', ['abcdefghi'])
let @/ = 'b'
+ " this gn wraps around the end of the file
exe "norm! 0fhvhhgngU"
- call assert_equal(['abcdefghi'], getline(1,'$'))
+ call assert_equal(['aBCDEFGHi'], getline(1,'$'))
sil! %d _
call setline('.', ['abcdefghi'])
let @/ = 'f'
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index 7f52481ba8..52ec281d82 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -285,3 +285,21 @@ func Test_compl_feedkeys()
bwipe!
set completeopt&
endfunc
+
+func Test_compl_in_cmdwin()
+ set wildmenu wildchar=<Tab>
+ com! -nargs=1 -complete=command GetInput let input = <q-args>
+ com! -buffer TestCommand echo 'TestCommand'
+
+ let input = ''
+ call feedkeys("q:iGetInput T\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('TestCommand', input)
+
+ let input = ''
+ call feedkeys("q::GetInput T\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('T', input)
+
+ delcom TestCommand
+ delcom GetInput
+ set wildmenu& wildchar&
+endfunc
diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim
index 1c97414164..ac6ef8f29f 100644
--- a/src/nvim/testdir/test_join.vim
+++ b/src/nvim/testdir/test_join.vim
@@ -9,6 +9,27 @@ func Test_join_with_count()
call setline(1, ['one', 'two', 'three', 'four'])
normal 10J
call assert_equal('one two three four', getline(1))
+
+ call setline(1, ['one', '', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', 'two'])
+ normal J
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', ' ', ' ', 'two'])
+ normal JJ
+ call assert_equal('one', getline(1))
+
+ call setline(1, ['one', '', '', 'two'])
+ normal 2J
+ call assert_equal('one', getline(1))
+
quit!
endfunc
@@ -33,3 +54,388 @@ func Test_join_marks()
call assert_equal([0, 4, 67, 0], getpos("']"))
enew!
endfunc
+
+" Test for joining lines and marks in them
+" in compatible and nocompatible modes
+" and with 'joinspaces' set or not
+" and with 'cpoptions' flag 'j' set or not
+func Test_join_spaces_marks()
+ new
+ " Text used for the test
+ insert
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf.
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+asdfasdf
+asdf
+zx cvn.
+as dfg?
+hjkl iop!
+ert
+zx cvn.
+as dfg?
+hjkl iop!
+ert
+.
+ let text = getline(1, '$')
+ normal gg
+
+ set nojoinspaces
+ set cpoptions-=j
+ normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ " set cpoptions+=j
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ " Expected output
+ let expected =<< trim [DATA]
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ zx cvn. as dfg? hjkl iop! ert ernop
+ zx cvn. as dfg? hjkl iop! ert ernop
+ [DATA]
+
+ call assert_equal(expected, getline(1, '$'))
+ throw 'skipped: Nvim does not support "set compatible" or "set cpoptions+=j"'
+
+ enew!
+ call append(0, text)
+ normal gg
+
+ set cpoptions-=j
+ set joinspaces
+ normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ set cpoptions+=j
+ normal j05lmx
+ normal 2j06lmy
+ normal 2k4Jy3l$p
+ normal `xyl$p
+ normal `yy2l$p
+
+ " Expected output
+ let expected =<< trim [DATA]
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ zx cvn. as dfg? hjkl iop! ert enop
+ zx cvn. as dfg? hjkl iop! ert ernop
+
+ [DATA]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ enew!
+ call append(0, text)
+ normal gg
+
+ set cpoptions-=j
+ set nojoinspaces
+ set compatible
+
+ normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ
+ normal j4Jy3l$pjdG
+
+ " Expected output
+ let expected =<< trim [DATA]
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf. asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ asdfasdf asdf
+ zx cvn. as dfg? hjkl iop! ert a
+ [DATA]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ set nocompatible
+ set cpoptions&vim
+ set joinspaces&vim
+ close!
+endfunc
+
+" Test for joining lines with comments
+func Test_join_lines_with_comments()
+ new
+
+ " Text used by the test
+ insert
+{
+
+/*
+* Make sure the previous comment leader is not removed.
+*/
+
+/*
+* Make sure the previous comment leader is not removed.
+*/
+
+// Should the next comment leader be left alone?
+// Yes.
+
+// Should the next comment leader be left alone?
+// Yes.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+if (condition) // Remove the next comment leader!
+// OK, I will.
+action();
+
+if (condition) // Remove the next comment leader!
+// OK, I will.
+action();
+}
+.
+
+ call cursor(2, 1)
+ set comments=s1:/*,mb:*,ex:*/,://
+ set nojoinspaces fo=j
+ set backspace=eol,start
+
+ .,+3join
+ exe "normal j4J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal jj3J\<CR>"
+
+ " Expected output
+ let expected =<< trim [CODE]
+ {
+ /* Make sure the previous comment leader is not removed. */
+ /* Make sure the previous comment leader is not removed. */
+ // Should the next comment leader be left alone? Yes.
+ // Should the next comment leader be left alone? Yes.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ }
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+
+ set comments&vim
+ set joinspaces&vim
+ set fo&vim
+ set backspace&vim
+ close!
+endfunc
+
+" Test for joining lines with different comment leaders
+func Test_join_comments_2()
+ new
+
+ insert
+{
+
+/*
+ * Make sure the previous comment leader is not removed.
+ */
+
+/*
+ * Make sure the previous comment leader is not removed.
+ */
+
+/* List:
+ * - item1
+ * foo bar baz
+ * foo bar baz
+ * - item2
+ * foo bar baz
+ * foo bar baz
+ */
+
+/* List:
+ * - item1
+ * foo bar baz
+ * foo bar baz
+ * - item2
+ * foo bar baz
+ * foo bar baz
+ */
+
+// Should the next comment leader be left alone?
+// Yes.
+
+// Should the next comment leader be left alone?
+// Yes.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+/* Here the comment leader should be left intact. */
+// And so should this one.
+
+if (condition) // Remove the next comment leader!
+ // OK, I will.
+ action();
+
+if (condition) // Remove the next comment leader!
+ // OK, I will.
+ action();
+
+int i = 7 /* foo *// 3
+ // comment
+ ;
+
+int i = 7 /* foo *// 3
+ // comment
+ ;
+
+># Note that the last character of the ending comment leader (left angle
+ # bracket) is a comment leader itself. Make sure that this comment leader is
+ # not removed from the next line #<
+< On this line a new comment is opened which spans 2 lines. This comment should
+< retain its comment leader.
+
+># Note that the last character of the ending comment leader (left angle
+ # bracket) is a comment leader itself. Make sure that this comment leader is
+ # not removed from the next line #<
+< On this line a new comment is opened which spans 2 lines. This comment should
+< retain its comment leader.
+
+}
+.
+
+ call cursor(2, 1)
+ set comments=sO:*\ -,mO:*\ \ ,exO:*/
+ set comments+=s1:/*,mb:*,ex:*/,://
+ set comments+=s1:>#,mb:#,ex:#<,:<
+ set cpoptions-=j joinspaces fo=j
+ set backspace=eol,start
+
+ .,+3join
+ exe "normal j4J\<CR>"
+ .,+8join
+ exe "normal j9J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal j3J\<CR>"
+ .,+2join
+ exe "normal jj3J\<CR>j"
+ .,+2join
+ exe "normal jj3J\<CR>j"
+ .,+5join
+ exe "normal j6J\<CR>"
+ exe "normal oSome code!\<CR>// Make sure backspacing does not remove this comment leader.\<Esc>0i\<C-H>\<Esc>"
+
+ " Expected output
+ let expected =<< trim [CODE]
+ {
+ /* Make sure the previous comment leader is not removed. */
+ /* Make sure the previous comment leader is not removed. */
+ /* List: item1 foo bar baz foo bar baz item2 foo bar baz foo bar baz */
+ /* List: item1 foo bar baz foo bar baz item2 foo bar baz foo bar baz */
+ // Should the next comment leader be left alone? Yes.
+ // Should the next comment leader be left alone? Yes.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ /* Here the comment leader should be left intact. */ // And so should this one.
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ if (condition) // Remove the next comment leader! OK, I will.
+ action();
+ int i = 7 /* foo *// 3 // comment
+ ;
+ int i = 7 /* foo *// 3 // comment
+ ;
+ ># Note that the last character of the ending comment leader (left angle bracket) is a comment leader itself. Make sure that this comment leader is not removed from the next line #< < On this line a new comment is opened which spans 2 lines. This comment should retain its comment leader.
+ ># Note that the last character of the ending comment leader (left angle bracket) is a comment leader itself. Make sure that this comment leader is not removed from the next line #< < On this line a new comment is opened which spans 2 lines. This comment should retain its comment leader.
+
+ Some code!// Make sure backspacing does not remove this comment leader.
+ }
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ close!
+endfunc
+
+func Test_join_lines()
+ new
+ call setline(1, ['a', 'b', '', 'c', 'd'])
+ %join
+ call assert_equal('a b c d', getline(1))
+ call setline(1, ['a', 'b', '', 'c', 'd'])
+ normal 5J
+ call assert_equal('a b c d', getline(1))
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim
index 1fce3d6937..0b9331ee38 100644
--- a/src/nvim/testdir/test_let.vim
+++ b/src/nvim/testdir/test_let.vim
@@ -24,6 +24,10 @@ func Test_let()
let out = execute('let a {0 == 1 ? "a" : "b"}')
let s = "\na #1\nb #2"
call assert_equal(s, out)
+
+ let x = 0
+ if 0 | let x = 1 | endif
+ call assert_equal(0, x)
endfunc
func s:set_arg1(a) abort
@@ -141,6 +145,11 @@ func Test_let_varg_fail()
call s:set_varg8([0])
endfunction
+func Test_let_utf8_environment()
+ let $a = 'ĀĒĪŌŪã‚ã„ã†ãˆãŠ'
+ call assert_equal('ĀĒĪŌŪã‚ã„ã†ãˆãŠ', $a)
+endfunc
+
func Test_let_heredoc_fails()
call assert_fails('let v =<< marker', 'E991:')
@@ -284,4 +293,12 @@ E
END
endif
call assert_equal([], check)
+
+ " unpack assignment
+ let [a, b, c] =<< END
+ x
+ \y
+ z
+END
+ call assert_equal([' x', ' \y', ' z'], [a, b, c])
endfunc
diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim
index 1e196e07f0..9bdada616c 100644
--- a/src/nvim/testdir/test_modeline.vim
+++ b/src/nvim/testdir/test_modeline.vim
@@ -5,12 +5,30 @@ func Test_modeline_invalid()
call writefile(['vi:0', 'nothing'], 'Xmodeline')
let modeline = &modeline
set modeline
- call assert_fails('set Xmodeline', 'E518:')
+ call assert_fails('split Xmodeline', 'E518:')
+
+ " Missing end colon (ignored).
+ call writefile(['// vim: set ts=2'], 'Xmodeline')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ " Missing colon at beginning (ignored).
+ call writefile(['// vim set ts=2:'], 'Xmodeline')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ " Missing space after vim (ignored).
+ call writefile(['// vim:ts=2:'], 'Xmodeline')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
let &modeline = modeline
bwipe!
call delete('Xmodeline')
- endfunc
+endfunc
func Test_modeline_filetype()
call writefile(['vim: set ft=c :', 'nothing'], 'Xmodeline_filetype')
@@ -60,8 +78,99 @@ func Test_modeline_keymap()
set keymap= iminsert=0 imsearch=-1
endfunc
+func Test_modeline_version()
+ let modeline = &modeline
+ set modeline
+
+ " Test with vim:{vers}: (version {vers} or later).
+ call writefile(['// vim' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bw!
+
+ " Test with vim>{vers}: (version after {vers}).
+ call writefile(['// vim>' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim>' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim>' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ " Test with vim<{vers}: (version before {vers}).
+ call writefile(['// vim<' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim<' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim<' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ " Test with vim={vers}: (version {vers} only).
+ call writefile(['// vim=' .. v:version .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(2, &ts)
+ bwipe!
+
+ call writefile(['// vim=' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ call writefile(['// vim=' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version')
+ edit Xmodeline_version
+ call assert_equal(8, &ts)
+ bwipe!
+
+ let &modeline = modeline
+ call delete('Xmodeline_version')
+endfunc
+
+func Test_modeline_colon()
+ let modeline = &modeline
+ set modeline
+
+ call writefile(['// vim: set showbreak=\: ts=2: sw=2'], 'Xmodeline_colon')
+ edit Xmodeline_colon
+
+ " backlash colon should become colon.
+ call assert_equal(':', &showbreak)
+
+ " 'ts' should be set.
+ " 'sw' should be ignored because it is after the end colon.
+ call assert_equal(2, &ts)
+ call assert_equal(8, &sw)
+
+ let &modeline = modeline
+ call delete('Xmodeline_colon')
+endfunc
+
func s:modeline_fails(what, text, error)
- if !exists('+' . a:what)
+ if !exists('+' .. a:what)
return
endif
let fname = "Xmodeline_fails_" . a:what
@@ -119,7 +228,7 @@ func Test_modeline_fails_always()
call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:')
call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:')
call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:')
- call s:modeline_fails('modelineexpr', 'modelineexpr=Something()', 'E520:')
+ call s:modeline_fails('modelineexpr', 'modelineexpr', 'E520:')
call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:')
call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:')
call s:modeline_fails('perldll', 'perldll=Something()', 'E520:')
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index eab638d19a..ad6d325510 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -1367,8 +1367,9 @@ func Test_normal23_K()
return
endif
- if has('mac')
- " In MacOS, the option for specifying a pager is different
+ let not_gnu_man = has('mac') || has('bsd')
+ if not_gnu_man
+ " In MacOS and BSD, the option for specifying a pager is different
set keywordprg=man\ -P\ cat
else
set keywordprg=man\ --pager=cat
@@ -1376,7 +1377,7 @@ func Test_normal23_K()
" Test for using man
2
let a = execute('unsilent norm! K')
- if has('mac')
+ if not_gnu_man
call assert_match("man -P cat 'man'", a)
else
call assert_match("man --pager=cat 'man'", a)
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index f4f5cbca61..6fcc372591 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -476,13 +476,19 @@ func Test_shortmess_F2()
call assert_match('file2', execute('bn', ''))
set shortmess+=F
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
set hidden
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
set nohidden
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
" Accommodate Nvim default.
set shortmess-=F
call assert_match('file1', execute('bn', ''))
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 8083672808..9db6112eeb 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -918,6 +918,20 @@ func Test_popup_complete_info_02()
bwipe!
endfunc
+func Test_popup_complete_info_no_pum()
+ new
+ call assert_false( pumvisible() )
+ let no_pum_info = complete_info()
+ let d = {
+ \ 'mode': '',
+ \ 'pum_visible': 0,
+ \ 'items': [],
+ \ 'selected': -1,
+ \ }
+ call assert_equal( d, complete_info() )
+ bwipe!
+endfunc
+
func Test_CompleteChanged()
new
call setline(1, ['foo', 'bar', 'foobar', ''])
@@ -952,4 +966,32 @@ func Test_CompleteChanged()
bw!
endfunc
+function! GetPumPosition()
+ call assert_true( pumvisible() )
+ let g:pum_pos = pum_getpos()
+ return ''
+endfunction
+
+func Test_pum_getpos()
+ new
+ inoremap <buffer><F5> <C-R>=GetPumPosition()<CR>
+ setlocal completefunc=UserDefinedComplete
+
+ let d = {
+ \ 'height': 5,
+ \ 'width': 15,
+ \ 'row': 1,
+ \ 'col': 0,
+ \ 'size': 5,
+ \ 'scrollbar': v:false,
+ \ }
+ call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx')
+ call assert_equal(d, g:pum_pos)
+
+ call assert_false( pumvisible() )
+ call assert_equal( {}, pum_getpos() )
+ bw!
+ unlet g:pum_pos
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 8949b3d968..d7b387c2c9 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -1320,6 +1320,28 @@ func SetXlistTests(cchar, bnum)
let l = g:Xgetlist()
call g:Xsetlist(l)
call assert_equal(0, g:Xgetlist()[0].valid)
+ " Adding a non-valid entry should not mark the list as having valid entries
+ call g:Xsetlist([{'bufnr':a:bnum, 'lnum':5, 'valid':0}], 'a')
+ Xwindow
+ call assert_equal(1, winnr('$'))
+
+ " :cnext/:cprev should still work even with invalid entries in the list
+ let l = [{'bufnr' : a:bnum, 'lnum' : 1, 'text' : '1', 'valid' : 0},
+ \ {'bufnr' : a:bnum, 'lnum' : 2, 'text' : '2', 'valid' : 0}]
+ call g:Xsetlist(l)
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+ " :cnext/:cprev should still work after appending invalid entries to an
+ " empty list
+ call g:Xsetlist([])
+ call g:Xsetlist(l, 'a')
+ Xnext
+ call assert_equal(2, g:Xgetlist({'idx' : 0}).idx)
+ Xprev
+ call assert_equal(1, g:Xgetlist({'idx' : 0}).idx)
+
call g:Xsetlist([{'text':'Text1', 'valid':1}])
Xwindow
call assert_equal(2, winnr('$'))
@@ -1620,6 +1642,14 @@ func Test_switchbuf()
call assert_equal(3, tabpagenr('$'))
tabfirst | enew | tabonly | only
+ set switchbuf=uselast
+ split
+ let last_winid = win_getid()
+ copen
+ exe "normal 1G\<CR>"
+ call assert_equal(last_winid, win_getid())
+ enew | only
+
set switchbuf=
edit Xqftestfile1
let file1_winid = win_getid()
@@ -2567,94 +2597,6 @@ func Test_resize_from_copen()
endtry
endfunc
-" Test for aborting quickfix commands using QuickFixCmdPre
-func Xtest_qfcmd_abort(cchar)
- call s:setup_commands(a:cchar)
-
- call g:Xsetlist([], 'f')
-
- " cexpr/lexpr
- let e = ''
- try
- Xexpr ["F1:10:Line10", "F2:20:Line20"]
- catch /.*/
- let e = v:exception
- endtry
- call assert_equal('AbortCmd', e)
- call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
-
- " cfile/lfile
- call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1')
- let e = ''
- try
- Xfile Xfile1
- catch /.*/
- let e = v:exception
- endtry
- call assert_equal('AbortCmd', e)
- call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
- call delete('Xfile1')
-
- " cgetbuffer/lgetbuffer
- enew!
- call append(0, ["F1:10:Line10", "F2:20:Line20"])
- let e = ''
- try
- Xgetbuffer
- catch /.*/
- let e = v:exception
- endtry
- call assert_equal('AbortCmd', e)
- call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
- enew!
-
- " vimgrep/lvimgrep
- let e = ''
- try
- Xvimgrep /func/ test_quickfix.vim
- catch /.*/
- let e = v:exception
- endtry
- call assert_equal('AbortCmd', e)
- call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
-
- " helpgrep/lhelpgrep
- let e = ''
- try
- Xhelpgrep quickfix
- catch /.*/
- let e = v:exception
- endtry
- call assert_equal('AbortCmd', e)
- call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
-
- " grep/lgrep
- if has('unix')
- let e = ''
- try
- silent Xgrep func test_quickfix.vim
- catch /.*/
- let e = v:exception
- endtry
- call assert_equal('AbortCmd', e)
- call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
- endif
-endfunc
-
-func Test_qfcmd_abort()
- augroup QF_Test
- au!
- autocmd QuickFixCmdPre * throw "AbortCmd"
- augroup END
-
- call Xtest_qfcmd_abort('c')
- call Xtest_qfcmd_abort('l')
-
- augroup QF_Test
- au!
- augroup END
-endfunc
-
" Tests for the quickfix buffer b:changedtick variable
func Xchangedtick_tests(cchar)
call s:setup_commands(a:cchar)
@@ -3011,185 +2953,101 @@ func Test_qf_id()
call Xqfid_tests('l')
endfunc
-func Test_getqflist_invalid_nr()
- " The following commands used to crash Vim
- cexpr ""
- call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX})
-
- " Cleanup
- call setqflist([], 'r')
-endfunc
-
-" Test for shortening/simplifying the file name when opening the
-" quickfix window or when displaying the quickfix list
-func Test_shorten_fname()
- if !has('unix')
- return
- endif
- %bwipe
- " Create a quickfix list with a absolute path filename
- let fname = getcwd() . '/test_quickfix.vim'
- call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
- call assert_equal(fname, bufname('test_quickfix.vim'))
- " Opening the quickfix window should simplify the file path
- cwindow
- call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
- cclose
- %bwipe
- " Create a quickfix list with a absolute path filename
- call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
- call assert_equal(fname, bufname('test_quickfix.vim'))
- " Displaying the quickfix list should simplify the file path
- silent! clist
- call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
-endfunc
-
-" Quickfix title tests
-" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands.
-" Otherwise due to indentation, the title is set with spaces at the beginning
-" of the command.
-func Test_qftitle()
- call writefile(["F1:1:Line1"], 'Xerr')
-
- " :cexpr
- exe "cexpr readfile('Xerr')"
- call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title)
-
- " :cgetexpr
- exe "cgetexpr readfile('Xerr')"
- call assert_equal(":cgetexpr readfile('Xerr')",
- \ getqflist({'title' : 1}).title)
-
- " :caddexpr
- call setqflist([], 'f')
- exe "caddexpr readfile('Xerr')"
- call assert_equal(":caddexpr readfile('Xerr')",
- \ getqflist({'title' : 1}).title)
-
- " :cbuffer
- new Xerr
- exe "cbuffer"
- call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title)
-
- " :cgetbuffer
- edit Xerr
- exe "cgetbuffer"
- call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title)
-
- " :caddbuffer
- call setqflist([], 'f')
- edit Xerr
- exe "caddbuffer"
- call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title)
-
- " :cfile
- exe "cfile Xerr"
- call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title)
-
- " :cgetfile
- exe "cgetfile Xerr"
- call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title)
-
- " :caddfile
- call setqflist([], 'f')
- exe "caddfile Xerr"
- call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title)
-
- " :grep
- set grepprg=internal
- exe "grep F1 Xerr"
- call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title)
-
- " :grepadd
- call setqflist([], 'f')
- exe "grepadd F1 Xerr"
- call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title)
- set grepprg&vim
-
- " :vimgrep
- exe "vimgrep F1 Xerr"
- call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title)
+func Xqfjump_tests(cchar)
+ call s:setup_commands(a:cchar)
- " :vimgrepadd
- call setqflist([], 'f')
- exe "vimgrepadd F1 Xerr"
- call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title)
+ call writefile(["Line1\tFoo", "Line2"], 'F1')
+ call writefile(["Line1\tBar", "Line2"], 'F2')
+ call writefile(["Line1\tBaz", "Line2"], 'F3')
- call setqflist(['F1:10:L10'], ' ')
- call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+ call g:Xsetlist([], 'f')
- call setqflist([], 'f')
- call setqflist(['F1:10:L10'], 'a')
- call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+ " Tests for
+ " Jumping to a line using a pattern
+ " Jumping to a column greater than the last column in a line
+ " Jumping to a line greater than the last line in the file
+ let l = []
+ for i in range(1, 7)
+ call add(l, {})
+ endfor
+ let l[0].filename='F1'
+ let l[0].pattern='Line1'
+ let l[1].filename='F2'
+ let l[1].pattern='Line1'
+ let l[2].filename='F3'
+ let l[2].pattern='Line1'
+ let l[3].filename='F3'
+ let l[3].lnum=1
+ let l[3].col=9
+ let l[3].vcol=1
+ let l[4].filename='F3'
+ let l[4].lnum=99
+ let l[5].filename='F3'
+ let l[5].lnum=1
+ let l[5].col=99
+ let l[5].vcol=1
+ let l[6].filename='F3'
+ let l[6].pattern='abcxyz'
- call setqflist([], 'f')
- call setqflist(['F1:10:L10'], 'r')
- call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+ call g:Xsetlist([], ' ', {'items' : l})
+ Xopen | only
+ 2Xnext
+ call assert_equal(3, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal('F3', bufname('%'))
+ Xnext
+ call assert_equal(7, col('.'))
+ Xnext
+ call assert_equal(2, line('.'))
+ Xnext
+ call assert_equal(9, col('.'))
+ 2
+ Xnext
+ call assert_equal(2, line('.'))
- close
- call delete('Xerr')
+ if a:cchar == 'l'
+ " When jumping to a location list entry in the location list window and
+ " no usable windows are available, then a new window should be opened.
+ enew! | new | only
+ call g:Xsetlist([], 'f')
+ setlocal buftype=nofile
+ new
+ call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']})
+ Xopen
+ let winid = win_getid()
+ wincmd p
+ close
+ call win_gotoid(winid)
+ Xnext
+ call assert_equal(3, winnr('$'))
+ call assert_equal(1, winnr())
+ call assert_equal(2, line('.'))
- call setqflist([], ' ', {'title' : 'Errors'})
- copen
- call assert_equal('Errors', w:quickfix_title)
- call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]})
- call assert_equal('Errors', w:quickfix_title)
- cclose
-endfunc
+ " When jumping to an entry in the location list window and the window
+ " associated with the location list is not present and a window containing
+ " the file is already present, then that window should be used.
+ close
+ belowright new
+ call g:Xsetlist([], 'f')
+ edit F3
+ call win_gotoid(winid)
+ Xlast
+ call assert_equal(3, winnr())
+ call assert_equal(6, g:Xgetlist({'size' : 1}).size)
+ call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid)
+ endif
-" Test for the position of the quickfix and location list window
-func Test_qfwin_pos()
- " Open two windows
+ " Cleanup
+ enew!
new | only
- new
- cexpr ['F1:10:L10']
- copen
- " Quickfix window should be the bottom most window
- call assert_equal(3, winnr())
- close
- " Open at the very top
- wincmd t
- topleft copen
- call assert_equal(1, winnr())
- close
- " open left of the current window
- wincmd t
- below new
- leftabove copen
- call assert_equal(2, winnr())
- close
- " open right of the current window
- rightbelow copen
- call assert_equal(3, winnr())
- close
-endfunc
-" The following test used to crash Vim
-func Test_lhelpgrep_autocmd()
- lhelpgrep quickfix
- autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
- lhelpgrep buffer
- call assert_equal('help', &filetype)
- call assert_equal(0, getloclist(0, {'nr' : '$'}).nr)
- lhelpgrep tabpage
- call assert_equal('help', &filetype)
- call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
- au! QuickFixCmdPost
- new | only
+ call delete('F1')
+ call delete('F2')
+ call delete('F3')
endfunc
-" Test to make sure that an empty quickfix buffer is not reused for loading
-" a normal buffer.
-func Test_empty_qfbuf()
- enew | only
- call writefile(["Test"], 'Xfile1')
- call setqflist([], 'f')
- copen | only
- let qfbuf = bufnr('')
- edit Xfile1
- call assert_notequal(qfbuf, bufnr(''))
- enew
- call delete('Xfile1')
+func Test_qfjump()
+ call Xqfjump_tests('c')
+ call Xqfjump_tests('l')
endfunc
" Tests for the getqflist() and getloclist() functions when the list is not
@@ -3289,6 +3147,16 @@ func Test_getqflist()
call Xgetlist_empty_tests('l')
endfunc
+
+func Test_getqflist_invalid_nr()
+ " The following commands used to crash Vim
+ cexpr ""
+ call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX})
+
+ " Cleanup
+ call setqflist([], 'r')
+endfunc
+
" Tests for the quickfix/location list changedtick
func Xqftick_tests(cchar)
call s:setup_commands(a:cchar)
@@ -3347,6 +3215,41 @@ func Test_qf_tick()
call Xqftick_tests('l')
endfunc
+" Test helpgrep with lang specifier
+func Xtest_helpgrep_with_lang_specifier(cchar)
+ call s:setup_commands(a:cchar)
+ Xhelpgrep Vim@en
+ call assert_equal('help', &filetype)
+ call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr)
+ new | only
+endfunc
+
+func Test_helpgrep_with_lang_specifier()
+ call Xtest_helpgrep_with_lang_specifier('c')
+ call Xtest_helpgrep_with_lang_specifier('l')
+endfunc
+
+" The following test used to crash Vim.
+" Open the location list window and close the regular window associated with
+" the location list. When the garbage collection runs now, it incorrectly
+" marks the location list context as not in use and frees the context.
+func Test_ll_window_ctx()
+ call setloclist(0, [], 'f')
+ call setloclist(0, [], 'a', {'context' : []})
+ lopen | only
+ call test_garbagecollect_now()
+ echo getloclist(0, {'context' : 1}).context
+ enew | only
+endfunc
+
+" The following test used to crash vim
+func Test_lfile_crash()
+ sp Xtest
+ au QuickFixCmdPre * bw
+ call assert_fails('lfile', 'E40')
+ au! QuickFixCmdPre
+endfunc
+
" The following test used to crash vim
func Test_lbuffer_crash()
sv Xtest
@@ -3408,136 +3311,31 @@ func Test_lvimgrep_crash()
enew | only
endfunc
-func Xqfjump_tests(cchar)
- call s:setup_commands(a:cchar)
-
- call writefile(["Line1\tFoo", "Line2"], 'F1')
- call writefile(["Line1\tBar", "Line2"], 'F2')
- call writefile(["Line1\tBaz", "Line2"], 'F3')
-
- call g:Xsetlist([], 'f')
-
- " Tests for
- " Jumping to a line using a pattern
- " Jumping to a column greater than the last column in a line
- " Jumping to a line greater than the last line in the file
- let l = []
- for i in range(1, 7)
- call add(l, {})
- endfor
- let l[0].filename='F1'
- let l[0].pattern='Line1'
- let l[1].filename='F2'
- let l[1].pattern='Line1'
- let l[2].filename='F3'
- let l[2].pattern='Line1'
- let l[3].filename='F3'
- let l[3].lnum=1
- let l[3].col=9
- let l[3].vcol=1
- let l[4].filename='F3'
- let l[4].lnum=99
- let l[5].filename='F3'
- let l[5].lnum=1
- let l[5].col=99
- let l[5].vcol=1
- let l[6].filename='F3'
- let l[6].pattern='abcxyz'
-
- call g:Xsetlist([], ' ', {'items' : l})
- Xopen | only
- 2Xnext
- call assert_equal(3, g:Xgetlist({'idx' : 0}).idx)
- call assert_equal('F3', bufname('%'))
- Xnext
- call assert_equal(7, col('.'))
- Xnext
- call assert_equal(2, line('.'))
- Xnext
- call assert_equal(9, col('.'))
- 2
- Xnext
- call assert_equal(2, line('.'))
-
- if a:cchar == 'l'
- " When jumping to a location list entry in the location list window and
- " no usable windows are available, then a new window should be opened.
- enew! | new | only
- call g:Xsetlist([], 'f')
- setlocal buftype=nofile
- new
- call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']})
- Xopen
- let winid = win_getid()
- wincmd p
- close
- call win_gotoid(winid)
- Xnext
- call assert_equal(3, winnr('$'))
- call assert_equal(1, winnr())
- call assert_equal(2, line('.'))
-
- " When jumping to an entry in the location list window and the window
- " associated with the location list is not present and a window containing
- " the file is already present, then that window should be used.
- close
- belowright new
- call g:Xsetlist([], 'f')
- edit F3
- call win_gotoid(winid)
- Xlast
- call assert_equal(3, winnr())
- call assert_equal(6, g:Xgetlist({'size' : 1}).size)
- call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid)
- endif
-
- " Cleanup
- enew!
- new | only
-
- call delete('F1')
- call delete('F2')
- call delete('F3')
-endfunc
-
-func Test_qfjump()
- call Xqfjump_tests('c')
- call Xqfjump_tests('l')
-endfunc
-
-" Test helpgrep with lang specifier
-func Xtest_helpgrep_with_lang_specifier(cchar)
- call s:setup_commands(a:cchar)
- Xhelpgrep Vim@en
- call assert_equal('help', &filetype)
- call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr)
+" Test for the position of the quickfix and location list window
+func Test_qfwin_pos()
+ " Open two windows
new | only
-endfunc
-
-func Test_helpgrep_with_lang_specifier()
- call Xtest_helpgrep_with_lang_specifier('c')
- call Xtest_helpgrep_with_lang_specifier('l')
-endfunc
-
-" The following test used to crash Vim.
-" Open the location list window and close the regular window associated with
-" the location list. When the garbage collection runs now, it incorrectly
-" marks the location list context as not in use and frees the context.
-func Test_ll_window_ctx()
- call setloclist(0, [], 'f')
- call setloclist(0, [], 'a', {'context' : []})
- lopen | only
- call test_garbagecollect_now()
- echo getloclist(0, {'context' : 1}).context
- enew | only
-endfunc
-
-" The following test used to crash vim
-func Test_lfile_crash()
- sp Xtest
- au QuickFixCmdPre * bw
- call assert_fails('lfile', 'E40')
- au! QuickFixCmdPre
+ new
+ cexpr ['F1:10:L10']
+ copen
+ " Quickfix window should be the bottom most window
+ call assert_equal(3, winnr())
+ close
+ " Open at the very top
+ wincmd t
+ topleft copen
+ call assert_equal(1, winnr())
+ close
+ " open left of the current window
+ wincmd t
+ below new
+ leftabove copen
+ call assert_equal(2, winnr())
+ close
+ " open right of the current window
+ rightbelow copen
+ call assert_equal(3, winnr())
+ close
endfunc
" Tests for quickfix/location lists changed by autocommands when
@@ -3581,6 +3379,137 @@ func Test_vimgrep_autocmd()
call setqflist([], 'f')
endfunc
+" The following test used to crash Vim
+func Test_lhelpgrep_autocmd()
+ lhelpgrep quickfix
+ autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
+ lhelpgrep buffer
+ call assert_equal('help', &filetype)
+ call assert_equal(0, getloclist(0, {'nr' : '$'}).nr)
+ lhelpgrep tabpage
+ call assert_equal('help', &filetype)
+ call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
+ au! QuickFixCmdPost
+ new | only
+endfunc
+
+" Test for shortening/simplifying the file name when opening the
+" quickfix window or when displaying the quickfix list
+func Test_shorten_fname()
+ if !has('unix')
+ return
+ endif
+ %bwipe
+ " Create a quickfix list with a absolute path filename
+ let fname = getcwd() . '/test_quickfix.vim'
+ call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
+ call assert_equal(fname, bufname('test_quickfix.vim'))
+ " Opening the quickfix window should simplify the file path
+ cwindow
+ call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+ cclose
+ %bwipe
+ " Create a quickfix list with a absolute path filename
+ call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'})
+ call assert_equal(fname, bufname('test_quickfix.vim'))
+ " Displaying the quickfix list should simplify the file path
+ silent! clist
+ call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+endfunc
+
+" Quickfix title tests
+" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands.
+" Otherwise due to indentation, the title is set with spaces at the beginning
+" of the command.
+func Test_qftitle()
+ call writefile(["F1:1:Line1"], 'Xerr')
+
+ " :cexpr
+ exe "cexpr readfile('Xerr')"
+ call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title)
+
+ " :cgetexpr
+ exe "cgetexpr readfile('Xerr')"
+ call assert_equal(":cgetexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :caddexpr
+ call setqflist([], 'f')
+ exe "caddexpr readfile('Xerr')"
+ call assert_equal(":caddexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :cbuffer
+ new Xerr
+ exe "cbuffer"
+ call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cgetbuffer
+ edit Xerr
+ exe "cgetbuffer"
+ call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :caddbuffer
+ call setqflist([], 'f')
+ edit Xerr
+ exe "caddbuffer"
+ call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cfile
+ exe "cfile Xerr"
+ call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title)
+
+ " :cgetfile
+ exe "cgetfile Xerr"
+ call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title)
+
+ " :caddfile
+ call setqflist([], 'f')
+ exe "caddfile Xerr"
+ call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title)
+
+ " :grep
+ set grepprg=internal
+ exe "grep F1 Xerr"
+ call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :grepadd
+ call setqflist([], 'f')
+ exe "grepadd F1 Xerr"
+ call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title)
+ set grepprg&vim
+
+ " :vimgrep
+ exe "vimgrep F1 Xerr"
+ call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :vimgrepadd
+ call setqflist([], 'f')
+ exe "vimgrepadd F1 Xerr"
+ call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title)
+
+ call setqflist(['F1:10:L10'], ' ')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'a')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'r')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ close
+ call delete('Xerr')
+
+ call setqflist([], ' ', {'title' : 'Errors'})
+ copen
+ call assert_equal('Errors', w:quickfix_title)
+ call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]})
+ call assert_equal('Errors', w:quickfix_title)
+ cclose
+endfunc
+
func Test_lbuffer_with_bwipe()
new
new
@@ -3593,23 +3522,6 @@ func Test_lbuffer_with_bwipe()
augroup END
endfunc
-" Tests for the ':filter /pat/ clist' command
-func Test_filter_clist()
- cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15']
- call assert_equal([' 2 Xfile2:15 col 15: Line 15'],
- \ split(execute('filter /Line 15/ clist'), "\n"))
- call assert_equal([' 1 Xfile1:10 col 10: Line 10'],
- \ split(execute('filter /Xfile1/ clist'), "\n"))
- call assert_equal([], split(execute('filter /abc/ clist'), "\n"))
-
- call setqflist([{'module' : 'abc', 'pattern' : 'pat1'},
- \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ')
- call assert_equal([' 2 pqr:pat2: '],
- \ split(execute('filter /pqr/ clist'), "\n"))
- call assert_equal([' 1 abc:pat1: '],
- \ split(execute('filter /pat1/ clist'), "\n"))
-endfunc
-
" Test for an autocmd freeing the quickfix/location list when cexpr/lexpr is
" running
func Xexpr_acmd_freelist(cchar)
@@ -3759,6 +3671,23 @@ func Test_autocmd_changelist()
call Xautocmd_changelist('l')
endfunc
+" Tests for the ':filter /pat/ clist' command
+func Test_filter_clist()
+ cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15']
+ call assert_equal([' 2 Xfile2:15 col 15: Line 15'],
+ \ split(execute('filter /Line 15/ clist'), "\n"))
+ call assert_equal([' 1 Xfile1:10 col 10: Line 10'],
+ \ split(execute('filter /Xfile1/ clist'), "\n"))
+ call assert_equal([], split(execute('filter /abc/ clist'), "\n"))
+
+ call setqflist([{'module' : 'abc', 'pattern' : 'pat1'},
+ \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ')
+ call assert_equal([' 2 pqr:pat2: '],
+ \ split(execute('filter /pqr/ clist'), "\n"))
+ call assert_equal([' 1 abc:pat1: '],
+ \ split(execute('filter /pat1/ clist'), "\n"))
+endfunc
+
" Tests for the "CTRL-W <CR>" command.
func Xview_result_split_tests(cchar)
call s:setup_commands(a:cchar)
@@ -3842,6 +3771,20 @@ func Test_viscol()
call delete('Xfile1')
endfunc
+" Test to make sure that an empty quickfix buffer is not reused for loading
+" a normal buffer.
+func Test_empty_qfbuf()
+ enew | only
+ call writefile(["Test"], 'Xfile1')
+ call setqflist([], 'f')
+ copen | only
+ let qfbuf = bufnr('')
+ edit Xfile1
+ call assert_notequal(qfbuf, bufnr(''))
+ enew
+ call delete('Xfile1')
+endfunc
+
" Test for the :cbelow, :cabove, :lbelow and :labove commands.
func Xtest_below(cchar)
call s:setup_commands(a:cchar)
@@ -3948,4 +3891,92 @@ func Test_cbelow()
call Xtest_below('l')
endfunc
+" Test for aborting quickfix commands using QuickFixCmdPre
+func Xtest_qfcmd_abort(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+
+ " cexpr/lexpr
+ let e = ''
+ try
+ Xexpr ["F1:10:Line10", "F2:20:Line20"]
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " cfile/lfile
+ call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1')
+ let e = ''
+ try
+ Xfile Xfile1
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ call delete('Xfile1')
+
+ " cgetbuffer/lgetbuffer
+ enew!
+ call append(0, ["F1:10:Line10", "F2:20:Line20"])
+ let e = ''
+ try
+ Xgetbuffer
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ enew!
+
+ " vimgrep/lvimgrep
+ let e = ''
+ try
+ Xvimgrep /func/ test_quickfix.vim
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " helpgrep/lhelpgrep
+ let e = ''
+ try
+ Xhelpgrep quickfix
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+
+ " grep/lgrep
+ if has('unix')
+ let e = ''
+ try
+ silent Xgrep func test_quickfix.vim
+ catch /.*/
+ let e = v:exception
+ endtry
+ call assert_equal('AbortCmd', e)
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+ endif
+endfunc
+
+func Test_qfcmd_abort()
+ augroup QF_Test
+ au!
+ autocmd QuickFixCmdPre * throw "AbortCmd"
+ augroup END
+
+ call Xtest_qfcmd_abort('c')
+ call Xtest_qfcmd_abort('l')
+
+ augroup QF_Test
+ au!
+ augroup END
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
index b5e99b0ed3..2ee0ee1c0c 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/src/nvim/testdir/test_regexp_latin.vim
@@ -3,7 +3,7 @@ set encoding=latin1
scriptencoding latin1
func s:equivalence_test()
- let str = "AÀÁÂÃÄÅ B C D EÈÉÊË F G H IÌÍÎÏ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X YÝ Z aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z"
+ let str = "AÀÃÂÃÄÅ B C D EÈÉÊË F G H IÃŒÃÃŽÃ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X Yà Z aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z"
let groups = split(str)
for group1 in groups
for c in split(group1, '\zs')
@@ -98,3 +98,669 @@ func Test_out_of_memory()
" This will be slow...
call assert_fails('call search("\\v((n||<)+);")', 'E363:')
endfunc
+
+" Tests for regexp patterns without multi-byte support.
+func Test_regexp_single_line_pat()
+ " tl is a List of Lists with:
+ " regexp engine
+ " regexp pattern
+ " text to test the pattern on
+ " expected match (optional)
+ " expected submatch 1 (optional)
+ " expected submatch 2 (optional)
+ " etc.
+ " When there is no match use only the first two items.
+ let tl = []
+
+ call add(tl, [2, 'ab', 'aab', 'ab'])
+ call add(tl, [2, 'b', 'abcdef', 'b'])
+ call add(tl, [2, 'bc*', 'abccccdef', 'bcccc'])
+ call add(tl, [2, 'bc\{-}', 'abccccdef', 'b'])
+ call add(tl, [2, 'bc\{-}\(d\)', 'abccccdef', 'bccccd', 'd'])
+ call add(tl, [2, 'bc*', 'abbdef', 'b'])
+ call add(tl, [2, 'c*', 'ccc', 'ccc'])
+ call add(tl, [2, 'bc*', 'abdef', 'b'])
+ call add(tl, [2, 'c*', 'abdef', ''])
+ call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc'])
+ call add(tl, [2, 'bc\+', 'abdef']) " no match
+
+ " operator \|
+ call add(tl, [2, 'a\|ab', 'cabd', 'a']) " alternation is ordered
+
+ call add(tl, [2, 'c\?', 'ccb', 'c'])
+ call add(tl, [2, 'bc\?', 'abd', 'b'])
+ call add(tl, [2, 'bc\?', 'abccd', 'bc'])
+
+ call add(tl, [2, '\va{1}', 'ab', 'a'])
+
+ call add(tl, [2, '\va{2}', 'aa', 'aa'])
+ call add(tl, [2, '\va{2}', 'caad', 'aa'])
+ call add(tl, [2, '\va{2}', 'aba'])
+ call add(tl, [2, '\va{2}', 'ab'])
+ call add(tl, [2, '\va{2}', 'abaa', 'aa'])
+ call add(tl, [2, '\va{2}', 'aaa', 'aa'])
+
+ call add(tl, [2, '\vb{1}', 'abca', 'b'])
+ call add(tl, [2, '\vba{2}', 'abaa', 'baa'])
+ call add(tl, [2, '\vba{3}', 'aabaac'])
+
+ call add(tl, [2, '\v(ab){1}', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1}', 'dabc', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1}', 'acb'])
+
+ call add(tl, [2, '\v(ab){0,2}', 'acb', "", ""])
+ call add(tl, [2, '\v(ab){0,2}', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1,2}', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(ab){1,2}', 'ababc', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2,4}', 'ababcab', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2,4}', 'abcababa', 'abab', 'ab'])
+
+ call add(tl, [2, '\v(ab){2}', 'abab', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2}', 'cdababe', 'abab', 'ab'])
+ call add(tl, [2, '\v(ab){2}', 'abac'])
+ call add(tl, [2, '\v(ab){2}', 'abacabab', 'abab', 'ab'])
+ call add(tl, [2, '\v((ab){2}){2}', 'abababab', 'abababab', 'abab', 'ab'])
+ call add(tl, [2, '\v((ab){2}){2}', 'abacabababab', 'abababab', 'abab', 'ab'])
+
+ call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
+ call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2}){1}', 'aaac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2}){1}', 'daaac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{1}){2}', 'daaac', 'aa', 'a'])
+ call add(tl, [2, '\v(a{1}){2}', 'aaa', 'aa', 'a'])
+ call add(tl, [2, '\v(a{2})+', 'adaac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2})+', 'aa', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa'])
+ call add(tl, [2, '\v(a{1}){2}', 'aa', 'aa', 'a'])
+ call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a'])
+ call add(tl, [2, '\v(a{2}){2}', 'aaaa', 'aaaa', 'aa'])
+ call add(tl, [2, '\v(a{2}){2}', 'aaabaaaa', 'aaaa', 'aa'])
+
+ call add(tl, [2, '\v(a+){2}', 'dadaac', 'aa', 'a'])
+ call add(tl, [2, '\v(a{3}){2}', 'aaaaaaa', 'aaaaaa', 'aaa'])
+
+ call add(tl, [2, '\v(a{1,2}){2}', 'daaac', 'aaa', 'a'])
+ call add(tl, [2, '\v(a{1,3}){2}', 'daaaac', 'aaaa', 'a'])
+ call add(tl, [2, '\v(a{1,3}){2}', 'daaaaac', 'aaaaa', 'aa'])
+ call add(tl, [2, '\v(a{1,3}){3}', 'daac'])
+ call add(tl, [2, '\v(a{1,2}){2}', 'dac'])
+ call add(tl, [2, '\v(a+)+', 'daac', 'aa', 'aa'])
+ call add(tl, [2, '\v(a+)+', 'aaa', 'aaa', 'aaa'])
+ call add(tl, [2, '\v(a+){1,2}', 'aaa', 'aaa', 'aaa'])
+ call add(tl, [2, '\v(a+)(a+)', 'aaa', 'aaa', 'aa', 'a'])
+ call add(tl, [2, '\v(a{3})+', 'daaaac', 'aaa', 'aaa'])
+ call add(tl, [2, '\v(a|b|c)+', 'aacb', 'aacb', 'b'])
+ call add(tl, [2, '\v(a|b|c){2}', 'abcb', 'ab', 'b'])
+ call add(tl, [2, '\v(abc){2}', 'abcabd', ])
+ call add(tl, [2, '\v(abc){2}', 'abdabcabc','abcabc', 'abc'])
+
+ call add(tl, [2, 'a*', 'cc', ''])
+ call add(tl, [2, '\v(a*)+', 'cc', ''])
+ call add(tl, [2, '\v((ab)+)+', 'ab', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(((ab)+)+)+', 'ab', 'ab', 'ab', 'ab', 'ab'])
+ call add(tl, [2, '\v(((ab)+)+)+', 'dababc', 'abab', 'abab', 'abab', 'ab'])
+ call add(tl, [2, '\v(a{0,2})+', 'cc', ''])
+ call add(tl, [2, '\v(a*)+', '', ''])
+ call add(tl, [2, '\v((a*)+)+', '', ''])
+ call add(tl, [2, '\v((ab)*)+', '', ''])
+ call add(tl, [2, '\va{1,3}', 'aab', 'aa'])
+ call add(tl, [2, '\va{2,3}', 'abaa', 'aa'])
+
+ call add(tl, [2, '\v((ab)+|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
+ call add(tl, [2, '\v(a{2})|(b{3})', 'bbabbbb', 'bbb', '', 'bbb'])
+ call add(tl, [2, '\va{2}|b{2}', 'abab'])
+ call add(tl, [2, '\v(a)+|(c)+', 'bbacbaacbbb', 'a', 'a'])
+ call add(tl, [2, '\vab{2,3}c', 'aabbccccccccccccc', 'abbc'])
+ call add(tl, [2, '\vab{2,3}c', 'aabbbccccccccccccc', 'abbbc'])
+ call add(tl, [2, '\vab{2,3}cd{2,3}e', 'aabbbcddee', 'abbbcdde'])
+ call add(tl, [2, '\va(bc){2}d', 'aabcbfbc' ])
+ call add(tl, [2, '\va*a{2}', 'a', ])
+ call add(tl, [2, '\va*a{2}', 'aa', 'aa' ])
+ call add(tl, [2, '\va*a{2}', 'aaa', 'aaa' ])
+ call add(tl, [2, '\va*a{2}', 'bbbabcc', ])
+ call add(tl, [2, '\va*b*|a*c*', 'a', 'a'])
+ call add(tl, [2, '\va{1}b{1}|a{1}b{1}', ''])
+
+ " submatches
+ call add(tl, [2, '\v(a)', 'ab', 'a', 'a'])
+ call add(tl, [2, '\v(a)(b)', 'ab', 'ab', 'a', 'b'])
+ call add(tl, [2, '\v(ab)(b)(c)', 'abbc', 'abbc', 'ab', 'b', 'c'])
+ call add(tl, [2, '\v((a)(b))', 'ab', 'ab', 'ab', 'a', 'b'])
+ call add(tl, [2, '\v(a)|(b)', 'ab', 'a', 'a'])
+
+ call add(tl, [2, '\v(a*)+', 'aaaa', 'aaaa', ''])
+ call add(tl, [2, 'x', 'abcdef'])
+
+ "
+ " Simple tests
+ "
+
+ " Search single groups
+ call add(tl, [2, 'ab', 'aab', 'ab'])
+ call add(tl, [2, 'ab', 'baced'])
+ call add(tl, [2, 'ab', ' ab ', 'ab'])
+
+ " Search multi-modifiers
+ call add(tl, [2, 'x*', 'xcd', 'x'])
+ call add(tl, [2, 'x*', 'xxxxxxxxxxxxxxxxsofijiojgf', 'xxxxxxxxxxxxxxxx'])
+ " empty match is good
+ call add(tl, [2, 'x*', 'abcdoij', ''])
+ " no match here
+ call add(tl, [2, 'x\+', 'abcdoin'])
+ call add(tl, [2, 'x\+', 'abcdeoijdfxxiuhfij', 'xx'])
+ call add(tl, [2, 'x\+', 'xxxxx', 'xxxxx'])
+ call add(tl, [2, 'x\+', 'abc x siufhiush xxxxxxxxx', 'x'])
+ call add(tl, [2, 'x\=', 'x sdfoij', 'x'])
+ call add(tl, [2, 'x\=', 'abc sfoij', '']) " empty match is good
+ call add(tl, [2, 'x\=', 'xxxxxxxxx c', 'x'])
+ call add(tl, [2, 'x\?', 'x sdfoij', 'x'])
+ " empty match is good
+ call add(tl, [2, 'x\?', 'abc sfoij', ''])
+ call add(tl, [2, 'x\?', 'xxxxxxxxxx c', 'x'])
+
+ call add(tl, [2, 'a\{0,0}', 'abcdfdoij', ''])
+ " same thing as 'a?'
+ call add(tl, [2, 'a\{0,1}', 'asiubid axxxaaa', 'a'])
+ " same thing as 'a\{0,1}'
+ call add(tl, [2, 'a\{1,0}', 'asiubid axxxaaa', 'a'])
+ call add(tl, [2, 'a\{3,6}', 'aa siofuh'])
+ call add(tl, [2, 'a\{3,6}', 'aaaaa asfoij afaa', 'aaaaa'])
+ call add(tl, [2, 'a\{3,6}', 'aaaaaaaa', 'aaaaaa'])
+ call add(tl, [2, 'a\{0}', 'asoiuj', ''])
+ call add(tl, [2, 'a\{2}', 'aaaa', 'aa'])
+ call add(tl, [2, 'a\{2}', 'iuash fiusahfliusah fiushfilushfi uhsaifuh askfj nasfvius afg aaaa sfiuhuhiushf', 'aa'])
+ call add(tl, [2, 'a\{2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
+ " same thing as 'a*'
+ call add(tl, [2, 'a\{0,}', 'oij sdigfusnf', ''])
+ call add(tl, [2, 'a\{0,}', 'aaaaa aa', 'aaaaa'])
+ call add(tl, [2, 'a\{2,}', 'sdfiougjdsafg'])
+ call add(tl, [2, 'a\{2,}', 'aaaaasfoij ', 'aaaaa'])
+ call add(tl, [2, 'a\{5,}', 'xxaaaaxxx '])
+ call add(tl, [2, 'a\{5,}', 'xxaaaaaxxx ', 'aaaaa'])
+ call add(tl, [2, 'a\{,0}', 'oidfguih iuhi hiu aaaa', ''])
+ call add(tl, [2, 'a\{,5}', 'abcd', 'a'])
+ call add(tl, [2, 'a\{,5}', 'aaaaaaaaaa', 'aaaaa'])
+ " leading star as normal char when \{} follows
+ call add(tl, [2, '^*\{4,}$', '***'])
+ call add(tl, [2, '^*\{4,}$', '****', '****'])
+ call add(tl, [2, '^*\{4,}$', '*****', '*****'])
+ " same thing as 'a*'
+ call add(tl, [2, 'a\{}', 'bbbcddiuhfcd', ''])
+ call add(tl, [2, 'a\{}', 'aaaaioudfh coisf jda', 'aaaa'])
+
+ call add(tl, [2, 'a\{-0,0}', 'abcdfdoij', ''])
+ " anti-greedy version of 'a?'
+ call add(tl, [2, 'a\{-0,1}', 'asiubid axxxaaa', ''])
+ call add(tl, [2, 'a\{-3,6}', 'aa siofuh'])
+ call add(tl, [2, 'a\{-3,6}', 'aaaaa asfoij afaa', 'aaa'])
+ call add(tl, [2, 'a\{-3,6}', 'aaaaaaaa', 'aaa'])
+ call add(tl, [2, 'a\{-0}', 'asoiuj', ''])
+ call add(tl, [2, 'a\{-2}', 'aaaa', 'aa'])
+ call add(tl, [2, 'a\{-2}', 'abcdefghijklmnopqrestuvwxyz1234567890'])
+ call add(tl, [2, 'a\{-0,}', 'oij sdigfusnf', ''])
+ call add(tl, [2, 'a\{-0,}', 'aaaaa aa', ''])
+ call add(tl, [2, 'a\{-2,}', 'sdfiougjdsafg'])
+ call add(tl, [2, 'a\{-2,}', 'aaaaasfoij ', 'aa'])
+ call add(tl, [2, 'a\{-,0}', 'oidfguih iuhi hiu aaaa', ''])
+ call add(tl, [2, 'a\{-,5}', 'abcd', ''])
+ call add(tl, [2, 'a\{-,5}', 'aaaaaaaaaa', ''])
+ " anti-greedy version of 'a*'
+ call add(tl, [2, 'a\{-}', 'bbbcddiuhfcd', ''])
+ call add(tl, [2, 'a\{-}', 'aaaaioudfh coisf jda', ''])
+
+ " Test groups of characters and submatches
+ call add(tl, [2, '\(abc\)*', 'abcabcabc', 'abcabcabc', 'abc'])
+ call add(tl, [2, '\(ab\)\+', 'abababaaaaa', 'ababab', 'ab'])
+ call add(tl, [2, '\(abaaaaa\)*cd', 'cd', 'cd', ''])
+ call add(tl, [2, '\(test1\)\? \(test2\)\?', 'test1 test3', 'test1 ', 'test1', ''])
+ call add(tl, [2, '\(test1\)\= \(test2\) \(test4443\)\=', ' test2 test4443 yupiiiiiiiiiii', ' test2 test4443', '', 'test2', 'test4443'])
+ call add(tl, [2, '\(\(sub1\) hello \(sub 2\)\)', 'asterix sub1 hello sub 2 obelix', 'sub1 hello sub 2', 'sub1 hello sub 2', 'sub1', 'sub 2'])
+ call add(tl, [2, '\(\(\(yyxxzz\)\)\)', 'abcdddsfiusfyyzzxxyyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz'])
+ call add(tl, [2, '\v((ab)+|c+)+', 'abcccaba', 'abcccab', 'ab', 'ab'])
+ call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab'])
+ call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', ''])
+ call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', ''])
+ call add(tl, [2, '\p*', 'aá ', 'aá '])
+
+ " Test greedy-ness and lazy-ness
+ call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa'])
+ call add(tl, [2, 'a\{-2,7}x','aaaaaaaaax', 'aaaaaaax'])
+ call add(tl, [2, 'a\{2,7}','aaaaaaaaaaaaaaaaaaaa', 'aaaaaaa'])
+ call add(tl, [2, 'a\{2,7}x','aaaaaaaaax', 'aaaaaaax'])
+ call add(tl, [2, '\vx(.{-,8})yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz','ayxa','xayzxayz'])
+ call add(tl, [2, '\vx(.*)yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz', 'ayxayzxayzxa',''])
+ call add(tl, [2, '\v(a{1,2}){-2,3}','aaaaaaa','aaaa','aa'])
+ call add(tl, [2, '\v(a{-1,3})+', 'aa', 'aa', 'a'])
+ call add(tl, [2, '^\s\{-}\zs\( x\|x$\)', ' x', ' x', ' x'])
+ call add(tl, [2, '^\s\{-}\zs\(x\| x$\)', ' x', ' x', ' x'])
+ call add(tl, [2, '^\s\{-}\ze\(x\| x$\)', ' x', '', ' x'])
+ call add(tl, [2, '^\(\s\{-}\)\(x\| x$\)', ' x', ' x', '', ' x'])
+
+ " Test Character classes
+ call add(tl, [2, '\d\+e\d\d','test 10e23 fd','10e23'])
+
+ " Test collections and character range []
+ call add(tl, [2, '\v[a]', 'abcd', 'a'])
+ call add(tl, [2, 'a[bcd]', 'abcd', 'ab'])
+ call add(tl, [2, 'a[b-d]', 'acbd', 'ac'])
+ call add(tl, [2, '[a-d][e-f][x-x]d', 'cexdxx', 'cexd'])
+ call add(tl, [2, '\v[[:alpha:]]+', 'abcdefghijklmnopqrstuvwxyz6','abcdefghijklmnopqrstuvwxyz'])
+ call add(tl, [2, '[[:alpha:]\+]', '6x8','x'])
+ call add(tl, [2, '[^abc]\+','abcabcabc'])
+ call add(tl, [2, '[^abc]','defghiasijvoinasoiunbvb','d'])
+ call add(tl, [2, '[^abc]\+','ddddddda','ddddddd'])
+ call add(tl, [2, '[^a-d]\+','aaaAAAZIHFNCddd','AAAZIHFNC'])
+ call add(tl, [2, '[a-f]*','iiiiiiii',''])
+ call add(tl, [2, '[a-f]*','abcdefgh','abcdef'])
+ call add(tl, [2, '[^a-f]\+','abcdefgh','gh'])
+ call add(tl, [2, '[a-c]\{-3,6}','abcabc','abc'])
+ call add(tl, [2, '[^[:alpha:]]\+','abcccadfoij7787ysf287yrnccdu','7787'])
+ call add(tl, [2, '[-a]', '-', '-'])
+ call add(tl, [2, '[a-]', '-', '-'])
+ call add(tl, [2, '[a-f]*\c','ABCDEFGH','ABCDEF'])
+ call add(tl, [2, '[abc][xyz]\c','-af-AF-BY--','BY'])
+ " filename regexp
+ call add(tl, [2, '[-./[:alnum:]_~]\+', 'log13.file', 'log13.file'])
+ " special chars
+ call add(tl, [2, '[\]\^\-\\]\+', '\^\\\-\---^', '\^\\\-\---^'])
+ " collation elem
+ call add(tl, [2, '[[.a.]]\+', 'aa', 'aa'])
+ " middle of regexp
+ call add(tl, [2, 'abc[0-9]*ddd', 'siuhabc ii'])
+ call add(tl, [2, 'abc[0-9]*ddd', 'adf abc44482ddd oijs', 'abc44482ddd'])
+ call add(tl, [2, '\_[0-9]\+', 'asfi9888u', '9888'])
+ call add(tl, [2, '[0-9\n]\+', 'asfi9888u', '9888'])
+ call add(tl, [2, '\_[0-9]\+', "asfi\n9888u", "\n9888"])
+ call add(tl, [2, '\_f', " \na ", "\n"])
+ call add(tl, [2, '\_f\+', " \na ", "\na"])
+ call add(tl, [2, '[0-9A-Za-z-_.]\+', " @0_a.A-{ ", "0_a.A-"])
+
+ " Test start/end of line, start/end of file
+ call add(tl, [2, '^a.', "a_\nb ", "a_"])
+ call add(tl, [2, '^a.', "b a \na_"])
+ call add(tl, [2, '.a$', " a\n "])
+ call add(tl, [2, '.a$', " a b\n_a", "_a"])
+ call add(tl, [2, '\%^a.', "a a\na", "a "])
+ call add(tl, [2, '\%^a', " a \na "])
+ call add(tl, [2, '.a\%$', " a\n "])
+ call add(tl, [2, '.a\%$', " a\n_a", "_a"])
+
+ " Test recognition of character classes
+ call add(tl, [2, '[0-7]\+', 'x0123456789x', '01234567'])
+ call add(tl, [2, '[^0-7]\+', '0a;X+% 897', 'a;X+% 89'])
+ call add(tl, [2, '[0-9]\+', 'x0123456789x', '0123456789'])
+ call add(tl, [2, '[^0-9]\+', '0a;X+% 9', 'a;X+% '])
+ call add(tl, [2, '[0-9a-fA-F]\+', 'x0189abcdefg', '0189abcdef'])
+ call add(tl, [2, '[^0-9A-Fa-f]\+', '0189g;X+% ab', 'g;X+% '])
+ call add(tl, [2, '[a-z_A-Z0-9]\+', ';+aso_SfOij ', 'aso_SfOij'])
+ call add(tl, [2, '[^a-z_A-Z0-9]\+', 'aSo_;+% sfOij', ';+% '])
+ call add(tl, [2, '[a-z_A-Z]\+', '0abyz_ABYZ;', 'abyz_ABYZ'])
+ call add(tl, [2, '[^a-z_A-Z]\+', 'abAB_09;+% yzYZ', '09;+% '])
+ call add(tl, [2, '[a-z]\+', '0abcxyz1', 'abcxyz'])
+ call add(tl, [2, '[a-z]\+', 'AabxyzZ', 'abxyz'])
+ call add(tl, [2, '[^a-z]\+', 'a;X09+% x', ';X09+% '])
+ call add(tl, [2, '[^a-z]\+', 'abX0;%yz', 'X0;%'])
+ call add(tl, [2, '[a-zA-Z]\+', '0abABxzXZ9', 'abABxzXZ'])
+ call add(tl, [2, '[^a-zA-Z]\+', 'ab09_;+ XZ', '09_;+ '])
+ call add(tl, [2, '[A-Z]\+', 'aABXYZz', 'ABXYZ'])
+ call add(tl, [2, '[^A-Z]\+', 'ABx0;%YZ', 'x0;%'])
+ call add(tl, [2, '[a-z]\+\c', '0abxyzABXYZ;', 'abxyzABXYZ'])
+ call add(tl, [2, '[A-Z]\+\c', '0abABxzXZ9', 'abABxzXZ'])
+ call add(tl, [2, '\c[^a-z]\+', 'ab09_;+ XZ', '09_;+ '])
+ call add(tl, [2, '\c[^A-Z]\+', 'ab09_;+ XZ', '09_;+ '])
+ call add(tl, [2, '\C[^A-Z]\+', 'ABCOIJDEOIFNSD jsfoij sa', ' jsfoij sa'])
+
+ " Tests for \z features
+ " match ends at \ze
+ call add(tl, [2, 'xx \ze test', 'xx '])
+ call add(tl, [2, 'abc\zeend', 'oij abcend', 'abc'])
+ call add(tl, [2, 'aa\zebb\|aaxx', ' aabb ', 'aa'])
+ call add(tl, [2, 'aa\zebb\|aaxx', ' aaxx ', 'aaxx'])
+ call add(tl, [2, 'aabb\|aa\zebb', ' aabb ', 'aabb'])
+ call add(tl, [2, 'aa\zebb\|aaebb', ' aabb ', 'aa'])
+ " match starts at \zs
+ call add(tl, [2, 'abc\zsdd', 'ddabcddxyzt', 'dd'])
+ call add(tl, [2, 'aa \zsax', ' ax'])
+ call add(tl, [2, 'abc \zsmatch\ze abc', 'abc abc abc match abc abc', 'match'])
+ call add(tl, [2, '\v(a \zsif .*){2}', 'a if then a if last', 'if last', 'a if last'])
+ call add(tl, [2, '\>\zs.', 'aword. ', '.'])
+ call add(tl, [2, '\s\+\ze\[/\|\s\zs\s\+', 'is [a t', ' '])
+
+ " Tests for \@= and \& features
+ call add(tl, [2, 'abc\@=', 'abc', 'ab'])
+ call add(tl, [2, 'abc\@=cd', 'abcd', 'abcd'])
+ call add(tl, [2, 'abc\@=', 'ababc', 'ab'])
+ " will never match, no matter the input text
+ call add(tl, [2, 'abcd\@=e', 'abcd'])
+ " will never match
+ call add(tl, [2, 'abcd\@=e', 'any text in here ... '])
+ call add(tl, [2, '\v(abc)@=..', 'xabcd', 'ab', 'abc'])
+ call add(tl, [2, '\(.*John\)\@=.*Bob', 'here is John, and here is B'])
+ call add(tl, [2, '\(John.*\)\@=.*Bob', 'John is Bobs friend', 'John is Bob', 'John is Bobs friend'])
+ call add(tl, [2, '\<\S\+\())\)\@=', '$((i=i+1))', 'i=i+1', '))'])
+ call add(tl, [2, '.*John\&.*Bob', 'here is John, and here is B'])
+ call add(tl, [2, '.*John\&.*Bob', 'John is Bobs friend', 'John is Bob'])
+ call add(tl, [2, '\v(test1)@=.*yep', 'this is a test1, yep it is', 'test1, yep', 'test1'])
+ call add(tl, [2, 'foo\(bar\)\@!', 'foobar'])
+ call add(tl, [2, 'foo\(bar\)\@!', 'foo bar', 'foo'])
+ call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if then else'])
+ call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if else ', 'if else ', ' '])
+ call add(tl, [2, '\(foo\)\@!bar', 'foobar', 'bar'])
+ call add(tl, [2, '\(foo\)\@!...bar', 'foobar'])
+ call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' bar foo '])
+ call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo bar '])
+ call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo xxx ', 'foo'])
+ call add(tl, [2, '[ ]\@!\p\%([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:'])
+ call add(tl, [2, '[ ]\@!\p\([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:', 's'])
+ call add(tl, [2, 'm\k\+_\@=\%(_\@!\k\)\@<=\k\+e', 'mx__xe', 'mx__xe'])
+ call add(tl, [2, '\%(\U\@<=S\k*\|S\l\)R', 'SuR', 'SuR'])
+
+ " Combining different tests and features
+ call add(tl, [2, '[[:alpha:]]\{-2,6}', '787abcdiuhsasiuhb4', 'ab'])
+ call add(tl, [2, '', 'abcd', ''])
+ call add(tl, [2, '\v(())', 'any possible text', ''])
+ call add(tl, [2, '\v%(ab(xyz)c)', ' abxyzc ', 'abxyzc', 'xyz'])
+ call add(tl, [2, '\v(test|)empty', 'tesempty', 'empty', ''])
+ call add(tl, [2, '\v(a|aa)(a|aa)', 'aaa', 'aa', 'a', 'a'])
+
+ " \%u and friends
+ call add(tl, [2, '\%d32', 'yes no', ' '])
+ call add(tl, [2, '\%o40', 'yes no', ' '])
+ call add(tl, [2, '\%x20', 'yes no', ' '])
+ call add(tl, [2, '\%u0020', 'yes no', ' '])
+ call add(tl, [2, '\%U00000020', 'yes no', ' '])
+ call add(tl, [2, '\%d0', "yes\x0ano", "\x0a"])
+
+ "" \%[abc]
+ call add(tl, [2, 'foo\%[bar]', 'fobar'])
+ call add(tl, [2, 'foo\%[bar]', 'foobar', 'foobar'])
+ call add(tl, [2, 'foo\%[bar]', 'fooxx', 'foo'])
+ call add(tl, [2, 'foo\%[bar]', 'foobxx', 'foob'])
+ call add(tl, [2, 'foo\%[bar]', 'foobaxx', 'fooba'])
+ call add(tl, [2, 'foo\%[bar]', 'foobarxx', 'foobar'])
+ call add(tl, [2, 'foo\%[bar]x', 'foobxx', 'foobx'])
+ call add(tl, [2, 'foo\%[bar]x', 'foobarxx', 'foobarx'])
+ call add(tl, [2, '\%[bar]x', 'barxx', 'barx'])
+ call add(tl, [2, '\%[bar]x', 'bxx', 'bx'])
+ call add(tl, [2, '\%[bar]x', 'xxx', 'x'])
+ call add(tl, [2, 'b\%[[ao]r]', 'bar bor', 'bar'])
+ call add(tl, [2, 'b\%[[]]r]', 'b]r bor', 'b]r'])
+ call add(tl, [2, '@\%[\w\-]*', '<http://john.net/pandoc/>[@pandoc]', '@pandoc'])
+
+ " Alternatives, must use first longest match
+ call add(tl, [2, 'goo\|go', 'google', 'goo'])
+ call add(tl, [2, '\<goo\|\<go', 'google', 'goo'])
+ call add(tl, [2, '\<goo\|go', 'google', 'goo'])
+
+ " Back references
+ call add(tl, [2, '\(\i\+\) \1', ' abc abc', 'abc abc', 'abc'])
+ call add(tl, [2, '\(\i\+\) \1', 'xgoo goox', 'goo goo', 'goo'])
+ call add(tl, [2, '\(a\)\(b\)\(c\)\(dd\)\(e\)\(f\)\(g\)\(h\)\(i\)\1\2\3\4\5\6\7\8\9', 'xabcddefghiabcddefghix', 'abcddefghiabcddefghi', 'a', 'b', 'c', 'dd', 'e', 'f', 'g', 'h', 'i'])
+ call add(tl, [2, '\(\d*\)a \1b', ' a b ', 'a b', ''])
+ call add(tl, [2, '^.\(.\).\_..\1.', "aaa\naaa\nb", "aaa\naaa", 'a'])
+ call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.com', 'foo.bat/foo.com', 'bat'])
+ call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<!$', 'foo.bat/foo.bat'])
+ call add(tl, [2, '^.*\.\(.*\)/.\+\(\1\)\@<=$', 'foo.bat/foo.bat', 'foo.bat/foo.bat', 'bat', 'bat'])
+ call add(tl, [2, '\\\@<!\${\(\d\+\%(:.\{-}\)\?\\\@<!\)}', '2013-06-27${0}', '${0}', '0'])
+ call add(tl, [2, '^\(a*\)\1$', 'aaaaaaaa', 'aaaaaaaa', 'aaaa'])
+ call add(tl, [2, '^\(a\{-2,}\)\1\+$', 'aaaaaaaaa', 'aaaaaaaaa', 'aaa'])
+
+ " Look-behind with limit
+ call add(tl, [2, '<\@<=span.', 'xxspanxx<spanyyy', 'spany'])
+ call add(tl, [2, '<\@1<=span.', 'xxspanxx<spanyyy', 'spany'])
+ call add(tl, [2, '<\@2<=span.', 'xxspanxx<spanyyy', 'spany'])
+ call add(tl, [2, '\(<<\)\@<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
+ call add(tl, [2, '\(<<\)\@1<=span.', 'xxspanxxxx<spanxx<<spanyyy'])
+ call add(tl, [2, '\(<<\)\@2<=span.', 'xxspanxxxx<spanxx<<spanyyy', 'spany', '<<'])
+ call add(tl, [2, '\(foo\)\@<!bar.', 'xx foobar1 xbar2 xx', 'bar2'])
+
+ " look-behind match in front of a zero-width item
+ call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" test header'])
+ call add(tl, [2, '\v\C%(<Last Changed:\s+)@<=.*$', '" Last Changed: 1970', '1970'])
+ call add(tl, [2, '\(foo\)\@<=\>', 'foobar'])
+ call add(tl, [2, '\(foo\)\@<=\>', 'barfoo', '', 'foo'])
+ call add(tl, [2, '\(foo\)\@<=.*', 'foobar', 'bar', 'foo'])
+
+ " complicated look-behind match
+ call add(tl, [2, '\(r\@<=\|\w\@<!\)\/', 'x = /word/;', '/'])
+ call add(tl, [2, '^[a-z]\+\ze \&\(asdf\)\@<!', 'foo bar', 'foo'])
+
+ "" \@>
+ call add(tl, [2, '\(a*\)\@>a', 'aaaa'])
+ call add(tl, [2, '\(a*\)\@>b', 'aaab', 'aaab', 'aaa'])
+ call add(tl, [2, '^\(.\{-}b\)\@>.', ' abcbd', ' abc', ' ab'])
+ call add(tl, [2, '\(.\{-}\)\(\)\@>$', 'abc', 'abc', 'abc', ''])
+ " TODO: BT engine does not restore submatch after failure
+ call add(tl, [1, '\(a*\)\@>a\|a\+', 'aaaa', 'aaaa'])
+
+ " "\_" prepended negated collection matches EOL
+ call add(tl, [2, '\_[^8-9]\+', "asfi\n9888", "asfi\n"])
+ call add(tl, [2, '\_[^a]\+', "asfi\n9888", "sfi\n9888"])
+
+ " Requiring lots of states.
+ call add(tl, [2, '[0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}', " 12345678-1234-1234-1234-123456789012 ", "12345678-1234-1234-1234-123456789012", "1234-"])
+
+ " Skip adding state twice
+ call add(tl, [2, '^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=', "#if FOO", "#if", ' FOO'])
+
+ " Test \%V atom
+ call add(tl, [2, '\%>70vGesamt', 'Jean-Michel Charlier & Victor Hubinon\Gesamtausgabe [Salleck] Buck Danny {Jean-Michel Charlier & Victor Hubinon}\Gesamtausgabe', 'Gesamt'])
+
+ " Run the tests
+ for t in tl
+ let re = t[0]
+ let pat = t[1]
+ let text = t[2]
+ let matchidx = 3
+ for engine in [0, 1, 2]
+ if engine == 2 && re == 0 || engine == 1 && re == 1
+ continue
+ endif
+ let &regexpengine = engine
+ try
+ let l = matchlist(text, pat)
+ catch
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", caused an exception: \"'
+ \ . v:exception . '\"')
+ endtry
+ " check the match itself
+ if len(l) == 0 && len(t) > matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", did not match, expected: \"'
+ \ . t[matchidx] . '\"')
+ elseif len(l) > 0 && len(t) == matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", match: \"' . l[0]
+ \ . '\", expected no match')
+ elseif len(t) > matchidx && l[0] != t[matchidx]
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", match: \"' . l[0]
+ \ . '\", expected: \"' . t[matchidx] . '\"')
+ else
+ " Test passed
+ endif
+
+ " check all the nine submatches
+ if len(l) > 0
+ for i in range(1, 9)
+ if len(t) <= matchidx + i
+ let e = ''
+ else
+ let e = t[matchidx + i]
+ endif
+ if l[i] != e
+ call assert_report('Error ' . engine . ': pat: \"' . pat
+ \ . '\", text: \"' . text . '\", submatch ' . i . ': \"'
+ \ . l[i] . '\", expected: \"' . e . '\"')
+ endif
+ endfor
+ unlet i
+ endif
+ endfor
+ endfor
+
+ unlet t tl e l
+endfunc
+
+" Tests for multi-line regexp patterns without multi-byte support.
+func Test_regexp_multiline_pat()
+ let tl = []
+
+ " back references
+ call add(tl, [2, '^.\(.\).\_..\1.', ['aaa', 'aaa', 'b'], ['XX', 'b']])
+ call add(tl, [2, '\v.*\/(.*)\n.*\/\1$', ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', './Dir1/Dir2/file1.txt', './OtherDir1/OtherDir2/file1.txt'], ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', 'XX']])
+
+ " line breaks
+ call add(tl, [2, '\S.*\nx', ['abc', 'def', 'ghi', 'xjk', 'lmn'], ['abc', 'def', 'XXjk', 'lmn']])
+
+ " Check that \_[0-9] matching EOL does not break a following \>
+ call add(tl, [2, '\<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>', ['', 'localnet/192.168.0.1', ''], ['', 'localnet/XX', '']])
+
+ " Check a pattern with a line break and ^ and $
+ call add(tl, [2, 'a\n^b$\n^c', ['a', 'b', 'c'], ['XX']])
+
+ call add(tl, [2, '\(^.\+\n\)\1', [' dog', ' dog', 'asdf'], ['XXasdf']])
+
+ " Run the multi-line tests
+ for t in tl
+ let re = t[0]
+ let pat = t[1]
+ let before = t[2]
+ let after = t[3]
+ for engine in [0, 1, 2]
+ if engine == 2 && re == 0 || engine == 1 && re ==1
+ continue
+ endif
+ let &regexpengine = engine
+ new
+ call setline(1, before)
+ exe '%s/' . pat . '/XX/'
+ let result = getline(1, '$')
+ q!
+ if result != after
+ call assert_report('Error: pat: \"' . pat . '\", text: \"'
+ \ . string(before) . '\", expected: \"' . string(after)
+ \ . '\", got: \"' . string(result) . '\"')
+ else
+ " Test passed
+ endif
+ endfor
+ endfor
+ unlet t tl
+endfunc
+
+" Check that using a pattern on two lines doesn't get messed up by using
+" matchstr() with \ze in between.
+func Test_matchstr_with_ze()
+ new
+ call append(0, ['Substitute here:', '<T="">Ta 5</Title>',
+ \ '<T="">Ac 7</Title>'])
+ call cursor(1, 1)
+ set re=0
+
+ .+1,.+2s/""/\='"' . matchstr(getline("."), '\d\+\ze<') . '"'
+ call assert_equal(['Substitute here:', '<T="5">Ta 5</Title>',
+ \ '<T="7">Ac 7</Title>', ''], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Check a pattern with a look beind crossing a line boundary
+func Test_lookbehind_across_line()
+ new
+ call append(0, ['Behind:', 'asdfasd<yyy', 'xxstart1', 'asdfasd<yy',
+ \ 'xxxstart2', 'asdfasd<yy', 'xxstart3'])
+ call cursor(1, 1)
+ call search('\(<\_[xy]\+\)\@3<=start')
+ call assert_equal([0, 7, 3, 0], getpos('.'))
+ bwipe!
+endfunc
+
+" Check matching Visual area
+func Test_matching_visual_area()
+ new
+ call append(0, ['Visual:', 'thexe the thexethe', 'andaxand andaxand',
+ \ 'oooxofor foroxooo', 'oooxofor foroxooo'])
+ call cursor(1, 1)
+ exe "normal jfxvfx:s/\\%Ve/E/g\<CR>"
+ exe "normal jV:s/\\%Va/A/g\<CR>"
+ exe "normal jfx\<C-V>fxj:s/\\%Vo/O/g\<CR>"
+ call assert_equal(['Visual:', 'thexE thE thExethe', 'AndAxAnd AndAxAnd',
+ \ 'oooxOfOr fOrOxooo', 'oooxOfOr fOrOxooo', ''], getline(1, '$'))
+ bwipe!
+endfunc
+
+" Check matching marks
+func Test_matching_marks()
+ new
+ call append(0, ['', '', '', 'Marks:', 'asdfSasdfsadfEasdf', 'asdfSas',
+ \ 'dfsadfEasdf', '', '', '', '', ''])
+ call cursor(4, 1)
+ exe "normal jfSmsfEme:.-4,.+6s/.\\%>'s.*\\%<'e../here/\<CR>"
+ exe "normal jfSmsj0fEme:.-4,.+6s/.\\%>'s\\_.*\\%<'e../again/\<CR>"
+ call assert_equal(['', '', '', 'Marks:', 'asdfhereasdf', 'asdfagainasdf',
+ \ '', '', '', '', '', ''], getline(1, '$'))
+ bwipe!
+endfunc
+
+" Check patterns matching cursor position.
+func s:curpos_test()
+ new
+ call setline(1, ['ffooooo', 'boboooo', 'zoooooo', 'koooooo', 'moooooo',
+ \ "\t\t\tfoo", 'abababababababfoo', 'bababababababafoo', '********_',
+ \ ' xxxxxxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx'])
+ call setpos('.', [0, 1, 0, 0])
+ s/\%>3c.//g
+ call setpos('.', [0, 2, 4, 0])
+ s/\%#.*$//g
+ call setpos('.', [0, 3, 0, 0])
+ s/\%<3c./_/g
+ %s/\%4l\%>5c./_/g
+ %s/\%6l\%>25v./_/g
+ %s/\%>6l\%3c./!/g
+ %s/\%>7l\%12c./?/g
+ %s/\%>7l\%<9l\%>5v\%<8v./#/g
+ $s/\%(|\u.*\)\@<=[^|\t]\+$//ge
+ call assert_equal(['ffo', 'bob', '__ooooo', 'koooo__', 'moooooo',
+ \ ' f__', 'ab!babababababfoo',
+ \ 'ba!ab##abab?bafoo', '**!*****_',
+ \ ' ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx'],
+ \ getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_matching_curpos()
+ set re=0
+ call s:curpos_test()
+ set re=1
+ call s:curpos_test()
+ set re=2
+ call s:curpos_test()
+ set re&
+endfunc
+
+" Test for matching the start and end of a buffer
+func Test_start_end_of_buffer_match()
+ new
+ call setline(1, repeat(['vim edit'], 20))
+ /\%^
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ exe "normal 50%/\\%^..\<CR>"
+ call assert_equal([0, 1, 1, 0], getpos('.'))
+ exe "normal 50%/\\%$\<CR>"
+ call assert_equal([0, 20, 8, 0], getpos('.'))
+ exe "normal 6gg/..\\%$\<CR>"
+ call assert_equal([0, 20, 7, 0], getpos('.'))
+ bwipe!
+endfunc
+
+" Check for detecting error
+func Test_regexp_error()
+ set regexpengine=2
+ call assert_fails("call matchlist('x x', ' \\ze*')", 'E888:')
+ call assert_fails("call matchlist('x x', ' \\zs*')", 'E888:')
+ set re&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index 298268a994..d4f58af10a 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -1,3 +1,16 @@
+"
+" Tests for register operations
+"
+
+" This test must be executed first to check for empty and unset registers.
+func Test_aaa_empty_reg_test()
+ call assert_fails('normal @@', 'E748:')
+ call assert_fails('normal @%', 'E354:')
+ call assert_fails('normal @#', 'E354:')
+ call assert_fails('normal @!', 'E354:')
+ call assert_fails('normal @:', 'E30:')
+ call assert_fails('normal @.', 'E29:')
+endfunc
func Test_yank_shows_register()
enew
@@ -82,3 +95,76 @@ func Test_recording_esc_sequence()
let &t_F2 = save_F2
endif
endfunc
+
+" Test for executing the last used register (@)
+func Test_last_used_exec_reg()
+ " Test for the @: command
+ let a = ''
+ call feedkeys(":let a ..= 'Vim'\<CR>", 'xt')
+ normal @:
+ call assert_equal('VimVim', a)
+
+ " Test for the @= command
+ let x = ''
+ let a = ":let x ..= 'Vim'\<CR>"
+ exe "normal @=a\<CR>"
+ normal @@
+ call assert_equal('VimVim', x)
+
+ " Test for the @. command
+ let a = ''
+ call feedkeys("i:let a ..= 'Edit'\<CR>", 'xt')
+ normal @.
+ normal @@
+ call assert_equal('EditEdit', a)
+
+ enew!
+endfunc
+
+func Test_get_register()
+ enew
+ edit Xfile1
+ edit Xfile2
+ call assert_equal('Xfile2', getreg('%'))
+ call assert_equal('Xfile1', getreg('#'))
+
+ call feedkeys("iTwo\<Esc>", 'xt')
+ call assert_equal('Two', getreg('.'))
+ call assert_equal('', getreg('_'))
+ call assert_beeps('normal ":yy')
+ call assert_beeps('normal "%yy')
+ call assert_beeps('normal ".yy')
+
+ call assert_equal('', getreg("\<C-F>"))
+ call assert_equal('', getreg("\<C-W>"))
+ call assert_equal('', getreg("\<C-L>"))
+
+ call assert_equal('', getregtype('!'))
+
+ enew!
+endfunc
+
+func Test_set_register()
+ call assert_fails("call setreg('#', 200)", 'E86:')
+
+ edit Xfile_alt_1
+ let b1 = bufnr('')
+ edit Xfile_alt_2
+ let b2 = bufnr('')
+ edit Xfile_alt_3
+ let b3 = bufnr('')
+ call setreg('#', 'alt_1')
+ call assert_equal('Xfile_alt_1', getreg('#'))
+ call setreg('#', b2)
+ call assert_equal('Xfile_alt_2', getreg('#'))
+
+ let ab = 'regwrite'
+ call setreg('=', '')
+ call setreg('=', 'a', 'a')
+ call setreg('=', 'b', 'a')
+ call assert_equal('regwrite', getreg('='))
+
+ enew!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 5d4c2a015f..68eb311e3c 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -57,7 +57,7 @@ func Test_search_cmdline()
call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
call assert_equal(' 8 them', getline('.'))
:1
- " eigth match
+ " eighth match
call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
call assert_equal(' 9 these', getline('.'))
:1
@@ -99,7 +99,7 @@ func Test_search_cmdline()
call feedkeys("/the".repeat("\<C-G>", 6)."\<cr>", 'tx')
call assert_equal(' 8 them', getline('.'))
:1
- " eigth match
+ " eighth match
call feedkeys("/the".repeat("\<C-G>", 7)."\<cr>", 'tx')
call assert_equal(' 9 these', getline('.'))
:1
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index e49b5542fa..e2016d7927 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -283,9 +283,9 @@ func Test_zz_affix()
\ ])
call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7)
- call RunGoodBad("meea1 meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
+ call RunGoodBad("meea1 meezero meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail",
\ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar",
- \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "prebar", "prebarmeat", "tail"],
+ \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "meezero", "prebar", "prebarmeat", "tail"],
\ [
\ ["bad", ["bar", "lead", "tail"]],
\ ["mee", ["meea1", "meea\xE9", "bar"]],
@@ -320,6 +320,19 @@ func Test_zz_Numbers()
\ ])
endfunc
+" Affix flags
+func Test_zz_affix_flags()
+ call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10)
+ call RunGoodBad("drink drinkable drinkables drinktable drinkabletable",
+ \ "bad: drinks drinkstable drinkablestable",
+ \ ["drink", "drinkable", "drinkables", "table"],
+ \ [['bad', []],
+ \ ['drinks', ['drink']],
+ \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']],
+ \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']],
+ \ ])
+endfunc
+
function FirstSpellWord()
call feedkeys("/^start:\n", 'tx')
normal ]smm
@@ -713,6 +726,9 @@ let g:test_data_aff7 = [
\"SFX 61003 Y 1",
\"SFX 61003 0 meat .",
\"",
+ \"SFX 0 Y 1",
+ \"SFX 0 0 zero .",
+ \"",
\"SFX 391 Y 1",
\"SFX 391 0 a1 .",
\"",
@@ -724,7 +740,7 @@ let g:test_data_aff7 = [
\ ]
let g:test_data_dic7 = [
\"1234",
- \"mee/391,111,9999",
+ \"mee/0,391,111,9999",
\"bar/17,61003,123",
\"lead/2",
\"tail/123",
@@ -748,6 +764,21 @@ let g:test_data_dic9 = [
\"foo",
\"bar",
\ ]
+let g:test_data_aff10 = [
+ \"COMPOUNDRULE se",
+ \"COMPOUNDPERMITFLAG p",
+ \"",
+ \"SFX A Y 1",
+ \"SFX A 0 able/Mp .",
+ \"",
+ \"SFX M Y 1",
+ \"SFX M 0 s .",
+ \ ]
+let g:test_data_dic10 = [
+ \"1234",
+ \"drink/As",
+ \"table/e",
+ \ ]
let g:test_data_aff_sal = [
\"SET ISO8859-1",
\"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index b86340a23a..48ec777ffd 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -347,3 +347,25 @@ func Test_statusline()
set laststatus&
set splitbelow&
endfunc
+
+func Test_statusline_visual()
+ func CallWordcount()
+ call wordcount()
+ endfunc
+ new x1
+ setl statusline=count=%{CallWordcount()}
+ " buffer must not be empty
+ call setline(1, 'hello')
+
+ " window with more lines than x1
+ new x2
+ call setline(1, range(10))
+ $
+ " Visual mode in line below liast line in x1 should not give ml_get error
+ call feedkeys("\<C-V>", "xt")
+ redraw
+
+ delfunc CallWordcount
+ bwipe! x1
+ bwipe! x2
+endfunc
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index e209310a05..e94bd22cea 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -241,7 +241,7 @@ func Test_sub_cmd_3()
call Run_SubCmd_Tests(tests)
endfunc
-" Test for submatch() on :substitue.
+" Test for submatch() on :substitute.
func Test_sub_cmd_4()
set magic&
set cpo&
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index ce527a5e1d..fe98ef1ae2 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -449,7 +449,8 @@ func Test_tag_line_toolong()
call assert_report(v:exception)
catch /.*/
endtry
- call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1])
+ call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])
+
call writefile([
\ '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML'
\ ], 'Xtags')
@@ -460,10 +461,52 @@ func Test_tag_line_toolong()
call assert_report(v:exception)
catch /.*/
endtry
- call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1])
+ call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1])
+
+ " binary search works in file with long line
+ call writefile([
+ \ 'asdfasfd nowhere 16',
+ \ 'foobar Xsomewhere 3; " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567',
+ \ 'zasdfasfd nowhere 16',
+ \ ], 'Xtags')
+ call writefile([
+ \ 'one',
+ \ 'two',
+ \ 'trhee',
+ \ 'four',
+ \ ], 'Xsomewhere')
+ tag foobar
+ call assert_equal('Xsomewhere', expand('%'))
+ call assert_equal(3, getcurpos()[1])
+
call delete('Xtags')
+ call delete('Xsomewhere')
set tags&
let &verbose = old_vbs
endfunc
+func Test_tagline()
+ call writefile([
+ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo',
+ \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:3 language:Python class:Bar',
+ \], 'Xtags')
+ call writefile([
+ \ ' def provision(self, **kwargs):',
+ \ ' pass',
+ \ ' def provision(self, **kwargs):',
+ \ ' pass',
+ \], 'Xtest.py')
+
+ set tags=Xtags
+
+ 1tag provision
+ call assert_equal(line('.'), 1)
+ 2tag provision
+ call assert_equal(line('.'), 3)
+
+ call delete('Xtags')
+ call delete('Xtest.py')
+ set tags&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim
index 9194e0014d..b20c4df311 100644
--- a/src/nvim/testdir/test_textobjects.vim
+++ b/src/nvim/testdir/test_textobjects.vim
@@ -46,8 +46,18 @@ func Test_quote_selection_selection_exclusive()
new
call setline(1, "a 'bcde' f")
set selection=exclusive
+
exe "norm! fdvhi'y"
call assert_equal('bcde', @")
+
+ let @"='dummy'
+ exe "norm! $gevi'y"
+ call assert_equal('bcde', @")
+
+ let @"='dummy'
+ exe "norm! 0fbhvi'y"
+ call assert_equal('bcde', @")
+
set selection&vim
bw!
endfunc
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index 86674889ef..adcdcb1cd9 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -238,7 +238,37 @@ func Test_undojoin()
call assert_equal(['aaaa', 'bbbb', 'cccc'], getline(2, '$'))
call feedkeys("u", 'xt')
call assert_equal(['aaaa'], getline(2, '$'))
- close!
+ bwipe!
+endfunc
+
+func Test_undojoin_redo()
+ new
+ call setline(1, ['first line', 'second line'])
+ call feedkeys("ixx\<Esc>", 'xt')
+ call feedkeys(":undojoin | redo\<CR>", 'xt')
+ call assert_equal('xxfirst line', getline(1))
+ call assert_equal('second line', getline(2))
+ bwipe!
+endfunc
+
+" undojoin not allowed after undo
+func Test_undojoin_after_undo()
+ new
+ call feedkeys("ixx\<Esc>u", 'xt')
+ call assert_fails(':undojoin', 'E790:')
+ bwipe!
+endfunc
+
+" undojoin is a noop when no change yet, or when 'undolevels' is negative
+func Test_undojoin_noop()
+ new
+ call feedkeys(":undojoin\<CR>", 'xt')
+ call assert_equal([''], getline(1, '$'))
+ setlocal undolevels=-1
+ call feedkeys("ixx\<Esc>u", 'xt')
+ call feedkeys(":undojoin\<CR>", 'xt')
+ call assert_equal(['xx'], getline(1, '$'))
+ bwipe!
endfunc
func Test_undo_write()
@@ -327,6 +357,22 @@ func Test_undofile_earlier()
call delete('Xundofile')
endfunc
+func Test_wundo_errors()
+ new
+ call setline(1, 'hello')
+ call assert_fails('wundo! Xdoesnotexist/Xundofile', 'E828:')
+ bwipe!
+endfunc
+
+func Test_rundo_errors()
+ call assert_fails('rundo XfileDoesNotExist', 'E822:')
+
+ call writefile(['abc'], 'Xundofile')
+ call assert_fails('rundo Xundofile', 'E823:')
+
+ call delete('Xundofile')
+endfunc
+
" Test for undo working properly when executing commands from a register.
" Also test this in an empty buffer.
func Test_cmd_in_reg_undo()
@@ -343,6 +389,24 @@ func Test_cmd_in_reg_undo()
let @a = ''
endfunc
+" undo or redo are noop if there is nothing to undo or redo
+func Test_undo_redo_noop()
+ new
+ call assert_fails('undo 2', 'E830:')
+
+ message clear
+ undo
+ let messages = split(execute('message'), "\n")
+ call assert_equal('Already at oldest change', messages[-1])
+
+ message clear
+ redo
+ let messages = split(execute('message'), "\n")
+ call assert_equal('Already at newest change', messages[-1])
+
+ bwipe!
+endfunc
+
func Test_redo_empty_line()
new
exe "norm\x16r\x160"
@@ -443,3 +507,190 @@ func Test_undo_0()
bwipe!
endfunc
+
+" Tests for the undo file
+" Explicitly break changes up in undo-able pieces by setting 'undolevels'.
+func Test_undofile_2()
+ set undolevels=100 undofile
+ edit Xtestfile
+ call append(0, 'this is one line')
+ call cursor(1, 1)
+
+ " first a simple one-line change.
+ set undolevels=100
+ s/one/ONE/
+ set undolevels=100
+ write
+ bwipe!
+ edit Xtestfile
+ undo
+ call assert_equal('this is one line', getline(1))
+
+ " change in original file fails check
+ set noundofile
+ edit! Xtestfile
+ s/line/Line/
+ write
+ set undofile
+ bwipe!
+ edit Xtestfile
+ undo
+ call assert_equal('this is ONE Line', getline(1))
+
+ " add 10 lines, delete 6 lines, undo 3
+ set undofile
+ call setbufline(0, 1, ['one', 'two', 'three', 'four', 'five', 'six',
+ \ 'seven', 'eight', 'nine', 'ten'])
+ set undolevels=100
+ normal 3Gdd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ write
+ bwipe!
+ edit Xtestfile
+ normal uuu
+ call assert_equal(['one', 'two', 'six', 'seven', 'eight', 'nine', 'ten'],
+ \ getline(1, '$'))
+
+ " Test that reading the undofiles when setting undofile works
+ set noundofile undolevels=0
+ exe "normal i\n"
+ undo
+ edit! Xtestfile
+ set undofile undolevels=100
+ normal uuuuuu
+ call assert_equal(['one', 'two', 'three', 'four', 'five', 'six', 'seven',
+ \ 'eight', 'nine', 'ten'], getline(1, '$'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels&
+endfunc
+
+" Test 'undofile' using a file encrypted with 'zip' crypt method
+func Test_undofile_cryptmethod_zip()
+ throw 'skipped: Nvim does not support cryptmethod'
+ edit Xtestfile
+ set undofile cryptmethod=zip
+ call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
+ call cursor(5, 1)
+
+ set undolevels=100
+ normal kkkdd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ " encrypt the file using key 'foobar'
+ call feedkeys("foobar\nfoobar\n")
+ X
+ write!
+ bwipe!
+
+ call feedkeys("foobar\n")
+ edit Xtestfile
+ set key=
+ normal uu
+ call assert_equal(['monday', 'wednesday', 'thursday', 'friday', ''],
+ \ getline(1, '$'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels& cryptmethod&
+endfunc
+
+" Test 'undofile' using a file encrypted with 'blowfish' crypt method
+func Test_undofile_cryptmethod_blowfish()
+ throw 'skipped: Nvim does not support cryptmethod'
+ edit Xtestfile
+ set undofile cryptmethod=blowfish
+ call append(0, ['jan', 'feb', 'mar', 'apr', 'jun'])
+ call cursor(5, 1)
+
+ set undolevels=100
+ exe 'normal kk0ifoo '
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ exe 'normal ibar '
+ set undolevels=100
+ " encrypt the file using key 'foobar'
+ call feedkeys("foobar\nfoobar\n")
+ X
+ write!
+ bwipe!
+
+ call feedkeys("foobar\n")
+ edit Xtestfile
+ set key=
+ call search('bar')
+ call assert_equal('bar apr', getline('.'))
+ undo
+ call assert_equal('apr', getline('.'))
+ undo
+ call assert_equal('foo mar', getline('.'))
+ undo
+ call assert_equal('mar', getline('.'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels& cryptmethod&
+endfunc
+
+" Test 'undofile' using a file encrypted with 'blowfish2' crypt method
+func Test_undofile_cryptmethod_blowfish2()
+ throw 'skipped: Nvim does not support cryptmethod'
+ edit Xtestfile
+ set undofile cryptmethod=blowfish2
+ call append(0, ['jan', 'feb', 'mar', 'apr', 'jun'])
+ call cursor(5, 1)
+
+ set undolevels=100
+ exe 'normal kk0ifoo '
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ exe 'normal ibar '
+ set undolevels=100
+ " encrypt the file using key 'foo2bar'
+ call feedkeys("foo2bar\nfoo2bar\n")
+ X
+ write!
+ bwipe!
+
+ call feedkeys("foo2bar\n")
+ edit Xtestfile
+ set key=
+ call search('bar')
+ call assert_equal('bar apr', getline('.'))
+ normal u
+ call assert_equal('apr', getline('.'))
+ normal u
+ call assert_equal('foo mar', getline('.'))
+ normal u
+ call assert_equal('mar', getline('.'))
+
+ bwipe!
+ call delete('Xtestfile')
+ let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~'
+ call delete(ufile)
+ set undofile& undolevels& cryptmethod&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 3fcba4134e..d2f13ff072 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1284,7 +1284,7 @@ func s:DoNothing()
endfunc
func Test_script_local_func()
- set nocp viminfo+=nviminfo
+ set nocp nomore viminfo+=nviminfo
new
nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr>
diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim
index 1e6b26a057..8f992f7501 100644
--- a/src/nvim/testdir/test_virtualedit.vim
+++ b/src/nvim/testdir/test_virtualedit.vim
@@ -82,3 +82,138 @@ func Test_edit_change()
call assert_equal('x', getline(1))
bwipe!
endfunc
+
+" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword".
+" Repeating CTRL-N fixes it. (Mary Ellen Foster)
+func Test_ve_completion()
+ new
+ set completeopt&vim
+ set virtualedit=all
+ exe "normal ikeyword keyw\<Esc>C\<C-N>"
+ call assert_equal('keyword keyword', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Using "C" then then <CR> moves the last remaining character to the next
+" line. (Mary Ellen Foster)
+func Test_ve_del_to_eol()
+ new
+ set virtualedit=all
+ call append(0, 'all your base are belong to us')
+ call search('are', 'w')
+ exe "normal C\<CR>are belong to vim"
+ call assert_equal(['all your base ', 'are belong to vim'], getline(1, 2))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" When past the end of a line that ends in a single character "b" skips
+" that word.
+func Test_ve_b_past_eol()
+ new
+ set virtualedit=all
+ call append(0, '1 2 3 4 5 6')
+ normal gg^$15lbC7
+ call assert_equal('1 2 3 4 5 7', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Make sure 'i', 'C', 'a', 'A' and 'D' works
+func Test_ve_ins_del()
+ new
+ set virtualedit=all
+ call append(0, ["'i'", "'C'", "'a'", "'A'", "'D'"])
+ call cursor(1, 1)
+ normal $4lix
+ call assert_equal("'i' x", getline(1))
+ call cursor(2, 1)
+ normal $4lCx
+ call assert_equal("'C' x", getline(2))
+ call cursor(3, 1)
+ normal $4lax
+ call assert_equal("'a' x", getline(3))
+ call cursor(4, 1)
+ normal $4lAx
+ call assert_equal("'A'x", getline(4))
+ call cursor(5, 1)
+ normal $4lDix
+ call assert_equal("'D' x", getline(5))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yank bug reported by Mark Waggoner.
+func Test_yank_block()
+ new
+ set virtualedit=block
+ call append(0, repeat(['this is a test'], 3))
+ exe "normal gg^2w\<C-V>3jy"
+ call assert_equal("a\na\na\n ", @")
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test "r" beyond the end of the line
+func Test_replace_after_eol()
+ new
+ set virtualedit=all
+ call append(0, '"r"')
+ normal gg$5lrxa
+ call assert_equal('"r" x', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test "r" on a tab
+" Note that for this test, 'ts' must be 8 (the default).
+func Test_replace_on_tab()
+ new
+ set virtualedit=all
+ call append(0, "'r'\t")
+ normal gg^5lrxAy
+ call assert_equal("'r' x y", getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test to make sure 'x' can delete control characters
+func Test_ve_del_ctrl_chars()
+ new
+ set virtualedit=all
+ call append(0, "a\<C-V>b\<CR>sd")
+ set display=uhex
+ normal gg^xxxxxxi[text]
+ set display=
+ call assert_equal('[text]', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for ^Y/^E due to bad w_virtcol value, reported by
+" Roy <royl@netropolis.net>.
+func Test_ins_copy_char()
+ new
+ set virtualedit=all
+ call append(0, 'abcv8efi.him2kl')
+ exe "normal gg^O\<Esc>3li\<C-E>\<Esc>4li\<C-E>\<Esc>4li\<C-E> <--"
+ exe "normal j^o\<Esc>4li\<C-Y>\<Esc>4li\<C-Y>\<Esc>4li\<C-Y> <--"
+ call assert_equal(' v i m <--', getline(1))
+ call assert_equal(' 8 . 2 <--', getline(3))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" Test for yanking and pasting using the small delete register
+func Test_yank_paste_small_del_reg()
+ new
+ set virtualedit=all
+ call append(0, "foo, bar")
+ normal ggdewve"-p
+ call assert_equal(', foo', getline(1))
+ bwipe!
+ set virtualedit=
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 844bc0db40..c71378463f 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -26,7 +26,7 @@ void tinput_init(TermInput *input, Loop *loop)
{
input->loop = loop;
input->paste = 0;
- input->in_fd = 0;
+ input->in_fd = STDIN_FILENO;
input->waiting_for_bg_response = 0;
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
uv_mutex_init(&input->key_buffer_mutex);
@@ -36,7 +36,7 @@ void tinput_init(TermInput *input, Loop *loop)
// echo q | nvim -es
// ls *.md | xargs nvim
#ifdef WIN32
- if (!os_isatty(0)) {
+ if (!os_isatty(input->in_fd)) {
const HANDLE conin_handle = CreateFile("CONIN$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
@@ -46,8 +46,8 @@ void tinput_init(TermInput *input, Loop *loop)
assert(input->in_fd != -1);
}
#else
- if (!os_isatty(0) && os_isatty(2)) {
- input->in_fd = 2;
+ if (!os_isatty(input->in_fd) && os_isatty(STDERR_FILENO)) {
+ input->in_fd = STDERR_FILENO;
}
#endif
input_global_fd_init(input->in_fd);
diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c
index 14023ce2cb..03173afe07 100644
--- a/src/nvim/tui/terminfo.c
+++ b/src/nvim/tui/terminfo.c
@@ -47,9 +47,8 @@ bool terminfo_is_bsd_console(const char *term)
// like cursor-shaping. Assume that TERM=xterm is degraded. #8644
return strequal(term, "xterm") && !!os_getenv("XTERM_VERSION");
# endif
-#else
- return false;
#endif
+ return false;
}
/// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 11746441aa..e168cf079a 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -100,7 +100,7 @@ typedef struct {
bool immediate_wrap_after_last_column;
bool bce;
bool mouse_enabled;
- bool busy, is_invisible;
+ bool busy, is_invisible, want_invisible;
bool cork, overflow;
bool cursor_color_changed;
bool is_starting;
@@ -198,6 +198,7 @@ static void terminfo_start(UI *ui)
data->default_attr = false;
data->can_clear_attr = false;
data->is_invisible = true;
+ data->want_invisible = false;
data->busy = false;
data->cork = false;
data->overflow = false;
@@ -220,7 +221,7 @@ static void terminfo_start(UI *ui)
data->unibi_ext.reset_cursor_style = -1;
data->unibi_ext.get_bg = -1;
data->unibi_ext.set_underline_color = -1;
- data->out_fd = 1;
+ data->out_fd = STDOUT_FILENO;
data->out_isatty = os_isatty(data->out_fd);
const char *term = os_getenv("TERM");
@@ -1032,7 +1033,11 @@ static void tui_set_mode(UI *ui, ModeShape mode)
if (c.id != 0 && c.id < (int)kv_size(data->attrs) && ui->rgb) {
HlAttrs aep = kv_A(data->attrs, c.id);
- if (aep.rgb_ae_attr & HL_INVERSE) {
+
+ data->want_invisible = aep.hl_blend == 100;
+ if (data->want_invisible) {
+ unibi_out(ui, unibi_cursor_invisible);
+ } else if (aep.rgb_ae_attr & HL_INVERSE) {
// We interpret "inverse" as "default" (no termcode for "inverse"...).
// Hopefully the user's default cursor color is inverse.
unibi_out_ext(ui, data->unibi_ext.reset_cursor_color);
@@ -1980,10 +1985,12 @@ static void flush_buf(UI *ui)
assert(data->is_invisible);
// not busy and the cursor is invisible. Write a "cursor normal" command
// after writing the buffer.
- bufp->base = data->norm;
- bufp->len = UV_BUF_LEN(data->normlen);
- bufp++;
- data->is_invisible = data->busy;
+ if (!data->want_invisible) {
+ bufp->base = data->norm;
+ bufp->len = UV_BUF_LEN(data->normlen);
+ bufp++;
+ }
+ data->is_invisible = false;
}
uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 7d3ecfa0b8..e582d8f859 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -626,7 +626,7 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
if (covered || curgrid->blending) {
// TODO(bfredl):
// 1. check if rectangles actually overlap
- // 2. calulate subareas that can scroll.
+ // 2. calculate subareas that can scroll.
compose_debug(top, bot, left, right, dbghl_recompose, true);
for (int r = (int)(top + MAX(-rows, 0)); r < bot - MAX(rows, 0); r++) {
// TODO(bfredl): workaround for win_update() performing two scrolls in a
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 035613c7fd..539d42765d 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -91,7 +91,9 @@
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/buffer_updates.h"
+#include "nvim/pos.h" // MAXLNUM
#include "nvim/mark.h"
+#include "nvim/mark_extended.h"
#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
@@ -106,6 +108,7 @@
#include "nvim/types.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
+#include "nvim/lib/kvec.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "undo.c.generated.h"
@@ -222,9 +225,6 @@ int u_save_cursor(void)
*/
int u_save(linenr_T top, linenr_T bot)
{
- if (undo_off)
- return OK;
-
if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) {
return FAIL; /* rely on caller to do error messages */
}
@@ -243,10 +243,7 @@ int u_save(linenr_T top, linenr_T bot)
*/
int u_savesub(linenr_T lnum)
{
- if (undo_off)
- return OK;
-
- return u_savecommon(lnum - 1, lnum + 1, lnum + 1, FALSE);
+ return u_savecommon(lnum - 1, lnum + 1, lnum + 1, false);
}
/*
@@ -257,10 +254,7 @@ int u_savesub(linenr_T lnum)
*/
int u_inssub(linenr_T lnum)
{
- if (undo_off)
- return OK;
-
- return u_savecommon(lnum - 1, lnum, lnum + 1, FALSE);
+ return u_savecommon(lnum - 1, lnum, lnum + 1, false);
}
/*
@@ -272,9 +266,6 @@ int u_inssub(linenr_T lnum)
*/
int u_savedel(linenr_T lnum, long nlines)
{
- if (undo_off)
- return OK;
-
return u_savecommon(lnum - 1, lnum + nlines,
nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, FALSE);
}
@@ -384,6 +375,7 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
* up the undo info when out of memory.
*/
uhp = xmalloc(sizeof(u_header_T));
+ kv_init(uhp->uh_extmark);
#ifdef U_DEBUG
uhp->uh_magic = UH_MAGIC;
#endif
@@ -2249,10 +2241,10 @@ static void u_undoredo(int undo, bool do_buf_event)
xfree((char_u *)uep->ue_array);
}
- /* adjust marks */
+ // Adjust marks
if (oldsize != newsize) {
mark_adjust(top + 1, top + oldsize, (long)MAXLNUM,
- (long)newsize - (long)oldsize, false);
+ (long)newsize - (long)oldsize, false, kExtmarkNOOP);
if (curbuf->b_op_start.lnum > top + oldsize) {
curbuf->b_op_start.lnum += newsize - oldsize;
}
@@ -2285,6 +2277,23 @@ static void u_undoredo(int undo, bool do_buf_event)
newlist = uep;
}
+ // Adjust Extmarks
+ ExtmarkUndoObject undo_info;
+ if (undo) {
+ for (i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
+ undo_info = kv_A(curhead->uh_extmark, i);
+ extmark_apply_undo(undo_info, undo);
+ }
+ // redo
+ } else {
+ for (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
+ undo_info = kv_A(curhead->uh_extmark, i);
+ extmark_apply_undo(undo_info, undo);
+ }
+ }
+ // finish Adjusting extmarks
+
+
curhead->uh_entry = newlist;
curhead->uh_flags = new_flags;
if ((old_flags & UH_EMPTYBUF) && BUFEMPTY()) {
@@ -2828,6 +2837,8 @@ u_freeentries(
u_freeentry(uep, uep->ue_size);
}
+ kv_destroy(uhp->uh_extmark);
+
#ifdef U_DEBUG
uhp->uh_magic = 0;
#endif
@@ -2902,9 +2913,6 @@ void u_undoline(void)
colnr_T t;
char_u *oldp;
- if (undo_off)
- return;
-
if (curbuf->b_u_line_ptr == NULL
|| curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) {
beep_flush();
@@ -3022,3 +3030,34 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
return list;
}
+
+// Given the buffer, Return the undo header. If none is set, set one first.
+// NULL will be returned if e.g undolevels = -1 (undo disabled)
+u_header_T *u_force_get_undo_header(buf_T *buf)
+{
+ u_header_T *uhp = NULL;
+ if (buf->b_u_curhead != NULL) {
+ uhp = buf->b_u_curhead;
+ } else if (buf->b_u_newhead) {
+ uhp = buf->b_u_newhead;
+ }
+ // Create the first undo header for the buffer
+ if (!uhp) {
+ // Undo is normally invoked in change code, which already has swapped
+ // curbuf.
+ buf_T *save_curbuf = curbuf;
+ curbuf = buf;
+ // Args are tricky: this means replace empty range by empty range..
+ u_savecommon(0, 1, 1, true);
+ curbuf = save_curbuf;
+
+ uhp = buf->b_u_curhead;
+ if (!uhp) {
+ uhp = buf->b_u_newhead;
+ if (get_undolevel() > 0 && !uhp) {
+ abort();
+ }
+ }
+ }
+ return uhp;
+}
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index 6c7e2bba41..0fa3b415ec 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -4,6 +4,7 @@
#include <time.h> // for time_t
#include "nvim/pos.h"
+#include "nvim/mark_extended_defs.h"
#include "nvim/mark_defs.h"
typedef struct u_header u_header_T;
@@ -56,14 +57,15 @@ struct u_header {
u_entry_T *uh_getbot_entry; /* pointer to where ue_bot must be set */
pos_T uh_cursor; /* cursor position before saving */
long uh_cursor_vcol;
- int uh_flags; /* see below */
- fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */
- visualinfo_T uh_visual; /* Visual areas before undo/after redo */
- time_t uh_time; /* timestamp when the change was made */
- long uh_save_nr; /* set when the file was saved after the
- changes in this block */
+ int uh_flags; // see below
+ fmark_T uh_namedm[NMARKS]; // marks before undo/after redo
+ extmark_undo_vec_t uh_extmark; // info to move extmarks
+ visualinfo_T uh_visual; // Visual areas before undo/after redo
+ time_t uh_time; // timestamp when the change was made
+ long uh_save_nr; // set when the file was saved after the
+ // changes in this block
#ifdef U_DEBUG
- int uh_magic; /* magic number to check allocation */
+ int uh_magic; // magic number to check allocation
#endif
};
diff --git a/src/nvim/version.c b/src/nvim/version.c
index db02279fa3..4cadc9fd6c 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -68,9 +68,6 @@ NULL
// clang-format off
static const int included_patches[] = {
- 2218,
- 2207,
- 2173,
1850,
1849,
1848,
@@ -86,7 +83,7 @@ static const int included_patches[] = {
1838,
1837,
1836,
- // 1835,
+ 1835,
1834,
1833,
1832,
@@ -123,12 +120,12 @@ static const int included_patches[] = {
1801,
1800,
1799,
- // 1798,
+ 1798,
1797,
1796,
1795,
// 1794,
- // 1793,
+ 1793,
1792,
1791,
1790,
@@ -179,7 +176,7 @@ static const int included_patches[] = {
// 1745,
// 1744,
// 1743,
- // 1742,
+ 1742,
1741,
1740,
1739,
@@ -196,10 +193,10 @@ static const int included_patches[] = {
1728,
1727,
1726,
- // 1725,
+ 1725,
1724,
1723,
- // 1722,
+ 1722,
1721,
1720,
1719,
@@ -210,7 +207,7 @@ static const int included_patches[] = {
1714,
1713,
// 1712,
- // 1711,
+ 1711,
1710,
1709,
1708,
@@ -236,7 +233,7 @@ static const int included_patches[] = {
1688,
1687,
1686,
- // 1685,
+ 1685,
1684,
1683,
1682,
@@ -255,8 +252,8 @@ static const int included_patches[] = {
1669,
// 1668,
1667,
- // 1666,
- // 1665,
+ 1666,
+ 1665,
1664,
1663,
1662,
@@ -297,12 +294,12 @@ static const int included_patches[] = {
1627,
1626,
1625,
- // 1624,
+ 1624,
1623,
1622,
1621,
1620,
- // 1619,
+ 1619,
1618,
// 1617,
// 1616,
@@ -339,7 +336,7 @@ static const int included_patches[] = {
1585,
1584,
1583,
- // 1582,
+ 1582,
1581,
1580,
1579,
@@ -429,7 +426,7 @@ static const int included_patches[] = {
// 1495,
1494,
1493,
- // 1492,
+ 1492,
// 1491,
1490,
1489,
@@ -470,9 +467,9 @@ static const int included_patches[] = {
// 1454,
1453,
1452,
- // 1451,
+ 1451,
1450,
- // 1449,
+ 1449,
1448,
1447,
1446,
@@ -516,7 +513,7 @@ static const int included_patches[] = {
1408,
1407,
1406,
- // 1405,
+ 1405,
1404,
1403,
1402,
@@ -745,7 +742,7 @@ static const int included_patches[] = {
1179,
1178,
1177,
- // 1176,
+ 1176,
1175,
1174,
1173,
@@ -987,7 +984,7 @@ static const int included_patches[] = {
937,
936,
935,
- // 934,
+ 934,
933,
932,
931,
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 0531ad1938..76fc36607c 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -633,6 +633,12 @@ void win_set_minimal_style(win_T *wp)
xfree(wp->w_p_scl);
wp->w_p_scl = (char_u *)xstrdup("auto");
}
+
+ // colorcolumn: cleared
+ if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
+ xfree(wp->w_p_cc);
+ wp->w_p_cc = (char_u *)xstrdup("");
+ }
}
void win_config_float(win_T *wp, FloatConfig fconfig)
@@ -2418,6 +2424,7 @@ int win_close(win_T *win, bool free_buf)
bool help_window = false;
tabpage_T *prev_curtab = curtab;
frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent;
+ const bool had_diffmode = win->w_p_diff;
if (last_window() && !win->w_floating) {
EMSG(_("E444: Cannot close last window"));
@@ -2642,6 +2649,22 @@ int win_close(win_T *win, bool free_buf)
if (help_window)
restore_snapshot(SNAP_HELP_IDX, close_curwin);
+ // If the window had 'diff' set and now there is only one window left in
+ // the tab page with 'diff' set, and "closeoff" is in 'diffopt', then
+ // execute ":diffoff!".
+ if (diffopt_closeoff() && had_diffmode && curtab == prev_curtab) {
+ int diffcount = 0;
+
+ FOR_ALL_WINDOWS_IN_TAB(dwin, curtab) {
+ if (dwin->w_p_diff) {
+ diffcount++;
+ }
+ }
+ if (diffcount == 1) {
+ do_cmdline_cmd("diffoff!");
+ }
+ }
+
curwin->w_pos_changed = true;
redraw_all_later(NOT_VALID);
return OK;
@@ -4349,9 +4372,10 @@ static void win_goto_hor(bool left, long count)
}
}
-/*
- * Make window "wp" the current window.
- */
+/// Make window `wp` the current window.
+///
+/// @warning Autocmds may close the window immediately, so caller must check
+/// win_valid(wp).
void win_enter(win_T *wp, bool undo_sync)
{
win_enter_ext(wp, undo_sync, false, false, true, true);