aboutsummaryrefslogtreecommitdiff
path: root/src/nvim
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim')
-rw-r--r--src/nvim/CMakeLists.txt20
-rw-r--r--src/nvim/api/buffer.c591
-rw-r--r--src/nvim/api/private/helpers.c77
-rw-r--r--src/nvim/api/ui_events.in.h10
-rw-r--r--src/nvim/api/vim.c163
-rw-r--r--src/nvim/ascii.h2
-rw-r--r--src/nvim/auevents.lua3
-rw-r--r--src/nvim/buffer.c24
-rw-r--r--src/nvim/buffer_defs.h15
-rw-r--r--src/nvim/buffer_updates.c42
-rw-r--r--src/nvim/buffer_updates.h1
-rw-r--r--src/nvim/change.c32
-rw-r--r--src/nvim/charset.c46
-rw-r--r--src/nvim/cursor_shape.c41
-rw-r--r--src/nvim/digraph.c1
-rw-r--r--src/nvim/edit.c122
-rw-r--r--src/nvim/eval.c185
-rw-r--r--src/nvim/eval.lua7
-rw-r--r--src/nvim/eval/decode.c2
-rw-r--r--src/nvim/eval/funcs.c186
-rw-r--r--src/nvim/eval/userfunc.c214
-rw-r--r--src/nvim/ex_cmds.c72
-rw-r--r--src/nvim/ex_cmds.lua22
-rw-r--r--src/nvim/ex_cmds2.c67
-rw-r--r--src/nvim/ex_cmds_defs.h4
-rw-r--r--src/nvim/ex_docmd.c879
-rw-r--r--src/nvim/ex_eval.c55
-rw-r--r--src/nvim/ex_getln.c665
-rw-r--r--src/nvim/ex_session.c9
-rw-r--r--src/nvim/extmark.c503
-rw-r--r--src/nvim/extmark.h26
-rw-r--r--src/nvim/extmark_defs.h32
-rw-r--r--src/nvim/fileio.c47
-rw-r--r--src/nvim/fold.c326
-rw-r--r--src/nvim/fold.h1
-rw-r--r--src/nvim/generators/gen_ex_cmds.lua28
-rw-r--r--src/nvim/generators/gen_options.lua9
-rw-r--r--src/nvim/getchar.c28
-rw-r--r--src/nvim/globals.h12
-rw-r--r--src/nvim/hashtab.h1
-rw-r--r--src/nvim/indent.c11
-rw-r--r--src/nvim/keymap.c13
-rw-r--r--src/nvim/log.h15
-rw-r--r--src/nvim/lua/converter.c7
-rw-r--r--src/nvim/lua/executor.c125
-rw-r--r--src/nvim/lua/executor.h9
-rw-r--r--src/nvim/lua/treesitter.c231
-rw-r--r--src/nvim/lua/vim.lua56
-rw-r--r--src/nvim/macros.h25
-rw-r--r--src/nvim/main.c4
-rw-r--r--src/nvim/map.c3
-rw-r--r--src/nvim/map.h1
-rw-r--r--src/nvim/marktree.h1
-rw-r--r--src/nvim/mbyte.c144
-rw-r--r--src/nvim/memline.c114
-rw-r--r--src/nvim/memline_defs.h2
-rw-r--r--src/nvim/message.c16
-rw-r--r--src/nvim/misc1.c4
-rw-r--r--src/nvim/move.c5
-rw-r--r--src/nvim/normal.c92
-rw-r--r--src/nvim/ops.c249
-rw-r--r--src/nvim/option.c94
-rw-r--r--src/nvim/option_defs.h10
-rw-r--r--src/nvim/options.lua31
-rw-r--r--src/nvim/os/env.c4
-rw-r--r--src/nvim/os/lang.c16
-rw-r--r--src/nvim/os/signal.c4
-rw-r--r--src/nvim/os/time.c9
-rw-r--r--src/nvim/po/check.vim1
-rw-r--r--src/nvim/po/uk.po1197
-rw-r--r--src/nvim/popupmnu.c85
-rw-r--r--src/nvim/quickfix.c524
-rw-r--r--src/nvim/regexp.c22
-rw-r--r--src/nvim/regexp_nfa.c2
-rw-r--r--src/nvim/screen.c760
-rw-r--r--src/nvim/search.c166
-rw-r--r--src/nvim/shada.c2
-rw-r--r--src/nvim/spell.c32
-rw-r--r--src/nvim/spellfile.c19
-rw-r--r--src/nvim/syntax.c2
-rw-r--r--src/nvim/testdir/check.vim88
-rw-r--r--src/nvim/testdir/runtest.vim101
-rw-r--r--src/nvim/testdir/shared.vim26
-rw-r--r--src/nvim/testdir/summarize.vim1
-rw-r--r--src/nvim/testdir/test_alot.vim2
-rw-r--r--src/nvim/testdir/test_arglist.vim10
-rw-r--r--src/nvim/testdir/test_autocmd.vim46
-rw-r--r--src/nvim/testdir/test_cjk_linebreak.vim97
-rw-r--r--src/nvim/testdir/test_cmdline.vim24
-rw-r--r--src/nvim/testdir/test_compiler.vim4
-rw-r--r--src/nvim/testdir/test_const.vim12
-rw-r--r--src/nvim/testdir/test_debugger.vim125
-rw-r--r--src/nvim/testdir/test_diffmode.vim180
-rw-r--r--src/nvim/testdir/test_display.vim155
-rw-r--r--src/nvim/testdir/test_edit.vim90
-rw-r--r--src/nvim/testdir/test_environ.vim25
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim24
-rw-r--r--src/nvim/testdir/test_expand_func.vim47
-rw-r--r--src/nvim/testdir/test_expr.vim3
-rw-r--r--src/nvim/testdir/test_filetype.vim17
-rw-r--r--src/nvim/testdir/test_filter_map.vim1
-rw-r--r--src/nvim/testdir/test_functions.vim51
-rw-r--r--src/nvim/testdir/test_gf.vim27
-rw-r--r--src/nvim/testdir/test_gn.vim27
-rw-r--r--src/nvim/testdir/test_highlight.vim14
-rw-r--r--src/nvim/testdir/test_ins_complete.vim112
-rw-r--r--src/nvim/testdir/test_interrupt.vim27
-rw-r--r--src/nvim/testdir/test_lambda.vim2
-rw-r--r--src/nvim/testdir/test_listdict.vim8
-rw-r--r--src/nvim/testdir/test_mapping.vim36
-rw-r--r--src/nvim/testdir/test_matchadd_conceal.vim75
-rw-r--r--src/nvim/testdir/test_messages.vim1
-rw-r--r--src/nvim/testdir/test_mksession.vim123
-rw-r--r--src/nvim/testdir/test_options.vim10
-rw-r--r--src/nvim/testdir/test_perl.vim311
-rw-r--r--src/nvim/testdir/test_popup.vim47
-rw-r--r--src/nvim/testdir/test_put.vim26
-rw-r--r--src/nvim/testdir/test_quickfix.vim397
-rw-r--r--src/nvim/testdir/test_ruby.vim376
-rw-r--r--src/nvim/testdir/test_search.vim490
-rw-r--r--src/nvim/testdir/test_search_stat.vim128
-rw-r--r--src/nvim/testdir/test_spell.vim42
-rw-r--r--src/nvim/testdir/test_startup.vim26
-rw-r--r--src/nvim/testdir/test_statusline.vim19
-rw-r--r--src/nvim/testdir/test_swap.vim60
-rw-r--r--src/nvim/testdir/test_syntax.vim102
-rw-r--r--src/nvim/testdir/test_tagjump.vim34
-rw-r--r--src/nvim/testdir/test_textformat.vim20
-rw-r--r--src/nvim/testdir/test_textobjects.vim30
-rw-r--r--src/nvim/testdir/test_timers.vim42
-rw-r--r--src/nvim/testdir/test_undo.vim39
-rw-r--r--src/nvim/testdir/test_usercommands.vim229
-rw-r--r--src/nvim/testdir/test_version.vim12
-rw-r--r--src/nvim/testdir/test_vimscript.vim11
-rw-r--r--src/nvim/testdir/test_visual.vim11
-rw-r--r--src/nvim/testdir/test_window_cmd.vim46
-rw-r--r--src/nvim/tui/tui.c210
-rw-r--r--src/nvim/types.h4
-rw-r--r--src/nvim/ui_bridge.c1
-rw-r--r--src/nvim/undo.c10
-rw-r--r--src/nvim/version.c32
-rw-r--r--src/nvim/viml/parser/expressions.c2
-rw-r--r--src/nvim/window.c21
143 files changed, 9422 insertions, 3624 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index c7258dde12..2d98f1a659 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -189,7 +189,11 @@ if(NOT MSVC)
endif()
# tree-sitter: inlined external project, we don't maintain it. #10124
- set_source_files_properties(${TREESITTER_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-pedantic -Wno-shadow -Wno-missing-prototypes -Wno-unused-variable")
+ set(TS_FLAGS "-Wno-conversion -Wno-pedantic -Wno-shadow -Wno-missing-prototypes -Wno-unused-variable")
+ if(HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
+ set(TS_FLAGS "${TS_FLAGS} -Wno-implicit-fallthrough")
+ endif()
+ set_source_files_properties(${TREESITTER_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} ${TS_FLAGS}")
endif()
if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$")
@@ -620,9 +624,19 @@ if(CLANG_ASAN_UBSAN)
message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.")
check_c_compiler_flag(-fno-sanitize-recover=all SANITIZE_RECOVER_ALL)
if(SANITIZE_RECOVER_ALL)
- set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+
+ if(TRAVIS_CI_BUILD)
+ # Try to recover from all sanitize issues so we get reports about all failures
+ set(SANITIZE_RECOVER -fsanitize-recover=all) # Clang 3.6+
+ else()
+ set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+
+ endif()
else()
- set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5-
+ if(TRAVIS_CI_BUILD)
+ # Try to recover from all sanitize issues so we get reports about all failures
+ set(SANITIZE_RECOVER -fsanitize-recover) # Clang 3.5-
+ else()
+ set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5-
+ endif()
endif()
set_property(TARGET nvim APPEND PROPERTY COMPILE_DEFINITIONS EXITFREE)
set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS ${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 8e61976c4b..cad4c8314f 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -7,6 +7,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>
+
#include <lauxlib.h>
#include "nvim/api/buffer.h"
@@ -175,8 +176,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
}
cb.on_lines = v->data.luaref;
v->data.luaref = LUA_NOREF;
- } else if (is_lua && strequal("_on_bytes", k.data)) {
- // NB: undocumented, untested and incomplete interface!
+ } else if (is_lua && strequal("on_bytes", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
goto error;
@@ -213,10 +213,10 @@ Boolean nvim_buf_attach(uint64_t channel_id,
error:
// TODO(bfredl): ASAN build should check that the ref table is empty?
- executor_free_luaref(cb.on_lines);
- executor_free_luaref(cb.on_bytes);
- executor_free_luaref(cb.on_changedtick);
- executor_free_luaref(cb.on_detach);
+ api_free_luaref(cb.on_lines);
+ api_free_luaref(cb.on_bytes);
+ api_free_luaref(cb.on_changedtick);
+ api_free_luaref(cb.on_detach);
return false;
}
@@ -245,78 +245,6 @@ Boolean nvim_buf_detach(uint64_t channel_id,
return true;
}
-static void buf_clear_luahl(buf_T *buf, bool force)
-{
- if (buf->b_luahl || force) {
- executor_free_luaref(buf->b_luahl_start);
- executor_free_luaref(buf->b_luahl_window);
- executor_free_luaref(buf->b_luahl_line);
- executor_free_luaref(buf->b_luahl_end);
- }
- buf->b_luahl_start = LUA_NOREF;
- buf->b_luahl_window = LUA_NOREF;
- buf->b_luahl_line = LUA_NOREF;
- buf->b_luahl_end = LUA_NOREF;
-}
-
-/// Unstabilized interface for defining syntax hl in lua.
-///
-/// This is not yet safe for general use, lua callbacks will need to
-/// be restricted, like textlock and probably other stuff.
-///
-/// The API on_line/nvim__put_attr is quite raw and not intended to be the
-/// final shape. Ideally this should operate on chunks larger than a single
-/// line to reduce interpreter overhead, and generate annotation objects
-/// (bufhl/virttext) on the fly but using the same representation.
-void nvim__buf_set_luahl(uint64_t channel_id, Buffer buffer,
- DictionaryOf(LuaRef) opts, Error *err)
- FUNC_API_LUA_ONLY
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return;
- }
-
- redraw_buf_later(buf, NOT_VALID);
- buf_clear_luahl(buf, false);
-
- for (size_t i = 0; i < opts.size; i++) {
- String k = opts.items[i].key;
- Object *v = &opts.items[i].value;
- if (strequal("on_start", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- buf->b_luahl_start = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else if (strequal("on_window", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- buf->b_luahl_window = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else if (strequal("on_line", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- buf->b_luahl_line = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- goto error;
- }
- }
- buf->b_luahl = true;
- return;
-error:
- buf_clear_luahl(buf, true);
- buf->b_luahl = false;
-}
-
void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last,
Error *err)
FUNC_API_LUA_ONLY
@@ -1025,6 +953,53 @@ Boolean nvim_buf_is_loaded(Buffer buffer)
return buf && buf->b_ml.ml_mfp != NULL;
}
+/// Deletes the buffer. See |:bwipeout|
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param opts Optional parameters. Keys:
+/// - force: Force deletion and ignore unsaved changes.
+/// - unload: Unloaded only, do not delete. See |:bunload|
+void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (ERROR_SET(err)) {
+ return;
+ }
+
+ bool force = false;
+ bool unload = false;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object v = opts.items[i].value;
+ if (strequal("force", k.data)) {
+ force = api_coerce_to_bool(v, "force", false, err);
+ } else if (strequal("unload", k.data)) {
+ unload = api_coerce_to_bool(v, "unload", false, err);
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return;
+ }
+ }
+
+ if (ERROR_SET(err)) {
+ return;
+ }
+
+ int result = do_buffer(
+ unload ? DOBUF_UNLOAD : DOBUF_WIPE,
+ DOBUF_FIRST,
+ FORWARD,
+ buf->handle,
+ force);
+
+ if (result == FAIL) {
+ api_set_error(err, kErrorTypeException, "Failed to unload buffer.");
+ return;
+ }
+}
+
/// Checks if a buffer is valid.
///
/// @note Even if a buffer is valid it may have been unloaded. See |api-buffer|
@@ -1108,15 +1083,67 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
+{
+ Array rv = ARRAY_DICT_INIT;
+ if (id) {
+ ADD(rv, INTEGER_OBJ((Integer)extmark.mark_id));
+ }
+ ADD(rv, INTEGER_OBJ(extmark.row));
+ ADD(rv, INTEGER_OBJ(extmark.col));
+
+ if (add_dict) {
+ Dictionary dict = ARRAY_DICT_INIT;
+
+ if (extmark.end_row >= 0) {
+ PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row));
+ PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col));
+ }
+
+ if (extmark.decor) {
+ Decoration *decor = extmark.decor;
+ if (decor->hl_id) {
+ String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
+ PUT(dict, "hl_group", STRING_OBJ(name));
+ }
+ if (kv_size(decor->virt_text)) {
+ Array chunks = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < decor->virt_text.size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &decor->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));
+ }
+ PUT(dict, "virt_text", ARRAY_OBJ(chunks));
+ }
+ }
+
+ if (dict.size) {
+ ADD(rv, DICTIONARY_OBJ(dict));
+ }
+ }
+
+ 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 opts Optional parameters. Keys:
+/// - limit: Maximum number of marks to return
+/// - details Whether to include the details dict
/// @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)
+ Integer id, Dictionary opts,
+ Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -1132,13 +1159,31 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
+ bool details = false;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("details", k.data)) {
+ if (v->type == kObjectTypeBoolean) {
+ details = v->data.boolean;
+ } else if (v->type == kObjectTypeInteger) {
+ details = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ return rv;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return rv;
+ }
+ }
+
+
ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
if (extmark.row < 0) {
return rv;
}
- ADD(rv, INTEGER_OBJ((Integer)extmark.row));
- ADD(rv, INTEGER_OBJ((Integer)extmark.col));
- return rv;
+ return extmark_to_array(extmark, false, (bool)details);
}
/// Gets extmarks in "traversal order" from a |charwise| region defined by
@@ -1181,10 +1226,12 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
/// (whose position defines the bound)
/// @param opts Optional parameters. Keys:
/// - limit: Maximum number of marks to return
+/// - details Whether to include the details dict
/// @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)
+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;
@@ -1198,7 +1245,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
+
Integer limit = -1;
+ bool details = false;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
@@ -1209,6 +1258,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
return rv;
}
limit = v->data.integer;
+ } else if (strequal("details", k.data)) {
+ if (v->type == kObjectTypeBoolean) {
+ details = v->data.boolean;
+ } else if (v->type == kObjectTypeInteger) {
+ details = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ return rv;
+ }
} else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
return rv;
@@ -1241,16 +1299,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
}
- ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, u_row,
- u_col, (int64_t)limit, reverse);
+ ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col,
+ u_row, u_col, (int64_t)limit, reverse);
for (size_t i = 0; i < kv_size(marks); i++) {
- Array mark = ARRAY_DICT_INIT;
- ExtmarkInfo extmark = kv_A(marks, i);
- ADD(mark, INTEGER_OBJ((Integer)extmark.mark_id));
- ADD(mark, INTEGER_OBJ(extmark.row));
- ADD(mark, INTEGER_OBJ(extmark.col));
- ADD(rv, ARRAY_OBJ(mark));
+ ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, (bool)details)));
}
kv_destroy(marks);
@@ -1260,27 +1313,40 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
/// 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.
+/// 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.)
///
+/// Using the optional arguments, it is possible to use this to highlight
+/// a range of text, and also to associate virtual text to the mark.
+///
/// @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 opts Optional parameters.
+/// - id : id of the extmark to edit.
+/// - end_line : ending line of the mark, 0-based inclusive.
+/// - end_col : ending col of the mark, 0-based inclusive.
+/// - hl_group : name of the highlight group used to highlight
+/// this mark.
+/// - virt_text : virtual text to link to this mark.
+/// - ephemeral : for use with |nvim_set_decoration_provider|
+/// callbacks. The mark will only be used for the current
+/// redraw cycle, and not be permantently stored in the
+/// buffer.
/// @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 nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
Integer line, Integer col,
Dictionary opts, Error *err)
FUNC_API_SINCE(7)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
+ api_set_error(err, kErrorTypeValidation, "Invalid buffer id");
return 0;
}
@@ -1289,11 +1355,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer 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");
@@ -1309,18 +1370,138 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
return 0;
}
- uint64_t id_num;
- if (id >= 0) {
- id_num = (uint64_t)id;
+ bool ephemeral = false;
+
+ uint64_t id = 0;
+ int line2 = -1, hl_id = 0;
+ colnr_T col2 = 0;
+ VirtText virt_text = KV_INITIAL_VALUE;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("id", k.data)) {
+ if (v->type != kObjectTypeInteger || v->data.integer <= 0) {
+ api_set_error(err, kErrorTypeValidation,
+ "id is not a positive integer");
+ goto error;
+ }
+
+ id = (uint64_t)v->data.integer;
+ } else if (strequal("end_line", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_line is not an integer");
+ goto error;
+ }
+ if (v->data.integer < 0 || v->data.integer > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_line value outside range");
+ goto error;
+ }
+
+ line2 = (int)v->data.integer;
+ } else if (strequal("end_col", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_col is not an integer");
+ goto error;
+ }
+ if (v->data.integer < 0 || v->data.integer > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_col value outside range");
+ goto error;
+ }
+
+ col2 = (colnr_T)v->data.integer;
+ } else if (strequal("hl_group", k.data)) {
+ String hl_group;
+ switch (v->type) {
+ case kObjectTypeString:
+ hl_group = v->data.string;
+ hl_id = syn_check_group(
+ (char_u *)(hl_group.data),
+ (int)hl_group.size);
+ break;
+ case kObjectTypeInteger:
+ hl_id = (int)v->data.integer;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation,
+ "hl_group is not valid.");
+ goto error;
+ }
+ } else if (strequal("virt_text", k.data)) {
+ if (v->type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text is not an Array");
+ goto error;
+ }
+ virt_text = parse_virt_text(v->data.array, err);
+ if (ERROR_SET(err)) {
+ goto error;
+ }
+ } else if (strequal("ephemeral", k.data)) {
+ ephemeral = api_coerce_to_bool(*v, "ephemeral", false, err);
+ if (ERROR_SET(err)) {
+ goto error;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ goto error;
+ }
+ }
+
+ if (col2 >= 0) {
+ if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
+ len = STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false));
+ } else if (line2 == buf->b_ml.ml_line_count) {
+ // We are trying to add an extmark past final newline
+ len = 0;
+ } else {
+ // reuse len from before
+ line2 = (int)line;
+ }
+ if (col2 > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ goto error;
+ }
+ } else if (line2 >= 0) {
+ col2 = 0;
+ }
+
+ // TODO(bfredl): synergize these two branches even more
+ if (ephemeral && redrawn_win && redrawn_win->w_buffer == buf) {
+ int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0;
+ VirtText *vt_allocated = NULL;
+ if (kv_size(virt_text)) {
+ vt_allocated = xmalloc(sizeof *vt_allocated);
+ *vt_allocated = virt_text;
+ }
+ decorations_add_ephemeral(attr_id, (int)line, (colnr_T)col,
+ (int)line2, (colnr_T)col2, vt_allocated);
} else {
- api_set_error(err, kErrorTypeValidation, "Invalid mark id");
- return 0;
+ if (ephemeral) {
+ api_set_error(err, kErrorTypeException, "not yet implemented");
+ goto error;
+ }
+ Decoration *decor = NULL;
+ if (kv_size(virt_text)) {
+ decor = xcalloc(1, sizeof(*decor));
+ decor->hl_id = hl_id;
+ decor->virt_text = virt_text;
+ } else if (hl_id) {
+ decor = decoration_hl(hl_id);
+ }
+
+ id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col,
+ line2, col2, decor, kExtmarkNoUndo);
}
- id_num = extmark_set(buf, (uint64_t)ns_id, id_num,
- (int)line, (colnr_T)col, kExtmarkUndo);
+ return (Integer)id;
- return (Integer)id_num;
+error:
+ clear_virttext(&virt_text);
+ return 0;
}
/// Removes an extmark.
@@ -1358,17 +1539,17 @@ Boolean nvim_buf_del_extmark(Buffer buffer,
/// like signs and marks do.
///
/// Namespaces are used for batch deletion/updating of a set of highlights. To
-/// create a namespace, use |nvim_create_namespace| which returns a namespace
+/// create a namespace, use |nvim_create_namespace()| which returns a namespace
/// id. Pass it in to this function as `ns_id` to add highlights to the
/// namespace. All highlights in the same namespace can then be cleared with
-/// single call to |nvim_buf_clear_namespace|. If the highlight never will be
+/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be
/// deleted by an API call, pass `ns_id = -1`.
///
/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
/// highlight, the allocated id is then returned. If `hl_group` is the empty
/// string no highlight is added, but a new `ns_id` is still returned. This is
/// supported for backwards compatibility, new code should use
-/// |nvim_create_namespace| to create a new empty namespace.
+/// |nvim_create_namespace()| to create a new empty namespace.
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id namespace to use or -1 for ungrouped highlight
@@ -1412,9 +1593,9 @@ Integer nvim_buf_add_highlight(Buffer buffer,
return src_id;
}
- int hlg_id = 0;
+ int hl_id = 0;
if (hl_group.size > 0) {
- hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
} else {
return src_id;
}
@@ -1425,10 +1606,10 @@ Integer nvim_buf_add_highlight(Buffer buffer,
end_line++;
}
- extmark_add_decoration(buf, ns_id, hlg_id,
- (int)line, (colnr_T)col_start,
- end_line, (colnr_T)col_end,
- VIRTTEXT_EMPTY);
+ extmark_set(buf, ns_id, 0,
+ (int)line, (colnr_T)col_start,
+ end_line, (colnr_T)col_end,
+ decoration_hl(hl_id), kExtmarkNoUndo);
return src_id;
}
@@ -1470,7 +1651,7 @@ void nvim_buf_clear_namespace(Buffer buffer,
/// Clears highlights and virtual text from namespace and range of lines
///
-/// @deprecated use |nvim_buf_clear_namespace|.
+/// @deprecated use |nvim_buf_clear_namespace()|.
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace to clear, or -1 to clear all.
@@ -1488,43 +1669,6 @@ void nvim_buf_clear_highlight(Buffer buffer,
nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
}
-static VirtText parse_virt_text(Array chunks, Error *err)
-{
- VirtText virt_text = KV_INITIAL_VALUE;
- for (size_t i = 0; i < chunks.size; i++) {
- if (chunks.items[i].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
- goto free_exit;
- }
- Array chunk = chunks.items[i].data.array;
- if (chunk.size == 0 || chunk.size > 2
- || chunk.items[0].type != kObjectTypeString
- || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
- api_set_error(err, kErrorTypeValidation,
- "Chunk is not an array with one or two strings");
- goto free_exit;
- }
-
- String str = chunk.items[0].data.string;
- char *text = transstr(str.size > 0 ? str.data : ""); // allocates
-
- int hl_id = 0;
- if (chunk.size == 2) {
- String hl = chunk.items[1].data.string;
- if (hl.size > 0) {
- hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
- }
- }
- kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
- }
-
- return virt_text;
-
-free_exit:
- clear_virttext(&virt_text);
- return virt_text;
-}
-
/// Set the virtual text (annotation) for a buffer line.
///
@@ -1534,11 +1678,11 @@ free_exit:
/// begin one cell (|lcs-eol| or space) after the ordinary text.
///
/// Namespaces are used to support batch deletion/updating of virtual text.
-/// To create a namespace, use |nvim_create_namespace|. Virtual text is
-/// cleared using |nvim_buf_clear_namespace|. The same `ns_id` can be used for
-/// both virtual text and highlights added by |nvim_buf_add_highlight|, both
-/// can then be cleared with a single call to |nvim_buf_clear_namespace|. If the
-/// virtual text never will be cleared by an API call, pass `ns_id = -1`.
+/// To create a namespace, use |nvim_create_namespace()|. Virtual text is
+/// cleared using |nvim_buf_clear_namespace()|. The same `ns_id` can be used for
+/// both virtual text and highlights added by |nvim_buf_add_highlight()|, both
+/// can then be cleared with a single call to |nvim_buf_clear_namespace()|. If
+/// the virtual text never will be cleared by an API call, pass `ns_id = -1`.
///
/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
/// virtual text, the allocated id is then returned.
@@ -1592,113 +1736,49 @@ Integer nvim_buf_set_virtual_text(Buffer buffer,
return src_id;
}
- extmark_add_decoration(buf, ns_id, 0,
- (int)line, 0, -1, -1,
- virt_text);
+ Decoration *decor = xcalloc(1, sizeof(*decor));
+ decor->virt_text = virt_text;
+
+ extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, kExtmarkNoUndo);
return src_id;
}
-/// 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.
+/// call a function with buffer as temporary current buffer
///
-/// The format is exactly the same as given to nvim_buf_set_virtual_text().
+/// This temporarily switches current buffer to "buffer".
+/// If the current window already shows "buffer", the window is not switched
+/// If a window inside the current tabpage (including a float) already shows the
+/// buffer One of these windows will be set as current window temporarily.
+/// Otherwise a temporary scratch window (calleed the "autocmd window" for
+/// historical reasons) will be used.
///
-/// If there is no virtual text associated with the given line, an empty list
-/// is returned.
+/// This is useful e.g. to call vimL functions that only work with the current
+/// buffer/window currently, like |termopen()|.
///
-/// @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 line, Error *err)
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param fun Function to call inside the buffer (currently lua callable
+/// only)
+/// @param[out] err Error details, if any
+/// @return Return value of function. NB: will deepcopy lua values
+/// currently, use upvalues to send lua references in and out.
+Object nvim_buf_call(Buffer buffer, LuaRef fun, 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 (line < 0 || line >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
- return chunks;
- }
-
- VirtText *virt_text = extmark_find_virttext(buf, (int)line, 0);
-
- if (!virt_text) {
- return chunks;
- }
-
- for (size_t i = 0; i < virt_text->size; i++) {
- Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &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;
-}
-
-Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group,
- Integer start_row, Integer start_col,
- Integer end_row, Integer end_col,
- Array virt_text,
- Error *err)
+ FUNC_API_LUA_ONLY
{
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 (start_row < 0 || start_row >= MAXLNUM || end_row > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
- return 0;
- }
-
- if (start_col < 0 || start_col > MAXCOL || end_col > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Column value outside range");
- return 0;
- }
- if (end_row < 0 || end_col < 0) {
- end_row = -1;
- end_col = -1;
- }
-
- if (start_row >= buf->b_ml.ml_line_count
- || end_row >= buf->b_ml.ml_line_count) {
- // safety check, we can't add marks outside the range
- return 0;
- }
-
- int hlg_id = 0;
- if (hl_group.size > 0) {
- hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ return NIL;
}
+ try_start();
+ aco_save_T aco;
+ aucmd_prepbuf(&aco, (buf_T *)buf);
- VirtText vt = parse_virt_text(virt_text, err);
- if (ERROR_SET(err)) {
- return 0;
- }
+ Array args = ARRAY_DICT_INIT;
+ Object res = nlua_call_ref(fun, NULL, args, true, err);
- uint64_t mark_id = extmark_add_decoration(buf, (uint64_t)ns_id, hlg_id,
- (int)start_row, (colnr_T)start_col,
- (int)end_row, (colnr_T)end_col, vt);
- return (Integer)mark_id;
+ aucmd_restbuf(&aco);
+ try_end(err);
+ return res;
}
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
@@ -1721,6 +1801,7 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// NB: this should be zero at any time API functions are called,
// this exists to debug issues
PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+ PUT(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2));
u_header_T *uhp = NULL;
if (buf->b_u_curhead != NULL) {
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index f194b6b474..981d41ae6e 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -15,6 +15,8 @@
#include "nvim/lua/executor.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
+#include "nvim/charset.h"
+#include "nvim/syntax.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
#include "nvim/window.h"
@@ -1198,7 +1200,7 @@ void api_free_object(Object value)
break;
case kObjectTypeLuaRef:
- executor_free_luaref(value.data.luaref);
+ api_free_luaref(value.data.luaref);
break;
default:
@@ -1579,3 +1581,76 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
return false;
}
}
+
+VirtText parse_virt_text(Array chunks, Error *err)
+{
+ VirtText virt_text = KV_INITIAL_VALUE;
+ for (size_t i = 0; i < chunks.size; i++) {
+ if (chunks.items[i].type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
+ goto free_exit;
+ }
+ Array chunk = chunks.items[i].data.array;
+ if (chunk.size == 0 || chunk.size > 2
+ || chunk.items[0].type != kObjectTypeString
+ || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
+ api_set_error(err, kErrorTypeValidation,
+ "Chunk is not an array with one or two strings");
+ goto free_exit;
+ }
+
+ String str = chunk.items[0].data.string;
+ char *text = transstr(str.size > 0 ? str.data : ""); // allocates
+
+ int hl_id = 0;
+ if (chunk.size == 2) {
+ String hl = chunk.items[1].data.string;
+ if (hl.size > 0) {
+ hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
+ }
+ }
+ kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ }
+
+ return virt_text;
+
+free_exit:
+ clear_virttext(&virt_text);
+ return virt_text;
+}
+
+/// Force obj to bool.
+/// If it fails, returns false and sets err
+/// @param obj The object to coerce to a boolean
+/// @param what The name of the object, used for error message
+/// @param nil_value What to return if the type is nil.
+/// @param err Set if there was an error in converting to a bool
+bool api_coerce_to_bool(
+ Object obj,
+ const char *what,
+ bool nil_value,
+ Error *err)
+{
+ if (obj.type == kObjectTypeBoolean) {
+ return obj.data.boolean;
+ } else if (obj.type == kObjectTypeInteger) {
+ return obj.data.integer; // C semantics: non-zero int is true
+ } else if (obj.type == kObjectTypeNil) {
+ return nil_value; // caller decides what NIL (missing retval in lua) means
+ } else {
+ api_set_error(err, kErrorTypeValidation, "%s is not an boolean", what);
+ return false;
+ }
+}
+
+const char *describe_ns(NS ns_id)
+{
+ String name;
+ handle_T id;
+ map_foreach(namespace_ids, name, id, {
+ if ((NS)id == ns_id && name.size) {
+ return name.data;
+ }
+ })
+ return "(UNKNOWN PLUGIN)";
+}
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index ab31db39e9..e934d5dc92 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -1,7 +1,7 @@
#ifndef NVIM_API_UI_EVENTS_IN_H
#define NVIM_API_UI_EVENTS_IN_H
-// This file is not compiled, just parsed for definitons
+// This file is not compiled, just parsed for definitions
#ifdef INCLUDE_GENERATED_DECLARATIONS
# error "don't include this file, include nvim/ui.h"
#endif
@@ -36,13 +36,15 @@ void set_title(String title)
FUNC_API_SINCE(3);
void set_icon(String icon)
FUNC_API_SINCE(3);
+void screenshot(String path)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL;
void option_set(String name, Object value)
FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL;
// Stop event is not exported as such, represented by EOF in the msgpack stream.
void stop(void)
FUNC_API_NOEXPORT;
-// First revison of the grid protocol, used by default
+// First revision of the grid protocol, used by default
void update_fg(Integer fg)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void update_bg(Integer bg)
@@ -66,7 +68,7 @@ void set_scroll_region(Integer top, Integer bot, Integer left, Integer right)
void scroll(Integer count)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
-// Second revison of the grid protocol, used with ext_linegrid ui option
+// Second revision of the grid protocol, used with ext_linegrid ui option
void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL;
@@ -89,7 +91,7 @@ void grid_scroll(Integer grid, Integer top, Integer bot,
void grid_destroy(Integer grid)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
-// For perfomance and simplicity, we use the dense screen representation
+// For performance and simplicity, we use the dense screen representation
// in internal code, such as compositor and TUI. The remote_ui module will
// translate this in to the public grid_line format.
void raw_line(Integer grid, Integer row, Integer startcol,
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index d6f95c7a5f..876b052a8e 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -203,10 +203,10 @@ Integer nvim_get_hl_id_by_name(String name)
/// flags. This is a blocking call, unlike |nvim_input()|.
///
/// On execution error: does not fail, but updates v:errmsg.
-//
-// If you need to input sequences like <C-o> use nvim_replace_termcodes
-// to replace the termcodes and then pass the resulting string to
-// nvim_feedkeys. You'll also want to enable escape_csi.
+///
+/// If you need to input sequences like <C-o> use |nvim_replace_termcodes| to
+/// replace the termcodes and then pass the resulting string to nvim_feedkeys.
+/// You'll also want to enable escape_csi.
///
/// Example:
/// <pre>
@@ -475,7 +475,7 @@ Object nvim_execute_lua(String code, Array args, Error *err)
FUNC_API_DEPRECATED_SINCE(7)
FUNC_API_REMOTE_ONLY
{
- return executor_exec_lua_api(code, args, err);
+ return nlua_exec(code, args, err);
}
/// Execute Lua code. Parameters (if any) are available as `...` inside the
@@ -494,7 +494,7 @@ 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);
+ return nlua_exec(code, args, err);
}
/// Calls a VimL function.
@@ -2477,7 +2477,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_exec_lua(s, a, err);
+ Object o = nlua_exec(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray) {
@@ -2523,7 +2523,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_exec_lua(s, a, err);
+ Object o = nlua_exec(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray && o.data.array.size == 0) {
@@ -2604,26 +2604,135 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
return ret;
}
-/// Set attrs in nvim__buf_set_lua_hl callbacks
-///
-/// TODO(bfredl): This is rather pedestrian. The final
-/// interface should probably be derived from a reformed
-/// bufhl/virttext interface with full support for multi-line
-/// ranges etc
-void nvim__put_attr(Integer id, Integer start_row, Integer start_col,
- Integer end_row, Integer end_col)
- FUNC_API_LUA_ONLY
+void nvim__screenshot(String path)
+ FUNC_API_FAST
{
- if (!lua_attr_active) {
- return;
- }
- if (id == 0 || syn_get_final_id((int)id) == 0) {
- return;
+ ui_call_screenshot(path);
+}
+
+static DecorationProvider *get_provider(NS ns_id, bool force)
+{
+ ssize_t i;
+ for (i = 0; i < (ssize_t)kv_size(decoration_providers); i++) {
+ DecorationProvider *item = &kv_A(decoration_providers, i);
+ if (item->ns_id == ns_id) {
+ return item;
+ } else if (item->ns_id > ns_id) {
+ break;
+ }
}
- int attr = syn_id2attr((int)id);
- if (attr == 0) {
- return;
+
+ if (!force) {
+ return NULL;
+ }
+
+ for (ssize_t j = (ssize_t)kv_size(decoration_providers)-1; j >= i; j++) {
+ // allocates if needed:
+ (void)kv_a(decoration_providers, (size_t)j+1);
+ kv_A(decoration_providers, (size_t)j+1) = kv_A(decoration_providers, j);
+ }
+ DecorationProvider *item = &kv_a(decoration_providers, (size_t)i);
+ *item = DECORATION_PROVIDER_INIT(ns_id);
+
+ return item;
+}
+
+static void clear_provider(DecorationProvider *p)
+{
+ NLUA_CLEAR_REF(p->redraw_start);
+ NLUA_CLEAR_REF(p->redraw_buf);
+ NLUA_CLEAR_REF(p->redraw_win);
+ NLUA_CLEAR_REF(p->redraw_line);
+ NLUA_CLEAR_REF(p->redraw_end);
+ p->active = false;
+}
+
+/// Set or change decoration provider for a namespace
+///
+/// This is a very general purpose interface for having lua callbacks
+/// being triggered during the redraw code.
+///
+/// The expected usage is to set extmarks for the currently
+/// redrawn buffer. |nvim_buf_set_extmark| can be called to add marks
+/// on a per-window or per-lines basis. Use the `ephemeral` key to only
+/// use the mark for the current screen redraw (the callback will be called
+/// again for the next redraw ).
+///
+/// Note: this function should not be called often. Rather, the callbacks
+/// themselves can be used to throttle unneeded callbacks. the `on_start`
+/// callback can return `false` to disable the provider until the next redraw.
+/// Similarily, return `false` in `on_win` will skip the `on_lines` calls
+/// for that window (but any extmarks set in `on_win` will still be used).
+/// A plugin managing multiple sources of decorations should ideally only set
+/// one provider, and merge the sources internally. You can use multiple `ns_id`
+/// for the extmarks set/modified inside the callback anyway.
+///
+/// Note: doing anything other than setting extmarks is considered experimental.
+/// Doing things like changing options are not expliticly forbidden, but is
+/// likely to have unexpected consequences (such as 100% CPU consumption).
+/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
+/// for the moment.
+///
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param opts Callbacks invoked during redraw:
+/// - on_start: called first on each screen redraw
+/// ["start", tick]
+/// - on_buf: called for each buffer being redrawn (before window
+/// callbacks)
+/// ["buf", bufnr, tick]
+/// - on_win: called when starting to redraw a specific window.
+/// ["win", winid, bufnr, topline, botline_guess]
+/// - on_line: called for each buffer line being redrawn. (The
+/// interation with fold lines is subject to change)
+/// ["win", winid, bufnr, row]
+/// - on_end: called at the end of a redraw cycle
+/// ["end", tick]
+void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
+ Error *err)
+ FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
+{
+ DecorationProvider *p = get_provider((NS)ns_id, true);
+ clear_provider(p);
+
+ // regardless of what happens, it seems good idea to redraw
+ redraw_later(NOT_VALID); // TODO(bfredl): too soon?
+
+ struct {
+ const char *name;
+ LuaRef *dest;
+ } cbs[] = {
+ { "on_start", &p->redraw_start },
+ { "on_buf", &p->redraw_buf },
+ { "on_win", &p->redraw_win },
+ { "on_line", &p->redraw_line },
+ { "on_end", &p->redraw_end },
+ { NULL, NULL },
+ };
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ size_t j;
+ for (j = 0; cbs[j].name; j++) {
+ if (strequal(cbs[j].name, k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation,
+ "%s is not a function", cbs[j].name);
+ goto error;
+ }
+ *(cbs[j].dest) = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ break;
+ }
+ }
+ if (!cbs[j].name) {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ goto error;
+ }
}
- decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col,
- (int)end_row, (colnr_T)end_col);
+
+ p->active = true;
+ return;
+error:
+ clear_provider(p);
}
diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h
index 31423e79af..2397af27cc 100644
--- a/src/nvim/ascii.h
+++ b/src/nvim/ascii.h
@@ -31,9 +31,7 @@
#define CSI 0x9b // Control Sequence Introducer
#define CSI_STR "\233"
#define DCS 0x90 // Device Control String
-#define DCS_STR "\033P"
#define STERM 0x9c // String Terminator
-#define STERM_STR "\033\\"
#define POUND 0xA3
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 4391d997a7..10647c01a4 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -65,7 +65,8 @@ return {
'InsertChange', -- when changing Insert/Replace mode
'InsertCharPre', -- before inserting a char
'InsertEnter', -- when entering Insert mode
- 'InsertLeave', -- when leaving Insert mode
+ 'InsertLeave', -- just after leaving Insert mode
+ 'InsertLeavePre', -- just before leaving Insert mode
'MenuPopup', -- just before popup menu is displayed
'OptionSet', -- after setting any option
'QuickFixCmdPost', -- after :make, :grep etc.
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index b3bbdce9d9..8f631ae13b 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -837,7 +837,7 @@ static void clear_wininfo(buf_T *buf)
buf->b_wininfo = wip->wi_next;
if (wip->wi_optset) {
clear_winopt(&wip->wi_opt);
- deleteFoldRecurse(&wip->wi_folds);
+ deleteFoldRecurse(buf, &wip->wi_folds);
}
xfree(wip);
}
@@ -1941,6 +1941,7 @@ void free_buf_options(buf_T *buf, int free_p_ff)
vim_regfree(buf->b_s.b_cap_prog);
buf->b_s.b_cap_prog = NULL;
clear_string_option(&buf->b_s.b_p_spl);
+ clear_string_option(&buf->b_s.b_p_spo);
clear_string_option(&buf->b_p_sua);
clear_string_option(&buf->b_p_ft);
clear_string_option(&buf->b_p_cink);
@@ -2502,7 +2503,7 @@ void buflist_setfpos(buf_T *const buf, win_T *const win,
}
if (copy_options && wip->wi_optset) {
clear_winopt(&wip->wi_opt);
- deleteFoldRecurse(&wip->wi_folds);
+ deleteFoldRecurse(buf, &wip->wi_folds);
}
}
if (lnum != 0) {
@@ -2650,7 +2651,7 @@ void buflist_list(exarg_T *eap)
int i;
garray_T buflist;
- buf_T **buflist_data = NULL, **p;
+ buf_T **buflist_data = NULL;
if (vim_strchr(eap->arg, 't')) {
ga_init(&buflist, sizeof(buf_T *), 50);
@@ -2662,13 +2663,14 @@ void buflist_list(exarg_T *eap)
qsort(buflist.ga_data, (size_t)buflist.ga_len,
sizeof(buf_T *), buf_time_compare);
- p = buflist_data = (buf_T **)buflist.ga_data;
- buf = *p;
+ buflist_data = (buf_T **)buflist.ga_data;
+ buf = *buflist_data;
}
+ buf_T **p = buflist_data;
for (;
buf != NULL && !got_int;
- buf = buflist_data
+ buf = buflist_data != NULL
? (++p < buflist_data + buflist.ga_len ? *p : NULL)
: buf->b_next) {
const bool is_terminal = buf->terminal;
@@ -5395,13 +5397,11 @@ bool buf_hide(const buf_T *const buf)
char_u *buf_spname(buf_T *buf)
{
if (bt_quickfix(buf)) {
- win_T *win;
- tabpage_T *tp;
+ win_T *win;
+ tabpage_T *tp;
- /*
- * For location list window, w_llist_ref points to the location list.
- * For quickfix window, w_llist_ref is NULL.
- */
+ // For location list window, w_llist_ref points to the location list.
+ // For quickfix window, w_llist_ref is NULL.
if (find_win_for_buf(buf, &win, &tp) && win->w_llist_ref != NULL) {
return (char_u *)_(msg_loclist);
} else {
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 550f8a5e40..8e855cb644 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -91,6 +91,7 @@ typedef struct {
#define BF_READERR 0x40 // got errors while reading the file
#define BF_DUMMY 0x80 // dummy buffer, only used internally
#define BF_PRESERVED 0x100 // ":preserve" was used
+#define BF_SYN_SET 0x200 // 'syntax' option was set
// Mask to check for flags that prevent normal writing
#define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR)
@@ -451,6 +452,7 @@ typedef struct {
regprog_T *b_cap_prog; // program for 'spellcapcheck'
char_u *b_p_spf; // 'spellfile'
char_u *b_p_spl; // 'spelllang'
+ char_u *b_p_spo; // 'spelloptions'
int b_cjk; // all CJK letters as OK
char_u b_syn_chartab[32]; // syntax iskeyword option
char_u *b_syn_isk; // iskeyword option
@@ -543,6 +545,9 @@ struct file_buffer {
long b_mod_xlines; // number of extra buffer lines inserted;
// negative when lines were deleted
wininfo_T *b_wininfo; // list of last used info for each window
+ int b_mod_tick_syn; // last display tick syntax was updated
+ int b_mod_tick_deco; // last display tick decoration providers
+ // where invoked
long b_mtime; // last change time of original file
long b_mtime_read; // last change time when reading
@@ -656,6 +661,9 @@ struct file_buffer {
char_u *b_p_com; ///< 'comments'
char_u *b_p_cms; ///< 'commentstring'
char_u *b_p_cpt; ///< 'complete'
+#ifdef BACKSLASH_IN_FILENAME
+ char_u *b_p_csl; ///< 'completeslash'
+#endif
char_u *b_p_cfu; ///< 'completefunc'
char_u *b_p_ofu; ///< 'omnifunc'
char_u *b_p_tfu; ///< 'tagfunc'
@@ -835,18 +843,13 @@ struct file_buffer {
// tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the
// deleted text.
size_t deleted_bytes;
+ size_t deleted_bytes2;
size_t deleted_codepoints;
size_t deleted_codeunits;
// The number for times the current line has been flushed in the memline.
int flush_count;
- bool b_luahl;
- LuaRef b_luahl_start;
- LuaRef b_luahl_window;
- LuaRef b_luahl_line;
- LuaRef b_luahl_end;
-
int b_diff_failed; // internal diff failed for this buffer
};
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index e6393bf02c..fc671ad9e2 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -2,6 +2,7 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include "nvim/buffer_updates.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/api/private/helpers.h"
#include "nvim/msgpack_rpc/channel.h"
@@ -157,7 +158,7 @@ void buf_updates_unregister_all(buf_T *buf)
args.items[0] = BUFFER_OBJ(buf->handle);
textlock++;
- executor_exec_lua_cb(cb.on_detach, "detach", args, false, NULL);
+ nlua_call_ref(cb.on_detach, "detach", args, false, NULL);
textlock--;
}
free_update_callbacks(cb);
@@ -265,7 +266,7 @@ void buf_updates_send_changes(buf_T *buf,
args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
}
textlock++;
- Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true, NULL);
+ Object res = nlua_call_ref(cb.on_lines, "lines", args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -281,12 +282,14 @@ void buf_updates_send_changes(buf_T *buf,
kv_size(buf->update_callbacks) = j;
}
-void buf_updates_send_splice(buf_T *buf,
- linenr_T start_line, colnr_T start_col,
- linenr_T oldextent_line, colnr_T oldextent_col,
- linenr_T newextent_line, colnr_T newextent_col)
+void buf_updates_send_splice(
+ buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte)
{
- if (!buf_updates_active(buf)) {
+ if (!buf_updates_active(buf)
+ || (old_byte == 0 && new_byte == 0)) {
return;
}
@@ -296,7 +299,7 @@ void buf_updates_send_splice(buf_T *buf,
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
if (cb.on_bytes != LUA_NOREF) {
- FIXED_TEMP_ARRAY(args, 8);
+ FIXED_TEMP_ARRAY(args, 11);
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
@@ -304,15 +307,18 @@ void buf_updates_send_splice(buf_T *buf,
// next argument is b:changedtick
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
- args.items[2] = INTEGER_OBJ(start_line);
+ args.items[2] = INTEGER_OBJ(start_row);
args.items[3] = INTEGER_OBJ(start_col);
- args.items[4] = INTEGER_OBJ(oldextent_line);
- args.items[5] = INTEGER_OBJ(oldextent_col);
- args.items[6] = INTEGER_OBJ(newextent_line);
- args.items[7] = INTEGER_OBJ(newextent_col);
+ args.items[4] = INTEGER_OBJ(start_byte);
+ args.items[5] = INTEGER_OBJ(old_row);
+ args.items[6] = INTEGER_OBJ(old_col);
+ args.items[7] = INTEGER_OBJ(old_byte);
+ args.items[8] = INTEGER_OBJ(new_row);
+ args.items[9] = INTEGER_OBJ(new_col);
+ args.items[10] = INTEGER_OBJ(new_byte);
textlock++;
- Object res = executor_exec_lua_cb(cb.on_bytes, "bytes", args, true, NULL);
+ Object res = nlua_call_ref(cb.on_bytes, "bytes", args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -347,8 +353,8 @@ void buf_updates_changedtick(buf_T *buf)
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
textlock++;
- Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick",
- args, true, NULL);
+ Object res = nlua_call_ref(cb.on_changedtick, "changedtick",
+ args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -382,6 +388,6 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
static void free_update_callbacks(BufUpdateCallbacks cb)
{
- executor_free_luaref(cb.on_lines);
- executor_free_luaref(cb.on_changedtick);
+ api_free_luaref(cb.on_lines);
+ api_free_luaref(cb.on_changedtick);
}
diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h
index b2d0a62270..961fec879b 100644
--- a/src/nvim/buffer_updates.h
+++ b/src/nvim/buffer_updates.h
@@ -2,6 +2,7 @@
#define NVIM_BUFFER_UPDATES_H
#include "nvim/buffer_defs.h"
+#include "nvim/extmark.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer_updates.h.generated.h"
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 51afb40b40..be52750c44 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -142,7 +142,6 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume,
long xtra)
{
int i;
- int cols;
pos_T *p;
int add;
@@ -170,7 +169,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume,
if (p->lnum != lnum) {
add = true;
} else {
- cols = comp_textwidth(false);
+ int cols = comp_textwidth(false);
if (cols == 0) {
cols = 79;
}
@@ -362,11 +361,10 @@ void changed_bytes(linenr_T lnum, colnr_T col)
/// insert/delete bytes at column
///
/// Like changed_bytes() but also adjust extmark for "new" bytes.
-/// When "new" is negative text was deleted.
-static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
+void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
{
if (curbuf_splice_pending == 0) {
- extmark_splice(curbuf, (int)lnum-1, col, 0, old, 0, new, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, col, old, new, kExtmarkUndo);
}
changed_bytes(lnum, col);
@@ -1597,7 +1595,7 @@ int open_line(
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,
- kExtmarkUndo);
+ kExtmarkNOOP);
}
did_append = true;
} else {
@@ -1611,6 +1609,7 @@ int open_line(
}
ml_replace(curwin->w_cursor.lnum, p_extra, true);
changed_bytes(curwin->w_cursor.lnum, 0);
+ // TODO(vigoux): extmark_splice_cols here??
curwin->w_cursor.lnum--;
did_append = false;
}
@@ -1676,6 +1675,16 @@ int open_line(
truncate_spaces(saved_line);
}
ml_replace(curwin->w_cursor.lnum, saved_line, false);
+
+ int new_len = (int)STRLEN(saved_line);
+
+ // TODO(vigoux): maybe there is issues there with expandtabs ?
+ if (new_len < curwin->w_cursor.col) {
+ extmark_splice_cols(
+ curbuf, (int)curwin->w_cursor.lnum,
+ new_len, curwin->w_cursor.col - new_len, 0, kExtmarkUndo);
+ }
+
saved_line = NULL;
if (did_append) {
changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
@@ -1691,8 +1700,9 @@ int open_line(
// Always move extmarks - Here we move only the line where the
// cursor is, the previous mark_adjust takes care of the lines after
int cols_added = mincol-1+less_cols_off-less_cols;
- extmark_splice(curbuf, (int)lnum-1, mincol-1, 0, less_cols_off,
- 1, cols_added, kExtmarkUndo);
+ extmark_splice(curbuf, (int)lnum-1, mincol-1,
+ 0, less_cols_off, less_cols_off,
+ 1, cols_added, 1 + cols_added, kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}
@@ -1704,8 +1714,10 @@ int open_line(
}
if (did_append) {
changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1,
- 0, 0, 0, 1, 0, kExtmarkUndo);
+ // bail out and just get the final lenght of the line we just manipulated
+ bcount_t extra = (bcount_t)STRLEN(ml_get(curwin->w_cursor.lnum));
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, 0,
+ 0, 0, 0, 1, 0, 1+extra, kExtmarkUndo);
}
curbuf_splice_pending--;
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index f9d5adbc12..fb158f377a 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -509,7 +509,7 @@ char_u* str_foldcase(char_u *str, int orglen, char_u *buf, int buflen)
// Does NOT work for multi-byte characters, c must be <= 255.
// Also doesn't work for the first byte of a multi-byte, "c" must be a
// character!
-static char_u transchar_buf[11];
+static char_u transchar_charbuf[11];
/// Translate a character into a printable one, leaving printable ASCII intact
///
@@ -520,11 +520,17 @@ static char_u transchar_buf[11];
/// @return translated character into a static buffer.
char_u *transchar(int c)
{
+ return transchar_buf(curbuf, c);
+}
+
+char_u *transchar_buf(const buf_T *buf, int c)
+ FUNC_ATTR_NONNULL_ALL
+{
int i = 0;
if (IS_SPECIAL(c)) {
// special key code, display as ~@ char
- transchar_buf[0] = '~';
- transchar_buf[1] = '@';
+ transchar_charbuf[0] = '~';
+ transchar_charbuf[1] = '@';
i = 2;
c = K_SECOND(c);
}
@@ -532,14 +538,14 @@ char_u *transchar(int c)
if ((!chartab_initialized && (((c >= ' ') && (c <= '~'))))
|| ((c <= 0xFF) && vim_isprintc_strict(c))) {
// printable character
- transchar_buf[i] = (char_u)c;
- transchar_buf[i + 1] = NUL;
+ transchar_charbuf[i] = (char_u)c;
+ transchar_charbuf[i + 1] = NUL;
} else if (c <= 0xFF) {
- transchar_nonprint(transchar_buf + i, c);
+ transchar_nonprint(buf, transchar_charbuf + i, c);
} else {
- transchar_hex((char *)transchar_buf + i, c);
+ transchar_hex((char *)transchar_charbuf + i, c);
}
- return transchar_buf;
+ return transchar_charbuf;
}
/// Like transchar(), but called with a byte instead of a character
@@ -548,13 +554,13 @@ char_u *transchar(int c)
///
/// @param[in] c Byte to translate.
///
-/// @return pointer to translated character in transchar_buf.
+/// @return pointer to translated character in transchar_charbuf.
char_u *transchar_byte(const int c)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (c >= 0x80) {
- transchar_nonprint(transchar_buf, c);
- return transchar_buf;
+ transchar_nonprint(curbuf, transchar_charbuf, c);
+ return transchar_charbuf;
}
return transchar(c);
}
@@ -563,16 +569,18 @@ char_u *transchar_byte(const int c)
///
/// @warning Does not work for multi-byte characters, c must be <= 255.
///
-/// @param[out] buf Buffer to store result in, must be able to hold at least
-/// 5 bytes (conversion result + NUL).
+/// @param[in] buf Required to check the file format
+/// @param[out] charbuf Buffer to store result in, must be able to hold
+/// at least 5 bytes (conversion result + NUL).
/// @param[in] c Character to convert. NUL is assumed to be NL according to
/// `:h NL-used-for-NUL`.
-void transchar_nonprint(char_u *buf, int c)
+void transchar_nonprint(const buf_T *buf, char_u *charbuf, int c)
+ FUNC_ATTR_NONNULL_ALL
{
if (c == NL) {
// we use newline in place of a NUL
c = NUL;
- } else if ((c == CAR) && (get_fileformat(curbuf) == EOL_MAC)) {
+ } else if ((c == CAR) && (get_fileformat(buf) == EOL_MAC)) {
// we use CR in place of NL in this case
c = NL;
}
@@ -580,14 +588,14 @@ void transchar_nonprint(char_u *buf, int c)
if (dy_flags & DY_UHEX || c > 0x7f) {
// 'display' has "uhex"
- transchar_hex((char *)buf, c);
+ transchar_hex((char *)charbuf, c);
} else {
// 0x00 - 0x1f and 0x7f
- buf[0] = '^';
+ charbuf[0] = '^';
// DEL displayed as ^?
- buf[1] = (char_u)(c ^ 0x40);
+ charbuf[1] = (char_u)(c ^ 0x40);
- buf[2] = NUL;
+ charbuf[2] = NUL;
}
}
diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c
index 3f06340611..0d21080aa5 100644
--- a/src/nvim/cursor_shape.c
+++ b/src/nvim/cursor_shape.c
@@ -13,6 +13,10 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ui.h"
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "cursor_shape.c.generated.h"
+#endif
+
/// Handling of cursor and mouse pointer shapes in various modes.
cursorentry_T shape_table[SHAPE_IDX_COUNT] =
{
@@ -77,7 +81,9 @@ Array mode_style_array(void)
return all;
}
-/// Parse the 'guicursor' option
+/// Parses the 'guicursor' option.
+///
+/// Clears `shape_table` if 'guicursor' is empty.
///
/// @param what SHAPE_CURSOR or SHAPE_MOUSE ('mouseshape')
///
@@ -99,11 +105,17 @@ char_u *parse_shape_opt(int what)
// First round: check for errors; second round: do it for real.
for (round = 1; round <= 2; round++) {
+ if (round == 2 || *p_guicursor == NUL) {
+ // Set all entries to default (block, blinkon0, default color).
+ // This is the default for anything that is not set.
+ clear_shape_table();
+ if (*p_guicursor == NUL) {
+ ui_mode_info_set();
+ return NULL;
+ }
+ }
// Repeat for all comma separated parts.
modep = p_guicursor;
- if (*p_guicursor == NUL) {
- modep = (char_u *)"a:block-blinkon0";
- }
while (modep != NULL && *modep != NUL) {
colonp = vim_strchr(modep, ':');
commap = vim_strchr(modep, ',');
@@ -144,14 +156,6 @@ char_u *parse_shape_opt(int what)
if (all_idx >= 0) {
idx = all_idx--;
- } else if (round == 2) {
- {
- // Set the defaults, for the missing parts
- shape_table[idx].shape = SHAPE_BLOCK;
- shape_table[idx].blinkwait = 0L;
- shape_table[idx].blinkon = 0L;
- shape_table[idx].blinkoff = 0L;
- }
}
/* Parse the part after the colon */
@@ -330,3 +334,16 @@ int cursor_get_mode_idx(void)
return SHAPE_IDX_N;
}
}
+
+/// Clears all entries in shape_table to block, blinkon0, and default color.
+static void clear_shape_table(void)
+{
+ for (int idx = 0; idx < SHAPE_IDX_COUNT; idx++) {
+ shape_table[idx].shape = SHAPE_BLOCK;
+ shape_table[idx].blinkwait = 0L;
+ shape_table[idx].blinkon = 0L;
+ shape_table[idx].blinkoff = 0L;
+ shape_table[idx].id = 0;
+ shape_table[idx].id_lm = 0;
+ }
+}
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index 65d95ff158..dd32cef1e3 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -856,6 +856,7 @@ static digr_T digraphdefault[] =
{ '9', '"', 0x201f },
{ '/', '-', 0x2020 },
{ '/', '=', 0x2021 },
+ { 'o', 'o', 0x2022 },
{ '.', '.', 0x2025 },
{ ',', '.', 0x2026 },
{ '%', '0', 0x2030 },
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index ea38221dc7..ac0e6cc9f6 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1254,14 +1254,6 @@ check_pum:
normalchar:
// Insert a normal character.
- if (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META) {
- // Unmapped ALT/META chord behaves like ESC+c. #8213
- stuffcharReadbuff(ESC);
- stuffcharReadbuff(s->c);
- u_sync(false);
- break;
- }
-
if (!p_paste) {
// Trigger InsertCharPre.
char_u *str = do_insert_char_pre(s->c);
@@ -1919,10 +1911,10 @@ change_indent (
// TODO(bfredl): test for crazy edge cases, like we stand on a TAB or
// something? does this even do the right text change then?
int delta = orig_col - new_col;
- extmark_splice(curbuf, curwin->w_cursor.lnum-1, new_col,
- 0, delta < 0 ? -delta : 0,
- 0, delta > 0 ? delta : 0,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, curwin->w_cursor.lnum-1, new_col,
+ delta < 0 ? -delta : 0,
+ delta > 0 ? delta : 0,
+ kExtmarkUndo);
}
}
@@ -4179,6 +4171,21 @@ static int ins_compl_get_exp(pos_T *ini)
EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) == OK) {
// May change home directory back to "~".
tilde_replace(compl_pattern, num_matches, matches);
+#ifdef BACKSLASH_IN_FILENAME
+ if (curbuf->b_p_csl[0] != NUL) {
+ for (int i = 0; i < num_matches; i++) {
+ char_u *ptr = matches[i];
+ while (*ptr != NUL) {
+ if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') {
+ *ptr = '/';
+ } else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') {
+ *ptr = '\\';
+ }
+ ptr += utfc_ptr2len(ptr);
+ }
+ }
+ }
+#endif
ins_compl_add_matches(num_matches, matches, p_fic || p_wic);
}
break;
@@ -5557,13 +5564,11 @@ void insertchar(
int second_indent // indent for second line if >= 0
)
{
- int textwidth;
char_u *p;
- int fo_ins_blank;
int force_format = flags & INSCHAR_FORMAT;
- textwidth = comp_textwidth(force_format);
- fo_ins_blank = has_format_option(FO_INS_BLANK);
+ const int textwidth = comp_textwidth(force_format);
+ const bool fo_ins_blank = has_format_option(FO_INS_BLANK);
/*
* Try to break the line in two or more pieces when:
@@ -5764,10 +5769,11 @@ internal_format (
int cc;
int save_char = NUL;
bool haveto_redraw = false;
- int fo_ins_blank = has_format_option(FO_INS_BLANK);
- int fo_multibyte = has_format_option(FO_MBYTE_BREAK);
- int fo_white_par = has_format_option(FO_WHITE_PAR);
- int first_line = TRUE;
+ const bool fo_ins_blank = has_format_option(FO_INS_BLANK);
+ const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK);
+ const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW);
+ const bool fo_white_par = has_format_option(FO_WHITE_PAR);
+ bool first_line = true;
colnr_T leader_len;
bool no_leader = false;
int do_comments = (flags & INSCHAR_DO_COM);
@@ -5846,6 +5852,7 @@ internal_format (
curwin->w_cursor.col = startcol;
foundcol = 0;
+ int skip_pos = 0;
/*
* Find position to break at.
@@ -5915,7 +5922,11 @@ internal_format (
foundcol = curwin->w_cursor.col;
if (curwin->w_cursor.col <= (colnr_T)wantcol)
break;
- } else if (cc >= 0x100 && fo_multibyte) {
+ } else if ((cc >= 0x100 || !utf_allow_break_before(cc))
+ && fo_multibyte) {
+ int ncc;
+ bool allow_break;
+
// Break after or before a multi-byte character.
if (curwin->w_cursor.col != startcol) {
// Don't break until after the comment leader
@@ -5924,8 +5935,11 @@ internal_format (
}
col = curwin->w_cursor.col;
inc_cursor();
- // Don't change end_foundcol if already set.
- if (foundcol != curwin->w_cursor.col) {
+ ncc = gchar_cursor();
+ allow_break = utf_allow_break(cc, ncc);
+
+ // If we have already checked this position, skip!
+ if (curwin->w_cursor.col != skip_pos && allow_break) {
foundcol = curwin->w_cursor.col;
end_foundcol = foundcol;
if (curwin->w_cursor.col <= (colnr_T)wantcol)
@@ -5937,6 +5951,7 @@ internal_format (
if (curwin->w_cursor.col == 0)
break;
+ ncc = cc;
col = curwin->w_cursor.col;
dec_cursor();
@@ -5945,17 +5960,56 @@ internal_format (
if (WHITECHAR(cc)) {
continue; // break with space
}
- // Don't break until after the comment leader
+ // Don't break until after the comment leader.
if (curwin->w_cursor.col < leader_len) {
break;
}
curwin->w_cursor.col = col;
+ skip_pos = curwin->w_cursor.col;
- foundcol = curwin->w_cursor.col;
- end_foundcol = foundcol;
- if (curwin->w_cursor.col <= (colnr_T)wantcol)
- break;
+ allow_break = utf_allow_break(cc, ncc);
+
+ // Must handle this to respect line break prohibition.
+ if (allow_break) {
+ foundcol = curwin->w_cursor.col;
+ end_foundcol = foundcol;
+ }
+ if (curwin->w_cursor.col <= (colnr_T)wantcol) {
+ const bool ncc_allow_break = utf_allow_break_before(ncc);
+
+ if (allow_break) {
+ break;
+ }
+ if (!ncc_allow_break && !fo_rigor_tw) {
+ // Enable at most 1 punct hang outside of textwidth.
+ if (curwin->w_cursor.col == startcol) {
+ // We are inserting a non-breakable char, postpone
+ // line break check to next insert.
+ end_foundcol = foundcol = 0;
+ break;
+ }
+
+ // Neither cc nor ncc is NUL if we are here, so
+ // it's safe to inc_cursor.
+ col = curwin->w_cursor.col;
+
+ inc_cursor();
+ cc = ncc;
+ ncc = gchar_cursor();
+ // handle insert
+ ncc = (ncc != NUL) ? ncc : c;
+
+ allow_break = utf_allow_break(cc, ncc);
+
+ if (allow_break) {
+ // Break only when we are not at end of line.
+ end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col;
+ break;
+ }
+ curwin->w_cursor.col = col;
+ }
+ }
}
if (curwin->w_cursor.col == 0)
break;
@@ -6057,7 +6111,7 @@ internal_format (
}
}
}
- first_line = FALSE;
+ first_line = false;
}
if (State & VREPLACE_FLAG) {
@@ -6244,12 +6298,10 @@ static void check_auto_format(
* Set default to window width (maximum 79) for "gq" operator.
*/
int comp_textwidth(
- int ff // force formatting (for "gq" command)
+ bool ff // force formatting (for "gq" command)
)
{
- int textwidth;
-
- textwidth = curbuf->b_p_tw;
+ int textwidth = curbuf->b_p_tw;
if (textwidth == 0 && curbuf->b_p_wm) {
// The width is the window width minus 'wrapmargin' minus all the
// things that add to the margin.
@@ -7699,6 +7751,10 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
undisplay_dollar();
}
+ if (cmdchar != 'r' && cmdchar != 'v') {
+ ins_apply_autocmds(EVENT_INSERTLEAVEPRE);
+ }
+
// When an autoindent was removed, curswant stays after the
// indent
if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 0cad5fd6c1..cccf1e50ff 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -736,7 +736,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv,
if (s == NULL || *s == NUL) {
return FAIL;
}
- if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL,
+ if (call_func(s, -1, rettv, argc, argv, NULL,
0L, 0L, &dummy, true, NULL, NULL) == FAIL) {
return FAIL;
}
@@ -746,7 +746,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv,
if (s == NULL || *s == NUL) {
return FAIL;
}
- if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL,
+ if (call_func(s, -1, rettv, argc, argv, NULL,
0L, 0L, &dummy, true, partial, NULL) == FAIL) {
return FAIL;
}
@@ -760,7 +760,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv,
if (eval1_emsg(&s, rettv, true) == FAIL) {
return FAIL;
}
- if (*s != NUL) { // check for trailing chars after expr
+ if (*skipwhite(s) != NUL) { // check for trailing chars after expr
tv_clear(rettv);
emsgf(_(e_invexpr2), s);
return FAIL;
@@ -1679,7 +1679,7 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
arg = (const char *)find_name_end((char_u *)arg, NULL, NULL,
FNE_INCL_BR | FNE_CHECK_START);
if (!ascii_iswhite(*arg) && !ends_excmd(*arg)) {
- emsg_severe = TRUE;
+ emsg_severe = true;
EMSG(_(e_trailing));
break;
}
@@ -1692,7 +1692,7 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
/* This is mainly to keep test 49 working: when expanding
* curly braces fails overrule the exception error message. */
if (len < 0 && !aborting()) {
- emsg_severe = TRUE;
+ emsg_severe = true;
EMSG2(_(e_invarg2), arg);
break;
}
@@ -2007,7 +2007,7 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
* expression evaluation has been cancelled due to an
* aborting error, an interrupt, or an exception. */
if (!aborting() && !quiet) {
- emsg_severe = TRUE;
+ emsg_severe = true;
EMSG2(_(e_invarg2), name);
return NULL;
}
@@ -2086,6 +2086,7 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
tv_clear(&var1);
return NULL;
}
+ p = skipwhite(p);
}
// Optionally get the second index [ :expr].
@@ -2674,6 +2675,7 @@ void ex_lockvar(exarg_T *eap)
static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
{
char_u *arg = argstart;
+ char_u *name_end;
bool error = false;
lval_T lv;
@@ -2686,43 +2688,43 @@ static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
return;
}
os_unsetenv(name);
- arg = skipwhite(arg);
- continue;
- }
-
- // Parse the name and find the end.
- char_u *const name_end = (char_u *)get_lval(arg, NULL, &lv, true,
- eap->skip || error,
- 0, FNE_CHECK_START);
- if (lv.ll_name == NULL) {
- error = true; // error, but continue parsing.
- }
- if (name_end == NULL || (!ascii_iswhite(*name_end)
- && !ends_excmd(*name_end))) {
- if (name_end != NULL) {
- emsg_severe = TRUE;
- EMSG(_(e_trailing));
+ name_end = arg;
+ } else {
+ // Parse the name and find the end.
+ name_end = get_lval(arg, NULL, &lv, true, eap->skip || error,
+ 0, FNE_CHECK_START);
+ if (lv.ll_name == NULL) {
+ error = true; // error, but continue parsing.
+ }
+ if (name_end == NULL
+ || (!ascii_iswhite(*name_end) && !ends_excmd(*name_end))) {
+ if (name_end != NULL) {
+ emsg_severe = true;
+ EMSG(_(e_trailing));
+ }
+ if (!(eap->skip || error)) {
+ clear_lval(&lv);
+ }
+ break;
}
- if (!(eap->skip || error))
- clear_lval(&lv);
- break;
- }
- if (!error && !eap->skip) {
- if (eap->cmdidx == CMD_unlet) {
- if (do_unlet_var(&lv, name_end, eap->forceit) == FAIL)
- error = TRUE;
- } else {
- if (do_lock_var(&lv, name_end, deep,
- eap->cmdidx == CMD_lockvar) == FAIL) {
- error = true;
+ if (!error && !eap->skip) {
+ if (eap->cmdidx == CMD_unlet) {
+ if (do_unlet_var(&lv, name_end, eap->forceit) == FAIL) {
+ error = true;
+ }
+ } else {
+ if (do_lock_var(&lv, name_end, deep,
+ eap->cmdidx == CMD_lockvar) == FAIL) {
+ error = true;
+ }
}
}
- }
-
- if (!eap->skip)
- clear_lval(&lv);
+ if (!eap->skip) {
+ clear_lval(&lv);
+ }
+ }
arg = skipwhite(name_end);
} while (!ends_excmd(*arg));
@@ -2992,7 +2994,6 @@ char_u *get_user_var_name(expand_T *xp, int idx)
static size_t tdone;
static size_t vidx;
static hashitem_T *hi;
- hashtab_T *ht;
if (idx == 0) {
gdone = bdone = wdone = vidx = 0;
@@ -3013,7 +3014,10 @@ char_u *get_user_var_name(expand_T *xp, int idx)
}
// b: variables
- ht = &curbuf->b_vars->dv_hashtab;
+ // In cmdwin, the alternative buffer should be used.
+ hashtab_T *ht = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? &prevwin->w_buffer->b_vars->dv_hashtab
+ : &curbuf->b_vars->dv_hashtab;
if (bdone < ht->ht_used) {
if (bdone++ == 0)
hi = ht->ht_array;
@@ -3025,7 +3029,10 @@ char_u *get_user_var_name(expand_T *xp, int idx)
}
// w: variables
- ht = &curwin->w_vars->dv_hashtab;
+ // In cmdwin, the alternative window should be used.
+ ht = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? &prevwin->w_vars->dv_hashtab
+ : &curwin->w_vars->dv_hashtab;
if (wdone < ht->ht_used) {
if (wdone++ == 0)
hi = ht->ht_array;
@@ -3800,8 +3807,9 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
*/
for (;; ) {
op = **arg;
- if (op != '*' && op != '/' && op != '%')
+ if (op != '*' && op != '/' && op != '%') {
break;
+ }
if (evaluate) {
if (rettv->v_type == VAR_FLOAT) {
@@ -3903,6 +3911,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
// (expression) nested expression
// [expr, expr] List
// {key: val, key: val} Dictionary
+// #{key: val, key: val} Dictionary with literal keys
//
// Also handle:
// ! in front logical NOT
@@ -4010,11 +4019,21 @@ static int eval7(
case '[': ret = get_list_tv(arg, rettv, evaluate);
break;
+ // Dictionary: #{key: val, key: val}
+ case '#':
+ if ((*arg)[1] == '{') {
+ (*arg)++;
+ ret = dict_get_tv(arg, rettv, evaluate, true);
+ } else {
+ ret = NOTDONE;
+ }
+ break;
+
// Lambda: {arg, arg -> expr}
- // Dictionary: {key: val, key: val}
+ // Dictionary: {'key': val, 'key': val}
case '{': ret = get_lambda_tv(arg, rettv, evaluate);
if (ret == NOTDONE) {
- ret = dict_get_tv(arg, rettv, evaluate);
+ ret = dict_get_tv(arg, rettv, evaluate, false);
}
break;
@@ -4516,7 +4535,6 @@ int get_option_tv(const char **const arg, typval_T *const rettv,
static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
{
char_u *p;
- char_u *name;
unsigned int extra = 0;
/*
@@ -4524,11 +4542,14 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
*/
for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) {
if (*p == '\\' && p[1] != NUL) {
- ++p;
- /* A "\<x>" form occupies at least 4 characters, and produces up
- * to 6 characters: reserve space for 2 extra */
- if (*p == '<')
- extra += 2;
+ p++;
+ // A "\<x>" form occupies at least 4 characters, and produces up
+ // to 21 characters (3 * 6 for the char and 3 for a modifier):
+ // reserve space for 18 extra.
+ // Each byte in the char could be encoded as K_SPECIAL K_EXTRA x.
+ if (*p == '<') {
+ extra += 18;
+ }
}
}
@@ -4547,7 +4568,8 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
* Copy the string into allocated memory, handling backslashed
* characters.
*/
- name = xmalloc(p - *arg + extra);
+ const int len = (int)(p - *arg + extra);
+ char_u *name = xmalloc(len);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = name;
@@ -4614,6 +4636,9 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
extra = trans_special((const char_u **)&p, STRLEN(p), name, true, true);
if (extra != 0) {
name += extra;
+ if (name >= rettv->vval.v_string + len) {
+ iemsg("get_string_tv() used more space than allocated");
+ }
break;
}
FALLTHROUGH;
@@ -5339,11 +5364,31 @@ static inline bool set_ref_dict(dict_T *dict, int copyID)
return false;
}
-/*
- * Allocate a variable for a Dictionary and fill it from "*arg".
- * Return OK or FAIL. Returns NOTDONE for {expr}.
- */
-static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
+
+// Get the key for *{key: val} into "tv" and advance "arg".
+// Return FAIL when there is no valid key.
+static int get_literal_key(char_u **arg, typval_T *tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char_u *p;
+
+ if (!ASCII_ISALNUM(**arg) && **arg != '_' && **arg != '-') {
+ return FAIL;
+ }
+ for (p = *arg; ASCII_ISALNUM(*p) || *p == '_' || *p == '-'; p++) {
+ }
+ tv->v_type = VAR_STRING;
+ tv->vval.v_string = vim_strnsave(*arg, (int)(p - *arg));
+
+ *arg = skipwhite(p);
+ return OK;
+}
+
+// Allocate a variable for a Dictionary and fill it from "*arg".
+// "literal" is true for *{key: val}
+// Return OK or FAIL. Returns NOTDONE for {expr}.
+static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate,
+ bool literal)
{
dict_T *d = NULL;
typval_T tvkey;
@@ -5364,7 +5409,7 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
if (eval1(&start, &tv, false) == FAIL) { // recursive!
return FAIL;
}
- if (*start == '}') {
+ if (*skipwhite(start) == '}') {
return NOTDONE;
}
}
@@ -5377,7 +5422,9 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
*arg = skipwhite(*arg + 1);
while (**arg != '}' && **arg != NUL) {
- if (eval1(arg, &tvkey, evaluate) == FAIL) { // recursive!
+ if ((literal
+ ? get_literal_key(arg, &tvkey)
+ : eval1(arg, &tvkey, evaluate)) == FAIL) { // recursive!
goto failret;
}
if (**arg != ':') {
@@ -6960,9 +7007,10 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append,
if (!append && lnum <= curbuf->b_ml.ml_line_count) {
// Existing line, replace it.
+ int old_len = (int)STRLEN(ml_get(lnum));
if (u_savesub(lnum) == OK
&& ml_replace(lnum, (char_u *)line, true) == OK) {
- changed_bytes(lnum, 0);
+ inserted_bytes(lnum, 0, old_len, STRLEN(line));
if (is_curbuf && lnum == curwin->w_cursor.lnum) {
check_cursor_col();
}
@@ -7073,7 +7121,7 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
do {
size_t dir_len;
const char *dir;
- iter = vim_env_iter(':', dirs, iter, &dir, &dir_len);
+ iter = vim_env_iter(ENV_SEPCHAR, dirs, iter, &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
char *dir_with_nvim = xmemdupz(dir, dir_len);
dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true);
@@ -7261,7 +7309,7 @@ bool callback_call(Callback *const callback, const int argcount_in,
}
int dummy;
- return call_func(name, (int)STRLEN(name), rettv, argcount_in, argvars_in,
+ return call_func(name, -1, rettv, argcount_in, argvars_in,
NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
true, partial, NULL);
}
@@ -8483,7 +8531,7 @@ handle_subscript(
} else {
s = (char_u *)"";
}
- ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg,
+ ret = get_func_tv(s, lua ? slen : -1, rettv, (char_u **)arg,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&len, evaluate, pt, selfdict);
@@ -9044,7 +9092,7 @@ static void set_var_const(const char *name, const size_t name_len,
}
if (is_const) {
- v->di_tv.v_lock |= VAR_LOCKED;
+ tv_item_lock(&v->di_tv, 1, true);
}
}
@@ -10381,10 +10429,13 @@ void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
list_T *args = tv_list_alloc(1);
tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1);
- *rettv = eval_call_provider(name, "eval", args);
+ *rettv = eval_call_provider(name, "eval", args, false);
}
-typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
+/// @param discard Clears the value returned by the provider and returns
+/// an empty typval_T.
+typval_T eval_call_provider(char *provider, char *method, list_T *arguments,
+ bool discard)
{
if (!eval_has_provider(provider)) {
emsgf("E319: No \"%s\" provider found. Run \":checkhealth provider\"",
@@ -10443,6 +10494,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
provider_call_nesting--;
assert(provider_call_nesting >= 0);
+ if (discard) {
+ tv_clear(&rettv);
+ }
+
return rettv;
}
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 023c60f118..6c316bb1fe 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -191,6 +191,7 @@ return {
inputsave={},
inputsecret={args={1, 2}},
insert={args={2, 3}},
+ interrupt={args=0},
invert={args=1},
isdirectory={args=1},
isinf={args=1},
@@ -255,6 +256,7 @@ return {
py3eval={args=1},
pyeval={args=1},
pyxeval={args=1},
+ perleval={args=1},
range={args={1, 3}},
readdir={args={1, 2}},
readfile={args={1, 3}},
@@ -273,6 +275,7 @@ return {
rpcrequest={args=varargs(2)},
rpcstart={args={1, 2}},
rpcstop={args=1},
+ rubyeval={args=1},
screenattr={args=2},
screenchar={args=2},
screencol={},
@@ -335,7 +338,7 @@ return {
stridx={args={2, 3}},
string={args=1},
strlen={args=1},
- strpart={args={2, 3}},
+ strpart={args={2, 4}},
strridx={args={2, 3}},
strtrans={args=1},
strwidth={args=1},
@@ -369,7 +372,7 @@ return {
tolower={args=1},
toupper={args=1},
tr={args=3},
- trim={args={1,2}},
+ trim={args={1,3}},
trunc={args=1, func="float_op_wrapper", data="&trunc"},
type={args=1},
undofile={args=1},
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index daba304f00..638fef331a 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -586,7 +586,7 @@ parse_json_number_check:
if (p == ints) {
emsgf(_("E474: Missing number after minus sign: %.*s"), LENP(s, e));
goto parse_json_number_fail;
- } else if (p == fracs || exps_s == fracs + 1) {
+ } else if (p == fracs || (fracs != NULL && exps_s == fracs + 1)) {
emsgf(_("E474: Missing number after decimal dot: %.*s"), LENP(s, e));
goto parse_json_number_fail;
} else if (p == exps) {
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 1a80d4d4bd..83ef9c8762 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -32,6 +32,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/lua/executor.h"
+#include "nvim/macros.h"
#include "nvim/mark.h"
#include "nvim/math.h"
#include "nvim/memline.h"
@@ -86,8 +87,10 @@ KHASH_MAP_INIT_STR(functions, VimLFuncDef)
#endif
PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
+PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH
#include "funcs.generated.h"
PRAGMA_DIAG_POP
+PRAGMA_DIAG_POP
#endif
@@ -2068,6 +2071,12 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
expand_T xpc;
bool error = false;
char_u *result;
+#ifdef BACKSLASH_IN_FILENAME
+ char_u *p_csl_save = p_csl;
+
+ // avoid using 'completeslash' here
+ p_csl = empty_option;
+#endif
rettv->v_type = VAR_STRING;
if (argvars[1].v_type != VAR_UNKNOWN
@@ -2120,6 +2129,9 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_string = NULL;
}
}
+#ifdef BACKSLASH_IN_FILENAME
+ p_csl = p_csl_save;
+#endif
}
@@ -2407,9 +2419,9 @@ static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
float_T f;
if (tv_get_float_chk(argvars, &f)) {
- if (f <= -VARNUMBER_MAX + DBL_EPSILON) {
+ if (f <= (float_T)-VARNUMBER_MAX + DBL_EPSILON) {
rettv->vval.v_number = -VARNUMBER_MAX;
- } else if (f >= VARNUMBER_MAX - DBL_EPSILON) {
+ } else if (f >= (float_T)VARNUMBER_MAX - DBL_EPSILON) {
rettv->vval.v_number = VARNUMBER_MAX;
} else {
rettv->vval.v_number = (varnumber_T)f;
@@ -2581,8 +2593,6 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
char_u *text;
char_u buf[FOLD_TEXT_LEN];
- foldinfo_T foldinfo;
- int fold_count;
static bool entered = false;
rettv->v_type = VAR_STRING;
@@ -2596,9 +2606,10 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (lnum < 0) {
lnum = 0;
}
- fold_count = foldedCount(curwin, lnum, &foldinfo);
- if (fold_count > 0) {
- text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf);
+
+ foldinfo_T info = fold_info(curwin, lnum);
+ if (info.fi_lines > 0) {
+ text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf);
if (text == buf) {
text = vim_strsave(text);
}
@@ -4005,7 +4016,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "globpath()" function
static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int flags = 0; // Flags for globpath.
+ int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath.
bool error = false;
// Return a string, or a list if the optional third argument is non-zero.
@@ -4717,6 +4728,14 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+// "interrupt()" function
+static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED,
+ typval_T *rettv FUNC_ATTR_UNUSED,
+ FunPtr fptr FUNC_ATTR_UNUSED)
+{
+ got_int = true;
+}
+
/*
* "invert(expr)" function
*/
@@ -5471,7 +5490,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv);
+ nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv);
}
/*
@@ -6363,6 +6382,20 @@ static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+///
+/// "perleval()" function
+///
+static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ script_host_eval("perl", argvars, rettv);
+}
+
+// "rubyeval()" function
+static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ script_host_eval("ruby", argvars, rettv);
+}
+
/*
* "range()" function
*/
@@ -6918,7 +6951,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
ptrdiff_t len = (ptrdiff_t)strlen(p);
- if (len > 0 && after_pathsep(p, p + len)) {
+ if (len > 1 && after_pathsep(p, p + len)) {
has_trailing_pathsep = true;
p[len - 1] = NUL; // The trailing slash breaks readlink().
}
@@ -7650,7 +7683,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
}
retval = do_searchpair(
- (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip,
+ spat, mpat, epat, dir, skip,
flags, match_pos, lnum_stop, time_limit);
theend:
@@ -7694,9 +7727,9 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
long
do_searchpair(
- char_u *spat, // start pattern
- char_u *mpat, // middle pattern
- char_u *epat, // end pattern
+ const char *spat, // start pattern
+ const char *mpat, // middle pattern
+ const char *epat, // end pattern
int dir, // BACKWARD or FORWARD
const typval_T *skip, // skip expression
int flags, // SP_SETPCMARK and other SP_ values
@@ -7704,6 +7737,7 @@ do_searchpair(
linenr_T lnum_stop, // stop at this line if not zero
long time_limit // stop after this many msec
)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 3)
{
char_u *save_cpo;
char_u *pat, *pat2 = NULL, *pat3 = NULL;
@@ -7718,8 +7752,6 @@ do_searchpair(
bool use_skip = false;
int options = SEARCH_KEEP;
proftime_T tm;
- size_t pat2_len;
- size_t pat3_len;
// Make 'cpoptions' empty, the 'l' flag should not be used here.
save_cpo = p_cpo;
@@ -7730,9 +7762,9 @@ do_searchpair(
// Make two search patterns: start/end (pat2, for in nested pairs) and
// start/middle/end (pat3, for the top pair).
- pat2_len = STRLEN(spat) + STRLEN(epat) + 17;
+ const size_t pat2_len = strlen(spat) + strlen(epat) + 17;
pat2 = xmalloc(pat2_len);
- pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25;
+ const size_t pat3_len = strlen(spat) + strlen(mpat) + strlen(epat) + 25;
pat3 = xmalloc(pat3_len);
snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
if (*mpat == NUL) {
@@ -8124,15 +8156,17 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// Create quickfix/location list from VimL values
///
/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
-/// list_arg, action_arg and what_arg arguments in which case errors out,
-/// including VAR_UNKNOWN parameters.
+/// args argument in which case errors out, including VAR_UNKNOWN parameters.
///
/// @param[in,out] wp Window to create location list for. May be NULL in
/// which case quickfix list will be created.
-/// @param[in] list_arg Quickfix list contents.
-/// @param[in] action_arg Action to perform: append to an existing list,
-/// replace its content or create a new one.
-/// @param[in] title_arg New list title. Defaults to caller function name.
+/// @param[in] args [list, action, what]
+/// @param[in] args[0] Quickfix list contents.
+/// @param[in] args[1] Optional. Action to perform:
+/// append to an existing list, replace its content,
+/// or create a new one.
+/// @param[in] args[2] Optional. Quickfix list properties or title.
+/// Defaults to caller function name.
/// @param[out] rettv Return value: 0 in case of success, -1 otherwise.
static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(2, 3)
@@ -8142,7 +8176,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
int action = ' ';
static int recursive = 0;
rettv->vval.v_number = -1;
- dict_T *d = NULL;
+ dict_T *what = NULL;
typval_T *list_arg = &args[0];
if (list_arg->v_type != VAR_LIST) {
@@ -8170,18 +8204,18 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
return;
}
- typval_T *title_arg = &args[2];
- if (title_arg->v_type == VAR_UNKNOWN) {
+ typval_T *const what_arg = &args[2];
+ if (what_arg->v_type == VAR_UNKNOWN) {
// Option argument was not given.
goto skip_args;
- } else if (title_arg->v_type == VAR_STRING) {
- title = tv_get_string_chk(title_arg);
+ } else if (what_arg->v_type == VAR_STRING) {
+ title = tv_get_string_chk(what_arg);
if (!title) {
// Type error. Error already printed by tv_get_string_chk().
return;
}
- } else if (title_arg->v_type == VAR_DICT) {
- d = title_arg->vval.v_dict;
+ } else if (what_arg->v_type == VAR_DICT && what_arg->vval.v_dict != NULL) {
+ what = what_arg->vval.v_dict;
} else {
EMSG(_(e_dictreq));
return;
@@ -8194,7 +8228,7 @@ skip_args:
recursive++;
list_T *const l = list_arg->vval.v_list;
- if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) {
+ if (set_errorlist(wp, l, action, (char_u *)title, what) == OK) {
rettv->vval.v_number = 0;
}
recursive--;
@@ -9156,7 +9190,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
res = call_func((const char_u *)func_name,
- (int)STRLEN(func_name),
+ -1,
&rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
partial, sortinfo->item_compare_selfdict);
tv_clear(&argv[0]);
@@ -9938,6 +9972,16 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
len = slen - n;
}
+ if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) {
+ int off;
+
+ // length in characters
+ for (off = n; off < (int)slen && len > 0; len--) {
+ off += utfc_ptr2len((char_u *)p + off);
+ }
+ len = off - n;
+ }
+
rettv->v_type = VAR_STRING;
rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len);
}
@@ -10796,52 +10840,72 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char_u *prev;
const char_u *p;
int c1;
+ int dir = 0;
rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
if (head == NULL) {
- rettv->vval.v_string = NULL;
return;
}
if (argvars[1].v_type == VAR_STRING) {
mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+ // leading or trailing characters to trim
+ dir = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return;
+ }
+ if (dir < 0 || dir > 2) {
+ emsgf(_(e_invarg2), tv_get_string(&argvars[2]));
+ return;
+ }
+ }
}
- while (*head != NUL) {
- c1 = PTR2CHAR(head);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == PTR2CHAR(p)) {
+ if (dir == 0 || dir == 1) {
+ // Trim leading characters
+ while (*head != NUL) {
+ c1 = PTR2CHAR(head);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
break;
}
}
- if (*p == NUL) {
- break;
- }
+ MB_PTR_ADV(head);
}
- MB_PTR_ADV(head);
}
- for (tail = head + STRLEN(head); tail > head; tail = prev) {
- prev = tail;
- MB_PTR_BACK(head, prev);
- c1 = PTR2CHAR(prev);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == PTR2CHAR(p)) {
+ tail = head + STRLEN(head);
+ if (dir == 0 || dir == 2) {
+ // Trim trailing characters
+ for (; tail > head; tail = prev) {
+ prev = tail;
+ MB_PTR_BACK(head, prev);
+ c1 = PTR2CHAR(prev);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
break;
}
- }
- if (*p == NUL) {
- break;
}
}
}
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 229f0e8dde..dc94bc698d 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -32,7 +32,11 @@
#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
#define FC_SANDBOX 0x40 // function defined in the sandbox
-#define FC_CFUNC 0x80 // C function extension
+#define FC_DEAD 0x80 // function kept only for reference to dfunc
+#define FC_EXPORT 0x100 // "export def Func()"
+#define FC_NOARGS 0x200 // no a: variables in lambda
+#define FC_VIM9 0x400 // defined in vim9 script file
+#define FC_CFUNC 0x800 // C function extension
#ifdef INCLUDE_GENERATED_DECLARATIONS
#include "eval/userfunc.c.generated.h"
@@ -246,6 +250,10 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
STRCPY(p, "return ");
STRLCPY(p + 7, s, e - s + 1);
+ if (strstr((char *)p + 7, "a:") == NULL) {
+ // No a: variables are used for sure.
+ flags |= FC_NOARGS;
+ }
fp->uf_refcount = 1;
STRCPY(fp->uf_name, name);
@@ -367,7 +375,7 @@ void emsg_funcname(char *ermsg, const char_u *name)
int
get_func_tv(
const char_u *name, // name of the function
- int len, // length of "name"
+ int len, // length of "name" or -1 to use strlen()
typval_T *rettv,
char_u **arg, // argument, pointing to the '('
linenr_T firstline, // first line of range
@@ -813,17 +821,12 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
current_funccal = fc;
fc->func = fp;
fc->rettv = rettv;
- rettv->vval.v_number = 0;
- fc->linenr = 0;
- fc->returned = FALSE;
fc->level = ex_nesting_level;
// Check if this function has a breakpoint.
fc->breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick;
// Set up fields for closure.
- fc->fc_refcount = 0;
- fc->fc_copyID = 0;
ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
func_ptr_ref(fp);
@@ -853,37 +856,42 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
++selfdict->dv_refcount;
}
- /*
- * Init a: variables.
- * Set a:0 to "argcount".
- * Set a:000 to a list with room for the "..." arguments.
- */
+ // Init a: variables, unless none found (in lambda).
+ // Set a:0 to "argcount".
+ // Set a:000 to a list with room for the "..." arguments.
init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
- (varnumber_T)(argcount - fp->uf_args.ga_len));
+ if ((fp->uf_flags & FC_NOARGS) == 0) {
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
+ (varnumber_T)(argcount - fp->uf_args.ga_len));
+ }
fc->l_avars.dv_lock = VAR_FIXED;
- // Use "name" to avoid a warning from some compiler that checks the
- // destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+ if ((fp->uf_flags & FC_NOARGS) == 0) {
+ // Use "name" to avoid a warning from some compiler that checks the
+ // destination size.
+ v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
#ifndef __clang_analyzer__
- name = v->di_key;
- STRCPY(name, "000");
+ name = v->di_key;
+ STRCPY(name, "000");
#endif
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- tv_dict_add(&fc->l_avars, v);
- v->di_tv.v_type = VAR_LIST;
- v->di_tv.v_lock = VAR_FIXED;
- v->di_tv.vval.v_list = &fc->l_varlist;
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ tv_dict_add(&fc->l_avars, v);
+ v->di_tv.v_type = VAR_LIST;
+ v->di_tv.v_lock = VAR_FIXED;
+ v->di_tv.vval.v_list = &fc->l_varlist;
+ }
tv_list_init_static(&fc->l_varlist);
tv_list_set_lock(&fc->l_varlist, VAR_FIXED);
// Set a:firstline to "firstline" and a:lastline to "lastline".
// Set a:name to named arguments.
// Set a:N to the "..." arguments.
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
- "firstline", (varnumber_T)firstline);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
- "lastline", (varnumber_T)lastline);
+ // Skipped when no a: variables used (in lambda).
+ if ((fp->uf_flags & FC_NOARGS) == 0) {
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ "firstline", (varnumber_T)firstline);
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ "lastline", (varnumber_T)lastline);
+ }
for (int i = 0; i < argcount; i++) {
bool addlocal = false;
@@ -895,6 +903,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
addlocal = true;
}
} else {
+ if ((fp->uf_flags & FC_NOARGS) != 0) {
+ // Bail out if no a: arguments used (in lambda).
+ break;
+ }
// "..." argument a:1, a:2, etc.
snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
name = numbuf;
@@ -1034,9 +1046,19 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
save_did_emsg = did_emsg;
did_emsg = FALSE;
- // call do_cmdline() to execute the lines
- do_cmdline(NULL, get_func_line, (void *)fc,
- DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
+ if (islambda) {
+ char_u *p = *(char_u **)fp->uf_lines.ga_data + 7;
+
+ // A Lambda always has the command "return {expr}". It is much faster
+ // to evaluate {expr} directly.
+ ex_nesting_level++;
+ (void)eval1(&p, rettv, true);
+ ex_nesting_level--;
+ } else {
+ // call do_cmdline() to execute the lines
+ do_cmdline(NULL, get_func_line, (void *)fc,
+ DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
+ }
--RedrawingDisabled;
@@ -1291,7 +1313,7 @@ int func_call(char_u *name, typval_T *args, partial_T *partial,
tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]);
});
- r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL,
+ r = call_func(name, -1, rettv, argc, argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&dummy, true, partial, selfdict);
@@ -1304,6 +1326,36 @@ func_call_skip_call:
return r;
}
+// Give an error message for the result of a function.
+// Nothing if "error" is FCERR_NONE.
+static void user_func_error(int error, const char_u *name)
+ FUNC_ATTR_NONNULL_ALL
+{
+ switch (error) {
+ case ERROR_UNKNOWN:
+ emsg_funcname(N_("E117: Unknown function: %s"), name);
+ break;
+ case ERROR_DELETED:
+ emsg_funcname(N_("E933: Function was deleted: %s"), name);
+ break;
+ case ERROR_TOOMANY:
+ emsg_funcname(_(e_toomanyarg), name);
+ break;
+ case ERROR_TOOFEW:
+ emsg_funcname(N_("E119: Not enough arguments for function: %s"),
+ name);
+ break;
+ case ERROR_SCRIPT:
+ emsg_funcname(N_("E120: Using <SID> not in a script context: %s"),
+ name);
+ break;
+ case ERROR_DICT:
+ emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"),
+ name);
+ break;
+ }
+}
+
/// Call a function with its resolved parameters
///
/// "argv_func", when not NULL, can be used to fill in arguments only when the
@@ -1316,7 +1368,7 @@ func_call_skip_call:
int
call_func(
const char_u *funcname, // name of the function
- int len, // length of "name"
+ int len, // length of "name" or -1 to use strlen()
typval_T *rettv, // [out] value goes here
int argcount_in, // number of "argvars"
typval_T *argvars_in, // vars for arguments, must have "argcount"
@@ -1333,11 +1385,11 @@ call_func(
{
int ret = FAIL;
int error = ERROR_NONE;
- ufunc_T *fp;
+ ufunc_T *fp = NULL;
char_u fname_buf[FLEN_FIXED + 1];
char_u *tofree = NULL;
- char_u *fname;
- char_u *name;
+ char_u *fname = NULL;
+ char_u *name = NULL;
int argcount = argcount_in;
typval_T *argvars = argvars_in;
dict_T *selfdict = selfdict_in;
@@ -1348,11 +1400,18 @@ call_func(
// even when call_func() returns FAIL.
rettv->v_type = VAR_UNKNOWN;
- // Make a copy of the name, if it comes from a funcref variable it could
- // be changed or deleted in the called function.
- name = vim_strnsave(funcname, len);
-
- fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ if (len <= 0) {
+ len = (int)STRLEN(funcname);
+ }
+ if (partial != NULL) {
+ fp = partial->pt_func;
+ }
+ if (fp == NULL) {
+ // Make a copy of the name, if it comes from a funcref variable it could
+ // be changed or deleted in the called function.
+ name = vim_strnsave(funcname, len);
+ fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ }
*doesrange = false;
@@ -1384,7 +1443,7 @@ call_func(
char_u *rfname = fname;
// Ignore "g:" before a function name.
- if (fname[0] == 'g' && fname[1] == ':') {
+ if (fp == NULL && fname[0] == 'g' && fname[1] == ':') {
rfname = fname + 2;
}
@@ -1395,14 +1454,11 @@ call_func(
if (is_luafunc(partial)) {
if (len > 0) {
error = ERROR_NONE;
- executor_call_lua((const char *)funcname, len,
- argvars, argcount, rettv);
+ nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv);
}
- } else if (!builtin_function((const char *)rfname, -1)) {
+ } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) {
// User defined function.
- if (partial != NULL && partial->pt_func != NULL) {
- fp = partial->pt_func;
- } else {
+ if (fp == NULL) {
fp = find_func(rfname);
}
@@ -1481,29 +1537,7 @@ theend:
// Report an error unless the argument evaluation or function call has been
// cancelled due to an aborting error, an interrupt, or an exception.
if (!aborting()) {
- switch (error) {
- case ERROR_UNKNOWN:
- emsg_funcname(N_("E117: Unknown function: %s"), name);
- break;
- case ERROR_DELETED:
- emsg_funcname(N_("E933: Function was deleted: %s"), name);
- break;
- case ERROR_TOOMANY:
- emsg_funcname(_(e_toomanyarg), name);
- break;
- case ERROR_TOOFEW:
- emsg_funcname(N_("E119: Not enough arguments for function: %s"),
- name);
- break;
- case ERROR_SCRIPT:
- emsg_funcname(N_("E120: Using <SID> not in a script context: %s"),
- name);
- break;
- case ERROR_DICT:
- emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"),
- name);
- break;
- }
+ user_func_error(error, (name != NULL) ? name : funcname);
}
while (argv_clear > 0) {
@@ -2854,7 +2888,7 @@ void ex_call(exarg_T *eap)
curwin->w_cursor.coladd = 0;
}
arg = startarg;
- if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg,
+ if (get_func_tv(name, -1, &rettv, &arg,
eap->line1, eap->line2, &doesrange,
true, partial, fudi.fd_dict) == FAIL) {
failed = true;
@@ -2886,8 +2920,10 @@ void ex_call(exarg_T *eap)
if (!failed || eap->cstack->cs_trylevel > 0) {
// Check for trailing illegal characters and a following command.
if (!ends_excmd(*arg)) {
- emsg_severe = TRUE;
- EMSG(_(e_trailing));
+ if (!failed) {
+ emsg_severe = true;
+ EMSG(_(e_trailing));
+ }
} else {
eap->nextcmd = check_nextcmd(arg);
}
@@ -3348,9 +3384,13 @@ bool set_ref_in_previous_funccal(int copyID)
{
bool abort = false;
- for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
+ for (funccall_T *fc = previous_funccal; !abort && fc != NULL;
+ fc = fc->caller) {
+ fc->fc_copyID = copyID + 1;
+ abort = abort
+ || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL)
+ || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL)
+ || set_ref_in_list(&fc->l_varlist, copyID + 1, NULL);
}
return abort;
}
@@ -3361,9 +3401,11 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID)
if (fc->fc_copyID != copyID) {
fc->fc_copyID = copyID;
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_func(NULL, fc->func, copyID);
+ abort = abort
+ || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL)
+ || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL)
+ || set_ref_in_list(&fc->l_varlist, copyID, NULL)
+ || set_ref_in_func(NULL, fc->func, copyID);
}
return abort;
}
@@ -3373,12 +3415,13 @@ bool set_ref_in_call_stack(int copyID)
{
bool abort = false;
- for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
+ for (funccall_T *fc = current_funccal; !abort && fc != NULL;
+ fc = fc->caller) {
abort = abort || set_ref_in_funccal(fc, copyID);
}
// Also go through the funccal_stack.
- for (funccal_entry_T *entry = funccal_stack; entry != NULL;
+ for (funccal_entry_T *entry = funccal_stack; !abort && entry != NULL;
entry = entry->next) {
for (funccall_T *fc = entry->top_funccal; !abort && fc != NULL;
fc = fc->caller) {
@@ -3457,12 +3500,7 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
{
char_u *name = get_lambda_name();
- ufunc_T *fp = NULL;
-
- fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
- if (fp == NULL) {
- return NULL;
- }
+ ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
fp->uf_refcount = 1;
fp->uf_varargs = true;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 519978f4fb..3669cbbd2d 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -135,7 +135,7 @@ void do_ascii(const exarg_T *const eap)
char buf1[20];
if (vim_isprintc_strict(c) && (c < ' ' || c > '~')) {
char_u buf3[7];
- transchar_nonprint(buf3, c);
+ transchar_nonprint(curbuf, buf3, c);
vim_snprintf(buf1, sizeof(buf1), " <%s>", (char *)buf3);
} else {
buf1[0] = NUL;
@@ -326,14 +326,19 @@ static int linelen(int *has_tab)
int save;
int len;
- /* find the first non-blank character */
+ // Get the line. If it's empty bail out early (could be the empty string
+ // for an unloaded buffer).
line = get_cursor_line_ptr();
+ if (*line == NUL) {
+ return 0;
+ }
+ // find the first non-blank character
first = skipwhite(line);
- /* find the character after the last non-blank character */
+ // find the character after the last non-blank character
for (last = first + STRLEN(first);
- last > first && ascii_iswhite(last[-1]); --last)
- ;
+ last > first && ascii_iswhite(last[-1]); last--) {
+ }
save = *last;
*last = NUL;
// Get line length.
@@ -846,6 +851,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
return OK;
}
+ bcount_t start_byte = ml_find_line_or_offset(curbuf, line1, NULL, true);
+ bcount_t end_byte = ml_find_line_or_offset(curbuf, line2+1, NULL, true);
+ bcount_t extent_byte = end_byte-start_byte;
+ bcount_t dest_byte = ml_find_line_or_offset(curbuf, dest+1, NULL, true);
+
num_lines = line2 - line1 + 1;
/*
@@ -880,6 +890,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
last_line = curbuf->b_ml.ml_line_count;
mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
+ int line_off = 0;
+ bcount_t byte_off = 0;
if (dest >= line2) {
mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
@@ -889,6 +901,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;
+ line_off = -num_lines;
+ byte_off = -extent_byte;
} else {
mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
@@ -904,11 +918,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
-(last_line - dest - extra), 0L, kExtmarkNOOP);
// extmarks are handled separately
- int size = line2-line1+1;
- int off = dest >= line2 ? -size : 0;
- extmark_move_region(curbuf, line1-1, 0,
- line2-line1+1, 0,
- dest+off, 0, kExtmarkUndo);
+ extmark_move_region(curbuf, line1-1, 0, start_byte,
+ line2-line1+1, 0, extent_byte,
+ dest+line_off, 0, dest_byte+byte_off,
+ kExtmarkUndo);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
@@ -2227,11 +2240,9 @@ int do_ecmd(
goto theend;
}
- /*
- * if the file was changed we may not be allowed to abandon it
- * - if we are going to re-edit the same file
- * - or if we are the only window on this file and if ECMD_HIDE is FALSE
- */
+ // If the file was changed we may not be allowed to abandon it:
+ // - if we are going to re-edit the same file
+ // - or if we are the only window on this file and if ECMD_HIDE is FALSE
if ( ((!other_file && !(flags & ECMD_OLDBUF))
|| (curbuf->b_nwindows == 1
&& !(flags & (ECMD_HIDE | ECMD_ADDBUF))))
@@ -2484,8 +2495,12 @@ int do_ecmd(
new_name = NULL;
}
set_bufref(&bufref, buf);
- if (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur) {
- // Save all the text, so that the reload can be undone.
+
+ // If the buffer was used before, store the current contents so that
+ // the reload can be undone. Do not do this if the (empty) buffer is
+ // being re-used for another file.
+ if (!(curbuf->b_flags & BF_NEVERLOADED)
+ && (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur)) {
// Sync first so that this is a separate undo-able action.
u_sync(false);
if (u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, true)
@@ -3908,6 +3923,18 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
ADJUST_SUB_FIRSTLNUM();
+ // TODO(bfredl): adjust also in preview, because decorations?
+ // this has some robustness issues, will look into later.
+ bool do_splice = !preview;
+ bcount_t replaced_bytes = 0;
+ lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ if (do_splice) {
+ for (i = 0; i < nmatch-1; i++) {
+ replaced_bytes += STRLEN(ml_get(lnum_start+i)) + 1;
+ }
+ replaced_bytes += end.col - start.col;
+ }
+
// 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
@@ -3951,17 +3978,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
current_match.end.col = new_endcol;
current_match.end.lnum = lnum;
- // TODO(bfredl): adjust in preview, because decorations?
- // this has some robustness issues, will look into later.
- if (!preview) {
- lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ if (do_splice) {
int matchcols = end.col - ((end.lnum == start.lnum)
? start.col : 0);
int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0);
extmark_splice(curbuf, lnum_start-1, start_col,
- end.lnum-start.lnum, matchcols,
- lnum-lnum_start, subcols, kExtmarkUndo);
- }
+ end.lnum-start.lnum, matchcols, replaced_bytes,
+ lnum-lnum_start, subcols, sublen-1, kExtmarkUndo);
+ }
}
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 252af409c0..d62b00fee1 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -337,7 +337,7 @@ return {
},
{
command='caddexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -409,7 +409,7 @@ return {
},
{
command='cexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR, BANG),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, BANG),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -447,7 +447,7 @@ return {
},
{
command='cgetexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -1299,7 +1299,7 @@ return {
},
{
command='laddexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -1389,7 +1389,7 @@ return {
},
{
command='lexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR, BANG),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, BANG),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -1427,7 +1427,7 @@ return {
},
{
command='lgetexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -1927,13 +1927,19 @@ return {
command='perl',
flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
- func='ex_script_ni',
+ func='ex_perl',
},
{
command='perldo',
flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
- func='ex_ni',
+ func='ex_perldo',
+ },
+ {
+ command='perlfile',
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
+ addr_type=ADDR_LINES,
+ func='ex_perlfile',
},
{
command='pedit',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 7f4b01e306..503fd8e0d0 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -935,6 +935,21 @@ void ex_pydo3(exarg_T *eap)
script_host_do_range("python3", eap);
}
+void ex_perl(exarg_T *eap)
+{
+ script_host_execute("perl", eap);
+}
+
+void ex_perlfile(exarg_T *eap)
+{
+ script_host_execute_file("perl", eap);
+}
+
+void ex_perldo(exarg_T *eap)
+{
+ script_host_do_range("perl", eap);
+}
+
// Command line expansion for :profile.
static enum {
PEXP_SUBCMD, ///< expand :profile sub-commands
@@ -1984,9 +1999,16 @@ void ex_argadd(exarg_T *eap)
/// ":argdelete"
void ex_argdelete(exarg_T *eap)
{
- if (eap->addr_count > 0) {
- // ":1,4argdel": Delete all arguments in the range.
- if (eap->line2 > ARGCOUNT) {
+ if (eap->addr_count > 0 || *eap->arg == NUL) {
+ // ":argdel" works like ":.argdel"
+ if (eap->addr_count == 0) {
+ if (curwin->w_arg_idx >= ARGCOUNT) {
+ EMSG(_("E610: No argument to delete"));
+ return;
+ }
+ eap->line1 = eap->line2 = curwin->w_arg_idx + 1;
+ } else if (eap->line2 > ARGCOUNT) {
+ // ":1,4argdel": Delete all arguments in the range.
eap->line2 = ARGCOUNT;
}
linenr_T n = eap->line2 - eap->line1 + 1;
@@ -2016,8 +2038,6 @@ void ex_argdelete(exarg_T *eap)
curwin->w_arg_idx = ARGCOUNT - 1;
}
}
- } else if (*eap->arg == NUL) {
- EMSG(_(e_argreq));
} else {
do_arglist(eap->arg, AL_DEL, 0);
}
@@ -2038,6 +2058,10 @@ void ex_listdo(exarg_T *eap)
// Don't do syntax HL autocommands. Skipping the syntax file is a
// great speed improvement.
save_ei = au_event_disable(",Syntax");
+
+ FOR_ALL_BUFFERS(buf) {
+ buf->b_flags &= ~BF_SYN_SET;
+ }
}
if (eap->cmdidx == CMD_windo
@@ -2232,9 +2256,32 @@ void ex_listdo(exarg_T *eap)
}
if (save_ei != NULL) {
+ buf_T *bnext;
+ aco_save_T aco;
+
au_event_restore(save_ei);
- apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn,
- curbuf->b_fname, true, curbuf);
+
+ for (buf_T *buf = firstbuf; buf != NULL; buf = bnext) {
+ bnext = buf->b_next;
+ if (buf->b_nwindows > 0 && (buf->b_flags & BF_SYN_SET)) {
+ buf->b_flags &= ~BF_SYN_SET;
+
+ // buffer was opened while Syntax autocommands were disabled,
+ // need to trigger them now.
+ if (buf == curbuf) {
+ apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn,
+ curbuf->b_fname, true, curbuf);
+ } else {
+ aucmd_prepbuf(&aco, buf);
+ apply_autocmds(EVENT_SYNTAX, buf->b_p_syn,
+ buf->b_fname, true, buf);
+ aucmd_restbuf(&aco);
+ }
+
+ // start over, in case autocommands messed things up.
+ bnext = firstbuf;
+ }
+ }
}
}
@@ -4152,7 +4199,7 @@ static void script_host_execute(char *name, exarg_T *eap)
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
- (void)eval_call_provider(name, "execute", args);
+ (void)eval_call_provider(name, "execute", args, true);
}
}
@@ -4167,7 +4214,7 @@ static void script_host_execute_file(char *name, exarg_T *eap)
// current range
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
- (void)eval_call_provider(name, "execute_file", args);
+ (void)eval_call_provider(name, "execute_file", args, true);
}
static void script_host_do_range(char *name, exarg_T *eap)
@@ -4176,7 +4223,7 @@ static void script_host_do_range(char *name, exarg_T *eap)
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
tv_list_append_string(args, (const char *)eap->arg, -1);
- (void)eval_call_provider(name, "do_range", args);
+ (void)eval_call_provider(name, "do_range", args, true);
}
/// ":drop"
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 1f0560ae48..ff5088ea5e 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -169,6 +169,10 @@ struct exarg {
LineGetter getline; ///< Function used to get the next line
void *cookie; ///< argument for getline()
cstack_T *cstack; ///< condition stack for ":if" etc.
+ long verbose_save; ///< saved value of p_verbose
+ int save_msg_silent; ///< saved value of msg_silent
+ int did_esilent; ///< how many times emsg_silent was incremented
+ bool did_sandbox; ///< when true did sandbox++
};
#define FORCE_BIN 1 // ":edit ++bin file"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 5bf6aa73c6..211791c19d 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -137,32 +137,6 @@ 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"
#endif
@@ -284,6 +258,27 @@ void do_exmode(int improved)
msg_scroll = save_msg_scroll;
}
+// Print the executed command for when 'verbose' is set.
+// When "lnum" is 0 only print the command.
+static void msg_verbose_cmd(linenr_T lnum, char_u *cmd)
+ FUNC_ATTR_NONNULL_ALL
+{
+ no_wait_return++;
+ verbose_enter_scroll();
+
+ if (lnum == 0) {
+ smsg(_("Executing: %s"), cmd);
+ } else {
+ smsg(_("line %" PRIdLINENR ": %s"), lnum, cmd);
+ }
+ if (msg_silent == 0) {
+ msg_puts("\n"); // don't overwrite this
+ }
+
+ verbose_leave_scroll();
+ no_wait_return--;
+}
+
/*
* Execute a simple command line. Used for translated commands like "*".
*/
@@ -593,17 +588,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
}
}
- if (p_verbose >= 15 && sourcing_name != NULL) {
- ++no_wait_return;
- verbose_enter_scroll();
-
- smsg(_("line %" PRIdLINENR ": %s"), sourcing_lnum, cmdline_copy);
- if (msg_silent == 0) {
- msg_puts("\n"); // don't overwrite this either
- }
-
- verbose_leave_scroll();
- --no_wait_return;
+ if ((p_verbose >= 15 && sourcing_name != NULL) || p_verbose >= 16) {
+ msg_verbose_cmd(sourcing_lnum, cmdline_copy);
}
/*
@@ -1235,292 +1221,6 @@ static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite)
return (char_u *)p;
}
-static void parse_state_to_global(const parse_state_T *parse_state)
-{
- 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;
-
- if (parse_state->set_eventignore) {
- set_string_option_direct(
- (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
- }
-}
-
-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;
-}
-
-//
-// 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] == '!') {
- return false;
- }
-
- /*
- * Repeat until no more command modifiers are found.
- */
- ea.cmd = *cmdlinep;
- for (;; ) {
- /*
- * 1. Skip comment lines and leading white space and colons.
- */
- while (*ea.cmd == ' '
- || *ea.cmd == '\t'
- || *ea.cmd == ':') {
- ea.cmd++;
- }
-
- // in ex mode, an empty line works like :+
- if (*ea.cmd == NUL && exmode_active
- && (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 *)"+";
- out->ex_pressedreturn = true;
- }
-
- // ignore comment and empty lines
- if (*ea.cmd == '"') {
- return false;
- }
- if (*ea.cmd == NUL) {
- out->ex_pressedreturn = true;
- return false;
- }
-
- /*
- * 2. Handle command modifiers.
- */
- char_u *p = skip_range(ea.cmd, NULL);
- switch (*p) {
- // When adding an entry, also modify cmd_exists().
- case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3))
- break;
- out->cmdmod.split |= WSP_ABOVE;
- continue;
-
- case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) {
- out->cmdmod.split |= WSP_BELOW;
- continue;
- }
- if (checkforcmd(&ea.cmd, "browse", 3)) {
- out->cmdmod.browse = true;
- continue;
- }
- if (!checkforcmd(&ea.cmd, "botright", 2)) {
- break;
- }
- out->cmdmod.split |= WSP_BOT;
- continue;
-
- case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4))
- break;
- out->cmdmod.confirm = true;
- continue;
-
- case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) {
- out->cmdmod.keepmarks = true;
- continue;
- }
- if (checkforcmd(&ea.cmd, "keepalt", 5)) {
- out->cmdmod.keepalt = true;
- continue;
- }
- if (checkforcmd(&ea.cmd, "keeppatterns", 5)) {
- out->cmdmod.keeppatterns = true;
- continue;
- }
- if (!checkforcmd(&ea.cmd, "keepjumps", 5)) {
- break;
- }
- out->cmdmod.keepjumps = true;
- continue;
-
- case 'f': { // only accept ":filter {pat} cmd"
- char_u *reg_pat;
-
- if (!checkforcmd(&p, "filter", 4) || *p == NUL || ends_excmd(*p)) {
- break;
- }
- if (*p == '!') {
- out->cmdmod.filter_force = true;
- p = skipwhite(p + 1);
- if (*p == NUL || ends_excmd(*p)) {
- break;
- }
- }
- p = skip_vimgrep_pat(p, &reg_pat, NULL);
- if (p == NULL || *p == NUL) {
- break;
- }
- 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
- case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3)
- || *p == NUL || ends_excmd(*p))
- break;
- ea.cmd = p;
- out->cmdmod.hide = true;
- continue;
-
- case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) {
- out->cmdmod.lockmarks = true;
- continue;
- }
-
- if (!checkforcmd(&ea.cmd, "leftabove", 5)) {
- break;
- }
- out->cmdmod.split |= WSP_ABOVE;
- continue;
-
- case 'n':
- if (checkforcmd(&ea.cmd, "noautocmd", 3)) {
- 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;
- }
- out->cmdmod.noswapfile = true;
- continue;
-
- case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6))
- break;
- out->cmdmod.split |= WSP_BELOW;
- continue;
-
- case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) {
- if (!out->did_sandbox) {
- out->sandbox++;
- }
- out->did_sandbox = true;
- continue;
- }
- if (!checkforcmd(&ea.cmd, "silent", 3)) {
- break;
- }
- 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"
- ea.cmd = skipwhite(ea.cmd + 1);
- 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);
-
- if (tabnr == MAXLNUM) {
- out->cmdmod.tab = tabpage_index(curtab) + 1;
- } else {
- if (tabnr < 0 || tabnr > LAST_TAB_NR) {
- out->errormsg = (char_u *)_(e_invrange);
- return false;
- }
- out->cmdmod.tab = tabnr + 1;
- }
- ea.cmd = p;
- continue;
- }
- if (!checkforcmd(&ea.cmd, "topleft", 2)) {
- break;
- }
- out->cmdmod.split |= WSP_TOP;
- continue;
-
- case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3))
- break;
- 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)) {
- out->cmdmod.split |= WSP_VERT;
- continue;
- }
- if (!checkforcmd(&p, "verbose", 4))
- break;
- 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;
- }
- 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.
-
- out->cmd = ea.cmd;
- ea.cmd = skip_range(ea.cmd, NULL);
- if (*ea.cmd == '*') {
- ea.cmd = skipwhite(ea.cmd + 1);
- }
- out->parsed_upto = find_command(&ea, NULL);
-
- *out->eap = ea;
-
- return true;
-}
-
/*
* Execute one Ex command.
*
@@ -1549,12 +1249,16 @@ static char_u * do_one_cmd(char_u **cmdlinep,
linenr_T lnum;
long n;
char_u *errormsg = NULL; // error message
+ char_u *after_modifier = NULL;
exarg_T ea;
int save_msg_scroll = msg_scroll;
- parse_state_T parsed;
cmdmod_T save_cmdmod;
const int save_reg_executing = reg_executing;
+ char_u *cmd;
+ memset(&ea, 0, sizeof(ea));
+ ea.line1 = 1;
+ ea.line2 = 1;
ex_nesting_level++;
/* When the last file has not been edited :q has to be typed twice. */
@@ -1571,31 +1275,44 @@ static char_u * do_one_cmd(char_u **cmdlinep,
* 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);
+ // "#!anything" is handled like a comment.
+ if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') {
+ goto doend;
+ }
+
+ // 1. Skip comment lines and leading white space and colons.
+ // 2. Handle command modifiers.
- // Update locals from parse_one_cmd()
- errormsg = parsed.errormsg;
- p = parsed.parsed_upto;
+ // The "ea" structure holds the arguments that can be used.
+ ea.cmd = *cmdlinep;
+ ea.cmdlinep = cmdlinep;
+ ea.getline = fgetline;
+ ea.cookie = cookie;
+ ea.cstack = cstack;
- if (!parse_success) {
+ if (parse_command_modifiers(&ea, &errormsg, false) == FAIL) {
goto doend;
}
+ after_modifier = ea.cmd;
+
ea.skip = (did_emsg
|| got_int
|| current_exception
|| (cstack->cs_idx >= 0
&& !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)));
+ // 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;
+ ea.cmd = skip_range(ea.cmd, NULL);
+ if (*ea.cmd == '*') {
+ ea.cmd = skipwhite(ea.cmd + 1);
+ }
+ p = find_command(&ea, NULL);
+
// Count this line for profiling if skip is TRUE.
if (do_profiling == PROF_YES
&& (!ea.skip || cstack->cs_idx == 0
@@ -1665,8 +1382,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,
}
}
- ea.cmd = parsed.cmd;
- if (parse_cmd_address(&ea, &errormsg) == FAIL) {
+ ea.cmd = cmd;
+ if (parse_cmd_address(&ea, &errormsg, false) == FAIL) {
goto doend;
}
@@ -1746,8 +1463,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 (parsed.after_modifier != NULL) {
- append_command(parsed.after_modifier);
+ if (after_modifier != NULL) {
+ append_command(after_modifier);
} else {
append_command(*cmdlinep);
}
@@ -2222,22 +1939,15 @@ 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 && parsed.did_esilent > 0) {
- emsg_silent -= parsed.did_esilent;
+ if (ea.cmdidx == CMD_try && ea.did_esilent > 0) {
+ emsg_silent -= ea.did_esilent;
if (emsg_silent < 0) {
emsg_silent = 0;
}
- parsed.did_esilent = 0;
+ ea.did_esilent = 0;
}
// 7. Execute the command.
- //
- // The "ea" structure holds the arguments that can be used.
- ea.cmdlinep = cmdlinep;
- ea.getline = fgetline;
- ea.cookie = cookie;
- ea.cstack = cstack;
-
if (IS_USER_CMDIDX(ea.cmdidx)) {
/*
* Execute a user-defined command.
@@ -2293,30 +2003,21 @@ doend:
? cmdnames[(int)ea.cmdidx].cmd_name
: (char_u *)NULL);
- 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,
- OPT_FREE, SID_NONE);
- free_string_option(cmdmod.save_ei);
- }
-
- if (cmdmod.filter_regmatch.regprog != NULL) {
- vim_regfree(cmdmod.filter_regmatch.regprog);
+ if (ea.verbose_save >= 0) {
+ p_verbose = ea.verbose_save;
}
+ free_cmdmod();
cmdmod = save_cmdmod;
reg_executing = save_reg_executing;
- if (parsed.save_msg_silent != -1) {
+ if (ea.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;
+ if (!did_emsg || msg_silent > ea.save_msg_silent) {
+ msg_silent = ea.save_msg_silent;
}
- emsg_silent -= parsed.did_esilent;
+ emsg_silent -= ea.did_esilent;
if (emsg_silent < 0) {
emsg_silent = 0;
}
@@ -2330,7 +2031,7 @@ doend:
msg_col = 0;
}
- if (parsed.did_sandbox) {
+ if (ea.did_sandbox) {
sandbox--;
}
@@ -2342,9 +2043,281 @@ doend:
return ea.nextcmd;
}
+// Parse and skip over command modifiers:
+// - update eap->cmd
+// - store flags in "cmdmod".
+// - Set ex_pressedreturn for an empty command line.
+// - set msg_silent for ":silent"
+// - set 'eventignore' to "all" for ":noautocmd"
+// - set p_verbose for ":verbose"
+// - Increment "sandbox" for ":sandbox"
+// When "skip_only" is true the global variables are not changed, except for
+// "cmdmod".
+// Return FAIL when the command is not to be executed.
+// May set "errormsg" to an error message.
+int parse_command_modifiers(exarg_T *eap, char_u **errormsg, bool skip_only)
+{
+ char_u *p;
+
+ memset(&cmdmod, 0, sizeof(cmdmod));
+ eap->verbose_save = -1;
+ eap->save_msg_silent = -1;
+
+ // Repeat until no more command modifiers are found.
+ for (;; ) {
+ while (*eap->cmd == ' '
+ || *eap->cmd == '\t'
+ || *eap->cmd == ':') {
+ eap->cmd++;
+ }
+
+ // in ex mode, an empty line works like :+
+ if (*eap->cmd == NUL && exmode_active
+ && (getline_equal(eap->getline, eap->cookie, getexmodeline)
+ || getline_equal(eap->getline, eap->cookie, getexline))
+ && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
+ eap->cmd = (char_u *)"+";
+ if (!skip_only) {
+ ex_pressedreturn = true;
+ }
+ }
+
+ // ignore comment and empty lines
+ if (*eap->cmd == '"') {
+ return FAIL;
+ }
+ if (*eap->cmd == NUL) {
+ if (!skip_only) {
+ ex_pressedreturn = true;
+ }
+ return FAIL;
+ }
+
+ p = skip_range(eap->cmd, NULL);
+ switch (*p) {
+ // When adding an entry, also modify cmd_exists().
+ case 'a': if (!checkforcmd(&eap->cmd, "aboveleft", 3))
+ break;
+ cmdmod.split |= WSP_ABOVE;
+ continue;
+
+ case 'b': if (checkforcmd(&eap->cmd, "belowright", 3)) {
+ cmdmod.split |= WSP_BELOW;
+ continue;
+ }
+ if (checkforcmd(&eap->cmd, "browse", 3)) {
+ cmdmod.browse = true;
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "botright", 2)) {
+ break;
+ }
+ cmdmod.split |= WSP_BOT;
+ continue;
+
+ case 'c': if (!checkforcmd(&eap->cmd, "confirm", 4))
+ break;
+ cmdmod.confirm = true;
+ continue;
+
+ case 'k': if (checkforcmd(&eap->cmd, "keepmarks", 3)) {
+ cmdmod.keepmarks = true;
+ continue;
+ }
+ if (checkforcmd(&eap->cmd, "keepalt", 5)) {
+ cmdmod.keepalt = true;
+ continue;
+ }
+ if (checkforcmd(&eap->cmd, "keeppatterns", 5)) {
+ cmdmod.keeppatterns = true;
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "keepjumps", 5)) {
+ break;
+ }
+ cmdmod.keepjumps = true;
+ continue;
+
+ case 'f': { // only accept ":filter {pat} cmd"
+ char_u *reg_pat;
+
+ if (!checkforcmd(&p, "filter", 4) || *p == NUL || ends_excmd(*p)) {
+ break;
+ }
+ if (*p == '!') {
+ cmdmod.filter_force = true;
+ p = skipwhite(p + 1);
+ if (*p == NUL || ends_excmd(*p)) {
+ break;
+ }
+ }
+ if (skip_only) {
+ p = skip_vimgrep_pat(p, NULL, NULL);
+ } else {
+ // NOTE: This puts a NUL after the pattern.
+ p = skip_vimgrep_pat(p, &reg_pat, NULL);
+ }
+ if (p == NULL || *p == NUL) {
+ break;
+ }
+ if (!skip_only) {
+ cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
+ if (cmdmod.filter_regmatch.regprog == NULL) {
+ break;
+ }
+ }
+ eap->cmd = p;
+ continue;
+ }
+
+ // ":hide" and ":hide | cmd" are not modifiers
+ case 'h': if (p != eap->cmd || !checkforcmd(&p, "hide", 3)
+ || *p == NUL || ends_excmd(*p))
+ break;
+ eap->cmd = p;
+ cmdmod.hide = true;
+ continue;
+
+ case 'l': if (checkforcmd(&eap->cmd, "lockmarks", 3)) {
+ cmdmod.lockmarks = true;
+ continue;
+ }
+
+ if (!checkforcmd(&eap->cmd, "leftabove", 5)) {
+ break;
+ }
+ cmdmod.split |= WSP_ABOVE;
+ continue;
+
+ case 'n':
+ if (checkforcmd(&eap->cmd, "noautocmd", 3)) {
+ if (cmdmod.save_ei == NULL && !skip_only) {
+ // 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);
+ }
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "noswapfile", 3)) {
+ break;
+ }
+ cmdmod.noswapfile = true;
+ continue;
+
+ case 'r': if (!checkforcmd(&eap->cmd, "rightbelow", 6))
+ break;
+ cmdmod.split |= WSP_BELOW;
+ continue;
+
+ case 's': if (checkforcmd(&eap->cmd, "sandbox", 3)) {
+ if (!skip_only) {
+ if (!eap->did_sandbox) {
+ sandbox++;
+ }
+ eap->did_sandbox = true;
+ }
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "silent", 3)) {
+ break;
+ }
+ if (!skip_only) {
+ if (eap->save_msg_silent == -1) {
+ eap->save_msg_silent = msg_silent;
+ }
+ msg_silent++;
+ }
+ if (*eap->cmd == '!' && !ascii_iswhite(eap->cmd[-1])) {
+ // ":silent!", but not "silent !cmd"
+ eap->cmd = skipwhite(eap->cmd + 1);
+ if (!skip_only) {
+ emsg_silent++;
+ eap->did_esilent++;
+ }
+ }
+ continue;
+
+ case 't': if (checkforcmd(&p, "tab", 3)) {
+ if (!skip_only) {
+ long tabnr = get_address(
+ eap, &eap->cmd, ADDR_TABS, eap->skip, skip_only, false, 1);
+
+ if (tabnr == MAXLNUM) {
+ cmdmod.tab = tabpage_index(curtab) + 1;
+ } else {
+ if (tabnr < 0 || tabnr > LAST_TAB_NR) {
+ *errormsg = (char_u *)_(e_invrange);
+ return false;
+ }
+ cmdmod.tab = tabnr + 1;
+ }
+ }
+ eap->cmd = p;
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "topleft", 2)) {
+ break;
+ }
+ cmdmod.split |= WSP_TOP;
+ continue;
+
+ case 'u': if (!checkforcmd(&eap->cmd, "unsilent", 3))
+ break;
+ if (!skip_only) {
+ if (eap->save_msg_silent == -1) {
+ eap->save_msg_silent = msg_silent;
+ }
+ msg_silent = 0;
+ }
+ continue;
+
+ case 'v': if (checkforcmd(&eap->cmd, "vertical", 4)) {
+ cmdmod.split |= WSP_VERT;
+ continue;
+ }
+ if (!checkforcmd(&p, "verbose", 4))
+ break;
+ if (!skip_only) {
+ if (eap->verbose_save < 0) {
+ eap->verbose_save = p_verbose;
+ }
+ if (ascii_isdigit(*eap->cmd)) {
+ p_verbose = atoi((char *)eap->cmd);
+ } else {
+ p_verbose = 1;
+ }
+ }
+ eap->cmd = p;
+ continue;
+ }
+ break;
+ }
+
+ return OK;
+}
+
+// Free contents of "cmdmod".
+static void free_cmdmod(void)
+{
+ if (cmdmod.save_ei != NULL) {
+ /* Restore 'eventignore' to the value before ":noautocmd". */
+ set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei,
+ OPT_FREE, SID_NONE);
+ free_string_option(cmdmod.save_ei);
+ }
+
+ if (cmdmod.filter_regmatch.regprog != NULL) {
+ vim_regfree(cmdmod.filter_regmatch.regprog);
+ }
+}
+
+
// Parse the address range, if any, in "eap".
+// May set the last search pattern, unless "silent" is true.
// Return FAIL and set "errormsg" or return OK.
-int parse_cmd_address(exarg_T *eap, char_u **errormsg)
+int parse_cmd_address(exarg_T *eap, char_u **errormsg, bool silent)
FUNC_ATTR_NONNULL_ALL
{
int address_count = 1;
@@ -2382,7 +2355,7 @@ int parse_cmd_address(exarg_T *eap, char_u **errormsg)
break;
}
eap->cmd = skipwhite(eap->cmd);
- lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip,
+ lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent,
eap->addr_count == 0, address_count++);
if (eap->cmd == NULL) { // error detected
return FAIL;
@@ -2427,6 +2400,7 @@ int parse_cmd_address(exarg_T *eap, char_u **errormsg)
}
break;
case ADDR_TABS_RELATIVE:
+ case ADDR_OTHER:
*errormsg = (char_u *)_(e_invrange);
return FAIL;
case ADDR_ARGUMENTS:
@@ -3725,18 +3699,18 @@ char_u *skip_range(
return (char_u *)cmd;
}
-/*
- * get a single EX address
- *
- * Set ptr to the next character after the part that was interpreted.
- * Set ptr to NULL when an error is encountered.
- *
- * Return MAXLNUM when no Ex address was found.
- */
+// Get a single EX address
+//
+// Set ptr to the next character after the part that was interpreted.
+// Set ptr to NULL when an error is encountered.
+// This may set the last used search pattern.
+//
+// Return MAXLNUM when no Ex address was found.
static linenr_T get_address(exarg_T *eap,
char_u **ptr,
int addr_type, // flag: one of ADDR_LINES, ...
int skip, // only skip the address, don't use it
+ bool silent, // no errors or side effects
int to_other_file, // flag: may jump to other file
int address_count) // 1 for first, >1 after comma
{
@@ -3868,13 +3842,15 @@ static linenr_T get_address(exarg_T *eap,
if (*cmd == c)
++cmd;
} else {
- pos = curwin->w_cursor; /* save curwin->w_cursor */
- /*
- * When '/' or '?' follows another address, start
- * from there.
- */
- if (lnum != MAXLNUM)
+ int flags;
+
+ pos = curwin->w_cursor; // save curwin->w_cursor
+
+ // When '/' or '?' follows another address, start from
+ // there.
+ if (lnum != MAXLNUM) {
curwin->w_cursor.lnum = lnum;
+ }
// Start a forward search at the end of the line (unless
// before the first line).
@@ -3888,7 +3864,8 @@ static linenr_T get_address(exarg_T *eap,
curwin->w_cursor.col = 0;
}
searchcmdlen = 0;
- if (!do_search(NULL, c, cmd, 1L, SEARCH_HIS | SEARCH_MSG, NULL)) {
+ flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG;
+ if (!do_search(NULL, c, cmd, 1L, flags, NULL)) {
curwin->w_cursor = pos;
cmd = NULL;
goto error;
@@ -4949,7 +4926,7 @@ check_more(
int n = ARGCOUNT - curwin->w_arg_idx - 1;
if (!forceit && only_one_window()
- && ARGCOUNT > 1 && !arg_had_last && n >= 0 && quitmore == 0) {
+ && ARGCOUNT > 1 && !arg_had_last && n > 0 && quitmore == 0) {
if (message) {
if ((p_confirm || cmdmod.confirm) && curbuf->b_fname != NULL) {
char_u buff[DIALOG_MSG_SIZE];
@@ -5026,8 +5003,13 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep,
}
if (cmp == 0) {
- if (!force) {
- EMSG(_("E174: Command already exists: add ! to replace it"));
+ // Command can be replaced with "command!" and when sourcing the
+ // same script again, but only once.
+ if (!force
+ && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
+ || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)) {
+ EMSG2(_("E174: Command already exists: add ! to replace it: %s"),
+ name);
goto fail;
}
@@ -5076,16 +5058,18 @@ fail:
static struct {
int expand;
char *name;
+ char *shortname;
} addr_type_complete[] =
{
- { ADDR_ARGUMENTS, "arguments" },
- { ADDR_LINES, "lines" },
- { ADDR_LOADED_BUFFERS, "loaded_buffers" },
- { ADDR_TABS, "tabs" },
- { ADDR_BUFFERS, "buffers" },
- { ADDR_WINDOWS, "windows" },
- { ADDR_QUICKFIX, "quickfix" },
- { -1, NULL }
+ { ADDR_ARGUMENTS, "arguments", "arg" },
+ { ADDR_LINES, "lines", "line" },
+ { ADDR_LOADED_BUFFERS, "loaded_buffers", "load" },
+ { ADDR_TABS, "tabs", "tab" },
+ { ADDR_BUFFERS, "buffers", "buf" },
+ { ADDR_WINDOWS, "windows", "win" },
+ { ADDR_QUICKFIX, "quickfix", "qf" },
+ { ADDR_OTHER, "other", "?" },
+ { -1, NULL, NULL }
};
/*
@@ -5170,7 +5154,7 @@ static void uc_list(char_u *name, size_t name_len)
// Put out the title first time
if (!found) {
MSG_PUTS_TITLE(_("\n Name Args Address "
- "Complete Definition"));
+ "Complete Definition"));
}
found = true;
msg_putchar('\n');
@@ -5256,13 +5240,13 @@ static void uc_list(char_u *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 9 - over);
+ } while (len < 8 - over);
// Address Type
for (j = 0; addr_type_complete[j].expand != -1; j++) {
if (addr_type_complete[j].expand != ADDR_LINES
&& addr_type_complete[j].expand == cmd->uc_addr_type) {
- STRCPY(IObuff + len, addr_type_complete[j].name);
+ STRCPY(IObuff + len, addr_type_complete[j].shortname);
len += (int)STRLEN(IObuff + len);
break;
}
@@ -5281,13 +5265,13 @@ static void uc_list(char_u *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 24 - over);
+ } while (len < 25 - over);
IObuff[len] = '\0';
msg_outtrans(IObuff);
msg_outtrans_special(cmd->uc_rep, false,
- name_len == 0 ? Columns - 46 : 0);
+ name_len == 0 ? Columns - 47 : 0);
if (p_verbose > 0) {
last_set_msg(cmd->uc_script_ctx);
}
@@ -5416,7 +5400,7 @@ invalid_count:
if (parse_addr_type_arg(val, (int)vallen, argt, addr_type_arg) == FAIL) {
return FAIL;
}
- if (addr_type_arg != ADDR_LINES) {
+ if (*addr_type_arg != ADDR_LINES) {
*argt |= (ZEROR | NOTADR);
}
} else {
@@ -6945,8 +6929,9 @@ void ex_splitview(exarg_T *eap)
{
win_T *old_curwin = curwin;
char_u *fname = NULL;
-
-
+ const bool use_tab = eap->cmdidx == CMD_tabedit
+ || eap->cmdidx == CMD_tabfind
+ || eap->cmdidx == CMD_tabnew;
/* A ":split" in the quickfix window works like ":new". Don't want two
* quickfix windows. But it's OK when doing ":tab split". */
@@ -6968,9 +6953,7 @@ void ex_splitview(exarg_T *eap)
/*
* Either open new tab page or split the window.
*/
- if (eap->cmdidx == CMD_tabedit
- || eap->cmdidx == CMD_tabfind
- || eap->cmdidx == CMD_tabnew) {
+ if (use_tab) {
if (win_new_tabpage(cmdmod.tab != 0 ? cmdmod.tab : eap->addr_count == 0
? 0 : (int)eap->line2 + 1, eap->arg) != FAIL) {
do_exedit(eap, old_curwin);
@@ -7155,14 +7138,14 @@ static void ex_resize(exarg_T *eap)
n = atol((char *)eap->arg);
if (cmdmod.split & WSP_VERT) {
if (*eap->arg == '-' || *eap->arg == '+') {
- n += curwin->w_width;
+ n += wp->w_width;
} else if (n == 0 && eap->arg[0] == NUL) { // default is very wide
n = Columns;
}
win_setwidth_win(n, wp);
} else {
if (*eap->arg == '-' || *eap->arg == '+') {
- n += curwin->w_height;
+ n += wp->w_height;
} else if (n == 0 && eap->arg[0] == NUL) { // default is very high
n = Rows-1;
}
@@ -7788,7 +7771,7 @@ static void ex_put(exarg_T *eap)
*/
static void ex_copymove(exarg_T *eap)
{
- long n = get_address(eap, &eap->arg, eap->addr_type, false, false, 1);
+ long n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1);
if (eap->arg == NULL) { // error detected
eap->nextcmd = NULL;
return;
@@ -8586,6 +8569,24 @@ static void ex_tag_cmd(exarg_T *eap, char_u *name)
eap->forceit, TRUE);
}
+enum {
+ SPEC_PERC = 0,
+ SPEC_HASH,
+ SPEC_CWORD,
+ SPEC_CCWORD,
+ SPEC_CEXPR,
+ SPEC_CFILE,
+ SPEC_SFILE,
+ SPEC_SLNUM,
+ SPEC_STACK,
+ SPEC_AFILE,
+ SPEC_ABUF,
+ SPEC_AMATCH,
+ SPEC_SFLNUM,
+ SPEC_SID,
+ // SPEC_CLIENT,
+};
+
/*
* Check "str" for starting with a special cmdline variable.
* If found return one of the SPEC_ values and set "*usedlen" to the length of
@@ -8596,30 +8597,21 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen)
{
size_t len;
static char *(spec_str[]) = {
- "%",
-#define SPEC_PERC 0
- "#",
-#define SPEC_HASH (SPEC_PERC + 1)
- "<cword>", // cursor word
-#define SPEC_CWORD (SPEC_HASH + 1)
- "<cWORD>", // cursor WORD
-#define SPEC_CCWORD (SPEC_CWORD + 1)
- "<cexpr>", // expr under cursor
-#define SPEC_CEXPR (SPEC_CCWORD + 1)
- "<cfile>", // cursor path name
-#define SPEC_CFILE (SPEC_CEXPR + 1)
- "<sfile>", // ":so" file name
-#define SPEC_SFILE (SPEC_CFILE + 1)
- "<slnum>", // ":so" file line number
-#define SPEC_SLNUM (SPEC_SFILE + 1)
- "<afile>", // autocommand file name
-#define SPEC_AFILE (SPEC_SLNUM + 1)
- "<abuf>", // autocommand buffer number
-#define SPEC_ABUF (SPEC_AFILE + 1)
- "<amatch>", // autocommand match name
-#define SPEC_AMATCH (SPEC_ABUF + 1)
- "<sflnum>", // script file line number
-#define SPEC_SFLNUM (SPEC_AMATCH + 1)
+ [SPEC_PERC] = "%",
+ [SPEC_HASH] = "#",
+ [SPEC_CWORD] = "<cword>", // cursor word
+ [SPEC_CCWORD] = "<cWORD>", // cursor WORD
+ [SPEC_CEXPR] = "<cexpr>", // expr under cursor
+ [SPEC_CFILE] = "<cfile>", // cursor path name
+ [SPEC_SFILE] = "<sfile>", // ":so" file name
+ [SPEC_SLNUM] = "<slnum>", // ":so" file line number
+ [SPEC_STACK] = "<stack>", // call stack
+ [SPEC_AFILE] = "<afile>", // autocommand file name
+ [SPEC_ABUF] = "<abuf>", // autocommand buffer number
+ [SPEC_AMATCH] = "<amatch>", // autocommand match name
+ [SPEC_SFLNUM] = "<sflnum>", // script file line number
+ [SPEC_SID] = "<SID>", // script ID: <SNR>123_
+ // [SPEC_CLIENT] = "<client>",
};
for (size_t i = 0; i < ARRAY_SIZE(spec_str); ++i) {
@@ -8866,6 +8858,16 @@ eval_vars (
result = (char_u *)strbuf;
break;
+ case SPEC_SID:
+ if (current_sctx.sc_sid <= 0) {
+ *errormsg = (char_u *)_(e_usingsid);
+ return NULL;
+ }
+ snprintf(strbuf, sizeof(strbuf), "<SNR>%" PRIdSCID "_",
+ current_sctx.sc_sid);
+ result = (char_u *)strbuf;
+ break;
+
default:
// should not happen
*errormsg = (char_u *)"";
@@ -9309,14 +9311,17 @@ static void ex_match(exarg_T *eap)
static void ex_fold(exarg_T *eap)
{
if (foldManualAllowed(true)) {
- foldCreate(curwin, eap->line1, eap->line2);
+ pos_T start = { eap->line1, 1, 0 };
+ pos_T end = { eap->line2, 1, 0 };
+ foldCreate(curwin, start, end);
}
}
static void ex_foldopen(exarg_T *eap)
{
- opFoldRange(eap->line1, eap->line2, eap->cmdidx == CMD_foldopen,
- eap->forceit, FALSE);
+ pos_T start = { eap->line1, 1, 0 };
+ pos_T end = { eap->line2, 1, 0 };
+ opFoldRange(start, end, eap->cmdidx == CMD_foldopen, eap->forceit, false);
}
static void ex_folddo(exarg_T *eap)
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 81274fcf2a..0c7562980a 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -87,17 +87,16 @@
*/
static int cause_abort = FALSE;
-/*
- * Return TRUE when immediately aborting on error, or when an interrupt
- * occurred or an exception was thrown but not caught. Use for ":{range}call"
- * to check whether an aborted function that does not handle a range itself
- * should be called again for the next line in the range. Also used for
- * cancelling expression evaluation after a function call caused an immediate
- * abort. Note that the first emsg() call temporarily resets "force_abort"
- * until the throw point for error messages has been reached. That is, during
- * cancellation of an expression evaluation after an aborting function call or
- * due to a parsing error, aborting() always returns the same value.
- */
+// Return true when immediately aborting on error, or when an interrupt
+// occurred or an exception was thrown but not caught. Use for ":{range}call"
+// to check whether an aborted function that does not handle a range itself
+// should be called again for the next line in the range. Also used for
+// cancelling expression evaluation after a function call caused an immediate
+// abort. Note that the first emsg() call temporarily resets "force_abort"
+// until the throw point for error messages has been reached. That is, during
+// cancellation of an expression evaluation after an aborting function call or
+// due to a parsing error, aborting() always returns the same value.
+// "got_int" is also set by calling interrupt().
int aborting(void)
{
return (did_emsg && force_abort) || got_int || current_exception;
@@ -139,16 +138,15 @@ int aborted_in_try(void)
return force_abort;
}
-/*
- * cause_errthrow(): Cause a throw of an error exception if appropriate.
- * Return TRUE if the error message should not be displayed by emsg().
- * Sets "ignore", if the emsg() call should be ignored completely.
- *
- * When several messages appear in the same command, the first is usually the
- * most specific one and used as the exception value. The "severe" flag can be
- * set to TRUE, if a later but severer message should be used instead.
- */
-int cause_errthrow(char_u *mesg, int severe, int *ignore)
+// cause_errthrow(): Cause a throw of an error exception if appropriate.
+// Return true if the error message should not be displayed by emsg().
+// Sets "ignore", if the emsg() call should be ignored completely.
+//
+// When several messages appear in the same command, the first is usually the
+// most specific one and used as the exception value. The "severe" flag can be
+// set to true, if a later but severer message should be used instead.
+bool cause_errthrow(const char_u *mesg, bool severe, bool *ignore)
+ FUNC_ATTR_NONNULL_ALL
{
struct msglist *elem;
struct msglist **plist;
@@ -159,8 +157,9 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore)
* level. Also when no exception can be thrown. The message will be
* displayed by emsg().
*/
- if (suppress_errthrow)
- return FALSE;
+ if (suppress_errthrow) {
+ return false;
+ }
/*
* If emsg() has not been called previously, temporarily reset
@@ -196,8 +195,8 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore)
* not replaced by an interrupt message error exception.
*/
if (mesg == (char_u *)_(e_interr)) {
- *ignore = TRUE;
- return TRUE;
+ *ignore = true;
+ return true;
}
/*
@@ -232,8 +231,8 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore)
* catch clause; just finally clauses are executed before the script
* is terminated.
*/
- return FALSE;
- } else
+ return false;
+ } else // NOLINT(readability/braces)
#endif
{
/*
@@ -272,7 +271,7 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore)
(*msg_list)->throw_msg = tmsg;
}
}
- return TRUE;
+ return true;
}
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 93684e8606..d67e9b2d7e 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -137,6 +137,7 @@ struct cmdline_info {
/// Last value of prompt_id, incremented when doing new prompt
static unsigned last_prompt_id = 0;
+// Struct to store the viewstate during 'incsearch' highlighting.
typedef struct {
colnr_T vs_curswant;
colnr_T vs_leftcol;
@@ -146,6 +147,19 @@ typedef struct {
int vs_empty_rows;
} viewstate_T;
+// Struct to store the state of 'incsearch' highlighting.
+typedef struct {
+ pos_T search_start; // where 'incsearch' starts searching
+ pos_T save_cursor;
+ viewstate_T init_viewstate;
+ viewstate_T old_viewstate;
+ pos_T match_start;
+ pos_T match_end;
+ bool did_incsearch;
+ bool incsearch_postponed;
+ int magic_save;
+} incsearch_state_T;
+
typedef struct command_line_state {
VimState state;
int firstc;
@@ -159,14 +173,7 @@ typedef struct command_line_state {
int save_hiscnt; // history line before attempting
// to jump to next match
int histype; // history type to be used
- pos_T search_start; // where 'incsearch' starts searching
- pos_T save_cursor;
- viewstate_T init_viewstate;
- viewstate_T old_viewstate;
- pos_T match_start;
- pos_T match_end;
- int did_incsearch;
- int incsearch_postponed;
+ incsearch_state_T is_state;
int did_wild_list; // did wild_list() recently
int wim_index; // index in wim_flags[]
int res;
@@ -252,6 +259,395 @@ static void restore_viewstate(viewstate_T *vs)
curwin->w_empty_rows = vs->vs_empty_rows;
}
+static void init_incsearch_state(incsearch_state_T *s)
+{
+ s->match_start = curwin->w_cursor;
+ s->did_incsearch = false;
+ s->incsearch_postponed = false;
+ s->magic_save = p_magic;
+ clearpos(&s->match_end);
+ s->save_cursor = curwin->w_cursor; // may be restored later
+ s->search_start = curwin->w_cursor;
+ save_viewstate(&s->init_viewstate);
+ save_viewstate(&s->old_viewstate);
+}
+
+// Return true when 'incsearch' highlighting is to be done.
+// Sets search_first_line and search_last_line to the address range.
+static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s,
+ int *skiplen, int *patlen)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char_u *cmd;
+ cmdmod_T save_cmdmod = cmdmod;
+ char_u *p;
+ bool delim_optional = false;
+ int delim;
+ char_u *end;
+ char_u *dummy;
+ exarg_T ea;
+ pos_T save_cursor;
+ bool use_last_pat;
+ bool retval = false;
+
+ *skiplen = 0;
+ *patlen = ccline.cmdlen;
+
+ if (!p_is || cmd_silent) {
+ return false;
+ }
+
+ // by default search all lines
+ search_first_line = 0;
+ search_last_line = MAXLNUM;
+
+ if (firstc == '/' || firstc == '?') {
+ return true;
+ }
+ if (firstc != ':') {
+ return false;
+ }
+
+ emsg_off++;
+ memset(&ea, 0, sizeof(ea));
+ ea.line1 = 1;
+ ea.line2 = 1;
+ ea.cmd = ccline.cmdbuff;
+ ea.addr_type = ADDR_LINES;
+
+ parse_command_modifiers(&ea, &dummy, true);
+ cmdmod = save_cmdmod;
+
+ cmd = skip_range(ea.cmd, NULL);
+ if (vim_strchr((char_u *)"sgvl", *cmd) == NULL) {
+ goto theend;
+ }
+
+ // Skip over "substitute" to find the pattern separator.
+ for (p = cmd; ASCII_ISALPHA(*p); p++) {}
+ if (*skipwhite(p) == NUL) {
+ goto theend;
+ }
+
+ if (STRNCMP(cmd, "substitute", p - cmd) == 0
+ || STRNCMP(cmd, "smagic", p - cmd) == 0
+ || STRNCMP(cmd, "snomagic", MAX(p - cmd, 3)) == 0
+ || STRNCMP(cmd, "vglobal", p - cmd) == 0) {
+ if (*cmd == 's' && cmd[1] == 'm') {
+ p_magic = true;
+ } else if (*cmd == 's' && cmd[1] == 'n') {
+ p_magic = false;
+ }
+ } else if (STRNCMP(cmd, "sort", MAX(p - cmd, 3)) == 0) {
+ // skip over ! and flags
+ if (*p == '!') {
+ p = skipwhite(p + 1);
+ }
+ while (ASCII_ISALPHA(*(p = skipwhite(p)))) {
+ p++;
+ }
+ if (*p == NUL) {
+ goto theend;
+ }
+ } else if (STRNCMP(cmd, "vimgrep", MAX(p - cmd, 3)) == 0
+ || STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0
+ || STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0
+ || STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0
+ || STRNCMP(cmd, "global", p - cmd) == 0) {
+ // skip over "!/".
+ if (*p == '!') {
+ p++;
+ if (*skipwhite(p) == NUL) {
+ goto theend;
+ }
+ }
+ if (*cmd != 'g') {
+ delim_optional = true;
+ }
+ } else {
+ goto theend;
+ }
+
+ p = skipwhite(p);
+ delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++;
+ end = skip_regexp(p, delim, p_magic, NULL);
+
+ use_last_pat = end == p && *end == delim;
+ if (end == p && !use_last_pat) {
+ goto theend;
+ }
+
+ // Don't do 'hlsearch' highlighting if the pattern matches everything.
+ if (!use_last_pat) {
+ char_u c = *end;
+ int empty;
+
+ *end = NUL;
+ empty = empty_pattern(p);
+ *end = c;
+ if (empty) {
+ goto theend;
+ }
+ }
+
+ // found a non-empty pattern or //
+ *skiplen = (int)(p - ccline.cmdbuff);
+ *patlen = (int)(end - p);
+
+ // parse the address range
+ save_cursor = curwin->w_cursor;
+ curwin->w_cursor = s->search_start;
+ parse_cmd_address(&ea, &dummy, true);
+ if (ea.addr_count > 0) {
+ // Allow for reverse match.
+ if (ea.line2 < ea.line1) {
+ search_first_line = ea.line2;
+ search_last_line = ea.line1;
+ } else {
+ search_first_line = ea.line1;
+ search_last_line = ea.line2;
+ }
+ } else if (cmd[0] == 's' && cmd[1] != 'o') {
+ // :s defaults to the current line
+ search_first_line = curwin->w_cursor.lnum;
+ search_last_line = curwin->w_cursor.lnum;
+ }
+
+ curwin->w_cursor = save_cursor;
+ retval = true;
+theend:
+ emsg_off--;
+ return retval;
+}
+
+// May do 'incsearch' highlighting if desired.
+static void may_do_incsearch_highlighting(int firstc, long count,
+ incsearch_state_T *s)
+{
+ pos_T end_pos;
+ proftime_T tm;
+ searchit_arg_T sia;
+ int skiplen, patlen;
+ char_u next_char;
+ char_u use_last_pat;
+
+ // Parsing range may already set the last search pattern.
+ // NOTE: must call restore_last_search_pattern() before returning!
+ save_last_search_pattern();
+
+ if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
+ restore_last_search_pattern();
+ finish_incsearch_highlighting(false, s, true);
+ return;
+ }
+
+ // if there is a character waiting, search and redraw later
+ if (char_avail()) {
+ restore_last_search_pattern();
+ s->incsearch_postponed = true;
+ return;
+ }
+ s->incsearch_postponed = false;
+
+ if (search_first_line == 0) {
+ // start at the original cursor position
+ curwin->w_cursor = s->search_start;
+ } else if (search_first_line > curbuf->b_ml.ml_line_count) {
+ // start after the last line
+ curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ curwin->w_cursor.col = MAXCOL;
+ } else {
+ // start at the first line in the range
+ curwin->w_cursor.lnum = search_first_line;
+ curwin->w_cursor.col = 0;
+ }
+ int found; // do_search() result
+
+ // Use the previous pattern for ":s//".
+ next_char = ccline.cmdbuff[skiplen + patlen];
+ use_last_pat = patlen == 0 && skiplen > 0
+ && ccline.cmdbuff[skiplen - 1] == next_char;
+
+ // If there is no pattern, don't do anything.
+ if (patlen == 0 && !use_last_pat) {
+ found = 0;
+ set_no_hlsearch(true); // turn off previous highlight
+ redraw_all_later(SOME_VALID);
+ } else {
+ int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK;
+ ui_busy_start();
+ ui_flush();
+ emsg_off++; // So it doesn't beep if bad expr
+ // Set the time limit to half a second.
+ tm = profile_setlimit(500L);
+ if (!p_hls) {
+ search_flags += SEARCH_KEEP;
+ }
+ if (search_first_line != 0) {
+ search_flags += SEARCH_START;
+ }
+ ccline.cmdbuff[skiplen + patlen] = NUL;
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_tm = &tm;
+ found = do_search(NULL, firstc == ':' ? '/' : firstc,
+ ccline.cmdbuff + skiplen, count,
+ search_flags, &sia);
+ ccline.cmdbuff[skiplen + patlen] = next_char;
+ emsg_off--;
+ if (curwin->w_cursor.lnum < search_first_line
+ || curwin->w_cursor.lnum > search_last_line) {
+ // match outside of address range
+ found = 0;
+ curwin->w_cursor = s->search_start;
+ }
+
+ // if interrupted while searching, behave like it failed
+ if (got_int) {
+ (void)vpeekc(); // remove <C-C> from input stream
+ got_int = false; // don't abandon the command line
+ found = 0;
+ } else if (char_avail()) {
+ // cancelled searching because a char was typed
+ s->incsearch_postponed = true;
+ }
+ ui_busy_stop();
+ }
+
+ if (found != 0) {
+ highlight_match = true; // highlight position
+ } else {
+ highlight_match = false; // remove highlight
+ }
+
+ // first restore the old curwin values, so the screen is
+ // positioned in the same way as the actual search command
+ restore_viewstate(&s->old_viewstate);
+ changed_cline_bef_curs();
+ update_topline();
+
+ if (found != 0) {
+ pos_T save_pos = curwin->w_cursor;
+
+ s->match_start = curwin->w_cursor;
+ set_search_match(&curwin->w_cursor);
+ validate_cursor();
+ end_pos = curwin->w_cursor;
+ s->match_end = end_pos;
+ curwin->w_cursor = save_pos;
+ } else {
+ end_pos = curwin->w_cursor; // shutup gcc 4
+ }
+ //
+ // Disable 'hlsearch' highlighting if the pattern matches
+ // everything. Avoids a flash when typing "foo\|".
+ if (!use_last_pat) {
+ next_char = ccline.cmdbuff[skiplen + patlen];
+ ccline.cmdbuff[skiplen + patlen] = NUL;
+ if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) {
+ redraw_all_later(SOME_VALID);
+ set_no_hlsearch(true);
+ }
+ ccline.cmdbuff[skiplen + patlen] = next_char;
+ }
+
+ validate_cursor();
+ // May redraw the status line to show the cursor position.
+ if (p_ru && curwin->w_status_height > 0) {
+ curwin->w_redr_status = true;
+ }
+
+ update_screen(SOME_VALID);
+ highlight_match = false;
+ restore_last_search_pattern();
+
+ // Leave it at the end to make CTRL-R CTRL-W work. But not when beyond the
+ // end of the pattern, e.g. for ":s/pat/".
+ if (ccline.cmdbuff[skiplen + patlen] != NUL) {
+ curwin->w_cursor = s->search_start;
+ } else if (found != 0) {
+ curwin->w_cursor = end_pos;
+ }
+
+ msg_starthere();
+ redrawcmdline();
+ s->did_incsearch = true;
+}
+
+// When CTRL-L typed: add character from the match to the pattern.
+// May set "*c" to the added character.
+// Return OK when calling command_line_not_changed.
+static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int skiplen, patlen;
+
+ // Parsing range may already set the last search pattern.
+ // NOTE: must call restore_last_search_pattern() before returning!
+ save_last_search_pattern();
+
+ // Add a character from under the cursor for 'incsearch'
+ if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
+ restore_last_search_pattern();
+ return FAIL;
+ }
+ restore_last_search_pattern();
+
+ if (s->did_incsearch) {
+ curwin->w_cursor = s->match_end;
+ *c = gchar_cursor();
+ if (*c != NUL) {
+ // If 'ignorecase' and 'smartcase' are set and the
+ // command line has no uppercase characters, convert
+ // the character to lowercase
+ if (p_ic && p_scs
+ && !pat_has_uppercase(ccline.cmdbuff + skiplen)) {
+ *c = mb_tolower(*c);
+ }
+ if (*c == firstc
+ || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), *c)
+ != NULL) {
+ // put a backslash before special characters
+ stuffcharReadbuff(*c);
+ *c = '\\';
+ }
+ return FAIL;
+ }
+ }
+ return OK;
+}
+
+static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s,
+ bool call_update_screen)
+{
+ if (s->did_incsearch) {
+ s->did_incsearch = false;
+ if (gotesc) {
+ curwin->w_cursor = s->save_cursor;
+ } else {
+ if (!equalpos(s->save_cursor, s->search_start)) {
+ // put the '" mark at the original position
+ curwin->w_cursor = s->save_cursor;
+ setpcmark();
+ }
+ curwin->w_cursor = s->search_start; // -V519
+ }
+ restore_viewstate(&s->old_viewstate);
+ highlight_match = false;
+
+ // by default search all lines
+ search_first_line = 0;
+ search_last_line = MAXLNUM;
+
+ p_magic = s->magic_save;
+
+ validate_cursor(); // needed for TAB
+ redraw_all_later(SOME_VALID);
+ if (call_update_screen) {
+ update_screen(SOME_VALID);
+ }
+ }
+}
+
/// Internal entry point for cmdline mode.
///
/// caller must use save_cmdline and restore_cmdline. Best is to use
@@ -269,11 +665,10 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
.save_msg_scroll = msg_scroll,
.save_State = State,
.ignore_drag_release = true,
- .match_start = curwin->w_cursor,
};
CommandLineState *s = &state;
s->save_p_icm = vim_strsave(p_icm);
- save_viewstate(&state.init_viewstate);
+ init_incsearch_state(&s->is_state);
if (s->firstc == -1) {
s->firstc = NUL;
@@ -288,10 +683,6 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ccline.prompt_id = last_prompt_id++;
ccline.level = cmdline_level;
ccline.overstrike = false; // always start in insert mode
- clearpos(&s->match_end);
- s->save_cursor = curwin->w_cursor; // may be restored later
- s->search_start = curwin->w_cursor;
- save_viewstate(&state.old_viewstate);
assert(indent >= 0);
@@ -455,22 +846,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
close_preview_windows();
}
- if (s->did_incsearch) {
- if (s->gotesc) {
- curwin->w_cursor = s->save_cursor;
- } else {
- if (!equalpos(s->save_cursor, s->search_start)) {
- // put the '" mark at the original position
- curwin->w_cursor = s->save_cursor;
- setpcmark();
- }
- curwin->w_cursor = s->search_start; // -V519
- }
- restore_viewstate(&s->old_viewstate);
- highlight_match = false;
- validate_cursor(); // needed for TAB
- redraw_all_later(SOME_VALID);
- }
+ finish_incsearch_highlighting(s->gotesc, &s->is_state, false);
if (ccline.cmdbuff != NULL) {
// Put line in history buffer (":" and "=" only when it was typed).
@@ -1075,24 +1451,46 @@ static int command_line_execute(VimState *state, int key)
return command_line_handle_key(s);
}
-static void command_line_next_incsearch(CommandLineState *s, bool next_match)
+// May adjust 'incsearch' highlighting for typing CTRL-G and CTRL-T, go to next
+// or previous match.
+// Returns FAIL when calling command_line_not_changed.
+static int may_do_command_line_next_incsearch(int firstc, long count,
+ incsearch_state_T *s,
+ bool next_match)
+ FUNC_ATTR_NONNULL_ALL
{
+ int skiplen, patlen;
+
+ // Parsing range may already set the last search pattern.
+ // NOTE: must call restore_last_search_pattern() before returning!
+ save_last_search_pattern();
+
+ if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
+ restore_last_search_pattern();
+ return OK;
+ }
+ if (patlen == 0 && ccline.cmdbuff[skiplen] == NUL) {
+ restore_last_search_pattern();
+ return FAIL;
+ }
+
ui_busy_start();
ui_flush();
pos_T t;
char_u *pat;
int search_flags = SEARCH_NOOF;
+ char_u save;
- if (s->firstc == ccline.cmdbuff[0]) {
+ if (firstc == ccline.cmdbuff[skiplen]) {
pat = last_search_pattern();
+ skiplen = 0;
+ patlen = (int)STRLEN(pat);
} else {
- pat = ccline.cmdbuff;
+ pat = ccline.cmdbuff + skiplen;
}
- save_last_search_pattern();
-
if (next_match) {
t = s->match_end;
if (lt(s->match_start, s->match_end)) {
@@ -1108,23 +1506,26 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
search_flags += SEARCH_KEEP;
}
emsg_off++;
+ save = pat[patlen];
+ pat[patlen] = NUL;
int found = searchit(curwin, curbuf, &t, NULL,
next_match ? FORWARD : BACKWARD,
- pat, s->count, search_flags,
+ pat, count, search_flags,
RE_SEARCH, NULL);
emsg_off--;
+ pat[patlen] = save;
ui_busy_stop();
if (found) {
s->search_start = s->match_start;
s->match_end = t;
s->match_start = t;
- if (!next_match && s->firstc == '/') {
+ if (!next_match && firstc != '?') {
// move just before the current match, so that
// when nv_search finishes the cursor will be
// put back on the match
s->search_start = t;
(void)decl(&s->search_start);
- } else if (next_match && s->firstc == '?') {
+ } else if (next_match && firstc == '?') {
// move just after the current match, so that
// when nv_search finishes the cursor will be
// put back on the match
@@ -1134,7 +1535,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
if (lt(t, s->search_start) && next_match) {
// wrap around
s->search_start = t;
- if (s->firstc == '?') {
+ if (firstc == '?') {
(void)incl(&s->search_start);
} else {
(void)decl(&s->search_start);
@@ -1149,12 +1550,14 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
highlight_match = true;
save_viewstate(&s->old_viewstate);
update_screen(NOT_VALID);
+ highlight_match = false;
redrawcmdline();
+ curwin->w_cursor = s->match_end;
} else {
vim_beep(BO_ERROR);
}
restore_last_search_pattern();
- return;
+ return FAIL;
}
static void command_line_next_histidx(CommandLineState *s, bool next_match)
@@ -1265,10 +1668,10 @@ static int command_line_handle_key(CommandLineState *s)
// Truncate at the end, required for multi-byte chars.
ccline.cmdbuff[ccline.cmdlen] = NUL;
if (ccline.cmdlen == 0) {
- s->search_start = s->save_cursor;
+ s->is_state.search_start = s->is_state.save_cursor;
// save view settings, so that the screen won't be restored at the
// wrong position
- s->old_viewstate = s->init_viewstate;
+ s->is_state.old_viewstate = s->is_state.init_viewstate;
}
redrawcmd();
} else if (ccline.cmdlen == 0 && s->c != Ctrl_W
@@ -1287,7 +1690,7 @@ static int command_line_handle_key(CommandLineState *s)
}
msg_putchar(' '); // delete ':'
}
- s->search_start = s->save_cursor;
+ s->is_state.search_start = s->is_state.save_cursor;
redraw_cmdline = true;
return 0; // back to cmd mode
}
@@ -1337,7 +1740,7 @@ static int command_line_handle_key(CommandLineState *s)
// Truncate at the end, required for multi-byte chars.
ccline.cmdbuff[ccline.cmdlen] = NUL;
if (ccline.cmdlen == 0) {
- s->search_start = s->save_cursor;
+ s->is_state.search_start = s->is_state.save_cursor;
}
redrawcmd();
return command_line_changed(s);
@@ -1565,31 +1968,7 @@ static int command_line_handle_key(CommandLineState *s)
return command_line_changed(s);
case Ctrl_L:
- if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
- // Add a character from under the cursor for 'incsearch'
- if (s->did_incsearch) {
- curwin->w_cursor = s->match_end;
- if (!equalpos(curwin->w_cursor, s->search_start)) {
- s->c = gchar_cursor();
- // If 'ignorecase' and 'smartcase' are set and the
- // command line has no uppercase characters, convert
- // the character to lowercase
- if (p_ic && p_scs
- && !pat_has_uppercase(ccline.cmdbuff)) {
- s->c = mb_tolower(s->c);
- }
- if (s->c != NUL) {
- if (s->c == s->firstc
- || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), s->c)
- != NULL) {
- // put a backslash before special characters
- stuffcharReadbuff(s->c);
- s->c = '\\';
- }
- break;
- }
- }
- }
+ if (may_add_char_to_search(s->firstc, &s->c, &s->is_state) == OK) {
return command_line_not_changed(s);
}
@@ -1703,10 +2082,8 @@ static int command_line_handle_key(CommandLineState *s)
case Ctrl_G: // next match
case Ctrl_T: // previous match
- if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
- if (ccline.cmdlen != 0) {
- command_line_next_incsearch(s, s->c == Ctrl_G);
- }
+ if (may_do_command_line_next_incsearch(s->firstc, s->count, &s->is_state,
+ s->c == Ctrl_G) == FAIL) {
return command_line_not_changed(s);
}
break;
@@ -1791,7 +2168,7 @@ static int command_line_not_changed(CommandLineState *s)
// command line did not change. Then we only search and redraw if something
// changed in the past.
// Enter command_line_changed() when the command line did change.
- if (!s->incsearch_postponed) {
+ if (!s->is_state.incsearch_postponed) {
return 1;
}
return command_line_changed(s);
@@ -1843,108 +2220,13 @@ static int command_line_changed(CommandLineState *s)
}
// 'incsearch' highlighting.
- if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
- pos_T end_pos;
- proftime_T tm;
- searchit_arg_T sia;
-
- // if there is a character waiting, search and redraw later
- if (char_avail()) {
- s->incsearch_postponed = true;
- return 1;
- }
- s->incsearch_postponed = false;
- curwin->w_cursor = s->search_start; // start at old position
- save_last_search_pattern();
- int i;
-
- // If there is no command line, don't do anything
- if (ccline.cmdlen == 0) {
- i = 0;
- set_no_hlsearch(true); // turn off previous highlight
- redraw_all_later(SOME_VALID);
- } else {
- int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK;
- ui_busy_start();
- ui_flush();
- ++emsg_off; // So it doesn't beep if bad expr
- // Set the time limit to half a second.
- tm = profile_setlimit(500L);
- if (!p_hls) {
- search_flags += SEARCH_KEEP;
- }
- memset(&sia, 0, sizeof(sia));
- sia.sa_tm = &tm;
- i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count,
- search_flags, &sia);
- emsg_off--;
- // if interrupted while searching, behave like it failed
- if (got_int) {
- (void)vpeekc(); // remove <C-C> from input stream
- got_int = false; // don't abandon the command line
- i = 0;
- } else if (char_avail()) {
- // cancelled searching because a char was typed
- s->incsearch_postponed = true;
- }
- ui_busy_stop();
- }
-
- if (i != 0) {
- highlight_match = true; // highlight position
- } else {
- highlight_match = false; // remove highlight
- }
-
- // first restore the old curwin values, so the screen is
- // positioned in the same way as the actual search command
- restore_viewstate(&s->old_viewstate);
- changed_cline_bef_curs();
- update_topline();
-
- if (i != 0) {
- pos_T save_pos = curwin->w_cursor;
-
- s->match_start = curwin->w_cursor;
- set_search_match(&curwin->w_cursor);
- validate_cursor();
- end_pos = curwin->w_cursor;
- s->match_end = end_pos;
- curwin->w_cursor = save_pos;
- } else {
- end_pos = curwin->w_cursor; // shutup gcc 4
- }
-
- // Disable 'hlsearch' highlighting if the pattern matches
- // everything. Avoids a flash when typing "foo\|".
- if (empty_pattern(ccline.cmdbuff)) {
- set_no_hlsearch(true);
- }
-
- validate_cursor();
- // May redraw the status line to show the cursor position.
- if (p_ru && curwin->w_status_height > 0) {
- curwin->w_redr_status = true;
- }
-
- update_screen(SOME_VALID);
- restore_last_search_pattern();
-
- // Leave it at the end to make CTRL-R CTRL-W work.
- if (i != 0) {
- curwin->w_cursor = end_pos;
- }
-
- msg_starthere();
- redrawcmdline();
- s->did_incsearch = true;
- } else if (s->firstc == ':'
- && current_sctx.sc_sid == 0 // only if interactive
- && *p_icm != NUL // 'inccommand' is set
- && curbuf->b_p_ma // buffer is modifiable
- && cmdline_star == 0 // not typing a password
- && cmd_can_preview(ccline.cmdbuff)
- && !vpeekc_any()) {
+ if (s->firstc == ':'
+ && current_sctx.sc_sid == 0 // only if interactive
+ && *p_icm != NUL // 'inccommand' is set
+ && curbuf->b_p_ma // buffer is modifiable
+ && cmdline_star == 0 // not typing a password
+ && cmd_can_preview(ccline.cmdbuff)
+ && !vpeekc_any()) {
// Show 'inccommand' preview. It works like this:
// 1. Do the command.
// 2. Command implementation detects CMDPREVIEW state, then:
@@ -1958,8 +2240,8 @@ static int command_line_changed(CommandLineState *s)
emsg_silent--; // Unblock error reporting
// Restore the window "view".
- curwin->w_cursor = s->save_cursor;
- restore_viewstate(&s->old_viewstate);
+ curwin->w_cursor = s->is_state.save_cursor;
+ restore_viewstate(&s->is_state.old_viewstate);
update_topline();
redrawcmdline();
@@ -1968,6 +2250,8 @@ static int command_line_changed(CommandLineState *s)
State = (State & ~CMDPREVIEW);
close_preview_windows();
update_screen(SOME_VALID); // Clear 'inccommand' preview.
+ } else {
+ may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state);
}
if (cmdmsg_rl || (p_arshape && !p_tbidi && enc_utf8)) {
@@ -4706,7 +4990,7 @@ ExpandFromContext (
char_u *pat,
int *num_file,
char_u ***file,
- int options /* EW_ flags */
+ int options // WILD_ flags
)
{
regmatch_T regmatch;
@@ -4768,6 +5052,21 @@ ExpandFromContext (
ret = expand_wildcards_eval(&pat, num_file, file, flags);
if (free_pat)
xfree(pat);
+#ifdef BACKSLASH_IN_FILENAME
+ if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) {
+ for (int i = 0; i < *num_file; i++) {
+ char_u *ptr = (*file)[i];
+ while (*ptr != NUL) {
+ if (p_csl[0] == 's' && *ptr == '\\') {
+ *ptr = '/';
+ } else if (p_csl[0] == 'b' && *ptr == '/') {
+ *ptr = '\\';
+ }
+ ptr += utfc_ptr2len(ptr);
+ }
+ }
+ }
+#endif
return ret;
}
@@ -4909,7 +5208,7 @@ ExpandFromContext (
* obtain strings, one by one. The strings are matched against a regexp
* program. Matching strings are copied into an array, which is returned.
*/
-void ExpandGeneric(
+static void ExpandGeneric(
expand_T *xp,
regmatch_T *regmatch,
int *num_file,
@@ -6189,12 +6488,15 @@ static int open_cmdwin(void)
// Save the command line info, can be used recursively.
save_cmdline(&save_ccline);
- /* No Ex mode here! */
+ // No Ex mode here!
exmode_active = 0;
State = NORMAL;
setmouse();
+ // Reset here so it can be set by a CmdWinEnter autocommand.
+ cmdwin_result = 0;
+
// Trigger CmdwinEnter autocommands.
typestr[0] = (char_u)cmdwin_type;
typestr[1] = NUL;
@@ -6210,7 +6512,6 @@ static int open_cmdwin(void)
/*
* Call the main loop until <CR> or CTRL-C is typed.
*/
- cmdwin_result = 0;
normal_enter(true, false);
RedrawingDisabled = i;
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 1b797c6168..6b00b986dc 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -493,7 +493,8 @@ static int put_view(
/// Writes commands for restoring the current buffers, for :mksession.
///
-/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled.
+/// Legacy 'sessionoptions'/'viewoptions' flags SSOP_UNIX, SSOP_SLASH are
+/// always enabled.
///
/// @param dirnow Current directory name
/// @param fd File descriptor to write to
@@ -822,9 +823,9 @@ void ex_loadview(exarg_T *eap)
/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession".
///
-/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled.
-/// - SSOP_UNIX: line-endings are always LF
-/// - SSOP_SLASH: filenames are always written with "/" slash
+/// Legacy 'sessionoptions'/'viewoptions' flags are always enabled:
+/// - SSOP_UNIX: line-endings are LF
+/// - SSOP_SLASH: filenames are written with "/" slash
void ex_mkrc(exarg_T *eap)
{
FILE *fd;
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 1457a1172d..0de396fd1f 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -1,29 +1,24 @@
// 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.
+// Implements extended marks for plugins. Marks sit in a MarkTree
+// datastructure which provides both efficient mark insertations/lookups
+// and adjustment to text changes. See marktree.c for more details.
//
-// 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_splice. Additionally mark_adjust
-// might adjust extmarks to line inserts/deletes.
+// Marks are moved by calls to extmark_splice. Some standard interfaces
+// mark_adjust and inserted_bytes already adjust marks, check if these are
+// being used before adding extmark_splice calls!
//
// Undo/Redo of marks is implemented by storing the call arguments to
// extmark_splice. 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.
+// We have to copy extmark positions when the extmarks are within a
+// deleted/changed region.
//
// 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.
@@ -48,6 +43,13 @@
# include "extmark.c.generated.h"
#endif
+static PMap(uint64_t) *hl_decors;
+
+void extmark_init(void)
+{
+ hl_decors = pmap_new(uint64_t)();
+}
+
static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) {
if (!buf->b_extmark_ns) {
if (!put) {
@@ -71,7 +73,8 @@ static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) {
/// must not be used during iteration!
/// @returns the mark id
uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
- int row, colnr_T col, ExtmarkOp op)
+ int row, colnr_T col, int end_row, colnr_T end_col,
+ Decoration *decor, ExtmarkOp op)
{
ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
mtpos_t old_pos;
@@ -82,7 +85,7 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
} else {
uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id);
if (old_mark) {
- if (old_mark & MARKTREE_PAIRED_FLAG) {
+ if (old_mark & MARKTREE_PAIRED_FLAG || end_row > -1) {
extmark_del(buf, ns_id, id);
} else {
// TODO(bfredl): we need to do more if "revising" a decoration mark.
@@ -90,7 +93,12 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
old_pos = marktree_lookup(buf->b_marktree, old_mark, itr);
assert(itr->node);
if (old_pos.row == row && old_pos.col == col) {
- map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark);
+ ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ old_mark);
+ if (it.decor) {
+ decoration_redraw(buf, row, row, it.decor);
+ free_decoration(it.decor);
+ }
mark = marktree_revise(buf->b_marktree, itr);
goto revised;
}
@@ -101,11 +109,17 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
}
}
- mark = marktree_put(buf->b_marktree, row, col, true);
+ if (end_row > -1) {
+ mark = marktree_put_pair(buf->b_marktree,
+ row, col, true,
+ end_row, end_col, false);
+ } else {
+ mark = marktree_put(buf->b_marktree, row, col, true);
+ }
+
revised:
map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark,
- (ExtmarkItem){ ns_id, id, 0,
- KV_INITIAL_VALUE });
+ (ExtmarkItem){ ns_id, id, decor });
map_put(uint64_t, uint64_t)(ns->map, id, mark);
if (op != kExtmarkNoUndo) {
@@ -114,6 +128,10 @@ revised:
// adding new marks to old undo headers.
u_extmark_set(buf, mark, row, col);
}
+
+ if (decor) {
+ decoration_redraw(buf, row, end_row > -1 ? end_row : row, decor);
+ }
return id;
}
@@ -152,27 +170,23 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)
assert(pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
+ mtpos_t pos2 = pos;
if (mark & MARKTREE_PAIRED_FLAG) {
- mtpos_t pos2 = marktree_lookup(buf->b_marktree,
- mark|MARKTREE_END_FLAG, itr);
+ pos2 = marktree_lookup(buf->b_marktree, mark|MARKTREE_END_FLAG, itr);
assert(pos2.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
- if (item.hl_id && pos2.row >= pos.row) {
- redraw_buf_range_later(buf, pos.row+1, pos2.row+1);
- }
}
- if (kv_size(item.virt_text)) {
- redraw_buf_line_later(buf, pos.row+1);
+ if (item.decor) {
+ decoration_redraw(buf, pos.row, pos2.row, item.decor);
+ free_decoration(item.decor);
}
- clear_virttext(&item.virt_text);
map_del(uint64_t, uint64_t)(ns->map, id);
map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
// TODO(bfredl): delete it from current undo header, opportunistically?
-
return true;
}
@@ -202,9 +216,11 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
}
// the value is either zero or the lnum (row+1) if highlight was present.
- static Map(uint64_t, uint64_t) *delete_set = NULL;
+ static Map(uint64_t, ssize_t) *delete_set = NULL;
+ typedef struct { Decoration *decor; int row1; } DecorItem;
+ static kvec_t(DecorItem) decors;
if (delete_set == NULL) {
- delete_set = map_new(uint64_t, uint64_t)();
+ delete_set = map_new(uint64_t, ssize_t)();
}
MarkTreeIter itr[1] = { 0 };
@@ -216,14 +232,16 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
|| (mark.row == u_row && mark.col > u_col)) {
break;
}
- uint64_t *del_status = map_ref(uint64_t, uint64_t)(delete_set, mark.id,
- false);
+ ssize_t *del_status = map_ref(uint64_t, ssize_t)(delete_set, mark.id,
+ false);
if (del_status) {
marktree_del_itr(buf->b_marktree, itr, false);
- map_del(uint64_t, uint64_t)(delete_set, mark.id);
- if (*del_status > 0) {
- redraw_buf_range_later(buf, (linenr_T)(*del_status), mark.row+1);
+ if (*del_status >= 0) { // we had a decor_id
+ DecorItem it = kv_A(decors, *del_status);
+ decoration_redraw(buf, it.row1, mark.row, it.decor);
+ free_decoration(it.decor);
}
+ map_del(uint64_t, ssize_t)(delete_set, mark.id);
continue;
}
@@ -233,15 +251,21 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
assert(item.ns_id > 0 && item.mark_id > 0);
if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) {
- if (kv_size(item.virt_text)) {
- redraw_buf_line_later(buf, mark.row+1);
- }
- clear_virttext(&item.virt_text);
marks_cleared = true;
if (mark.id & MARKTREE_PAIRED_FLAG) {
uint64_t other = mark.id ^ MARKTREE_END_FLAG;
- uint64_t status = item.hl_id ? ((uint64_t)mark.row+1) : 0;
- map_put(uint64_t, uint64_t)(delete_set, other, status);
+ ssize_t decor_id = -1;
+ if (item.decor) {
+ // Save the decoration and the first pos. Clear the decoration
+ // later when we know the full range.
+ decor_id = (ssize_t)kv_size(decors);
+ kv_push(decors,
+ ((DecorItem) { .decor = item.decor, .row1 = mark.row }));
+ }
+ map_put(uint64_t, ssize_t)(delete_set, other, decor_id);
+ } else if (item.decor) {
+ decoration_redraw(buf, mark.row, mark.row, item.decor);
+ free_decoration(item.decor);
}
ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
@@ -251,16 +275,20 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
marktree_itr_next(buf->b_marktree, itr);
}
}
- uint64_t id, status;
- map_foreach(delete_set, id, status, {
+ uint64_t id;
+ ssize_t decor_id;
+ map_foreach(delete_set, id, decor_id, {
mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr);
assert(itr->node);
marktree_del_itr(buf->b_marktree, itr, false);
- if (status > 0) {
- redraw_buf_range_later(buf, (linenr_T)status, pos.row+1);
+ if (decor_id >= 0) {
+ DecorItem it = kv_A(decors, decor_id);
+ decoration_redraw(buf, it.row1, pos.row, it.decor);
+ free_decoration(it.decor);
}
});
- map_clear(uint64_t, uint64_t)(delete_set);
+ map_clear(uint64_t, ssize_t)(delete_set);
+ kv_size(decors) = 0;
return marks_cleared;
}
@@ -270,31 +298,44 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
// 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_id,
- int l_row, colnr_T l_col,
- int u_row, colnr_T u_col,
- int64_t amount, bool reverse)
+ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id,
+ int l_row, colnr_T l_col,
+ int u_row, colnr_T u_col,
+ int64_t amount, bool reverse)
{
- ExtmarkArray array = KV_INITIAL_VALUE;
- MarkTreeIter itr[1] = { 0 };
+ ExtmarkInfoArray array = KV_INITIAL_VALUE;
+ MarkTreeIter itr[1];
// Find all the marks
marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col },
itr, reverse, false, NULL);
int order = reverse ? -1 : 1;
while ((int64_t)kv_size(array) < amount) {
mtmark_t mark = marktree_itr_current(itr);
+ mtpos_t endpos = { -1, -1 };
if (mark.row < 0
|| (mark.row - u_row) * order > 0
|| (mark.row == u_row && (mark.col - u_col) * order > 0)) {
break;
}
+ if (mark.id & MARKTREE_END_FLAG) {
+ goto next_mark;
+ } else if (mark.id & MARKTREE_PAIRED_FLAG) {
+ endpos = marktree_lookup(buf->b_marktree, mark.id | MARKTREE_END_FLAG,
+ NULL);
+ }
+
+
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
mark.id);
if (item.ns_id == ns_id) {
kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id,
.mark_id = item.mark_id,
- .row = mark.row, .col = mark.col }));
+ .row = mark.row, .col = mark.col,
+ .end_row = endpos.row,
+ .end_col = endpos.col,
+ .decor = item.decor }));
}
+next_mark:
if (reverse) {
marktree_itr_prev(buf->b_marktree, itr);
} else {
@@ -308,7 +349,7 @@ ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id,
ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
{
ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
- ExtmarkInfo ret = { 0, 0, -1, -1 };
+ ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, NULL };
if (!ns) {
return ret;
}
@@ -319,12 +360,22 @@ ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
}
mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL);
+ mtpos_t endpos = { -1, -1 };
+ if (mark & MARKTREE_PAIRED_FLAG) {
+ endpos = marktree_lookup(buf->b_marktree, mark | MARKTREE_END_FLAG, NULL);
+ }
assert(pos.row >= 0);
+ ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark);
+
ret.ns_id = ns_id;
ret.mark_id = id;
ret.row = pos.row;
ret.col = pos.col;
+ ret.end_row = endpos.row;
+ ret.end_col = endpos.col;
+ ret.decor = item.decor;
return ret;
}
@@ -352,7 +403,7 @@ void extmark_free_all(buf_T *buf)
map_foreach(buf->b_extmark_index, id, item, {
(void)id;
- clear_virttext(&item.virt_text);
+ free_decoration(item.decor);
});
map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index);
buf->b_extmark_index = NULL;
@@ -428,18 +479,18 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
// Undo
ExtmarkSplice splice = undo_info.data.splice;
if (undo) {
- extmark_splice(curbuf,
- splice.start_row, splice.start_col,
- splice.newextent_row, splice.newextent_col,
- splice.oldextent_row, splice.oldextent_col,
- kExtmarkNoUndo);
+ extmark_splice_impl(curbuf,
+ splice.start_row, splice.start_col, splice.start_byte,
+ splice.new_row, splice.new_col, splice.new_byte,
+ splice.old_row, splice.old_col, splice.old_byte,
+ kExtmarkNoUndo);
} else {
- extmark_splice(curbuf,
- splice.start_row, splice.start_col,
- splice.oldextent_row, splice.oldextent_col,
- splice.newextent_row, splice.newextent_col,
- kExtmarkNoUndo);
+ extmark_splice_impl(curbuf,
+ splice.start_row, splice.start_col, splice.start_byte,
+ splice.old_row, splice.old_col, splice.old_byte,
+ splice.new_row, splice.new_col, splice.new_byte,
+ kExtmarkNoUndo);
}
// kExtmarkSavePos
} else if (undo_info.type == kExtmarkSavePos) {
@@ -458,15 +509,15 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
ExtmarkMove move = undo_info.data.move;
if (undo) {
extmark_move_region(curbuf,
- move.new_row, move.new_col,
- move.extent_row, move.extent_col,
- move.start_row, move.start_col,
+ move.new_row, move.new_col, move.new_byte,
+ move.extent_row, move.extent_col, move.extent_byte,
+ move.start_row, move.start_col, move.start_byte,
kExtmarkNoUndo);
} else {
extmark_move_region(curbuf,
- move.start_row, move.start_col,
- move.extent_row, move.extent_col,
- move.new_row, move.new_col,
+ move.start_row, move.start_col, move.start_byte,
+ move.extent_row, move.extent_col, move.extent_byte,
+ move.new_row, move.new_col, move.new_byte,
kExtmarkNoUndo);
}
}
@@ -481,51 +532,84 @@ void extmark_adjust(buf_T *buf,
long amount_after,
ExtmarkOp undo)
{
- if (!curbuf_splice_pending) {
- int old_extent, new_extent;
- if (amount == MAXLNUM) {
- old_extent = (int)(line2 - line1+1);
- new_extent = (int)(amount_after + old_extent);
- } else {
- // A region is either deleted (amount == MAXLNUM) or
- // added (line2 == MAXLNUM). The only other case is :move
- // which is handled by a separate entry point extmark_move_region.
- assert(line2 == MAXLNUM);
- old_extent = 0;
- new_extent = (int)amount;
- }
- extmark_splice(buf,
- (int)line1-1, 0,
- old_extent, 0,
- new_extent, 0, undo);
+ if (curbuf_splice_pending) {
+ return;
+ }
+ bcount_t start_byte = ml_find_line_or_offset(buf, line1, NULL, true);
+ bcount_t old_byte = 0, new_byte = 0;
+ int old_row, new_row;
+ if (amount == MAXLNUM) {
+ old_row = (int)(line2 - line1+1);
+ // TODO(bfredl): ej kasta?
+ old_byte = (bcount_t)buf->deleted_bytes2;
+
+ new_row = (int)(amount_after + old_row);
+ } else {
+ // A region is either deleted (amount == MAXLNUM) or
+ // added (line2 == MAXLNUM). The only other case is :move
+ // which is handled by a separate entry point extmark_move_region.
+ assert(line2 == MAXLNUM);
+ old_row = 0;
+ new_row = (int)amount;
}
+ if (new_row > 0) {
+ new_byte = ml_find_line_or_offset(buf, line1+new_row, NULL, true)
+ - start_byte;
+ }
+ extmark_splice_impl(buf,
+ (int)line1-1, 0, start_byte,
+ old_row, 0, old_byte,
+ new_row, 0, new_byte, undo);
}
void extmark_splice(buf_T *buf,
int start_row, colnr_T start_col,
- int oldextent_row, colnr_T oldextent_col,
- int newextent_row, colnr_T newextent_col,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
ExtmarkOp undo)
{
- buf_updates_send_splice(buf, start_row, start_col,
- oldextent_row, oldextent_col,
- newextent_row, newextent_col);
+ long offset = ml_find_line_or_offset(buf, start_row + 1, NULL, true);
+
+ // On empty buffers, when editing the first line, the line is buffered,
+ // causing offset to be < 0. While the buffer is not actually empty, the
+ // buffered line has not been flushed (and should not be) yet, so the call is
+ // valid but an edge case.
+ //
+ // TODO(vigoux): maybe the is a better way of testing that ?
+ if (offset < 0 && buf->b_ml.ml_chunksize == NULL) {
+ offset = 0;
+ }
+ extmark_splice_impl(buf, start_row, start_col, offset + start_col,
+ old_row, old_col, old_byte, new_row, new_col, new_byte,
+ undo);
+}
+
+void extmark_splice_impl(buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
+ ExtmarkOp undo)
+{
+ curbuf->deleted_bytes2 = 0;
+ buf_updates_send_splice(buf, start_row, start_col, start_byte,
+ old_row, old_col, old_byte,
+ new_row, new_col, new_byte);
- if (undo == kExtmarkUndo && (oldextent_row > 0 || oldextent_col > 0)) {
+ if (undo == kExtmarkUndo && (old_row > 0 || old_col > 0)) {
// Copy marks that would be effected by delete
// TODO(bfredl): Be "smart" about gravity here, left-gravity at the
// beginning and right-gravity at the end need not be preserved.
// Also be smart about marks that already have been saved (important for
// merge!)
- int end_row = start_row + oldextent_row;
- int end_col = (oldextent_row ? 0 : start_col) + oldextent_col;
+ int end_row = start_row + old_row;
+ int end_col = (old_row ? 0 : start_col) + old_col;
u_extmark_copy(buf, start_row, start_col, end_row, end_col);
}
marktree_splice(buf->b_marktree, start_row, start_col,
- oldextent_row, oldextent_col,
- newextent_row, newextent_col);
+ old_row, old_col,
+ new_row, new_col);
if (undo == kExtmarkUndo) {
u_header_T *uhp = u_force_get_undo_header(buf);
@@ -537,25 +621,29 @@ void extmark_splice(buf_T *buf,
// TODO(bfredl): this is quite rudimentary. We merge small (within line)
// inserts with each other and small deletes with each other. Add full
// merge algorithm later.
- if (oldextent_row == 0 && newextent_row == 0 && kv_size(uhp->uh_extmark)) {
+ if (old_row == 0 && new_row == 0 && kv_size(uhp->uh_extmark)) {
ExtmarkUndoObject *item = &kv_A(uhp->uh_extmark,
kv_size(uhp->uh_extmark)-1);
if (item->type == kExtmarkSplice) {
ExtmarkSplice *splice = &item->data.splice;
- if (splice->start_row == start_row && splice->oldextent_row == 0
- && splice->newextent_row == 0) {
- if (oldextent_col == 0 && start_col >= splice->start_col
- && start_col <= splice->start_col+splice->newextent_col) {
- splice->newextent_col += newextent_col;
+ if (splice->start_row == start_row && splice->old_row == 0
+ && splice->new_row == 0) {
+ if (old_col == 0 && start_col >= splice->start_col
+ && start_col <= splice->start_col+splice->new_col) {
+ splice->new_col += new_col;
+ splice->new_byte += new_byte;
merged = true;
- } else if (newextent_col == 0
- && start_col == splice->start_col+splice->newextent_col) {
- splice->oldextent_col += oldextent_col;
+ } else if (new_col == 0
+ && start_col == splice->start_col+splice->new_col) {
+ splice->old_col += old_col;
+ splice->old_byte += old_byte;
merged = true;
- } else if (newextent_col == 0
- && start_col + oldextent_col == splice->start_col) {
+ } else if (new_col == 0
+ && start_col + old_col == splice->start_col) {
splice->start_col = start_col;
- splice->oldextent_col += oldextent_col;
+ splice->start_byte = start_byte;
+ splice->old_col += old_col;
+ splice->old_byte += old_byte;
merged = true;
}
}
@@ -566,10 +654,13 @@ void extmark_splice(buf_T *buf,
ExtmarkSplice splice;
splice.start_row = start_row;
splice.start_col = start_col;
- splice.oldextent_row = oldextent_row;
- splice.oldextent_col = oldextent_col;
- splice.newextent_row = newextent_row;
- splice.newextent_col = newextent_col;
+ splice.start_byte = start_byte;
+ splice.old_row = old_row;
+ splice.old_col = old_col;
+ splice.old_byte = old_byte;
+ splice.new_row = new_row;
+ splice.new_col = new_col;
+ splice.new_byte = new_byte;
kv_push(uhp->uh_extmark,
((ExtmarkUndoObject){ .type = kExtmarkSplice,
@@ -584,30 +675,31 @@ void extmark_splice_cols(buf_T *buf,
ExtmarkOp undo)
{
extmark_splice(buf, start_row, start_col,
- 0, old_col,
- 0, new_col, undo);
+ 0, old_col, old_col,
+ 0, new_col, new_col, undo);
}
-void extmark_move_region(buf_T *buf,
- int start_row, colnr_T start_col,
- int extent_row, colnr_T extent_col,
- int new_row, colnr_T new_col,
- ExtmarkOp undo)
+void extmark_move_region(
+ buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int extent_row, colnr_T extent_col, bcount_t extent_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
+ ExtmarkOp undo)
{
// TODO(bfredl): this is not synced to the buffer state inside the callback.
// But unless we make the undo implementation smarter, this is not ensured
// anyway.
- buf_updates_send_splice(buf, start_row, start_col,
- extent_row, extent_col,
- 0, 0);
+ buf_updates_send_splice(buf, start_row, start_col, start_byte,
+ extent_row, extent_col, extent_byte,
+ 0, 0, 0);
marktree_move_region(buf->b_marktree, start_row, start_col,
extent_row, extent_col,
new_row, new_col);
- buf_updates_send_splice(buf, new_row, new_col,
- 0, 0,
- extent_row, extent_col);
+ buf_updates_send_splice(buf, new_row, new_col, new_byte,
+ 0, 0, 0,
+ extent_row, extent_col, extent_byte);
if (undo == kExtmarkUndo) {
@@ -619,10 +711,13 @@ void extmark_move_region(buf_T *buf,
ExtmarkMove move;
move.start_row = start_row;
move.start_col = start_col;
+ move.start_byte = start_byte;
move.extent_row = extent_row;
move.extent_col = extent_col;
+ move.extent_byte = extent_byte;
move.new_row = new_row;
move.new_col = new_col;
+ move.new_byte = new_byte;
kv_push(uhp->uh_extmark,
((ExtmarkUndoObject){ .type = kExtmarkMove,
@@ -642,50 +737,6 @@ uint64_t src2ns(Integer *src_id)
}
}
-/// Adds a decoration to a buffer.
-///
-/// Unlike matchaddpos() highlights, these follow changes to the the buffer
-/// texts. Decorations are represented internally and in the API as extmarks.
-///
-/// @param buf The buffer to add decorations to
-/// @param ns_id A valid namespace id.
-/// @param hl_id Id of the highlight group to use (or zero)
-/// @param start_row The line to highlight
-/// @param start_col First column to highlight
-/// @param end_row The line to highlight
-/// @param end_col The last column to highlight
-/// @param virt_text Virtual text (currently placed at the EOL of start_row)
-/// @return The extmark id inside the namespace
-uint64_t extmark_add_decoration(buf_T *buf, uint64_t ns_id, int hl_id,
- int start_row, colnr_T start_col,
- int end_row, colnr_T end_col,
- VirtText virt_text)
-{
- ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
- ExtmarkItem item;
- item.ns_id = ns_id;
- item.mark_id = ns->free_id++;
- item.hl_id = hl_id;
- item.virt_text = virt_text;
-
- uint64_t mark;
-
- if (end_row > -1) {
- mark = marktree_put_pair(buf->b_marktree,
- start_row, start_col, true,
- end_row, end_col, false);
- } else {
- mark = marktree_put(buf->b_marktree, start_row, start_col, true);
- }
-
- map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, item);
- map_put(uint64_t, uint64_t)(ns->map, item.mark_id, mark);
-
- redraw_buf_range_later(buf, start_row+1,
- (end_row >= 0 ? end_row : start_row) + 1);
- return item.mark_id;
-}
-
/// Add highlighting to a buffer, bounded by two cursor positions,
/// with an offset.
///
@@ -705,6 +756,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf,
{
colnr_T hl_start = 0;
colnr_T hl_end = 0;
+ Decoration *decor = decoration_hl(hl_id);
// TODO(bfredl): if decoration had blocky mode, we could avoid this loop
for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) {
@@ -729,10 +781,44 @@ void bufhl_add_hl_pos_offset(buf_T *buf,
hl_start = pos_start.col + offset;
hl_end = pos_end.col + offset;
}
- (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id,
- (int)lnum-1, hl_start,
- (int)lnum-1+end_off, hl_end,
- VIRTTEXT_EMPTY);
+ (void)extmark_set(buf, (uint64_t)src_id, 0,
+ (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end,
+ decor, kExtmarkNoUndo);
+ }
+}
+
+Decoration *decoration_hl(int hl_id)
+{
+ assert(hl_id > 0);
+ Decoration **dp = (Decoration **)pmap_ref(uint64_t)(hl_decors,
+ (uint64_t)hl_id, true);
+ if (*dp) {
+ return *dp;
+ }
+
+ Decoration *decor = xcalloc(1, sizeof(*decor));
+ decor->hl_id = hl_id;
+ decor->shared = true;
+ *dp = decor;
+ return decor;
+}
+
+void decoration_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
+{
+ if (decor->hl_id && row2 >= row1) {
+ redraw_buf_range_later(buf, row1+1, row2+1);
+ }
+
+ if (kv_size(decor->virt_text)) {
+ redraw_buf_line_later(buf, row1+1);
+ }
+}
+
+void free_decoration(Decoration *decor)
+{
+ if (decor && !decor->shared) {
+ clear_virttext(&decor->virt_text);
+ xfree(decor);
}
}
@@ -757,8 +843,8 @@ VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id)
ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
mark.id, false);
if (item && (ns_id == 0 || ns_id == item->ns_id)
- && kv_size(item->virt_text)) {
- return &item->virt_text;
+ && item->decor && kv_size(item->decor->virt_text)) {
+ return &item->decor->virt_text;
}
marktree_itr_next(buf->b_marktree, itr);
}
@@ -768,8 +854,15 @@ VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id)
bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state)
{
state->row = -1;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ HlRange item = kv_A(state->active, i);
+ if (item.virt_text_owned) {
+ clear_virttext(item.virt_text);
+ xfree(item.virt_text);
+ }
+ }
kv_size(state->active) = 0;
- return buf->b_extmark_index || buf->b_luahl;
+ return buf->b_extmark_index;
}
@@ -787,7 +880,6 @@ bool decorations_redraw_start(buf_T *buf, int top_row,
if (mark.row < 0) { // || mark.row > end_row
break;
}
- // TODO(bfredl): dedicated flag for being a decoration?
if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) {
goto next_mark;
}
@@ -797,25 +889,30 @@ bool decorations_redraw_start(buf_T *buf, int top_row,
uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
start_id, false);
+ if (!item || !item->decor) {
+ // TODO(bfredl): dedicated flag for being a decoration?
+ goto next_mark;
+ }
+ Decoration *decor = item->decor;
+
if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row
- && item && !kv_size(item->virt_text))
+ && item && !kv_size(decor->virt_text))
|| ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) {
goto next_mark;
}
- if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
- int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
- VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
- HlRange range;
- if (mark.id&MARKTREE_END_FLAG) {
- range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
- attr_id, vt };
- } else {
- range = (HlRange){ mark.row, mark.col, altpos.row,
- altpos.col, attr_id, vt };
- }
- kv_push(state->active, range);
+ int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
+ VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
+ HlRange range;
+ if (mark.id&MARKTREE_END_FLAG) {
+ range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
+ attr_id, vt, false };
+ } else {
+ range = (HlRange){ mark.row, mark.col, altpos.row,
+ altpos.col, attr_id, vt, false };
}
+ kv_push(state->active, range);
+
next_mark:
if (marktree_itr_node_done(state->itr)) {
break;
@@ -860,21 +957,24 @@ int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state)
ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
mark.id, false);
+ if (!item || !item->decor) {
+ // TODO(bfredl): dedicated flag for being a decoration?
+ goto next_mark;
+ }
+ Decoration *decor = item->decor;
if (endpos.row < mark.row
|| (endpos.row == mark.row && endpos.col <= mark.col)) {
- if (item && !kv_size(item->virt_text)) {
+ if (item && !kv_size(decor->virt_text)) {
goto next_mark;
}
}
- if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
- int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
- VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
- kv_push(state->active, ((HlRange){ mark.row, mark.col,
- endpos.row, endpos.col,
- attr_id, vt }));
- }
+ int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
+ VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
+ kv_push(state->active, ((HlRange){ mark.row, mark.col,
+ endpos.row, endpos.col,
+ attr_id, vt, false }));
next_mark:
marktree_itr_next(buf->b_marktree, state->itr);
@@ -908,6 +1008,9 @@ next_mark:
}
if (keep) {
kv_A(state->active, j++) = kv_A(state->active, i);
+ } else if (item.virt_text_owned) {
+ clear_virttext(item.virt_text);
+ xfree(item.virt_text);
}
}
kv_size(state->active) = j;
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
index b5eb0db3b6..101527ab4f 100644
--- a/src/nvim/extmark.h
+++ b/src/nvim/extmark.h
@@ -1,6 +1,7 @@
#ifndef NVIM_EXTMARK_H
#define NVIM_EXTMARK_H
+#include "nvim/pos.h"
#include "nvim/buffer_defs.h"
#include "nvim/extmark_defs.h"
#include "nvim/marktree.h"
@@ -13,19 +14,28 @@ typedef struct
uint64_t mark_id;
int row;
colnr_T col;
+ int end_row;
+ colnr_T end_col;
+ Decoration *decor;
} ExtmarkInfo;
-typedef kvec_t(ExtmarkInfo) ExtmarkArray;
+typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray;
+
+// TODO(bfredl): good enough name for now.
+typedef ptrdiff_t bcount_t;
// delete the columns between mincol and endcol
typedef struct {
int start_row;
colnr_T start_col;
- int oldextent_row;
- colnr_T oldextent_col;
- int newextent_row;
- colnr_T newextent_col;
+ int old_row;
+ colnr_T old_col;
+ int new_row;
+ colnr_T new_col;
+ bcount_t start_byte;
+ bcount_t old_byte;
+ bcount_t new_byte;
} ExtmarkSplice;
// adjust marks after :move operation
@@ -36,6 +46,9 @@ typedef struct {
int extent_col;
int new_row;
int new_col;
+ bcount_t start_byte;
+ bcount_t extent_byte;
+ bcount_t new_byte;
} ExtmarkMove;
// extmark was updated
@@ -73,6 +86,7 @@ typedef struct {
int end_col;
int attr_id;
VirtText *virt_text;
+ bool virt_text_owned;
} HlRange;
typedef struct {
@@ -85,6 +99,8 @@ typedef struct {
VirtText *virt_text;
} DecorationRedrawState;
+EXTERN kvec_t(DecorationProvider) decoration_providers INIT(= KV_INITIAL_VALUE);
+EXTERN win_T *redrawn_win INIT(= NULL); // used for ephemeral extmarks
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "extmark.h.generated.h"
diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h
index c927048981..f5ca0ebbb0 100644
--- a/src/nvim/extmark_defs.h
+++ b/src/nvim/extmark_defs.h
@@ -1,7 +1,7 @@
#ifndef NVIM_EXTMARK_DEFS_H
#define NVIM_EXTMARK_DEFS_H
-#include "nvim/pos.h" // for colnr_T
+#include "nvim/types.h"
#include "nvim/lib/kvec.h"
typedef struct {
@@ -14,12 +14,20 @@ typedef kvec_t(VirtTextChunk) VirtText;
typedef struct
{
- uint64_t ns_id;
- uint64_t mark_id;
int hl_id; // highlight group
- // TODO(bfredl): virt_text is pretty larger than the rest,
- // pointer indirection?
VirtText virt_text;
+ // TODO(bfredl): style, signs, etc
+ bool shared; // shared decoration, don't free
+} Decoration;
+
+typedef struct
+{
+ uint64_t ns_id;
+ uint64_t mark_id;
+ // TODO(bfredl): a lot of small allocations. Should probably use
+ // kvec_t(Decoration) as an arena. Alternatively, store ns_id/mark_id
+ // _inline_ in MarkTree and use the map only for decorations.
+ Decoration *decor;
} ExtmarkItem;
typedef struct undo_object ExtmarkUndoObject;
@@ -34,4 +42,18 @@ typedef enum {
kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
} ExtmarkOp;
+typedef struct {
+ NS ns_id;
+ bool active;
+ LuaRef redraw_start;
+ LuaRef redraw_buf;
+ LuaRef redraw_win;
+ LuaRef redraw_line;
+ LuaRef redraw_end;
+} DecorationProvider;
+
+#define DECORATION_PROVIDER_INIT(ns_id) (DecorationProvider) \
+ { ns_id, false, LUA_NOREF, LUA_NOREF, \
+ LUA_NOREF, LUA_NOREF, LUA_NOREF }
+
#endif // NVIM_EXTMARK_DEFS_H
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index f922591d0b..ad856b588a 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -210,7 +210,8 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr)
if (msg_silent != 0) {
return;
}
- add_quoted_fname((char *)IObuff, IOSIZE - 80, buf, (const char *)name);
+ add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)name);
+ // Avoid an over-long translation to cause trouble.
xstrlcat((char *)IObuff, (const char *)s, IOSIZE);
// For the first message may have to start a new line.
// For further ones overwrite the previous one, reset msg_scroll before
@@ -349,6 +350,7 @@ readfile(
char_u *old_b_fname;
int using_b_ffname;
int using_b_fname;
+ static char *msg_is_a_directory = N_("is a directory");
au_did_filetype = false; // reset before triggering any autocommands
@@ -443,21 +445,31 @@ readfile(
else
msg_scroll = TRUE; /* don't overwrite previous file message */
- /*
- * If the name is too long we might crash further on, quit here.
- */
+ // If the name is too long we might crash further on, quit here.
if (fname != NULL && *fname != NUL) {
- if (STRLEN(fname) >= MAXPATHL) {
+ size_t namelen = STRLEN(fname);
+
+ // If the name is too long we might crash further on, quit here.
+ if (namelen >= MAXPATHL) {
filemess(curbuf, fname, (char_u *)_("Illegal file name"), 0);
msg_end();
msg_scroll = msg_save;
return FAIL;
}
+
+ // If the name ends in a path separator, we can't open it. Check here,
+ // because reading the file may actually work, but then creating the
+ // swap file may destroy it! Reported on MS-DOS and Win 95.
+ if (after_pathsep((const char *)fname, (const char *)(fname + namelen))) {
+ filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0);
+ msg_end();
+ msg_scroll = msg_save;
+ return FAIL;
+ }
}
if (!read_buffer && !read_stdin && !read_fifo) {
perm = os_getperm((const char *)fname);
-#ifdef UNIX
// On Unix it is possible to read a directory, so we have to
// check for it before os_open().
if (perm >= 0 && !S_ISREG(perm) // not a regular file ...
@@ -473,7 +485,7 @@ readfile(
# endif
) {
if (S_ISDIR(perm)) {
- filemess(curbuf, fname, (char_u *)_("is a directory"), 0);
+ filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0);
} else {
filemess(curbuf, fname, (char_u *)_("is not a file"), 0);
}
@@ -481,7 +493,6 @@ readfile(
msg_scroll = msg_save;
return S_ISDIR(perm) ? NOTDONE : FAIL;
}
-#endif
}
/* Set default or forced 'fileformat' and 'binary'. */
@@ -540,13 +551,6 @@ readfile(
if (fd < 0) { // cannot open at all
msg_scroll = msg_save;
-#ifndef UNIX
- // On non-unix systems we can't open a directory, check here.
- if (os_isdir(fname)) {
- filemess(curbuf, sfname, (char_u *)_("is a directory"), 0);
- curbuf->b_p_ro = true; // must use "w!" now
- } else {
-#endif
if (!newfile) {
return FAIL;
}
@@ -604,9 +608,6 @@ readfile(
return FAIL;
}
-#ifndef UNIX
- }
-#endif
/*
* Only set the 'ro' flag for readonly files the first time they are
@@ -1797,6 +1798,7 @@ failed:
linecnt--;
}
curbuf->deleted_bytes = 0;
+ curbuf->deleted_bytes2 = 0;
curbuf->deleted_codepoints = 0;
curbuf->deleted_codeunits = 0;
linecnt = curbuf->b_ml.ml_line_count - linecnt;
@@ -3591,7 +3593,7 @@ restore_backup:
* the backup file our 'original' file.
*/
if (*p_pm && dobackup) {
- char *org = modname((char *)fname, (char *)p_pm, FALSE);
+ char *const org = modname((char *)fname, (char *)p_pm, false);
if (backup != NULL) {
/*
@@ -5553,7 +5555,6 @@ static void au_del_cmd(AutoCmd *ac)
static void au_cleanup(void)
{
AutoPat *ap, **prev_ap;
- AutoCmd *ac, **prev_ac;
event_T event;
if (autocmd_busy || !au_need_clean) {
@@ -5566,11 +5567,11 @@ static void au_cleanup(void)
// Loop over all autocommand patterns.
prev_ap = &(first_autopat[(int)event]);
for (ap = *prev_ap; ap != NULL; ap = *prev_ap) {
- // Loop over all commands for this pattern.
- prev_ac = &(ap->cmds);
bool has_cmd = false;
- for (ac = *prev_ac; ac != NULL; ac = *prev_ac) {
+ // Loop over all commands for this pattern.
+ AutoCmd **prev_ac = &(ap->cmds);
+ for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) {
// Remove the command if the pattern is to be deleted or when
// the command has been marked for deletion.
if (ap->pat == NULL || ac->cmd == NULL) {
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 61a85171e8..197aedabec 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -153,14 +153,22 @@ bool hasFolding(linenr_T lnum, linenr_T *firstp, linenr_T *lastp)
return hasFoldingWin(curwin, lnum, firstp, lastp, true, NULL);
}
-/* hasFoldingWin() {{{2 */
+// hasFoldingWin() {{{2
+/// Search folds starting at lnum
+/// @param lnum first line to search
+/// @param[out] first first line of fold containing lnum
+/// @param[out] lastp last line with a fold
+/// @param cache when true: use cached values of window
+/// @param[out] infop where to store fold info
+///
+/// @return true if range contains folds
bool hasFoldingWin(
win_T *const win,
const linenr_T lnum,
linenr_T *const firstp,
linenr_T *const lastp,
- const bool cache, // when true: use cached values of window
- foldinfo_T *const infop // where to store fold info
+ const bool cache,
+ foldinfo_T *const infop
)
{
bool had_folded = false;
@@ -280,26 +288,31 @@ int foldLevel(linenr_T lnum)
// Return false if line is not folded.
bool lineFolded(win_T *const win, const linenr_T lnum)
{
- return foldedCount(win, lnum, NULL) != 0;
+ return fold_info(win, lnum).fi_lines != 0;
}
-/* foldedCount() {{{2 */
-/*
- * Count the number of lines that are folded at line number "lnum".
- * Normally "lnum" is the first line of a possible fold, and the returned
- * number is the number of lines in the fold.
- * Doesn't use caching from the displayed window.
- * Returns number of folded lines from "lnum", or 0 if line is not folded.
- * When "infop" is not NULL, fills *infop with the fold level info.
- */
-long foldedCount(win_T *win, linenr_T lnum, foldinfo_T *infop)
+/// fold_info() {{{2
+///
+/// Count the number of lines that are folded at line number "lnum".
+/// Normally "lnum" is the first line of a possible fold, and the returned
+/// number is the number of lines in the fold.
+/// Doesn't use caching from the displayed window.
+///
+/// @return with the fold level info.
+/// fi_lines = number of folded lines from "lnum",
+/// or 0 if line is not folded.
+foldinfo_T fold_info(win_T *win, linenr_T lnum)
{
+ foldinfo_T info;
linenr_T last;
- if (hasFoldingWin(win, lnum, NULL, &last, false, infop)) {
- return (long)(last - lnum + 1);
+ if (hasFoldingWin(win, lnum, NULL, &last, false, &info)) {
+ info.fi_lines = (long)(last - lnum + 1);
+ } else {
+ info.fi_lines = 0;
}
- return 0;
+
+ return info;
}
/* foldmethodIsManual() {{{2 */
@@ -356,23 +369,21 @@ int foldmethodIsDiff(win_T *wp)
return wp->w_p_fdm[0] == 'd';
}
-/* closeFold() {{{2 */
-/*
- * Close fold for current window at line "lnum".
- * Repeat "count" times.
- */
-void closeFold(linenr_T lnum, long count)
+// closeFold() {{{2
+/// Close fold for current window at line "lnum".
+/// Repeat "count" times.
+void closeFold(pos_T pos, long count)
{
- setFoldRepeat(lnum, count, FALSE);
+ setFoldRepeat(pos, count, false);
}
/* closeFoldRecurse() {{{2 */
/*
* Close fold for current window at line "lnum" recursively.
*/
-void closeFoldRecurse(linenr_T lnum)
+void closeFoldRecurse(pos_T pos)
{
- (void)setManualFold(lnum, FALSE, TRUE, NULL);
+ (void)setManualFold(pos, false, true, NULL);
}
/* opFoldRange() {{{2 */
@@ -382,28 +393,32 @@ void closeFoldRecurse(linenr_T lnum)
*/
void
opFoldRange(
- linenr_T first,
- linenr_T last,
+ pos_T firstpos,
+ pos_T lastpos,
int opening, // TRUE to open, FALSE to close
int recurse, // TRUE to do it recursively
int had_visual // TRUE when Visual selection used
)
{
- int done = DONE_NOTHING; /* avoid error messages */
+ int done = DONE_NOTHING; // avoid error messages
+ linenr_T first = firstpos.lnum;
+ linenr_T last = lastpos.lnum;
linenr_T lnum;
linenr_T lnum_next;
for (lnum = first; lnum <= last; lnum = lnum_next + 1) {
+ pos_T temp = { lnum, 0, 0 };
lnum_next = lnum;
/* Opening one level only: next fold to open is after the one going to
* be opened. */
if (opening && !recurse)
(void)hasFolding(lnum, NULL, &lnum_next);
- (void)setManualFold(lnum, opening, recurse, &done);
- /* Closing one level only: next line to close a fold is after just
- * closed fold. */
- if (!opening && !recurse)
+ (void)setManualFold(temp, opening, recurse, &done);
+ // Closing one level only: next line to close a fold is after just
+ // closed fold.
+ if (!opening && !recurse) {
(void)hasFolding(lnum, NULL, &lnum_next);
+ }
}
if (done == DONE_NOTHING)
EMSG(_(e_nofold));
@@ -417,18 +432,18 @@ opFoldRange(
* Open fold for current window at line "lnum".
* Repeat "count" times.
*/
-void openFold(linenr_T lnum, long count)
+void openFold(pos_T pos, long count)
{
- setFoldRepeat(lnum, count, TRUE);
+ setFoldRepeat(pos, count, true);
}
/* openFoldRecurse() {{{2 */
/*
* Open fold for current window at line "lnum" recursively.
*/
-void openFoldRecurse(linenr_T lnum)
+void openFoldRecurse(pos_T pos)
{
- (void)setManualFold(lnum, TRUE, TRUE, NULL);
+ (void)setManualFold(pos, true, true, NULL);
}
/* foldOpenCursor() {{{2 */
@@ -443,9 +458,10 @@ void foldOpenCursor(void)
if (hasAnyFolding(curwin))
for (;; ) {
done = DONE_NOTHING;
- (void)setManualFold(curwin->w_cursor.lnum, TRUE, FALSE, &done);
- if (!(done & DONE_ACTION))
+ (void)setManualFold(curwin->w_cursor, true, false, &done);
+ if (!(done & DONE_ACTION)) {
break;
+ }
}
}
@@ -542,21 +558,21 @@ int foldManualAllowed(int create)
// foldCreate() {{{2
/// Create a fold from line "start" to line "end" (inclusive) in the current
/// window.
-void foldCreate(win_T *wp, linenr_T start, linenr_T end)
+void foldCreate(win_T *wp, pos_T start, pos_T end)
{
fold_T *fp;
garray_T *gap;
garray_T fold_ga;
- int i, j;
+ int i;
int cont;
int use_level = FALSE;
int closed = FALSE;
int level = 0;
- linenr_T start_rel = start;
- linenr_T end_rel = end;
+ pos_T start_rel = start;
+ pos_T end_rel = end;
- if (start > end) {
- /* reverse the range */
+ if (start.lnum > end.lnum) {
+ // reverse the range
end = start_rel;
start = end_rel;
start_rel = start;
@@ -573,60 +589,70 @@ void foldCreate(win_T *wp, linenr_T start, linenr_T end)
// Find the place to insert the new fold
gap = &wp->w_folds;
- for (;; ) {
- if (!foldFind(gap, start_rel, &fp))
- break;
- if (fp->fd_top + fp->fd_len > end_rel) {
- /* New fold is completely inside this fold: Go one level deeper. */
- gap = &fp->fd_nested;
- start_rel -= fp->fd_top;
- end_rel -= fp->fd_top;
- if (use_level || fp->fd_flags == FD_LEVEL) {
- use_level = true;
- if (level >= wp->w_p_fdl) {
+ if (gap->ga_len == 0) {
+ i = 0;
+ } else {
+ for (;;) {
+ if (!foldFind(gap, start_rel.lnum, &fp)) {
+ break;
+ }
+ if (fp->fd_top + fp->fd_len > end_rel.lnum) {
+ // New fold is completely inside this fold: Go one level deeper.
+ gap = &fp->fd_nested;
+ start_rel.lnum -= fp->fd_top;
+ end_rel.lnum -= fp->fd_top;
+ if (use_level || fp->fd_flags == FD_LEVEL) {
+ use_level = true;
+ if (level >= wp->w_p_fdl) {
+ closed = true;
+ }
+ } else if (fp->fd_flags == FD_CLOSED) {
closed = true;
}
- } else if (fp->fd_flags == FD_CLOSED) {
- closed = true;
+ level++;
+ } else {
+ // This fold and new fold overlap: Insert here and move some folds
+ // inside the new fold.
+ break;
}
- level++;
- } else {
- /* This fold and new fold overlap: Insert here and move some folds
- * inside the new fold. */
- break;
}
+ i = (int)(fp - (fold_T *)gap->ga_data);
}
- i = (int)(fp - (fold_T *)gap->ga_data);
ga_grow(gap, 1);
{
fp = (fold_T *)gap->ga_data + i;
ga_init(&fold_ga, (int)sizeof(fold_T), 10);
- /* Count number of folds that will be contained in the new fold. */
- for (cont = 0; i + cont < gap->ga_len; ++cont)
- if (fp[cont].fd_top > end_rel)
+ // Count number of folds that will be contained in the new fold.
+ for (cont = 0; i + cont < gap->ga_len; cont++) {
+ if (fp[cont].fd_top > end_rel.lnum) {
break;
+ }
+ }
if (cont > 0) {
ga_grow(&fold_ga, cont);
/* If the first fold starts before the new fold, let the new fold
* start there. Otherwise the existing fold would change. */
- if (start_rel > fp->fd_top)
- start_rel = fp->fd_top;
-
- /* When last contained fold isn't completely contained, adjust end
- * of new fold. */
- if (end_rel < fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1)
- end_rel = fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1;
- /* Move contained folds to inside new fold. */
+ if (start_rel.lnum > fp->fd_top) {
+ start_rel.lnum = fp->fd_top;
+ }
+
+ // When last contained fold isn't completely contained, adjust end
+ // of new fold.
+ if (end_rel.lnum < fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1) {
+ end_rel.lnum = fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1;
+ }
+ // Move contained folds to inside new fold
memmove(fold_ga.ga_data, fp, sizeof(fold_T) * (size_t)cont);
fold_ga.ga_len += cont;
i += cont;
/* Adjust line numbers in contained folds to be relative to the
* new fold. */
- for (j = 0; j < cont; ++j)
- ((fold_T *)fold_ga.ga_data)[j].fd_top -= start_rel;
+ for (int j = 0; j < cont; j++) {
+ ((fold_T *)fold_ga.ga_data)[j].fd_top -= start_rel.lnum;
+ }
}
/* Move remaining entries to after the new fold. */
if (i < gap->ga_len)
@@ -636,8 +662,8 @@ void foldCreate(win_T *wp, linenr_T start, linenr_T end)
/* insert new fold */
fp->fd_nested = fold_ga;
- fp->fd_top = start_rel;
- fp->fd_len = end_rel - start_rel + 1;
+ fp->fd_top = start_rel.lnum;
+ fp->fd_len = end_rel.lnum - start_rel.lnum + 1;
/* We want the new fold to be closed. If it would remain open because
* of using 'foldlevel', need to adjust fd_flags of containing folds.
@@ -766,7 +792,7 @@ void deleteFold(
*/
void clearFolding(win_T *win)
{
- deleteFoldRecurse(&win->w_folds);
+ deleteFoldRecurse(win->w_buffer, &win->w_folds);
win->w_foldinvalid = false;
}
@@ -788,13 +814,15 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
return;
}
- // Mark all folds from top to bot as maybe-small.
- fold_T *fp;
- (void)foldFind(&wp->w_folds, top, &fp);
- while (fp < (fold_T *)wp->w_folds.ga_data + wp->w_folds.ga_len
- && fp->fd_top < bot) {
- fp->fd_small = kNone;
- fp++;
+ if (wp->w_folds.ga_len > 0) {
+ // Mark all folds from top to bot as maybe-small.
+ fold_T *fp;
+ (void)foldFind(&wp->w_folds, top, &fp);
+ while (fp < (fold_T *)wp->w_folds.ga_data + wp->w_folds.ga_len
+ && fp->fd_top < bot) {
+ fp->fd_small = kNone;
+ fp++;
+ }
}
if (foldmethodIsIndent(wp)
@@ -1058,11 +1086,16 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to)
* the first fold below it (careful: it can be beyond the end of the array!).
* Returns FALSE when there is no fold that contains "lnum".
*/
-static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
+static bool foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
{
linenr_T low, high;
fold_T *fp;
+ if (gap->ga_len == 0) {
+ *fpp = NULL;
+ return false;
+ }
+
/*
* Perform a binary search.
* "low" is lowest index of possible match.
@@ -1086,7 +1119,7 @@ static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
}
}
*fpp = fp + low;
- return FALSE;
+ return false;
}
/* foldLevelWin() {{{2 */
@@ -1131,14 +1164,14 @@ static void checkupdate(win_T *wp)
* Open or close fold for current window at line "lnum".
* Repeat "count" times.
*/
-static void setFoldRepeat(linenr_T lnum, long count, int do_open)
+static void setFoldRepeat(pos_T pos, long count, int do_open)
{
int done;
long n;
for (n = 0; n < count; ++n) {
done = DONE_NOTHING;
- (void)setManualFold(lnum, do_open, FALSE, &done);
+ (void)setManualFold(pos, do_open, false, &done);
if (!(done & DONE_ACTION)) {
/* Only give an error message when no fold could be opened. */
if (n == 0 && !(done & DONE_FOLD))
@@ -1155,12 +1188,13 @@ static void setFoldRepeat(linenr_T lnum, long count, int do_open)
*/
static linenr_T
setManualFold(
- linenr_T lnum,
+ pos_T pos,
int opening, // TRUE when opening, FALSE when closing
int recurse, // TRUE when closing/opening recursive
int *donep
)
{
+ linenr_T lnum = pos.lnum;
if (foldmethodIsDiff(curwin) && curwin->w_p_scb) {
linenr_T dlnum;
@@ -1220,9 +1254,10 @@ setManualFoldWin(
gap = &wp->w_folds;
for (;; ) {
if (!foldFind(gap, lnum, &fp)) {
- /* If there is a following fold, continue there next time. */
- if (fp < (fold_T *)gap->ga_data + gap->ga_len)
+ // If there is a following fold, continue there next time.
+ if (fp != NULL && fp < (fold_T *)gap->ga_data + gap->ga_len) {
next = fp->fd_top + off;
+ }
break;
}
@@ -1313,7 +1348,7 @@ static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx,
fold_T *fp = (fold_T *)gap->ga_data + idx;
if (recursive || GA_EMPTY(&fp->fd_nested)) {
// recursively delete the contained folds
- deleteFoldRecurse(&fp->fd_nested);
+ deleteFoldRecurse(wp->w_buffer, &fp->fd_nested);
gap->ga_len--;
if (idx < gap->ga_len) {
memmove(fp, fp + 1, sizeof(*fp) * (size_t)(gap->ga_len - idx));
@@ -1355,9 +1390,9 @@ static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx,
/*
* Delete nested folds in a fold.
*/
-void deleteFoldRecurse(garray_T *gap)
+void deleteFoldRecurse(buf_T *bp, garray_T *gap)
{
-# define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(&((fd)->fd_nested))
+# define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(bp, &((fd)->fd_nested))
GA_DEEP_CLEAR(gap, fold_T, DELETE_FOLD_NESTED);
}
@@ -1389,6 +1424,10 @@ static void foldMarkAdjustRecurse(
linenr_T last;
linenr_T top;
+ if (gap->ga_len == 0) {
+ return;
+ }
+
/* In Insert mode an inserted line at the top of a fold is considered part
* of the fold, otherwise it isn't. */
if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM)
@@ -1593,7 +1632,7 @@ static void setSmallMaybe(garray_T *gap)
* Create a fold from line "start" to line "end" (inclusive) in the current
* window by adding markers.
*/
-static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end)
+static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end)
{
buf_T *buf = wp->w_buffer;
if (!MODIFIABLE(buf)) {
@@ -1608,13 +1647,13 @@ static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end)
/* Update both changes here, to avoid all folds after the start are
* changed when the start marker is inserted and the end isn't. */
// TODO(teto): pass the buffer
- changed_lines(start, (colnr_T)0, end, 0L, false);
+ changed_lines(start.lnum, (colnr_T)0, end.lnum, 0L, false);
// Note: foldAddMarker() may not actually change start and/or end if
// u_save() is unable to save the buffer line, but we send the
// nvim_buf_lines_event anyway since it won't do any harm.
- int64_t num_changed = 1 + end - start;
- buf_updates_send_changes(buf, start, num_changed, num_changed, true);
+ int64_t num_changed = 1 + end.lnum - start.lnum;
+ buf_updates_send_changes(buf, start.lnum, num_changed, num_changed, true);
}
/* foldAddMarker() {{{2 */
@@ -1622,13 +1661,14 @@ static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end)
* Add "marker[markerlen]" in 'commentstring' to line "lnum".
*/
static void foldAddMarker(
- buf_T *buf, linenr_T lnum, const char_u *marker, size_t markerlen)
+ buf_T *buf, pos_T pos, const char_u *marker, size_t markerlen)
{
char_u *cms = buf->b_p_cms;
char_u *line;
char_u *newline;
char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s");
bool line_is_comment = false;
+ linenr_T lnum = pos.lnum;
// Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end
line = ml_get_buf(buf, lnum, false);
@@ -1727,19 +1767,23 @@ static void foldDelMarker(
STRCPY(newline + (p - line), p + len);
ml_replace_buf(buf, lnum, newline, false);
extmark_splice_cols(buf, (int)lnum-1, (int)(p - line),
- (int)len,
- 0, kExtmarkUndo);
+ (int)len, 0, kExtmarkUndo);
}
break;
}
}
// get_foldtext() {{{2
-/// Return the text for a closed fold at line "lnum", with last line "lnume".
-/// When 'foldtext' isn't set puts the result in "buf[FOLD_TEXT_LEN]".
+/// Generates text to display
+///
+/// @param buf allocated memory of length FOLD_TEXT_LEN. Used when 'foldtext'
+/// isn't set puts the result in "buf[FOLD_TEXT_LEN]".
+/// @param at line "lnum", with last line "lnume".
+/// @return the text for a closed fold
+///
/// Otherwise the result is in allocated memory.
char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume,
- foldinfo_T *foldinfo, char_u *buf)
+ foldinfo_T foldinfo, char_u *buf)
FUNC_ATTR_NONNULL_ARG(1)
{
char_u *text = NULL;
@@ -1767,11 +1811,12 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume,
set_vim_var_nr(VV_FOLDSTART, (varnumber_T) lnum);
set_vim_var_nr(VV_FOLDEND, (varnumber_T) lnume);
- /* Set "v:folddashes" to a string of "level" dashes. */
- /* Set "v:foldlevel" to "level". */
- level = foldinfo->fi_level;
- if (level > (int)sizeof(dashes) - 1)
+ // Set "v:folddashes" to a string of "level" dashes.
+ // Set "v:foldlevel" to "level".
+ level = foldinfo.fi_level;
+ if (level > (int)sizeof(dashes) - 1) {
level = (int)sizeof(dashes) - 1;
+ }
memset(dashes, '-', (size_t)level);
dashes[level] = NUL;
set_vim_var_string(VV_FOLDDASHES, dashes, -1);
@@ -2265,14 +2310,15 @@ static linenr_T foldUpdateIEMSRecurse(
/* Find an existing fold to re-use. Preferably one that
* includes startlnum, otherwise one that ends just before
* startlnum or starts after it. */
- if (foldFind(gap, startlnum, &fp)
- || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
- && fp->fd_top <= firstlnum)
- || foldFind(gap, firstlnum - concat, &fp)
- || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
- && ((lvl < level && fp->fd_top < flp->lnum)
- || (lvl >= level
- && fp->fd_top <= flp->lnum_save)))) {
+ if (gap->ga_len > 0
+ && (foldFind(gap, startlnum, &fp)
+ || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
+ && fp->fd_top <= firstlnum)
+ || foldFind(gap, firstlnum - concat, &fp)
+ || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
+ && ((lvl < level && fp->fd_top < flp->lnum)
+ || (lvl >= level
+ && fp->fd_top <= flp->lnum_save))))) {
if (fp->fd_top + fp->fd_len + concat > firstlnum) {
/* Use existing fold for the new fold. If it starts
* before where we started looking, extend it. If it
@@ -2363,7 +2409,11 @@ static linenr_T foldUpdateIEMSRecurse(
} else {
/* Insert new fold. Careful: ga_data may be NULL and it
* may change! */
- i = (int)(fp - (fold_T *)gap->ga_data);
+ if (gap->ga_len == 0) {
+ i = 0;
+ } else {
+ i = (int)(fp - (fold_T *)gap->ga_data);
+ }
foldInsert(gap, i);
fp = (fold_T *)gap->ga_data + i;
/* The new fold continues until bot, unless we find the
@@ -2559,9 +2609,10 @@ static void foldInsert(garray_T *gap, int i)
ga_grow(gap, 1);
fp = (fold_T *)gap->ga_data + i;
- if (i < gap->ga_len)
+ if (gap->ga_len > 0 && i < gap->ga_len) {
memmove(fp + 1, fp, sizeof(fold_T) * (size_t)(gap->ga_len - i));
- ++gap->ga_len;
+ }
+ gap->ga_len++;
ga_init(&fp->fd_nested, (int)sizeof(fold_T), 10);
}
@@ -2596,17 +2647,18 @@ static void foldSplit(buf_T *buf, garray_T *const gap,
* any between top and bot, they have been removed by the caller. */
garray_T *const gap1 = &fp->fd_nested;
garray_T *const gap2 = &fp[1].fd_nested;
- (void)(foldFind(gap1, bot + 1 - fp->fd_top, &fp2));
- const int len = (int)((fold_T *)gap1->ga_data + gap1->ga_len - fp2);
- if (len > 0) {
- ga_grow(gap2, len);
- for (int idx = 0; idx < len; idx++) {
- ((fold_T *)gap2->ga_data)[idx] = fp2[idx];
- ((fold_T *)gap2->ga_data)[idx].fd_top
- -= fp[1].fd_top - fp->fd_top;
- }
- gap2->ga_len = len;
- gap1->ga_len -= len;
+ if (foldFind(gap1, bot + 1 - fp->fd_top, &fp2)) {
+ const int len = (int)((fold_T *)gap1->ga_data + gap1->ga_len - fp2);
+ if (len > 0) {
+ ga_grow(gap2, len);
+ for (int idx = 0; idx < len; idx++) {
+ ((fold_T *)gap2->ga_data)[idx] = fp2[idx];
+ ((fold_T *)gap2->ga_data)[idx].fd_top
+ -= fp[1].fd_top - fp->fd_top;
+ }
+ gap2->ga_len = len;
+ gap1->ga_len -= len;
+ }
}
fp->fd_len = top - fp->fd_top;
fold_changed = true;
@@ -2641,7 +2693,7 @@ static void foldRemove(
return; // nothing to do
}
- for (;; ) {
+ while (gap->ga_len > 0) {
// Find fold that includes top or a following one.
if (foldFind(gap, top, &fp) && fp->fd_top < top) {
// 2: or 3: need to delete nested folds
@@ -2657,7 +2709,8 @@ static void foldRemove(
fold_changed = true;
continue;
}
- if (fp >= (fold_T *)(gap->ga_data) + gap->ga_len
+ if (gap->ga_data == NULL
+ || fp >= (fold_T *)(gap->ga_data) + gap->ga_len
|| fp->fd_top > bot) {
// 6: Found a fold below bot, can stop looking.
break;
@@ -2738,7 +2791,8 @@ static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end)
}
#define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1)
-#define VALID_FOLD(fp, gap) ((fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
+#define VALID_FOLD(fp, gap) \
+ ((gap)->ga_len > 0 && (fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
#define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data)))
void foldMoveRange(
win_T *const wp, garray_T *gap,
diff --git a/src/nvim/fold.h b/src/nvim/fold.h
index f35b328fb1..95c4b0c1dc 100644
--- a/src/nvim/fold.h
+++ b/src/nvim/fold.h
@@ -18,6 +18,7 @@ typedef struct foldinfo {
other fields are invalid */
int fi_low_level; /* lowest fold level that starts in the same
line */
+ long fi_lines;
} foldinfo_T;
diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua
index 075d8ba9cc..849c82f50e 100644
--- a/src/nvim/generators/gen_ex_cmds.lua
+++ b/src/nvim/generators/gen_ex_cmds.lua
@@ -24,8 +24,6 @@ local defsfile = io.open(defsfname, 'w')
local defs = require('ex_cmds')
-local first = true
-
local byte_a = string.byte('a')
local byte_z = string.byte('z')
local a_to_z = byte_z - byte_a + 1
@@ -41,8 +39,7 @@ static const uint16_t cmdidxs1[%u] = {
-- fit in a byte.
local cmdidxs2_out = string.format([[
static const char_u cmdidxs2[%u][%u] = {
-/* 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 */
]], a_to_z, a_to_z)
enumfile:write([[
@@ -50,10 +47,8 @@ typedef enum CMD_index {
]])
defsfile:write(string.format([[
static const int command_count = %u;
-]], #defs))
-defsfile:write(string.format([[
static CommandDefinition cmdnames[%u] = {
-]], #defs))
+]], #defs, #defs))
local cmds, cmdidxs1, cmdidxs2 = {}, {}, {}
for _, cmd in ipairs(defs) do
local enumname = cmd.enum or ('CMD_' .. cmd.command)
@@ -61,11 +56,6 @@ for _, cmd in ipairs(defs) do
if byte_a <= byte_cmd and byte_cmd <= byte_z then
table.insert(cmds, cmd.command)
end
- if first then
- first = false
- else
- defsfile:write(',\n')
- end
enumfile:write(' ' .. enumname .. ',\n')
defsfile:write(string.format([[
[%s] = {
@@ -73,7 +63,8 @@ for _, cmd in ipairs(defs) do
.cmd_func = (ex_func_T)&%s,
.cmd_argt = %uL,
.cmd_addr_type = %i
- }]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type))
+ },
+]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type))
end
for i = #cmds, 1, -1 do
local cmd = cmds[i]
@@ -104,15 +95,14 @@ for i = byte_a, byte_z do
end
cmdidxs2_out = cmdidxs2_out .. ' },\n'
end
-defsfile:write([[
-
-};
-]])
enumfile:write([[
CMD_SIZE,
CMD_USER = -1,
CMD_USER_BUF = -2
} cmdidx_T;
]])
-defsfile:write(cmdidxs1_out .. '};\n')
-defsfile:write(cmdidxs2_out .. '};\n')
+defsfile:write(string.format([[
+};
+%s};
+%s};
+]], cmdidxs1_out, cmdidxs2_out))
diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua
index a8cf496cb9..d80a6219eb 100644
--- a/src/nvim/generators/gen_options.lua
+++ b/src/nvim/generators/gen_options.lua
@@ -141,9 +141,6 @@ local dump_option = function(i, o)
elseif #o.scope == 1 and o.scope[1] == 'window' then
w(' .var=VAR_WIN')
end
- if o.enable_if then
- w('#endif')
- end
if #o.scope == 1 and o.scope[1] == 'global' then
w(' .indir=PV_NONE')
else
@@ -163,6 +160,12 @@ local dump_option = function(i, o)
defines['PV_' .. varname:sub(3):upper()] = pv_name
w(' .indir=' .. pv_name)
end
+ if o.enable_if then
+ w('#else')
+ w(' .var=NULL')
+ w(' .indir=PV_NONE')
+ w('#endif')
+ end
if o.defaults then
if o.defaults.condition then
w(get_cond(o.defaults.condition))
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 5ab5a7db1b..456979be00 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -27,6 +27,7 @@
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/func_attr.h"
+#include "nvim/lua/executor.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -455,6 +456,9 @@ void flush_buffers(flush_buffers_T flush_typeahead)
typebuf.tb_silent = 0;
cmd_silent = false;
typebuf.tb_no_abbr_cnt = 0;
+ if (++typebuf.tb_change_cnt == 0) {
+ typebuf.tb_change_cnt = 1;
+ }
}
/*
@@ -1524,6 +1528,17 @@ int vgetc(void)
c = utf_ptr2char(buf);
}
+ // If mappings are enabled (i.e., not Ctrl-v) and the user directly typed
+ // something with a meta- or alt- modifier that was not mapped, interpret
+ // <M-x> as <Esc>x rather than as an unbound meta keypress. #8213
+ if (!no_mapping && KeyTyped
+ && (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META)) {
+ mod_mask = 0;
+ stuffcharReadbuff(c);
+ u_sync(false);
+ c = ESC;
+ }
+
break;
}
}
@@ -1535,6 +1550,9 @@ int vgetc(void)
*/
may_garbage_collect = false;
+ // Exec lua callbacks for on_keystroke
+ nlua_execute_log_keystroke(c);
+
return c;
}
@@ -2037,14 +2055,19 @@ static int vgetorpeek(bool advance)
*/
if (mp->m_expr) {
int save_vgetc_busy = vgetc_busy;
+ const bool save_may_garbage_collect = may_garbage_collect;
vgetc_busy = 0;
+ may_garbage_collect = false;
+
save_m_keys = vim_strsave(mp->m_keys);
save_m_str = vim_strsave(mp->m_str);
s = eval_map_expr(save_m_str, NUL);
vgetc_busy = save_vgetc_busy;
- } else
+ may_garbage_collect = save_may_garbage_collect;
+ } else {
s = mp->m_str;
+ }
/*
* Insert the 'to' part in the typebuf.tb_buf.
@@ -4181,7 +4204,6 @@ int put_escstr(FILE *fd, char_u *strstart, int what)
{
char_u *str = strstart;
int c;
- int modifiers;
// :map xx <Nop>
if (*str == NUL && what == 1) {
@@ -4208,7 +4230,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what)
* when they are read back.
*/
if (c == K_SPECIAL && what != 2) {
- modifiers = 0x0;
+ int modifiers = 0;
if (str[1] == KS_MODIFIER) {
modifiers = str[2];
str += 3;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index d6d00d6e83..2db8689a56 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -125,8 +125,6 @@ typedef off_t off_T;
EXTERN int mod_mask INIT(= 0x0); // current key modifiers
-EXTERN bool lua_attr_active INIT(= false);
-
// Cmdline_row is the row where the command line starts, just below the
// last window.
// When the cmdline gets longer than the available space the screen gets
@@ -208,7 +206,7 @@ EXTERN int need_clr_eos INIT(= false); // need to clear text before
// displaying a message.
EXTERN int emsg_skip INIT(= 0); // don't display errors for
// expression that is skipped
-EXTERN int emsg_severe INIT(= false); // use message of next of several
+EXTERN bool emsg_severe INIT(= false); // use message of next of several
// emsg() calls for throw
EXTERN int did_endif INIT(= false); // just had ":endif"
EXTERN dict_T vimvardict; // Dictionary with v: variables
@@ -353,9 +351,11 @@ EXTERN int t_colors INIT(= 256); // int value of T_CCO
// position. Search_match_lines is the number of lines after the match (0 for
// a match within one line), search_match_endcol the column number of the
// character just after the match in the last line.
-EXTERN int highlight_match INIT(= false); // show search match pos
-EXTERN linenr_T search_match_lines; // lines of of matched string
-EXTERN colnr_T search_match_endcol; // col nr of match end
+EXTERN bool highlight_match INIT(= false); // show search match pos
+EXTERN linenr_T search_match_lines; // lines of of matched string
+EXTERN colnr_T search_match_endcol; // col nr of match end
+EXTERN linenr_T search_first_line INIT(= 0); // for :{FIRST},{last}s/pat
+EXTERN linenr_T search_last_line INIT(= MAXLNUM); // for :{first},{LAST}s/pat
EXTERN int no_smartcase INIT(= false); // don't use 'smartcase' once
diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h
index 19633d455f..c82a6cc121 100644
--- a/src/nvim/hashtab.h
+++ b/src/nvim/hashtab.h
@@ -51,6 +51,7 @@ typedef struct hashitem_S {
/// Initial size for a hashtable.
/// Our items are relatively small and growing is expensive, thus start with 16.
/// Must be a power of 2.
+/// This allows for storing 10 items (2/3 of 16) before a resize is needed.
#define HT_INIT_SIZE 16
/// An array-based hashtable.
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index fb277b25fd..bb0fdfec01 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -297,11 +297,12 @@ int set_indent(int size, int flags)
if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) {
ml_replace(curwin->w_cursor.lnum, newline, false);
if (!(flags & SIN_NOMARK)) {
- extmark_splice(curbuf,
- (int)curwin->w_cursor.lnum-1, skipcols,
- 0, (int)(p-oldline) - skipcols,
- 0, (int)(s-newline) - skipcols,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf,
+ (int)curwin->w_cursor.lnum-1,
+ skipcols,
+ (int)(p-oldline) - skipcols,
+ (int)(s-newline) - skipcols,
+ kExtmarkUndo);
}
if (flags & SIN_CHANGED) {
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index a553110552..2b6f022d9d 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -530,13 +530,24 @@ unsigned int trans_special(const char_u **srcp, const size_t src_len,
{
int modifiers = 0;
int key;
- unsigned int dlen = 0;
key = find_special_key(srcp, src_len, &modifiers, keycode, false, in_string);
if (key == 0) {
return 0;
}
+ return special_to_buf(key, modifiers, keycode, dst);
+}
+
+/// Put the character sequence for "key" with "modifiers" into "dst" and return
+/// the resulting length.
+/// When "keycode" is TRUE prefer key code, e.g. K_DEL instead of DEL.
+/// The sequence is not NUL terminated.
+/// This is how characters in a string are encoded.
+unsigned int special_to_buf(int key, int modifiers, bool keycode, char_u *dst)
+{
+ unsigned int dlen = 0;
+
// Put the appropriate modifier in a string.
if (modifiers != 0) {
dst[dlen++] = K_SPECIAL;
diff --git a/src/nvim/log.h b/src/nvim/log.h
index 17ff095473..f2e74df031 100644
--- a/src/nvim/log.h
+++ b/src/nvim/log.h
@@ -5,6 +5,17 @@
#include <stdbool.h>
#include "auto/config.h"
+#include "nvim/macros.h"
+
+// USDT probes. Example invokation:
+// NVIM_PROBE(nvim_foo_bar, 1, string.data);
+#if defined(HAVE_SYS_SDT_H)
+#include <sys/sdt.h> // NOLINT
+#define NVIM_PROBE(name, n, ...) STAP_PROBE##n(neovim, name, __VA_ARGS__)
+#else
+#define NVIM_PROBE(name, n, ...)
+#endif
+
#define DEBUG_LOG_LEVEL 0
#define INFO_LOG_LEVEL 1
@@ -68,6 +79,10 @@
# define LOG_CALLSTACK_TO_FILE(fp) log_callstack_to_file(fp, __func__, __LINE__)
#endif
+#if NVIM_HAS_INCLUDE("sanitizer/asan_interface.h")
+# include "sanitizer/asan_interface.h"
+#endif
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "log.h.generated.h"
#endif
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 32e804d213..030df69caa 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -1249,6 +1249,13 @@ type_error:
return ret;
}
+LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
+{
+ LuaRef rv = nlua_ref(lstate, -1);
+ lua_pop(lstate, 1);
+ return rv;
+}
+
#define GENERATE_INDEX_FUNCTION(type) \
type nlua_pop_##type(lua_State *lstate, Error *err) \
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 86da517685..5c665920b5 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -299,45 +299,66 @@ static int nlua_wait(lua_State *lstate)
return luaL_error(lstate, "timeout must be > 0");
}
- // Check if condition can be called.
- bool is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
+ int lua_top = lua_gettop(lstate);
- // Check if condition is callable table
- if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) {
- is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
- lua_pop(lstate, 1);
- }
+ // Check if condition can be called.
+ bool is_function = false;
+ if (lua_top >= 2 && !lua_isnil(lstate, 2)) {
+ is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
+
+ // Check if condition is callable table
+ if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) {
+ is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
+ lua_pop(lstate, 1);
+ }
- if (!is_function) {
- lua_pushliteral(lstate, "vim.wait: condition must be a function");
- return lua_error(lstate);
+ if (!is_function) {
+ lua_pushliteral(
+ lstate,
+ "vim.wait: if passed, condition must be a function");
+ return lua_error(lstate);
+ }
}
intptr_t interval = 200;
- if (lua_gettop(lstate) >= 3) {
+ if (lua_top >= 3 && !lua_isnil(lstate, 3)) {
interval = luaL_checkinteger(lstate, 3);
if (interval < 0) {
return luaL_error(lstate, "interval must be > 0");
}
}
+ bool fast_only = false;
+ if (lua_top >= 4) {
+ fast_only = lua_toboolean(lstate, 4);
+ }
+
+ MultiQueue *loop_events = fast_only || in_fast_callback > 0
+ ? main_loop.fast_events : main_loop.events;
+
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
// Start dummy timer.
time_watcher_init(&main_loop, tw, NULL);
- tw->events = main_loop.events;
+ tw->events = loop_events;
tw->blockable = true;
- time_watcher_start(tw, dummy_timer_due_cb,
- (uint64_t)interval, (uint64_t)interval);
+ time_watcher_start(
+ tw,
+ dummy_timer_due_cb,
+ (uint64_t)interval,
+ (uint64_t)interval);
int pcall_status = 0;
bool callback_result = false;
LOOP_PROCESS_EVENTS_UNTIL(
&main_loop,
- main_loop.events,
+ loop_events,
(int)timeout,
- nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int);
+ is_function ? nlua_wait_condition(
+ lstate,
+ &pcall_status,
+ &callback_result) : false || got_int);
// Stop dummy timer
time_watcher_stop(tw);
@@ -845,7 +866,7 @@ void nlua_unref(lua_State *lstate, LuaRef ref)
}
}
-void executor_free_luaref(LuaRef ref)
+void api_free_luaref(LuaRef ref)
{
lua_State *const lstate = nlua_enter();
nlua_unref(lstate, ref);
@@ -879,8 +900,8 @@ LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
/// @param[out] ret_tv Location where result will be saved.
///
/// @return Result of the execution.
-void executor_eval_lua(const String str, typval_T *const arg,
- typval_T *const ret_tv)
+void nlua_typval_eval(const String str, typval_T *const arg,
+ typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
#define EVALHEADER "local _A=select(1,...) return ("
@@ -902,8 +923,8 @@ void executor_eval_lua(const String str, typval_T *const arg,
}
}
-void executor_call_lua(const char *str, size_t len, typval_T *const args,
- int argcount, typval_T *ret_tv)
+void nlua_typval_call(const char *str, size_t len, typval_T *const args,
+ int argcount, typval_T *ret_tv)
FUNC_ATTR_NONNULL_ALL
{
#define CALLHEADER "return "
@@ -1006,14 +1027,14 @@ int typval_exec_lua_callable(
/// Execute Lua string
///
-/// Used for nvim_exec_lua().
+/// Used for nvim_exec_lua() and internally to execute a lua string.
///
/// @param[in] str String to execute.
/// @param[in] args array of ... args
/// @param[out] err Location where error will be saved.
///
/// @return Return value of the execution.
-Object executor_exec_lua_api(const String str, const Array args, Error *err)
+Object nlua_exec(const String str, const Array args, Error *err)
{
lua_State *const lstate = nlua_enter();
@@ -1040,17 +1061,30 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err)
return nlua_pop_Object(lstate, false, err);
}
-Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args,
- bool retval, Error *err)
+/// call a LuaRef as a function (or table with __call metamethod)
+///
+/// @param ref the reference to call (not consumed)
+/// @param name if non-NULL, sent to callback as first arg
+/// if NULL, only args are used
+/// @param retval if true, convert return value to Object
+/// if false, discard return value
+/// @param err Error details, if any (if NULL, errors are echoed)
+/// @return Return value of function, if retval was set. Otherwise NIL.
+Object nlua_call_ref(LuaRef ref, const char *name, Array args,
+ bool retval, Error *err)
{
lua_State *const lstate = nlua_enter();
nlua_pushref(lstate, ref);
- lua_pushstring(lstate, name);
+ int nargs = (int)args.size;
+ if (name != NULL) {
+ lua_pushstring(lstate, name);
+ nargs++;
+ }
for (size_t i = 0; i < args.size; i++) {
nlua_push_Object(lstate, args.items[i], false);
}
- if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) {
+ if (lua_pcall(lstate, nargs, retval ? 1 : 0, 0)) {
// if err is passed, the caller will deal with the error.
if (err) {
size_t len;
@@ -1465,3 +1499,40 @@ void nlua_free_typval_dict(dict_T *const d)
d->lua_table_ref = LUA_NOREF;
}
}
+
+void nlua_execute_log_keystroke(int c)
+{
+ char_u buf[NUMBUFLEN];
+ size_t buf_len = special_to_buf(c, mod_mask, false, buf);
+
+ lua_State *const lstate = nlua_enter();
+
+#ifndef NDEBUG
+ int top = lua_gettop(lstate);
+#endif
+
+ // [ vim ]
+ lua_getglobal(lstate, "vim");
+
+ // [ vim, vim._log_keystroke ]
+ lua_getfield(lstate, -1, "_log_keystroke");
+ luaL_checktype(lstate, -1, LUA_TFUNCTION);
+
+ // [ vim, vim._log_keystroke, buf ]
+ lua_pushlstring(lstate, (const char *)buf, buf_len);
+
+ if (lua_pcall(lstate, 1, 0, 0)) {
+ nlua_error(
+ lstate,
+ _("Error executing vim.log_keystroke lua callback: %.*s"));
+ }
+
+ // [ vim ]
+ lua_pop(lstate, 1);
+
+#ifndef NDEBUG
+ // [ ]
+ assert(top == lua_gettop(lstate));
+#endif
+}
+
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 6599b44584..1d7a15d9aa 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -24,6 +24,15 @@ EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
memcpy(&err_->msg[0], s, sizeof(s)); \
} while (0)
+#define NLUA_CLEAR_REF(x) \
+ do { \
+ /* Take the address to avoid double evaluation. #1375 */ \
+ if ((x) != LUA_NOREF) { \
+ api_free_luaref(x); \
+ (x) = LUA_NOREF; \
+ } \
+ } while (0)
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/executor.h.generated.h"
#endif
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 138031237e..5258352e72 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -39,10 +39,11 @@ typedef struct {
static struct luaL_Reg parser_meta[] = {
{ "__gc", parser_gc },
{ "__tostring", parser_tostring },
- { "parse_buf", parser_parse_buf },
+ { "parse", parser_parse },
{ "edit", parser_edit },
{ "tree", parser_tree },
{ "set_included_ranges", parser_set_ranges },
+ { "included_ranges", parser_get_ranges },
{ NULL, NULL }
};
@@ -62,6 +63,7 @@ static struct luaL_Reg node_meta[] = {
{ "end_", node_end },
{ "type", node_type },
{ "symbol", node_symbol },
+ { "field", node_field },
{ "named", node_named },
{ "missing", node_missing },
{ "has_error", node_has_error },
@@ -73,6 +75,7 @@ static struct luaL_Reg node_meta[] = {
{ "descendant_for_range", node_descendant_for_range },
{ "named_descendant_for_range", node_named_descendant_for_range },
{ "parent", node_parent },
+ { "iter_children", node_iter_children },
{ "_rawquery", node_rawquery },
{ NULL, NULL }
};
@@ -84,12 +87,17 @@ static struct luaL_Reg query_meta[] = {
{ NULL, NULL }
};
-// cursor is not exposed, but still needs garbage collection
+// cursors are not exposed, but still needs garbage collection
static struct luaL_Reg querycursor_meta[] = {
{ "__gc", querycursor_gc },
{ NULL, NULL }
};
+static struct luaL_Reg treecursor_meta[] = {
+ { "__gc", treecursor_gc },
+ { NULL, NULL }
+};
+
static PMap(cstr_t) *langs;
static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
@@ -116,6 +124,7 @@ void tslua_init(lua_State *L)
build_meta(L, "treesitter_node", node_meta);
build_meta(L, "treesitter_query", query_meta);
build_meta(L, "treesitter_querycursor", querycursor_meta);
+ build_meta(L, "treesitter_treecursor", treecursor_meta);
}
int tslua_has_language(lua_State *L)
@@ -166,6 +175,14 @@ int tslua_add_language(lua_State *L)
return luaL_error(L, "Failed to load parser: internal error");
}
+ uint32_t lang_version = ts_language_version(lang);
+ if (lang_version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) {
+ return luaL_error(
+ L,
+ "ABI version mismatch : expected %" PRIu32 ", found %" PRIu32,
+ TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, lang_version);
+ }
+
pmap_put(cstr_t)(langs, xstrdup(lang_name), lang);
lua_pushboolean(L, true);
@@ -278,43 +295,84 @@ static const char *input_cb(void *payload, uint32_t byte_index,
}
char_u *line = ml_get_buf(bp, position.row+1, false);
size_t len = STRLEN(line);
-
if (position.column > len) {
*bytes_read = 0;
- } else {
- size_t tocopy = MIN(len-position.column, BUFSIZE);
-
- memcpy(buf, line+position.column, tocopy);
- // Translate embedded \n to NUL
- memchrsub(buf, '\n', '\0', tocopy);
- *bytes_read = (uint32_t)tocopy;
- if (tocopy < BUFSIZE) {
- // now add the final \n. If it didn't fit, input_cb will be called again
- // on the same line with advanced column.
- buf[tocopy] = '\n';
- (*bytes_read)++;
- }
+ return "";
+ }
+ size_t tocopy = MIN(len-position.column, BUFSIZE);
+
+ memcpy(buf, line+position.column, tocopy);
+ // Translate embedded \n to NUL
+ memchrsub(buf, '\n', '\0', tocopy);
+ *bytes_read = (uint32_t)tocopy;
+ if (tocopy < BUFSIZE) {
+ // now add the final \n. If it didn't fit, input_cb will be called again
+ // on the same line with advanced column.
+ buf[tocopy] = '\n';
+ (*bytes_read)++;
}
return buf;
#undef BUFSIZE
}
-static int parser_parse_buf(lua_State *L)
+static void push_ranges(lua_State *L,
+ const TSRange *ranges,
+ const unsigned int length)
+{
+ lua_createtable(L, length, 0);
+ for (size_t i = 0; i < length; i++) {
+ lua_createtable(L, 4, 0);
+ lua_pushinteger(L, ranges[i].start_point.row);
+ lua_rawseti(L, -2, 1);
+ lua_pushinteger(L, ranges[i].start_point.column);
+ lua_rawseti(L, -2, 2);
+ lua_pushinteger(L, ranges[i].end_point.row);
+ lua_rawseti(L, -2, 3);
+ lua_pushinteger(L, ranges[i].end_point.column);
+ lua_rawseti(L, -2, 4);
+
+ lua_rawseti(L, -2, i+1);
+ }
+}
+
+static int parser_parse(lua_State *L)
{
TSLua_parser *p = parser_check(L);
if (!p) {
return 0;
}
- long bufnr = lua_tointeger(L, 2);
- buf_T *buf = handle_get_buffer(bufnr);
+ TSTree *new_tree;
+ size_t len;
+ const char *str;
+ long bufnr;
+ buf_T *buf;
+ TSInput input;
+
+ // This switch is necessary because of the behavior of lua_isstring, that
+ // consider numbers as strings...
+ switch (lua_type(L, 2)) {
+ case LUA_TSTRING:
+ str = lua_tolstring(L, 2, &len);
+ new_tree = ts_parser_parse_string(p->parser, p->tree, str, len);
+ break;
+
+ case LUA_TNUMBER:
+ bufnr = lua_tointeger(L, 2);
+ buf = handle_get_buffer(bufnr);
+
+ if (!buf) {
+ return luaL_error(L, "invalid buffer handle: %d", bufnr);
+ }
- if (!buf) {
- return luaL_error(L, "invalid buffer handle: %d", bufnr);
- }
+ input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8 };
+ new_tree = ts_parser_parse(p->parser, p->tree, input);
- TSInput input = { (void *)buf, input_cb, TSInputEncodingUTF8 };
- TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input);
+ break;
+
+ default:
+ return luaL_error(L, "invalid argument to parser:parse()");
+ }
uint32_t n_ranges = 0;
TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree,
@@ -326,20 +384,8 @@ static int parser_parse_buf(lua_State *L)
tslua_push_tree(L, p->tree);
- lua_createtable(L, n_ranges, 0);
- for (size_t i = 0; i < n_ranges; i++) {
- lua_createtable(L, 4, 0);
- lua_pushinteger(L, changed[i].start_point.row);
- lua_rawseti(L, -2, 1);
- lua_pushinteger(L, changed[i].start_point.column);
- lua_rawseti(L, -2, 2);
- lua_pushinteger(L, changed[i].end_point.row);
- lua_rawseti(L, -2, 3);
- lua_pushinteger(L, changed[i].end_point.column);
- lua_rawseti(L, -2, 4);
+ push_ranges(L, changed, n_ranges);
- lua_rawseti(L, -2, i+1);
- }
xfree(changed);
return 2;
}
@@ -437,6 +483,21 @@ static int parser_set_ranges(lua_State *L)
return 0;
}
+static int parser_get_ranges(lua_State *L)
+{
+ TSLua_parser *p = parser_check(L);
+ if (!p || !p->parser) {
+ return 0;
+ }
+
+ unsigned int len;
+ const TSRange *ranges = ts_parser_included_ranges(p->parser, &len);
+
+ push_ranges(L, ranges, len);
+
+ return 1;
+}
+
// Tree methods
@@ -646,6 +707,34 @@ static int node_symbol(lua_State *L)
return 1;
}
+static int node_field(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+
+ size_t name_len;
+ const char *field_name = luaL_checklstring(L, 2, &name_len);
+
+ TSTreeCursor cursor = ts_tree_cursor_new(node);
+
+ lua_newtable(L); // [table]
+ unsigned int curr_index = 0;
+
+ if (ts_tree_cursor_goto_first_child(&cursor)) {
+ do {
+ if (!STRCMP(field_name, ts_tree_cursor_current_field_name(&cursor))) {
+ push_node(L, ts_tree_cursor_current_node(&cursor), 1); // [table, node]
+ lua_rawseti(L, -2, ++curr_index);
+ }
+ } while (ts_tree_cursor_goto_next_sibling(&cursor));
+ }
+
+ ts_tree_cursor_delete(&cursor);
+ return 1;
+}
+
static int node_named(lua_State *L)
{
TSNode node;
@@ -746,6 +835,74 @@ static int node_named_descendant_for_range(lua_State *L)
return 1;
}
+static int node_next_child(lua_State *L)
+{
+ TSTreeCursor *ud = luaL_checkudata(
+ L, lua_upvalueindex(1), "treesitter_treecursor");
+ if (!ud) {
+ return 0;
+ }
+
+ TSNode source;
+ if (!node_check(L, lua_upvalueindex(2), &source)) {
+ return 0;
+ }
+
+ // First call should return first child
+ if (ts_node_eq(source, ts_tree_cursor_current_node(ud))) {
+ if (ts_tree_cursor_goto_first_child(ud)) {
+ goto push;
+ } else {
+ goto end;
+ }
+ }
+
+ if (ts_tree_cursor_goto_next_sibling(ud)) {
+push:
+ push_node(
+ L,
+ ts_tree_cursor_current_node(ud),
+ lua_upvalueindex(2)); // [node]
+
+ const char * field = ts_tree_cursor_current_field_name(ud);
+
+ if (field != NULL) {
+ lua_pushstring(L, ts_tree_cursor_current_field_name(ud));
+ } else {
+ lua_pushnil(L);
+ } // [node, field_name_or_nil]
+ return 2;
+ }
+
+end:
+ return 0;
+}
+
+static int node_iter_children(lua_State *L)
+{
+ TSNode source;
+ if (!node_check(L, 1, &source)) {
+ return 0;
+ }
+
+ TSTreeCursor *ud = lua_newuserdata(L, sizeof(TSTreeCursor)); // [udata]
+ *ud = ts_tree_cursor_new(source);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_treecursor"); // [udata, mt]
+ lua_setmetatable(L, -2); // [udata]
+ lua_pushvalue(L, 1); // [udata, source_node]
+ lua_pushcclosure(L, node_next_child, 2);
+
+ return 1;
+}
+
+static int treecursor_gc(lua_State *L)
+{
+ TSTreeCursor *ud = luaL_checkudata(L, 1, "treesitter_treecursor");
+ ts_tree_cursor_delete(ud);
+ return 0;
+}
+
static int node_parent(lua_State *L)
{
TSNode node;
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 820b237c4f..bfa8b91208 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -489,4 +489,60 @@ function vim.defer_fn(fn, timeout)
return timer
end
+local on_keystroke_callbacks = {}
+
+--- Register a lua {fn} with an {id} to be run after every keystroke.
+---
+--@param fn function: Function to call. It should take one argument, which is a string.
+--- The string will contain the literal keys typed.
+--- See |i_CTRL-V|
+---
+--- If {fn} is nil, it removes the callback for the associated {ns_id}
+--@param ns_id number? Namespace ID. If not passed or 0, will generate and return a new
+--- namespace ID from |nvim_create_namesapce()|
+---
+--@return number Namespace ID associated with {fn}
+---
+--@note {fn} will be automatically removed if an error occurs while calling.
+--- This is to prevent the annoying situation of every keystroke erroring
+--- while trying to remove a broken callback.
+--@note {fn} will not be cleared from |nvim_buf_clear_namespace()|
+--@note {fn} will receive the keystrokes after mappings have been evaluated
+function vim.register_keystroke_callback(fn, ns_id)
+ vim.validate {
+ fn = { fn, 'c', true},
+ ns_id = { ns_id, 'n', true }
+ }
+
+ if ns_id == nil or ns_id == 0 then
+ ns_id = vim.api.nvim_create_namespace('')
+ end
+
+ on_keystroke_callbacks[ns_id] = fn
+ return ns_id
+end
+
+--- Function that executes the keystroke callbacks.
+--@private
+function vim._log_keystroke(char)
+ local failed_ns_ids = {}
+ local failed_messages = {}
+ for k, v in pairs(on_keystroke_callbacks) do
+ local ok, err_msg = pcall(v, char)
+ if not ok then
+ vim.register_keystroke_callback(nil, k)
+
+ table.insert(failed_ns_ids, k)
+ table.insert(failed_messages, err_msg)
+ end
+ end
+
+ if failed_ns_ids[1] then
+ error(string.format(
+ "Error executing 'on_keystroke' with ns_ids of '%s'\n With messages: %s",
+ table.concat(failed_ns_ids, ", "),
+ table.concat(failed_messages, "\n")))
+ end
+end
+
return module
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index 3df7fa768d..07dcb4a8e8 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -1,6 +1,8 @@
#ifndef NVIM_MACROS_H
#define NVIM_MACROS_H
+#include "auto/config.h"
+
// EXTERN is only defined in main.c. That's where global variables are
// actually defined and initialized.
#ifndef EXTERN
@@ -150,6 +152,12 @@
#define STR_(x) #x
#define STR(x) STR_(x)
+#ifndef __has_include
+# define NVIM_HAS_INCLUDE(x) 0
+#else
+# define NVIM_HAS_INCLUDE __has_include
+#endif
+
#ifndef __has_attribute
# define NVIM_HAS_ATTRIBUTE(x) 0
#elif defined(__clang__) && __clang__ == 1 \
@@ -203,16 +211,33 @@
# define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wmissing-prototypes\"")
+# ifdef HAVE_WIMPLICIT_FALLTHROUGH_FLAG
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wimplicit-fallthrough\"")
+# else
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \
+ _Pragma("clang diagnostic push")
+# endif
# define PRAGMA_DIAG_POP \
_Pragma("clang diagnostic pop")
#elif defined(__GNUC__)
# define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"")
+# ifdef HAVE_WIMPLICIT_FALLTHROUGH_FLAG
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
+# else
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \
+ _Pragma("GCC diagnostic push")
+# endif
# define PRAGMA_DIAG_POP \
_Pragma("GCC diagnostic pop")
#else
# define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH
# define PRAGMA_DIAG_POP
#endif
diff --git a/src/nvim/main.c b/src/nvim/main.c
index f79fb57eae..a22df9cc69 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -7,6 +7,8 @@
#include <string.h>
#include <stdbool.h>
+#include <lua.h>
+#include <lauxlib.h>
#include <msgpack.h>
#include "nvim/ascii.h"
@@ -21,6 +23,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
+#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
@@ -160,6 +163,7 @@ void early_init(mparm_T *paramp)
env_init();
fs_init();
handle_init();
+ extmark_init();
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
init_normal_cmds(); // Init the table of Normal mode commands.
diff --git a/src/nvim/map.c b/src/nvim/map.c
index cba39f24b3..ca8ea76333 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -183,8 +183,7 @@ MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
#define EXTMARK_NS_INITIALIZER { 0, 0 }
MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER)
-#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
-#define EXTMARK_ITEM_INITIALIZER { 0, 0, 0, KVEC_INITIALIZER }
+#define EXTMARK_ITEM_INITIALIZER { 0, 0, NULL }
MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 0ad7865bf0..63a18f4129 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -73,6 +73,7 @@ MAP_DECLS(String, handle_T)
#define pmap_has(T) map_has(T, ptr_t)
#define pmap_key(T) map_key(T, ptr_t)
#define pmap_put(T) map_put(T, ptr_t)
+#define pmap_ref(T) map_ref(T, ptr_t)
/// @see pmap_del2
#define pmap_del(T) map_del(T, ptr_t)
#define pmap_clear(T) map_clear(T, ptr_t)
diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h
index 0c73e75b2e..8a1c564a6d 100644
--- a/src/nvim/marktree.h
+++ b/src/nvim/marktree.h
@@ -2,6 +2,7 @@
#define NVIM_MARKTREE_H
#include <stdint.h>
+#include "nvim/pos.h"
#include "nvim/map.h"
#include "nvim/garray.h"
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index e67be60aa6..ec4f4cbc21 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -512,7 +512,7 @@ int utf_ptr2cells(const char_u *p)
{
int c;
- /* Need to convert to a wide character. */
+ // Need to convert to a character number.
if (*p >= 0x80) {
c = utf_ptr2char(p);
/* An illegal byte is displayed as <xx>. */
@@ -582,7 +582,7 @@ size_t mb_string2cells_len(const char_u *str, size_t size)
return clen;
}
-/// Convert a UTF-8 byte sequence to a wide character
+/// Convert a UTF-8 byte sequence to a character number.
///
/// If the sequence is illegal or truncated by a NUL then the first byte is
/// returned.
@@ -1624,6 +1624,146 @@ int utf_head_off(const char_u *base, const char_u *p)
return (int)(p - q);
}
+// Whether space is NOT allowed before/after 'c'.
+bool utf_eat_space(int cc)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return (cc >= 0x2000 && cc <= 0x206F) // General punctuations
+ || (cc >= 0x2e00 && cc <= 0x2e7f) // Supplemental punctuations
+ || (cc >= 0x3000 && cc <= 0x303f) // CJK symbols and punctuations
+ || (cc >= 0xff01 && cc <= 0xff0f) // Full width ASCII punctuations
+ || (cc >= 0xff1a && cc <= 0xff20) // ..
+ || (cc >= 0xff3b && cc <= 0xff40) // ..
+ || (cc >= 0xff5b && cc <= 0xff65); // ..
+}
+
+// Whether line break is allowed before "cc".
+bool utf_allow_break_before(int cc)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ static const int BOL_prohibition_punct[] = {
+ '!',
+ '%',
+ ')',
+ ',',
+ ':',
+ ';',
+ '>',
+ '?',
+ ']',
+ '}',
+ 0x2019, // ’ right single quotation mark
+ 0x201d, // ” right double quotation mark
+ 0x2020, // † dagger
+ 0x2021, // ‡ double dagger
+ 0x2026, // … horizontal ellipsis
+ 0x2030, // ‰ per mille sign
+ 0x2031, // ‱ per then thousand sign
+ 0x203c, // ‼ double exclamation mark
+ 0x2047, // ⁇ double question mark
+ 0x2048, // ⁈ question exclamation mark
+ 0x2049, // ⁉ exclamation question mark
+ 0x2103, // ℃ degree celsius
+ 0x2109, // ℉ degree fahrenheit
+ 0x3001, // 、 ideographic comma
+ 0x3002, // 。 ideographic full stop
+ 0x3009, // 〉 right angle bracket
+ 0x300b, // 》 right double angle bracket
+ 0x300d, // 」 right corner bracket
+ 0x300f, // 』 right white corner bracket
+ 0x3011, // 】 right black lenticular bracket
+ 0x3015, // 〕 right tortoise shell bracket
+ 0x3017, // 〗 right white lenticular bracket
+ 0x3019, // 〙 right white tortoise shell bracket
+ 0x301b, // 〛 right white square bracket
+ 0xff01, // ! fullwidth exclamation mark
+ 0xff09, // ) fullwidth right parenthesis
+ 0xff0c, // , fullwidth comma
+ 0xff0e, // . fullwidth full stop
+ 0xff1a, // : fullwidth colon
+ 0xff1b, // ; fullwidth semicolon
+ 0xff1f, // ? fullwidth question mark
+ 0xff3d, // ] fullwidth right square bracket
+ 0xff5d, // } fullwidth right curly bracket
+ };
+
+ int first = 0;
+ int last = ARRAY_SIZE(BOL_prohibition_punct) - 1;
+
+ while (first < last) {
+ const int mid = (first + last) / 2;
+
+ if (cc == BOL_prohibition_punct[mid]) {
+ return false;
+ } else if (cc > BOL_prohibition_punct[mid]) {
+ first = mid + 1;
+ } else {
+ last = mid - 1;
+ }
+ }
+
+ return cc != BOL_prohibition_punct[first];
+}
+
+// Whether line break is allowed after "cc".
+bool utf_allow_break_after(int cc)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ static const int EOL_prohibition_punct[] = {
+ '(',
+ '<',
+ '[',
+ '`',
+ '{',
+ // 0x2014, // — em dash
+ 0x2018, // ‘ left single quotation mark
+ 0x201c, // “ left double quotation mark
+ // 0x2053, // ~ swung dash
+ 0x3008, // 〈 left angle bracket
+ 0x300a, // 《 left double angle bracket
+ 0x300c, // 「 left corner bracket
+ 0x300e, // 『 left white corner bracket
+ 0x3010, // 【 left black lenticular bracket
+ 0x3014, // 〔 left tortoise shell bracket
+ 0x3016, // 〖 left white lenticular bracket
+ 0x3018, // 〘 left white tortoise shell bracket
+ 0x301a, // 〚 left white square bracket
+ 0xff08, // ( fullwidth left parenthesis
+ 0xff3b, // [ fullwidth left square bracket
+ 0xff5b, // { fullwidth left curly bracket
+ };
+
+ int first = 0;
+ int last = ARRAY_SIZE(EOL_prohibition_punct) - 1;
+
+ while (first < last) {
+ const int mid = (first + last)/2;
+
+ if (cc == EOL_prohibition_punct[mid]) {
+ return false;
+ } else if (cc > EOL_prohibition_punct[mid]) {
+ first = mid + 1;
+ } else {
+ last = mid - 1;
+ }
+ }
+
+ return cc != EOL_prohibition_punct[first];
+}
+
+// Whether line break is allowed between "cc" and "ncc".
+bool utf_allow_break(int cc, int ncc)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // don't break between two-letter punctuations
+ if (cc == ncc
+ && (cc == 0x2014 // em dash
+ || cc == 0x2026)) { // horizontal ellipsis
+ return false;
+ }
+ return utf_allow_break_after(cc) && utf_allow_break_before(ncc);
+}
+
/// Copy a character, advancing the pointers
///
/// @param[in,out] fp Source of the character to copy.
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index d5788d96b3..57ed0d6588 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -257,11 +257,12 @@ int ml_open(buf_T *buf)
/*
* init fields in memline struct
*/
- buf->b_ml.ml_stack_size = 0; /* no stack yet */
- buf->b_ml.ml_stack = NULL; /* no stack yet */
- buf->b_ml.ml_stack_top = 0; /* nothing in the stack */
- buf->b_ml.ml_locked = NULL; /* no cached block */
- buf->b_ml.ml_line_lnum = 0; /* no cached line */
+ buf->b_ml.ml_stack_size = 0; // no stack yet
+ buf->b_ml.ml_stack = NULL; // no stack yet
+ buf->b_ml.ml_stack_top = 0; // nothing in the stack
+ buf->b_ml.ml_locked = NULL; // no cached block
+ buf->b_ml.ml_line_lnum = 0; // no cached line
+ buf->b_ml.ml_line_offset = 0;
buf->b_ml.ml_chunksize = NULL;
if (cmdmod.noswapfile) {
@@ -831,11 +832,12 @@ void ml_recover(bool checkext)
/*
* init fields in memline struct
*/
- buf->b_ml.ml_stack_size = 0; /* no stack yet */
- buf->b_ml.ml_stack = NULL; /* no stack yet */
- buf->b_ml.ml_stack_top = 0; /* nothing in the stack */
- buf->b_ml.ml_line_lnum = 0; /* no cached line */
- buf->b_ml.ml_locked = NULL; /* no locked block */
+ buf->b_ml.ml_stack_size = 0; // no stack yet
+ buf->b_ml.ml_stack = NULL; // no stack yet
+ buf->b_ml.ml_stack_top = 0; // nothing in the stack
+ buf->b_ml.ml_line_lnum = 0; // no cached line
+ buf->b_ml.ml_line_offset = 0;
+ buf->b_ml.ml_locked = NULL; // no locked block
buf->b_ml.ml_flags = 0;
/*
@@ -1358,7 +1360,7 @@ recover_names (
* Try finding a swap file by simply adding ".swp" to the file name.
*/
if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) {
- char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", TRUE);
+ char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", true);
if (swapname != NULL) {
if (os_path_exists(swapname)) {
files = xmalloc(sizeof(char_u *));
@@ -1636,10 +1638,11 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot)
// May also add the file name with a dot prepended, for swap file in same
// dir as original file.
if (prepend_dot) {
- names[num_names] = (char_u *)modname((char *)path, ".sw?", TRUE);
- if (names[num_names] == NULL)
+ names[num_names] = (char_u *)modname((char *)path, ".sw?", true);
+ if (names[num_names] == NULL) {
return num_names;
- ++num_names;
+ }
+ num_names++;
}
// Form the normal swap file name pattern by appending ".sw?".
@@ -1816,6 +1819,7 @@ ml_get_buf (
linenr_T lnum,
bool will_change // line will be changed
)
+ FUNC_ATTR_NONNULL_ALL
{
bhdr_T *hp;
DATA_BL *dp;
@@ -2403,12 +2407,13 @@ void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len)
if (len == -1) {
len = STRLEN(ptr);
}
- buf->deleted_bytes += len+1;
- if (buf->update_need_codepoints) {
- mb_utflen(ptr, len, &buf->deleted_codepoints,
- &buf->deleted_codeunits);
- buf->deleted_codepoints++; // NL char
- buf->deleted_codeunits++;
+ curbuf->deleted_bytes += len+1;
+ curbuf->deleted_bytes2 += len+1;
+ if (curbuf->update_need_codepoints) {
+ mb_utflen(ptr, len, &curbuf->deleted_codepoints,
+ &curbuf->deleted_codeunits);
+ curbuf->deleted_codepoints++; // NL char
+ curbuf->deleted_codeunits++;
}
}
@@ -2418,17 +2423,17 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy)
return ml_replace_buf(curbuf, lnum, line, copy);
}
-/*
- * Replace line lnum, with buffering, in current buffer.
- *
- * If "copy" is TRUE, make a copy of the line, otherwise the line has been
- * copied to allocated memory already.
- *
- * Check: The caller of this function should probably also call
- * changed_lines(), unless update_screen(NOT_VALID) is used.
- *
- * return FAIL for failure, OK otherwise
- */
+// Replace line "lnum", with buffering, in current buffer.
+//
+// If "copy" is true, make a copy of the line, otherwise the line has been
+// copied to allocated memory already.
+// If "copy" is false the "line" may be freed to add text properties!
+// Do not use it after calling ml_replace().
+//
+// Check: The caller of this function should probably also call
+// changed_lines(), unless update_screen(NOT_VALID) is used.
+//
+// return FAIL for failure, OK otherwise
int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy)
{
if (line == NULL) /* just checking... */
@@ -2831,6 +2836,7 @@ static void ml_flush_line(buf_T *buf)
}
buf->b_ml.ml_line_lnum = 0;
+ buf->b_ml.ml_line_offset = 0;
}
/*
@@ -3188,6 +3194,12 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name
char_u *fname_res = fname;
#ifdef HAVE_READLINK
char_u fname_buf[MAXPATHL];
+
+ // Expand symlink in the file name, so that we put the swap file with the
+ // actual file instead of with the symlink.
+ if (resolve_symlink(fname, fname_buf) == OK) {
+ fname_res = fname_buf;
+ }
#endif
int len = (int)STRLEN(dir_name);
@@ -3196,20 +3208,14 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name
&& len > 1
&& s[-1] == s[-2]) { // Ends with '//', Use Full path
r = NULL;
- if ((s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname)) != NULL) {
- r = (char_u *)modname((char *)s, ".swp", FALSE);
+ s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname_res);
+ if (s != NULL) {
+ r = (char_u *)modname((char *)s, ".swp", false);
xfree(s);
}
return r;
}
-#ifdef HAVE_READLINK
- /* Expand symlink in the file name, so that we put the swap file with the
- * actual file instead of with the symlink. */
- if (resolve_symlink(fname, fname_buf) == OK)
- fname_res = fname_buf;
-#endif
-
// Prepend a '.' to the swap file name for the current directory.
r = (char_u *)modname((char *)fname_res, ".swp",
dir_name[0] == '.' && dir_name[1] == NUL);
@@ -3980,10 +3986,10 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
/// Find offset for line or line with offset.
///
/// @param buf buffer to use
-/// @param lnum if > 0, find offset of lnum, store offset in offp
+/// @param lnum if > 0, find offset of lnum, return offset
/// if == 0, return line with offset *offp
-/// @param offp Location where offset of line is stored, or to read offset to
-/// use to find line. In the later case, store remaining offset.
+/// @param offp offset to use to find line, store remaining column offset
+/// Should be NULL when getting offset of line
/// @param no_ff ignore 'fileformat' option, always use one byte for NL.
///
/// @return -1 if information is not available
@@ -4003,8 +4009,22 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS);
int extra = 0;
- // take care of cached line first
- ml_flush_line(buf);
+ // take care of cached line first. Only needed if the cached line is before
+ // the requested line. Additionally cache the value for the cached line.
+ // This is used by the extmark code which needs the byte offset of the edited
+ // line. So when doing multiple small edits on the same line the value is
+ // only calculated once.
+ //
+ // NB: caching doesn't work with 'fileformat'. This is not a problem for
+ // bytetracking, as bytetracking ignores 'fileformat' option. But calling
+ // line2byte() will invalidate the cache for the time being (this function
+ // was never cached to start with anyway).
+ bool can_cache = (lnum != 0 && !ffdos && buf->b_ml.ml_line_lnum == lnum);
+ if (lnum == 0 || buf->b_ml.ml_line_lnum < lnum || !no_ff) {
+ ml_flush_line(curbuf);
+ } else if (can_cache && buf->b_ml.ml_line_offset > 0) {
+ return buf->b_ml.ml_line_offset;
+ }
if (buf->b_ml.ml_usedchunks == -1
|| buf->b_ml.ml_chunksize == NULL
@@ -4100,6 +4120,10 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
}
}
+ if (can_cache && size > 0) {
+ buf->b_ml.ml_line_offset = size;
+ }
+
return size;
}
diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h
index edd933b2cd..9a6f29a908 100644
--- a/src/nvim/memline_defs.h
+++ b/src/nvim/memline_defs.h
@@ -57,6 +57,8 @@ typedef struct memline {
linenr_T ml_line_lnum; // line number of cached line, 0 if not valid
char_u *ml_line_ptr; // pointer to cached line
+ size_t ml_line_offset; // cached byte offset of ml_line_lnum
+ int ml_line_offset_ff; // fileformat of cached line
bhdr_T *ml_locked; // block used by last ml_get
linenr_T ml_locked_low; // first line in ml_locked
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 8999365d32..6cd5616acf 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -607,8 +607,7 @@ int emsg_not_now(void)
static bool emsg_multiline(const char *s, bool multiline)
{
int attr;
- int ignore = false;
- int severe;
+ bool ignore = false;
// Skip this if not giving error messages at the moment.
if (emsg_not_now()) {
@@ -617,9 +616,9 @@ static bool emsg_multiline(const char *s, bool multiline)
called_emsg = true;
- // If "emsg_severe" is TRUE: When an error exception is to be thrown,
+ // If "emsg_severe" is true: When an error exception is to be thrown,
// prefer this message over previous messages for the same command.
- severe = emsg_severe;
+ bool severe = emsg_severe;
emsg_severe = false;
if (!emsg_off || vim_strchr(p_debug, 't') != NULL) {
@@ -630,7 +629,7 @@ static bool emsg_multiline(const char *s, bool multiline)
* when the message should be ignored completely (used for the
* interrupt message).
*/
- if (cause_errthrow((char_u *)s, severe, &ignore) == true) {
+ if (cause_errthrow((char_u *)s, severe, &ignore)) {
if (!ignore) {
did_emsg++;
}
@@ -1622,7 +1621,7 @@ const char *str2special(const char **const sp, const bool replace_spaces,
// Check for an illegal byte.
if (MB_BYTE2LEN((uint8_t)(*str)) > len) {
- transchar_nonprint((char_u *)buf, c);
+ transchar_nonprint(curbuf, (char_u *)buf, c);
*sp = str + 1;
return buf;
}
@@ -1887,6 +1886,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)
// wait-return prompt later. Needed when scrolling, resetting
// need_wait_return after some prompt, and then outputting something
// without scrolling
+ // Not needed when only using CR to move the cursor.
bool overflow = false;
if (ui_has(kUIMessages)) {
int count = msg_ext_visible + (msg_ext_overwrite ? 0 : 1);
@@ -1898,7 +1898,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)
overflow = msg_scrolled != 0;
}
- if (overflow && !msg_scrolled_ign) {
+ if (overflow && !msg_scrolled_ign && strcmp(str, "\r") != 0) {
need_wait_return = true;
}
msg_didany = true; // remember that something was outputted
@@ -2000,7 +2000,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
|| (*s == TAB && msg_col <= 7)
|| (utf_ptr2cells(s) > 1
&& msg_col <= 2))
- : (msg_col + t_col >= Columns - 1
+ : ((*s != '\r' && msg_col + t_col >= Columns - 1)
|| (*s == TAB
&& msg_col + t_col >= ((Columns - 1) & ~7))
|| (utf_ptr2cells(s) > 1
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 6dafbafb3e..1cd9ff2c4d 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -983,7 +983,7 @@ void preserve_exit(void)
FOR_ALL_BUFFERS(buf) {
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
- mch_errmsg((uint8_t *)"Vim: preserving files...\n");
+ mch_errmsg("Vim: preserving files...\r\n");
ui_flush();
ml_sync_all(false, false, true); // preserve all swap files
break;
@@ -992,7 +992,7 @@ void preserve_exit(void)
ml_close_all(false); // close all memfiles, without deleting
- mch_errmsg("Vim: Finished.\n");
+ mch_errmsg("Vim: Finished.\r\n");
getout(1);
}
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 8a8a639a52..e2a304efa5 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -641,7 +641,7 @@ void validate_virtcol_win(win_T *wp)
/*
* Validate curwin->w_cline_height only.
*/
-static void validate_cheight(void)
+void validate_cheight(void)
{
check_cursor_moved(curwin);
if (!(curwin->w_valid & VALID_CHEIGHT)) {
@@ -943,6 +943,9 @@ void curs_columns(
redraw_later(SOME_VALID);
}
+ // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise
+ curwin->w_valid_leftcol = curwin->w_leftcol;
+
curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 968cfde388..1cc400166c 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1977,20 +1977,20 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
case OP_FOLD:
VIsual_reselect = false; // don't reselect now
- foldCreate(curwin, oap->start.lnum, oap->end.lnum);
+ foldCreate(curwin, oap->start, oap->end);
break;
case OP_FOLDOPEN:
case OP_FOLDOPENREC:
case OP_FOLDCLOSE:
case OP_FOLDCLOSEREC:
- VIsual_reselect = false; /* don't reselect now */
- opFoldRange(oap->start.lnum, oap->end.lnum,
- oap->op_type == OP_FOLDOPEN
- || oap->op_type == OP_FOLDOPENREC,
- oap->op_type == OP_FOLDOPENREC
- || oap->op_type == OP_FOLDCLOSEREC,
- oap->is_VIsual);
+ VIsual_reselect = false; // don't reselect now
+ opFoldRange(oap->start, oap->end,
+ oap->op_type == OP_FOLDOPEN
+ || oap->op_type == OP_FOLDOPENREC,
+ oap->op_type == OP_FOLDOPENREC
+ || oap->op_type == OP_FOLDCLOSEREC,
+ oap->is_VIsual);
break;
case OP_FOLDDEL:
@@ -2483,7 +2483,7 @@ do_mouse (
typval_T rettv;
int doesrange;
(void)call_func((char_u *)tab_page_click_defs[mouse_col].func,
- (int)strlen(tab_page_click_defs[mouse_col].func),
+ -1,
&rettv, ARRAY_SIZE(argv), argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&doesrange, true, NULL, NULL);
@@ -2590,14 +2590,16 @@ do_mouse (
&& !is_drag
&& (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN))
&& which_button == MOUSE_LEFT) {
- /* open or close a fold at this line */
- if (jump_flags & MOUSE_FOLD_OPEN)
- openFold(curwin->w_cursor.lnum, 1L);
- else
- closeFold(curwin->w_cursor.lnum, 1L);
- /* don't move the cursor if still in the same window */
- if (curwin == old_curwin)
+ // open or close a fold at this line
+ if (jump_flags & MOUSE_FOLD_OPEN) {
+ openFold(curwin->w_cursor, 1L);
+ } else {
+ closeFold(curwin->w_cursor, 1L);
+ }
+ // don't move the cursor if still in the same window
+ if (curwin == old_curwin) {
curwin->w_cursor = save_cursor;
+ }
}
@@ -4393,51 +4395,55 @@ dozet:
case 'i': curwin->w_p_fen = !curwin->w_p_fen;
break;
- /* "za": open closed fold or close open fold at cursor */
- case 'a': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL))
- openFold(curwin->w_cursor.lnum, cap->count1);
- else {
- closeFold(curwin->w_cursor.lnum, cap->count1);
+ // "za": open closed fold or close open fold at cursor
+ case 'a': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) {
+ openFold(curwin->w_cursor, cap->count1);
+ } else {
+ closeFold(curwin->w_cursor, cap->count1);
curwin->w_p_fen = true;
}
break;
- /* "zA": open fold at cursor recursively */
- case 'A': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL))
- openFoldRecurse(curwin->w_cursor.lnum);
- else {
- closeFoldRecurse(curwin->w_cursor.lnum);
+ // "zA": open fold at cursor recursively
+ case 'A': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) {
+ openFoldRecurse(curwin->w_cursor);
+ } else {
+ closeFoldRecurse(curwin->w_cursor);
curwin->w_p_fen = true;
}
break;
- /* "zo": open fold at cursor or Visual area */
- case 'o': if (VIsual_active)
+ // "zo": open fold at cursor or Visual area
+ case 'o': if (VIsual_active) {
nv_operator(cap);
- else
- openFold(curwin->w_cursor.lnum, cap->count1);
+ } else {
+ openFold(curwin->w_cursor, cap->count1);
+ }
break;
- /* "zO": open fold recursively */
- case 'O': if (VIsual_active)
+ // "zO": open fold recursively
+ case 'O': if (VIsual_active) {
nv_operator(cap);
- else
- openFoldRecurse(curwin->w_cursor.lnum);
+ } else {
+ openFoldRecurse(curwin->w_cursor);
+ }
break;
- /* "zc": close fold at cursor or Visual area */
- case 'c': if (VIsual_active)
+ // "zc": close fold at cursor or Visual area
+ case 'c': if (VIsual_active) {
nv_operator(cap);
- else
- closeFold(curwin->w_cursor.lnum, cap->count1);
+ } else {
+ closeFold(curwin->w_cursor, cap->count1);
+ }
curwin->w_p_fen = true;
break;
- /* "zC": close fold recursively */
- case 'C': if (VIsual_active)
+ // "zC": close fold recursively
+ case 'C': if (VIsual_active) {
nv_operator(cap);
- else
- closeFoldRecurse(curwin->w_cursor.lnum);
+ } else {
+ closeFoldRecurse(curwin->w_cursor);
+ }
curwin->w_p_fen = true;
break;
@@ -6835,7 +6841,7 @@ static void nv_g_cmd(cmdarg_T *cap)
} else {
if (cap->count1 > 1) {
// if it fails, let the cursor still move to the last char
- cursor_down(cap->count1 - 1, false);
+ (void)cursor_down(cap->count1 - 1, false);
}
i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1;
coladvance((colnr_T)i);
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 595a699563..939cde0ba1 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -119,7 +119,7 @@ static char opchars[][3] =
{ 'r', NUL, OPF_CHANGE }, // OP_REPLACE
{ 'I', NUL, OPF_CHANGE }, // OP_INSERT
{ 'A', NUL, OPF_CHANGE }, // OP_APPEND
- { 'z', 'f', OPF_LINES }, // OP_FOLD
+ { 'z', 'f', 0 }, // OP_FOLD
{ 'z', 'o', OPF_LINES }, // OP_FOLDOPEN
{ 'z', 'O', OPF_LINES }, // OP_FOLDOPENREC
{ 'z', 'c', OPF_LINES }, // OP_FOLDCLOSE
@@ -496,9 +496,9 @@ static void shift_block(oparg_T *oap, int amount)
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
changed_bytes(curwin->w_cursor.lnum, (colnr_T)bd.textcol);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, startcol,
- 0, oldlen, 0, newlen,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, startcol,
+ oldlen, newlen,
+ kExtmarkUndo);
State = oldstate;
curwin->w_cursor.col = oldcol;
p_ri = old_p_ri;
@@ -595,9 +595,8 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
STRMOVE(newp + offset, oldp);
ml_replace(lnum, newp, false);
- extmark_splice(curbuf, (int)lnum-1, startcol,
- 0, skipped,
- 0, offset-startcol, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, startcol,
+ skipped, offset-startcol, kExtmarkUndo);
if (lnum == oap->end.lnum) {
/* Set "']" mark to the end of the block instead of the end of
@@ -1534,10 +1533,9 @@ int op_delete(oparg_T *oap)
// replace the line
ml_replace(lnum, newp, false);
- extmark_splice(curbuf, (int)lnum-1, bd.textcol,
- 0, bd.textlen,
- 0, bd.startspaces+bd.endspaces,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, bd.textcol,
+ bd.textlen, bd.startspaces+bd.endspaces,
+ kExtmarkUndo);
}
check_cursor_col();
@@ -1546,10 +1544,10 @@ int op_delete(oparg_T *oap)
oap->line_count = 0; // no lines deleted
} else if (oap->motion_type == kMTLineWise) {
if (oap->op_type == OP_CHANGE) {
- /* Delete the lines except the first one. Temporarily move the
- * cursor to the next line. Save the current line number, if the
- * last line is deleted it may be changed.
- */
+ // Delete the lines except the first one. Temporarily move the
+ // cursor to the next line. Save the current line number, if the
+ // last line is deleted it may be changed.
+
if (oap->line_count > 1) {
lnum = curwin->w_cursor.lnum;
++curwin->w_cursor.lnum;
@@ -1562,12 +1560,21 @@ int op_delete(oparg_T *oap)
beginline(BL_WHITE); // cursor on first non-white
did_ai = true; // delete the indent when ESC hit
ai_col = curwin->w_cursor.col;
- } else
- beginline(0); /* cursor in column 0 */
- truncate_line(FALSE); /* delete the rest of the line */
- /* leave cursor past last char in line */
- if (oap->line_count > 1)
- u_clearline(); /* "U" command not possible after "2cc" */
+ } else {
+ beginline(0); // cursor in column 0
+ }
+
+ int old_len = (int)STRLEN(ml_get(curwin->w_cursor.lnum));
+ truncate_line(false); // delete the rest of the line
+
+ extmark_splice_cols(curbuf,
+ (int)curwin->w_cursor.lnum-1, curwin->w_cursor.col,
+ old_len - curwin->w_cursor.col, 0, kExtmarkUndo);
+
+ // leave cursor past last char in line
+ if (oap->line_count > 1) {
+ u_clearline(); // "U" command not possible after "2cc"
+ }
} else {
del_lines(oap->line_count, TRUE);
beginline(BL_WHITE | BL_FIX);
@@ -1661,17 +1668,20 @@ int op_delete(oparg_T *oap)
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
del_lines(oap->line_count - 2, false);
+ bcount_t deleted_bytes = (bcount_t)curbuf->deleted_bytes2 - startpos.col;
// delete from start of line until op_end
n = (oap->end.col + 1 - !oap->inclusive);
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
+ deleted_bytes += n;
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
curbuf_splice_pending--;
extmark_splice(curbuf, (int)startpos.lnum-1, startpos.col,
- (int)oap->line_count-1, n, 0, 0, kExtmarkUndo);
+ (int)oap->line_count-1, n, deleted_bytes,
+ 0, 0, 0, kExtmarkUndo);
}
}
@@ -1711,7 +1721,7 @@ static inline void pbyte(pos_T lp, int c)
assert(c <= UCHAR_MAX);
*(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c;
if (!curbuf_splice_pending) {
- extmark_splice(curbuf, (int)lp.lnum-1, lp.col, 0, 1, 0, 1, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lp.lnum-1, lp.col, 1, 1, kExtmarkUndo);
}
}
@@ -1856,6 +1866,7 @@ int op_replace(oparg_T *oap, int c)
}
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
+ curbuf_splice_pending++;
linenr_T baselnum = curwin->w_cursor.lnum;
if (after_p != NULL) {
ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
@@ -1863,9 +1874,10 @@ int op_replace(oparg_T *oap, int c)
oap->end.lnum++;
xfree(after_p);
}
+ curbuf_splice_pending--;
extmark_splice(curbuf, (int)baselnum-1, bd.textcol,
- 0, bd.textlen,
- newrows, newcols, kExtmarkUndo);
+ 0, bd.textlen, bd.textlen,
+ newrows, newcols, newrows+newcols, kExtmarkUndo);
}
} else {
// Characterwise or linewise motion replace.
@@ -2399,9 +2411,8 @@ int op_change(oparg_T *oap)
oldp += bd.textcol;
STRMOVE(newp + offset, oldp);
ml_replace(linenr, newp, false);
- extmark_splice(curbuf, (int)linenr-1, bd.textcol,
- 0, 0,
- 0, vpos.coladd+(int)ins_len, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)linenr-1, bd.textcol,
+ 0, vpos.coladd+(int)ins_len, kExtmarkUndo);
}
}
check_cursor();
@@ -3098,6 +3109,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
for (i = 0; i < y_size; i++) {
int spaces;
char shortline;
+ // can just be 0 or 1, needed for blockwise paste beyond the current
+ // buffer end
+ int lines_appended = 0;
bd.startspaces = 0;
bd.endspaces = 0;
@@ -3111,6 +3125,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
break;
}
nr_lines++;
+ lines_appended = 1;
}
/* get the old line and advance to the position to insert at */
oldp = get_cursor_line_ptr();
@@ -3182,17 +3197,16 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
assert(columns >= 0);
memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns);
ml_replace(curwin->w_cursor.lnum, newp, false);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol,
- 0, delcount,
- 0, (int)totlen,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol,
+ delcount, (int)totlen + lines_appended, kExtmarkUndo);
++curwin->w_cursor.lnum;
if (i == 0)
curwin->w_cursor.col += bd.startspaces;
}
- changed_lines(lnum, 0, curwin->w_cursor.lnum, nr_lines, true);
+ changed_lines(lnum, 0, curbuf->b_op_start.lnum + (linenr_T)y_size
+ - (linenr_T)nr_lines , nr_lines, true);
/* Set '[ mark. */
curbuf->b_op_start = curwin->w_cursor;
@@ -3238,21 +3252,43 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
--lnum;
new_cursor = curwin->w_cursor;
- // simple case: insert into current line
+ // simple case: insert into one line at a time
if (y_type == kMTCharWise && y_size == 1) {
linenr_T end_lnum = 0; // init for gcc
+ linenr_T start_lnum = lnum;
if (VIsual_active) {
end_lnum = curbuf->b_visual.vi_end.lnum;
if (end_lnum < curbuf->b_visual.vi_start.lnum) {
end_lnum = curbuf->b_visual.vi_start.lnum;
}
+ if (end_lnum > start_lnum) {
+ // "col" is valid for the first line, in following lines
+ // the virtual column needs to be used. Matters for
+ // multi-byte characters.
+ pos_T pos = {
+ .lnum = lnum,
+ .col = col,
+ .coladd = 0,
+ };
+ getvcol(curwin, &pos, NULL, &vcol, NULL);
+ }
}
do {
totlen = (size_t)(count * yanklen);
if (totlen > 0) {
oldp = ml_get(lnum);
+ if (lnum > start_lnum) {
+ pos_T pos = {
+ .lnum = lnum,
+ };
+ if (getvpos(&pos, vcol) == OK) {
+ col = pos.col;
+ } else {
+ col = MAXCOL;
+ }
+ }
if (VIsual_active && col > (int)STRLEN(oldp)) {
lnum++;
continue;
@@ -3287,9 +3323,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND)))
++curwin->w_cursor.col;
changed_bytes(lnum, col);
- extmark_splice(curbuf, (int)lnum-1, col,
- 0, 0,
- 0, (int)totlen, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, col,
+ 0, (int)totlen, kExtmarkUndo);
} else {
// Insert at least one line. When y_type is kMTCharWise, break the first
// line in two.
@@ -3353,13 +3388,23 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
+ bcount_t totsize = 0;
+ int lastsize = 0;
+ if (y_type == kMTCharWise
+ || (y_type == kMTLineWise && flags & PUT_LINE_SPLIT)) {
+ for (i = 0; i < y_size-1; i++) {
+ totsize += (bcount_t)STRLEN(y_array[i]) + 1;
+ }
+ lastsize = (int)STRLEN(y_array[y_size-1]);
+ totsize += lastsize;
+ }
if (y_type == kMTCharWise) {
- extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
- (int)y_size-1, (int)STRLEN(y_array[y_size-1]),
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0,
+ (int)y_size-1, lastsize, totsize,
kExtmarkUndo);
} else if (y_type == kMTLineWise && flags & PUT_LINE_SPLIT) {
- extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
- (int)y_size+1, 0, kExtmarkUndo);
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0,
+ (int)y_size+1, 0, totsize+1, kExtmarkUndo);
}
}
@@ -3788,7 +3833,8 @@ int do_join(size_t count,
&& (!has_format_option(FO_MBYTE_JOIN)
|| (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100))
&& (!has_format_option(FO_MBYTE_JOIN2)
- || utf_ptr2char(curr) < 0x100 || endcurr1 < 0x100)
+ || (utf_ptr2char(curr) < 0x100 && !utf_eat_space(endcurr1))
+ || (endcurr1 < 0x100 && !utf_eat_space(utf_ptr2char(curr))))
) {
/* don't add a space if the line is ending in a space */
if (endcurr1 == ' ')
@@ -3803,9 +3849,10 @@ int do_join(size_t count,
}
if (t > 0 && curbuf_splice_pending == 0) {
+ colnr_T removed = (int)(curr- curr_start);
extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, sumsize,
- 1, (int)(curr- curr_start),
- 0, spaces[t],
+ 1, removed, removed + 1,
+ 0, spaces[t], spaces[t],
kExtmarkUndo);
}
currsize = (int)STRLEN(curr);
@@ -4112,49 +4159,41 @@ format_lines(
int avoid_fex /* don't use 'formatexpr' */
)
{
- int max_len;
- int is_not_par; /* current line not part of parag. */
- int next_is_not_par; /* next line not part of paragraph */
- int is_end_par; /* at end of paragraph */
- int prev_is_end_par = FALSE; /* prev. line not part of parag. */
- int next_is_start_par = FALSE;
- int leader_len = 0; /* leader len of current line */
- int next_leader_len; /* leader len of next line */
- char_u *leader_flags = NULL; /* flags for leader of current line */
- char_u *next_leader_flags; /* flags for leader of next line */
- int do_comments; /* format comments */
- int do_comments_list = 0; /* format comments with 'n' or '2' */
- int advance = TRUE;
- int second_indent = -1; /* indent for second line (comment
- * aware) */
- int do_second_indent;
- int do_number_indent;
- int do_trail_white;
- int first_par_line = TRUE;
+ bool is_not_par; // current line not part of parag.
+ bool next_is_not_par; // next line not part of paragraph
+ bool is_end_par; // at end of paragraph
+ bool prev_is_end_par = false; // prev. line not part of parag.
+ bool next_is_start_par = false;
+ int leader_len = 0; // leader len of current line
+ int next_leader_len; // leader len of next line
+ char_u *leader_flags = NULL; // flags for leader of current line
+ char_u *next_leader_flags; // flags for leader of next line
+ bool advance = true;
+ int second_indent = -1; // indent for second line (comment aware)
+ bool first_par_line = true;
int smd_save;
long count;
- int need_set_indent = TRUE; /* set indent of next paragraph */
- int force_format = FALSE;
- int old_State = State;
-
- /* length of a line to force formatting: 3 * 'tw' */
- max_len = comp_textwidth(TRUE) * 3;
-
- /* check for 'q', '2' and '1' in 'formatoptions' */
- do_comments = has_format_option(FO_Q_COMS);
- do_second_indent = has_format_option(FO_Q_SECOND);
- do_number_indent = has_format_option(FO_Q_NUMBER);
- do_trail_white = has_format_option(FO_WHITE_PAR);
-
- /*
- * Get info about the previous and current line.
- */
- if (curwin->w_cursor.lnum > 1)
- is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1
- , &leader_len, &leader_flags, do_comments
- );
- else
- is_not_par = TRUE;
+ bool need_set_indent = true; // set indent of next paragraph
+ bool force_format = false;
+ const int old_State = State;
+
+ // length of a line to force formatting: 3 * 'tw'
+ const int max_len = comp_textwidth(true) * 3;
+
+ // check for 'q', '2' and '1' in 'formatoptions'
+ const bool do_comments = has_format_option(FO_Q_COMS); // format comments
+ int do_comments_list = 0; // format comments with 'n' or '2'
+ const bool do_second_indent = has_format_option(FO_Q_SECOND);
+ const bool do_number_indent = has_format_option(FO_Q_NUMBER);
+ const bool do_trail_white = has_format_option(FO_WHITE_PAR);
+
+ // Get info about the previous and current line.
+ if (curwin->w_cursor.lnum > 1) {
+ is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1,
+ &leader_len, &leader_flags, do_comments);
+ } else {
+ is_not_par = true;
+ }
next_is_not_par = fmt_check_par(curwin->w_cursor.lnum
, &next_leader_len, &next_leader_flags, do_comments
);
@@ -4179,7 +4218,7 @@ format_lines(
* The last line to be formatted.
*/
if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) {
- next_is_not_par = TRUE;
+ next_is_not_par = true;
next_leader_len = 0;
next_leader_flags = NULL;
} else {
@@ -4190,7 +4229,7 @@ format_lines(
next_is_start_par =
(get_number_indent(curwin->w_cursor.lnum + 1) > 0);
}
- advance = TRUE;
+ advance = true;
is_end_par = (is_not_par || next_is_not_par || next_is_start_par);
if (!is_end_par && do_trail_white)
is_end_par = !ends_in_white(curwin->w_cursor.lnum);
@@ -4241,7 +4280,7 @@ format_lines(
leader_len, leader_flags,
next_leader_len, next_leader_flags)
)
- is_end_par = TRUE;
+ is_end_par = true;
/*
* If we have got to the end of a paragraph, or the line is
@@ -4278,9 +4317,9 @@ format_lines(
* end of the paragraph. */
if (line_count < 0)
break;
- first_par_line = TRUE;
+ first_par_line = true;
}
- force_format = FALSE;
+ force_format = false;
}
/*
@@ -4288,7 +4327,7 @@ format_lines(
* first delete the leader from the second line.
*/
if (!is_end_par) {
- advance = FALSE;
+ advance = false;
curwin->w_cursor.lnum++;
curwin->w_cursor.col = 0;
if (line_count < 0 && u_save_cursor() == FAIL)
@@ -4311,12 +4350,13 @@ format_lines(
beep_flush();
break;
}
- first_par_line = FALSE;
- /* If the line is getting long, format it next time */
- if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len)
- force_format = TRUE;
- else
- force_format = FALSE;
+ first_par_line = false;
+ // If the line is getting long, format it next time
+ if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len) {
+ force_format = true;
+ } else {
+ force_format = false;
+ }
}
}
line_breakcheck();
@@ -4377,11 +4417,10 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags,
int paragraph_start(linenr_T lnum)
{
char_u *p;
- int leader_len = 0; /* leader len of current line */
- char_u *leader_flags = NULL; /* flags for leader of current line */
- int next_leader_len = 0; /* leader len of next line */
- char_u *next_leader_flags = NULL; /* flags for leader of next line */
- int do_comments; /* format comments */
+ int leader_len = 0; // leader len of current line
+ char_u *leader_flags = NULL; // flags for leader of current line
+ int next_leader_len = 0; // leader len of next line
+ char_u *next_leader_flags = NULL; // flags for leader of next line
if (lnum <= 1)
return TRUE; /* start of the file */
@@ -4390,7 +4429,7 @@ int paragraph_start(linenr_T lnum)
if (*p == NUL)
return TRUE; /* after empty line */
- do_comments = has_format_option(FO_Q_COMS);
+ const bool do_comments = has_format_option(FO_Q_COMS); // format comments
if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) {
return true; // after non-paragraph line
}
@@ -5897,7 +5936,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
const char regname = (char)name;
tv_list_append_string(args, &regname, 1);
- typval_T result = eval_call_provider("clipboard", "get", args);
+ typval_T result = eval_call_provider("clipboard", "get", args, false);
if (result.v_type != VAR_LIST) {
if (result.v_type == VAR_NUMBER && result.vval.v_number == 0) {
@@ -6045,7 +6084,7 @@ static void set_clipboard(int name, yankreg_T *reg)
tv_list_append_string(args, &regtype, 1); // -V614
tv_list_append_string(args, ((char[]) { (char)name }), 1);
- (void)eval_call_provider("clipboard", "set", args);
+ (void)eval_call_provider("clipboard", "set", args, true);
}
/// Avoid slow things (clipboard) during batch operations (while/for-loops).
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 168160834b..ca902d5669 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -174,6 +174,7 @@ static char_u *p_syn;
static char_u *p_spc;
static char_u *p_spf;
static char_u *p_spl;
+static char_u *p_spo;
static long p_ts;
static long p_tw;
static int p_udf;
@@ -311,6 +312,9 @@ static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent",
static char *(p_fcl_values[]) = { "all", NULL };
static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview",
"noinsert", "noselect", NULL };
+#ifdef BACKSLASH_IN_FILENAME
+static char *(p_csl_values[]) = { "slash", "backslash", NULL };
+#endif
static char *(p_icm_values[]) = { "nosplit", "split", NULL };
static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2",
"auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9",
@@ -347,22 +351,23 @@ static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len)
return &dest[len + shift];
}
-/// Compute length of a colon-separated value, doubled and with some suffixes
+/// Compute length of a ENV_SEPCHAR-separated value, doubled and with some
+/// suffixes
///
-/// @param[in] val Colon-separated array value.
+/// @param[in] val ENV_SEPCHAR-separated array value.
/// @param[in] common_suf_len Length of the common suffix which is appended to
/// each item in the array, twice.
/// @param[in] single_suf_len Length of the suffix which is appended to each
/// item in the array once.
///
-/// @return Length of the comma-separated string array that contains each item
-/// in the original array twice with suffixes with given length
+/// @return Length of the ENV_SEPCHAR-separated string array that contains each
+/// item in the original array twice with suffixes with given length
/// (common_suf is present after each new item, single_suf is present
/// after half of the new items) and with commas after each item, commas
/// inside the values are escaped.
-static inline size_t compute_double_colon_len(const char *const val,
- const size_t common_suf_len,
- const size_t single_suf_len)
+static inline size_t compute_double_env_sep_len(const char *const val,
+ const size_t common_suf_len,
+ const size_t single_suf_len)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
if (val == NULL || *val == NUL) {
@@ -373,7 +378,7 @@ static inline size_t compute_double_colon_len(const char *const val,
do {
size_t dir_len;
const char *dir;
- iter = vim_env_iter(':', val, iter, &dir, &dir_len);
+ iter = vim_env_iter(ENV_SEPCHAR, val, iter, &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len
+ !after_pathsep(dir, dir + dir_len)) * 2
@@ -385,13 +390,13 @@ static inline size_t compute_double_colon_len(const char *const val,
#define NVIM_SIZE (sizeof("nvim") - 1)
-/// Add directories to a comma-separated array from a colon-separated one
+/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one
///
/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in
/// addition to suf1 and suf2.
///
/// @param[in,out] dest Destination comma-separated array.
-/// @param[in] val Source colon-separated array.
+/// @param[in] val Source ENV_SEPCHAR-separated array.
/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it
/// directory separator is appended. Suffix must not contain
/// commas.
@@ -404,10 +409,10 @@ static inline size_t compute_double_colon_len(const char *const val,
/// Otherwise in reverse.
///
/// @return (dest + appended_characters_length)
-static inline char *add_colon_dirs(char *dest, const char *const val,
- const char *const suf1, const size_t len1,
- const char *const suf2, const size_t len2,
- const bool forward)
+static inline char *add_env_sep_dirs(char *dest, const char *const val,
+ const char *const suf1, const size_t len1,
+ const char *const suf2, const size_t len2,
+ const bool forward)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1)
{
if (val == NULL || *val == NUL) {
@@ -417,8 +422,8 @@ static inline char *add_colon_dirs(char *dest, const char *const val,
do {
size_t dir_len;
const char *dir;
- iter = (forward ? vim_env_iter : vim_env_iter_rev)(':', val, iter, &dir,
- &dir_len);
+ iter = (forward ? vim_env_iter : vim_env_iter_rev)(ENV_SEPCHAR, val, iter,
+ &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
dest = strcpy_comma_escaped(dest, dir, dir_len);
if (!after_pathsep(dest - 1, dest)) {
@@ -581,10 +586,11 @@ static void set_runtimepath_default(bool clean_arg)
rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1;
}
}
- rtp_size += compute_double_colon_len(data_dirs, NVIM_SIZE + 1 + SITE_SIZE + 1,
- AFTER_SIZE + 1);
- rtp_size += compute_double_colon_len(config_dirs, NVIM_SIZE + 1,
- AFTER_SIZE + 1);
+ rtp_size += compute_double_env_sep_len(data_dirs,
+ NVIM_SIZE + 1 + SITE_SIZE + 1,
+ AFTER_SIZE + 1);
+ rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1,
+ AFTER_SIZE + 1);
if (rtp_size == 0) {
return;
}
@@ -592,20 +598,20 @@ static void set_runtimepath_default(bool clean_arg)
char *rtp_cur = rtp;
rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
NULL, 0, NULL, 0);
- rtp_cur = add_colon_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true);
+ rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true);
rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
"site", SITE_SIZE, NULL, 0);
- rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0,
- true);
+ rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0,
+ true);
rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone,
NULL, 0, NULL, 0);
rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0);
- rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
- "after", AFTER_SIZE, false);
+ rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
+ "after", AFTER_SIZE, false);
rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
"site", SITE_SIZE, "after", AFTER_SIZE);
- rtp_cur = add_colon_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0,
- false);
+ rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0,
+ false);
rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
"after", AFTER_SIZE, NULL, 0);
// Strip trailing comma.
@@ -1355,11 +1361,11 @@ int do_set(
// Disallow changing some options from modelines.
if (opt_flags & OPT_MODELINE) {
if (flags & (P_SECURE | P_NO_ML)) {
- errmsg = (char_u *)_("E520: Not allowed in a modeline");
+ errmsg = (char_u *)N_("E520: Not allowed in a modeline");
goto skip;
}
if ((flags & P_MLE) && !p_mle) {
- errmsg = (char_u *)_(
+ errmsg = (char_u *)N_(
"E992: Not allowed in a modeline when 'modelineexpr' is off");
goto skip;
}
@@ -1376,7 +1382,7 @@ int do_set(
// Disallow changing some options in the sandbox
if (sandbox != 0 && (flags & P_SECURE)) {
- errmsg = (char_u *)_(e_sandbox);
+ errmsg = e_sandbox;
goto skip;
}
@@ -1712,6 +1718,7 @@ int do_set(
#ifdef BACKSLASH_IN_FILENAME
&& !((flags & P_EXPAND)
&& vim_isfilec(arg[1])
+ && !ascii_iswhite(arg[1])
&& (arg[1] != '\\'
|| (s == newval
&& arg[2] != '\\')))
@@ -2282,6 +2289,7 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_s.b_p_spc);
check_string_option(&buf->b_s.b_p_spf);
check_string_option(&buf->b_s.b_p_spl);
+ check_string_option(&buf->b_s.b_p_spo);
check_string_option(&buf->b_p_sua);
check_string_option(&buf->b_p_cink);
check_string_option(&buf->b_p_cino);
@@ -3087,6 +3095,10 @@ ambw_end:
} else if (varp == &(curwin->w_s->b_p_spc)) {
// When 'spellcapcheck' is set compile the regexp program.
errmsg = compile_cap_prog(curwin->w_s);
+ } else if (varp == &(curwin->w_s->b_p_spo)) { // 'spelloptions'
+ if (**varp != NUL && STRCMP("camel", *varp) != 0) {
+ errmsg = e_invarg;
+ }
} else if (varp == &p_sps) { // 'spellsuggest'
if (spell_check_sps() != OK) {
errmsg = e_invarg;
@@ -3179,6 +3191,13 @@ ambw_end:
} else {
completeopt_was_set();
}
+#ifdef BACKSLASH_IN_FILENAME
+ } else if (gvarp == &p_csl) { // 'completeslash'
+ if (check_opt_strings(p_csl, p_csl_values, false) != OK
+ || check_opt_strings(curbuf->b_p_csl, p_csl_values, false) != OK) {
+ errmsg = e_invarg;
+ }
+#endif
} else if (varp == &curwin->w_p_scl) {
// 'signcolumn'
if (check_opt_strings(*varp, p_scl_values, false) != OK) {
@@ -3429,6 +3448,7 @@ ambw_end:
// recursively, to avoid endless recurrence.
apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname,
value_changed || syn_recursive == 1, curbuf);
+ curbuf->b_flags |= BF_SYN_SET;
syn_recursive--;
} else if (varp == &(curbuf->b_p_ft)) {
// 'filetype' is set, trigger the FileType autocommand
@@ -5856,6 +5876,9 @@ static char_u *get_varp(vimoption_T *p)
case PV_COM: return (char_u *)&(curbuf->b_p_com);
case PV_CMS: return (char_u *)&(curbuf->b_p_cms);
case PV_CPT: return (char_u *)&(curbuf->b_p_cpt);
+# ifdef BACKSLASH_IN_FILENAME
+ case PV_CSL: return (char_u *)&(curbuf->b_p_csl);
+# endif
case PV_CFU: return (char_u *)&(curbuf->b_p_cfu);
case PV_OFU: return (char_u *)&(curbuf->b_p_ofu);
case PV_EOL: return (char_u *)&(curbuf->b_p_eol);
@@ -5893,6 +5916,7 @@ static char_u *get_varp(vimoption_T *p)
case PV_SPC: return (char_u *)&(curwin->w_s->b_p_spc);
case PV_SPF: return (char_u *)&(curwin->w_s->b_p_spf);
case PV_SPL: return (char_u *)&(curwin->w_s->b_p_spl);
+ case PV_SPO: return (char_u *)&(curwin->w_s->b_p_spo);
case PV_SW: return (char_u *)&(curbuf->b_p_sw);
case PV_TFU: return (char_u *)&(curbuf->b_p_tfu);
case PV_TS: return (char_u *)&(curbuf->b_p_ts);
@@ -6142,6 +6166,9 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_inf = p_inf;
buf->b_p_swf = cmdmod.noswapfile ? false : p_swf;
buf->b_p_cpt = vim_strsave(p_cpt);
+# ifdef BACKSLASH_IN_FILENAME
+ buf->b_p_csl = vim_strsave(p_csl);
+# endif
buf->b_p_cfu = vim_strsave(p_cfu);
buf->b_p_ofu = vim_strsave(p_ofu);
buf->b_p_tfu = vim_strsave(p_tfu);
@@ -6172,6 +6199,7 @@ void buf_copy_options(buf_T *buf, int flags)
(void)compile_cap_prog(&buf->b_s);
buf->b_s.b_p_spf = vim_strsave(p_spf);
buf->b_s.b_p_spl = vim_strsave(p_spl);
+ buf->b_s.b_p_spo = vim_strsave(p_spo);
buf->b_p_inde = vim_strsave(p_inde);
buf->b_p_indk = vim_strsave(p_indk);
buf->b_p_fp = empty_option;
@@ -6791,7 +6819,8 @@ static void langmap_set(void)
/// Return true if format option 'x' is in effect.
/// Take care of no formatting when 'paste' is set.
-int has_format_option(int x)
+bool has_format_option(int x)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (p_paste) {
return false;
@@ -7258,7 +7287,8 @@ unsigned int get_bkc_value(buf_T *buf)
}
/// Return the current end-of-line type: EOL_DOS, EOL_UNIX or EOL_MAC.
-int get_fileformat(buf_T *buf)
+int get_fileformat(const buf_T *buf)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
int c = *buf->b_p_ff;
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index ecaa941082..4042b79262 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -77,12 +77,13 @@
#define FO_ONE_LETTER '1'
#define FO_WHITE_PAR 'w' // trailing white space continues paragr.
#define FO_AUTO 'a' // automatic formatting
+#define FO_RIGOROUS_TW ']' // respect textwidth rigorously
#define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines
#define FO_PERIOD_ABBR 'p' // don't break a single space after a period
#define DFLT_FO_VI "vt"
#define DFLT_FO_VIM "tcqj"
-#define FO_ALL "tcroq2vlb1mMBn,awjp" // for do_set()
+#define FO_ALL "tcroq2vlb1mMBn,aw]jp" // for do_set()
// characters for the p_cpo option:
#define CPO_ALTREAD 'a' // ":read" sets alternate file name
@@ -372,6 +373,9 @@ EXTERN long p_columns; // 'columns'
EXTERN int p_confirm; // 'confirm'
EXTERN int p_cp; // 'compatible'
EXTERN char_u *p_cot; // 'completeopt'
+# ifdef BACKSLASH_IN_FILENAME
+EXTERN char_u *p_csl; // 'completeslash'
+# endif
EXTERN long p_pb; // 'pumblend'
EXTERN long p_ph; // 'pumheight'
EXTERN long p_pw; // 'pumwidth'
@@ -453,7 +457,6 @@ EXTERN char_u *p_header; // 'printheader'
EXTERN int p_prompt; // 'prompt'
EXTERN char_u *p_guicursor; // 'guicursor'
EXTERN char_u *p_guifont; // 'guifont'
-EXTERN char_u *p_guifontset; // 'guifontset'
EXTERN char_u *p_guifontwide; // 'guifontwide'
EXTERN char_u *p_hf; // 'helpfile'
EXTERN long p_hh; // 'helpheight'
@@ -513,6 +516,7 @@ EXTERN long p_mle; // 'modelineexpr'
EXTERN long p_mls; // 'modelines'
EXTERN char_u *p_mouse; // 'mouse'
EXTERN char_u *p_mousem; // 'mousemodel'
+EXTERN long p_mousef; // 'mousefocus'
EXTERN long p_mouset; // 'mousetime'
EXTERN int p_more; // 'more'
EXTERN char_u *p_opfunc; // 'operatorfunc'
@@ -743,6 +747,7 @@ enum {
, BV_CPT
, BV_DICT
, BV_TSR
+ , BV_CSL
, BV_CFU
, BV_DEF
, BV_INC
@@ -787,6 +792,7 @@ enum {
, BV_SPC
, BV_SPF
, BV_SPL
+ , BV_SPO
, BV_STS
, BV_SUA
, BV_SW
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 60a38dc2e3..65ef7a4527 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -453,6 +453,15 @@ return {
defaults={if_true={vi="menu,preview"}}
},
{
+ full_name='completeslash', abbreviation='csl',
+ type='string', scope={'buffer'},
+ vi_def=true,
+ vim=true,
+ varname='p_csl',
+ enable_if='BACKSLASH_IN_FILENAME',
+ defaults={if_true={vi=""}}
+ },
+ {
full_name='confirm', abbreviation='cf',
type='bool', scope={'global'},
vi_def=true,
@@ -1022,15 +1031,6 @@ return {
defaults={if_true={vi=""}}
},
{
- full_name='guifontset', abbreviation='gfs',
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- vi_def=true,
- varname='p_guifontset',
- redraw={'ui_option'},
- defaults={if_true={vi=""}}
- },
- {
full_name='guifontwide', abbreviation='gfw',
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
@@ -1597,7 +1597,8 @@ return {
full_name='mousefocus', abbreviation='mousef',
type='bool', scope={'global'},
vi_def=true,
- enable_if=false,
+ redraw={'ui_option'},
+ varname='p_mousef',
defaults={if_true={vi=false}}
},
{
@@ -2328,6 +2329,16 @@ return {
defaults={if_true={vi="best"}}
},
{
+ full_name='spelloptions', abbreviation='spo',
+ type='string', list='onecomma', scope={'buffer'},
+ deny_duplicates=true,
+ secure=true,
+ vi_def=true,
+ expand=true,
+ varname='p_spo',
+ defaults={if_true={vi="", vim=""}}
+ },
+ {
full_name='splitbelow', abbreviation='sb',
type='bool', scope={'global'},
vi_def=true,
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 082ad58223..879266e3d4 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -1176,7 +1176,9 @@ bool os_setenv_append_path(const char *fname)
temp[0] = NUL;
} else {
xstrlcpy(temp, path, newlen);
- xstrlcat(temp, ENV_SEPSTR, newlen);
+ if (ENV_SEPCHAR != path[pathlen - 1]) {
+ xstrlcat(temp, ENV_SEPSTR, newlen);
+ }
}
xstrlcat(temp, os_buf, newlen);
os_setenv("PATH", temp, 1);
diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c
index fe2d7986bf..603191a0ff 100644
--- a/src/nvim/os/lang.c
+++ b/src/nvim/os/lang.c
@@ -43,14 +43,20 @@ void lang_init(void)
}
}
+ char buf[50] = { 0 };
+ bool set_lang;
if (lang_region) {
- os_setenv("LANG", lang_region, true);
+ set_lang = true;
+ xstrlcpy(buf, lang_region, sizeof(buf));
} else {
- char buf[20] = { 0 };
- if (CFStringGetCString(cf_lang_region, buf, 20,
- kCFStringEncodingUTF8)) {
- os_setenv("LANG", buf, true);
+ set_lang = CFStringGetCString(cf_lang_region, buf, 40,
+ kCFStringEncodingUTF8);
+ }
+ if (set_lang) {
+ if (strcasestr(buf, "utf-8") == NULL) {
+ xstrlcat(buf, ".UTF-8", sizeof(buf));
}
+ os_setenv("LANG", buf, true);
}
CFRelease(cf_lang_region);
# ifdef HAVE_LOCALE_H
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index bfe230b521..bc774b8ebc 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -161,8 +161,8 @@ static void deadly_signal(int signum)
WLOG("got signal %d (%s)", signum, signal_name(signum));
- snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\n",
- signal_name(signum));
+ snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n",
+ signal_name(signum));
// Preserve files and exit.
preserve_exit();
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 346e40e02e..4b6533cd0c 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -56,6 +56,8 @@ uint64_t os_now(void)
/// Sleeps for `ms` milliseconds.
///
+/// @see uv_sleep() (libuv v1.34.0)
+///
/// @param ms Number of milliseconds to sleep
/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
void os_delay(uint64_t ms, bool ignoreinput)
@@ -72,6 +74,8 @@ void os_delay(uint64_t ms, bool ignoreinput)
/// Sleeps for `us` microseconds.
///
+/// @see uv_sleep() (libuv v1.34.0)
+///
/// @param us Number of microseconds to sleep.
/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
/// If false, waiting is aborted on any input.
@@ -172,10 +176,11 @@ char *os_ctime_r(const time_t *restrict clock, char *restrict result,
struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local);
// MSVC returns NULL for an invalid value of seconds.
if (clock_local_ptr == NULL) {
- snprintf(result, result_len, "%s\n", _("(Invalid)"));
+ xstrlcpy(result, _("(Invalid)"), result_len);
} else {
- strftime(result, result_len, "%a %b %d %H:%M:%S %Y\n", clock_local_ptr);
+ strftime(result, result_len, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr);
}
+ xstrlcat(result, "\n", result_len);
return result;
}
diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim
index 650c6155e2..d55d4cfa4d 100644
--- a/src/nvim/po/check.vim
+++ b/src/nvim/po/check.vim
@@ -25,6 +25,7 @@ func! GetMline()
" remove '%', not used for formatting.
let idline = substitute(idline, "'%'", '', 'g')
+ let idline = substitute(idline, "%%", '', 'g')
" remove '%' used for plural forms.
let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '')
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 19ea8e897a..f2179f4f1b 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -14,8 +14,8 @@ msgid ""
msgstr ""
"Project-Id-Version: vim 7.4\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-12-18 22:42+0200\n"
-"PO-Revision-Date: 2018-12-18 22:42+0200\n"
+"POT-Creation-Date: 2020-08-23 18:45+0300\n"
+"PO-Revision-Date: 2020-08-23 20:19+0300\n"
"Last-Translator: Анатолій Сахнік <sakhnik@gmail.com>\n"
"Language-Team: Ukrainian\n"
"Language: uk\n"
@@ -95,6 +95,12 @@ msgid ""
"E89: No write since last change for buffer %<PRId64> (add ! to override)"
msgstr "E89: Буфер %<PRId64> має зміни (! щоб не зважати)"
+msgid "E37: No write since last change (add ! to override)"
+msgstr "E37: Зміни не було записано (! щоб не зважати)"
+
+msgid "E37: No write since last change"
+msgstr "E37: Не записано після останніх змін"
+
msgid "W14: Warning: List of file names overflow"
msgstr "W14: Обережно: Список назв файлів переповнено"
@@ -123,9 +129,6 @@ msgstr " [Змінено]"
msgid "[Not edited]"
msgstr "[Не редаговано]"
-msgid "[New file]"
-msgstr "[Новий файл]"
-
msgid "[Read errors]"
msgstr "[Помилки читання]"
@@ -168,23 +171,17 @@ msgstr "Знизу"
msgid "Top"
msgstr "Вгорі"
-msgid "[Scratch]"
-msgstr "[З нуля]"
+msgid "E382: Cannot write, 'buftype' option is set"
+msgstr "E382: Не можу записати, вказана опція 'buftype'"
-msgid ""
-"\n"
-"--- Signs ---"
-msgstr ""
-"\n"
-"--- Позначки ---"
+msgid "[Prompt]"
+msgstr "[Запит]"
-#, c-format
-msgid "Signs for %s:"
-msgstr "Позначки для %s:"
+msgid "[Scratch]"
+msgstr "[З нуля]"
-#, c-format
-msgid " line=%<PRId64> id=%d name=%s"
-msgstr " рядок=%<PRId64> id=%d назва=%s"
+msgid "W10: Warning: Changing a readonly file"
+msgstr "W10: Застереження: Змінюється файл призначений лише для читання"
msgid "can only be opened in headless mode"
msgstr "можна відкрити тільки без інтерфейсу користувача"
@@ -198,6 +195,9 @@ msgstr "Неможливо надіслати дані у закритий по
msgid "Can't send raw data to rpc channel"
msgstr "Неможливо надіслати дані у канал завдання"
+msgid "E474: Failed to convert list to msgpack string buffer"
+msgstr "E474: Не вдалося перетворити список у текстовий буфер msgpack"
+
msgid "E545: Missing colon"
msgstr "E545: Пропущено двокрапку"
@@ -233,9 +233,6 @@ msgstr "E816: Не вдалося прочитати результат patch"
msgid "E98: Cannot read diff output"
msgstr "E98: Не вдалося прочитати результат diff"
-msgid "E959: Invalid diff format."
-msgstr "E959: Неправильний формат порівняння."
-
msgid "E99: Current buffer is not in diff mode"
msgstr "E99: Цей буфер не в режимі порівняння"
@@ -264,6 +261,81 @@ msgstr "E787: Буфер несподівано змінився"
msgid "E104: Escape not allowed in digraph"
msgstr "E104: У диграфах не може міститися escape"
+msgid "Custom"
+msgstr "Власне"
+
+msgid "Latin supplement"
+msgstr "Латиниця доповнення"
+
+msgid "Greek and Coptic"
+msgstr "Грецька і коптська"
+
+msgid "Cyrillic"
+msgstr "Кирилиця"
+
+msgid "Hebrew"
+msgstr "Іврит"
+
+msgid "Arabic"
+msgstr "Арабська"
+
+msgid "Latin extended"
+msgstr "Латиниця розширена"
+
+msgid "Greek extended"
+msgstr "Грецька розширена"
+
+msgid "Punctuation"
+msgstr "Пунктуація"
+
+msgid "Super- and subscripts"
+msgstr "Над- і підписи"
+
+msgid "Currency"
+msgstr "Валюта"
+
+msgid "Other"
+msgstr "Інше"
+
+msgid "Roman numbers"
+msgstr "Римські цифри"
+
+msgid "Arrows"
+msgstr "Стрілки"
+
+msgid "Mathematical operators"
+msgstr "Математичні оператори"
+
+msgid "Technical"
+msgstr "Технічне"
+
+msgid "Box drawing"
+msgstr "Малювання прямокутників"
+
+msgid "Block elements"
+msgstr "Частини блоків"
+
+msgid "Geometric shapes"
+msgstr "Геометричні фігури"
+
+msgid "Symbols"
+msgstr "Символи"
+
+msgid "Dingbats"
+msgstr "Дурниці"
+
+msgid "CJK symbols and punctuation"
+msgstr "Символи і пунктуація CJK"
+
+msgid "Hiragana"
+msgstr "Хірагана"
+
+msgid "Katakana"
+msgstr "Катакана"
+
+msgid "Bopomofo"
+msgstr "Бопомофо"
+
msgid "E544: Keymap file not found"
msgstr "E544: Не знайдено файл розкладки"
@@ -379,55 +451,18 @@ msgstr "E18: Неочікувані символи у :let"
msgid "E111: Missing ']'"
msgstr "E111: Бракує ']'"
-#, c-format
-msgid "E686: Argument of %s must be a List"
-msgstr "E686: Аргумент у %s має бути списком"
-
-#, c-format
-msgid "E712: Argument of %s must be a List or Dictionary"
-msgstr "E712: Аргумент у %s має бути списком чи словником"
-
-msgid "E714: List required"
-msgstr "E714: Потрібен список"
-
-msgid "E715: Dictionary required"
-msgstr "E715: Потрібен словник"
-
-msgid "E928: String required"
-msgstr "E928: Потрібен String"
-
-#, c-format
-msgid "E118: Too many arguments for function: %s"
-msgstr "E118: Забагато аргументів для функції: %s"
-
-#, c-format
-msgid "E716: Key not present in Dictionary: %s"
-msgstr "E716: Немає такого ключа у словнику: %s"
-
-#, c-format
-msgid "E122: Function %s already exists, add ! to replace it"
-msgstr "E122: Функція %s уже існує, ! щоб замінити"
-
-msgid "E717: Dictionary entry already exists"
-msgstr "E717: Запис у словнику вже існує"
-
-msgid "E718: Funcref required"
-msgstr "E718: Треба посилання на функцію"
-
msgid "E719: Cannot use [:] with a Dictionary"
msgstr "E719: Не можна використати [:] зі словником"
#, c-format
-msgid "E130: Unknown function: %s"
-msgstr "E130: Невідома функція: %s"
-
-#, c-format
msgid "E461: Illegal variable name: %s"
msgstr "E461: Неприпустима назва змінної: %s"
-#, c-format
-msgid "E46: Cannot change read-only variable \"%.*s\""
-msgstr "E46: Змінна тільки для читання: «%.*s»"
+msgid "E995: Cannot modify existing variable"
+msgstr "E995: Неможливо змінити наявну змінну"
+
+msgid "E957: Invalid window number"
+msgstr "E957: Некоректний номер вікна"
#, c-format
msgid "E734: Wrong variable type for %s="
@@ -439,6 +474,19 @@ msgid ""
msgstr ""
"E5700: Вираз із 'spellsuggest' має повертати список із рівно двома елементами"
+msgid "E991: cannot use =<< here"
+msgstr "E991: Тут не можна використати =<<"
+
+msgid "E221: Marker cannot start with lower case letter"
+msgstr "E221: Позначка не може починатися із малої літери"
+
+msgid "E172: Missing marker"
+msgstr "E172: Бракує позначки"
+
+#, c-format
+msgid "E990: Missing end marker '%s'"
+msgstr "E990: Бракує позначки кінця «%s»"
+
msgid "E687: Less targets than List items"
msgstr "E687: Цілей менше, ніж елементів списку"
@@ -452,6 +500,15 @@ msgstr "Друга ; у списку змінних"
msgid "E738: Can't list variables for %s"
msgstr "E738: Не можна перерахувати змінні у %s"
+msgid "E996: Cannot lock an environment variable"
+msgstr "E996: Неможливо заблокувати змінну оточення"
+
+msgid "E996: Cannot lock an option"
+msgstr "E996: Неможливо заблокувати опцію"
+
+msgid "E996: Cannot lock a register"
+msgstr "E996: Неможливо заблокувати регістр"
+
#, c-format
msgid "E121: Undefined variable: %.*s"
msgstr "E121: Невизначена змінна: %.*s"
@@ -468,20 +525,22 @@ msgstr "E713: Неможливо вжити порожній ключ після
msgid "E709: [:] requires a List value"
msgstr "E709: [:] вимагає список"
+msgid "E996: Cannot lock a range"
+msgstr "E996: Неможливо заблокувати діапазон"
+
msgid "E710: List value has more items than target"
msgstr "E710: Список має більше елементів, ніж ціль"
msgid "E711: List value has not enough items"
msgstr "E711: Список має недостатньо елементів"
+msgid "E996: Cannot lock a list or dict"
+msgstr "E996: Неможливо заблокувати список чи словник"
+
msgid "E690: Missing \"in\" after :for"
msgstr "E690: Пропущено «in» після :for"
#, c-format
-msgid "E107: Missing parentheses: %s"
-msgstr "E107: Пропущено дужки: %s"
-
-#, c-format
msgid "E108: No such variable: \"%s\""
msgstr "E108: Змінної немає: «%s»"
@@ -563,68 +622,6 @@ msgstr "E722: Бракує коми у словнику: %s"
msgid "E723: Missing end of Dictionary '}': %s"
msgstr "E723: Немає кінцівки словника '}': %s"
-#, c-format
-msgid "E125: Illegal argument: %s"
-msgstr "E125: Недозволений аргумент: %s"
-
-#, c-format
-msgid "E853: Duplicate argument name: %s"
-msgstr "E853: Назва аргументу повторюється: %s"
-
-#, c-format
-msgid "E740: Too many arguments for function %s"
-msgstr "E740: Забагато аргументів для функції %s"
-
-#, c-format
-msgid "E116: Invalid arguments for function %s"
-msgstr "E116: Неправильні аргументи функції %s"
-
-#, c-format
-msgid "E117: Unknown function: %s"
-msgstr "E117: Невідома функція: %s"
-
-#, c-format
-msgid "E933: Function was deleted: %s"
-msgstr "E933: Функцію було видалено: %s"
-
-#, c-format
-msgid "E119: Not enough arguments for function: %s"
-msgstr "E119: Замало аргументів для функції %s"
-
-#, c-format
-msgid "E120: Using <SID> not in a script context: %s"
-msgstr "E120: <SID> використовується не у контексті скрипту: %s"
-
-#, c-format
-msgid "E725: Calling dict function without Dictionary: %s"
-msgstr "E725: Виклик dict-функції без словника: %s"
-
-#, c-format
-msgid "Error converting the call result: %s"
-msgstr "Не вдалося перетворити результат виклику: %s"
-
-msgid "add() argument"
-msgstr "аргумент add()"
-
-msgid "E699: Too many arguments"
-msgstr "E699: Забагато аргументів"
-
-#, c-format
-msgid "Invalid channel stream \"%s\""
-msgstr "Некоректний потік завдання «%s»"
-
-msgid "E785: complete() can only be used in Insert mode"
-msgstr "E785: complete() можна вживати тільки в режимі вставляння"
-
-msgid "&Ok"
-msgstr "&O:Гаразд"
-
-msgid "dictwatcheradd() argument"
-msgstr "аргумент dictwatcheradd()"
-
-msgid "extend() argument"
-msgstr "аргумент extend()"
-
msgid "map() argument"
msgstr "аргумент map()"
@@ -641,124 +638,13 @@ msgstr "E922: очікується словник"
msgid "E923: Second argument of function() must be a list or a dict"
msgstr "E923: Другий аргумент function() має бути списком чи словником"
-msgid "E5000: Cannot find tab number."
-msgstr "E5000: Не можна знайти номер вкладки."
-
-msgid "E5001: Higher scope cannot be -1 if lower scope is >= 0."
-msgstr "E5001: Вища область не може бути -1, якщо нижча область >= 0."
-
-msgid "E5002: Cannot find window number."
-msgstr "E5002: Неможливо знайти номер вікна."
-
msgid "E5050: {opts} must be the only argument"
msgstr "E5050: {opts} має бути єдиним аргументом"
-msgid "called inputrestore() more often than inputsave()"
-msgstr "Виклики до inputrestore() частіше, ніж до inputsave()"
-
-msgid "insert() argument"
-msgstr "аргумент insert()"
-
-msgid "E786: Range not allowed"
-msgstr "E786: Інтервал не дозволено"
-
-msgid "E474: Failed to convert list to string"
-msgstr "E474: Не вдалося перетворити список у текст"
-
-#, c-format
-msgid "E474: Failed to parse %.*s"
-msgstr "E474: Не вдалося розібрати %.*s"
-
-msgid "E701: Invalid type for len()"
-msgstr "E701: Некоректний тип для len()"
-
-#, c-format
-msgid "E798: ID is reserved for \":match\": %<PRId64>"
-msgstr "E798: ID зарезервовано для \":match\": %<PRId64>"
-
-#, c-format
-msgid "E798: ID is reserved for \"match\": %<PRId64>"
-msgstr "E798: ID зарезервовано для \"match\": %<PRId64>"
-
-#, c-format
-msgid "msgpackdump() argument, index %i"
-msgstr "аргумент msgpackdump(), індекс %i"
-
-msgid "E5070: Character number must not be less than zero"
-msgstr "E5070: Номер символа має бути невід’ємним"
-
-#, c-format
-msgid "E5071: Character number must not be greater than INT_MAX (%i)"
-msgstr "E5071: Номер символа не може бути більшим, ніж INT_MAX (%i)"
-
-msgid "E726: Stride is zero"
-msgstr "E726: Крок нульовий"
-
-msgid "E727: Start past end"
-msgstr "E727: Початок за кінцем"
-
-msgid "<empty>"
-msgstr "<нічого>"
-
-msgid "remove() argument"
-msgstr "аргумент remove()"
-
-msgid "E655: Too many symbolic links (cycle?)"
-msgstr "E655: Забагато символьних посилань (цикл?)"
-
-msgid "reverse() argument"
-msgstr "аргумент reverse()"
-
-#, c-format
-msgid "E5010: List item %d of the second argument is not a string"
-msgstr "E5010: Елемент списку %d другого аргументу не текст"
-
-#, c-format
-msgid "E927: Invalid action: '%s'"
-msgstr "E927: Неправильна дія: «%s»"
-
-#, c-format
-msgid "E474: List item %d is either not a dictionary or an empty one"
-msgstr "E474: Елемент списку %d або не словник або порожній"
-
-#, c-format
-msgid "E474: List item %d is missing one of the required keys"
-msgstr "E474: Елемент списку %d немає одного з обов’язкових ключів"
-
-#, c-format
-msgid "connection failed: %s"
-msgstr "з’єднання не вдалося: %s"
-
-msgid "sort() argument"
-msgstr "аргумент sort()"
-
-msgid "uniq() argument"
-msgstr "аргумент uniq()"
-
-msgid "E702: Sort compare function failed"
-msgstr "E702: Помилка у функції порівняння"
-
-msgid "E882: Uniq compare function failed"
-msgstr "E882: Помилка у функції порівняння uniq"
-
-#, c-format
-msgid "E6100: \"%s\" is not a valid stdpath"
-msgstr "E6100: \"%s\" — некоректний stdpath"
-
-msgid "(Invalid)"
-msgstr "(Неможливо)"
-
-#, c-format
-msgid "E935: invalid submatch number: %d"
-msgstr "E935: неправильний номер групи співпадіння: %d"
-
#, c-format
msgid "Executing command: \"%s\""
msgstr "Виконується команда: «%s»"
-msgid "Can only call this function in an unmodified buffer"
-msgstr "Цю функцію можна викликати тільки у незміненому буфері"
-
msgid "E921: Invalid callback argument"
msgstr "E921: Некоректний аргумент функції зворотнього виклику"
@@ -767,21 +653,6 @@ msgid "E80: Error while writing: %s"
msgstr "E80: Помилка під час запису: %s"
#, c-format
-msgid "E5060: Unknown flag: %s"
-msgstr "E5060: Невідомий прапорець: %s"
-
-msgid "E482: Can't open file with an empty name"
-msgstr "E482: Не вдалося відкрити файл з порожнім ім’ям"
-
-#, c-format
-msgid "E482: Can't open file %s for writing: %s"
-msgstr "E482: Не вдалося відкрити файл %s для запису: %s"
-
-#, c-format
-msgid "E80: Error when closing file %s: %s"
-msgstr "E80: Помилка при закритті файлу %s: %s"
-
-#, c-format
msgid "E963: setting %s to value with wrong type"
msgstr "E963: встановлення %s до значення з неправильним типом"
@@ -799,90 +670,11 @@ msgstr "E704: Назва змінної Funcref має починатися з
#, c-format
msgid "E705: Variable name conflicts with existing function: %s"
-msgstr "E705: Назва змінної співпадає з існуючою функцією: %s"
+msgstr "E705: Назва змінної співпадає з наявною функцією: %s"
msgid "E698: variable nested too deep for making a copy"
msgstr "E698: Змінна вкладена занадто глибоко щоб зробити її копію"
-#, c-format
-msgid "E123: Undefined function: %s"
-msgstr "E123: Невизначена функція: %s"
-
-#, c-format
-msgid "E124: Missing '(': %s"
-msgstr "E124: Бракує '(': %s"
-
-msgid "E862: Cannot use g: here"
-msgstr "E862: Тут не можна використати g:"
-
-#, c-format
-msgid "E932: Closure function should not be at top level: %s"
-msgstr "E932: Функція замикання не може бути на верхньому рівні: %s"
-
-msgid "E126: Missing :endfunction"
-msgstr "E126: Бракує :endfunction"
-
-#, c-format
-msgid "W22: Text found after :endfunction: %s"
-msgstr "W22: Трапився текст після :endfunction: %s"
-
-#, c-format
-msgid "E707: Function name conflicts with variable: %s"
-msgstr "E707: Назва функції співпадає зі змінною: %s"
-
-#, c-format
-msgid "E127: Cannot redefine function %s: It is in use"
-msgstr "E127: Не вдалося перевизначити функцію %s: вона використовується"
-
-#, c-format
-msgid "E746: Function name does not match script file name: %s"
-msgstr "E746: Назва функції не збігається з назвою файлу скрипту: %s"
-
-msgid "E129: Function name required"
-msgstr "E129: Не вказано назву функції"
-
-#, c-format
-msgid "E128: Function name must start with a capital or \"s:\": %s"
-msgstr "E128: Назва функції має починатися з великої літери або \"s:\": %s"
-
-#, c-format
-msgid "E884: Function name cannot contain a colon: %s"
-msgstr "E884: Назва функції не може містити двокрапку: %s"
-
-#, c-format
-msgid "E131: Cannot delete function %s: It is in use"
-msgstr "E131: Не вдалося знищити функцію %s: Вона використовується"
-
-#, c-format
-msgid "Cannot delete function %s: It is being used internally"
-msgstr "Не вдалося знищити функцію %s: Вона використовується"
-
-msgid "E132: Function call depth is higher than 'maxfuncdepth'"
-msgstr "E132: Глибина викликів функції перевищує 'maxfuncdepth'"
-
-#, c-format
-msgid "calling %s"
-msgstr "викликається %s"
-
-#, c-format
-msgid "%s aborted"
-msgstr "%s припинено"
-
-#, c-format
-msgid "%s returning #%<PRId64>"
-msgstr "%s повертає #%<PRId64>"
-
-#, c-format
-msgid "%s returning %s"
-msgstr "%s повертає %s"
-
-#, c-format
-msgid "continuing in %s"
-msgstr "продовження в %s"
-
-msgid "E133: :return not inside a function"
-msgstr "E133: :return поза межами функції"
-
msgid ""
"\n"
"\tLast set from "
@@ -1155,6 +947,179 @@ msgid "E684: list index out of range: %<PRId64>"
msgstr "E684: Індекс списку поза межами: %<PRId64>"
#, c-format
+msgid "E686: Argument of %s must be a List"
+msgstr "E686: Аргумент у %s має бути списком"
+
+msgid "E928: String required"
+msgstr "E928: Потрібен String"
+
+#, c-format
+msgid "Error converting the call result: %s"
+msgstr "Не вдалося перетворити результат виклику: %s"
+
+msgid "add() argument"
+msgstr "аргумент add()"
+
+#, c-format
+msgid "E158: Invalid buffer name: %s"
+msgstr "E158: Некоректна назва буфера: %s"
+
+#, c-format
+msgid "Invalid channel stream \"%s\""
+msgstr "Некоректний потік завдання «%s»"
+
+msgid "E785: complete() can only be used in Insert mode"
+msgstr "E785: complete() можна вживати тільки в режимі вставляння"
+
+msgid "&Ok"
+msgstr "&O:Гаразд"
+
+msgid "Context stack is empty"
+msgstr "Стек контексту порожній"
+
+msgid "dictwatcheradd() argument"
+msgstr "аргумент dictwatcheradd()"
+
+msgid "E900: maxdepth must be non-negative number"
+msgstr "E900: maxdepth має бути невід’ємним числом"
+
+msgid "flatten() argument"
+msgstr "аргумент flatten()"
+
+msgid "extend() argument"
+msgstr "аргумент extend()"
+
+msgid "E5000: Cannot find tab number."
+msgstr "E5000: Не можна знайти номер вкладки."
+
+msgid "E5001: Higher scope cannot be -1 if lower scope is >= 0."
+msgstr "E5001: Вища область не може бути -1, якщо нижча область >= 0."
+
+msgid "E5002: Cannot find window number."
+msgstr "E5002: Неможливо знайти номер вікна."
+
+msgid "called inputrestore() more often than inputsave()"
+msgstr "Виклики до inputrestore() частіше, ніж до inputsave()"
+
+msgid "insert() argument"
+msgstr "аргумент insert()"
+
+msgid "E786: Range not allowed"
+msgstr "E786: Інтервал не дозволено"
+
+msgid "E474: Failed to convert list to string"
+msgstr "E474: Не вдалося перетворити список у текст"
+
+#, c-format
+msgid "E474: Failed to parse %.*s"
+msgstr "E474: Не вдалося розібрати %.*s"
+
+msgid "E701: Invalid type for len()"
+msgstr "E701: Некоректний тип для len()"
+
+#, c-format
+msgid "E798: ID is reserved for \":match\": %<PRId64>"
+msgstr "E798: ID зарезервовано для \":match\": %<PRId64>"
+
+#, c-format
+msgid "E798: ID is reserved for \"match\": %<PRId64>"
+msgstr "E798: ID зарезервовано для \"match\": %<PRId64>"
+
+#, c-format
+msgid "msgpackdump() argument, index %i"
+msgstr "аргумент msgpackdump(), індекс %i"
+
+msgid "E5070: Character number must not be less than zero"
+msgstr "E5070: Номер символа має бути невід’ємним"
+
+#, c-format
+msgid "E5071: Character number must not be greater than INT_MAX (%i)"
+msgstr "E5071: Номер символа не може бути більшим, ніж INT_MAX (%i)"
+
+msgid "E726: Stride is zero"
+msgstr "E726: Крок нульовий"
+
+msgid "E727: Start past end"
+msgstr "E727: Початок за кінцем"
+
+msgid "<empty>"
+msgstr "<нічого>"
+
+msgid "remove() argument"
+msgstr "аргумент remove()"
+
+msgid "E655: Too many symbolic links (cycle?)"
+msgstr "E655: Забагато символьних посилань (цикл?)"
+
+msgid "reverse() argument"
+msgstr "аргумент reverse()"
+
+#, c-format
+msgid "E5010: List item %d of the second argument is not a string"
+msgstr "E5010: Елемент списку %d другого аргументу не текст"
+
+#, c-format
+msgid "E927: Invalid action: '%s'"
+msgstr "E927: Неправильна дія: «%s»"
+
+#, c-format
+msgid "E474: List item %d is either not a dictionary or an empty one"
+msgstr "E474: Елемент списку %d або не словник або порожній"
+
+#, c-format
+msgid "E474: List item %d is missing one of the required keys"
+msgstr "E474: Елемент списку %d немає одного з обов’язкових ключів"
+
+#, c-format
+msgid "E962: Invalid action: '%s'"
+msgstr "E962: Неправильна дія: «%s»"
+
+#, c-format
+msgid "connection failed: %s"
+msgstr "з’єднання не вдалося: %s"
+
+msgid "sort() argument"
+msgstr "аргумент sort()"
+
+msgid "uniq() argument"
+msgstr "аргумент uniq()"
+
+msgid "E702: Sort compare function failed"
+msgstr "E702: Помилка у функції порівняння"
+
+msgid "E882: Uniq compare function failed"
+msgstr "E882: Помилка у функції порівняння uniq"
+
+#, c-format
+msgid "E6100: \"%s\" is not a valid stdpath"
+msgstr "E6100: \"%s\" — некоректний stdpath"
+
+msgid "(Invalid)"
+msgstr "(Неможливо)"
+
+#, c-format
+msgid "E935: invalid submatch number: %d"
+msgstr "E935: неправильний номер групи співпадіння: %d"
+
+msgid "Can only call this function in an unmodified buffer"
+msgstr "Цю функцію можна викликати тільки у незміненому буфері"
+
+#, c-format
+msgid "E5060: Unknown flag: %s"
+msgstr "E5060: Невідомий прапорець: %s"
+
+msgid "E482: Can't open file with an empty name"
+msgstr "E482: Не вдалося відкрити файл з порожнім ім’ям"
+
+#, c-format
+msgid "E482: Can't open file %s for writing: %s"
+msgstr "E482: Не вдалося відкрити файл %s для запису: %s"
+
+#, c-format
+msgid "E80: Error when closing file %s: %s"
+msgstr "E80: Помилка при закритті файлу %s: %s"
+
+#, c-format
msgid "E5142: Failed to open file %s: %s"
msgstr "E5142: Не вдалося відкрити файл %s: %s"
@@ -1199,6 +1164,9 @@ msgstr "E745: Очікується Number чи String, трапився List"
msgid "E728: Expected a Number or a String, Dictionary found"
msgstr "E728: Очікується Number чи String, трапився Dictionary"
+msgid "E5299: Expected a Number or a String, Boolean found"
+msgstr "E5299: Очікується Number чи String, трапився Boolean"
+
msgid "E5300: Expected a Number or a String"
msgstr "E5300: Очікується Number чи String"
@@ -1235,12 +1203,151 @@ msgstr "E893: List вжито як Float"
msgid "E894: Using a Dictionary as a Float"
msgstr "E894: Dictionary вжито як Float"
+msgid "E362: Using a boolean value as a Float"
+msgstr "E362: Використано логічне значення як Float"
+
msgid "E907: Using a special value as a Float"
msgstr "E907: Використано спеціальне значення як Float"
msgid "E808: Number or Float required"
msgstr "E808: Треба вказати Number чи Float"
+#, c-format
+msgid "E122: Function %s already exists, add ! to replace it"
+msgstr "E122: Функція %s уже існує, ! щоб замінити"
+
+msgid "E717: Dictionary entry already exists"
+msgstr "E717: Запис у словнику вже існує"
+
+msgid "E718: Funcref required"
+msgstr "E718: Треба посилання на функцію"
+
+#, c-format
+msgid "E130: Unknown function: %s"
+msgstr "E130: Невідома функція: %s"
+
+#, c-format
+msgid "E125: Illegal argument: %s"
+msgstr "E125: Недозволений аргумент: %s"
+
+#, c-format
+msgid "E853: Duplicate argument name: %s"
+msgstr "E853: Назва аргументу повторюється: %s"
+
+#, c-format
+msgid "E740: Too many arguments for function %s"
+msgstr "E740: Забагато аргументів для функції %s"
+
+#, c-format
+msgid "E116: Invalid arguments for function %s"
+msgstr "E116: Неправильні аргументи функції %s"
+
+msgid "E132: Function call depth is higher than 'maxfuncdepth'"
+msgstr "E132: Глибина викликів функції перевищує 'maxfuncdepth'"
+
+#, c-format
+msgid "calling %s"
+msgstr "викликається %s"
+
+#, c-format
+msgid "%s aborted"
+msgstr "%s припинено"
+
+#, c-format
+msgid "%s returning #%<PRId64>"
+msgstr "%s повертає #%<PRId64>"
+
+#, c-format
+msgid "%s returning %s"
+msgstr "%s повертає %s"
+
+#, c-format
+msgid "continuing in %s"
+msgstr "продовження в %s"
+
+msgid "E699: Too many arguments"
+msgstr "E699: Забагато аргументів"
+
+#, c-format
+msgid "E117: Unknown function: %s"
+msgstr "E117: Невідома функція: %s"
+
+#, c-format
+msgid "E933: Function was deleted: %s"
+msgstr "E933: Функцію було видалено: %s"
+
+#, c-format
+msgid "E119: Not enough arguments for function: %s"
+msgstr "E119: Замало аргументів для функції %s"
+
+#, c-format
+msgid "E120: Using <SID> not in a script context: %s"
+msgstr "E120: <SID> використовується не у контексті скрипту: %s"
+
+#, c-format
+msgid "E725: Calling dict function without Dictionary: %s"
+msgstr "E725: Виклик dict-функції без словника: %s"
+
+msgid "E129: Function name required"
+msgstr "E129: Не вказано назву функції"
+
+#, c-format
+msgid "E128: Function name must start with a capital or \"s:\": %s"
+msgstr "E128: Назва функції має починатися з великої літери або \"s:\": %s"
+
+#, c-format
+msgid "E884: Function name cannot contain a colon: %s"
+msgstr "E884: Назва функції не може містити двокрапку: %s"
+
+#, c-format
+msgid "E123: Undefined function: %s"
+msgstr "E123: Невизначена функція: %s"
+
+#, c-format
+msgid "E124: Missing '(': %s"
+msgstr "E124: Бракує '(': %s"
+
+msgid "E862: Cannot use g: here"
+msgstr "E862: Тут не можна використати g:"
+
+#, c-format
+msgid "E932: Closure function should not be at top level: %s"
+msgstr "E932: Функція замикання не може бути на верхньому рівні: %s"
+
+msgid "E126: Missing :endfunction"
+msgstr "E126: Бракує :endfunction"
+
+#, c-format
+msgid "W22: Text found after :endfunction: %s"
+msgstr "W22: Трапився текст після :endfunction: %s"
+
+#, c-format
+msgid "E707: Function name conflicts with variable: %s"
+msgstr "E707: Назва функції співпадає зі змінною: %s"
+
+#, c-format
+msgid "E127: Cannot redefine function %s: It is in use"
+msgstr "E127: Не вдалося перевизначити функцію %s: вона використовується"
+
+#, c-format
+msgid "E746: Function name does not match script file name: %s"
+msgstr "E746: Назва функції не збігається з назвою файлу скрипту: %s"
+
+#, c-format
+msgid "E131: Cannot delete function %s: It is in use"
+msgstr "E131: Не вдалося знищити функцію %s: Вона використовується"
+
+#, c-format
+msgid "Cannot delete function %s: It is being used internally"
+msgstr "Не вдалося знищити функцію %s: Вона використовується"
+
+msgid "E133: :return not inside a function"
+msgstr "E133: :return поза межами функції"
+
+#, c-format
+msgid "E107: Missing parentheses: %s"
+msgstr "E107: Пропущено дужки: %s"
+
msgid "tcp address must be host:port"
msgstr "адреса tcp має бути вузол:порт"
@@ -1306,7 +1413,7 @@ msgstr "E140: Використайте ! для запису частини бу
#, c-format
msgid "Overwrite existing file \"%s\"?"
-msgstr "Переписати існуючий файл «%s»?"
+msgstr "Переписати наявний файл «%s»?"
#, c-format
msgid "Swap file \"%s\" exists, overwrite anyway?"
@@ -1352,8 +1459,9 @@ msgstr "E143: Автокоманди несподівано знищили но
msgid "E144: non-numeric argument to :z"
msgstr "E144: нечисловий аргумент для :z"
-msgid "E145: Shell commands not allowed in restricted mode"
-msgstr "E145: У обмеженому режимі не дозволені команди оболонки"
+msgid ""
+"E145: Shell commands and some functionality not allowed in restricted mode"
+msgstr "E145: У обмеженому режимі не дозволені команди оболонки і деяка функіональність"
msgid "E146: Regular expressions can't be delimited by letters"
msgstr "E146: Регулярні вирази не можна розділяти літерами"
@@ -1439,48 +1547,6 @@ msgstr "E154: Повторення мітки «%s» у файлі %s/%s"
msgid "E150: Not a directory: %s"
msgstr "E150: Не є каталогом: %s"
-#, c-format
-msgid "E160: Unknown sign command: %s"
-msgstr "E160: Невідома команда надпису: %s"
-
-msgid "E156: Missing sign name"
-msgstr "E156: Пропущено назву надпису"
-
-msgid "E612: Too many signs defined"
-msgstr "E612: Визначено забагато надписів"
-
-#, c-format
-msgid "E239: Invalid sign text: %s"
-msgstr "E239: Некоректний надпис: %s"
-
-#, c-format
-msgid "E155: Unknown sign: %s"
-msgstr "E155: Невідомий надпис: %s"
-
-msgid "E159: Missing sign number"
-msgstr "E159: Пропущено номер надпису"
-
-#, c-format
-msgid "E158: Invalid buffer name: %s"
-msgstr "E158: Некоректна назва буфера: %s"
-
-msgid "E934: Cannot jump to a buffer that does not have a name"
-msgstr "E934: Не можна перейти до буфера без назви"
-
-#, c-format
-msgid "E157: Invalid sign ID: %<PRId64>"
-msgstr "E157: Неправильний ID надпису: %<PRId64>"
-
-#, c-format
-msgid "E885: Not possible to change sign %s"
-msgstr "E885: Неможливо змінити знак %s"
-
-msgid " (not supported)"
-msgstr " (не підтримується)"
-
-msgid "[Deleted]"
-msgstr "[Знищено]"
-
msgid "No old files"
msgstr "Жодного старого файлу"
@@ -1545,6 +1611,9 @@ msgstr "E164: Це вже найперший файл"
msgid "E165: Cannot go beyond last file"
msgstr "E165: Це вже останній файл"
+msgid "E610: No argument to delete"
+msgstr "E610: Немає аргументів для знищення"
+
#, c-format
msgid "E666: compiler not supported: %s"
msgstr "E666: Компілятор не підтримується: %s"
@@ -1562,6 +1631,10 @@ msgid "not found in '%s': \"%s\""
msgstr "не знайдено в '%s': «%s»"
#, c-format
+msgid ":source error parsing command %s"
+msgstr ":source помилка розбору команди %s"
+
+#, c-format
msgid "Cannot source a directory: \"%s\""
msgstr "Не вдалося прочитати каталог: «%s»"
@@ -1607,6 +1680,9 @@ msgstr "Lua"
msgid "API client (channel id %<PRIu64>)"
msgstr "Клієнт API (канал «%<PRIu64>»)"
+msgid "anonymous :source"
+msgstr "анонімний :source"
+
msgid "W15: Warning: Wrong line separator, ^M may be missing"
msgstr "W15: Застереження: Неправильний роздільник рядків, можливо, бракує ^M"
@@ -1652,6 +1728,9 @@ msgstr "E464: Неоднозначний вжиток команди корис
msgid "E492: Not an editor command"
msgstr "E492: Це не команда редактора"
+msgid "E981: Command not allowed in restricted mode"
+msgstr "E981: Команду не дозволено у обмеженому режимі"
+
msgid "E493: Backwards range given"
msgstr "E493: Інтервал задано навиворіт"
@@ -1661,6 +1740,9 @@ msgstr "Інтервал задано навиворіт, щоб помінят
msgid "E494: Use w or w>>"
msgstr "E494: Спробуйте w або w>>"
+msgid "E943: Command table needs to be updated, run 'make'"
+msgstr "E943: Потрібно поновити таблицю команд, запустіть 'make'"
+
msgid "E319: The command is not available in this version"
msgstr "E319: Вибачте, цієї команди немає у цій версії"
@@ -1678,15 +1760,16 @@ msgstr "E173: Залишилося відредагувати ще один фа
msgid "E173: %<PRId64> more files to edit"
msgstr "E173: Залишилося %<PRId64> не редагованих файлів"
-msgid "E174: Command already exists: add ! to replace it"
-msgstr "E174: Команда вже існує, ! щоб замінити її"
+#, c-format
+msgid "E174: Command already exists: add ! to replace it: %s"
+msgstr "E174: Команда вже існує, ! щоб замінити її: %s"
msgid ""
"\n"
-" Name Args Address Complete Definition"
+" Name Args Address Complete Definition"
msgstr ""
"\n"
-" Назва Арг. Адреса Доповнення Визначення"
+" Назва Арг. Адреса Доповнення Визначення"
msgid "No user-defined commands found"
msgstr "Не знайдено команд користувача"
@@ -1805,6 +1888,9 @@ msgstr "E498: Немає назви файлу :source для заміни «<sf
msgid "E842: no line number to use for \"<slnum>\""
msgstr "E842: немає номера рядка, щоб використати з «<sfile>»"
+msgid "E961: no line number to use for \"<sflnum>\""
+msgstr "E961: немає номера рядка, щоб використати з «<sflnum>»"
+
#, c-format
msgid "E499: Empty file name for '%' or '#', only works with \":p:h\""
msgstr "E499: Назва файлу для '%' чи '#' порожня, працює лише з «:p:h»"
@@ -1962,9 +2048,6 @@ msgstr " тип файлу\n"
msgid "'history' option is zero"
msgstr "Опція 'history' порожня"
-msgid "E198: cmd_pchar beyond the command length"
-msgstr "E198: cmd_pchar поза межами команди"
-
msgid "E199: Active window or buffer deleted"
msgstr "E199: Активне вікно або буфер було знищено"
@@ -2007,9 +2090,6 @@ msgstr "каталог"
msgid "is not a file"
msgstr "не файл"
-msgid "[New File]"
-msgstr "[Новий файл]"
-
msgid "[New DIRECTORY]"
msgstr "[Новий каталог]"
@@ -2043,6 +2123,9 @@ msgstr "[спец. символьний]"
msgid "[CR missing]"
msgstr "[Бракує CR]"
+msgid "[long lines split]"
+msgstr "[довгі рядки розбито]"
+
msgid "[NOT converted]"
msgstr "[НЕ конвертовано]"
@@ -2069,6 +2152,12 @@ msgstr "Конвертація з 'charconvert' не вдалася"
msgid "can't read output of 'charconvert'"
msgstr "не вдалося прочитати вивід 'charconvert'"
+msgid "[New File]"
+msgstr "[Новий файл]"
+
+msgid "[New]"
+msgstr "[Новий]"
+
msgid "E676: No matching autocommands for acwrite buffer"
msgstr "E676: Немає відповідних автокоманд"
@@ -2087,15 +2176,6 @@ msgstr "лише для читання (! щоб не зважати)"
msgid "E506: Can't write to backup file (add ! to override)"
msgstr "E506: Не вдалося записати резервний файл (! щоб не зважати)"
-#, c-format
-msgid "E507: Close error for backup file (add ! to override): %s"
-msgstr "E507: Помилка закриття резервного файлу (! щоб не зважати): %s"
-
-msgid "E508: Can't read file for backup (add ! to override)"
-msgstr ""
-"E508: Не вдалося прочитати файл щоб створити резервну копію (! щоб не "
-"зважати)"
-
msgid "E509: Cannot create backup file (add ! to override)"
msgstr "E509: Не вдалося створити резервну копію (! щоб не зважати)"
@@ -2116,10 +2196,6 @@ msgid "E212: Can't open file for writing: %s"
msgstr "E212: Не вдалося відкрити файл для запису: %s"
#, c-format
-msgid "E667: Fsync failed: %s"
-msgstr "E667: Не вдалося виконати fsync: %s"
-
-#, c-format
msgid "E512: Close failed: %s"
msgstr "E512: Не вдалося закрити: %s"
@@ -2142,9 +2218,6 @@ msgstr " у рядку %<PRId64>;"
msgid "[Device]"
msgstr "[Пристрій]"
-msgid "[New]"
-msgstr "[Новий]"
-
msgid " [a]"
msgstr "[д]"
@@ -2461,6 +2534,18 @@ msgid "E475: Invalid argument: %s"
msgstr "E475: Некоректний аргумент: %s"
#, c-format
+msgid "E475: Invalid value for argument %s"
+msgstr "E475: Некоректне значення аргументу %s"
+
+#, c-format
+msgid "E475: Invalid value for argument %s: %s"
+msgstr "E475: Некоректне значення аргументу %s: %s"
+
+#, c-format
+msgid "E983: Duplicate argument: %s"
+msgstr "E983: Аргумент повторюється: %s"
+
+#, c-format
msgid "E15: Invalid expression: %s"
msgstr "E15: Неправильний вираз: %s"
@@ -2512,6 +2597,10 @@ msgid "E364: Library call failed for \"%s()\""
msgstr "E364: Бібліотечний виклик до «%s()» не вдався"
#, c-format
+msgid "E667: Fsync failed: %s"
+msgstr "E667: Не вдалося виконати fsync: %s"
+
+#, c-format
msgid "E739: Cannot create directory %s: %s"
msgstr "E739: Не вдалося створити каталог %s: %s"
@@ -2589,12 +2678,6 @@ msgstr "E484: Не вдалося відкрити файл %s: %s"
msgid "E485: Can't read file %s"
msgstr "E485: Не вдалося прочитати файл %s"
-msgid "E37: No write since last change (add ! to override)"
-msgstr "E37: Зміни не було записано (! щоб не зважати)"
-
-msgid "E37: No write since last change"
-msgstr "E37: Не записано після останніх змін"
-
msgid "E38: Null argument"
msgstr "E38: Відсутній аргумент"
@@ -2636,6 +2719,28 @@ msgstr "E44: Зіпсована програма регулярних вираз
msgid "E45: 'readonly' option is set (add ! to override)"
msgstr "E45: Встановлено опцію 'readonly' (! щоб не зважати)"
+#, c-format
+msgid "E46: Cannot change read-only variable \"%.*s\""
+msgstr "E46: Змінна тільки для читання: «%.*s»"
+
+msgid "E715: Dictionary required"
+msgstr "E715: Потрібен словник"
+
+#, c-format
+msgid "E118: Too many arguments for function: %s"
+msgstr "E118: Забагато аргументів для функції: %s"
+
+#, c-format
+msgid "E716: Key not present in Dictionary: %s"
+msgstr "E716: Немає такого ключа у словнику: %s"
+
+msgid "E714: List required"
+msgstr "E714: Потрібен список"
+
+#, c-format
+msgid "E712: Argument of %s must be a List or Dictionary"
+msgstr "E712: Аргумент у %s має бути списком чи словником"
+
msgid "E47: Error while reading errorfile"
msgstr "E47: Помилка читання файлу помилок"
@@ -2756,12 +2861,29 @@ msgstr "E5521: Заміна клавіш <Cmd> має закінчуватися
msgid "E5522: <Cmd> mapping must not include %s key"
msgstr "E5522: Заміна клавіш <Cmd> не може містити ключ %s"
+#, c-format
+msgid "E5555: API call: %s"
+msgstr "E5555: виклик API: %s"
+
+#, c-format
+msgid "E5560: %s must not be called in a lua loop callback"
+msgstr "E5560: %s має бути викликане у контексті циклу lua"
+
+msgid "E5601: Cannot close window, only floating window would remain"
+msgstr "E5601: Не вдалося закрити вікно, залишилося б тільки плавуче вікно"
+
+msgid "E5602: Cannot exchange or rotate float"
+msgstr "E5602: Не можна обміняти чи покрутити плавуче вікно"
+
msgid "search hit TOP, continuing at BOTTOM"
msgstr "Пошук дійшов до ПОЧАТКУ, продовжується з КІНЦЯ"
msgid "search hit BOTTOM, continuing at TOP"
msgstr "Пошук дійшов до КІНЦЯ, продовжується з ПОЧАТКУ"
+msgid " line "
+msgstr " рядок "
+
msgid "E550: Missing colon"
msgstr "E550: Пропущено двокрапку"
@@ -3032,6 +3154,14 @@ msgid "E5102: Lua failed to grow stack to %i"
msgstr "E5102: Lua не вдалося збільшити стек до %i"
#, c-format
+msgid "Error executing vim.schedule lua callback: %.*s"
+msgstr "Помилка виконання обробника lua vim.schedule: %.*s"
+
+#, c-format
+msgid "E5106: Error while creating shared module: %.*s"
+msgstr "E5106: Помилка створення розділюваного модуля: %.*s"
+
+#, c-format
msgid "E5106: Error while creating vim module: %.*s"
msgstr "E5106: Помилка створення модуля vim: %.*s"
@@ -3043,14 +3173,6 @@ msgid "E5117: Error while updating package paths: %.*s"
msgstr "E5117: Помилка оновлення шляхів пакунку: %.*s"
#, c-format
-msgid "E5104: Error while creating lua chunk: %.*s"
-msgstr "E5104: Помилка створення шматку lua: %.*s"
-
-#, c-format
-msgid "E5105: Error while calling lua chunk: %.*s"
-msgstr "E5105: Помилка виклику шматку lua: %.*s"
-
-#, c-format
msgid "E5114: Error while converting print argument #%i: %.*s"
msgstr "E5114: Не вдалося перетворити аргумент #%i друку: %.*s"
@@ -3063,27 +3185,31 @@ msgid "E5116: Error while calling debug string: %.*s"
msgstr "E5116: Помилка виклику налагодження: %.*s"
#, c-format
-msgid "E5107: Error while creating lua chunk for luaeval(): %.*s"
-msgstr "E5107: Помилка створення шматку lua для luaeval(): %.*s"
+msgid "E5107: Error loading lua %.*s"
+msgstr "E5107: Помилка завантаження lua %.*s"
#, c-format
-msgid "E5108: Error while calling lua chunk for luaeval(): %.*s"
-msgstr "E5108: Помилка виклику шматку lua для luaeval(): %.*s"
+msgid "E5108: Error executing lua %.*s"
+msgstr "E5108: Помилка виконання lua %.*s"
+
+#, c-format
+msgid "Error executing lua callback: %.*s"
+msgstr "Помилка виконання обробника lua: %.*s"
msgid "cannot save undo information"
msgstr "Не вдалося записати інформацію повернення"
#, c-format
-msgid "E5109: Error while creating lua chunk: %.*s"
-msgstr "E5109: Помилка створення шматку lua: %.*s"
+msgid "E5109: Error loading lua: %.*s"
+msgstr "E5109: Помилка завантаження lua: %.*s"
#, c-format
-msgid "E5110: Error while creating lua function: %.*s"
-msgstr "E5110: Помилка створення функції lua: %.*s"
+msgid "E5110: Error executing lua: %.*s"
+msgstr "E5110: Помилка виконання lua: %.*s"
#, c-format
-msgid "E5111: Error while calling lua function: %.*s"
-msgstr "E5111: Помилка виклику функції lua: %.*s"
+msgid "E5111: Error calling lua: %.*s"
+msgstr "E5111: Помилка виклику lua: %.*s"
#, c-format
msgid "E5112: Error while creating lua chunk: %.*s"
@@ -3093,6 +3219,10 @@ msgstr "E5112: Помилка створення шматку lua: %.*s"
msgid "E5113: Error while calling lua chunk: %.*s"
msgstr "E5113: Помилка виклику шматку lua: %.*s"
+#, c-format
+msgid "Error executing vim.log_keystroke lua callback: %.*s"
+msgstr "Помилка виконання обробника lua vim.log_keystroke: %.*s"
+
msgid "Argument missing after"
msgstr "Пропущено аргумент після"
@@ -3123,6 +3253,9 @@ msgstr "Не вдалося відкрити для читання: \"%s\": %s\n
msgid "Cannot open for script output: \""
msgstr "Не вдалося відкрити як вихідний файл: \""
+msgid "--embed conflicts with -es/-Es"
+msgstr "--embed конфліктує з -es/-Es"
+
msgid "pre-vimrc command line"
msgstr "команди перед vimrc"
@@ -3567,8 +3700,8 @@ msgid "E315: ml_get: invalid lnum: %<PRId64>"
msgstr "E315: ml_get: неправильний lnum: %<PRId64>"
#, c-format
-msgid "E316: ml_get: cannot find line %<PRId64>"
-msgstr "E316: ml_get: не знайшов рядок %<PRId64>"
+msgid "E316: ml_get: cannot find line %<PRId64> in buffer %d %s"
+msgstr "E316: ml_get: не знайшов рядок %<PRId64> у буфері %d %s"
msgid "E317: pointer block id wrong 3"
msgstr "E317: Вказівник блоку помилковий 3"
@@ -3666,6 +3799,9 @@ msgstr ""
"»,\n"
" щоб позбутися цього повідомлення.\n"
+msgid "Found a swap file that is not useful, deleting it"
+msgstr "Знайдено файл обміну, яким не можна скористатися, знищення"
+
msgid "Swap file \""
msgstr "Файл обміну «"
@@ -3750,6 +3886,10 @@ msgstr ""
"\n"
"--- Меню ---"
+#, c-format
+msgid "E335: Menu not defined for %s mode"
+msgstr "E335: Для режиму %s меню не визначено"
+
msgid "E333: Menu path must lead to a menu item"
msgstr "E333: Шлях повинен вести до елемента меню"
@@ -3758,10 +3898,6 @@ msgid "E334: Menu not found: %s"
msgstr "E334: Меню не знайдено: %s"
#, c-format
-msgid "E335: Menu not defined for %s mode"
-msgstr "E335: Для режиму %s меню не визначено"
-
-#, c-format
msgid "Error detected while processing %s:"
msgstr "Виявлено помилку під час виконання %s:"
@@ -3821,9 +3957,6 @@ msgstr ""
"&D:Жодного\n"
"&C:Скасувати"
-msgid "W10: Warning: Changing a readonly file"
-msgstr "W10: Застереження: Змінюється файл призначений лише для читання"
-
msgid "Type number and <Enter> or click with mouse (empty cancels): "
msgstr "Наберіть число й <Enter> чи клацніть мишкою (порожнє скасовує): "
@@ -3856,9 +3989,6 @@ msgstr "E349: Немає ідентифікатора над курсором"
msgid "E774: 'operatorfunc' is empty"
msgstr "E774: 'operatorfunc' порожня"
-msgid "Warning: terminal cannot highlight"
-msgstr "Застереження: Термінал не підтримує кольори"
-
msgid "E348: No string under cursor"
msgstr "E348: Немає рядка на курсорі"
@@ -3878,6 +4008,10 @@ msgid "Type :qa! and press <Enter> to abandon all changes and exit Nvim"
msgstr ""
"Введіть :qa! і натисність <Enter> щоб відкинути всі зміни і вийти Nvim"
+msgid "Type :qa and press <Enter> to exit Nvim"
+msgstr ""
+"Введіть :qa і натисність <Enter> щоб вийти з Nvim"
+
#, c-format
msgid "1 line %sed 1 time"
msgstr "Один рядок %s-но"
@@ -3998,6 +4132,9 @@ msgstr "E518: Невідома опція"
msgid "E520: Not allowed in a modeline"
msgstr "E520: Не дозволено у modeline"
+msgid "E992: Not allowed in a modeline when 'modelineexpr' is off"
+msgstr "E992: Не дозволено у modeline, коли вимкнено 'modelineexpr'"
+
msgid "E846: Key code not set"
msgstr "E846: Код ключа не встановлено"
@@ -4144,24 +4281,16 @@ msgstr ""
msgid "E5677: Error writing input to shell-command: %s"
msgstr "E5677: Не вдалося записати на вхід команди оболонки: %s"
-msgid ""
-"\n"
-"Could not get security context for "
-msgstr ""
-"\n"
-"Не вдалося отримати контекст безпеки для "
-
-msgid ""
-"\n"
-"Could not set security context for "
-msgstr ""
-"\n"
-"Не вдалося встановити контекст безпеки для "
-
#, c-format
msgid "E447: Can't find file \"%s\" in path"
msgstr "E447: Файл «%s» не знайдено у шляху пошуку"
+msgid "E553: No more items"
+msgstr "E553: Немає більше елементів"
+
+msgid "E926: Current location list was changed"
+msgstr "E926: Цей список місць було змінено"
+
#, c-format
msgid "E372: Too many %%%c in format string"
msgstr "E372: Забагато %%%c у рядку формату"
@@ -4191,18 +4320,12 @@ msgstr "E378: 'errorformat' не містить зразок"
msgid "E379: Missing or empty directory name"
msgstr "E379: Пропущена чи порожня назва каталогу"
-msgid "E553: No more items"
-msgstr "E553: Немає більше елементів"
-
msgid "E924: Current window was closed"
msgstr "E924: Активне вікно було закрито"
msgid "E925: Current quickfix was changed"
msgstr "E925: Цей quickfix було змінено"
-msgid "E926: Current location list was changed"
-msgstr "E926: Цей список місць було змінено"
-
#, c-format
msgid "(%d of %d)%s%s: "
msgstr "(%d з %d)%s%s: "
@@ -4223,9 +4346,6 @@ msgstr "E381: Вершина стеку виправлень"
msgid "No entries"
msgstr "Нічого"
-msgid "E382: Cannot write, 'buftype' option is set"
-msgstr "E382: Не можу записати, вказана опція 'buftype'"
-
msgid "E683: File name missing or invalid pattern"
msgstr "E683: Пропущено назву файлу чи некоректний шаблон"
@@ -4279,6 +4399,12 @@ msgstr "E69: Пропущено ] після %s%%["
msgid "E70: Empty %s%%[]"
msgstr "E70: %s%%[] порожній"
+msgid "E956: Cannot use pattern recursively"
+msgstr "E956: Не можна рекурсивно використати шаблон"
+
+msgid "E65: Illegal back reference"
+msgstr "E65: Некоректне зворотнє посилання"
+
msgid "E339: Pattern too long"
msgstr "E339: Зразок занадто довгий"
@@ -4315,9 +4441,6 @@ msgstr "E63: Некоректно вжито \\_"
msgid "E64: %s%c follows nothing"
msgstr "E64: Після %s%c нічого немає"
-msgid "E65: Illegal back reference"
-msgstr "E65: Некоректне зворотнє посилання"
-
msgid "E68: Invalid character after \\z"
msgstr "E68: Неправильний символ після \\z"
@@ -4658,6 +4781,63 @@ msgstr ""
"Помилка при читанні файлу ShaDa: список буферів у позиції %<PRIu64> містить "
"поле, яке не має назву файлу"
+msgid "[Deleted]"
+msgstr "[Знищено]"
+
+msgid ""
+"\n"
+"--- Signs ---"
+msgstr ""
+"\n"
+"--- Позначки ---"
+
+#, c-format
+msgid "Signs for %s:"
+msgstr "Позначки для %s:"
+
+#, c-format
+msgid " group=%s"
+msgstr " група=%s"
+
+#, c-format
+msgid " line=%ld id=%d%s name=%s priority=%d"
+msgstr " рядок=%ld id=%d%s назва=%s пріоритет=%d"
+
+msgid "E612: Too many signs defined"
+msgstr "E612: Визначено забагато надписів"
+
+#, c-format
+msgid "E239: Invalid sign text: %s"
+msgstr "E239: Некоректний надпис: %s"
+
+#, c-format
+msgid "E155: Unknown sign: %s"
+msgstr "E155: Невідомий надпис: %s"
+
+#, c-format
+msgid "E885: Not possible to change sign %s"
+msgstr "E885: Неможливо змінити знак %s"
+
+msgid "E159: Missing sign number"
+msgstr "E159: Пропущено номер надпису"
+
+#, c-format
+msgid "E157: Invalid sign ID: %<PRId64>"
+msgstr "E157: Неправильний ID надпису: %<PRId64>"
+
+msgid "E934: Cannot jump to a buffer that does not have a name"
+msgstr "E934: Не можна перейти до буфера без назви"
+
+#, c-format
+msgid "E160: Unknown sign command: %s"
+msgstr "E160: Невідома команда надпису: %s"
+
+msgid "E156: Missing sign name"
+msgstr "E156: Пропущено назву надпису"
+
+msgid " (not supported)"
+msgstr " (не підтримується)"
+
msgid "E759: Format error in spell file"
msgstr "E759: Помилка формату у файлі орфографії"
@@ -4709,12 +4889,6 @@ msgstr "Зайвий текст у %s у рядку %d: %s"
msgid "Affix name too long in %s line %d: %s"
msgstr "Назва афіксу завелика у %s у рядку %d: %s"
-msgid "E761: Format error in affix file FOL, LOW or UPP"
-msgstr "E761: Помилка формату у файлі афіксів FOL, LOW чи UPP"
-
-msgid "E762: Character in FOL, LOW or UPP is out of range"
-msgstr "E762: Символ у FOL, LOW чи UPP поза межами"
-
msgid "Compressing word tree..."
msgstr "Стискується дерево слів..."
@@ -4855,10 +5029,6 @@ msgstr "Повторення символу у MAP у %s у рядку %d"
msgid "Unrecognized or duplicate item in %s line %d: %s"
msgstr "Нерозпізнаний чи повторний елемент у %s у рядку %d: %s"
-#, c-format
-msgid "Missing FOL/LOW/UPP line in %s"
-msgstr "Пропущено рядок FOL/LOW/UPP у %s"
-
msgid "COMPOUNDSYLMAX used without SYLLABLE"
msgstr "Вжито COMPOUNDSYLMAX без SYLLABLE"
@@ -4960,8 +5130,8 @@ msgid "Ignored %d words with non-ASCII characters"
msgstr "Проігноровано %d слів із не-ASCII символами"
#, c-format
-msgid "Compressed %d of %d nodes; %d (%d%%) remaining"
-msgstr "Стиснено %d з %d вузлів; залишилося %d (%d%%)"
+msgid "Compressed %s of %ld nodes; %ld (%ld%%) remaining"
+msgstr "Стиснено %s з %ld вузлів; залишилося %ld (%ld%%)"
msgid "Reading back spell file..."
msgstr "Перечитується файл орфографії..."
@@ -5033,25 +5203,34 @@ msgstr "E807: Очікується аргумент Float для printf()"
msgid "E767: Too many arguments to printf()"
msgstr "E767: Забагато аргументів для printf()"
+#, c-format
+msgid "E390: Illegal argument: %s"
+msgstr "E390: Неправильний аргумент: %s"
+
msgid "No Syntax items defined for this buffer"
msgstr "Для буфера не визначено елементів синтаксису"
+msgid "'redrawtime' exceeded, syntax highlighting disabled"
+msgstr "'redrawtime' перевищено, підсвічування синтаксису вимкнено"
+
msgid "syntax conceal on"
msgstr "синтаксичне приховування увімк"
msgid "syntax conceal off"
msgstr "синтаксичне приховування вимк"
-#, c-format
-msgid "E390: Illegal argument: %s"
-msgstr "E390: Неправильний аргумент: %s"
-
msgid "syntax case ignore"
msgstr "синтаксис ігнорувати регістр"
msgid "syntax case match"
msgstr "синтаксис дотримуватися регістру"
+msgid "syntax foldlevel start"
+msgstr "рівень згортки синтаксису початок"
+
+msgid "syntax foldlevel minimum"
+msgstr "рівень згортки синтаксису мінімум"
+
msgid "syntax spell toplevel"
msgstr "синтаксис перевіряти всюди"
@@ -5064,6 +5243,9 @@ msgstr "синтаксис початково"
msgid "syntax iskeyword "
msgstr "синтаксис iskeyword "
+msgid "syntax iskeyword not set"
+msgstr "не встановлено синтаксис iskeyword"
+
#, c-format
msgid "E391: No such syntax cluster: %s"
msgstr "E391: Немає такого синтаксичного кластера: %s"
@@ -5124,7 +5306,7 @@ msgid "E844: invalid cchar value"
msgstr "E844: Некоректне значення cchar"
msgid "E393: group[t]here not accepted here"
-msgstr "E393: group[t]hete тут неприйнятний"
+msgstr "E393: group[t]here тут неприйнятний"
#, c-format
msgid "E394: Didn't find region item for %s"
@@ -5266,6 +5448,12 @@ msgstr "E555: Кінець стеку міток"
msgid "E556: at top of tag stack"
msgstr "E556: Вершина стеку міток"
+msgid "E986: cannot modify the tag stack within tagfunc"
+msgstr "E986: Не можна змінювати стек міток у tagfunc"
+
+msgid "E987: invalid return value from tagfunc"
+msgstr "E987: Некоректне повернене значення з tagfunc"
+
msgid "E425: Cannot go before first matching tag"
msgstr "E425: Це вже найперша відповідна мітка"
@@ -5273,12 +5461,6 @@ msgstr "E425: Це вже найперша відповідна мітка"
msgid "E426: tag not found: %s"
msgstr "E426: Мітку не знайдено: %s"
-msgid " # pri kind tag"
-msgstr " # прі тип мітка"
-
-msgid "file\n"
-msgstr "файл\n"
-
msgid "E427: There is only one matching tag"
msgstr "E427: Лише одна відповідна мітка"
@@ -5303,6 +5485,12 @@ msgstr " Використано мітку, не розрізняючи вел
msgid "E429: File \"%s\" does not exist"
msgstr "E429: Файл «%s» не існує"
+msgid " # pri kind tag"
+msgstr " # прі тип мітка"
+
+msgid "file\n"
+msgstr "файл\n"
+
msgid ""
"\n"
" # TO tag FROM line in file/text"
@@ -5314,9 +5502,6 @@ msgstr ""
msgid "Searching tags file %s"
msgstr "Шукається у файлі теґів %s"
-msgid "Ignoring long line in tags file"
-msgstr "Ігнорується довгий рядок у файлі з позначками"
-
#, c-format
msgid "E431: Format error in tags file \"%s\""
msgstr "E431: Помилка формату у файлі теґів «%s»"
@@ -5453,10 +5638,6 @@ msgstr "Немає нічого скасовувати"
msgid "number changes when saved"
msgstr "номер зміни час збережено"
-#, c-format
-msgid "%<PRId64> seconds ago"
-msgstr "%<PRId64> секунд тому"
-
msgid "E790: undojoin is not allowed after undo"
msgstr "E790: Не можна виконати undojoin після undo"
@@ -5468,22 +5649,22 @@ msgstr "E440: Відсутній рядок скасування"
msgid ""
"\n"
-"Compiled "
+"\n"
+"Features: "
msgstr ""
"\n"
-"Скомпілював "
-
-msgid "by "
-msgstr " "
+"\n"
+"Характеристики: "
msgid ""
"\n"
-"\n"
-"Features: "
+"Compiled "
msgstr ""
"\n"
-"\n"
-"Характеристики: "
+"Скомпілював "
+
+msgid "by "
+msgstr " "
msgid " system vimrc file: \""
msgstr " системний vimrc: \""
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index c712762bdf..d2d20852aa 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -226,6 +226,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
pum_above = false;
// Leave two lines of context if possible
+ validate_cheight();
if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) {
context_lines = 3;
} else {
@@ -300,49 +301,49 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
if (pum_width < p_pw) {
pum_width = (int)p_pw;
}
- }
- } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl)
- || (pum_rl && (cursor_col < Columns - p_pw
- || cursor_col < Columns - max_width))) {
- // align pum edge with "cursor_col"
- if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) {
- pum_col = cursor_col + max_width + pum_scrollbar + 1;
- if (pum_col >= Columns) {
- pum_col = Columns - 1;
- }
- } else if (!pum_rl) {
- if (curwin->w_wincol > Columns - max_width - pum_scrollbar
- && max_width <= p_pw) {
- // use full width to end of the screen
- pum_col = cursor_col - max_width - pum_scrollbar;
- if (pum_col < 0) {
- pum_col = 0;
+ } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl)
+ || (pum_rl && (cursor_col < Columns - p_pw
+ || cursor_col < Columns - max_width))) {
+ // align pum edge with "cursor_col"
+ if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) {
+ pum_col = cursor_col + max_width + pum_scrollbar + 1;
+ if (pum_col >= Columns) {
+ pum_col = Columns - 1;
+ }
+ } else if (!pum_rl) {
+ if (curwin->w_wincol > Columns - max_width - pum_scrollbar
+ && max_width <= p_pw) {
+ // use full width to end of the screen
+ pum_col = Columns - max_width - pum_scrollbar;
+ if (pum_col < 0) {
+ pum_col = 0;
+ }
}
}
- }
-
- if (pum_rl) {
- pum_width = pum_col - pum_scrollbar + 1;
- } else {
- pum_width = Columns - pum_col - pum_scrollbar;
- }
- if (pum_width < p_pw) {
- pum_width = (int)p_pw;
if (pum_rl) {
- if (pum_width > pum_col) {
- pum_width = pum_col;
- }
+ pum_width = pum_col - pum_scrollbar + 1;
} else {
- if (pum_width >= Columns - pum_col) {
- pum_width = Columns - pum_col - 1;
- }
+ pum_width = Columns - pum_col - pum_scrollbar;
}
- } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
- && pum_width > p_pw) {
- pum_width = max_width + pum_kind_width + pum_extra_width + 1;
+
if (pum_width < p_pw) {
pum_width = (int)p_pw;
+ if (pum_rl) {
+ if (pum_width > pum_col) {
+ pum_width = pum_col;
+ }
+ } else {
+ if (pum_width >= Columns - pum_col) {
+ pum_width = Columns - pum_col - 1;
+ }
+ }
+ } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
+ && pum_width > p_pw) {
+ pum_width = max_width + pum_kind_width + pum_extra_width + 1;
+ if (pum_width < p_pw) {
+ pum_width = (int)p_pw;
+ }
}
}
} else if (Columns < def_width) {
@@ -918,11 +919,11 @@ void pum_set_event_info(dict_T *dict)
r = (double)pum_row;
c = (double)pum_col;
}
- tv_dict_add_float(dict, S_LEN("height"), h);
- tv_dict_add_float(dict, S_LEN("width"), w);
- tv_dict_add_float(dict, S_LEN("row"), r);
- tv_dict_add_float(dict, S_LEN("col"), c);
- tv_dict_add_nr(dict, S_LEN("size"), pum_size);
- tv_dict_add_bool(dict, S_LEN("scrollbar"),
- pum_scrollbar ? kBoolVarTrue : kBoolVarFalse);
+ (void)tv_dict_add_float(dict, S_LEN("height"), h);
+ (void)tv_dict_add_float(dict, S_LEN("width"), w);
+ (void)tv_dict_add_float(dict, S_LEN("row"), r);
+ (void)tv_dict_add_float(dict, S_LEN("col"), c);
+ (void)tv_dict_add_nr(dict, S_LEN("size"), pum_size);
+ (void)tv_dict_add_bool(dict, S_LEN("scrollbar"),
+ pum_scrollbar ? kBoolVarTrue : kBoolVarFalse);
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index ddce1e922d..1cd7879dc0 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -239,7 +239,10 @@ static char_u *e_no_more_items = (char_u *)N_("E553: No more items");
static char_u *qf_last_bufname = NULL;
static bufref_T qf_last_bufref = { NULL, 0, 0 };
-static char *e_loc_list_changed = N_("E926: Current location list was changed");
+static char *e_current_quickfix_list_was_changed =
+ N_("E925: Current quickfix list was changed");
+static char *e_current_location_list_was_changed =
+ N_("E926: Current location list was changed");
// Counter to prevent autocmds from freeing up location lists when they are
// still being used.
@@ -808,7 +811,7 @@ retry:
}
break;
}
- if (STRLEN(IObuff) < IOSIZE - 1 || IObuff[IOSIZE - 1] == '\n') {
+ if (STRLEN(IObuff) < IOSIZE - 1 || IObuff[IOSIZE - 2] == '\n') {
break;
}
}
@@ -901,6 +904,7 @@ static bool qf_list_has_valid_entries(qf_list_T *qfl)
/// Return a pointer to a list in the specified quickfix stack
static qf_list_T * qf_get_list(qf_info_T *qi, int idx)
+ FUNC_ATTR_NONNULL_ALL
{
return &qi->qf_lists[idx];
}
@@ -1088,6 +1092,7 @@ qf_init_ext(
)
FUNC_ATTR_NONNULL_ARG(1)
{
+ qf_list_T *qfl;
qfstate_T state = { 0 };
qffields_T fields = { 0 };
qfline_T *old_last = NULL;
@@ -1111,15 +1116,16 @@ qf_init_ext(
// make place for a new list
qf_new_list(qi, qf_title);
qf_idx = qi->qf_curlist;
+ qfl = qf_get_list(qi, qf_idx);
} else {
// Adding to existing list, use last entry.
adding = true;
- if (!qf_list_empty(qf_get_list(qi, qf_idx) )) {
- old_last = qi->qf_lists[qf_idx].qf_last;
+ qfl = qf_get_list(qi, qf_idx);
+ if (!qf_list_empty(qfl)) {
+ old_last = qfl->qf_last;
}
}
- qf_list_T *qfl = qf_get_list(qi, qf_idx);
// Use the local value of 'errorformat' if it's set.
if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) {
@@ -1230,6 +1236,7 @@ static char_u * qf_cmdtitle(char_u *cmd)
/// Return a pointer to the current list in the specified quickfix stack
static qf_list_T * qf_get_curlist(qf_info_T *qi)
+ FUNC_ATTR_NONNULL_ALL
{
return qf_get_list(qi, qi->qf_curlist);
}
@@ -1660,8 +1667,8 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
}
if (!qfprev->qf_col) {
qfprev->qf_col = fields->col;
+ qfprev->qf_viscol = fields->use_viscol;
}
- qfprev->qf_viscol = fields->use_viscol;
if (!qfprev->qf_fnum) {
qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory,
*fields->namebuf || qfl->qf_directory
@@ -1770,7 +1777,7 @@ static void decr_quickfix_busy(void)
void check_quickfix_busy(void)
{
if (quickfix_busy != 0) {
- EMSGN("quickfix_busy not zero on exit: %ld", (long)quickfix_busy);
+ EMSGN("quickfix_busy not zero on exit: %" PRId64, (int64_t)quickfix_busy);
# ifdef ABORT_ON_INTERNAL_ERROR
abort();
# endif
@@ -2348,25 +2355,27 @@ static qfline_T *get_prev_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr,
/// dir == FORWARD or FORWARD_FILE: next valid entry
/// dir == BACKWARD or BACKWARD_FILE: previous valid entry
static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr,
- qfline_T *qf_ptr, int *qf_index, int dir)
+ int dir, int *new_qfidx)
{
+ qfline_T *qf_ptr = qfl->qf_ptr;
+ int qf_idx = qfl->qf_index;
qfline_T *prev_qf_ptr;
int prev_index;
char_u *err = e_no_more_items;
while (errornr--) {
prev_qf_ptr = qf_ptr;
- prev_index = *qf_index;
+ prev_index = qf_idx;
if (dir == FORWARD || dir == FORWARD_FILE) {
- qf_ptr = get_next_valid_entry(qfl, qf_ptr, qf_index, dir);
+ qf_ptr = get_next_valid_entry(qfl, qf_ptr, &qf_idx, dir);
} else {
- qf_ptr = get_prev_valid_entry(qfl, qf_ptr, qf_index, dir);
+ qf_ptr = get_prev_valid_entry(qfl, qf_ptr, &qf_idx, dir);
}
if (qf_ptr == NULL) {
qf_ptr = prev_qf_ptr;
- *qf_index = prev_index;
+ qf_idx = prev_index;
if (err != NULL) {
EMSG(_(err));
return NULL;
@@ -2377,14 +2386,16 @@ static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr,
err = NULL;
}
+ *new_qfidx = qf_idx;
return qf_ptr;
}
-/// Get n'th (errornr) quickfix entry
-static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr,
- int *cur_qfidx)
+/// Get n'th (errornr) quickfix entry from the current entry in the quickfix
+/// list 'qfl'. Returns a pointer to the new entry and the index in 'new_qfidx'
+static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, int *new_qfidx)
{
- int qf_idx = *cur_qfidx;
+ qfline_T *qf_ptr = qfl->qf_ptr;
+ int qf_idx = qfl->qf_index;;
// New error number is less than the current error number
while (errornr < qf_idx && qf_idx > 1 && qf_ptr->qf_prev != NULL) {
@@ -2400,10 +2411,31 @@ static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr,
qf_ptr = qf_ptr->qf_next;
}
- *cur_qfidx = qf_idx;
+ *new_qfidx = qf_idx;
return qf_ptr;
}
+/// Get a entry specied by 'errornr' and 'dir' from the current
+/// quickfix/location list. 'errornr' specifies the index of the entry and 'dir'
+/// specifies the direction (FORWARD/BACKWARD/FORWARD_FILE/BACKWARD_FILE).
+/// Returns a pointer to the entry and the index of the new entry is stored in
+/// 'new_qfidx'.
+static qfline_T * qf_get_entry(qf_list_T *qfl, int errornr,
+ int dir, int *new_qfidx)
+{
+ qfline_T *qf_ptr = qfl->qf_ptr;
+ int qfidx = qfl->qf_index;
+
+ if (dir != 0) { // next/prev valid entry
+ qf_ptr = get_nth_valid_entry(qfl, errornr, dir, &qfidx);
+ } else if (errornr != 0) { // go to specified number
+ qf_ptr = get_nth_entry(qfl, errornr, &qfidx);
+ }
+
+ *new_qfidx = qfidx;
+ return qf_ptr;
+}
+
// Find a window displaying a Vim help file.
static win_T *qf_find_help_win(void)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
@@ -2423,12 +2455,13 @@ static void win_set_loclist(win_T *wp, qf_info_T *qi)
qi->qf_refcount++;
}
-/// Find a help window or open one.
-static int jump_to_help_window(qf_info_T *qi, int *opened_window)
+/// Find a help window or open one. If 'newwin' is true, then open a new help
+/// window.
+static int jump_to_help_window(qf_info_T *qi, bool newwin, int *opened_window)
{
win_T *wp = NULL;
- if (cmdmod.tab != 0) {
+ if (cmdmod.tab != 0 || newwin) {
wp = NULL;
} else {
wp = qf_find_help_win();
@@ -2446,8 +2479,10 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
flags |= WSP_TOP;
}
- if (IS_LL_STACK(qi)) {
- flags |= WSP_NEWLOC; // don't copy the location list
+ // If the user asks to open a new window, then copy the location list.
+ // Otherwise, don't copy the location list.
+ if (IS_LL_STACK(qi) && !newwin) {
+ flags |= WSP_NEWLOC;
}
if (win_split(0, flags) == FAIL) {
@@ -2460,8 +2495,10 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
win_setheight((int)p_hh);
}
- if (IS_LL_STACK(qi)) { // not a quickfix list
- // The new window should use the supplied location list
+ // When using location list, the new window should use the supplied
+ // location list. If the user asks to open a new window, then the new
+ // window will get a copy of the location list.
+ if (IS_LL_STACK(qi) && !newwin) {
win_set_loclist(curwin, qi);
}
}
@@ -2622,14 +2659,19 @@ static void qf_goto_win_with_qfl_file(int qf_fnum)
// Find a suitable window for opening a file (qf_fnum) from the
// quickfix/location list and jump to it. If the file is already opened in a
-// window, jump to it. Otherwise open a new window to display the file. This is
-// called from either a quickfix or a location list window.
-static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
+// window, jump to it. Otherwise open a new window to display the file. If
+// 'newwin' is true, then always open a new window. This is called from either
+// a quickfix or a location list window.
+static int qf_jump_to_usable_window(int qf_fnum, bool newwin,
+ int *opened_window)
{
win_T *usable_win_ptr = NULL;
bool usable_win = false;
- qf_info_T *ll_ref = curwin->w_llist_ref;
+ // If opening a new window, then don't use the location list referred by
+ // the current window. Otherwise two windows will refer to the same
+ // location list.
+ qf_info_T *ll_ref = newwin ? NULL : curwin->w_llist_ref;
if (ll_ref != NULL) {
// Find a non-quickfix window with this location list
usable_win_ptr = qf_find_win_with_loclist(ll_ref);
@@ -2654,7 +2696,7 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
// If there is only one window and it is the quickfix window, create a
// new one above the quickfix window.
- if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) {
+ if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win || newwin) {
if (qf_open_new_file_win(ll_ref) != OK) {
return FAIL;
}
@@ -2672,52 +2714,52 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
/// Edit the selected file or help file.
static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
- win_T *oldwin, int *opened_window, int *abort)
+ win_T *oldwin, int *opened_window)
{
qf_list_T *qfl = qf_get_curlist(qi);
+ long old_changetick = qfl->qf_changedtick;
+ int old_qf_curlist = qi->qf_curlist;
qfltype_T qfl_type = qfl->qfl_type;
int retval = OK;
+ unsigned save_qfid = qfl->qf_id;
if (qf_ptr->qf_type == 1) {
// Open help file (do_ecmd() will set b_help flag, readfile() will
// set b_p_ro flag).
if (!can_abandon(curbuf, forceit)) {
no_write_message();
- retval = FAIL;
+ return FAIL;
} else {
retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1,
ECMD_HIDE + ECMD_SET_HELP,
oldwin == curwin ? curwin : NULL);
}
} else {
- unsigned save_qfid = qfl->qf_id;
-
retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
GETF_SETMARK | GETF_SWITCH, forceit);
+ }
+ // If a location list, check whether the associated window is still
+ // present.
+ if (qfl_type == QFLT_LOCATION && !win_valid_any_tab(oldwin)) {
+ EMSG(_("E924: Current window was closed"));
+ *opened_window = false;
+ return NOTDONE;
+ }
- if (qfl_type == QFLT_LOCATION) {
- // Location list. Check whether the associated window is still
- // present and the list is still valid.
- if (!win_valid_any_tab(oldwin)) {
- EMSG(_("E924: Current window was closed"));
- *abort = true;
- *opened_window = false;
- } else if (!qflist_valid(oldwin, save_qfid)) {
- EMSG(_(e_loc_list_changed));
- *abort = true;
- }
- } else if (!is_qf_entry_present(qfl, qf_ptr)) {
- if (qfl_type == QFLT_QUICKFIX) {
- EMSG(_("E925: Current quickfix was changed"));
- } else {
- EMSG(_(e_loc_list_changed));
- }
- *abort = true;
- }
+ if (qfl_type == QFLT_QUICKFIX && !qflist_valid(NULL, save_qfid)) {
+ EMSG(_(e_current_quickfix_list_was_changed));
+ return NOTDONE;
+ }
- if (*abort) {
- retval = FAIL;
+ if (old_qf_curlist != qi->qf_curlist
+ || old_changetick != qfl->qf_changedtick
+ || !is_qf_entry_present(qfl, qf_ptr)) {
+ if (qfl_type == QFLT_QUICKFIX) {
+ EMSG(_(e_current_quickfix_list_was_changed));
+ } else {
+ EMSG(_(e_current_location_list_was_changed));
}
+ return NOTDONE;
}
return retval;
@@ -2792,22 +2834,133 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr,
msg_scroll = (int)i;
}
-/// jump to a quickfix line
-/// if dir == FORWARD go "errornr" valid entries forward
-/// if dir == BACKWARD go "errornr" valid entries backward
-/// if dir == FORWARD_FILE go "errornr" valid entries files backward
-/// if dir == BACKWARD_FILE go "errornr" valid entries files backward
-/// else if "errornr" is zero, redisplay the same line
-/// else go to entry "errornr"
+/// Find a usable window for opening a file from the quickfix/location list. If
+/// a window is not found then open a new window. If 'newwin' is true, then open
+/// a new window.
+/// Returns OK if successfully jumped or opened a window. Returns FAIL if not
+/// able to jump/open a window. Returns NOTDONE if a file is not associated
+/// with the entry.
+static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin,
+ int *opened_window)
+{
+ qf_list_T *qfl = qf_get_curlist(qi);
+ long old_changetick = qfl->qf_changedtick;
+ int old_qf_curlist = qi->qf_curlist;
+ qfltype_T qfl_type = qfl->qfl_type;
+
+ // For ":helpgrep" find a help window or open one.
+ if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) {
+ if (jump_to_help_window(qi, newwin, opened_window) == FAIL) {
+ return FAIL;
+ }
+ }
+ if (old_qf_curlist != qi->qf_curlist
+ || old_changetick != qfl->qf_changedtick
+ || !is_qf_entry_present(qfl, qf_ptr)) {
+ if (qfl_type == QFLT_QUICKFIX) {
+ EMSG(_(e_current_quickfix_list_was_changed));
+ } else {
+ EMSG(_(e_current_location_list_was_changed));
+ }
+ return FAIL;
+ }
+
+ // If currently in the quickfix window, find another window to show the
+ // file in.
+ if (bt_quickfix(curbuf) && !*opened_window) {
+ // If there is no file specified, we don't know where to go.
+ // But do advance, otherwise ":cn" gets stuck.
+ if (qf_ptr->qf_fnum == 0) {
+ return NOTDONE;
+ }
+
+ if (qf_jump_to_usable_window(qf_ptr->qf_fnum, newwin, opened_window)
+ == FAIL) {
+ return FAIL;
+ }
+ }
+ if (old_qf_curlist != qi->qf_curlist
+ || old_changetick != qfl->qf_changedtick
+ || !is_qf_entry_present(qfl, qf_ptr)) {
+ if (qfl_type == QFLT_QUICKFIX) {
+ EMSG(_(e_current_quickfix_list_was_changed));
+ } else {
+ EMSG(_(e_current_location_list_was_changed));
+ }
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/// Edit a selected file from the quickfix/location list and jump to a
+/// particular line/column, adjust the folds and display a message about the
+/// jump.
+/// Returns OK on success and FAIL on failing to open the file/buffer. Returns
+/// NOTDONE if the quickfix/location list is freed by an autocmd when opening
+/// the file.
+static int qf_jump_to_buffer(qf_info_T *qi, int qf_index, qfline_T *qf_ptr,
+ int forceit, win_T *oldwin, int *opened_window,
+ int openfold, int print_message)
+{
+ buf_T *old_curbuf;
+ linenr_T old_lnum;
+ int retval = OK;
+
+ // 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;
+
+ if (qf_ptr->qf_fnum != 0) {
+ retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin,
+ opened_window);
+ if (retval != OK) {
+ return retval;
+ }
+ }
+
+ // When not switched to another buffer, still need to set pc mark
+ if (curbuf == old_curbuf) {
+ setpcmark();
+ }
+
+ qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol,
+ qf_ptr->qf_pattern);
+
+ if ((fdo_flags & FDO_QUICKFIX) && openfold) {
+ foldOpenCursor();
+ }
+ if (print_message) {
+ qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum);
+ }
+
+ return retval;
+}
+
+/// Jump to a quickfix line and try to use an existing window.
void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
{
+ qf_jump_newwin(qi, dir, errornr, forceit, false);
+}
+
+// Jump to a quickfix line.
+// If dir == 0 go to entry "errornr".
+// If dir == FORWARD go "errornr" valid entries forward.
+// If dir == BACKWARD go "errornr" valid entries backward.
+// If dir == FORWARD_FILE go "errornr" valid entries files backward.
+// If dir == BACKWARD_FILE go "errornr" valid entries files backward
+// else if "errornr" is zero, redisplay the same line
+// If 'forceit' is true, then can discard changes to the current buffer.
+// If 'newwin' is true, then open the file in a new window.
+static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit,
+ bool newwin)
+{
qf_list_T *qfl;
qfline_T *qf_ptr;
qfline_T *old_qf_ptr;
int qf_index;
int old_qf_index;
- buf_T *old_curbuf;
- linenr_T old_lnum;
char_u *old_swb = p_swb;
unsigned old_swb_flags = swb_flags;
int opened_window = false;
@@ -2830,15 +2983,12 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
old_qf_ptr = qf_ptr;
qf_index = qfl->qf_index;
old_qf_index = qf_index;
- if (dir != 0) { // next/prev valid entry
- qf_ptr = get_nth_valid_entry(qfl, errornr, qf_ptr, &qf_index, dir);
- if (qf_ptr == NULL) {
- qf_ptr = old_qf_ptr;
- qf_index = old_qf_index;
- goto theend; // The horror... the horror...
- }
- } else if (errornr != 0) { // go to specified number
- qf_ptr = get_nth_entry(qfl, errornr, qf_ptr, &qf_index);
+
+ qf_ptr = qf_get_entry(qfl, errornr, dir, &qf_index);
+ if (qf_ptr == NULL) {
+ qf_ptr = old_qf_ptr;
+ qf_index = old_qf_index;
+ goto theend;
}
qfl->qf_index = qf_index;
@@ -2848,58 +2998,23 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
print_message = false;
}
- // For ":helpgrep" find a help window or open one.
- if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) {
- if (jump_to_help_window(qi, &opened_window) == FAIL) {
- goto theend;
- }
+ retval = qf_jump_open_window(qi, qf_ptr, newwin, &opened_window);
+ if (retval == FAIL) {
+ goto failed;
}
-
- // If currently in the quickfix window, find another window to show the
- // file in.
- if (bt_quickfix(curbuf) && !opened_window) {
- // If there is no file specified, we don't know where to go.
- // But do advance, otherwise ":cn" gets stuck.
- if (qf_ptr->qf_fnum == 0) {
- goto theend;
- }
- if (qf_jump_to_usable_window(qf_ptr->qf_fnum, &opened_window) == FAIL) {
- goto failed;
- }
+ if (retval == NOTDONE) {
+ goto theend;
}
- // 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;
-
- if (qf_ptr->qf_fnum != 0) {
- int abort = false;
- retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin, &opened_window,
- &abort);
- if (abort) {
- qi = NULL;
- qf_ptr = NULL;
- }
+ retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, oldwin,
+ &opened_window, old_KeyTyped, print_message);
+ if (retval == NOTDONE) {
+ // Quickfix/location list is freed by an autocmd
+ qi = NULL;
+ qf_ptr = NULL;
}
- if (retval == OK) {
- // When not switched to another buffer, still need to set pc mark
- if (curbuf == old_curbuf) {
- setpcmark();
- }
-
- if (qf_ptr != NULL) {
- qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol,
- qf_ptr->qf_pattern);
- }
-
- if ((fdo_flags & FDO_QUICKFIX) && old_KeyTyped)
- foldOpenCursor();
- if (print_message) {
- qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum);
- }
- } else {
+ if (retval != OK) {
if (opened_window) {
win_close(curwin, true); // Close opened window
}
@@ -3185,7 +3300,7 @@ void qf_history(exarg_T *eap)
qf_info_T *qi = qf_cmd_get_stack(eap, false);
int i;
- if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) {
+ if (qf_stack_empty(qi)) {
MSG(_("No entries"));
} else {
for (i = 0; i < qi->qf_listcount; i++) {
@@ -3353,14 +3468,9 @@ void qf_view_result(bool split)
}
if (split) {
- char cmd[32];
-
- snprintf(cmd, sizeof(cmd), "split +%" PRId64 "%s",
- (int64_t)curwin->w_cursor.lnum,
- IS_LL_WINDOW(curwin) ? "ll" : "cc");
- if (do_cmdline_cmd(cmd) == OK) {
- do_cmdline_cmd("clearjumps");
- }
+ // Open the selected entry in a new window
+ qf_jump_newwin(qi, 0, (int)curwin->w_cursor.lnum, false, true);
+ do_cmdline_cmd("clearjumps");
return;
}
@@ -3391,7 +3501,7 @@ void ex_cwindow(exarg_T *eap)
// it if we have errors; otherwise, leave it closed.
if (qf_stack_empty(qi)
|| qfl->qf_nonevalid
- || qf_list_empty(qf_get_curlist(qi))) {
+ || qf_list_empty(qfl)) {
if (win != NULL) {
ex_cclose(eap);
}
@@ -3444,10 +3554,23 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz,
return OK;
}
+// Set options for the buffer in the quickfix or location list window.
+static void qf_set_cwindow_options(void)
+{
+ // switch off 'swapfile'
+ set_option_value("swf", 0L, NULL, OPT_LOCAL);
+ set_option_value("bt", 0L, "quickfix", OPT_LOCAL);
+ set_option_value("bh", 0L, "wipe", OPT_LOCAL);
+ RESET_BINDING(curwin);
+ curwin->w_p_diff = false;
+ set_option_value("fdm", 0L, "manual", OPT_LOCAL);
+}
+
// Open a new quickfix or location list window, load the quickfix buffer and
// set the appropriate options for the window.
// Returns FAIL if the window could not be opened.
-static int qf_open_new_cwindow(const qf_info_T *qi, int height)
+static int qf_open_new_cwindow(qf_info_T *qi, int height)
+ FUNC_ATTR_NONNULL_ALL
{
win_T *oldwin = curwin;
const tabpage_T *const prevtab = curtab;
@@ -3490,14 +3613,13 @@ static int qf_open_new_cwindow(const qf_info_T *qi, int height)
} else {
// Create a new quickfix buffer
(void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin);
+ }
- // switch off 'swapfile'
- set_option_value("swf", 0L, NULL, OPT_LOCAL);
- set_option_value("bt", 0L, "quickfix", OPT_LOCAL);
- set_option_value("bh", 0L, "wipe", OPT_LOCAL);
- RESET_BINDING(curwin);
- curwin->w_p_diff = false;
- set_option_value("fdm", 0L, "manual", OPT_LOCAL);
+ // Set the options for the quickfix buffer/window (if not already done)
+ // Do this even if the quickfix buffer was already present, as an autocmd
+ // might have previously deleted (:bdelete) the quickfix buffer.
+ if (curbuf->b_p_bt[0] != 'q') {
+ qf_set_cwindow_options();
}
// Only set the height when still in the same tab page and there is no
@@ -3686,8 +3808,8 @@ static win_T *qf_find_win(const qf_info_T *qi)
// 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
+static buf_T *qf_find_buf(qf_info_T *qi)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
FOR_ALL_TAB_WINDOWS(tp, win) {
if (is_qf_win(win, qi)) {
@@ -3750,7 +3872,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
// Add an error line to the quickfix buffer.
static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp,
- char_u *dirname)
+ char_u *dirname, bool first_bufline)
FUNC_ATTR_NONNULL_ALL
{
int len;
@@ -3765,9 +3887,12 @@ static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp,
if (qfp->qf_type == 1) { // :helpgrep
STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE - 1);
} else {
- // shorten the file name if not done already
- if (errbuf->b_sfname == NULL
- || path_is_absolute(errbuf->b_sfname)) {
+ // Shorten the file name if not done already.
+ // For optimization, do this only for the first entry in a
+ // buffer.
+ if (first_bufline
+ && (errbuf->b_sfname == NULL
+ || path_is_absolute(errbuf->b_sfname))) {
if (*dirname == NUL) {
os_dirname(dirname, MAXPATHL);
}
@@ -3845,6 +3970,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
// Check if there is anything to display
if (qfl != NULL) {
char_u dirname[MAXPATHL];
+ int prev_bufnr = -1;
*dirname = NUL;
@@ -3857,9 +3983,11 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
lnum = buf->b_ml.ml_line_count;
}
while (lnum < qfl->qf_count) {
- if (qf_buf_add_line(buf, lnum, qfp, dirname) == FAIL) {
+ if (qf_buf_add_line(buf, lnum, qfp, dirname,
+ prev_bufnr != qfp->qf_fnum) == FAIL) {
break;
}
+ prev_bufnr = qfp->qf_fnum;
lnum++;
qfp = qfp->qf_next;
if (qfp == NULL) {
@@ -4806,7 +4934,7 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid,
if (!qflist_valid(wp, qfid)) {
if (wp != NULL) {
// An autocmd has freed the location list
- EMSG(_(e_loc_list_changed));
+ EMSG(_(e_current_location_list_was_changed));
return false;
} else {
// Quickfix list is not found, create a new one.
@@ -4824,20 +4952,21 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid,
/// Search for a pattern in all the lines in a buffer and add the matching lines
/// to a quickfix list.
-static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
- regmmatch_T *regmatch, long tomatch,
+static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf,
+ regmmatch_T *regmatch, long *tomatch,
int duplicate_name, int flags)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5)
{
bool found_match = false;
- for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0; lnum++) {
+ for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) {
colnr_T col = 0;
while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL,
NULL) > 0) {
// Pass the buffer number so that it gets used even for a
// dummy buffer, unless duplicate_name is set, then the
// buffer will be wiped out below.
- if (qf_add_entry(qf_get_curlist(qi),
+ if (qf_add_entry(qfl,
NULL, // dir
fname,
NULL,
@@ -4856,7 +4985,7 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
break;
}
found_match = true;
- if (--tomatch == 0) {
+ if (--*tomatch == 0) {
break;
}
if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
@@ -4898,6 +5027,20 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
}
}
+// Return true if "buf" had an existing swap file, the current swap file does
+// not end in ".swp".
+static bool existing_swapfile(const buf_T *buf)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
+ const char_u *const fname = buf->b_ml.ml_mfp->mf_fname;
+ const size_t len = STRLEN(fname);
+
+ return fname[len - 1] != 'p' || fname[len - 2] != 'w';
+ }
+ return false;
+}
+
// ":vimgrep {pattern} file(s)"
// ":vimgrepadd {pattern} file(s)"
// ":lvimgrep {pattern} file(s)"
@@ -5030,7 +5173,8 @@ void ex_vimgrep(exarg_T *eap)
} else {
// Try for a match in all lines of the buffer.
// For ":1vimgrep" look for first match only.
- found_match = vgr_match_buflines(qi, fname, buf, &regmatch, tomatch,
+ found_match = vgr_match_buflines(qf_get_curlist(qi),
+ fname, buf, &regmatch, &tomatch,
duplicate_name, flags);
if (using_dummy) {
@@ -5054,7 +5198,9 @@ void ex_vimgrep(exarg_T *eap)
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
- } else if (buf != first_match_buf || (flags & VGR_NOJUMP)) {
+ } else if (buf != first_match_buf
+ || (flags & VGR_NOJUMP)
+ || existing_swapfile(buf)) {
unload_dummy_buffer(buf, dirname_start);
// Keeping the buffer, remove the dummy flag.
buf->b_flags &= ~BF_DUMMY;
@@ -6020,6 +6166,49 @@ static int qf_setprop_context(qf_list_T *qfl, dictitem_T *di)
return OK;
}
+// Set the current index in the specified quickfix list
+static int qf_setprop_curidx(qf_info_T *qi, qf_list_T *qfl,
+ const dictitem_T *di)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int newidx;
+
+ // If the specified index is '$', then use the last entry
+ if (di->di_tv.v_type == VAR_STRING
+ && di->di_tv.vval.v_string != NULL
+ && STRCMP(di->di_tv.vval.v_string, "$") == 0) {
+ newidx = qfl->qf_count;
+ } else {
+ // Otherwise use the specified index
+ bool denote = false;
+ newidx = (int)tv_get_number_chk(&di->di_tv, &denote);
+ if (denote) {
+ return FAIL;
+ }
+ }
+
+ if (newidx < 1) { // sanity check
+ return FAIL;
+ }
+ if (newidx > qfl->qf_count) {
+ newidx = qfl->qf_count;
+ }
+ const int old_qfidx = qfl->qf_index;
+ qfline_T *const qf_ptr = get_nth_entry(qfl, newidx, &newidx);
+ if (qf_ptr == NULL) {
+ return FAIL;
+ }
+ qfl->qf_ptr = qf_ptr;
+ qfl->qf_index = newidx;
+
+ // If the current list is modified and it is displayed in the quickfix
+ // window, then Update it.
+ if (qi->qf_lists[qi->qf_curlist].qf_id == qfl->qf_id) {
+ qf_win_pos_update(qi, old_qfidx);
+ }
+ return OK;
+}
+
/// Set quickfix/location list properties (title, items, context).
/// Also used to add items from parsing a list of lines.
/// Used by the setqflist() and setloclist() Vim script functions.
@@ -6055,6 +6244,9 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) {
retval = qf_setprop_context(qfl, di);
}
+ if ((di = tv_dict_find(what, S_LEN("idx"))) != NULL) {
+ retval = qf_setprop_curidx(qi, qfl, di);
+ }
if (retval == OK) {
qf_list_changed(qfl);
@@ -6065,7 +6257,8 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
/// Find the non-location list window with the specified location list stack in
/// the current tabpage.
-static win_T * find_win_with_ll(qf_info_T *qi)
+static win_T *find_win_with_ll(const qf_info_T *qi)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) {
@@ -6124,6 +6317,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi)
// Populate the quickfix list with the items supplied in the list
// of dictionaries. "title" will be copied to w:quickfix_title
// "action" is 'a' for add, 'r' for replace. Otherwise create a new list.
+// When "what" is not NULL then only set some properties.
int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
dict_T *what)
{
@@ -6140,6 +6334,12 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
return OK;
}
+ // A dict argument cannot be specified with a non-empty list argument
+ if (list != NULL && tv_list_len(list) != 0 && what != NULL) {
+ EMSG2(_(e_invarg2), _("cannot have both a list and a \"what\" argument"));
+ return FAIL;
+ }
+
incr_quickfix_busy();
if (what != NULL) {
@@ -6368,7 +6568,7 @@ void ex_cexpr(exarg_T *eap)
// 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 (eval0(eap->arg, &tv, &eap->nextcmd, true) != FAIL) {
if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL)
|| tv.v_type == VAR_LIST) {
incr_quickfix_busy();
@@ -6439,7 +6639,7 @@ static qf_info_T *hgr_get_ll(bool *new_ll)
// Search for a pattern in a help file.
static void hgr_search_file(
- qf_info_T *qi,
+ qf_list_T *qfl,
char_u *fname,
regmatch_T *p_regmatch)
FUNC_ATTR_NONNULL_ARG(1, 3)
@@ -6461,7 +6661,7 @@ static void hgr_search_file(
line[--l] = NUL;
}
- if (qf_add_entry(qf_get_curlist(qi),
+ if (qf_add_entry(qfl,
NULL, // dir
fname,
NULL,
@@ -6494,7 +6694,7 @@ static void hgr_search_file(
// Search for a pattern in all the help files in the doc directory under
// the given directory.
static void hgr_search_files_in_dir(
- qf_info_T *qi,
+ qf_list_T *qfl,
char_u *dirname,
regmatch_T *p_regmatch,
const char_u *lang)
@@ -6519,7 +6719,7 @@ static void hgr_search_files_in_dir(
continue;
}
- hgr_search_file(qi, fnames[fi], p_regmatch);
+ hgr_search_file(qfl, fnames[fi], p_regmatch);
}
FreeWild(fcount, fnames);
}
@@ -6529,7 +6729,7 @@ static void hgr_search_files_in_dir(
// and add the matches to a quickfix list.
// 'lang' is the language specifier. If supplied, then only matches in the
// specified language are found.
-static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch,
+static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch,
const char_u *lang)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
@@ -6538,7 +6738,7 @@ static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch,
while (*p != NUL && !got_int) {
copy_option_part(&p, NameBuff, MAXPATHL, ",");
- hgr_search_files_in_dir(qi, NameBuff, p_regmatch, lang);
+ hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, lang);
}
}
@@ -6580,12 +6780,12 @@ void ex_helpgrep(exarg_T *eap)
if (regmatch.regprog != NULL) {
// Create a new quickfix list.
qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep));
+ qf_list_T *const qfl = qf_get_curlist(qi);
- hgr_search_in_rtp(qi, &regmatch, lang);
+ hgr_search_in_rtp(qfl, &regmatch, lang);
vim_regfree(regmatch.regprog);
- qf_list_T *qfl = qf_get_curlist(qi);
qfl->qf_nonevalid = false;
qfl->qf_ptr = qfl->qf_start;
qfl->qf_index = 1;
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 9705896e9b..6316129c6a 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -1270,8 +1270,9 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags)
int len;
int flags;
- if (expr == NULL)
- EMSG_RET_NULL(_(e_null));
+ if (expr == NULL) {
+ IEMSG_RET_NULL(_(e_null));
+ }
init_class_tab();
@@ -3483,7 +3484,7 @@ static long bt_regexec_both(char_u *line,
/* Be paranoid... */
if (prog == NULL || line == NULL) {
- EMSG(_(e_null));
+ IEMSG(_(e_null));
goto theend;
}
@@ -4789,7 +4790,7 @@ static bool regmatch(
break;
default:
- EMSG(_(e_re_corr));
+ IEMSG(_(e_re_corr));
#ifdef REGEXP_DEBUG
printf("Illegal op code %d\n", op);
#endif
@@ -5147,7 +5148,7 @@ static bool regmatch(
* We get here only if there's trouble -- normally "case END" is
* the terminating point.
*/
- EMSG(_(e_re_corr));
+ IEMSG(_(e_re_corr));
#ifdef REGEXP_DEBUG
printf("Premature EOL\n");
#endif
@@ -5552,8 +5553,8 @@ do_class:
}
break;
- default: /* Oh dear. Called inappropriately. */
- EMSG(_(e_re_corr));
+ default: // Oh dear. Called inappropriately.
+ IEMSG(_(e_re_corr));
#ifdef REGEXP_DEBUG
printf("Called regrepeat with op code %d\n", OP(p));
#endif
@@ -6707,14 +6708,14 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
argv[0].vval.v_list = &matchList.sl_list;
if (expr->v_type == VAR_FUNC) {
s = expr->vval.v_string;
- call_func(s, (int)STRLEN(s), &rettv, 1, argv,
+ call_func(s, -1, &rettv, 1, argv,
fill_submatch_list, 0L, 0L, &dummy,
true, NULL, NULL);
} else if (expr->v_type == VAR_PARTIAL) {
partial_T *partial = expr->vval.v_partial;
s = partial_name(partial);
- call_func(s, (int)STRLEN(s), &rettv, 1, argv,
+ call_func(s, -1, &rettv, 1, argv,
fill_submatch_list, 0L, 0L, &dummy,
true, partial, NULL);
}
@@ -6911,7 +6912,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
}
} else if (*s == NUL) { // we hit NUL.
if (copy) {
- EMSG(_(e_re_damg));
+ IEMSG(_(e_re_damg));
}
goto exit;
} else {
@@ -7386,6 +7387,7 @@ long vim_regexec_multi(
proftime_T *tm, // timeout limit or NULL
int *timed_out // flag is set when timeout limit reached
)
+ FUNC_ATTR_NONNULL_ARG(1)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 506c4e87db..7cd1ae93d2 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -6519,7 +6519,7 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
/* Be paranoid... */
if (prog == NULL || line == NULL) {
- EMSG(_(e_null));
+ IEMSG(_(e_null));
goto theend;
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 69de1de6b2..df0c5ce791 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -119,10 +119,12 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/lua/executor.h"
+#include "nvim/lib/kvec.h"
#define MB_FILLER_CHAR '<' /* character used when a double-width character
* doesn't fit. */
+typedef kvec_withinit_t(DecorationProvider *, 4) Providers;
// temporary buffer for rendering a single screenline, so it can be
// compared with previous contents to calculate smallest delta.
@@ -133,8 +135,6 @@ static sattr_T *linebuf_attr = NULL;
static match_T search_hl; /* used for 'hlsearch' highlight matching */
-static foldinfo_T win_foldinfo; /* info for 'foldcolumn' */
-
StlClickDefinition *tab_page_click_defs = NULL;
long tab_page_click_defs_size = 0;
@@ -158,11 +158,43 @@ static bool msg_grid_invalid = false;
static bool resizing = false;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "screen.c.generated.h"
#endif
#define SEARCH_HL_PRIORITY 0
+static char * provider_first_error = NULL;
+
+static bool provider_invoke(NS ns_id, const char *name, LuaRef ref,
+ Array args, bool default_true)
+{
+ Error err = ERROR_INIT;
+
+ textlock++;
+ Object ret = nlua_call_ref(ref, name, args, true, &err);
+ textlock--;
+
+ if (!ERROR_SET(&err)
+ && api_coerce_to_bool(ret, "provider %s retval", default_true, &err)) {
+ return true;
+ }
+
+ if (ERROR_SET(&err)) {
+ const char *ns_name = describe_ns(ns_id);
+ ELOG("error in provider %s:%s: %s", ns_name, name, err.msg);
+ bool verbose_errs = true; // TODO(bfredl):
+ if (verbose_errs && provider_first_error == NULL) {
+ static char errbuf[IOSIZE];
+ snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg);
+ provider_first_error = xstrdup(errbuf);
+ }
+ }
+
+ api_free_object(ret);
+ return false;
+}
+
/*
* Redraw the current window later, with update_screen(type).
* Set must_redraw only if not already set to a higher value.
@@ -446,6 +478,29 @@ int update_screen(int type)
ui_comp_set_screen_valid(true);
+ Providers providers;
+ kvi_init(providers);
+ for (size_t i = 0; i < kv_size(decoration_providers); i++) {
+ DecorationProvider *p = &kv_A(decoration_providers, i);
+ if (!p->active) {
+ continue;
+ }
+
+ bool active;
+ if (p->redraw_start != LUA_NOREF) {
+ FIXED_TEMP_ARRAY(args, 2);
+ args.items[0] = INTEGER_OBJ(display_tick);
+ args.items[1] = INTEGER_OBJ(type);
+ active = provider_invoke(p->ns_id, "start", p->redraw_start, args, true);
+ } else {
+ active = true;
+ }
+
+ if (active) {
+ kvi_push(providers, p);
+ }
+ }
+
if (clear_cmdline) /* going to clear cmdline (done below) */
check_for_delay(FALSE);
@@ -494,30 +549,24 @@ int update_screen(int type)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
update_window_hl(wp, type >= NOT_VALID);
- if (wp->w_buffer->b_mod_set) {
- win_T *wwp;
-
- // Check if we already did this buffer.
- for (wwp = firstwin; wwp != wp; wwp = wwp->w_next) {
- if (wwp->w_buffer == wp->w_buffer) {
- break;
- }
- }
- if (wwp == wp && syntax_present(wp)) {
- syn_stack_apply_changes(wp->w_buffer);
- }
-
- buf_T *buf = wp->w_buffer;
- if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
- Error err = ERROR_INIT;
- FIXED_TEMP_ARRAY(args, 2);
- args.items[0] = BUFFER_OBJ(buf->handle);
- args.items[1] = INTEGER_OBJ(display_tick);
- executor_exec_lua_cb(buf->b_luahl_start, "start", args, false, &err);
- if (ERROR_SET(&err)) {
- ELOG("error in luahl start: %s", err.msg);
- api_clear_error(&err);
+ buf_T *buf = wp->w_buffer;
+ if (buf->b_mod_set) {
+ if (buf->b_mod_tick_syn < display_tick
+ && syntax_present(wp)) {
+ syn_stack_apply_changes(buf);
+ buf->b_mod_tick_syn = display_tick;
+ }
+
+ if (buf->b_mod_tick_deco < display_tick) {
+ for (size_t i = 0; i < kv_size(providers); i++) {
+ DecorationProvider *p = kv_A(providers, i);
+ if (p && p->redraw_buf != LUA_NOREF) {
+ FIXED_TEMP_ARRAY(args, 1);
+ args.items[0] = BUFFER_OBJ(buf->handle);
+ provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true);
+ }
}
+ buf->b_mod_tick_deco = display_tick;
}
}
}
@@ -531,6 +580,8 @@ int update_screen(int type)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ redrawn_win = wp;
+
if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) {
grid_invalidate(&wp->w_grid);
wp->w_redr_type = NOT_VALID;
@@ -541,13 +592,15 @@ int update_screen(int type)
did_one = TRUE;
start_search_hl();
}
- win_update(wp);
+ win_update(wp, &providers);
}
/* redraw status line after the window to minimize cursor movement */
if (wp->w_redr_status) {
win_redr_status(wp);
}
+
+ redrawn_win = NULL;
}
end_search_hl();
@@ -578,6 +631,21 @@ int update_screen(int type)
maybe_intro_message();
did_intro = TRUE;
+ for (size_t i = 0; i < kv_size(providers); i++) {
+ DecorationProvider *p = kv_A(providers, i);
+ if (!p->active) {
+ continue;
+ }
+
+ if (p->redraw_end != LUA_NOREF) {
+ FIXED_TEMP_ARRAY(args, 1);
+ args.items[0] = INTEGER_OBJ(display_tick);
+ provider_invoke(p->ns_id, "end", p->redraw_end, args, true);
+ }
+ }
+ kvi_destroy(providers);
+
+
// either cmdline is cleared, not drawn or mode is last drawn
cmdline_was_last_drawn = false;
return OK;
@@ -635,14 +703,15 @@ bool win_cursorline_standout(const win_T *wp)
}
static DecorationRedrawState decorations;
-bool decorations_active = false;
-void decorations_add_luahl_attr(int attr_id,
- int start_row, int start_col,
- int end_row, int end_col)
+void decorations_add_ephemeral(int attr_id,
+ int start_row, int start_col,
+ int end_row, int end_col, VirtText *virt_text)
{
kv_push(decorations.active,
- ((HlRange){ start_row, start_col, end_row, end_col, attr_id, NULL }));
+ ((HlRange){ start_row, start_col,
+ end_row, end_col,
+ attr_id, virt_text, virt_text != NULL }));
}
/*
@@ -672,7 +741,7 @@ void decorations_add_luahl_attr(int attr_id,
* mid: from mid_start to mid_end (update inversion or changed text)
* bot: from bot_start to last row (when scrolled up)
*/
-static void win_update(win_T *wp)
+static void win_update(win_T *wp, Providers *providers)
{
buf_T *buf = wp->w_buffer;
int type;
@@ -697,9 +766,10 @@ static void win_update(win_T *wp)
int didline = FALSE; /* if TRUE, we finished the last line */
int i;
long j;
- static int recursive = FALSE; /* being called recursively */
- int old_botline = wp->w_botline;
- long fold_count;
+ static bool recursive = false; // being called recursively
+ const linenr_T old_botline = wp->w_botline;
+ const int old_wrow = wp->w_wrow;
+ const int old_wcol = wp->w_wcol;
// Remember what happened to the previous line.
#define DID_NONE 1 // didn't update a line
#define DID_LINE 2 // updated a normal line
@@ -710,6 +780,7 @@ static void win_update(win_T *wp)
linenr_T mod_bot = 0;
int save_got_int;
+
// If we can compute a change in the automatic sizing of the sign column
// under 'signcolumn=auto:X' and signs currently placed in the buffer, better
// figuring it out here so we can redraw the entire screen for it.
@@ -898,11 +969,12 @@ static void win_update(win_T *wp)
|| type == INVERTED || type == INVERTED_ALL)
&& !wp->w_botfill && !wp->w_old_botfill
) {
- if (mod_top != 0 && wp->w_topline == mod_top) {
- /*
- * w_topline is the first changed line, the scrolling will be done
- * further down.
- */
+ if (mod_top != 0
+ && wp->w_topline == mod_top
+ && (!wp->w_lines[0].wl_valid
+ || wp->w_topline <= wp->w_lines[0].wl_lnum)) {
+ // w_topline is the first changed line and window is not scrolled,
+ // the scrolling from changed lines will be done further down.
} else if (wp->w_lines[0].wl_valid
&& (wp->w_topline < wp->w_lines[0].wl_lnum
|| (wp->w_topline == wp->w_lines[0].wl_lnum
@@ -1226,7 +1298,6 @@ static void win_update(win_T *wp)
// Set the time limit to 'redrawtime'.
proftime_T syntax_tm = profile_setlimit(p_rdt);
syn_set_timeout(&syntax_tm);
- win_foldinfo.fi_level = 0;
/*
* Update all the window rows.
@@ -1236,25 +1307,27 @@ static void win_update(win_T *wp)
srow = 0;
lnum = wp->w_topline; // first line shown in window
- decorations_active = decorations_redraw_reset(buf, &decorations);
+ decorations_redraw_reset(buf, &decorations);
- if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
- Error err = ERROR_INIT;
- FIXED_TEMP_ARRAY(args, 4);
- linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
- ? wp->w_botline
- : (wp->w_topline + wp->w_height_inner));
- args.items[0] = WINDOW_OBJ(wp->handle);
- args.items[1] = BUFFER_OBJ(buf->handle);
- // TODO(bfredl): we are not using this, but should be first drawn line?
- args.items[2] = INTEGER_OBJ(wp->w_topline-1);
- args.items[3] = INTEGER_OBJ(knownmax);
- // TODO(bfredl): we could allow this callback to change mod_top, mod_bot.
- // For now the "start" callback is expected to use nvim__buf_redraw_range.
- executor_exec_lua_cb(buf->b_luahl_window, "window", args, false, &err);
- if (ERROR_SET(&err)) {
- ELOG("error in luahl window: %s", err.msg);
- api_clear_error(&err);
+ Providers line_providers;
+ kvi_init(line_providers);
+
+ linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
+ ? wp->w_botline
+ : (wp->w_topline + wp->w_height_inner));
+
+ for (size_t k = 0; k < kv_size(*providers); k++) {
+ DecorationProvider *p = kv_A(*providers, k);
+ if (p && p->redraw_win != LUA_NOREF) {
+ FIXED_TEMP_ARRAY(args, 4);
+ args.items[0] = WINDOW_OBJ(wp->handle);
+ args.items[1] = BUFFER_OBJ(buf->handle);
+ // TODO(bfredl): we are not using this, but should be first drawn line?
+ args.items[2] = INTEGER_OBJ(wp->w_topline-1);
+ args.items[3] = INTEGER_OBJ(knownmax);
+ if (provider_invoke(p->ns_id, "win", p->redraw_win, args, true)) {
+ kvi_push(line_providers, p);
+ }
}
}
@@ -1448,24 +1521,19 @@ static void win_update(win_T *wp)
* Otherwise, display normally (can be several display lines when
* 'wrap' is on).
*/
- fold_count = foldedCount(wp, lnum, &win_foldinfo);
- if (fold_count != 0) {
- fold_line(wp, fold_count, &win_foldinfo, lnum, row);
- ++row;
- --fold_count;
- wp->w_lines[idx].wl_folded = TRUE;
- wp->w_lines[idx].wl_lastlnum = lnum + fold_count;
- did_update = DID_FOLD;
- } else if (idx < wp->w_lines_valid
- && wp->w_lines[idx].wl_valid
- && wp->w_lines[idx].wl_lnum == lnum
- && lnum > wp->w_topline
- && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))
- && srow + wp->w_lines[idx].wl_size > wp->w_grid.Rows
- && diff_check_fill(wp, lnum) == 0
- ) {
- /* This line is not going to fit. Don't draw anything here,
- * will draw "@ " lines below. */
+ foldinfo_T foldinfo = fold_info(wp, lnum);
+
+ if (foldinfo.fi_lines == 0
+ && idx < wp->w_lines_valid
+ && wp->w_lines[idx].wl_valid
+ && wp->w_lines[idx].wl_lnum == lnum
+ && lnum > wp->w_topline
+ && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))
+ && srow + wp->w_lines[idx].wl_size > wp->w_grid.Rows
+ && diff_check_fill(wp, lnum) == 0
+ ) {
+ // This line is not going to fit. Don't draw anything here,
+ // will draw "@ " lines below.
row = wp->w_grid.Rows + 1;
} else {
prepare_search_hl(wp, lnum);
@@ -1474,14 +1542,21 @@ static void win_update(win_T *wp)
&& syntax_present(wp))
syntax_end_parsing(syntax_last_parsed + 1);
- /*
- * Display one line.
- */
- row = win_line(wp, lnum, srow, wp->w_grid.Rows, mod_top == 0, false);
+ // Display one line
+ row = win_line(wp, lnum, srow,
+ foldinfo.fi_lines ? srow : wp->w_grid.Rows,
+ mod_top == 0, false, foldinfo, &line_providers);
- wp->w_lines[idx].wl_folded = FALSE;
+ wp->w_lines[idx].wl_folded = foldinfo.fi_lines != 0;
wp->w_lines[idx].wl_lastlnum = lnum;
did_update = DID_LINE;
+
+ if (foldinfo.fi_lines > 0) {
+ did_update = DID_FOLD;
+ foldinfo.fi_lines--;
+ wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
+ }
+
syntax_last_parsed = lnum;
}
@@ -1496,20 +1571,18 @@ static void win_update(win_T *wp)
idx++;
break;
}
- if (dollar_vcol == -1)
+ if (dollar_vcol == -1) {
wp->w_lines[idx].wl_size = row - srow;
- ++idx;
- lnum += fold_count + 1;
+ }
+ idx++;
+ lnum += foldinfo.fi_lines + 1;
} else {
if (wp->w_p_rnu) {
// 'relativenumber' set: The text doesn't need to be drawn, but
// the number column nearly always does.
- fold_count = foldedCount(wp, lnum, &win_foldinfo);
- if (fold_count != 0) {
- fold_line(wp, fold_count, &win_foldinfo, lnum, row);
- } else {
- (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true);
- }
+ foldinfo_T info = fold_info(wp, lnum);
+ (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true,
+ info, &line_providers);
}
// This line does not need to be drawn, advance to the next one.
@@ -1605,6 +1678,8 @@ static void win_update(win_T *wp)
HLF_EOB);
}
+ kvi_destroy(line_providers);
+
if (wp->w_redr_type >= REDRAW_TOP) {
draw_vsep_win(wp, 0);
}
@@ -1631,18 +1706,51 @@ static void win_update(win_T *wp)
wp->w_valid |= VALID_BOTLINE;
wp->w_viewport_invalid = true;
if (wp == curwin && wp->w_botline != old_botline && !recursive) {
- recursive = TRUE;
+ const linenr_T old_topline = wp->w_topline;
+ const int new_wcol = wp->w_wcol;
+ recursive = true;
curwin->w_valid &= ~VALID_TOPLINE;
- update_topline(); /* may invalidate w_botline again */
- if (must_redraw != 0) {
- /* Don't update for changes in buffer again. */
+ update_topline(); // may invalidate w_botline again
+
+ if (old_wcol != new_wcol
+ && (wp->w_valid & (VALID_WCOL|VALID_WROW))
+ != (VALID_WCOL|VALID_WROW)) {
+ // A win_line() call applied a fix to screen cursor column to
+ // accomodate concealment of cursor line, but in this call to
+ // update_topline() the cursor's row or column got invalidated.
+ // If they are left invalid, setcursor() will recompute them
+ // but there won't be any further win_line() call to re-fix the
+ // column and the cursor will end up misplaced. So we call
+ // cursor validation now and reapply the fix again (or call
+ // win_line() to do it for us).
+ validate_cursor();
+ if (wp->w_wcol == old_wcol
+ && wp->w_wrow == old_wrow
+ && old_topline == wp->w_topline) {
+ wp->w_wcol = new_wcol;
+ } else {
+ redrawWinline(wp, wp->w_cursor.lnum);
+ }
+ }
+ // New redraw either due to updated topline or due to wcol fix.
+ if (wp->w_redr_type != 0) {
+ // Don't update for changes in buffer again.
i = curbuf->b_mod_set;
curbuf->b_mod_set = false;
- win_update(curwin);
- must_redraw = 0;
+ j = curbuf->b_mod_xlines;
+ curbuf->b_mod_xlines = 0;
+ win_update(curwin, providers);
curbuf->b_mod_set = i;
+ curbuf->b_mod_xlines = j;
+ }
+ // Other windows might have w_redr_type raised in update_topline().
+ must_redraw = 0;
+ FOR_ALL_WINDOWS_IN_TAB(wwp, curtab) {
+ if (wwp->w_redr_type > must_redraw) {
+ must_redraw = wwp->w_redr_type;
+ }
}
- recursive = FALSE;
+ recursive = false;
}
}
@@ -1741,31 +1849,6 @@ static int advance_color_col(int vcol, int **color_cols)
return **color_cols >= 0;
}
-// Returns the next grid column.
-static int text_to_screenline(win_T *wp, char_u *text, int col, int off)
- FUNC_ATTR_NONNULL_ALL
-{
- int idx = wp->w_p_rl ? off : off + col;
- LineState s = LINE_STATE(text);
-
- while (*s.p != NUL) {
- // TODO(bfredl): cargo-culted from the old Vim code:
- // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; }
- // This is obvious wrong. If Vim ever fixes this, solve for "cells" again
- // in the correct condition.
- const int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0);
- const int cells = line_putchar(&s, &linebuf_char[idx], maxcells,
- wp->w_p_rl);
- if (cells == -1) {
- break;
- }
- col += cells;
- idx += cells;
- }
-
- return col;
-}
-
// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
// space is available for window "wp", minus "col".
static int compute_foldcolumn(win_T *wp, int col)
@@ -1830,271 +1913,6 @@ static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl)
return cells;
}
-/*
- * Display one folded line.
- */
-static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T lnum, int row)
-{
- char_u buf[FOLD_TEXT_LEN];
- pos_T *top, *bot;
- linenr_T lnume = lnum + fold_count - 1;
- int len;
- char_u *text;
- int fdc;
- int col;
- int txtcol;
- int off;
-
- /* Build the fold line:
- * 1. Add the cmdwin_type for the command-line window
- * 2. Add the 'foldcolumn'
- * 3. Add the 'number' or 'relativenumber' column
- * 4. Compose the text
- * 5. Add the text
- * 6. set highlighting for the Visual area an other text
- */
- col = 0;
- off = 0;
-
- /*
- * 1. Add the cmdwin_type for the command-line window
- * Ignores 'rightleft', this window is never right-left.
- */
- if (cmdwin_type != 0 && wp == curwin) {
- schar_from_ascii(linebuf_char[off], cmdwin_type);
- linebuf_attr[off] = win_hl_attr(wp, HLF_AT);
- col++;
- }
-
-# define RL_MEMSET(p, v, l) \
- do { \
- if (wp->w_p_rl) { \
- for (int ri = 0; ri < l; ri++) { \
- linebuf_attr[off + (wp->w_grid.Columns - (p) - (l)) + ri] = v; \
- } \
- } else { \
- for (int ri = 0; ri < l; ri++) { \
- linebuf_attr[off + (p) + ri] = v; \
- } \
- } \
- } while (0)
-
- // 2. Add the 'foldcolumn'
- // Reduce the width when there is not enough space.
- fdc = compute_foldcolumn(wp, col);
- if (fdc > 0) {
- fill_foldcolumn(buf, wp, true, lnum);
- const char_u *it = &buf[0];
- for (int i = 0; i < fdc; i++) {
- int mb_c = mb_ptr2char_adv(&it);
- if (wp->w_p_rl) {
- schar_from_char(linebuf_char[off + wp->w_grid.Columns - i - 1 - col],
- mb_c);
- } else {
- schar_from_char(linebuf_char[off + col + i], mb_c);
- }
- }
- RL_MEMSET(col, win_hl_attr(wp, HLF_FC), fdc);
- col += fdc;
- }
-
- /* Set all attributes of the 'number' or 'relativenumber' column and the
- * text */
- RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_grid.Columns - col);
-
- // If signs are being displayed, add spaces.
- if (win_signcol_count(wp) > 0) {
- len = wp->w_grid.Columns - col;
- if (len > 0) {
- int len_max = win_signcol_width(wp) * win_signcol_count(wp);
- if (len > len_max) {
- len = len_max;
- }
- char_u space_buf[18] = " ";
- assert((size_t)len_max <= sizeof(space_buf));
- copy_text_attr(off + col, space_buf, len,
- win_hl_attr(wp, HLF_FL));
- col += len;
- }
- }
-
- /*
- * 3. Add the 'number' or 'relativenumber' column
- */
- if (wp->w_p_nu || wp->w_p_rnu) {
- len = wp->w_grid.Columns - col;
- if (len > 0) {
- int w = number_width(wp);
- long num;
- char *fmt = "%*ld ";
-
- if (len > w + 1)
- len = w + 1;
-
- if (wp->w_p_nu && !wp->w_p_rnu)
- /* 'number' + 'norelativenumber' */
- num = (long)lnum;
- else {
- /* 'relativenumber', don't use negative numbers */
- num = labs((long)get_cursor_rel_lnum(wp, lnum));
- if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
- /* 'number' + 'relativenumber': cursor line shows absolute
- * line number */
- num = lnum;
- fmt = "%-*ld ";
- }
- }
-
- snprintf((char *)buf, FOLD_TEXT_LEN, fmt, w, num);
- if (wp->w_p_rl) {
- // the line number isn't reversed
- copy_text_attr(off + wp->w_grid.Columns - len - col, buf, len,
- win_hl_attr(wp, HLF_FL));
- } else {
- copy_text_attr(off + col, buf, len, win_hl_attr(wp, HLF_FL));
- }
- col += len;
- }
- }
-
- /*
- * 4. Compose the folded-line string with 'foldtext', if set.
- */
- text = get_foldtext(wp, lnum, lnume, foldinfo, buf);
-
- txtcol = col; /* remember where text starts */
-
- // 5. move the text to linebuf_char[off]. Fill up with "fold".
- // Right-left text is put in columns 0 - number-col, normal text is put
- // in columns number-col - window-width.
- col = text_to_screenline(wp, text, col, off);
-
- /* Fill the rest of the line with the fold filler */
- if (wp->w_p_rl)
- col -= txtcol;
-
- schar_T sc;
- schar_from_char(sc, wp->w_p_fcs_chars.fold);
- while (col < wp->w_grid.Columns
- - (wp->w_p_rl ? txtcol : 0)
- ) {
- schar_copy(linebuf_char[off+col++], sc);
- }
-
- if (text != buf)
- xfree(text);
-
- /*
- * 6. set highlighting for the Visual area an other text.
- * If all folded lines are in the Visual area, highlight the line.
- */
- if (VIsual_active && wp->w_buffer == curwin->w_buffer) {
- if (ltoreq(curwin->w_cursor, VIsual)) {
- /* Visual is after curwin->w_cursor */
- top = &curwin->w_cursor;
- bot = &VIsual;
- } else {
- /* Visual is before curwin->w_cursor */
- top = &VIsual;
- bot = &curwin->w_cursor;
- }
- if (lnum >= top->lnum
- && lnume <= bot->lnum
- && (VIsual_mode != 'v'
- || ((lnum > top->lnum
- || (lnum == top->lnum
- && top->col == 0))
- && (lnume < bot->lnum
- || (lnume == bot->lnum
- && (bot->col - (*p_sel == 'e'))
- >= (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, lnume,
- FALSE))))))) {
- if (VIsual_mode == Ctrl_V) {
- // Visual block mode: highlight the chars part of the block
- if (wp->w_old_cursor_fcol + txtcol < (colnr_T)wp->w_grid.Columns) {
- if (wp->w_old_cursor_lcol != MAXCOL
- && wp->w_old_cursor_lcol + txtcol
- < (colnr_T)wp->w_grid.Columns) {
- len = wp->w_old_cursor_lcol;
- } else {
- len = wp->w_grid.Columns - txtcol;
- }
- RL_MEMSET(wp->w_old_cursor_fcol + txtcol, win_hl_attr(wp, HLF_V),
- len - (int)wp->w_old_cursor_fcol);
- }
- } else {
- // Set all attributes of the text
- RL_MEMSET(txtcol, win_hl_attr(wp, HLF_V), wp->w_grid.Columns - txtcol);
- }
- }
- }
-
- // Show colorcolumn in the fold line, but let cursorcolumn override it.
- if (wp->w_p_cc_cols) {
- int i = 0;
- int j = wp->w_p_cc_cols[i];
- int old_txtcol = txtcol;
-
- while (j > -1) {
- txtcol += j;
- if (wp->w_p_wrap) {
- txtcol -= wp->w_skipcol;
- } else {
- txtcol -= wp->w_leftcol;
- }
- if (txtcol >= 0 && txtcol < wp->w_grid.Columns) {
- linebuf_attr[off + txtcol] =
- hl_combine_attr(linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_MC));
- }
- txtcol = old_txtcol;
- j = wp->w_p_cc_cols[++i];
- }
- }
-
- /* Show 'cursorcolumn' in the fold line. */
- if (wp->w_p_cuc) {
- txtcol += wp->w_virtcol;
- if (wp->w_p_wrap)
- txtcol -= wp->w_skipcol;
- else
- txtcol -= wp->w_leftcol;
- if (txtcol >= 0 && txtcol < wp->w_grid.Columns) {
- linebuf_attr[off + txtcol] = hl_combine_attr(
- linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_CUC));
- }
- }
-
- grid_put_linebuf(&wp->w_grid, row, 0, wp->w_grid.Columns, wp->w_grid.Columns,
- false, wp, wp->w_hl_attr_normal, false);
-
- /*
- * Update w_cline_height and w_cline_folded if the cursor line was
- * updated (saves a call to plines() later).
- */
- if (wp == curwin
- && lnum <= curwin->w_cursor.lnum
- && lnume >= curwin->w_cursor.lnum) {
- curwin->w_cline_row = row;
- curwin->w_cline_height = 1;
- curwin->w_cline_folded = true;
- curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
- conceal_cursor_used = conceal_cursor_line(curwin);
- }
-}
-
-
-/// Copy "buf[len]" to linebuf_char["off"] and set attributes to "attr".
-///
-/// Only works for ASCII text!
-static void copy_text_attr(int off, char_u *buf, int len, int attr)
-{
- int i;
-
- for (i = 0; i < len; i++) {
- schar_from_ascii(linebuf_char[off + i], buf[i]);
- linebuf_attr[off + i] = attr;
- }
-}
/// Fills the foldcolumn at "p" for window "wp".
/// Only to be called when 'foldcolumn' > 0.
@@ -2109,7 +1927,7 @@ static size_t
fill_foldcolumn(
char_u *p,
win_T *wp,
- int closed,
+ foldinfo_T foldinfo,
linenr_T lnum
)
{
@@ -2120,10 +1938,11 @@ fill_foldcolumn(
size_t char_counter = 0;
int symbol = 0;
int len = 0;
+ bool closed = foldinfo.fi_lines > 0;
// Init to all spaces.
memset(p, ' ', MAX_MCO * fdc + 1);
- level = win_foldinfo.fi_level;
+ level = foldinfo.fi_level;
// If the column is too narrow, we start at the lowest level that
// fits and use numbers to indicated the depth.
@@ -2133,8 +1952,8 @@ fill_foldcolumn(
}
for (i = 0; i < MIN(fdc, level); i++) {
- if (win_foldinfo.fi_lnum == lnum
- && first_level + i >= win_foldinfo.fi_low_level) {
+ if (foldinfo.fi_lnum == lnum
+ && first_level + i >= foldinfo.fi_low_level) {
symbol = wp->w_p_fcs_chars.foldopen;
} else if (first_level == 1) {
symbol = wp->w_p_fcs_chars.foldsep;
@@ -2165,22 +1984,23 @@ fill_foldcolumn(
return MAX(char_counter + (fdc-i), (size_t)fdc);
}
-/*
- * Display line "lnum" of window 'wp' on the screen.
- * Start at row "startrow", stop when "endrow" is reached.
- * wp->w_virtcol needs to be valid.
- *
- * Return the number of last row the line occupies.
- */
-static int
-win_line (
- win_T *wp,
- linenr_T lnum,
- int startrow,
- int endrow,
- bool nochange, // not updating for changed text
- bool number_only // only update the number column
-)
+/// Display line "lnum" of window 'wp' on the screen.
+/// wp->w_virtcol needs to be valid.
+///
+/// @param lnum line to display
+/// @param startrow first row relative to window grid
+/// @param endrow last grid row to be redrawn
+/// @param nochange not updating for changed text
+/// @param number_only only update the number column
+/// @param foldinfo fold info for this line
+/// @param[in, out] providers decoration providers active this line
+/// items will be disables if they cause errors
+/// or explicitly return `false`.
+///
+/// @return the number of last row the line occupies.
+static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
+ bool nochange, bool number_only, foldinfo_T foldinfo,
+ Providers *providers)
{
int c = 0; // init for GCC
long vcol = 0; // virtual column (for tabs)
@@ -2284,6 +2104,8 @@ win_line (
bool has_decorations = false; // this buffer has decorations
bool do_virttext = false; // draw virtual text for this line
+ char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext
+
/* draw_state: items that are drawn in sequence: */
#define WL_START 0 /* nothing done yet */
# define WL_CMDLINE WL_START + 1 /* cmdline window column */
@@ -2322,7 +2144,7 @@ win_line (
row = startrow;
- char *luatext = NULL;
+ char *err_text = NULL;
buf_T *buf = wp->w_buffer;
@@ -2347,35 +2169,33 @@ win_line (
}
}
- if (decorations_active) {
- if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) {
- Error err = ERROR_INIT;
+ has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1,
+ &decorations);
+
+ for (size_t k = 0; k < kv_size(*providers); k++) {
+ DecorationProvider *p = kv_A(*providers, k);
+ if (p && p->redraw_line != LUA_NOREF) {
FIXED_TEMP_ARRAY(args, 3);
args.items[0] = WINDOW_OBJ(wp->handle);
args.items[1] = BUFFER_OBJ(buf->handle);
args.items[2] = INTEGER_OBJ(lnum-1);
- lua_attr_active = true;
- extra_check = true;
- Object o = executor_exec_lua_cb(buf->b_luahl_line, "line",
- args, true, &err);
- lua_attr_active = false;
- if (o.type == kObjectTypeString) {
- // TODO(bfredl): this is a bit of a hack. A final API should use an
- // "unified" interface where luahl can add both bufhl and virttext
- luatext = o.data.string.data;
- do_virttext = true;
- } else if (ERROR_SET(&err)) {
- ELOG("error in luahl line: %s", err.msg);
- luatext = err.msg;
- do_virttext = true;
+ if (provider_invoke(p->ns_id, "line", p->redraw_line, args, true)) {
+ has_decorations = true;
+ } else {
+ // return 'false' or error: skip rest of this window
+ kv_A(*providers, k) = NULL;
}
}
+ }
- has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1,
- &decorations);
- if (has_decorations) {
- extra_check = true;
- }
+ if (has_decorations) {
+ extra_check = true;
+ }
+
+ if (provider_first_error) {
+ err_text = provider_first_error;
+ provider_first_error = NULL;
+ do_virttext = true;
}
// Check for columns to display for 'colorcolumn'.
@@ -2554,6 +2374,7 @@ win_line (
}
// If this line has a sign with line highlighting set line_attr.
+ // TODO(bfredl, vigoux): this should not take priority over decorations!
v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1);
if (v != 0) {
line_attr = sign_get_attr((int)v, SIGN_LINEHL);
@@ -2839,7 +2660,7 @@ win_line (
// already be in use.
xfree(p_extra_free);
p_extra_free = xmalloc(MAX_MCO * fdc + 1);
- n_extra = fill_foldcolumn(p_extra_free, wp, false, lnum);
+ n_extra = fill_foldcolumn(p_extra_free, wp, foldinfo, lnum);
p_extra_free[n_extra] = NUL;
p_extra = p_extra_free;
c_extra = NUL;
@@ -3065,6 +2886,51 @@ win_line (
break;
}
+ if (draw_state == WL_LINE
+ && foldinfo.fi_level != 0
+ && foldinfo.fi_lines > 0
+ && vcol == 0
+ && n_extra == 0
+ && row == startrow) {
+ char_attr = win_hl_attr(wp, HLF_FL);
+
+ linenr_T lnume = lnum + foldinfo.fi_lines - 1;
+ memset(buf_fold, ' ', FOLD_TEXT_LEN);
+ p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold);
+ n_extra = STRLEN(p_extra);
+
+ if (p_extra != buf_fold) {
+ xfree(p_extra_free);
+ p_extra_free = p_extra;
+ }
+ c_extra = NUL;
+ c_final = NUL;
+ p_extra[n_extra] = NUL;
+ }
+
+ if (draw_state == WL_LINE
+ && foldinfo.fi_level != 0
+ && foldinfo.fi_lines > 0
+ && col < grid->Columns
+ && n_extra == 0
+ && row == startrow) {
+ // fill rest of line with 'fold'
+ c_extra = wp->w_p_fcs_chars.fold;
+ c_final = NUL;
+
+ n_extra = wp->w_p_rl ? (col + 1) : (grid->Columns - col);
+ }
+
+ if (draw_state == WL_LINE
+ && foldinfo.fi_level != 0
+ && foldinfo.fi_lines > 0
+ && col >= grid->Columns
+ && n_extra != 0
+ && row == startrow) {
+ // Truncate the folding.
+ n_extra = 0;
+ }
+
if (draw_state == WL_LINE && (area_highlighting || has_spell)) {
// handle Visual or match highlighting in this line
if (vcol == fromcol
@@ -3293,6 +3159,10 @@ win_line (
p_extra++;
}
n_extra--;
+ } else if (foldinfo.fi_lines > 0) {
+ // skip writing the buffer line itself
+ c = NUL;
+ XFREE_CLEAR(p_extra_free);
} else {
int c0;
@@ -3764,7 +3634,7 @@ win_line (
mb_utf8 = false; // don't draw as UTF-8
}
} else if (c != NUL) {
- p_extra = transchar(c);
+ p_extra = transchar_buf(wp->w_buffer, c);
if (n_extra == 0) {
n_extra = byte2cells(c) - 1;
}
@@ -3861,7 +3731,7 @@ win_line (
// not showing the '>', put pointer back to avoid getting stuck
ptr++;
}
- }
+ } // end of printing from buffer content
/* In the cursor line and we may be concealing characters: correct
* the cursor column when we reach its position. */
@@ -3918,7 +3788,7 @@ win_line (
}
// At end of the text line or just after the last character.
- if (c == NUL) {
+ if (c == NUL && eol_hl_off == 0) {
long prevcol = (long)(ptr - line) - 1;
// we're not really at that column when skipping some text
@@ -4028,8 +3898,10 @@ win_line (
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
VirtText virt_text = KV_INITIAL_VALUE;
- if (luatext) {
- kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 }));
+ if (err_text) {
+ int hl_err = syn_check_group((char_u *)S_LEN("ErrorMsg"));
+ kv_push(virt_text, ((VirtTextChunk){ .text = err_text,
+ .hl_id = hl_err }));
do_virttext = true;
} else if (has_decorations) {
VirtText *vp = decorations_redraw_virt_text(wp->w_buffer, &decorations);
@@ -4171,11 +4043,10 @@ win_line (
if (wp == curwin && lnum == curwin->w_cursor.lnum) {
curwin->w_cline_row = startrow;
curwin->w_cline_height = row - startrow;
- curwin->w_cline_folded = false;
+ curwin->w_cline_folded = foldinfo.fi_lines > 0;
curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
conceal_cursor_used = conceal_cursor_line(curwin);
}
-
break;
}
@@ -4364,6 +4235,7 @@ win_line (
* so far. If there is no more to display it is caught above.
*/
if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns))
+ && foldinfo.fi_lines == 0
&& (*ptr != NUL
|| filler_todo > 0
|| (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
@@ -4449,7 +4321,7 @@ win_line (
}
xfree(p_extra_free);
- xfree(luatext);
+ xfree(err_text);
return row;
}
@@ -5918,6 +5790,12 @@ next_search_hl (
long nmatched = 0;
int save_called_emsg = called_emsg;
+ // for :{range}s/pat only highlight inside the range
+ if (lnum < search_first_line || lnum > search_last_line) {
+ shl->lnum = 0;
+ return;
+ }
+
if (shl->lnum != 0) {
// Check for three situations:
// 1. If the "lnum" is below a previous match, start a new search.
@@ -6210,7 +6088,7 @@ void win_grid_alloc(win_T *wp)
|| grid->Rows != rows
|| grid->Columns != cols) {
if (want_allocation) {
- grid_alloc(grid, rows, cols, wp->w_grid.valid, wp->w_grid.valid);
+ grid_alloc(grid, rows, cols, wp->w_grid.valid, false);
grid->valid = true;
} else {
// Single grid mode, all rendering will be redirected to default_grid.
diff --git a/src/nvim/search.c b/src/nvim/search.c
index b105d99d7c..9458e6333f 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -96,11 +96,8 @@ static int lastc_bytelen = 1; // >1 for multi-byte char
// copy of spats[], for keeping the search patterns while executing autocmds
static struct spat saved_spats[2];
-// copy of spats[RE_SEARCH], for keeping the search patterns while incremental
-// searching
-static struct spat saved_last_search_spat;
-static int saved_last_idx = 0;
-static bool saved_no_hlsearch = false;
+static int saved_spats_last_idx = 0;
+static bool saved_spats_no_hlsearch = false;
static char_u *mr_pattern = NULL; // pattern used by search_regcomp()
static int mr_pattern_alloced = false; // mr_pattern was allocated
@@ -267,8 +264,8 @@ void save_search_patterns(void)
saved_spats[1] = spats[1];
if (spats[1].pat != NULL)
saved_spats[1].pat = vim_strsave(spats[1].pat);
- saved_last_idx = last_idx;
- saved_no_hlsearch = no_hlsearch;
+ saved_spats_last_idx = last_idx;
+ saved_spats_no_hlsearch = no_hlsearch;
}
}
@@ -280,8 +277,8 @@ void restore_search_patterns(void)
set_vv_searchforward();
free_spat(&spats[1]);
spats[1] = saved_spats[1];
- last_idx = saved_last_idx;
- set_no_hlsearch(saved_no_hlsearch);
+ last_idx = saved_spats_last_idx;
+ set_no_hlsearch(saved_spats_no_hlsearch);
}
}
@@ -308,6 +305,13 @@ void free_search_patterns(void)
#endif
+// copy of spats[RE_SEARCH], for keeping the search patterns while incremental
+// searching
+static struct spat saved_last_search_spat;
+static int did_save_last_search_spat = 0;
+static int saved_last_idx = 0;
+static bool saved_no_hlsearch = false;
+
/// Save and restore the search pattern for incremental highlight search
/// feature.
///
@@ -316,6 +320,11 @@ void free_search_patterns(void)
/// cancelling incremental searching even if it's called inside user functions.
void save_last_search_pattern(void)
{
+ if (++did_save_last_search_spat != 1) {
+ // nested call, nothing to do
+ return;
+ }
+
saved_last_search_spat = spats[RE_SEARCH];
if (spats[RE_SEARCH].pat != NULL) {
saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat);
@@ -326,8 +335,19 @@ void save_last_search_pattern(void)
void restore_last_search_pattern(void)
{
+ if (--did_save_last_search_spat > 0) {
+ // nested call, nothing to do
+ return;
+ }
+ if (did_save_last_search_spat != 0) {
+ iemsg("restore_last_search_pattern() called more often than"
+ " save_last_search_pattern()");
+ return;
+ }
+
xfree(spats[RE_SEARCH].pat);
spats[RE_SEARCH] = saved_last_search_spat;
+ saved_last_search_spat.pat = NULL;
set_vv_searchforward();
last_idx = saved_last_idx;
set_no_hlsearch(saved_no_hlsearch);
@@ -474,7 +494,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast)
saved_spats[idx].pat = NULL;
else
saved_spats[idx].pat = vim_strsave(spats[idx].pat);
- saved_last_idx = last_idx;
+ saved_spats_last_idx = last_idx;
}
/* If 'hlsearch' set and search pat changed: need redraw. */
if (p_hls && idx == last_idx && !no_hlsearch)
@@ -631,6 +651,10 @@ int searchit(
colnr_T col = at_first_line && (options & SEARCH_COL) ? pos->col : 0;
nmatched = vim_regexec_multi(&regmatch, win, buf,
lnum, col, tm, timed_out);
+ // vim_regexec_multi() may clear "regprog"
+ if (regmatch.regprog == NULL) {
+ break;
+ }
// Abort searching on an error (e.g., out of stack).
if (called_emsg || (timed_out != NULL && *timed_out)) {
break;
@@ -702,6 +726,10 @@ int searchit(
match_ok = false;
break;
}
+ // vim_regexec_multi() may clear "regprog"
+ if (regmatch.regprog == NULL) {
+ break;
+ }
matchpos = regmatch.startpos[0];
endpos = regmatch.endpos[0];
submatch = first_submatch(&regmatch);
@@ -791,10 +819,13 @@ int searchit(
}
break;
}
-
- /* Need to get the line pointer again, a
- * multi-line search may have made it invalid. */
- ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
+ // vim_regexec_multi() may clear "regprog"
+ if (regmatch.regprog == NULL) {
+ break;
+ }
+ // Need to get the line pointer again, a
+ // multi-line search may have made it invalid.
+ ptr = ml_get_buf(buf, lnum + matchpos.lnum, false);
}
/*
@@ -871,6 +902,11 @@ int searchit(
}
at_first_line = FALSE;
+ // vim_regexec_multi() may clear "regprog"
+ if (regmatch.regprog == NULL) {
+ break;
+ }
+
// Stop the search if wrapscan isn't set, "stop_lnum" is
// specified, after an interrupt, after a match and after looping
// twice.
@@ -1135,8 +1171,8 @@ int do_search(
pat = p; /* put pat after search command */
}
- if ((options & SEARCH_ECHO) && messaging()
- && !cmd_silent && msg_silent == 0) {
+ if ((options & SEARCH_ECHO) && messaging() && !msg_silent
+ && (!cmd_silent || !shortmess(SHM_SEARCHCOUNT))) {
char_u *trunc;
char_u off_buf[40];
size_t off_len = 0;
@@ -1145,7 +1181,8 @@ int do_search(
msg_start();
// Get the offset, so we know how long it is.
- if (spats[0].off.line || spats[0].off.end || spats[0].off.off) {
+ if (!cmd_silent
+ && (spats[0].off.line || spats[0].off.end || spats[0].off.off)) {
p = off_buf; // -V507
*p++ = dirc;
if (spats[0].off.end) {
@@ -1165,19 +1202,19 @@ int do_search(
}
if (*searchstr == NUL) {
- p = spats[last_idx].pat;
+ p = spats[0].pat;
} else {
p = searchstr;
}
- if (!shortmess(SHM_SEARCHCOUNT)) {
+ if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent) {
// Reserve enough space for the search pattern + offset +
// search stat. Use all the space available, so that the
// search state is right aligned. If there is not enough space
// msg_strtrunc() will shorten in the middle.
if (ui_has(kUIMessages)) {
len = 0; // adjusted below
- } else if (msg_scrolled != 0) {
+ } else if (msg_scrolled != 0 && !cmd_silent) {
// Use all the columns.
len = (Rows - msg_row) * Columns - 1;
} else {
@@ -1194,11 +1231,13 @@ int do_search(
xfree(msgbuf);
msgbuf = xmalloc(len);
- {
- memset(msgbuf, ' ', len);
- msgbuf[0] = dirc;
- msgbuf[len - 1] = NUL;
+ memset(msgbuf, ' ', len);
+ msgbuf[len - 1] = NUL;
+ // do not fill the msgbuf buffer, if cmd_silent is set, leave it
+ // empty for the search_stat feature.
+ if (!cmd_silent) {
+ msgbuf[0] = dirc;
if (utf_iscomposing(utf_ptr2char(p))) {
// Use a space to draw the composing char on.
msgbuf[1] = ' ';
@@ -1342,12 +1381,15 @@ int do_search(
// Show [1/15] if 'S' is not in 'shortmess'.
if ((options & SEARCH_ECHO)
&& messaging()
- && !(cmd_silent + msg_silent)
+ && !msg_silent
&& c != FAIL
&& !shortmess(SHM_SEARCHCOUNT)
&& msgbuf != NULL) {
search_stat(dirc, &pos, show_top_bot_msg, msgbuf,
- (count != 1 || has_offset));
+ (count != 1
+ || has_offset
+ || (!(fdo_flags & FDO_SEARCH)
+ && hasFolding(curwin->w_cursor.lnum, NULL, NULL))));
}
// The search command can be followed by a ';' to do another search.
@@ -1612,8 +1654,9 @@ static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos)
if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col) {
break;
}
- if (*p == ')' && p[delim_len + 1] == '"'
- && STRNCMP(delim_copy, p + 1, delim_len) == 0) {
+ if (*p == ')'
+ && STRNCMP(delim_copy, p + 1, delim_len) == 0
+ && p[delim_len + 1] == '"') {
found = true;
break;
}
@@ -3393,7 +3436,6 @@ current_tagblock(
pos_T start_pos;
pos_T end_pos;
pos_T old_start, old_end;
- char_u *spat, *epat;
char_u *p;
char_u *cp;
int len;
@@ -3447,9 +3489,9 @@ again:
*/
for (long n = 0; n < count; n++) {
if (do_searchpair(
- (char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
- (char_u *)"",
- (char_u *)"</[^>]*>", BACKWARD, NULL, 0,
+ "<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
+ "",
+ "</[^>]*>", BACKWARD, NULL, 0,
NULL, (linenr_T)0, 0L) <= 0) {
curwin->w_cursor = old_pos;
goto theend;
@@ -3471,12 +3513,15 @@ again:
curwin->w_cursor = old_pos;
goto theend;
}
- spat = xmalloc(len + 31);
- epat = xmalloc(len + 9);
- sprintf((char *)spat, "<%.*s\\>\\%%(\\s\\_[^>]\\{-}[^/]>\\|>\\)\\c", len, p);
- sprintf((char *)epat, "</%.*s>\\c", len, p);
-
- const int r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL,
+ const size_t spat_len = len + 39;
+ char *const spat = xmalloc(spat_len);
+ const size_t epat_len = len + 9;
+ char *const epat = xmalloc(epat_len);
+ snprintf(spat, spat_len,
+ "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p);
+ snprintf(epat, epat_len, "</%.*s>\\c", len, p);
+
+ const int r = do_searchpair(spat, "", epat, FORWARD, NULL,
0, NULL, (linenr_T)0, 0L);
xfree(spat);
@@ -4054,7 +4099,7 @@ abort_search:
int
current_search(
long count,
- int forward // true for forward, false for backward
+ bool forward // true for forward, false for backward
)
{
bool old_p_ws = p_ws;
@@ -4069,6 +4114,11 @@ current_search(
pos_T pos; // position after the pattern
int result; // result of various function calls
+ // When searching forward and the cursor is at the start of the Visual
+ // area, skip the first search backward, otherwise it doesn't move.
+ const bool skip_first_backward = forward && VIsual_active
+ && lt(curwin->w_cursor, VIsual);
+
orig_pos = pos = curwin->w_cursor;
if (VIsual_active) {
// Searching further will extend the match.
@@ -4086,13 +4136,20 @@ current_search(
return FAIL; // pattern not found
}
- /*
- * The trick is to first search backwards and then search forward again,
- * so that a match at the current cursor position will be correctly
- * captured.
- */
+ // The trick is to first search backwards and then search forward again,
+ // so that a match at the current cursor position will be correctly
+ // captured. When "forward" is false do it the other way around.
for (int i = 0; i < 2; i++) {
- int dir = forward ? i : !i;
+ int dir;
+ if (forward) {
+ if (i == 0 && skip_first_backward) {
+ continue;
+ }
+ dir = i;
+ } else {
+ dir = !i;
+ }
+
int flags = 0;
if (!dir && !zero_width) {
@@ -4139,11 +4196,17 @@ current_search(
VIsual = start_pos;
}
- // put cursor on last character of match
+ // put the cursor after the match
curwin->w_cursor = end_pos;
if (lt(VIsual, end_pos) && forward) {
- dec_cursor();
- } else if (VIsual_active && lt(curwin->w_cursor, VIsual)) {
+ if (skip_first_backward) {
+ // put the cursor on the start of the match
+ curwin->w_cursor = pos;
+ } else {
+ // put the cursor on last character of match
+ dec_cursor();
+ }
+ } else if (VIsual_active && lt(curwin->w_cursor, VIsual) && forward) {
curwin->w_cursor = pos; // put the cursor on the start of the match
}
VIsual_active = true;
@@ -4216,7 +4279,8 @@ is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction)
if (nmatched != 0) {
break;
}
- } while (direction == FORWARD
+ } while (regmatch.regprog != NULL
+ && direction == FORWARD
? regmatch.startpos[0].col < pos.col
: regmatch.startpos[0].col > pos.col);
@@ -4335,7 +4399,9 @@ static void search_stat(int dirc, pos_T *pos,
len = STRLEN(t);
if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) {
- STRCPY(t + len, " W");
+ memmove(t + 2, t, len);
+ t[0] = 'W';
+ t[1] = ' ';
len += 2;
}
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index aa19d1db1f..2444910bb3 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -4148,7 +4148,7 @@ static inline size_t shada_init_jumps(
}
const char *const fname = (char *) (fm.fmark.fnum == 0
? (fm.fname == NULL ? NULL : fm.fname)
- : buf->b_ffname);
+ : buf ? buf->b_ffname : NULL);
if (fname == NULL) {
continue;
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 95948dac78..1984a357c3 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -362,6 +362,8 @@ size_t spell_check(
size_t wrongcaplen = 0;
int lpi;
bool count_word = docount;
+ bool use_camel_case = *wp->w_s->b_p_spo != NUL;
+ bool camel_case = false;
// A word never starts at a space or a control character. Return quickly
// then, skipping over the character.
@@ -394,9 +396,23 @@ size_t spell_check(
mi.mi_word = ptr;
mi.mi_fend = ptr;
if (spell_iswordp(mi.mi_fend, wp)) {
+ bool this_upper = false; // init for gcc
+
+ if (use_camel_case) {
+ c = PTR2CHAR(mi.mi_fend);
+ this_upper = SPELL_ISUPPER(c);
+ }
+
do {
MB_PTR_ADV(mi.mi_fend);
- } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp));
+ if (use_camel_case) {
+ const bool prev_upper = this_upper;
+ c = PTR2CHAR(mi.mi_fend);
+ this_upper = SPELL_ISUPPER(c);
+ camel_case = !prev_upper && this_upper;
+ }
+ } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp)
+ && !camel_case);
if (capcol != NULL && *capcol == 0 && wp->w_s->b_cap_prog != NULL) {
// Check word starting with capital letter.
@@ -428,6 +444,11 @@ size_t spell_check(
(void)spell_casefold(ptr, (int)(mi.mi_fend - ptr), mi.mi_fword, MAXWLEN + 1);
mi.mi_fwordlen = (int)STRLEN(mi.mi_fword);
+ if (camel_case) {
+ // introduce a fake word end space into the folded word.
+ mi.mi_fword[mi.mi_fwordlen - 1] = ' ';
+ }
+
// The word is bad unless we recognize it.
mi.mi_result = SP_BAD;
mi.mi_result2 = SP_BAD;
@@ -4405,8 +4426,6 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
}
break;
- FALLTHROUGH;
-
case STATE_INS:
// Insert one byte. Repeat this for each possible byte at this
// node.
@@ -5663,6 +5682,9 @@ check_suggestions (
int len;
hlf_T attr;
+ if (gap->ga_len == 0) {
+ return;
+ }
stp = &SUG(*gap, 0);
for (int i = gap->ga_len - 1; i >= 0; --i) {
// Need to append what follows to check for "the the".
@@ -5765,14 +5787,14 @@ cleanup_suggestions (
)
FUNC_ATTR_NONNULL_ALL
{
- suggest_T *stp = &SUG(*gap, 0);
-
if (gap->ga_len > 0) {
// Sort the list.
qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
// Truncate the list to the number of suggestions that will be displayed.
if (gap->ga_len > keep) {
+ suggest_T *const stp = &SUG(*gap, 0);
+
for (int i = keep; i < gap->ga_len; i++) {
xfree(stp[i].st_word);
}
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 6b9348e55d..09d8646c6d 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -628,6 +628,7 @@ spell_load_file (
case SP_OTHERERROR: {
emsgf(_("E5042: Failed to read spell file %s: %s"),
fname, strerror(ferror(fd)));
+ goto endFAIL;
}
case 0: {
break;
@@ -983,15 +984,17 @@ nextone:
static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
{
int cnt = 0;
- int i;
char_u *str;
// read the length bytes, MSB first
- for (i = 0; i < cnt_bytes; ++i)
- cnt = (cnt << 8) + getc(fd);
- if (cnt < 0) {
- *cntp = SP_TRUNCERROR;
- return NULL;
+ for (int i = 0; i < cnt_bytes; i++) {
+ const int c = getc(fd);
+
+ if (c == EOF) {
+ *cntp = SP_TRUNCERROR;
+ return NULL;
+ }
+ cnt = (cnt << 8) + (unsigned)c;
}
*cntp = cnt;
if (cnt == 0)
@@ -3037,9 +3040,9 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
spin->si_msg_count = 999999;
// Read and ignore the first line: word count.
- (void)vim_fgets(line, MAXLINELEN, fd);
- if (!ascii_isdigit(*skipwhite(line)))
+ if (vim_fgets(line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite(line))) {
EMSG2(_("E760: No word count in %s"), fname);
+ }
// Read all the lines in the file one by one.
// The words are converted to 'encoding' here, before being added to
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 4aa7c21ce4..9a9cc45c6b 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -5588,9 +5588,11 @@ void ex_ownsyntax(exarg_T *eap)
hash_init(&curwin->w_s->b_keywtab_ic);
// TODO: Keep the spell checking as it was. NOLINT(readability/todo)
curwin->w_p_spell = false; // No spell checking
+ // make sure option values are "empty_option" instead of NULL
clear_string_option(&curwin->w_s->b_p_spc);
clear_string_option(&curwin->w_s->b_p_spf);
clear_string_option(&curwin->w_s->b_p_spl);
+ clear_string_option(&curwin->w_s->b_p_spo);
clear_string_option(&curwin->w_s->b_syn_isk);
}
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
index 57a8eb57b8..7f6b7dcfec 100644
--- a/src/nvim/testdir/check.vim
+++ b/src/nvim/testdir/check.vim
@@ -1,6 +1,54 @@
source shared.vim
source term_util.vim
+" Command to check for the presence of a feature.
+command -nargs=1 CheckFeature call CheckFeature(<f-args>)
+func CheckFeature(name)
+ if !has(a:name)
+ throw 'Skipped: ' .. a:name .. ' feature missing'
+ endif
+endfunc
+
+" Command to check for the presence of a working option.
+command -nargs=1 CheckOption call CheckOption(<f-args>)
+func CheckOption(name)
+ if !exists('+' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' option not supported'
+ endif
+endfunc
+
+" Command to check for the presence of a function.
+command -nargs=1 CheckFunction call CheckFunction(<f-args>)
+func CheckFunction(name)
+ if !exists('*' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' function missing'
+ endif
+endfunc
+
+" Command to check for the presence of python. Argument should have been
+" obtained with PythonProg()
+func CheckPython(name)
+ if a:name == ''
+ throw 'Skipped: python command not available'
+ endif
+endfunc
+
+" Command to check for running on MS-Windows
+command CheckMSWindows call CheckMSWindows()
+func CheckMSWindows()
+ if !has('win32')
+ throw 'Skipped: only works on MS-Windows'
+ endif
+endfunc
+
+" Command to check for running on Unix
+command CheckUnix call CheckUnix()
+func CheckUnix()
+ if !has('unix')
+ throw 'Skipped: only works on Unix'
+ endif
+endfunc
+
" Command to check that making screendumps is supported.
" Caller must source screendump.vim
command CheckScreendump call CheckScreendump()
@@ -9,3 +57,43 @@ func CheckScreendump()
throw 'Skipped: cannot make screendumps'
endif
endfunc
+
+" Command to check that we can Run Vim in a terminal window
+command CheckRunVimInTerminal call CheckRunVimInTerminal()
+func CheckRunVimInTerminal()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+endfunc
+
+" Command to check that we can run the GUI
+command CheckCanRunGui call CheckCanRunGui()
+func CheckCanRunGui()
+ if !has('gui') || ($DISPLAY == "" && !has('gui_running'))
+ throw 'Skipped: cannot start the GUI'
+ endif
+endfunc
+
+" Command to check that not currently using the GUI
+command CheckNotGui call CheckNotGui()
+func CheckNotGui()
+ if has('gui_running')
+ throw 'Skipped: only works in the terminal'
+ endif
+endfunc
+
+" Command to check that the current language is English
+command CheckEnglish call CheckEnglish()
+func CheckEnglish()
+ if v:lang != "C" && v:lang !~ '^[Ee]n'
+ throw 'Skipped: only works in English language environment'
+ endif
+endfunc
+
+" Command to check for NOT running on MS-Windows
+command CheckNotMSWindows call CheckNotMSWindows()
+func CheckNotMSWindows()
+ if has('win32')
+ throw 'Skipped: does not work on MS-Windows'
+ endif
+endfunc
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 2bf61b0719..b02514143c 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -7,6 +7,19 @@
" ../vim -u NONE -S runtest.vim test_channel.vim open_delay
" The output can be found in the "messages" file.
"
+" If the environment variable $TEST_FILTER is set then only test functions
+" matching this pattern are executed. E.g. for sh/bash:
+" export TEST_FILTER=Test_channel
+" For csh:
+" setenv TEST_FILTER Test_channel
+"
+" To ignore failure for tests that are known to fail in a certain environment,
+" set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for
+" sh/bash:
+" export TEST_MAY_FAIL=Test_channel_one,Test_channel_other
+" The failure report will then not be included in the test.log file and
+" "make test" will not fail.
+"
" The test script may contain anything, only functions that start with
" "Test_" are special. These will be invoked and should contain assert
" functions. See test_assert.vim for an example.
@@ -65,10 +78,17 @@ set encoding=utf-8
let s:test_script_fname = expand('%')
au! SwapExists * call HandleSwapExists()
func HandleSwapExists()
- " Only ignore finding a swap file for the test script (the user might be
+ if exists('g:ignoreSwapExists')
+ return
+ endif
+ " Ignore finding a swap file for the test script (the user might be
" editing it and do ":make test_name") and the output file.
+ " Report finding another swap file and chose 'q' to avoid getting stuck.
if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname
let v:swapchoice = 'e'
+ else
+ call assert_report('Unexpected swap file: ' .. v:swapname)
+ let v:swapchoice = 'q'
endif
endfunc
@@ -84,6 +104,8 @@ let &runtimepath .= ','.expand($BUILD_DIR).'/runtime/'
" Always use forward slashes.
set shellslash
+let s:t_bold = &t_md
+let s:t_normal = &t_me
if has('win32')
" avoid prompt that is long or contains a line break
let $PROMPT = '$P$G'
@@ -136,13 +158,6 @@ func RunTheTest(test)
endtry
endif
- let message = 'Executed ' . a:test
- if has('reltime')
- let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds'
- endif
- call add(s:messages, message)
- let s:done += 1
-
if a:test =~ 'Test_nocatch_'
" Function handles errors itself. This avoids skipping commands after the
" error.
@@ -196,13 +211,34 @@ func RunTheTest(test)
endwhile
exe 'cd ' . save_cwd
+
+ let message = 'Executed ' . a:test
+ if has('reltime')
+ let message ..= repeat(' ', 50 - len(message))
+ let time = reltime(func_start)
+ if has('float') && reltimefloat(time) > 0.1
+ let message = s:t_bold .. message
+ endif
+ let message ..= ' in ' .. reltimestr(time) .. ' seconds'
+ if has('float') && reltimefloat(time) > 0.1
+ let message ..= s:t_normal
+ endif
+ endif
+ call add(s:messages, message)
+ let s:done += 1
endfunc
-func AfterTheTest()
+func AfterTheTest(func_name)
if len(v:errors) > 0
- let s:fail += 1
- call add(s:errors, 'Found errors in ' . s:test . ':')
- call extend(s:errors, v:errors)
+ if match(s:may_fail_list, '^' .. a:func_name) >= 0
+ let s:fail_expected += 1
+ call add(s:errors_expected, 'Found errors in ' . s:test . ':')
+ call extend(s:errors_expected, v:errors)
+ else
+ let s:fail += 1
+ call add(s:errors, 'Found errors in ' . s:test . ':')
+ call extend(s:errors, v:errors)
+ endif
let v:errors = []
endif
endfunc
@@ -218,7 +254,7 @@ endfunc
" This function can be called by a test if it wants to abort testing.
func FinishTesting()
- call AfterTheTest()
+ call AfterTheTest('')
" Don't write viminfo on exit.
set viminfo=
@@ -226,7 +262,7 @@ func FinishTesting()
" Clean up files created by setup.vim
call delete('XfakeHOME', 'rf')
- if s:fail == 0
+ if s:fail == 0 && s:fail_expected == 0
" Success, create the .res file so that make knows it's done.
exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
write
@@ -242,12 +278,21 @@ func FinishTesting()
endif
if s:done == 0
- let message = 'NO tests executed'
+ if s:filtered > 0
+ let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'"
+ else
+ let message = 'NO tests executed'
+ endif
else
+ if s:filtered > 0
+ call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER")
+ endif
let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
endif
- if has('reltime')
+ if s:done > 0 && has('reltime')
+ let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
+ let message ..= s:t_normal
endif
echo message
call add(s:messages, message)
@@ -257,6 +302,12 @@ func FinishTesting()
call add(s:messages, message)
call extend(s:messages, s:errors)
endif
+ if s:fail_expected > 0
+ let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):'
+ echo message
+ call add(s:messages, message)
+ call extend(s:messages, s:errors_expected)
+ endif
" Add SKIPPED messages
call extend(s:messages, s:skipped)
@@ -276,11 +327,13 @@ endfunc
let g:testname = expand('%')
let s:done = 0
let s:fail = 0
+let s:fail_expected = 0
let s:errors = []
+let s:errors_expected = []
let s:messages = []
let s:skipped = []
if expand('%') =~ 'test_vimscript.vim'
- " this test has intentional s:errors, don't use try/catch.
+ " this test has intentional errors, don't use try/catch.
source %
else
try
@@ -311,11 +364,12 @@ 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_double_click_to_create_tab()',
\ 'Test_term_mouse_multiple_clicks_to_visually_select()',
\ 'Test_terminal_composing_unicode()',
\ 'Test_terminal_redir_file()',
\ 'Test_terminal_tmap()',
+ \ 'Test_termwinscroll()',
\ 'Test_with_partial_callback()',
\ ]
@@ -335,8 +389,17 @@ endif
" If the environment variable $TEST_FILTER is set then filter the function
" names against it.
+let s:filtered = 0
if $TEST_FILTER != ''
+ let s:filtered = len(s:tests)
let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER')
+ let s:filtered -= len(s:tests)
+endif
+
+let s:may_fail_list = []
+if $TEST_MAY_FAIL != ''
+ " Split the list at commas and add () to make it match s:test.
+ let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'})
endif
" Execute the tests in alphabetical order.
@@ -388,7 +451,7 @@ for s:test in sort(s:tests)
endwhile
endif
- call AfterTheTest()
+ call AfterTheTest(s:test)
endfor
call FinishTesting()
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index 41ff9b2bd6..4f1ddcd7b9 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -56,6 +56,9 @@ endfunc
" Run "cmd". Returns the job if using a job.
func RunCommand(cmd)
+ " Running an external command can occasionally be slow or fail.
+ let g:test_is_flaky = 1
+
let job = 0
if has('job')
let job = job_start(a:cmd, {"stoponexit": "hup"})
@@ -271,7 +274,7 @@ func GetVimCommand(...)
let cmd = cmd . ' -u ' . name
endif
let cmd .= ' --headless -i NONE'
- let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '')
+ let cmd = substitute(cmd, 'VIMRUNTIME=\S\+', '', '')
" If using valgrind, make sure every run uses a different log file.
if cmd =~ 'valgrind.*--log-file='
@@ -294,6 +297,13 @@ func GetVimCommandClean()
return cmd
endfunc
+" Get the command to run Vim, with --clean, and force to run in terminal so it
+" won't start a new GUI.
+func GetVimCommandCleanTerm()
+ " Add -v to have gvim run in the terminal (if possible)
+ return GetVimCommandClean() .. ' -v '
+endfunc
+
" Run Vim, using the "vimcmd" file and "-u NORC".
" "before" is a list of Vim commands to be executed before loading plugins.
" "after" is a list of Vim commands to be executed after loading plugins.
@@ -330,6 +340,16 @@ func RunVimPiped(before, after, arguments, pipecmd)
return 1
endfunc
-func CanRunGui()
- return has('gui') && ($DISPLAY != "" || has('gui_running'))
+" Get all messages but drop the maintainer entry.
+func GetMessages()
+ redir => result
+ redraw | messages
+ redir END
+ let msg_list = split(result, "\n")
+ " if msg_list->len() > 0 && msg_list[0] =~ 'Messages maintainer:'
+ " return msg_list[1:]
+ " endif
+ return msg_list
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/summarize.vim b/src/nvim/testdir/summarize.vim
index 7f8f758a71..da5856a2e7 100644
--- a/src/nvim/testdir/summarize.vim
+++ b/src/nvim/testdir/summarize.vim
@@ -1,3 +1,4 @@
+set cpo&vim
if 1
" This is executed only with the eval feature
set nocompatible
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 5668f45dea..7647475427 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -13,7 +13,6 @@ source test_ex_undo.vim
source test_ex_z.vim
source test_execute_func.vim
source test_expand_func.vim
-source test_expr.vim
source test_feedkeys.vim
source test_filter_cmd.vim
source test_filter_map.vim
@@ -50,6 +49,7 @@ source test_tagjump.vim
source test_taglist.vim
source test_true_false.vim
source test_unlet.vim
+source test_version.vim
source test_virtualedit.vim
source test_window_cmd.vim
source test_wnext.vim
diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
index 6e7583ade3..92fedf9bfb 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/src/nvim/testdir/test_arglist.vim
@@ -397,9 +397,15 @@ func Test_argdelete()
last
argdelete %
call assert_equal(['b'], argv())
- call assert_fails('argdelete', 'E471:')
+ call assert_fails('argdelete', 'E610:')
call assert_fails('1,100argdelete', 'E16:')
- %argd
+
+ call Reset_arglist()
+ args a b c d
+ next
+ argdel
+ call Assert_argc(['a', 'c', 'd'])
+ %argdel
endfunc
func Test_argdelete_completion()
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index d116246ef3..04a678eeb8 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -1,6 +1,8 @@
" Tests for autocommands
source shared.vim
+source check.vim
+source term_util.vim
func! s:cleanup_buffers() abort
for bnr in range(1, bufnr('$'))
@@ -1125,7 +1127,7 @@ func Test_change_mark_in_autocmds()
write
au! BufWritePre
- if executable('cat')
+ if has('unix')
write XtestFilter
write >> XtestFilter
@@ -1735,6 +1737,35 @@ func Test_throw_in_BufWritePre()
au! throwing
endfunc
+func Test_autocmd_CmdWinEnter()
+ CheckRunVimInTerminal
+ " There is not cmdwin switch, so
+ " test for cmdline_hist
+ " (both are available with small builds)
+ CheckFeature cmdline_hist
+ let lines =<< trim END
+ let b:dummy_var = 'This is a dummy'
+ autocmd CmdWinEnter * quit
+ let winnr = winnr('$')
+ END
+ let filename='XCmdWinEnter'
+ call writefile(lines, filename)
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 6})
+
+ call term_sendkeys(buf, "q:")
+ call term_wait(buf)
+ call term_sendkeys(buf, ":echo b:dummy_var\<cr>")
+ call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 1000)
+ call term_sendkeys(buf, ":echo &buftype\<cr>")
+ call WaitForAssert({-> assert_notmatch('^nofile', term_getline(buf, 6))}, 1000)
+ call term_sendkeys(buf, ":echo winnr\<cr>")
+ call WaitForAssert({-> assert_match('^1', term_getline(buf, 6))}, 1000)
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete(filename)
+endfunc
+
func Test_FileChangedShell_reload()
if !has('unix')
return
@@ -1866,4 +1897,17 @@ func Test_autocmd_FileReadCmd()
delfunc ReadFileCmd
endfunc
+" Tests for SigUSR1 autocmd event, which is only available on posix systems.
+func Test_autocmd_sigusr1()
+ CheckUnix
+
+ let g:sigusr1_passed = 0
+ au Signal SIGUSR1 let g:sigusr1_passed = 1
+ call system('/bin/kill -s usr1 ' . getpid())
+ call WaitForAssert({-> assert_true(g:sigusr1_passed)})
+
+ au! Signal
+ unlet g:sigusr1_passed
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cjk_linebreak.vim b/src/nvim/testdir/test_cjk_linebreak.vim
new file mode 100644
index 0000000000..dfaa8fa1af
--- /dev/null
+++ b/src/nvim/testdir/test_cjk_linebreak.vim
@@ -0,0 +1,97 @@
+scriptencoding utf-8
+
+func Run_cjk_linebreak_after(rigorous)
+ set textwidth=12
+ for punct in [
+ \ '!', '%', ')', ',', ':', ';', '>', '?', ']', '}', '’', '”', '†', '‡',
+ \ '…', '‰', '‱', '‼', '⁇', '⁈', '⁉', '℃', '℉', '、', '。', '〉', '》',
+ \ '」', '』', '】', '〕', '〗', '〙', '〛', '!', ')', ',', '.', ':',
+ \ ';', '?', ']', '}']
+ call setline('.', '这是一个测试' .. punct.'试试 CJK 行禁则补丁。')
+ normal gqq
+ if a:rigorous
+ call assert_equal('这是一个测', getline(1))
+ else
+ call assert_equal('这是一个测试' .. punct, getline(1))
+ endif
+ %d_
+ endfor
+endfunc
+
+func Test_cjk_linebreak_after()
+ set formatoptions=croqn2mB1j
+ call Run_cjk_linebreak_after(0)
+endfunc
+
+func Test_cjk_linebreak_after_rigorous()
+ set formatoptions=croqn2mB1j]
+ call Run_cjk_linebreak_after(1)
+endfunc
+
+func Run_cjk_linebreak_before()
+ set textwidth=12
+ for punct in [
+ \ '(', '<', '[', '`', '{', '‘', '“', '〈', '《', '「', '『', '【', '〔',
+ \ '〖', '〘', '〚', '(', '[', '{']
+ call setline('.', '这是个测试' .. punct.'试试 CJK 行禁则补丁。')
+ normal gqq
+ call assert_equal('这是个测试', getline(1))
+ %d_
+ endfor
+endfunc
+
+func Test_cjk_linebreak_before()
+ set formatoptions=croqn2mB1j
+ call Run_cjk_linebreak_before()
+endfunc
+
+func Test_cjk_linebreak_before_rigorous()
+ set formatoptions=croqn2mB1j]
+ call Run_cjk_linebreak_before()
+endfunc
+
+func Run_cjk_linebreak_nobetween(rigorous)
+ " …… must not start a line
+ call setline('.', '这是个测试……试试 CJK 行禁则补丁。')
+ set textwidth=12 ambiwidth=double
+ normal gqq
+ if a:rigorous
+ call assert_equal('这是个测', getline(1))
+ else
+ call assert_equal('这是个测试……', getline(1))
+ endif
+ %d_
+
+ call setline('.', '这是一个测试……试试 CJK 行禁则补丁。')
+ set textwidth=12 ambiwidth=double
+ normal gqq
+ call assert_equal('这是一个测', getline(1))
+ %d_
+
+ " but —— can
+ call setline('.', '这是个测试——试试 CJK 行禁则补丁。')
+ set textwidth=12 ambiwidth=double
+ normal gqq
+ call assert_equal('这是个测试', getline(1))
+endfunc
+
+func Test_cjk_linebreak_nobetween()
+ set formatoptions=croqn2mB1j
+ call Run_cjk_linebreak_nobetween(0)
+endfunc
+
+func Test_cjk_linebreak_nobetween_rigorous()
+ set formatoptions=croqn2mB1j]
+ call Run_cjk_linebreak_nobetween(1)
+endfunc
+
+func Test_cjk_linebreak_join_punct()
+ for punct in ['——', '〗', ',', '。', '……']
+ call setline(1, '文本文本' .. punct)
+ call setline(2, 'English')
+ set formatoptions=croqn2mB1j
+ normal ggJ
+ call assert_equal('文本文本' .. punct.'English', getline(1))
+ %d_
+ endfor
+endfunc
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index f8d84f1a49..871143699a 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -1,5 +1,8 @@
" Tests for editing the command line.
+source check.vim
+source screendump.vim
+
func Test_complete_tab()
call writefile(['testfile'], 'Xtestfile')
call feedkeys(":e Xtestf\t\r", "tx")
@@ -718,6 +721,27 @@ func Test_verbosefile()
call delete('Xlog')
endfunc
+func Test_verbose_option()
+ " See test/functional/ui/cmdline_spec.lua
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ command DoSomething echo 'hello' |set ts=4 |let v = '123' |echo v
+ call feedkeys("\r", 't') " for the hit-enter prompt
+ set verbose=20
+ [SCRIPT]
+ call writefile(lines, 'XTest_verbose')
+
+ let buf = RunVimInTerminal('-S XTest_verbose', {'rows': 12})
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, ":DoSomething\<CR>")
+ call VerifyScreenDump(buf, 'Test_verbose_option_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_verbose')
+endfunc
+
func Test_setcmdpos()
func InsertTextAtPos(text, pos)
call assert_equal(0, setcmdpos(a:pos))
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index 6bb602717f..9101f8cfa0 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -42,12 +42,12 @@ func Test_compiler_without_arg()
let a = split(execute('compiler'))
call assert_match(runtime .. '/compiler/ant.vim$', a[0])
call assert_match(runtime .. '/compiler/bcc.vim$', a[1])
- call assert_match(runtime .. '/compiler/xmlwf.vim$', a[-1])
+ call assert_match(runtime .. '/compiler/xo.vim$', a[-1])
endfunc
func Test_compiler_completion()
call feedkeys(":compiler \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^"compiler ant bcc .* xmlwf$', @:)
+ call assert_match('^"compiler ant bcc .* xmlwf xo$', @:)
call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"compiler pbx perl php pylint pyunit', @:)
diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim
index eaf200e9bb..fc7ea71f6e 100644
--- a/src/nvim/testdir/test_const.vim
+++ b/src/nvim/testdir/test_const.vim
@@ -36,6 +36,7 @@ func Test_define_var_with_lock()
call assert_fails('let s = "vim"', 'E741:')
call assert_fails('let F = funcref("s:noop")', 'E741:')
call assert_fails('let l = [1, 2, 3]', 'E741:')
+ call assert_fails('call filter(l, "v:val % 2 == 0")', 'E741:')
call assert_fails('let d = {"foo": 10}', 'E741:')
if has('channel')
call assert_fails('let j = test_null_job()', 'E741:')
@@ -247,11 +248,14 @@ func Test_lock_depth_is_1()
const l = [1, 2, 3]
const d = {'foo': 10}
- " Modify list
- call add(l, 4)
+ " Modify list - setting item is OK, adding/removing items not
let l[0] = 42
+ call assert_fails('call add(l, 4)', 'E741:')
+ call assert_fails('unlet l[1]', 'E741:')
- " Modify dict
- let d['bar'] = 'hello'
+ " Modify dict - changing item is OK, adding/removing items not
+ let d['foo'] = 'hello'
let d.foo = 44
+ call assert_fails("let d['bar'] = 'hello'", 'E741:')
+ call assert_fails("unlet d['foo']", 'E741:')
endfunc
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
index 811717208e..59d51b855b 100644
--- a/src/nvim/testdir/test_debugger.vim
+++ b/src/nvim/testdir/test_debugger.vim
@@ -316,3 +316,128 @@ func Test_Debugger()
call delete('Xtest.vim')
endfunc
+
+" Test for setting a breakpoint on a :endif where the :if condition is false
+" and then quit the script. This should generate an interrupt.
+func Test_breakpt_endif_intr()
+ func F()
+ let g:Xpath ..= 'a'
+ if v:false
+ let g:Xpath ..= 'b'
+ endif
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ call F()
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" Test for setting a breakpoint on a :else where the :if condition is false
+" and then quit the script. This should generate an interrupt.
+func Test_breakpt_else_intr()
+ func F()
+ let g:Xpath ..= 'a'
+ if v:false
+ let g:Xpath ..= 'b'
+ else
+ invalid_command
+ endif
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ call F()
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" Test for setting a breakpoint on a :endwhile where the :while condition is
+" false and then quit the script. This should generate an interrupt.
+func Test_breakpt_endwhile_intr()
+ func F()
+ let g:Xpath ..= 'a'
+ while v:false
+ let g:Xpath ..= 'b'
+ endwhile
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ call F()
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" Test for setting a breakpoint on an :endtry where an exception is pending to
+" be processed and then quit the script. This should generate an interrupt and
+" the thrown exception should be ignored.
+func Test_breakpt_endtry_intr()
+ func F()
+ try
+ let g:Xpath ..= 'a'
+ throw "abc"
+ endtry
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ let caught_abc = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ call F()
+ catch /abc/
+ let caught_abc = 1
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal(0, caught_abc)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index a1f1dd3bab..f09a64c329 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -724,10 +724,140 @@ func Test_diff_lastline()
bwipe!
endfunc
+func Test_diff_screen()
+ CheckScreendump
+ CheckFeature menu
+
+ " clean up already existing swap files, just in case
+ call delete('.Xfile1.swp')
+ call delete('.Xfile2.swp')
+
+ " Test 1: Add a line in beginning of file 2
+ call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ let buf = RunVimInTerminal('-d Xfile1 Xfile2', {})
+ " Set autoread mode, so that Vim won't complain once we re-write the test
+ " files
+ call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w")
+
+ call VerifyBoth(buf, 'Test_diff_01', '')
+
+ " Test 2: Add a line in beginning of file 1
+ call WriteDiffFiles(buf, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_02', '')
+
+ " Test 3: Add a line at the end of file 2
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+ call VerifyBoth(buf, 'Test_diff_03', '')
+
+ " Test 4: Add a line at the end of file 1
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_04', '')
+
+ " Test 5: Add a line in the middle of file 2, remove on at the end of file 1
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_05', '')
+
+ " Test 6: Add a line in the middle of file 1, remove on at the end of file 2
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+ call VerifyBoth(buf, 'Test_diff_06', '')
+
+ " Variants on test 6 with different context settings
+ call term_sendkeys(buf, ":set diffopt+=context:2\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.2', {})
+ call term_sendkeys(buf, ":set diffopt-=context:2\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=context:1\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.1', {})
+ call term_sendkeys(buf, ":set diffopt-=context:1\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=context:0\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.0', {})
+ call term_sendkeys(buf, ":set diffopt-=context:0\<cr>")
+
+ " Test 7 - 9: Test normal/patience/histogram diff algorithm
+ call WriteDiffFiles(buf, ['#include <stdio.h>', '', '// Frobs foo heartily', 'int frobnitz(int foo)', '{',
+ \ ' int i;', ' for(i = 0; i < 10; i++)', ' {', ' printf("Your answer is: ");',
+ \ ' printf("%d\n", foo);', ' }', '}', '', 'int fact(int n)', '{', ' if(n > 1)', ' {',
+ \ ' return fact(n-1) * n;', ' }', ' return 1;', '}', '', 'int main(int argc, char **argv)',
+ \ '{', ' frobnitz(fact(10));', '}'],
+ \ ['#include <stdio.h>', '', 'int fib(int n)', '{', ' if(n > 2)', ' {',
+ \ ' return fib(n-1) + fib(n-2);', ' }', ' return 1;', '}', '', '// Frobs foo heartily',
+ \ 'int frobnitz(int foo)', '{', ' int i;', ' for(i = 0; i < 10; i++)', ' {',
+ \ ' printf("%d\n", foo);', ' }', '}', '',
+ \ 'int main(int argc, char **argv)', '{', ' frobnitz(fib(10));', '}'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_07', {})
+
+ call term_sendkeys(buf, ":set diffopt+=algorithm:patience\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_08', {})
+
+ call term_sendkeys(buf, ":set diffopt+=algorithm:histogram\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_09', {})
+
+ " Test 10-11: normal/indent-heuristic
+ call term_sendkeys(buf, ":set diffopt&vim\<cr>")
+ call WriteDiffFiles(buf, ['', ' def finalize(values)', '', ' values.each do |v|', ' v.finalize', ' end'],
+ \ ['', ' def finalize(values)', '', ' values.each do |v|', ' v.prepare', ' end', '',
+ \ ' values.each do |v|', ' v.finalize', ' end'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_10', {})
+
+ " Leave trailing : at commandline!
+ call term_sendkeys(buf, ":set diffopt+=indent-heuristic\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'one')
+ " shouldn't matter, if indent-algorithm comes before or after the algorithm
+ call term_sendkeys(buf, ":set diffopt&\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=indent-heuristic,algorithm:patience\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'two')
+ call term_sendkeys(buf, ":set diffopt&\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=algorithm:patience,indent-heuristic\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'three')
+
+ " Test 12: diff the same file
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_12', '')
+
+ " Test 13: diff an empty file
+ call WriteDiffFiles(buf, [], [])
+ call VerifyBoth(buf, 'Test_diff_13', '')
+
+ " Test 14: test diffopt+=icase
+ call WriteDiffFiles(buf, ['a', 'b', 'cd'], ['A', 'b', 'cDe'])
+ call VerifyBoth(buf, 'Test_diff_14', " diffopt+=filler diffopt+=icase")
+
+ " Test 15-16: test diffopt+=iwhite
+ call WriteDiffFiles(buf, ['int main()', '{', ' printf("Hello, World!");', ' return 0;', '}'],
+ \ ['int main()', '{', ' if (0)', ' {', ' printf("Hello, World!");', ' return 0;', ' }', '}'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt&vim diffopt+=filler diffopt+=iwhite\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_15', {})
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_16', {})
+
+ " Test 17: test diffopt+=iblank
+ call WriteDiffFiles(buf, ['a', ' ', 'cd', 'ef', 'xxx'], ['a', 'cd', '', 'ef', 'yyy'])
+ call VerifyInternal(buf, 'Test_diff_17', " diffopt+=iblank")
+
+ " Test 18: test diffopt+=iblank,iwhite / iwhiteall / iwhiteeol
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhite")
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteall")
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteeol")
+
+ " Test 19: test diffopt+=iwhiteeol
+ call WriteDiffFiles(buf, ['a ', 'x', 'cd', 'ef', 'xx xx', 'foo', 'bar'], ['a', 'x', 'c d', ' ef', 'xx xx', 'foo', '', 'bar'])
+ call VerifyInternal(buf, 'Test_diff_19', " diffopt+=iwhiteeol")
+
+ " Test 19: test diffopt+=iwhiteall
+ call VerifyInternal(buf, 'Test_diff_20', " diffopt+=iwhiteall")
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xfile1')
+ call delete('Xfile2')
+endfunc
+
func Test_diff_with_cursorline()
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
+ CheckScreendump
call writefile([
\ 'hi CursorLine ctermbg=red ctermfg=white',
@@ -751,13 +881,45 @@ func Test_diff_with_cursorline()
call delete('Xtest_diff_cursorline')
endfunc
+func Test_diff_with_syntax()
+ CheckScreendump
+
+ let lines =<< trim END
+ void doNothing() {
+ int x = 0;
+ char *s = "hello";
+ return 5;
+ }
+ END
+ call writefile(lines, 'Xprogram1.c')
+ let lines =<< trim END
+ void doSomething() {
+ int x = 0;
+ char *s = "there";
+ return 5;
+ }
+ END
+ call writefile(lines, 'Xprogram2.c')
+
+ let lines =<< trim END
+ edit Xprogram1.c
+ diffsplit Xprogram2.c
+ END
+ call writefile(lines, 'Xtest_diff_syntax')
+ let buf = RunVimInTerminal('-S Xtest_diff_syntax', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_syntax_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_diff_syntax')
+ call delete('Xprogram1.c')
+ call delete('Xprogram2.c')
+endfunc
+
func Test_diff_of_diff()
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
- if !has("rightleft")
- throw 'Skipped: rightleft not supported'
- endif
+ CheckScreendump
+ CheckFeature rightleft
call writefile([
\ 'call setline(1, ["aa","bb","cc","@@ -3,2 +5,7 @@","dd","ee","ff"])',
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
index 1c2f5a05ff..c702b44b88 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/src/nvim/testdir/test_display.vim
@@ -6,11 +6,12 @@
" endif
source view_util.vim
+source check.vim
+source screendump.vim
+
+func Test_display_foldcolumn()
+ CheckFeature folding
-func! Test_display_foldcolumn()
- if !has("folding")
- return
- endif
new
vnew
vert resize 25
@@ -26,10 +27,10 @@ func! Test_display_foldcolumn()
call cursor(2, 1)
norm! zt
- let lines=ScreenLines([1,2], winwidth(0))
+ let lines = ScreenLines([1,2], winwidth(0))
call assert_equal(expect, lines)
set fdc=2
- let lines=ScreenLines([1,2], winwidth(0))
+ let lines = ScreenLines([1,2], winwidth(0))
let expect = [
\ " e more noise blah blah<",
\ " 82> more stuff here "
@@ -41,9 +42,8 @@ func! Test_display_foldcolumn()
endfunc
func! Test_display_foldtext_mbyte()
- if !has("folding")
- return
- endif
+ CheckFeature folding
+
call NewWindow(10, 40)
call append(0, range(1,20))
exe "set foldmethod=manual foldtext=foldtext() fillchars=fold:\u2500,vert:\u2502 fdc=2"
@@ -70,6 +70,42 @@ func! Test_display_foldtext_mbyte()
bw!
endfunc
+" check that win_ins_lines() and win_del_lines() work when t_cs is empty.
+func Test_scroll_without_region()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ set t_cs=
+ set laststatus=2
+ END
+ call writefile(lines, 'Xtestscroll')
+ let buf = RunVimInTerminal('-S Xtestscroll', #{rows: 10})
+
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_1', {})
+
+ call term_sendkeys(buf, ":3delete\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_2', {})
+
+ call term_sendkeys(buf, ":4put\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_3', {})
+
+ call term_sendkeys(buf, ":undo\<cr>")
+ call term_sendkeys(buf, ":undo\<cr>")
+ call term_sendkeys(buf, ":set laststatus=0\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_4', {})
+
+ call term_sendkeys(buf, ":3delete\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_5', {})
+
+ call term_sendkeys(buf, ":4put\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_6', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtestscroll')
+endfunc
+
func Test_display_listchars_precedes()
set fillchars+=vert:\|
call NewWindow(10, 10)
@@ -125,3 +161,104 @@ func Test_display_listchars_precedes()
set list& listchars& wrap&
bw!
endfunc
+
+" Check that win_lines() works correctly with the number_only parameter=TRUE
+" should break early to optimize cost of drawing, but needs to make sure
+" that the number column is correctly highlighted.
+func Test_scroll_CursorLineNr_update()
+ CheckScreendump
+
+ let lines =<< trim END
+ hi CursorLineNr ctermfg=73 ctermbg=236
+ set nu rnu cursorline cursorlineopt=number
+ exe ":norm! o\<esc>110ia\<esc>"
+ END
+ let filename = 'Xdrawscreen'
+ call writefile(lines, filename)
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 5, cols: 50})
+ call term_sendkeys(buf, "k")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_winline_rnu', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete(filename)
+endfunc
+
+" check a long file name does not result in the hit-enter prompt
+func Test_edit_long_file_name()
+ CheckScreendump
+
+ let longName = 'x'->repeat(min([&columns, 255]))
+ call writefile([], longName)
+ let buf = RunVimInTerminal('-N -u NONE ' .. longName, #{rows: 8})
+
+ call VerifyScreenDump(buf, 'Test_long_file_name_1', {})
+
+ call term_sendkeys(buf, ":q\<cr>")
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete(longName)
+endfunc
+
+func Test_unprintable_fileformats()
+ CheckScreendump
+
+ call writefile(["unix\r", "two"], 'Xunix.txt')
+ call writefile(["mac\r", "two"], 'Xmac.txt')
+ let lines =<< trim END
+ edit Xunix.txt
+ split Xmac.txt
+ edit ++ff=mac
+ END
+ let filename = 'Xunprintable'
+ call writefile(lines, filename)
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 9, cols: 50})
+ call VerifyScreenDump(buf, 'Test_display_unprintable_01', {})
+ call term_sendkeys(buf, "\<C-W>\<C-W>\<C-L>")
+ call VerifyScreenDump(buf, 'Test_display_unprintable_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xunix.txt')
+ call delete('Xmac.txt')
+ call delete(filename)
+endfunc
+
+" Test for scrolling that modifies buffer during visual block
+func Test_visual_block_scroll()
+ " See test/functional/legacy/visual_mode_spec.lua
+ CheckScreendump
+
+ let lines =<< trim END
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set scrolloff=1
+ call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'])
+ call cursor(5, 1)
+ END
+
+ let filename = 'Xvisualblockmodifiedscroll'
+ call writefile(lines, filename)
+
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 7})
+ call term_sendkeys(buf, "V\<C-D>\<C-D>")
+
+ call VerifyScreenDump(buf, 'Test_display_visual_block_scroll', {})
+
+ call StopVimInTerminal(buf)
+ call delete(filename)
+endfunc
+
+func Test_display_scroll_at_topline()
+ " See test/functional/legacy/display_spec.lua
+ CheckScreendump
+
+ let buf = RunVimInTerminal('', #{cols: 20})
+ call term_sendkeys(buf, ":call setline(1, repeat('a', 21))\<CR>")
+ call term_wait(buf)
+ call term_sendkeys(buf, "O\<Esc>")
+ call VerifyScreenDump(buf, 'Test_display_scroll_at_topline', #{rows: 4})
+
+ call StopVimInTerminal(buf)
+endfunc
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index 12d5d9790e..15c718b243 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -1,9 +1,11 @@
" Test for edit functions
-"
+
if exists("+t_kD")
let &t_kD="[3;*~"
endif
+source check.vim
+
" Needed for testing basic rightleft: Test_edit_rightleft
source view_util.vim
@@ -733,17 +735,16 @@ func! Test_edit_CTRL_O()
endfunc
func! Test_edit_CTRL_R()
- throw 'skipped: Nvim does not support test_override()'
" Insert Register
new
- call test_override("ALL", 1)
+ " call test_override("ALL", 1)
set showcmd
call feedkeys("AFOOBAR eins zwei\<esc>", 'tnix')
call feedkeys("O\<c-r>.", 'tnix')
call feedkeys("O\<c-r>=10*500\<cr>\<esc>", 'tnix')
call feedkeys("O\<c-r>=getreg('=', 1)\<cr>\<esc>", 'tnix')
call assert_equal(["getreg('=', 1)", '5000', "FOOBAR eins zwei", "FOOBAR eins zwei"], getline(1, '$'))
- call test_override("ALL", 0)
+ " call test_override("ALL", 0)
set noshowcmd
bw!
endfunc
@@ -955,7 +956,6 @@ func! Test_edit_DROP()
endfunc
func! Test_edit_CTRL_V()
- throw 'skipped: Nvim does not support test_override()'
if has("ebcdic")
return
endif
@@ -965,7 +965,7 @@ func! Test_edit_CTRL_V()
" force some redraws
set showmode showcmd
"call test_override_char_avail(1)
- call test_override('ALL', 1)
+ " call test_override('ALL', 1)
call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix')
call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$'))
@@ -978,7 +978,7 @@ func! Test_edit_CTRL_V()
set norl
endif
- call test_override('ALL', 0)
+ " call test_override('ALL', 0)
set noshowmode showcmd
bw!
endfunc
@@ -1441,31 +1441,40 @@ endfunc
func Test_edit_InsertLeave()
new
+ au InsertLeavePre * let g:did_au_pre = 1
au InsertLeave * let g:did_au = 1
+ let g:did_au_pre = 0
let g:did_au = 0
call feedkeys("afoo\<Esc>", 'tx')
+ call assert_equal(1, g:did_au_pre)
call assert_equal(1, g:did_au)
call assert_equal('foo', getline(1))
+ let g:did_au_pre = 0
let g:did_au = 0
call feedkeys("Sbar\<C-C>", 'tx')
+ call assert_equal(1, g:did_au_pre)
call assert_equal(0, g:did_au)
call assert_equal('bar', getline(1))
inoremap x xx<Esc>
+ let g:did_au_pre = 0
let g:did_au = 0
call feedkeys("Saax", 'tx')
+ call assert_equal(1, g:did_au_pre)
call assert_equal(1, g:did_au)
call assert_equal('aaxx', getline(1))
inoremap x xx<C-C>
+ let g:did_au_pre = 0
let g:did_au = 0
call feedkeys("Sbbx", 'tx')
+ call assert_equal(1, g:did_au_pre)
call assert_equal(0, g:did_au)
call assert_equal('bbxx', getline(1))
bwipe!
- au! InsertLeave
+ au! InsertLeave InsertLeavePre
iunmap x
endfunc
@@ -1514,3 +1523,68 @@ func Test_edit_startinsert()
set backspace&
bwipe!
endfunc
+
+func Test_edit_noesckeys()
+ CheckNotGui
+ new
+
+ " <Left> moves cursor when 'esckeys' is set
+ exe "set t_kl=\<Esc>OD"
+ " set esckeys
+ call feedkeys("axyz\<Esc>ODX", "xt")
+ " call assert_equal("xyXz", getline(1))
+
+ " <Left> exits Insert mode when 'esckeys' is off
+ " set noesckeys
+ call setline(1, '')
+ call feedkeys("axyz\<Esc>ODX", "xt")
+ call assert_equal(["DX", "xyz"], getline(1, 2))
+
+ bwipe!
+ " set esckeys
+endfunc
+
+" Test for editing a directory
+func Test_edit_is_a_directory()
+ CheckEnglish
+ let dirname = getcwd() . "/Xdir"
+ call mkdir(dirname, 'p')
+
+ new
+ redir => msg
+ exe 'edit' dirname
+ redir END
+ call assert_match("is a directory$", split(msg, "\n")[0])
+ bwipe!
+
+ let dirname .= '/'
+
+ new
+ redir => msg
+ exe 'edit' dirname
+ redir END
+ call assert_match("is a directory$", split(msg, "\n")[0])
+ bwipe!
+
+ call delete(dirname, 'rf')
+endfunc
+
+func Test_edit_browse()
+ " in the GUI this opens a file picker, we only test the terminal behavior
+ CheckNotGui
+
+ " ":browse xxx" checks for the FileExplorer augroup and assumes editing "."
+ " works then.
+ augroup FileExplorer
+ au!
+ augroup END
+
+ " When the USE_FNAME_CASE is defined this used to cause a crash.
+ browse enew
+ bwipe!
+
+ browse split
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim
index 21bb09a690..a25d83753c 100644
--- a/src/nvim/testdir/test_environ.vim
+++ b/src/nvim/testdir/test_environ.vim
@@ -1,5 +1,9 @@
+" Test for environment variables.
+
scriptencoding utf-8
+source check.vim
+
func Test_environ()
unlet! $TESTENV
call assert_equal(0, has_key(environ(), 'TESTENV'))
@@ -42,3 +46,24 @@ func Test_external_env()
endif
call assert_equal('', result)
endfunc
+
+func Test_mac_locale()
+ CheckFeature osxdarwin
+
+ " If $LANG is not set then the system locale will be used.
+ " Run Vim after unsetting all the locale environmental vars, and capture the
+ " output of :lang.
+ let lang_results = system("unset LANG; unset LC_MESSAGES; " ..
+ \ shellescape(v:progpath) ..
+ \ " --clean -esX -c 'redir @a' -c 'lang' -c 'put a' -c 'print' -c 'qa!' ")
+
+ " Check that:
+ " 1. The locale is the form of <locale>.UTF-8.
+ " 2. Check that fourth item (LC_NUMERIC) is properly set to "C".
+ " Example match: "en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8"
+ call assert_match('"\([a-zA-Z_]\+\.UTF-8/\)\{3}C\(/[a-zA-Z_]\+\.UTF-8\)\{2}"',
+ \ lang_results,
+ \ "Default locale should have UTF-8 encoding set, and LC_NUMERIC set to 'C'")
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 4b54a0d39f..061364fb73 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -108,3 +108,27 @@ func Test_skip_after_throw()
catch /something/
endtry
endfunc
+
+func Test_curly_assignment()
+ let s:svar = 'svar'
+ let g:gvar = 'gvar'
+ let lname = 'gvar'
+ let gname = 'gvar'
+ let {'s:'.lname} = {'g:'.gname}
+ call assert_equal('gvar', s:gvar)
+ let s:gvar = ''
+ let { 's:'.lname } = { 'g:'.gname }
+ call assert_equal('gvar', s:gvar)
+ let s:gvar = ''
+ let { 's:' . lname } = { 'g:' . gname }
+ call assert_equal('gvar', s:gvar)
+ let s:gvar = ''
+ let { 's:' .. lname } = { 'g:' .. gname }
+ call assert_equal('gvar', s:gvar)
+
+ unlet s:svar
+ unlet s:gvar
+ unlet g:gvar
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim
index fb29c3eb7a..9588d3b89d 100644
--- a/src/nvim/testdir/test_expand_func.vim
+++ b/src/nvim/testdir/test_expand_func.vim
@@ -1,5 +1,7 @@
" Tests for expand()
+source shared.vim
+
let s:sfile = expand('<sfile>')
let s:slnum = str2nr(expand('<slnum>'))
let s:sflnum = str2nr(expand('<sflnum>'))
@@ -16,6 +18,25 @@ func s:expand_sflnum()
return str2nr(expand('<sflnum>'))
endfunc
+" This test depends on the location in the test file, put it first.
+func Test_expand_sflnum()
+ call assert_equal(7, s:sflnum)
+ call assert_equal(24, str2nr(expand('<sflnum>')))
+
+ " Line-continuation
+ call assert_equal(
+ \ 27,
+ \ str2nr(expand('<sflnum>')))
+
+ " Call in script-local function
+ call assert_equal(18, s:expand_sflnum())
+
+ " Call in command
+ command Flnum echo expand('<sflnum>')
+ call assert_equal(36, str2nr(trim(execute('Flnum'))))
+ delcommand Flnum
+endfunc
+
func Test_expand_sfile()
call assert_match('test_expand_func\.vim$', s:sfile)
call assert_match('^function .*\.\.Test_expand_sfile$', expand('<sfile>'))
@@ -30,7 +51,7 @@ func Test_expand_sfile()
endfunc
func Test_expand_slnum()
- call assert_equal(4, s:slnum)
+ call assert_equal(6, s:slnum)
call assert_equal(2, str2nr(expand('<slnum>')))
" Line-continuation
@@ -47,20 +68,14 @@ func Test_expand_slnum()
delcommand Slnum
endfunc
-func Test_expand_sflnum()
- call assert_equal(5, s:sflnum)
- call assert_equal(52, str2nr(expand('<sflnum>')))
-
- " Line-continuation
- call assert_equal(
- \ 55,
- \ str2nr(expand('<sflnum>')))
-
- " Call in script-local function
- call assert_equal(16, s:expand_sflnum())
+func s:sid_test()
+ return 'works'
+endfunc
- " Call in command
- command Flnum echo expand('<sflnum>')
- call assert_equal(64, str2nr(trim(execute('Flnum'))))
- delcommand Flnum
+func Test_expand_SID()
+ let sid = expand('<SID>')
+ execute 'let g:sid_result = ' .. sid .. 'sid_test()'
+ call assert_equal('works', g:sid_result)
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 264d8b000f..b8d6f5aa7d 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -49,6 +49,9 @@ func Test_dict()
let d['a'] = 'aaa'
call assert_equal('none', d[''])
call assert_equal('aaa', d['a'])
+
+ let d[ 'b' ] = 'bbb'
+ call assert_equal('bbb', d[ 'b' ])
endfunc
func Test_strgetchar()
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 2e280417ae..9f79c1b545 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -73,8 +73,9 @@ let s:filename_checks = {
\ 'autoit': ['file.au3'],
\ 'automake': ['GNUmakefile.am'],
\ 'ave': ['file.ave'],
- \ 'awk': ['file.awk'],
+ \ 'awk': ['file.awk', 'file.gawk'],
\ 'b': ['file.mch', 'file.ref', 'file.imp'],
+ \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'],
\ 'bc': ['file.bc'],
\ 'bdf': ['file.bdf'],
\ 'bib': ['file.bib'],
@@ -127,6 +128,7 @@ let s:filename_checks = {
\ 'dart': ['file.dart', 'file.drt'],
\ 'datascript': ['file.ds'],
\ 'dcd': ['file.dcd'],
+ \ 'debchangelog': ['changelog.Debian', 'changelog.dch', 'NEWS.Debian', 'NEWS.dch', '/debian/changelog'],
\ 'debcontrol': ['/debian/control'],
\ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list'],
\ 'def': ['file.def'],
@@ -140,7 +142,7 @@ let s:filename_checks = {
\ 'dnsmasq': ['/etc/dnsmasq.conf'],
\ 'dockerfile': ['Dockerfile', 'file.Dockerfile'],
\ 'dosbatch': ['file.bat', 'file.sys'],
- \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini'],
+ \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5'],
\ 'dot': ['file.dot', 'file.gv'],
\ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'],
\ 'dsl': ['file.dsl'],
@@ -325,7 +327,7 @@ let s:filename_checks = {
\ 'pamconf': ['/etc/pam.conf'],
\ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment'],
\ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'],
- \ 'pascal': ['file.pas', 'file.dpr'],
+ \ 'pascal': ['file.pas', 'file.pp', 'file.dpr', 'file.lpr'],
\ 'passwd': ['any/etc/passwd', 'any/etc/passwd-', 'any/etc/passwd.edit', 'any/etc/shadow', 'any/etc/shadow-', 'any/etc/shadow.edit', 'any/var/backups/passwd.bak', 'any/var/backups/shadow.bak'],
\ 'pccts': ['file.g'],
\ 'pdf': ['file.pdf'],
@@ -454,7 +456,7 @@ let s:filename_checks = {
\ 'texmf': ['texmf.cnf'],
\ 'text': ['file.text', 'README'],
\ 'tf': ['file.tf', '.tfrc', 'tfrc'],
- \ 'tidy': ['.tidyrc', 'tidyrc'],
+ \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'],
\ 'tilde': ['file.t.html'],
\ 'tli': ['file.tli'],
\ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf'],
@@ -471,6 +473,7 @@ let s:filename_checks = {
\ 'uc': ['file.uc'],
\ 'udevconf': ['/etc/udev/udev.conf'],
\ 'udevperm': ['/etc/udev/permissions.d/file.permissions'],
+ \ 'udevrules': ['/etc/udev/rules.d/file.rules', '/usr/lib/udev/rules.d/file.rules', '/lib/udev/rules.d/file.rules'],
\ 'uil': ['file.uit', 'file.uil'],
\ 'updatedb': ['/etc/updatedb.conf'],
\ 'upstart': ['/usr/share/upstart/file.conf', '/usr/share/upstart/file.override', '/etc/init/file.conf', '/etc/init/file.override', '/.init/file.conf', '/.init/file.override', '/.config/upstart/file.conf', '/.config/upstart/file.override'],
@@ -525,9 +528,11 @@ let s:filename_checks = {
let s:filename_case_checks = {
\ 'modula2': ['file.DEF', 'file.MOD'],
+ \ 'bzl': ['file.BUILD', 'BUILD'],
\ }
func CheckItems(checks)
+ set noswapfile
for [ft, names] in items(a:checks)
for i in range(0, len(names) - 1)
new
@@ -544,6 +549,7 @@ func CheckItems(checks)
bwipe!
endfor
endfor
+ set swapfile&
endfunc
func Test_filetype_detection()
@@ -596,7 +602,8 @@ let s:script_checks = {
\ 'bc': [['#!/path/bc']],
\ 'sed': [['#!/path/sed']],
\ 'ocaml': [['#!/path/ocaml']],
- \ 'awk': [['#!/path/awk']],
+ \ 'awk': [['#!/path/awk'],
+ \ ['#!/path/gawk']],
\ 'wml': [['#!/path/wml']],
\ 'scheme': [['#!/path/scheme']],
\ 'cfengine': [['#!/path/cfengine']],
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
index 1dd3a5b29f..a15567bcf2 100644
--- a/src/nvim/testdir/test_filter_map.vim
+++ b/src/nvim/testdir/test_filter_map.vim
@@ -11,6 +11,7 @@ func Test_filter_map_list_expr_string()
call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
+ call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
endfunc
" dict with expression string
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 51689db9c4..917a5e8eca 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -214,7 +214,7 @@ func Test_strftime()
endif
endfunc
-func Test_resolve()
+func Test_resolve_unix()
if !has('unix')
return
endif
@@ -258,6 +258,8 @@ func Test_resolve()
call assert_equal('Xlink2', resolve('Xlink1'))
call assert_equal('./Xlink2', resolve('./Xlink1'))
call delete('Xlink1')
+
+ call assert_equal('/', resolve('/'))
endfunc
func Test_simplify()
@@ -334,6 +336,10 @@ func Test_strpart()
call assert_equal('lép', strpart('éléphant', 2, 4))
call assert_equal('léphant', strpart('éléphant', 2))
+
+ call assert_equal('é', strpart('éléphant', 0, 1, 1))
+ call assert_equal('ép', strpart('éléphant', 3, 2, v:true))
+ call assert_equal('ó', strpart('cómposed', 1, 1, 1))
endfunc
func Test_tolower()
@@ -1079,6 +1085,12 @@ func Test_trim()
call assert_equal("", trim("", ""))
call assert_equal("a", trim("a", ""))
call assert_equal("", trim("", "a"))
+ call assert_equal("vim", trim(" vim ", " ", 0))
+ call assert_equal("vim ", trim(" vim ", " ", 1))
+ call assert_equal(" vim", trim(" vim ", " ", 2))
+ call assert_fails('call trim(" vim ", " ", [])', 'E745:')
+ call assert_fails('call trim(" vim ", " ", -1)', 'E475:')
+ call assert_fails('call trim(" vim ", " ", 3)', 'E475:')
let chars = join(map(range(1, 0x20) + [0xa0], {n -> nr2char(n)}), '')
call assert_equal("x", trim(chars . "x" . chars))
@@ -1217,6 +1229,24 @@ func Test_reg_executing_and_recording()
unlet s:reg_stat
endfunc
+func Test_getchar()
+ call feedkeys('a', '')
+ call assert_equal(char2nr('a'), getchar())
+
+ " call test_setmouse(1, 3)
+ " let v:mouse_win = 9
+ " let v:mouse_winid = 9
+ " let v:mouse_lnum = 9
+ " let v:mouse_col = 9
+ " call feedkeys("\<S-LeftMouse>", '')
+ call nvim_input_mouse('left', 'press', 'S', 0, 0, 2)
+ call assert_equal("\<S-LeftMouse>", getchar())
+ call assert_equal(1, v:mouse_win)
+ call assert_equal(win_getid(1), v:mouse_winid)
+ call assert_equal(1, v:mouse_lnum)
+ call assert_equal(3, v:mouse_col)
+endfunc
+
func Test_libcall_libcallnr()
if !has('libcall')
return
@@ -1337,3 +1367,22 @@ func Test_readdir()
call delete('Xdir', 'rf')
endfunc
+
+" Test for the eval() function
+func Test_eval()
+ call assert_fails("call eval('5 a')", 'E488:')
+endfunc
+
+" Test for the nr2char() function
+func Test_nr2char()
+ " set encoding=latin1
+ call assert_equal('@', nr2char(64))
+ set encoding=utf8
+ call assert_equal('a', nr2char(97, 1))
+ call assert_equal('a', nr2char(97, 0))
+
+ call assert_equal("\x80\xfc\b\xf4\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x100000) .. '>"'))
+ call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index 4a4ffcefa1..ee548037ba 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -1,3 +1,4 @@
+" Test for the gf and gF (goto file) commands
" This is a test if a URL is recognized by "gf", with the cursor before and
" after the "://". Also test ":\\".
@@ -109,7 +110,7 @@ func Test_gf()
endfunc
func Test_gf_visual()
- call writefile([], "Xtest_gf_visual")
+ call writefile(['one', 'two', 'three', 'four'], "Xtest_gf_visual")
new
call setline(1, 'XXXtest_gf_visualXXX')
set hidden
@@ -118,6 +119,30 @@ func Test_gf_visual()
norm! ttvtXgf
call assert_equal('Xtest_gf_visual', bufname('%'))
+ " if multiple lines are selected, then gf should fail
+ call setline(1, ["one", "two"])
+ normal VGgf
+ call assert_equal('Xtest_gf_visual', @%)
+
+ " following line number is used for gF
+ bwipe!
+ new
+ call setline(1, 'XXXtest_gf_visual:3XXX')
+ norm! 0ttvt:gF
+ call assert_equal('Xtest_gf_visual', bufname('%'))
+ call assert_equal(3, getcurpos()[1])
+
+ " line number in visual area is used for file name
+ if has('unix')
+ bwipe!
+ call writefile([], "Xtest_gf_visual:3")
+ new
+ call setline(1, 'XXXtest_gf_visual:3XXX')
+ norm! 0ttvtXgF
+ call assert_equal('Xtest_gf_visual:3', bufname('%'))
+ call delete('Xtest_gf_visual:3')
+ endif
+
bwipe!
call delete('Xtest_gf_visual')
set hidden&
diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim
index d41675be0c..9acec51913 100644
--- a/src/nvim/testdir/test_gn.vim
+++ b/src/nvim/testdir/test_gn.vim
@@ -158,7 +158,32 @@ func Test_gn_command()
set wrapscan&vim
set belloff&vim
-endfu
+endfunc
+
+func Test_gN_repeat()
+ new
+ call setline(1, 'this list is a list with a list of a list.')
+ /list
+ normal $gNgNgNx
+ call assert_equal('list with a list of a list', @")
+ bwipe!
+endfunc
+
+func Test_gN_then_gn()
+ new
+
+ call setline(1, 'this list is a list with a list of a last.')
+ /l.st
+ normal $gNgNgnx
+ call assert_equal('last', @")
+
+ call setline(1, 'this list is a list with a lust of a last.')
+ /l.st
+ normal $gNgNgNgnx
+ call assert_equal('lust of a last', @")
+
+ bwipe!
+endfunc
func Test_gn_multi_line()
new
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index 6aa187b17e..00e42733a7 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -596,9 +596,17 @@ endfunc
" This test must come before the Test_cursorline test, as it appears this
" defines the Normal highlighting group anyway.
func Test_1_highlight_Normalgroup_exists()
- " MS-Windows GUI sets the font
- if !has('win32') || !has('gui_running')
- let hlNormal = HighlightArgs('Normal')
+ let hlNormal = HighlightArgs('Normal')
+ if !has('gui_running')
call assert_match('hi Normal\s*clear', hlNormal)
+ elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+ " expect is DEFAULT_FONT of gui_gtk_x11.c
+ call assert_match('hi Normal\s*font=Monospace 10', hlNormal)
+ elseif has('gui_motif') || has('gui_athena')
+ " expect is DEFAULT_FONT of gui_x11.c
+ call assert_match('hi Normal\s*font=7x13', hlNormal)
+ elseif has('win32')
+ " expect any font
+ call assert_match('hi Normal\s*font=.*', hlNormal)
endif
endfunc
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index 1c275d5bd1..57a0a7aaf4 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -1,3 +1,5 @@
+source screendump.vim
+source check.vim
" Test for insert expansion
func Test_ins_complete()
@@ -325,7 +327,10 @@ 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 w:test_winvar = 'winvar'
+ let b:test_bufvar = 'bufvar'
+ " User-defined commands
let input = ''
call feedkeys("q:iGetInput T\<C-x>\<C-v>\<CR>", 'tx!')
call assert_equal('TestCommand', input)
@@ -334,7 +339,114 @@ func Test_compl_in_cmdwin()
call feedkeys("q::GetInput T\<Tab>\<CR>:q\<CR>", 'tx!')
call assert_equal('T', input)
+ com! -nargs=1 -complete=var GetInput let input = <q-args>
+ " Window-local variables
+ let input = ''
+ call feedkeys("q:iGetInput w:test_\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('w:test_winvar', input)
+
+ let input = ''
+ call feedkeys("q::GetInput w:test_\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('w:test_', input)
+
+ " Buffer-local variables
+ let input = ''
+ call feedkeys("q:iGetInput b:test_\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('b:test_bufvar', input)
+
+ let input = ''
+ call feedkeys("q::GetInput b:test_\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('b:test_', input)
+
delcom TestCommand
delcom GetInput
+ unlet w:test_winvar
+ unlet b:test_bufvar
set wildmenu& wildchar&
endfunc
+
+" Test for insert path completion with completeslash option
+func Test_ins_completeslash()
+ CheckMSWindows
+
+ call mkdir('Xdir')
+
+ let orig_shellslash = &shellslash
+ set cpt&
+
+ new
+
+ set noshellslash
+
+ set completeslash=
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir\', getline('.'))
+
+ set completeslash=backslash
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir\', getline('.'))
+
+ set completeslash=slash
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir/', getline('.'))
+
+ set shellslash
+
+ set completeslash=
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir/', getline('.'))
+
+ set completeslash=backslash
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir\', getline('.'))
+
+ set completeslash=slash
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir/', getline('.'))
+ %bw!
+ call delete('Xdir', 'rf')
+
+ set noshellslash
+ set completeslash=slash
+ call assert_true(stridx(globpath(&rtp, 'syntax/*.vim', 1, 1)[0], '\') != -1)
+
+ let &shellslash = orig_shellslash
+ set completeslash=
+endfunc
+
+func Test_issue_7021()
+ CheckMSWindows
+
+ let orig_shellslash = &shellslash
+ set noshellslash
+
+ set completeslash=slash
+ call assert_false(expand('~') =~ '/')
+
+ let &shellslash = orig_shellslash
+ set completeslash=
+endfunc
+
+func Test_pum_with_folds_two_tabs()
+ CheckScreendump
+
+ let lines =<< trim END
+ set fdm=marker
+ call setline(1, ['" x {{{1', '" a some text'])
+ call setline(3, range(&lines)->map({_, val -> '" a' .. val}))
+ norm! zm
+ tab sp
+ call feedkeys('2Gzv', 'xt')
+ call feedkeys("0fa", 'xt')
+ END
+
+ call writefile(lines, 'Xpumscript')
+ let buf = RunVimInTerminal('-S Xpumscript', #{rows: 10})
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, "a\<C-N>")
+ call VerifyScreenDump(buf, 'Test_pum_with_folds_two_tabs', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xpumscript')
+endfunc
diff --git a/src/nvim/testdir/test_interrupt.vim b/src/nvim/testdir/test_interrupt.vim
new file mode 100644
index 0000000000..111752d16a
--- /dev/null
+++ b/src/nvim/testdir/test_interrupt.vim
@@ -0,0 +1,27 @@
+" Test behavior of interrupt()
+
+let s:bufwritepre_called = 0
+let s:bufwritepost_called = 0
+
+func s:bufwritepre()
+ let s:bufwritepre_called = 1
+ call interrupt()
+endfunction
+
+func s:bufwritepost()
+ let s:bufwritepost_called = 1
+endfunction
+
+func Test_interrupt()
+ new Xfile
+ let n = 0
+ try
+ au BufWritePre Xfile call s:bufwritepre()
+ au BufWritePost Xfile call s:bufwritepost()
+ w!
+ catch /^Vim:Interrupt$/
+ endtry
+ call assert_equal(1, s:bufwritepre_called)
+ call assert_equal(0, s:bufwritepost_called)
+ call assert_equal(0, filereadable('Xfile'))
+endfunc
diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim
index bfbb3e5c5b..f026c8a55f 100644
--- a/src/nvim/testdir/test_lambda.vim
+++ b/src/nvim/testdir/test_lambda.vim
@@ -181,7 +181,7 @@ function! Test_lambda_scope()
let l:D = s:NewCounter2()
call assert_equal(1, l:C())
- call assert_fails(':call l:D()', 'E15:') " E121: then E15:
+ call assert_fails(':call l:D()', 'E121:')
call assert_equal(2, l:C())
endfunction
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index bea62cb0ad..31a8b48d37 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -280,6 +280,14 @@ func Test_dict_func_remove_in_use()
call assert_equal(expected, d.func(string(remove(d, 'func'))))
endfunc
+func Test_dict_literal_keys()
+ call assert_equal({'one': 1, 'two2': 2, '3three': 3, '44': 4}, #{one: 1, two2: 2, 3three: 3, 44: 4},)
+
+ " why *{} cannot be used
+ let blue = 'blue'
+ call assert_equal('6', trim(execute('echo 2 *{blue: 3}.blue')))
+endfunc
+
" Nasty: deepcopy() dict that refers to itself (fails when noref used)
func Test_dict_deepcopy()
let d = {1:1, 2:2}
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index 82562339f6..152afb4b9d 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -391,6 +391,42 @@ func Test_motionforce_omap()
delfunc GetCommand
endfunc
+func Test_error_in_map_expr()
+ if !has('terminal') || (has('win32') && has('gui_running'))
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+
+ let lines =<< trim [CODE]
+ func Func()
+ " fail to create list
+ let x = [
+ endfunc
+ nmap <expr> ! Func()
+ set updatetime=50
+ [CODE]
+ call writefile(lines, 'Xtest.vim')
+
+ let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8})
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})
+
+ " GC must not run during map-expr processing, which can make Vim crash.
+ call term_sendkeys(buf, '!')
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, "\<CR>")
+ call term_wait(buf, 100)
+ call assert_equal('run', job_status(job))
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitFor({-> job_status(job) ==# 'dead'})
+ if has('unix')
+ call assert_equal('', job_info(job).termsig)
+ endif
+
+ call delete('Xtest.vim')
+ exe buf .. 'bwipe!'
+endfunc
+
" Test for mapping errors
func Test_map_error()
call assert_fails('unmap', 'E474:')
diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim
index b918525dbc..393e183ddb 100644
--- a/src/nvim/testdir/test_matchadd_conceal.vim
+++ b/src/nvim/testdir/test_matchadd_conceal.vim
@@ -1,9 +1,11 @@
" Test for matchadd() and conceal feature
-if !has('conceal')
- finish
-endif
+
+source check.vim
+CheckFeature conceal
source shared.vim
+source term_util.vim
+source view_util.vim
function! Test_simple_matchadd()
new
@@ -273,3 +275,70 @@ function! Test_matchadd_and_syn_conceal()
call assert_notequal(screenattr(1, 11) , screenattr(1, 12))
call assert_equal(screenattr(1, 11) , screenattr(1, 32))
endfunction
+
+func Test_cursor_column_in_concealed_line_after_window_scroll()
+ CheckRunVimInTerminal
+
+ " Test for issue #5012 fix.
+ " For a concealed line with cursor, there should be no window's cursor
+ " position invalidation during win_update() after scrolling attempt that is
+ " not successful and no real topline change happens. The invalidation would
+ " cause a window's cursor position recalc outside of win_line() where it's
+ " not possible to take conceal into account.
+ let lines =<< trim END
+ 3split
+ let m = matchadd('Conceal', '=')
+ setl conceallevel=2 concealcursor=nc
+ normal gg
+ "==expr==
+ END
+ call writefile(lines, 'Xcolesearch')
+ let buf = RunVimInTerminal('Xcolesearch', {})
+ call term_wait(buf, 100)
+
+ " Jump to something that is beyond the bottom of the window,
+ " so there's a scroll down.
+ call term_sendkeys(buf, ":so %\<CR>")
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, "/expr\<CR>")
+ call term_wait(buf, 100)
+
+ " Are the concealed parts of the current line really hidden?
+ let cursor_row = term_scrape(buf, '.')->map({_, e -> e.chars})->join('')
+ call assert_equal('"expr', cursor_row)
+
+ " BugFix check: Is the window's cursor column properly updated for hidden
+ " parts of the current line?
+ call assert_equal(2, term_getcursor(buf)[1])
+
+ call StopVimInTerminal(buf)
+ call delete('Xcolesearch')
+endfunc
+
+func Test_cursor_column_in_concealed_line_after_leftcol_change()
+ CheckRunVimInTerminal
+
+ " Test for issue #5214 fix.
+ let lines =<< trim END
+ 0put = 'ab' .. repeat('-', &columns) .. 'c'
+ call matchadd('Conceal', '-')
+ set nowrap ss=0 cole=3 cocu=n
+ END
+ call writefile(lines, 'Xcurs-columns')
+ let buf = RunVimInTerminal('-S Xcurs-columns', {})
+
+ " Go to the end of the line (3 columns beyond the end of the screen).
+ " Horizontal scroll would center the cursor in the screen line, but conceal
+ " makes it go to screen column 1.
+ call term_sendkeys(buf, "$")
+ call term_wait(buf)
+
+ " Are the concealed parts of the current line really hidden?
+ call WaitForAssert({-> assert_equal('c', term_getline(buf, '.'))})
+
+ " BugFix check: Is the window's cursor column properly updated for conceal?
+ call assert_equal(1, term_getcursor(buf)[1])
+
+ call StopVimInTerminal(buf)
+ call delete('Xcurs-columns')
+endfunc
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index 7fbf04311d..ca14494248 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/src/nvim/testdir/test_messages.vim
@@ -74,6 +74,7 @@ func Test_echomsg()
call assert_equal("\n12345", execute(':echomsg 12345'))
call assert_equal("\n[]", execute(':echomsg []'))
call assert_equal("\n[1, 2, 3]", execute(':echomsg [1, 2, 3]'))
+ call assert_equal("\n[1, 2, []]", execute(':echomsg [1, 2, v:_null_list]'))
call assert_equal("\n{}", execute(':echomsg {}'))
call assert_equal("\n{'a': 1, 'b': 2}", execute(':echomsg {"a": 1, "b": 2}'))
if has('float')
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index 9c9e04be07..3243edbf55 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -9,6 +9,29 @@ endif
source shared.vim
source term_util.vim
+" Test for storing global and local argument list in a session file
+" This one must be done first.
+func Test__mksession_arglocal()
+ enew | only
+ n a b c
+ new
+ arglocal
+ mksession! Xtest_mks.out
+
+ %bwipe!
+ %argdelete
+ argglobal
+ source Xtest_mks.out
+ call assert_equal(2, winnr('$'))
+ call assert_equal(2, arglistid(1))
+ call assert_equal(0, arglistid(2))
+
+ %bwipe!
+ %argdelete
+ argglobal
+ call delete('Xtest_mks.out')
+endfunc
+
func Test_mksession()
tabnew
let wrap_save = &wrap
@@ -155,7 +178,7 @@ endfunc
" Verify that arglist is stored correctly to the session file.
func Test_mksession_arglist()
- argdel *
+ %argdel
next file1 file2 file3 file4
mksession! Xtest_mks.out
source Xtest_mks.out
@@ -307,6 +330,104 @@ func Test_mksession_quote_in_filename()
call delete('Xtest_mks_quoted.out')
endfunc
+" Test for storing global variables in a session file
+func Test_mksession_globals()
+ set sessionoptions+=globals
+
+ " create different global variables
+ let g:Global_string = "Sun is shining"
+ let g:Global_count = 100
+ let g:Global_pi = 3.14
+
+ mksession! Xtest_mks.out
+
+ unlet g:Global_string
+ unlet g:Global_count
+ unlet g:Global_pi
+
+ source Xtest_mks.out
+ call assert_equal("Sun is shining", g:Global_string)
+ call assert_equal(100, g:Global_count)
+ call assert_equal(3.14, g:Global_pi)
+
+ unlet g:Global_string
+ unlet g:Global_count
+ unlet g:Global_pi
+ call delete('Xtest_mks.out')
+ set sessionoptions&
+endfunc
+
+" Test for changing backslash to forward slash in filenames
+func Test_mksession_slash()
+ if exists('+shellslash')
+ throw 'Skipped: cannot use backslash in file name'
+ endif
+ enew
+ %bwipe!
+ e a\\b\\c
+ mksession! Xtest_mks1.out
+ set sessionoptions+=slash
+ mksession! Xtest_mks2.out
+
+ %bwipe!
+ source Xtest_mks1.out
+ call assert_equal('a/b/c', bufname(''))
+ %bwipe!
+ source Xtest_mks2.out
+ call assert_equal('a/b/c', bufname(''))
+
+ %bwipe!
+ call delete('Xtest_mks1.out')
+ call delete('Xtest_mks2.out')
+ set sessionoptions&
+endfunc
+
+" Test for changing directory to the session file directory
+func Test_mksession_sesdir()
+ call mkdir('Xproj')
+ mksession! Xproj/Xtest_mks1.out
+ set sessionoptions-=curdir
+ set sessionoptions+=sesdir
+ mksession! Xproj/Xtest_mks2.out
+
+ source Xproj/Xtest_mks1.out
+ call assert_equal('testdir', fnamemodify(getcwd(), ':t'))
+ source Xproj/Xtest_mks2.out
+ call assert_equal('Xproj', fnamemodify(getcwd(), ':t'))
+ cd ..
+
+ set sessionoptions&
+ call delete('Xproj', 'rf')
+endfunc
+
+" Test for storing the 'lines' and 'columns' settings
+func Test_mksession_resize()
+ mksession! Xtest_mks1.out
+ set sessionoptions+=resize
+ mksession! Xtest_mks2.out
+
+ let lines = readfile('Xtest_mks1.out')
+ let found_resize = v:false
+ for line in lines
+ if line =~ '^set lines='
+ let found_resize = v:true
+ endif
+ endfor
+ call assert_equal(v:false, found_resize)
+ let lines = readfile('Xtest_mks2.out')
+ let found_resize = v:false
+ for line in lines
+ if line =~ '^set lines='
+ let found_resize = v:true
+ endif
+ endfor
+ call assert_equal(v:true, found_resize)
+
+ call delete('Xtest_mks1.out')
+ call delete('Xtest_mks2.out')
+ set sessionoptions&
+endfunc
+
func s:ClearMappings()
mapclear
omapclear
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 04a5c62f66..9e8da74db7 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -576,3 +576,13 @@ func Test_opt_boolean()
set number&
endfunc
+" Test for setting option value containing spaces with isfname+=32
+func Test_isfname_with_options()
+ set isfname+=32
+ setlocal keywordprg=:term\ help.exe
+ call assert_equal(':term help.exe', &keywordprg)
+ set isfname&
+ setlocal keywordprg&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_perl.vim b/src/nvim/testdir/test_perl.vim
new file mode 100644
index 0000000000..872194a804
--- /dev/null
+++ b/src/nvim/testdir/test_perl.vim
@@ -0,0 +1,311 @@
+" Tests for Perl interface
+
+if !has('perl') || has('win32')
+ finish
+endif
+
+" FIXME: RunTest don't see any error when Perl abort...
+perl $SIG{__WARN__} = sub { die "Unexpected warnings from perl: @_" };
+
+func Test_change_buffer()
+ call setline(line('$'), ['1 line 1'])
+ perl VIM::DoCommand("normal /^1\n")
+ perl $curline = VIM::Eval("line('.')")
+ perl $curbuf->Set($curline, "1 changed line 1")
+ call assert_equal('1 changed line 1', getline('$'))
+endfunc
+
+func Test_evaluate_list()
+ call setline(line('$'), ['2 line 2'])
+ perl VIM::DoCommand("normal /^2\n")
+ perl $curline = VIM::Eval("line('.')")
+ let l = ["abc", "def"]
+ perl << EOF
+ $l = VIM::Eval("l");
+ $curbuf->Append($curline, $l);
+EOF
+ normal j
+ .perldo s|\n|/|g
+ " call assert_equal('abc/def/', getline('$'))
+ call assert_equal('def', getline('$'))
+endfunc
+
+funct Test_VIM_Blob()
+ call assert_equal('0z', perleval('VIM::Blob("")'))
+ call assert_equal('0z31326162', perleval('VIM::Blob("12ab")'))
+ call assert_equal('0z00010203', perleval('VIM::Blob("\x00\x01\x02\x03")'))
+ call assert_equal('0z8081FEFF', perleval('VIM::Blob("\x80\x81\xfe\xff")'))
+endfunc
+
+func Test_buffer_Delete()
+ new
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
+ perl $curbuf->Delete(7)
+ perl $curbuf->Delete(2, 5)
+ perl $curbuf->Delete(10)
+ call assert_equal(['a', 'f', 'h'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Append()
+ new
+ perl $curbuf->Append(1, '1')
+ perl $curbuf->Append(2, '2', '3', '4')
+ perl @l = ('5' ..'7')
+ perl $curbuf->Append(0, @l)
+ call assert_equal(['5', '6', '7', '', '1', '2', '3', '4'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Set()
+ new
+ call setline(1, ['1', '2', '3', '4', '5'])
+ perl $curbuf->Set(2, 'a', 'b', 'c')
+ perl $curbuf->Set(4, 'A', 'B', 'C')
+ call assert_equal(['1', 'a', 'b', 'A', 'B'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Get()
+ new
+ call setline(1, ['1', '2', '3', '4'])
+ call assert_equal('2:3', perleval('join(":", $curbuf->Get(2, 3))'))
+ bwipe!
+endfunc
+
+func Test_buffer_Count()
+ new
+ call setline(1, ['a', 'b', 'c'])
+ call assert_equal(3, perleval('$curbuf->Count()'))
+ bwipe!
+endfunc
+
+func Test_buffer_Name()
+ new
+ call assert_equal('', perleval('$curbuf->Name()'))
+ bwipe!
+ new Xfoo
+ call assert_equal('Xfoo', perleval('$curbuf->Name()'))
+ bwipe!
+endfunc
+
+func Test_buffer_Number()
+ call assert_equal(bufnr('%'), perleval('$curbuf->Number()'))
+endfunc
+
+func Test_window_Cursor()
+ new
+ call setline(1, ['line1', 'line2'])
+ perl $curwin->Cursor(2, 3)
+ call assert_equal('2:3', perleval('join(":", $curwin->Cursor())'))
+ " Col is numbered from 0 in Perl, and from 1 in Vim script.
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+ bwipe!
+endfunc
+
+func Test_window_SetHeight()
+ new
+ perl $curwin->SetHeight(2)
+ call assert_equal(2, winheight(0))
+ bwipe!
+endfunc
+
+func Test_VIM_Windows()
+ new
+ " VIM::Windows() without argument in scalar and list context.
+ perl $winnr = VIM::Windows()
+ perl @winlist = VIM::Windows()
+ perl $curbuf->Append(0, $winnr, scalar(@winlist))
+ call assert_equal(['2', '2', ''], getline(1, '$'))
+
+ " VIM::Windows() with window number argument.
+ perl (VIM::Windows(VIM::Eval('winnr()')))[0]->Buffer()->Set(1, 'bar')
+ call assert_equal('bar', getline(1))
+ bwipe!
+endfunc
+
+func Test_VIM_Buffers()
+ new Xbar
+ " VIM::Buffers() without argument in scalar and list context.
+ perl $nbuf = VIM::Buffers()
+ perl @buflist = VIM::Buffers()
+
+ " VIM::Buffers() with argument.
+ perl $curbuf = (VIM::Buffers('Xbar'))[0]
+ perl $curbuf->Append(0, $nbuf, scalar(@buflist))
+ call assert_equal(['2', '2', ''], getline(1, '$'))
+ bwipe!
+endfunc
+
+func <SID>catch_peval(expr)
+ try
+ call perleval(a:expr)
+ catch
+ return v:exception
+ endtry
+ call assert_report('no exception for `perleval("'.a:expr.'")`')
+ return ''
+endfunc
+
+func Test_perleval()
+ call assert_false(perleval('undef'))
+
+ " scalar
+ call assert_equal(0, perleval('0'))
+ call assert_equal(2, perleval('2'))
+ call assert_equal(-2, perleval('-2'))
+ if has('float')
+ call assert_equal(2.5, perleval('2.5'))
+ else
+ call assert_equal(2, perleval('2.5'))
+ end
+
+ " sandbox call assert_equal(2, perleval('2'))
+
+ call assert_equal('abc', perleval('"abc"'))
+ " call assert_equal("abc\ndef", perleval('"abc\0def"'))
+
+ " ref
+ call assert_equal([], perleval('[]'))
+ call assert_equal(['word', 42, [42],{}], perleval('["word", 42, [42], {}]'))
+
+ call assert_equal({}, perleval('{}'))
+ call assert_equal({'foo': 'bar'}, perleval('{foo => "bar"}'))
+
+ perl our %h; our @a;
+ let a = perleval('[\(%h, %h, @a, @a)]')
+ " call assert_true((a[0] is a[1]))
+ call assert_equal(a[0], a[1])
+ " call assert_true((a[2] is a[3]))
+ call assert_equal(a[2], a[3])
+ perl undef %h; undef @a;
+
+ " call assert_true(<SID>catch_peval('{"" , 0}') =~ 'Malformed key Dictionary')
+ " call assert_true(<SID>catch_peval('{"\0" , 0}') =~ 'Malformed key Dictionary')
+ " call assert_true(<SID>catch_peval('{"foo\0bar" , 0}') =~ 'Malformed key Dictionary')
+
+ call assert_equal('*VIM', perleval('"*VIM"'))
+ " call assert_true(perleval('\\0') =~ 'SCALAR(0x\x\+)')
+endfunc
+
+func Test_perldo()
+ sp __TEST__
+ exe 'read ' g:testname
+ perldo s/perl/vieux_chameau/g
+ 1
+ call assert_false(search('\Cperl'))
+ bw!
+
+ " Check deleting lines does not trigger ml_get error.
+ new
+ call setline(1, ['one', 'two', 'three'])
+ perldo VIM::DoCommand("%d_")
+ bwipe!
+
+ " Check switching to another buffer does not trigger ml_get error.
+ new
+ let wincount = winnr('$')
+ call setline(1, ['one', 'two', 'three'])
+ perldo VIM::DoCommand("new")
+ call assert_equal(wincount + 1, winnr('$'))
+ bwipe!
+ bwipe!
+endfunc
+
+func Test_VIM_package()
+ perl VIM::DoCommand('let l:var = "foo"')
+ call assert_equal(l:var, 'foo')
+
+ set noet
+ perl VIM::SetOption('et')
+ call assert_true(&et)
+endfunc
+
+func Test_stdio()
+ throw 'skipped: TODO: '
+ redir =>l:out
+ perl <<EOF
+ VIM::Msg("&VIM::Msg");
+ print "STDOUT";
+ print STDERR "STDERR";
+EOF
+ redir END
+ call assert_equal(['&VIM::Msg', 'STDOUT', 'STDERR'], split(l:out, "\n"))
+endfunc
+
+" Run first to get a clean namespace
+func Test_000_SvREFCNT()
+ throw 'skipped: TODO: '
+ for i in range(8)
+ exec 'new X'.i
+ endfor
+ new t
+ perl <<--perl
+#line 5 "Test_000_SvREFCNT()"
+ my ($b, $w);
+
+ my $num = 0;
+ for ( 0 .. 100 ) {
+ if ( ++$num >= 8 ) { $num = 0 }
+ VIM::DoCommand("buffer X$num");
+ $b = $curbuf;
+ }
+
+ VIM::DoCommand("buffer t");
+
+ $b = $curbuf for 0 .. 100;
+ $w = $curwin for 0 .. 100;
+ () = VIM::Buffers for 0 .. 100;
+ () = VIM::Windows for 0 .. 100;
+
+ VIM::DoCommand('bw! t');
+ if (exists &Internals::SvREFCNT) {
+ my $cb = Internals::SvREFCNT($$b);
+ my $cw = Internals::SvREFCNT($$w);
+ VIM::Eval("assert_equal(2, $cb, 'T1')");
+ VIM::Eval("assert_equal(2, $cw, 'T2')");
+ my $strongref;
+ foreach ( VIM::Buffers, VIM::Windows ) {
+ VIM::DoCommand("%bw!");
+ my $c = Internals::SvREFCNT($_);
+ VIM::Eval("assert_equal(2, $c, 'T3')");
+ $c = Internals::SvREFCNT($$_);
+ next if $c == 2 && !$strongref++;
+ VIM::Eval("assert_equal(1, $c, 'T4')");
+ }
+ $cb = Internals::SvREFCNT($$curbuf);
+ $cw = Internals::SvREFCNT($$curwin);
+ VIM::Eval("assert_equal(3, $cb, 'T5')");
+ VIM::Eval("assert_equal(3, $cw, 'T6')");
+ }
+ VIM::Eval("assert_false($$b)");
+ VIM::Eval("assert_false($$w)");
+--perl
+ %bw!
+endfunc
+
+func Test_set_cursor()
+ " Check that setting the cursor position works.
+ new
+ call setline(1, ['first line', 'second line'])
+ normal gg
+ perldo $curwin->Cursor(1, 5)
+ call assert_equal([1, 6], [line('.'), col('.')])
+
+ " Check that movement after setting cursor position keeps current column.
+ normal j
+ call assert_equal([2, 6], [line('.'), col('.')])
+endfunc
+
+" Test for various heredoc syntax
+func Test_perl_heredoc()
+ perl << END
+VIM::DoCommand('let s = "A"')
+END
+ perl <<
+VIM::DoCommand('let s ..= "B"')
+.
+ call assert_equal('AB', s)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index bb0ed6e00c..fb464d95ea 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -2,6 +2,7 @@
source shared.vim
source screendump.vim
+source check.vim
let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
let g:setting = ''
@@ -755,6 +756,52 @@ func Test_popup_and_previewwindow_dump()
call delete('Xscript')
endfunc
+func Test_balloon_split()
+ CheckFunction balloon_split
+
+ call assert_equal([
+ \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"',
+ \ ], balloon_split(
+ \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"'))
+ call assert_equal([
+ \ 'one two three four one two three four one two thre',
+ \ 'e four',
+ \ ], balloon_split(
+ \ 'one two three four one two three four one two three four'))
+
+ eval 'struct = {one = 1, two = 2, three = 3}'
+ \ ->balloon_split()
+ \ ->assert_equal([
+ \ 'struct = {',
+ \ ' one = 1,',
+ \ ' two = 2,',
+ \ ' three = 3}',
+ \ ])
+
+ call assert_equal([
+ \ 'struct = {',
+ \ ' one = 1,',
+ \ ' nested = {',
+ \ ' n1 = "yes",',
+ \ ' n2 = "no"}',
+ \ ' two = 2}',
+ \ ], balloon_split(
+ \ 'struct = {one = 1, nested = {n1 = "yes", n2 = "no"} two = 2}'))
+ call assert_equal([
+ \ 'struct = 0x234 {',
+ \ ' long = 2343 "\\"some long string that will be wr',
+ \ 'apped in two\\"",',
+ \ ' next = 123}',
+ \ ], balloon_split(
+ \ 'struct = 0x234 {long = 2343 "\\"some long string that will be wrapped in two\\"", next = 123}'))
+ call assert_equal([
+ \ 'Some comment',
+ \ '',
+ \ 'typedef this that;',
+ \ ], balloon_split(
+ \ "Some comment\n\ntypedef this that;"))
+endfunc
+
func Test_popup_position()
if !CanRunVimInTerminal()
return
diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim
index 884ada7e88..15745d5619 100644
--- a/src/nvim/testdir/test_put.vim
+++ b/src/nvim/testdir/test_put.vim
@@ -22,12 +22,21 @@ endfunc
func Test_put_char_block2()
new
- let a = [ getreg('a'), getregtype('a') ]
call setreg('a', ' one ', 'v')
call setline(1, ['Line 1', '', 'Line 3', ''])
" visually select the first 3 lines and put register a over it
exe "norm! ggl\<c-v>2j2l\"ap"
- call assert_equal(['L one 1', '', 'L one 3', ''], getline(1,4))
+ call assert_equal(['L one 1', '', 'L one 3', ''], getline(1, 4))
+ " clean up
+ bw!
+endfunc
+
+func Test_put_lines()
+ new
+ let a = [ getreg('a'), getregtype('a') ]
+ call setline(1, ['Line 1', 'Line2', 'Line 3', ''])
+ exe 'norm! gg"add"AddG""p'
+ call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1, '$'))
" clean up
bw!
call setreg('a', a[0], a[1])
@@ -42,21 +51,10 @@ func Test_put_expr()
exec "4norm! \"=\<cr>P"
norm! j0.
norm! j0.
- call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1,'$'))
+ call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1, '$'))
bw!
endfunc
-func Test_put_lines()
- new
- let a = [ getreg('a'), getregtype('a') ]
- call setline(1, ['Line 1', 'Line2', 'Line 3', ''])
- exe 'norm! gg"add"AddG""p'
- call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1,'$'))
- " clean up
- bw!
- call setreg('a', a[0], a[1])
-endfunc
-
func Test_put_fails_when_nomodifiable()
new
setlocal nomodifiable
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 926103b69f..cf0af07528 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -1,8 +1,7 @@
" Test for the quickfix commands.
-if !has('quickfix')
- finish
-endif
+source check.vim
+CheckFeature quickfix
set encoding=utf-8
@@ -95,7 +94,7 @@ func XlistTests(cchar)
" Populate the list and then try
Xgetexpr ['non-error 1', 'Xtestfile1:1:3:Line1',
\ 'non-error 2', 'Xtestfile2:2:2:Line2',
- \ 'non-error 3', 'Xtestfile3:3:1:Line3']
+ \ 'non-error| 3', 'Xtestfile3:3:1:Line3']
" List only valid entries
let l = split(execute('Xlist', ''), "\n")
@@ -107,7 +106,7 @@ func XlistTests(cchar)
let l = split(execute('Xlist!', ''), "\n")
call assert_equal([' 1: non-error 1', ' 2 Xtestfile1:1 col 3: Line1',
\ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2',
- \ ' 5: non-error 3', ' 6 Xtestfile3:3 col 1: Line3'], l)
+ \ ' 5: non-error| 3', ' 6 Xtestfile3:3 col 1: Line3'], l)
" List a range of errors
let l = split(execute('Xlist 3,6', ''), "\n")
@@ -507,7 +506,7 @@ func Xtest_browse(cchar)
Xexpr ""
call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
call assert_equal(0, g:Xgetlist({'size' : 0}).size)
- Xaddexpr ['foo', 'bar', 'baz', 'quux', 'shmoo']
+ Xaddexpr ['foo', 'bar', 'baz', 'quux', 'sh|moo']
call assert_equal(5, g:Xgetlist({'size' : 0}).size)
Xlast
call assert_equal(5, g:Xgetlist({'idx' : 0}).idx)
@@ -1284,6 +1283,30 @@ func Test_quickfix_was_changed_by_autocmd()
call XquickfixChangedByAutocmd('l')
endfunc
+func Test_setloclist_in_autocommand()
+ call writefile(['test1', 'test2'], 'Xfile')
+ edit Xfile
+ let s:bufnr = bufnr()
+ call setloclist(1,
+ \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'},
+ \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}])
+
+ augroup Test_LocList
+ au!
+ autocmd BufEnter * call setloclist(1,
+ \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'},
+ \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}], 'r')
+ augroup END
+
+ lopen
+ call assert_fails('exe "normal j\<CR>"', 'E926:')
+
+ augroup Test_LocList
+ au!
+ augroup END
+ call delete('Xfile')
+endfunc
+
func Test_caddbuffer_to_empty()
helpgr quickfix
call setqflist([], 'r')
@@ -1597,6 +1620,24 @@ func Test_long_lines()
call s:long_lines_tests('l')
endfunc
+func Test_cgetfile_on_long_lines()
+ " Problematic values if the line is longer than 4096 bytes. Then 1024 bytes
+ " are read at a time.
+ for len in [4078, 4079, 4080, 5102, 5103, 5104, 6126, 6127, 6128, 7150, 7151, 7152]
+ let lines = [
+ \ '/tmp/file1:1:1:aaa',
+ \ '/tmp/file2:1:1:%s',
+ \ '/tmp/file3:1:1:bbb',
+ \ '/tmp/file4:1:1:ccc',
+ \ ]
+ let lines[1] = substitute(lines[1], '%s', repeat('x', len), '')
+ call writefile(lines, 'Xcqetfile.txt')
+ cgetfile Xcqetfile.txt
+ call assert_equal(4, getqflist(#{size: v:true}).size, 'with length ' .. len)
+ endfor
+ call delete('Xcqetfile.txt')
+endfunc
+
func s:create_test_file(filename)
let l = []
for i in range(1, 20)
@@ -1874,6 +1915,13 @@ func HistoryTest(cchar)
call g:Xsetlist([], 'f')
let l = split(execute(a:cchar . 'hist'), "\n")
call assert_equal('No entries', l[0])
+
+ " An empty list should still show the stack history
+ call g:Xsetlist([])
+ let res = split(execute(a:cchar . 'hist'), "\n")
+ call assert_equal('> error list 1 of 1; 0 ' . common, res[0])
+
+ call g:Xsetlist([], 'f')
endfunc
func Test_history()
@@ -2124,6 +2172,9 @@ func Xproperty_tests(cchar)
call assert_equal(['Colors'], newl2.context)
call assert_equal('Line10', newl2.items[0].text)
call g:Xsetlist([], 'f')
+
+ " Cannot specify both a non-empty list argument and a dict argument
+ call assert_fails("call g:Xsetlist([{}], ' ', {})", 'E475:')
endfunc
func Test_qf_property()
@@ -2131,6 +2182,56 @@ func Test_qf_property()
call Xproperty_tests('l')
endfunc
+" Test for setting the current index in the location/quickfix list
+func Xtest_setqfidx(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xgetexpr "F1:10:1:Line1\nF2:20:2:Line2\nF3:30:3:Line3"
+ Xgetexpr "F4:10:1:Line1\nF5:20:2:Line2\nF6:30:3:Line3"
+ Xgetexpr "F7:10:1:Line1\nF8:20:2:Line2\nF9:30:3:Line3"
+
+ call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 2})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'idx' : 2})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 3})
+ Xolder 2
+ Xopen
+ call assert_equal(3, line('.'))
+ Xnewer
+ call assert_equal(2, line('.'))
+ Xnewer
+ call assert_equal(2, line('.'))
+ " Update the current index with the quickfix window open
+ wincmd w
+ call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 3})
+ Xopen
+ call assert_equal(3, line('.'))
+ Xclose
+
+ " Set the current index to the last entry
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : '$'})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ " A large value should set the index to the last index
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 1})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 999})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ " Invalid index values
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : -1})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 0})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 'xx'})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ call assert_fails("call g:Xsetlist([], 'a', {'nr':1, 'idx':[]})", 'E745:')
+
+ call g:Xsetlist([], 'f')
+ new | only
+endfunc
+
+func Test_setqfidx()
+ call Xtest_setqfidx('c')
+ call Xtest_setqfidx('l')
+endfunc
+
" Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands
func QfAutoCmdHandler(loc, cmd)
call add(g:acmds, a:loc . a:cmd)
@@ -2450,6 +2551,57 @@ func Test_vimgrep()
call XvimgrepTests('l')
endfunc
+" Test for incsearch highlighting of the :vimgrep pattern
+" This test used to cause "E315: ml_get: invalid lnum" errors.
+func Test_vimgrep_incsearch()
+ throw 'skipped: Nvim does not support test_override()'
+ enew
+ set incsearch
+ call test_override("char_avail", 1)
+
+ call feedkeys(":2vimgrep assert test_quickfix.vim test_cdo.vim\<CR>", "ntx")
+ let l = getqflist()
+ call assert_equal(2, len(l))
+
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+" Test vimgrep without swap file
+func Test_vimgrep_without_swap_file()
+ let lines =<< trim [SCRIPT]
+ vimgrep grep test_c*
+ call writefile(['done'], 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript')
+ if RunVim([], [], '--clean -n -S Xscript Xscript')
+ call assert_equal(['done'], readfile('Xresult'))
+ endif
+ call delete('Xscript')
+ call delete('Xresult')
+endfunc
+
+func Test_vimgrep_existing_swapfile()
+ call writefile(['match apple with apple'], 'Xapple')
+ call writefile(['swapfile'], '.Xapple.swp')
+ let g:foundSwap = 0
+ let g:ignoreSwapExists = 1
+ augroup grep
+ au SwapExists * let foundSwap = 1 | let v:swapchoice = 'e'
+ augroup END
+ vimgrep apple Xapple
+ call assert_equal(1, g:foundSwap)
+ call assert_match('.Xapple.swo', swapname(''))
+
+ call delete('Xapple')
+ call delete('Xapple.swp')
+ augroup grep
+ au! SwapExists
+ augroup END
+ unlet g:ignoreSwapExists
+endfunc
+
func XfreeTests(cchar)
call s:setup_commands(a:cchar)
@@ -3342,6 +3494,17 @@ func Test_lvimgrep_crash()
augroup QF_Test
au!
augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setloclist(0, [], 'r')
+ augroup END
+ call assert_fails('lvimgrep Test_lvimgrep_crash *', 'E926:')
+ augroup QF_Test
+ au!
+ augroup END
+
enew | only
endfunc
@@ -3432,6 +3595,37 @@ func Test_lhelpgrep_autocmd()
call assert_equal('help', &filetype)
call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
au! QuickFixCmdPost
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setqflist([], 'f')
+ augroup END
+ call assert_fails('helpgrep quickfix', 'E925:')
+ augroup QF_Test
+ au! BufEnter
+ augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setqflist([], 'r')
+ augroup END
+ call assert_fails('helpgrep quickfix', 'E925:')
+ augroup QF_Test
+ au! BufEnter
+ augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setloclist(0, [], 'r')
+ augroup END
+ call assert_fails('lhelpgrep quickfix', 'E926:')
+ augroup QF_Test
+ au! BufEnter
+ augroup END
+
new | only
endfunc
@@ -3457,6 +3651,18 @@ func Test_shorten_fname()
" Displaying the quickfix list should simplify the file path
silent! clist
call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+ " Add a few entries for the same file with different paths and check whether
+ " the buffer name is shortened
+ %bwipe
+ call setqflist([], 'f')
+ call setqflist([{'filename' : 'test_quickfix.vim', 'lnum' : 10},
+ \ {'filename' : '../testdir/test_quickfix.vim', 'lnum' : 20},
+ \ {'filename' : fname, 'lnum' : 30}], ' ')
+ copen
+ call assert_equal(['test_quickfix.vim|10| ',
+ \ 'test_quickfix.vim|20| ',
+ \ 'test_quickfix.vim|30| '], getline(1, '$'))
+ cclose
endfunc
" Quickfix title tests
@@ -3757,6 +3963,52 @@ func Test_curswant()
cclose | helpclose
endfunc
+" Test for opening a file from the quickfix window using CTRL-W <Enter>
+" doesn't leave an empty buffer around.
+func Test_splitview()
+ call s:create_test_file('Xtestfile1')
+ call s:create_test_file('Xtestfile2')
+ new | only
+ let last_bufnr = bufnr('Test_sv_1', 1)
+ let l = ['Xtestfile1:2:Line2', 'Xtestfile2:4:Line4']
+ cgetexpr l
+ copen
+ let numbufs = len(getbufinfo())
+ exe "normal \<C-W>\<CR>"
+ copen
+ exe "normal j\<C-W>\<CR>"
+ " Make sure new empty buffers are not created
+ call assert_equal(numbufs, len(getbufinfo()))
+ " Creating a new buffer should use the next available buffer number
+ call assert_equal(last_bufnr + 4, bufnr("Test_sv_2", 1))
+ bwipe Test_sv_1
+ bwipe Test_sv_2
+ new | only
+
+ " When split opening files from location list window, make sure that two
+ " windows doesn't refer to the same location list
+ lgetexpr l
+ let locid = getloclist(0, {'id' : 0}).id
+ lopen
+ exe "normal \<C-W>\<CR>"
+ call assert_notequal(locid, getloclist(0, {'id' : 0}).id)
+ call assert_equal(0, getloclist(0, {'winid' : 0}).winid)
+ new | only
+
+ " When split opening files from a helpgrep location list window, a new help
+ " window should be opend with a copy of the location list.
+ lhelpgrep window
+ let locid = getloclist(0, {'id' : 0}).id
+ lwindow
+ exe "normal j\<C-W>\<CR>"
+ call assert_notequal(locid, getloclist(0, {'id' : 0}).id)
+ call assert_equal(0, getloclist(0, {'winid' : 0}).winid)
+ new | only
+
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+endfunc
+
" Test for parsing entries using visual screen column
func Test_viscol()
enew
@@ -3808,11 +4060,102 @@ func Test_viscol()
cnext
call assert_equal([16, 25], [col('.'), virtcol('.')])
+ " Use screen column number with a multi-line error message
+ enew
+ call writefile(["à test"], 'Xfile1')
+ set efm=%E===\ %f\ ===,%C%l:%v,%Z%m
+ cexpr ["=== Xfile1 ===", "1:3", "errormsg"]
+ call assert_equal('Xfile1', @%)
+ call assert_equal([0, 1, 4, 0], getpos('.'))
+
+ " Repeat previous test with byte offset %c: ensure that fix to issue #7145
+ " does not break this
+ set efm=%E===\ %f\ ===,%C%l:%c,%Z%m
+ cexpr ["=== Xfile1 ===", "1:3", "errormsg"]
+ call assert_equal('Xfile1', @%)
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
enew | only
set efm&
call delete('Xfile1')
endfunc
+" Test for the quickfix window buffer
+func Xqfbuf_test(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Quickfix buffer should be reused across closing and opening a quickfix
+ " window
+ Xexpr "F1:10:Line10"
+ Xopen
+ let qfbnum = bufnr('')
+ Xclose
+ " Even after the quickfix window is closed, the buffer should be loaded
+ call assert_true(bufloaded(qfbnum))
+ Xopen
+ " Buffer should be reused when opening the window again
+ call assert_equal(qfbnum, bufnr(''))
+ Xclose
+
+ if a:cchar == 'l'
+ %bwipe
+ " For a location list, when both the file window and the location list
+ " window for the list are closed, then the buffer should be freed.
+ new | only
+ lexpr "F1:10:Line10"
+ let wid = win_getid()
+ lopen
+ let qfbnum = bufnr('')
+ call assert_match(qfbnum . ' %a- "\[Location List]"', execute('ls'))
+ close
+ " When the location list window is closed, the buffer name should not
+ " change to 'Quickfix List'
+ call assert_match(qfbnum . ' h- "\[Location List]"', execute('ls'))
+ call assert_true(bufloaded(qfbnum))
+
+ " After deleting a location list buffer using ":bdelete", opening the
+ " location list window should mark the buffer as a location list buffer.
+ exe "bdelete " . qfbnum
+ lopen
+ call assert_equal("quickfix", &buftype)
+ call assert_equal(1, getwininfo(win_getid(winnr()))[0].loclist)
+ call assert_equal(wid, getloclist(0, {'filewinid' : 0}).filewinid)
+ call assert_false(&swapfile)
+ lclose
+
+ " When the location list is cleared for the window, the buffer should be
+ " removed
+ call setloclist(0, [], 'f')
+ call assert_false(bufexists(qfbnum))
+
+ " When the location list is freed with the location list window open, the
+ " location list buffer should not be lost. It should be reused when the
+ " location list is again populated.
+ lexpr "F1:10:Line10"
+ lopen
+ let wid = win_getid()
+ let qfbnum = bufnr('')
+ wincmd p
+ call setloclist(0, [], 'f')
+ lexpr "F1:10:Line10"
+ lopen
+ call assert_equal(wid, win_getid())
+ call assert_equal(qfbnum, bufnr(''))
+ lclose
+
+ " When the window with the location list is closed, the buffer should be
+ " removed
+ new | only
+ call assert_false(bufexists(qfbnum))
+ endif
+endfunc
+
+func Test_qfbuf()
+ throw 'skipped: enable after porting patch 8.1.0877'
+ call Xqfbuf_test('c')
+ call Xqfbuf_test('l')
+endfunc
+
" Test to make sure that an empty quickfix buffer is not reused for loading
" a normal buffer.
func Test_empty_qfbuf()
@@ -4021,4 +4364,46 @@ func Test_qfcmd_abort()
augroup END
endfunc
+" Test for using a file in one of the parent directories.
+func Test_search_in_dirstack()
+ call mkdir('Xtestdir/a/b/c', 'p')
+ let save_cwd = getcwd()
+ call writefile(["X1_L1", "X1_L2"], 'Xtestdir/Xfile1')
+ call writefile(["X2_L1", "X2_L2"], 'Xtestdir/a/Xfile2')
+ call writefile(["X3_L1", "X3_L2"], 'Xtestdir/a/b/Xfile3')
+ call writefile(["X4_L1", "X4_L2"], 'Xtestdir/a/b/c/Xfile4')
+
+ let lines = "Entering dir Xtestdir\n" .
+ \ "Entering dir a\n" .
+ \ "Entering dir b\n" .
+ \ "Xfile2:2:X2_L2\n" .
+ \ "Leaving dir a\n" .
+ \ "Xfile1:2:X1_L2\n" .
+ \ "Xfile3:1:X3_L1\n" .
+ \ "Entering dir c\n" .
+ \ "Xfile4:2:X4_L2\n" .
+ \ "Leaving dir c\n"
+ set efm=%DEntering\ dir\ %f,%XLeaving\ dir\ %f,%f:%l:%m
+ cexpr lines .. "Leaving dir Xtestdir|\n" | let next = 1
+ call assert_equal(11, getqflist({'size' : 0}).size)
+ call assert_equal(4, getqflist({'idx' : 0}).idx)
+ call assert_equal('X2_L2', getline('.'))
+ call assert_equal(1, next)
+ cnext
+ call assert_equal(6, getqflist({'idx' : 0}).idx)
+ call assert_equal('X1_L2', getline('.'))
+ cnext
+ call assert_equal(7, getqflist({'idx' : 0}).idx)
+ call assert_equal(1, line('$'))
+ call assert_equal('', getline(1))
+ cnext
+ call assert_equal(9, getqflist({'idx' : 0}).idx)
+ call assert_equal(1, line('$'))
+ call assert_equal('', getline(1))
+
+ set efm&
+ exe 'cd ' . save_cwd
+ call delete('Xtestdir', 'rf')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ruby.vim b/src/nvim/testdir/test_ruby.vim
index 64199570a9..07ad8561c3 100644
--- a/src/nvim/testdir/test_ruby.vim
+++ b/src/nvim/testdir/test_ruby.vim
@@ -11,37 +11,6 @@ func Test_ruby_change_buffer()
call assert_equal('1 changed line 1', getline('$'))
endfunc
-func Test_ruby_evaluate_list()
- throw 'skipped: TODO: '
- call setline(line('$'), ['2 line 2'])
- ruby Vim.command("normal /^2\n")
- let l = ["abc", "def"]
- ruby << EOF
- curline = $curbuf.line_number
- l = Vim.evaluate("l");
- $curbuf.append(curline, l.join("\n"))
-EOF
- normal j
- .rubydo $_ = $_.gsub(/\n/, '/')
- call assert_equal('abc/def', getline('$'))
-endfunc
-
-func Test_ruby_evaluate_dict()
- let d = {'a': 'foo', 'b': 123}
- redir => l:out
- ruby d = Vim.evaluate("d"); print d
- redir END
- call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n"))
-endfunc
-
-func Test_ruby_evaluate_special_var()
- let l = [v:true, v:false, v:null]
- redir => l:out
- ruby d = Vim.evaluate("l"); print d
- redir END
- call assert_equal(['[true, false, nil]'], split(l:out, "\n"))
-endfunc
-
func Test_rubydo()
throw 'skipped: TODO: '
" Check deleting lines does not trigger ml_get error.
@@ -56,8 +25,7 @@ func Test_rubydo()
call setline(1, ['one', 'two', 'three'])
rubydo Vim.command("new")
call assert_equal(wincount + 1, winnr('$'))
- bwipe!
- bwipe!
+ %bwipe!
endfunc
func Test_rubyfile()
@@ -75,8 +43,350 @@ func Test_set_cursor()
normal gg
rubydo $curwin.cursor = [1, 5]
call assert_equal([1, 6], [line('.'), col('.')])
+ call assert_equal([1, 5], rubyeval('$curwin.cursor'))
" Check that movement after setting cursor position keeps current column.
normal j
call assert_equal([2, 6], [line('.'), col('.')])
+ call assert_equal([2, 5], rubyeval('$curwin.cursor'))
+
+ " call assert_fails('ruby $curwin.cursor = [1]',
+ " \ 'ArgumentError: array length must be 2')
+ bwipe!
+endfunc
+
+" Test buffer.count and buffer.length (number of lines in buffer)
+func Test_buffer_count()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ call assert_equal(3, rubyeval('$curbuf.count'))
+ call assert_equal(3, rubyeval('$curbuf.length'))
+ bwipe!
+endfunc
+
+" Test buffer.name (buffer name)
+func Test_buffer_name()
+ new Xfoo
+ call assert_equal(expand('%:p'), rubyeval('$curbuf.name'))
+ bwipe
+ call assert_equal('', rubyeval('$curbuf.name'))
+endfunc
+
+" Test buffer.number (number of the buffer).
+func Test_buffer_number()
+ new
+ call assert_equal(bufnr('%'), rubyeval('$curbuf.number'))
+ new
+ call assert_equal(bufnr('%'), rubyeval('$curbuf.number'))
+
+ %bwipe
+endfunc
+
+" Test buffer.delete({n}) (delete line {n})
+func Test_buffer_delete()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ ruby $curbuf.delete(2)
+ call assert_equal(['one', 'three'], getline(1, '$'))
+
+ " call assert_fails('ruby $curbuf.delete(0)', 'IndexError: line number 0 out of range')
+ " call assert_fails('ruby $curbuf.delete(3)', 'IndexError: line number 3 out of range')
+ call assert_fails('ruby $curbuf.delete(3)', 'RuntimeError: Index out of bounds')
+
+ bwipe!
+endfunc
+
+" Test buffer.append({str}, str) (append line {str} after line {n})
+func Test_buffer_append()
+ new
+ ruby $curbuf.append(0, 'one')
+ ruby $curbuf.append(1, 'three')
+ ruby $curbuf.append(1, 'two')
+ ruby $curbuf.append(4, 'four')
+
+ call assert_equal(['one', 'two', 'three', '', 'four'], getline(1, '$'))
+
+ " call assert_fails('ruby $curbuf.append(-1, "x")',
+ " \ 'IndexError: line number -1 out of range')
+ call assert_fails('ruby $curbuf.append(-1, "x")',
+ \ 'ArgumentError: Index out of bounds')
+ call assert_fails('ruby $curbuf.append(6, "x")',
+ \ 'RuntimeError: Index out of bounds')
+
+ bwipe!
+endfunc
+
+" Test buffer.line (get or set the current line)
+func Test_buffer_line()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ 2
+ call assert_equal('two', rubyeval('$curbuf.line'))
+
+ ruby $curbuf.line = 'TWO'
+ call assert_equal(['one', 'TWO', 'three'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test buffer.line_number (get current line number)
+func Test_buffer_line_number()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ 2
+ call assert_equal(2, rubyeval('$curbuf.line_number'))
+
+ bwipe!
+endfunc
+
+func Test_buffer_get()
+ new
+ call setline(1, ['one', 'two'])
+ call assert_equal('one', rubyeval('$curbuf[1]'))
+ call assert_equal('two', rubyeval('$curbuf[2]'))
+
+ " call assert_fails('ruby $curbuf[0]',
+ " \ 'IndexError: line number 0 out of range')
+ call assert_fails('ruby $curbuf[3]',
+ \ 'RuntimeError: Index out of bounds')
+
+ bwipe!
+endfunc
+
+func Test_buffer_set()
+ new
+ call setline(1, ['one', 'two'])
+ ruby $curbuf[2] = 'TWO'
+ ruby $curbuf[1] = 'ONE'
+
+ " call assert_fails('ruby $curbuf[0] = "ZERO"',
+ " \ 'IndexError: line number 0 out of range')
+ " call assert_fails('ruby $curbuf[3] = "THREE"',
+ " \ 'IndexError: line number 3 out of range')
+ call assert_fails('ruby $curbuf[3] = "THREE"',
+ \ 'RuntimeError: Index out of bounds')
+ bwipe!
+endfunc
+
+" Test window.width (get or set window height).
+func Test_window_height()
+ new
+
+ " Test setting window height
+ ruby $curwin.height = 2
+ call assert_equal(2, winheight(0))
+
+ " Test getting window height
+ call assert_equal(2, rubyeval('$curwin.height'))
+
+ bwipe
+endfunc
+
+" Test window.width (get or set window width).
+func Test_window_width()
+ vnew
+
+ " Test setting window width
+ ruby $curwin.width = 2
+ call assert_equal(2, winwidth(0))
+
+ " Test getting window width
+ call assert_equal(2, rubyeval('$curwin.width'))
+
+ bwipe
+endfunc
+
+" Test window.buffer (get buffer object of a window object).
+func Test_window_buffer()
+ new Xfoo1
+ new Xfoo2
+ ruby $b2 = $curwin.buffer
+ ruby $w2 = $curwin
+ wincmd j
+ ruby $b1 = $curwin.buffer
+ ruby $w1 = $curwin
+
+ " call assert_equal(rubyeval('$b1'), rubyeval('$w1.buffer'))
+ " call assert_equal(rubyeval('$b2'), rubyeval('$w2.buffer'))
+ call assert_equal(bufnr('Xfoo1'), rubyeval('$w1.buffer.number'))
+ call assert_equal(bufnr('Xfoo2'), rubyeval('$w2.buffer.number'))
+
+ ruby $b1, $w1, $b2, $w2 = nil
+ %bwipe
+endfunc
+
+" Test Vim::Window.current (get current window object)
+func Test_Vim_window_current()
+ let cw = rubyeval('$curwin.to_s')
+ " call assert_equal(cw, rubyeval('Vim::Window.current'))
+ call assert_match('^#<Neovim::Window:0x\x\+>$', cw)
+endfunc
+
+" Test Vim::Window.count (number of windows)
+func Test_Vim_window_count()
+ new Xfoo1
+ new Xfoo2
+ split
+ call assert_equal(4, rubyeval('Vim::Window.count'))
+ %bwipe
+ call assert_equal(1, rubyeval('Vim::Window.count'))
+endfunc
+
+" Test Vim::Window[n] (get window object of window n)
+func Test_Vim_window_get()
+ new Xfoo1
+ new Xfoo2
+ call assert_match('Xfoo2$', rubyeval('Vim::Window[0].buffer.name'))
+ wincmd j
+ call assert_match('Xfoo1$', rubyeval('Vim::Window[1].buffer.name'))
+ wincmd j
+ call assert_equal('', rubyeval('Vim::Window[2].buffer.name'))
+ %bwipe
+endfunc
+
+" Test Vim::Buffer.current (return the buffer object of current buffer)
+func Test_Vim_buffer_current()
+ let cb = rubyeval('$curbuf.to_s')
+ " call assert_equal(cb, rubyeval('Vim::Buffer.current'))
+ call assert_match('^#<Neovim::Buffer:0x\x\+>$', cb)
+endfunc
+
+" Test Vim::Buffer:.count (return the number of buffers)
+func Test_Vim_buffer_count()
+ new Xfoo1
+ new Xfoo2
+ call assert_equal(3, rubyeval('Vim::Buffer.count'))
+ %bwipe
+ call assert_equal(1, rubyeval('Vim::Buffer.count'))
+endfunc
+
+" Test Vim::buffer[n] (return the buffer object of buffer number n)
+func Test_Vim_buffer_get()
+ new Xfoo1
+ new Xfoo2
+
+ " Index of Vim::Buffer[n] goes from 0 to the number of buffers.
+ call assert_equal('', rubyeval('Vim::Buffer[0].name'))
+ call assert_match('Xfoo1$', rubyeval('Vim::Buffer[1].name'))
+ call assert_match('Xfoo2$', rubyeval('Vim::Buffer[2].name'))
+ call assert_fails('ruby print Vim::Buffer[3].name',
+ \ "NoMethodError: undefined method `name' for nil:NilClass")
+ %bwipe
+endfunc
+
+" Test Vim::command({cmd}) (execute a Ex command))
+" Test Vim::command({cmd})
+func Test_Vim_command()
+ new
+ call setline(1, ['one', 'two', 'three', 'four'])
+ ruby Vim::command('2,3d')
+ call assert_equal(['one', 'four'], getline(1, '$'))
+ bwipe!
+endfunc
+
+" Test Vim::set_option (set a vim option)
+func Test_Vim_set_option()
+ call assert_equal(0, &number)
+ ruby Vim::set_option('number')
+ call assert_equal(1, &number)
+ ruby Vim::set_option('nonumber')
+ call assert_equal(0, &number)
+endfunc
+
+func Test_Vim_evaluate()
+ call assert_equal(123, rubyeval('Vim::evaluate("123")'))
+ " Vim::evaluate("123").class gives Integer or Fixnum depending
+ " on versions of Ruby.
+ call assert_match('^Integer\|Fixnum$', rubyeval('Vim::evaluate("123").class'))
+
+ call assert_equal(1.23, rubyeval('Vim::evaluate("1.23")'))
+ call assert_equal('Float', rubyeval('Vim::evaluate("1.23").class'))
+
+ call assert_equal('foo', rubyeval('Vim::evaluate("\"foo\"")'))
+ call assert_equal('String', rubyeval('Vim::evaluate("\"foo\"").class'))
+
+ call assert_equal([1, 2], rubyeval('Vim::evaluate("[1, 2]")'))
+ call assert_equal('Array', rubyeval('Vim::evaluate("[1, 2]").class'))
+
+ call assert_equal({'1': 2}, rubyeval('Vim::evaluate("{1:2}")'))
+ call assert_equal('Hash', rubyeval('Vim::evaluate("{1:2}").class'))
+
+ call assert_equal(v:null, rubyeval('Vim::evaluate("v:null")'))
+ call assert_equal('NilClass', rubyeval('Vim::evaluate("v:null").class'))
+
+ " call assert_equal(v:null, rubyeval('Vim::evaluate("v:none")'))
+ " call assert_equal('NilClass', rubyeval('Vim::evaluate("v:none").class'))
+
+ call assert_equal(v:true, rubyeval('Vim::evaluate("v:true")'))
+ call assert_equal('TrueClass', rubyeval('Vim::evaluate("v:true").class'))
+ call assert_equal(v:false, rubyeval('Vim::evaluate("v:false")'))
+ call assert_equal('FalseClass',rubyeval('Vim::evaluate("v:false").class'))
+endfunc
+
+func Test_Vim_evaluate_list()
+ call setline(line('$'), ['2 line 2'])
+ ruby Vim.command("normal /^2\n")
+ let l = ["abc", "def"]
+ ruby << EOF
+ curline = $curbuf.line_number
+ l = Vim.evaluate("l");
+ $curbuf.append(curline, l.join("|"))
+EOF
+ normal j
+ .rubydo $_ = $_.gsub(/\|/, '/')
+ call assert_equal('abc/def', getline('$'))
+endfunc
+
+func Test_Vim_evaluate_dict()
+ let d = {'a': 'foo', 'b': 123}
+ redir => l:out
+ ruby d = Vim.evaluate("d"); print d
+ redir END
+ call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n"))
+endfunc
+
+" Test Vim::message({msg}) (display message {msg})
+func Test_Vim_message()
+ throw 'skipped: TODO: '
+ ruby Vim::message('A message')
+ let messages = split(execute('message'), "\n")
+ call assert_equal('A message', messages[-1])
+endfunc
+
+func Test_print()
+ func RubyPrint(expr)
+ return trim(execute('ruby print ' . a:expr))
+ endfunc
+
+ call assert_equal('123', RubyPrint('123'))
+ call assert_equal('1.23', RubyPrint('1.23'))
+ call assert_equal('Hello World!', RubyPrint('"Hello World!"'))
+ call assert_equal('[1, 2]', RubyPrint('[1, 2]'))
+ call assert_equal('{"k1"=>"v1", "k2"=>"v2"}', RubyPrint('({"k1" => "v1", "k2" => "v2"})'))
+ call assert_equal('true', RubyPrint('true'))
+ call assert_equal('false', RubyPrint('false'))
+ call assert_equal('', RubyPrint('nil'))
+ call assert_match('Vim', RubyPrint('Vim'))
+ call assert_match('Module', RubyPrint('Vim.class'))
+
+ delfunc RubyPrint
+endfunc
+
+func Test_p()
+ ruby p 'Just a test'
+ let messages = split(execute('message'), "\n")
+ call assert_equal('"Just a test"', messages[-1])
+
+ " Check return values of p method
+
+ call assert_equal(123, rubyeval('p(123)'))
+ call assert_equal([1, 2, 3], rubyeval('p(1, 2, 3)'))
+
+ " Avoid the "message maintainer" line.
+ let $LANG = ''
+ messages clear
+ call assert_equal(v:true, rubyeval('p() == nil'))
+
+ let messages = split(execute('message'), "\n")
+ call assert_equal(0, len(messages))
endfunc
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 8036dea29f..5db23c22a8 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -244,6 +244,10 @@ func Test_search_cmdline2()
" go to previous match (on line 2)
call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<C-T>\<cr>", 'tx')
call assert_equal(' 2 these', getline('.'))
+ 1
+ " go to previous match (on line 2)
+ call feedkeys("/the\<C-G>\<C-R>\<C-W>\<cr>", 'tx')
+ call assert_equal('theother', @/)
" Test 2: keep the view,
" after deleting a character from the search cmd
@@ -255,7 +259,7 @@ func Test_search_cmdline2()
call assert_equal({'lnum': 10, 'leftcol': 0, 'col': 4, 'topfill': 0, 'topline': 6, 'coladd': 0, 'skipcol': 0, 'curswant': 4}, winsaveview())
" remove all history entries
- for i in range(10)
+ for i in range(11)
call histdel('/')
endfor
@@ -346,25 +350,104 @@ func Test_searchc()
bw!
endfunc
-func Test_search_cmdline3()
+func Cmdline3_prep()
throw 'skipped: Nvim does not support test_override()'
- if !exists('+incsearch')
- return
- endif
" need to disable char_avail,
" so that expansion of commandline works
call test_override("char_avail", 1)
new
call setline(1, [' 1', ' 2 the~e', ' 3 the theother'])
set incsearch
+endfunc
+
+func Incsearch_cleanup()
+ throw 'skipped: Nvim does not support test_override()'
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
+func Test_search_cmdline3()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
1
" first match
call feedkeys("/the\<c-l>\<cr>", 'tx')
call assert_equal(' 2 the~e', getline('.'))
- " clean up
- set noincsearch
- call test_override("char_avail", 0)
- bw!
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline3s()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
+ 1
+ call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxxe', getline('.'))
+ undo
+ call feedkeys(":%subs/the\<c-l>/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxxe', getline('.'))
+ undo
+ call feedkeys(":%substitute/the\<c-l>/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxxe', getline('.'))
+ undo
+ call feedkeys(":%smagic/the.e/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxx', getline('.'))
+ undo
+ call assert_fails(":%snomagic/the.e/xxx\<cr>", 'E486')
+ "
+ call feedkeys(":%snomagic/the\\.e/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxx', getline('.'))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline3g()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
+ 1
+ call feedkeys(":g/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline(2))
+ undo
+ call feedkeys(":global/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline(2))
+ undo
+ call feedkeys(":g!/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+ undo
+ call feedkeys(":global!/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline3v()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
+ 1
+ call feedkeys(":v/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+ undo
+ call feedkeys(":vglobal/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+
+ call Incsearch_cleanup()
endfunc
func Test_search_cmdline4()
@@ -410,19 +493,58 @@ func Test_search_cmdline5()
" Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work
" regardless char_avail.
new
- call setline(1, [' 1 the first', ' 2 the second', ' 3 the third'])
+ call setline(1, [' 1 the first', ' 2 the second', ' 3 the third', ''])
set incsearch
1
call feedkeys("/the\<c-g>\<c-g>\<cr>", 'tx')
call assert_equal(' 3 the third', getline('.'))
$
call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx')
- call assert_equal(' 2 the second', getline('.'))
+ call assert_equal(' 1 the first', getline('.'))
" clean up
set noincsearch
bw!
endfunc
+func Test_search_cmdline7()
+ throw 'skipped: Nvim does not support test_override()'
+ " Test that an pressing <c-g> in an empty command line
+ " does not move the cursor
+ if !exists('+incsearch')
+ return
+ endif
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ let @/ = 'b'
+ call setline(1, [' bbvimb', ''])
+ set incsearch
+ " first match
+ norm! gg0
+ " moves to next match of previous search pattern, just like /<cr>
+ call feedkeys("/\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,2,0], getpos('.'))
+ " moves to next match of previous search pattern, just like /<cr>
+ call feedkeys("/\<cr>", 'tx')
+ call assert_equal([0,1,3,0], getpos('.'))
+ " moves to next match of previous search pattern, just like /<cr>
+ call feedkeys("/\<c-t>\<cr>", 'tx')
+ call assert_equal([0,1,7,0], getpos('.'))
+
+ " using an offset uses the last search pattern
+ call cursor(1, 1)
+ call setline(1, ['1 bbvimb', ' 2 bbvimb'])
+ let @/ = 'b'
+ call feedkeys("//e\<c-g>\<cr>", 'tx')
+ call assert_equal('1 bbvimb', getline('.'))
+ call assert_equal(4, col('.'))
+
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
" Tests for regexp with various magic settings
func Test_search_regexp()
enew!
@@ -504,6 +626,7 @@ func Test_incsearch_substitute_dump()
\ 'for n in range(1, 10)',
\ ' call setline(n, "foo " . n)',
\ 'endfor',
+ \ 'call setline(11, "bar 11")',
\ '3',
\ ], 'Xis_subst_script')
let buf = RunVimInTerminal('-S Xis_subst_script', {'rows': 9, 'cols': 70})
@@ -512,6 +635,7 @@ func Test_incsearch_substitute_dump()
sleep 100m
" Need to send one key at a time to force a redraw.
+ " Select three lines at the cursor with typed pattern.
call term_sendkeys(buf, ':.,.+2s/')
sleep 100m
call term_sendkeys(buf, 'f')
@@ -520,12 +644,219 @@ func Test_incsearch_substitute_dump()
sleep 100m
call term_sendkeys(buf, 'o')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Select three lines at the cursor using previous pattern.
+ call term_sendkeys(buf, "/foo\<CR>")
+ sleep 100m
+ call term_sendkeys(buf, ':.,.+2s//')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_02', {})
+
+ " Deleting last slash should remove the match.
+ call term_sendkeys(buf, "\<BS>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_03', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Reverse range is accepted
+ call term_sendkeys(buf, ':5,2s/foo')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_04', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " White space after the command is skipped
+ call term_sendkeys(buf, ':2,3sub /fo')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_05', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Command modifiers are skipped
+ call term_sendkeys(buf, ':above below browse botr confirm keepmar keepalt keeppat keepjum filter xxx hide lockm leftabove noau noswap rightbel sandbox silent silent! $tab top unsil vert verbose 4,5s/fo.')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_06', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Cursorline highlighting at match
+ call term_sendkeys(buf, ":set cursorline\<CR>")
+ call term_sendkeys(buf, 'G9G')
+ call term_sendkeys(buf, ':9,11s/bar')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_07', {})
+ call term_sendkeys(buf, "\<Esc>")
+ " Cursorline highlighting at cursor when no match
+ call term_sendkeys(buf, ':9,10s/bar')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_08', {})
call term_sendkeys(buf, "\<Esc>")
+
+ " Only \v handled as empty pattern, does not move cursor
+ call term_sendkeys(buf, '3G4G')
+ call term_sendkeys(buf, ":nohlsearch\<CR>")
+ call term_sendkeys(buf, ':6,7s/\v')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_09', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ":set nocursorline\<CR>")
+
+ " All matches are highlighted for 'hlsearch' after the incsearch canceled
+ call term_sendkeys(buf, "1G*")
+ call term_sendkeys(buf, ":2,5s/foo")
+ sleep 100m
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_10', {})
+
+ call term_sendkeys(buf, ":split\<CR>")
+ call term_sendkeys(buf, ":let @/ = 'xyz'\<CR>")
+ call term_sendkeys(buf, ":%s/.")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_11', {})
+ call term_sendkeys(buf, "\<BS>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_12', {})
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_13', {})
+ call term_sendkeys(buf, ":%bwipe!\<CR>")
+ call term_sendkeys(buf, ":only!\<CR>")
+
+ " get :'<,'>s command in history
+ call term_sendkeys(buf, ":set cmdheight=2\<CR>")
+ call term_sendkeys(buf, "aasdfasdf\<Esc>")
+ call term_sendkeys(buf, "V:s/a/b/g\<CR>")
+ " Using '<,'> does not give E20
+ call term_sendkeys(buf, ":new\<CR>")
+ call term_sendkeys(buf, "aasdfasdf\<Esc>")
+ call term_sendkeys(buf, ":\<Up>\<Up>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_14', {})
+ call term_sendkeys(buf, "<Esc>")
+
call StopVimInTerminal(buf)
call delete('Xis_subst_script')
endfunc
+" Similar to Test_incsearch_substitute_dump() for :sort
+func Test_incsearch_sort_dump()
+ if !exists('+incsearch')
+ return
+ endif
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
+ \ ], 'Xis_sort_script')
+ let buf = RunVimInTerminal('-S Xis_sort_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ call term_sendkeys(buf, ':sort ni u /on')
+ call VerifyScreenDump(buf, 'Test_incsearch_sort_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':sort! /on')
+ call VerifyScreenDump(buf, 'Test_incsearch_sort_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_sort_script')
+endfunc
+
+" Similar to Test_incsearch_substitute_dump() for :vimgrep famiry
+func Test_incsearch_vimgrep_dump()
+ if !exists('+incsearch')
+ return
+ endif
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
+ \ ], 'Xis_vimgrep_script')
+ let buf = RunVimInTerminal('-S Xis_vimgrep_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ " Need to send one key at a time to force a redraw.
+ call term_sendkeys(buf, ':vimgrep on')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':vimg /on/ *.txt')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':vimgrepadd "\<on')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_03', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':lv "tha')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_04', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':lvimgrepa "the" **/*.txt')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_05', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_vimgrep_script')
+endfunc
+
+func Test_keep_last_search_pattern()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ new
+ call setline(1, ['foo', 'foo', 'foo'])
+ set incsearch
+ call test_override("char_avail", 1)
+ let @/ = 'bar'
+ call feedkeys(":/foo/s//\<Esc>", 'ntx')
+ call assert_equal('bar', @/)
+
+ " no error message if pattern not found
+ call feedkeys(":/xyz/s//\<Esc>", 'ntx')
+ call assert_equal('bar', @/)
+
+ bwipe!
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+func Test_word_under_cursor_after_match()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ new
+ call setline(1, 'foo bar')
+ set incsearch
+ call test_override("char_avail", 1)
+ try
+ call feedkeys("/foo\<C-R>\<C-W>\<CR>", 'ntx')
+ catch /E486:/
+ endtry
+ call assert_equal('foobar', @/)
+
+ bwipe!
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+func Test_subst_word_under_cursor()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ new
+ call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)'])
+ set incsearch
+ call test_override("char_avail", 1)
+ call feedkeys("/LongName\<CR>", 'ntx')
+ call feedkeys(":%s/xxx/\<C-R>\<C-W>/g\<CR>", 'ntx')
+ call assert_equal('for (SomeLongName = 1; SomeLongName < len; ++SomeLongName)', getline(2))
+
+ bwipe!
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
func Test_incsearch_with_change()
if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal()
throw 'Skipped: cannot make screendumps and/or timers feature and/or incsearch option missing'
@@ -550,6 +881,21 @@ func Test_incsearch_with_change()
call delete('Xis_change_script')
endfunc
+func Test_incsearch_cmdline_modifier()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call test_override("char_avail", 1)
+ new
+ call setline(1, ['foo'])
+ set incsearch
+ " Test that error E14 does not occur in parsing command modifier.
+ call feedkeys("V:tab", 'tx')
+
+ call Incsearch_cleanup()
+endfunc
+
func Test_incsearch_scrolling()
if !CanRunVimInTerminal()
return
@@ -580,6 +926,76 @@ func Test_incsearch_scrolling()
call delete('Xscript')
endfunc
+func Test_incsearch_search_dump()
+ if !exists('+incsearch')
+ return
+ endif
+ if !CanRunVimInTerminal()
+ return
+ endif
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'for n in range(1, 8)',
+ \ ' call setline(n, "foo " . n)',
+ \ 'endfor',
+ \ '3',
+ \ ], 'Xis_search_script')
+ let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ " Need to send one key at a time to force a redraw.
+ call term_sendkeys(buf, '/fo')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+ sleep 100m
+
+ call term_sendkeys(buf, '/\v')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_search_script')
+endfunc
+
+func Test_incsearch_substitute()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call test_override("char_avail", 1)
+ new
+ set incsearch
+ for n in range(1, 10)
+ call setline(n, 'foo ' . n)
+ endfor
+ 4
+ call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
+ call assert_equal('foo 3', getline(3))
+ call assert_equal('xxx 4', getline(4))
+ call assert_equal('xxx 5', getline(5))
+ call assert_equal('xxx 6', getline(6))
+ call assert_equal('foo 7', getline(7))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_incsearch_substitute_long_line()
+ throw 'skipped: Nvim does not support test_override()'
+ new
+ call test_override("char_avail", 1)
+ set incsearch
+
+ call repeat('x', 100000)->setline(1)
+ call feedkeys(':s/\%c', 'xt')
+ redraw
+ call feedkeys("\<Esc>", 'xt')
+
+ call Incsearch_cleanup()
+ bwipe!
+endfunc
+
func Test_search_undefined_behaviour()
if !has("terminal")
return
@@ -616,6 +1032,40 @@ func Test_search_sentence()
/
endfunc
+" Test that there is no crash when there is a last search pattern but no last
+" substitute pattern.
+func Test_no_last_substitute_pat()
+ " Use viminfo to set the last search pattern to a string and make the last
+ " substitute pattern the most recent used and make it empty (NULL).
+ call writefile(['~MSle0/bar', '~MSle0~&'], 'Xviminfo')
+ rviminfo! Xviminfo
+ call assert_fails('normal n', 'E35:')
+
+ call delete('Xviminfo')
+endfunc
+
+func Test_search_Ctrl_L_combining()
+ " Make sure, that Ctrl-L works correctly with combining characters.
+ " It uses an artificial example of an 'a' with 4 combining chars:
+ " 'a' U+0061 Dec:97 LATIN SMALL LETTER A &#x61; /\%u61\Z "\u0061"
+ " ' ̀' U+0300 Dec:768 COMBINING GRAVE ACCENT &#x300; /\%u300\Z "\u0300"
+ " ' ́' U+0301 Dec:769 COMBINING ACUTE ACCENT &#x301; /\%u301\Z "\u0301"
+ " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE &#x307; /\%u307\Z "\u0307"
+ " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
+ " Those should also appear on the commandline
+ if !has('multi_byte') || !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
+ 1
+ let bufcontent = ['', 'Miạ̀́̇m']
+ call append('$', bufcontent)
+ call feedkeys("/Mi\<c-l>\<c-l>\<cr>", 'tx')
+ call assert_equal(5, line('.'))
+ call assert_equal(bufcontent[1], @/)
+ call Incsearch_cleanup()
+endfunc
+
func Test_large_hex_chars1()
" This used to cause a crash, the character becomes an NFA state.
try
@@ -653,6 +1103,24 @@ func Test_one_error_msg()
call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:')
endfunc
+func Test_incsearch_add_char_under_cursor()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ set incsearch
+ new
+ call setline(1, ['find match', 'anything'])
+ 1
+ call test_override('char_avail', 1)
+ call feedkeys("fc/m\<C-L>\<C-L>\<C-L>\<C-L>\<C-L>\<CR>", 'tx')
+ call assert_equal('match', @/)
+ call test_override('char_avail', 0)
+
+ set incsearch&
+ bwipe!
+endfunc
+
" Test for the search() function with match at the cursor position
func Test_search_match_at_curpos()
new
diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim
index cf36f3214a..11c6489ca2 100644
--- a/src/nvim/testdir/test_search_stat.vim
+++ b/src/nvim/testdir/test_search_stat.vim
@@ -1,11 +1,9 @@
" Tests for search_stats, when "S" is not in 'shortmess'
-"
-" This test is fragile, it might not work interactively, but it works when run
-" as test!
-source shared.vim
+source screendump.vim
+source check.vim
-func! Test_search_stat()
+func Test_search_stat()
new
set shortmess-=S
" Append 50 lines with text to search for, "foobar" appears 20 times
@@ -45,7 +43,7 @@ func! Test_search_stat()
call assert_match(pat .. stat, g:a)
call cursor(line('$'), 1)
let g:a = execute(':unsilent :norm! n')
- let stat = '\[1/>99\] W'
+ let stat = 'W \[1/>99\]'
call assert_match(pat .. stat, g:a)
" Many matches
@@ -55,7 +53,7 @@ func! Test_search_stat()
call assert_match(pat .. stat, g:a)
call cursor(1, 1)
let g:a = execute(':unsilent :norm! N')
- let stat = '\[>99/>99\] W'
+ let stat = 'W \[>99/>99\]'
call assert_match(pat .. stat, g:a)
" right-left
@@ -87,7 +85,7 @@ func! Test_search_stat()
call cursor('$',1)
let pat = 'raboof/\s\+'
let g:a = execute(':unsilent :norm! n')
- let stat = '\[20/1\]'
+ let stat = 'W \[20/1\]'
call assert_match(pat .. stat, g:a)
call assert_match('search hit BOTTOM, continuing at TOP', g:a)
set norl
@@ -98,10 +96,10 @@ func! Test_search_stat()
let @/ = 'foobar'
let pat = '?foobar\s\+'
let g:a = execute(':unsilent :norm! N')
- let stat = '\[20/20\]'
+ let stat = 'W \[20/20\]'
call assert_match(pat .. stat, g:a)
call assert_match('search hit TOP, continuing at BOTTOM', g:a)
- call assert_match('\[20/20\] W', Screenline(&lines))
+ call assert_match('W \[20/20\]', Screenline(&lines))
" normal, no match
call cursor(1,1)
@@ -160,7 +158,115 @@ func! Test_search_stat()
let stat = '\[1/2\]'
call assert_notmatch(pat .. stat, g:a)
- " close the window
+ " normal, n comes from a silent mapping
+ " First test a normal mapping, then a silent mapping
+ call cursor(1,1)
+ nnoremap n n
+ let @/ = 'find this'
+ let pat = '/find this\s\+'
+ let g:a = execute(':unsilent :norm n')
+ let g:b = split(g:a, "\n")[-1]
+ let stat = '\[1/2\]'
+ call assert_match(pat .. stat, g:b)
+ nnoremap <silent> n n
+ call cursor(1,1)
+ let g:a = execute(':unsilent :norm n')
+ let g:b = split(g:a, "\n")[-1]
+ let stat = '\[1/2\]'
+ call assert_notmatch(pat .. stat, g:b)
+ call assert_match(stat, g:b)
+ " Test that the message is not truncated
+ " it would insert '...' into the output.
+ call assert_match('^\s\+' .. stat, g:b)
+ unmap n
+
+ " Clean up
set shortmess+=S
+ " close the window
bwipe!
endfunc
+
+func Test_search_stat_foldopen()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ setl foldenable foldmethod=indent foldopen-=search
+ call append(0, ['if', "\tfoo", "\tfoo", 'endif'])
+ let @/ = 'foo'
+ call cursor(1,1)
+ norm n
+ END
+ call writefile(lines, 'Xsearchstat1')
+
+ let buf = RunVimInTerminal('-S Xsearchstat1', #{rows: 10})
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_3', {})
+
+ call term_sendkeys(buf, "n")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_3', {})
+
+ call term_sendkeys(buf, "n")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_3', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xsearchstat1')
+endfunc
+
+func! Test_search_stat_screendump()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ " Append 50 lines with text to search for, "foobar" appears 20 times
+ call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 20))
+ call setline(2, 'find this')
+ call setline(70, 'find this')
+ nnoremap n n
+ let @/ = 'find this'
+ call cursor(1,1)
+ norm n
+ END
+ call writefile(lines, 'Xsearchstat')
+ let buf = RunVimInTerminal('-S Xsearchstat', #{rows: 10})
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_1', {})
+
+ call term_sendkeys(buf, ":nnoremap <silent> n n\<cr>")
+ call term_sendkeys(buf, "gg0n")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_2', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xsearchstat')
+endfunc
+
+func Test_searchcount_in_statusline()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ call append(0, 'this is something')
+ function TestSearchCount() abort
+ let search_count = searchcount()
+ if !empty(search_count)
+ return '[' . search_count.current . '/' . search_count.total . ']'
+ else
+ return ''
+ endif
+ endfunction
+ set hlsearch
+ set laststatus=2 statusline+=%{TestSearchCount()}
+ END
+ call writefile(lines, 'Xsearchstatusline')
+ let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10})
+ call TermWait(buf)
+ call term_sendkeys(buf, "/something")
+ call VerifyScreenDump(buf, 'Test_searchstat_4', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xsearchstatusline')
+endfunc
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index 414c7278eb..ab8a998bb8 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -79,6 +79,11 @@ func Test_spellbadword()
call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.'))
call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence'))
+ call assert_equal(['TheCamelWord', 'bad'], spellbadword('TheCamelWord asdf'))
+ set spelloptions=camel
+ call assert_equal(['asdf', 'bad'], spellbadword('TheCamelWord asdf'))
+ set spelloptions=
+
set spelllang=en
call assert_equal(['', ''], spellbadword('centre'))
call assert_equal(['', ''], spellbadword('center'))
@@ -113,6 +118,43 @@ foobar/?
set spell&
endfunc
+func Test_spelllang_inv_region()
+ set spell spelllang=en_xx
+ let messages = GetMessages()
+ call assert_equal('Warning: region xx not supported', messages[-1])
+ set spell& spelllang&
+endfunc
+
+func Test_compl_with_CTRL_X_CTRL_K_using_spell()
+ " When spell checking is enabled and 'dictionary' is empty,
+ " CTRL-X CTRL-K in insert mode completes using the spelling dictionary.
+ new
+ set spell spelllang=en dictionary=
+
+ set ignorecase
+ call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['English'], getline(1, '$'))
+ call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['English'], getline(1, '$'))
+
+ set noignorecase
+ call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['englis'], getline(1, '$'))
+ call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['English'], getline(1, '$'))
+
+ set spelllang=en_us
+ call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['theater'], getline(1, '$'))
+ set spelllang=en_gb
+ call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx')
+ " FIXME: commented out, expected theatre bug got theater. See issue #7025.
+ " call assert_equal(['theatre'], getline(1, '$'))
+
+ bwipe!
+ set spell& spelllang& dictionary& ignorecase&
+endfunc
+
func Test_spellreall()
new
set spell
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index 9abaca5957..12bec745a8 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -369,12 +369,11 @@ func Test_invalid_args()
endfor
if has('clientserver')
- " FIXME: need to add --servername to this list
- " but it causes vim-8.1.1282 to crash!
for opt in ['--remote', '--remote-send', '--remote-silent', '--remote-expr',
\ '--remote-tab', '--remote-tab-wait',
\ '--remote-tab-wait-silent', '--remote-tab-silent',
\ '--remote-wait', '--remote-wait-silent',
+ \ '--servername',
\ ]
let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
call assert_equal(1, v:shell_error)
@@ -384,14 +383,21 @@ func Test_invalid_args()
endfor
endif
- " FIXME: commented out as this causes vim-8.1.1282 to crash!
- "if has('clipboard')
- " let out = split(system(GetVimCommand() .. ' --display'), "\n")
- " call assert_equal(1, v:shell_error)
- " call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
- " call assert_equal('Argument missing after: "--display"', out[1])
- " call assert_equal('More info with: "vim -h"', out[2])
- "endif
+ if has('gui_gtk')
+ let out = split(system(GetVimCommand() .. ' --display'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "--display"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endif
+
+ if has('xterm_clipboard')
+ let out = split(system(GetVimCommand() .. ' -display'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "-display"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endif
let out = split(system(GetVimCommand() .. ' -ix'), "\n")
call assert_equal(1, v:shell_error)
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index 8c81ec3431..7efd181d04 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -412,3 +412,22 @@ func Test_statusline_removed_group()
call StopVimInTerminal(buf)
call delete('XTest_statusline')
endfunc
+
+func Test_statusline_after_split_vsplit()
+ only
+
+ " Make the status line of each window show the window number.
+ set ls=2 stl=%{winnr()}
+
+ split | redraw
+ vsplit | redraw
+
+ " The status line of the third window should read '3' here.
+ call assert_equal('3', nr2char(screenchar(&lines - 1, 1)))
+
+ only
+ set ls& stl&
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
index e072e9ed7f..f27920d20f 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/src/nvim/testdir/test_swap.vim
@@ -1,5 +1,7 @@
" Tests for the swap feature
+source check.vim
+
func s:swapname()
return trim(execute('swapname'))
endfunc
@@ -305,3 +307,61 @@ func Test_swap_recover_ext()
augroup END
augroup! test_swap_recover_ext
endfunc
+
+" Test for selecting 'q' in the attention prompt
+func Test_swap_prompt_splitwin()
+ CheckRunVimInTerminal
+
+ call writefile(['foo bar'], 'Xfile1')
+ edit Xfile1
+ preserve " should help to make sure the swap file exists
+
+ let buf = RunVimInTerminal('', {'rows': 20})
+ call term_sendkeys(buf, ":set nomore\n")
+ call term_sendkeys(buf, ":set noruler\n")
+ call term_sendkeys(buf, ":split Xfile1\n")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: $', term_getline(buf, 20))})
+ call term_sendkeys(buf, "q")
+ call term_wait(buf)
+ call term_sendkeys(buf, ":\<CR>")
+ call WaitForAssert({-> assert_match('^:$', term_getline(buf, 20))})
+ call term_sendkeys(buf, ":echomsg winnr('$')\<CR>")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))})
+ call StopVimInTerminal(buf)
+ %bwipe!
+ call delete('Xfile1')
+endfunc
+
+func Test_swap_symlink()
+ if !has("unix")
+ return
+ endif
+
+ call writefile(['text'], 'Xtestfile')
+ silent !ln -s -f Xtestfile Xtestlink
+
+ set dir=.
+
+ " Test that swap file uses the name of the file when editing through a
+ " symbolic link (so that editing the file twice is detected)
+ edit Xtestlink
+ call assert_match('Xtestfile\.swp$', s:swapname())
+ bwipe!
+
+ call mkdir('Xswapdir')
+ exe 'set dir=' . getcwd() . '/Xswapdir//'
+
+ " Check that this also works when 'directory' ends with '//'
+ edit Xtestlink
+ call assert_match('Xtestfile\.swp$', s:swapname())
+ bwipe!
+
+ set dir&
+ call delete('Xtestfile')
+ call delete('Xtestlink')
+ call delete('Xswapdir', 'rf')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 85ee42420e..2617aa3945 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -369,7 +369,11 @@ func Test_ownsyntax()
call setline(1, '#define FOO')
syntax on
set filetype=c
+
ownsyntax perl
+ " this should not crash
+ set
+
call assert_equal('perlComment', synIDattr(synID(line('.'), col('.'), 1), 'name'))
call assert_equal('c', b:current_syntax)
call assert_equal('perl', w:current_syntax)
@@ -471,6 +475,40 @@ func Test_bg_detection()
hi Normal ctermbg=NONE
endfunc
+func Test_syntax_hangs()
+ if !has('reltime') || !has('float') || !has('syntax')
+ return
+ endif
+
+ " This pattern takes a long time to match, it should timeout.
+ new
+ call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
+ let start = reltime()
+ set nolazyredraw redrawtime=101
+ syn match Error /\%#=1a*.*X\@<=b*/
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed > 0.1)
+ call assert_true(elapsed < 1.0)
+
+ " second time syntax HL is disabled
+ let start = reltime()
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed < 0.1)
+
+ " after CTRL-L the timeout flag is reset
+ let start = reltime()
+ exe "normal \<C-L>"
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed > 0.1)
+ call assert_true(elapsed < 1.0)
+
+ set redrawtime&
+ bwipe!
+endfunc
+
func Test_synstack_synIDtrans()
new
setfiletype c
@@ -546,38 +584,42 @@ func Test_syn_wrong_z_one()
bwipe!
endfunc
-func Test_syntax_hangs()
- if !has('reltime') || !has('float') || !has('syntax')
- return
- endif
-
- " This pattern takes a long time to match, it should timeout.
- new
- call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
- let start = reltime()
- set nolazyredraw redrawtime=101
- syn match Error /\%#=1a*.*X\@<=b*/
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed > 0.1)
- call assert_true(elapsed < 1.0)
-
- " second time syntax HL is disabled
- let start = reltime()
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed < 0.1)
+func Test_syntax_after_bufdo()
+ call writefile(['/* aaa comment */'], 'Xaaa.c')
+ call writefile(['/* bbb comment */'], 'Xbbb.c')
+ call writefile(['/* ccc comment */'], 'Xccc.c')
+ call writefile(['/* ddd comment */'], 'Xddd.c')
+
+ let bnr = bufnr('%')
+ new Xaaa.c
+ badd Xbbb.c
+ badd Xccc.c
+ badd Xddd.c
+ exe "bwipe " . bnr
+ let l = []
+ bufdo call add(l, bufnr('%'))
+ call assert_equal(4, len(l))
- " after CTRL-L the timeout flag is reset
- let start = reltime()
- exe "normal \<C-L>"
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed > 0.1)
- call assert_true(elapsed < 1.0)
+ syntax on
- set redrawtime&
- bwipe!
+ " This used to only enable syntax HL in the last buffer.
+ bufdo tab split
+ tabrewind
+ for tab in range(1, 4)
+ norm fm
+ call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
+ tabnext
+ endfor
+
+ bwipe! Xaaa.c
+ bwipe! Xbbb.c
+ bwipe! Xccc.c
+ bwipe! Xddd.c
+ syntax off
+ call delete('Xaaa.c')
+ call delete('Xbbb.c')
+ call delete('Xccc.c')
+ call delete('Xddd.c')
endfunc
func Test_syntax_foldlevel()
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index 6abe5b7c89..7057cdefb2 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -1,5 +1,8 @@
" Tests for tagjump (tags and special searches)
+source check.vim
+source screendump.vim
+
" SEGV occurs in older versions. (At least 7.4.1748 or older)
func Test_ptag_with_notagstack()
set notagstack
@@ -551,6 +554,37 @@ func Test_tag_line_toolong()
let &verbose = old_vbs
endfunc
+" Check that using :tselect does not run into the hit-enter prompt.
+" Requires a terminal to trigger that prompt.
+func Test_tselect()
+ CheckScreendump
+
+ call writefile([
+ \ 'main Xtest.h /^void test();$/;" f',
+ \ 'main Xtest.c /^int main()$/;" f',
+ \ 'main Xtest.x /^void test()$/;" f',
+ \ ], 'Xtags')
+ cal writefile([
+ \ 'int main()',
+ \ 'void test()',
+ \ ], 'Xtest.c')
+
+ let lines =<< trim [SCRIPT]
+ set tags=Xtags
+ [SCRIPT]
+ call writefile(lines, 'XTest_tselect')
+ let buf = RunVimInTerminal('-S XTest_tselect', {'rows': 10, 'cols': 50})
+
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, ":tselect main\<CR>2\<CR>")
+ call VerifyScreenDump(buf, 'Test_tselect_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xtags')
+ call delete('Xtest.c')
+ call delete('XTest_tselect')
+endfunc
+
func Test_tagline()
call writefile([
\ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo',
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 75673adf0a..2223be952c 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -1,4 +1,7 @@
" Tests for the various 'formatoptions' settings
+
+source check.vim
+
func Test_text_format()
enew!
@@ -490,6 +493,23 @@ func Test_format_list_auto()
set fo& ai& bs&
endfunc
+func Test_crash_github_issue_5095()
+ CheckFeature autocmd
+
+ " This used to segfault, see https://github.com/vim/vim/issues/5095
+ augroup testing
+ au BufNew x center
+ augroup END
+
+ next! x
+
+ bw
+ augroup testing
+ au!
+ augroup END
+ augroup! testing
+endfunc
+
" Test for formatting multi-byte text with 'fo=t'
func Test_tw_2_fo_t()
new
diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim
index 7863317eb0..f70cc1f70a 100644
--- a/src/nvim/testdir/test_textobjects.vim
+++ b/src/nvim/testdir/test_textobjects.vim
@@ -152,6 +152,36 @@ func Test_string_html_objects()
normal! dit
call assert_equal('-<b></b>', getline('.'))
+ " copy the tag block from leading indentation before the start tag
+ let t = " <b>\ntext\n</b>"
+ $put =t
+ normal! 2kvaty
+ call assert_equal("<b>\ntext\n</b>", @")
+
+ " copy the tag block from the end tag
+ let t = "<title>\nwelcome\n</title>"
+ $put =t
+ normal! $vaty
+ call assert_equal("<title>\nwelcome\n</title>", @")
+
+ " copy the outer tag block from a tag without an end tag
+ let t = "<html>\n<title>welcome\n</html>"
+ $put =t
+ normal! k$vaty
+ call assert_equal("<html>\n<title>welcome\n</html>", @")
+
+ " nested tag that has < in a different line from >
+ let t = "<div><div\n></div></div>"
+ $put =t
+ normal! k0vaty
+ call assert_equal("<div><div\n></div></div>", @")
+
+ " nested tag with attribute that has < in a different line from >
+ let t = "<div><div\nattr=\"attr\"\n></div></div>"
+ $put =t
+ normal! 2k0vaty
+ call assert_equal("<div><div\nattr=\"attr\"\n></div></div>", @")
+
set quoteescape&
enew!
endfunc
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
index cffd80ff4f..13971a918d 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -233,16 +233,17 @@ func Test_timer_catch_error()
endfunc
func FeedAndPeek(timer)
- call test_feedinput('a')
+ " call test_feedinput('a')
+ call nvim_input('a')
call getchar(1)
endfunc
func Interrupt(timer)
- call test_feedinput("\<C-C>")
+ " call test_feedinput("\<C-C>")
+ call nvim_input("\<C-C>")
endfunc
func Test_peek_and_get_char()
- throw 'skipped: Nvim does not support test_feedinput()'
if !has('unix') && !has('gui_running')
return
endif
@@ -339,6 +340,41 @@ func Test_nocatch_garbage_collect()
delfunc FeedChar
endfunc
+func Test_error_in_timer_callback()
+ if !has('terminal') || (has('win32') && has('gui_running'))
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+
+ let lines =<< trim [CODE]
+ func Func(timer)
+ " fail to create list
+ let x = [
+ endfunc
+ set updatetime=50
+ call timer_start(1, 'Func')
+ [CODE]
+ call writefile(lines, 'Xtest.vim')
+
+ let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8})
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})
+
+ " GC must not run during timer callback, which can make Vim crash.
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, "\<CR>")
+ call term_wait(buf, 100)
+ call assert_equal('run', job_status(job))
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitFor({-> job_status(job) ==# 'dead'})
+ if has('unix')
+ call assert_equal('', job_info(job).termsig)
+ endif
+
+ call delete('Xtest.vim')
+ exe buf .. 'bwipe!'
+endfunc
+
func Test_timer_invalid_callback()
call assert_fails('call timer_start(0, "0")', 'E921')
endfunc
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index adcdcb1cd9..3b66071d6d 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -364,6 +364,25 @@ func Test_wundo_errors()
bwipe!
endfunc
+" Check that reading a truncted undo file doesn't hang.
+func Test_undofile_truncated()
+ throw 'skipped: TODO: '
+ new
+ call setline(1, 'hello')
+ set ul=100
+ wundo Xundofile
+ let contents = readfile('Xundofile', 'B')
+
+ " try several sizes
+ for size in range(20, 500, 33)
+ call writefile(contents[0:size], 'Xundofile')
+ call assert_fails('rundo Xundofile', 'E825:')
+ endfor
+
+ bwipe!
+ call delete('Xundofile')
+endfunc
+
func Test_rundo_errors()
call assert_fails('rundo XfileDoesNotExist', 'E822:')
@@ -373,6 +392,26 @@ func Test_rundo_errors()
call delete('Xundofile')
endfunc
+func Test_undofile_next()
+ set undofile
+ new Xfoo.txt
+ execute "norm ix\<c-g>uy\<c-g>uz\<Esc>"
+ write
+ bwipe
+
+ next Xfoo.txt
+ call assert_equal('xyz', getline(1))
+ silent undo
+ call assert_equal('xy', getline(1))
+ silent undo
+ call assert_equal('x', getline(1))
+ bwipe!
+
+ call delete('Xfoo.txt')
+ call delete('.Xfoo.txt.un~')
+ set undofile&
+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()
diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim
index 2c7cb7bab7..0a89066a2b 100644
--- a/src/nvim/testdir/test_usercommands.vim
+++ b/src/nvim/testdir/test_usercommands.vim
@@ -184,6 +184,34 @@ func Test_Ambiguous()
call assert_fails("\x4ei\041", 'E492: Not an editor command: Ni!')
endfunc
+func Test_redefine_on_reload()
+ call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists')
+ call assert_equal(0, exists(':ExistingCommand'))
+ source Xcommandexists
+ call assert_equal(2, exists(':ExistingCommand'))
+ " Redefining a command when reloading a script is OK.
+ source Xcommandexists
+ call assert_equal(2, exists(':ExistingCommand'))
+
+ " But redefining in another script is not OK.
+ call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists2')
+ call assert_fails('source Xcommandexists2', 'E174:')
+ call delete('Xcommandexists2')
+
+ " And defining twice in one script is not OK.
+ delcommand ExistingCommand
+ call assert_equal(0, exists(':ExistingCommand'))
+ call writefile([
+ \ 'command ExistingCommand echo "yes"',
+ \ 'command ExistingCommand echo "no"',
+ \ ], 'Xcommandexists')
+ call assert_fails('source Xcommandexists', 'E174:')
+ call assert_equal(2, exists(':ExistingCommand'))
+
+ call delete('Xcommandexists')
+ delcommand ExistingCommand
+endfunc
+
func Test_CmdUndefined()
call assert_fails('Doit', 'E492:')
au CmdUndefined Doit :command Doit let g:didit = 'yes'
@@ -248,7 +276,7 @@ func Test_CmdCompletion()
call assert_equal('"com -nargs=* + 0 1 ?', @:)
call feedkeys(":com -addr=\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -addr=arguments buffers lines loaded_buffers quickfix tabs windows', @:)
+ call assert_equal('"com -addr=arguments buffers lines loaded_buffers other quickfix tabs windows', @:)
call feedkeys(":com -complete=co\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"com -complete=color command compiler', @:)
@@ -312,3 +340,202 @@ func Test_use_execute_in_completion()
call assert_equal('"DoExec hi', @:)
delcommand DoExec
endfunc
+
+func Test_addr_all()
+ throw 'skipped: requires patch v8.1.0341 to pass'
+ command! -addr=lines DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(line('$'), g:a2)
+
+ command! -addr=arguments DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ args one two three
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(3, g:a2)
+
+ command! -addr=buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ %DoSomething
+ for low in range(1, bufnr('$'))
+ if buflisted(low)
+ break
+ endif
+ endfor
+ call assert_equal(low, g:a1)
+ call assert_equal(bufnr('$'), g:a2)
+
+ command! -addr=loaded_buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ %DoSomething
+ for low in range(1, bufnr('$'))
+ if bufloaded(low)
+ break
+ endif
+ endfor
+ call assert_equal(low, g:a1)
+ for up in range(bufnr('$'), 1, -1)
+ if bufloaded(up)
+ break
+ endif
+ endfor
+ call assert_equal(up, g:a2)
+
+ command! -addr=windows DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ new
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(winnr('$'), g:a2)
+ bwipe
+
+ command! -addr=tabs DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ tabnew
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(len(gettabinfo()), g:a2)
+ bwipe
+
+ command! -addr=other DoSomething echo 'nothing'
+ DoSomething
+ call assert_fails('%DoSomething')
+
+ delcommand DoSomething
+endfunc
+
+func Test_command_list()
+ command! DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 :",
+ \ execute('command DoCmd'))
+
+ " Test with various -range= and -count= argument values.
+ command! -range DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . :",
+ \ execute('command DoCmd'))
+ command! -range=% DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 % :",
+ \ execute('command! DoCmd'))
+ command! -range=2 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 2 :",
+ \ execute('command DoCmd'))
+ command! -count=2 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 2c :",
+ \ execute('command DoCmd'))
+
+ " Test with various -addr= argument values.
+ command! -addr=lines DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . :",
+ \ execute('command DoCmd'))
+ command! -addr=arguments DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . arg :",
+ \ execute('command DoCmd'))
+ command! -addr=buffers DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . buf :",
+ \ execute('command DoCmd'))
+ command! -addr=loaded_buffers DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . load :",
+ \ execute('command DoCmd'))
+ command! -addr=windows DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . win :",
+ \ execute('command DoCmd'))
+ command! -addr=tabs DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . tab :",
+ \ execute('command DoCmd'))
+ command! -addr=other DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . ? :",
+ \ execute('command DoCmd'))
+
+ " Test with various -complete= argument values (non-exhaustive list)
+ command! -complete=arglist DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 arglist :",
+ \ execute('command DoCmd'))
+ command! -complete=augroup DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 augroup :",
+ \ execute('command DoCmd'))
+ command! -complete=custom,CustomComplete DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 custom :",
+ \ execute('command DoCmd'))
+ command! -complete=customlist,CustomComplete DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 customlist :",
+ \ execute('command DoCmd'))
+
+ " Test with various -narg= argument values.
+ command! -nargs=0 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -nargs=1 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 1 :",
+ \ execute('command DoCmd'))
+ command! -nargs=* DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd * :",
+ \ execute('command DoCmd'))
+ command! -nargs=? DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd ? :",
+ \ execute('command DoCmd'))
+ command! -nargs=+ DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd + :",
+ \ execute('command DoCmd'))
+
+ " Test with other arguments.
+ command! -bang DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n! DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -bar DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n| DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -register DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n\" DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -buffer DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\nb DoCmd 0 :"
+ \ .. "\n\" DoCmd 0 :",
+ \ execute('command DoCmd'))
+ comclear
+
+ " Test with many args.
+ command! -bang -bar -register -buffer -nargs=+ -complete=environment -addr=windows -count=3 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n!\"b|DoCmd + 3c win environment :",
+ \ execute('command DoCmd'))
+ comclear
+
+ " Test with special characters in command definition.
+ command! DoCmd :<cr><tab><c-d>
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 :<CR><Tab><C-D>",
+ \ execute('command DoCmd'))
+
+ " Test output in verbose mode.
+ command! DoCmd :
+ call assert_match("^\n"
+ \ .. " Name Args Address Complete Definition\n"
+ \ .. " DoCmd 0 :\n"
+ \ .. "\tLast set from .*/test_usercommands.vim line \\d\\+$",
+ \ execute('verbose command DoCmd'))
+
+ comclear
+ call assert_equal("\nNo user-defined commands found", execute(':command Xxx'))
+ call assert_equal("\nNo user-defined commands found", execute('command'))
+endfunc
diff --git a/src/nvim/testdir/test_version.vim b/src/nvim/testdir/test_version.vim
new file mode 100644
index 0000000000..46cf34979f
--- /dev/null
+++ b/src/nvim/testdir/test_version.vim
@@ -0,0 +1,12 @@
+" Test :version Ex command
+
+func Test_version()
+ " version should always return the same string.
+ let v1 = execute('version')
+ let v2 = execute('version')
+ call assert_equal(v1, v2)
+
+ call assert_match("^\n\nNVIM v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+.*", v1)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index d2f13ff072..cb81997d39 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1409,6 +1409,17 @@ func Test_compound_assignment_operators()
let @/ = ''
endfunc
+func Test_funccall_garbage_collect()
+ func Func(x, ...)
+ call add(a:x, a:000)
+ endfunc
+ call Func([], [])
+ " Must not crash cause by invalid freeing
+ call test_garbagecollect_now()
+ call assert_true(v:true)
+ delfunc Func
+endfunc
+
func Test_function_defined_line()
if has('gui_running')
" Can't catch the output of gvim.
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 7fc8cdd7f4..734f264672 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -432,3 +432,14 @@ func Test_Visual_Block()
close!
endfunc
+
+func Test_visual_put_in_block()
+ new
+ call setline(1, ['xxxx', 'y∞yy', 'zzzz'])
+ normal 1G2yl
+ exe "normal 1G2l\<C-V>jjlp"
+ call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index aaa291f87d..500e3ff088 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -841,4 +841,50 @@ func Test_winnr()
only | tabonly
endfunc
+func Test_window_resize()
+ " Vertical :resize (absolute, relative, min and max size).
+ vsplit
+ vert resize 8
+ call assert_equal(8, winwidth(0))
+ vert resize +2
+ call assert_equal(10, winwidth(0))
+ vert resize -2
+ call assert_equal(8, winwidth(0))
+ vert resize
+ call assert_equal(&columns - 2, winwidth(0))
+ vert resize 0
+ call assert_equal(1, winwidth(0))
+ vert resize 99999
+ call assert_equal(&columns - 2, winwidth(0))
+
+ %bwipe!
+
+ " Horizontal :resize (with absolute, relative size, min and max size).
+ split
+ resize 8
+ call assert_equal(8, winheight(0))
+ resize +2
+ call assert_equal(10, winheight(0))
+ resize -2
+ call assert_equal(8, winheight(0))
+ resize
+ call assert_equal(&lines - 4, winheight(0))
+ resize 0
+ call assert_equal(1, winheight(0))
+ resize 99999
+ call assert_equal(&lines - 4, winheight(0))
+
+ " :resize with explicit window number.
+ let other_winnr = winnr('j')
+ exe other_winnr .. 'resize 10'
+ call assert_equal(10, winheight(other_winnr))
+ call assert_equal(&lines - 10 - 3, winheight(0))
+ exe other_winnr .. 'resize +1'
+ exe other_winnr .. 'resize +1'
+ call assert_equal(12, winheight(other_winnr))
+ call assert_equal(&lines - 10 - 3 -2, winheight(0))
+
+ %bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index bfd9435c49..62d7dc8b18 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -52,15 +52,10 @@
#define OUTBUF_SIZE 0xffff
#define TOO_MANY_EVENTS 1000000
-#define STARTS_WITH(str, prefix) \
- (strlen(str) >= (sizeof(prefix) - 1) && 0 == memcmp((str), (prefix), \
- sizeof(prefix) - 1))
-#define SCREEN_WRAP(is_screen, seq) ((is_screen) \
- ? DCS_STR seq STERM_STR : seq)
-#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \
- ((is_screen) \
- ? DCS_STR seq STERM_STR : (is_tmux) \
- ? DCS_STR "tmux;\x1b" seq STERM_STR : seq)
+#define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \
+ && 0 == memcmp((str), (prefix), sizeof(prefix) - 1))
+#define TMUX_WRAP(is_tmux, seq) ((is_tmux) \
+ ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq)
#define LINUXSET0C "\x1b[?0c"
#define LINUXSET1C "\x1b[?1c"
@@ -113,6 +108,7 @@ typedef struct {
bool cork, overflow;
bool cursor_color_changed;
bool is_starting;
+ FILE *screenshot;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs clear_attrs;
kvec_t(HlAttrs) attrs;
@@ -172,6 +168,7 @@ UI *tui_start(void)
ui->suspend = tui_suspend;
ui->set_title = tui_set_title;
ui->set_icon = tui_set_icon;
+ ui->screenshot = tui_screenshot;
ui->option_set= tui_option_set;
ui->raw_line = tui_raw_line;
@@ -319,7 +316,13 @@ static void terminfo_start(UI *ui)
#ifdef WIN32
uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW);
#else
- uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO);
+ int retry_count = 10;
+ // A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a
+ // few times. #12322
+ while (uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO) == UV_EINTR
+ && retry_count > 0) {
+ retry_count--;
+ }
#endif
} else {
uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0);
@@ -417,6 +420,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
data->bridge = bridge;
data->loop = &tui_loop;
data->is_starting = true;
+ data->screenshot = NULL;
kv_init(data->invalid_regions);
signal_watcher_init(data->loop, &data->winch_handle, ui);
signal_watcher_init(data->loop, &data->cont_handle, data);
@@ -1085,6 +1089,7 @@ static void tui_set_mode(UI *ui, ModeShape mode)
}
} else if (c.id == 0) {
// No cursor color for this mode; reset to default.
+ data->want_invisible = false;
unibi_out_ext(ui, data->unibi_ext.reset_cursor_color);
}
@@ -1103,6 +1108,15 @@ static void tui_set_mode(UI *ui, ModeShape mode)
static void tui_mode_change(UI *ui, String mode, Integer mode_idx)
{
TUIData *data = ui->data;
+#ifdef UNIX
+ // If stdin is not a TTY, the LHS of pipe may change the state of the TTY
+ // after calling uv_tty_set_mode. So, set the mode of the TTY again here.
+ // #13073
+ if (data->is_starting && data->input.in_fd == STDERR_FILENO) {
+ uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL);
+ uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO);
+ }
+#endif
tui_set_mode(ui, (ModeShape)mode_idx);
data->is_starting = false; // mode entered, no longer starting
data->showing_mode = (ModeShape)mode_idx;
@@ -1322,6 +1336,31 @@ static void tui_set_icon(UI *ui, String icon)
{
}
+static void tui_screenshot(UI *ui, String path)
+{
+ TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
+ flush_buf(ui);
+ grid->row = 0;
+ grid->col = 0;
+
+ FILE *f = fopen(path.data, "w");
+ data->screenshot = f;
+ fprintf(f, "%d,%d\n", grid->height, grid->width);
+ unibi_out(ui, unibi_clear_screen);
+ for (int i = 0; i < grid->height; i++) {
+ cursor_goto(ui, i, 0);
+ for (int j = 0; j < grid->width; j++) {
+ print_cell(ui, &grid->cells[i][j]);
+ }
+ }
+ flush_buf(ui);
+ data->screenshot = NULL;
+
+ fclose(f);
+}
+
+
static void tui_option_set(UI *ui, String name, Object value)
{
TUIData *data = ui->data;
@@ -1591,10 +1630,6 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
bool mate_pretending_xterm = xterm && colorterm
&& strstr(colorterm, "mate-terminal");
bool true_xterm = xterm && !!xterm_version && !bsdvt;
- bool true_screen = screen && !os_getenv("TMUX");
- bool screen_host_linuxvt =
- terminfo_is_term_family(true_screen && term[6] == '.'
- ? term + 7 : NULL, "linux");
bool cygwin = terminfo_is_term_family(term, "cygwin");
char *fix_normal = (char *)unibi_get_str(ut, unibi_cursor_normal);
@@ -1737,10 +1772,8 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
#define XTERM_SETAB_16 \
"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m"
- data->unibi_ext.get_bg =
- (int)unibi_add_ext_str(ut, "ext.get_bg",
- SCREEN_TMUX_WRAP(true_screen,
- tmux, "\x1b]11;?\x07"));
+ data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg",
+ "\x1b]11;?\x07");
// Terminals with 256-colour SGR support despite what terminfo says.
if (unibi_get_num(ut, unibi_max_colors) < 256) {
@@ -1775,32 +1808,6 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss");
}
- // GNU Screen does not have Ss/Se. When terminfo has Ss/Se, it is wrapped with
- // DCS because it is inherited from the host terminal.
- if (true_screen) {
- size_t len;
- size_t dcs_st_len = strlen(DCS_STR) + strlen(STERM_STR);
- if (-1 != data->unibi_ext.set_cursor_style) {
- const char *orig_ss =
- unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style);
- len = STRLEN(orig_ss) + dcs_st_len + 1;
- char *ss = xmalloc(len);
- snprintf(ss, len, "%s%s%s", DCS_STR, orig_ss, STERM_STR);
- unibi_set_ext_str(data->ut, (size_t)data->unibi_ext.set_cursor_style, ss);
- xfree(ss);
- }
- if (-1 != data->unibi_ext.reset_cursor_style) {
- const char *orig_se =
- unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style);
- len = strlen(orig_se) + dcs_st_len + 1;
- char *se = xmalloc(len);
- snprintf(se, len, "%s%s%s", DCS_STR, orig_se, STERM_STR);
- unibi_set_ext_str(data->ut,
- (size_t)data->unibi_ext.reset_cursor_style, se);
- xfree(se);
- }
- }
-
// Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So
// adding them to terminal types, that have such control sequences but lack
// the correct terminfo entries, is a fixup, not an augmentation.
@@ -1816,12 +1823,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
|| (konsolev >= 180770) // #9364
|| tmux // per tmux manual page
// https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html
- || (true_screen
- && (!screen_host_linuxvt
- || (screen_host_linuxvt
- && (xterm_version || (vte_version > 0) || colorterm))))
- // Since GNU Screen does not support DECSCUSR, DECSCUSR is wrapped
- // in DCS and output to the host terminal.
+ || screen
|| st // #7641
|| rxvt // per command.C
// per analysis of VT100Terminal.m
@@ -1834,72 +1836,58 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
|| (linuxvt
&& (xterm_version || (vte_version > 0) || colorterm)))) {
data->unibi_ext.set_cursor_style =
- (int)unibi_add_ext_str(ut, "Ss",
- SCREEN_WRAP(true_screen, "\x1b[%p1%d q"));
+ (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q");
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- SCREEN_WRAP(true_screen, "\x1b[ q"));
- } else if (linuxvt || screen_host_linuxvt) {
+ "\x1b[ q");
+ } else if (linuxvt) {
// Linux uses an idiosyncratic escape code to set the cursor shape and
// does not support DECSCUSR.
// See http://linuxgazette.net/137/anonymous.html for more info
- //
- // Since gnu Screen does not have Ss/Se, if the host terminal is a linux
- // console that does not support xterm extensions, it will wraps the
- // linux-specific sequence in DCS and outputs it.
- data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(
- ut, "Ss",
- SCREEN_WRAP(true_screen,
- "\x1b[?"
- "%?"
- // The parameter passed to Ss is the DECSCUSR parameter,
- // so the
- // terminal capability has to translate into the Linux
- // idiosyncratic parameter.
- //
- // linuxvt only supports block and underline. It is also
- // only possible to have a steady block (no steady
- // underline)
- "%p1%{2}%<" "%t%{8}" // blink block
- "%e%p1%{2}%=" "%t%{112}" // steady block
- "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half
- // block)
- "%e%p1%{4}%=" "%t%{4}" // steady underline
- "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline)
- "%e%p1%{6}%=" "%t%{2}" // steady bar
- "%e%{0}" // anything else
- "%;" "%dc"));
+ data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
+ "\x1b[?"
+ "%?"
+ // The parameter passed to Ss is the DECSCUSR parameter, so the
+ // terminal capability has to translate into the Linux idiosyncratic
+ // parameter.
+ //
+ // linuxvt only supports block and underline. It is also only
+ // possible to have a steady block (no steady underline)
+ "%p1%{2}%<" "%t%{8}" // blink block
+ "%e%p1%{2}%=" "%t%{112}" // steady block
+ "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block)
+ "%e%p1%{4}%=" "%t%{4}" // steady underline
+ "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline)
+ "%e%p1%{6}%=" "%t%{2}" // steady bar
+ "%e%{0}" // anything else
+ "%;" "%dc");
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- SCREEN_WRAP(true_screen, "\x1b[?c"));
+ "\x1b[?c");
} else if (konsolev > 0 && konsolev < 180770) {
// Konsole before version 18.07.70: set up a nonce profile. This has
// side-effects on temporary font resizing. #6798
- data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(
- ut, "Ss",
- SCREEN_TMUX_WRAP(true_screen, tmux,
- "\x1b]50;CursorShape=%?"
- "%p1%{3}%<" "%t%{0}" // block
- "%e%p1%{5}%<" "%t%{2}" // underline
- "%e%{1}" // everything else is bar
- "%;%d;BlinkingCursorEnabled=%?"
- "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude
- // zero as special,
- "%e%p1%{1}%&" // in all other c2ses we can treat bit
- // #0 as a flag.
- "%;%d\x07"));
+ data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
+ TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?"
+ "%p1%{3}%<" "%t%{0}" // block
+ "%e%p1%{5}%<" "%t%{2}" // underline
+ "%e%{1}" // everything else is bar
+ "%;%d;BlinkingCursorEnabled=%?"
+ "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special,
+ "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag.
+ "%;%d\x07"));
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- SCREEN_TMUX_WRAP(true_screen, tmux, "\x1b]50;\x07"));
+ "\x1b]50;\x07");
}
}
}
@@ -1931,10 +1919,6 @@ static void augment_terminfo(TUIData *data, const char *term,
const char *xterm_version = os_getenv("XTERM_VERSION");
bool true_xterm = xterm && !!xterm_version && !bsdvt;
- bool true_screen = screen && !os_getenv("TMUX");
- bool screen_host_rxvt =
- terminfo_is_term_family(true_screen
- && term[6] == '.' ? term + 7 : NULL, "rxvt");
// Only define this capability for terminal types that we know understand it.
if (dtterm // originated this extension
@@ -2001,7 +1985,7 @@ static void augment_terminfo(TUIData *data, const char *term,
// all panes, which is not particularly desirable. A better approach
// would use a tmux control sequence and an extra if(screen) test.
data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(
- ut, NULL, SCREEN_TMUX_WRAP(true_screen, tmux, "\033]Pl%p1%06x\033\\"));
+ ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\"));
} else if ((xterm || rxvt || tmux || alacritty)
&& (vte_version == 0 || vte_version >= 3900)) {
// Supported in urxvt, newer VTE.
@@ -2021,27 +2005,21 @@ static void augment_terminfo(TUIData *data, const char *term,
/// Terminals usually ignore unrecognized private modes, and there is no
/// known ambiguity with these. So we just set them unconditionally.
- /// If the DECSET is not supported by GNU Screen, it is wrapped with DCS and
- /// sent to the host terminal.
data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(
ut, "ext.enable_lr_margin", "\x1b[?69h");
data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(
ut, "ext.disable_lr_margin", "\x1b[?69l");
data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(
- ut, "ext.enable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004h"));
+ ut, "ext.enable_bpaste", "\x1b[?2004h");
data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(
- ut, "ext.disable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004l"));
+ ut, "ext.disable_bpaste", "\x1b[?2004l");
// For urxvt send BOTH xterm and old urxvt sequences. #8695
data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(
ut, "ext.enable_focus",
- (rxvt || screen_host_rxvt)
- ? SCREEN_WRAP(true_screen, "\x1b[?1004h\x1b]777;focus;on\x7")
- : SCREEN_WRAP(true_screen, "\x1b[?1004h"));
+ rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h");
data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(
ut, "ext.disable_focus",
- (rxvt || screen_host_rxvt)
- ? SCREEN_WRAP(true_screen, "\x1b[?1004l\x1b]777;focus;off\x7")
- : SCREEN_WRAP(true_screen, "\x1b[?1004l"));
+ rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l");
data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(
ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h");
data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(
@@ -2120,9 +2098,15 @@ static void flush_buf(UI *ui)
}
}
- uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
- bufs, (unsigned)(bufp - bufs), NULL);
- uv_run(&data->write_loop, UV_RUN_DEFAULT);
+ if (data->screenshot) {
+ for (size_t i = 0; i < (size_t)(bufp - bufs); i++) {
+ fwrite(bufs[i].base, bufs[i].len, 1, data->screenshot);
+ }
+ } else {
+ uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
+ bufs, (unsigned)(bufp - bufs), NULL);
+ uv_run(&data->write_loop, UV_RUN_DEFAULT);
+ }
data->bufpos = 0;
data->overflow = false;
}
diff --git a/src/nvim/types.h b/src/nvim/types.h
index 87560a43da..a3d87f35ca 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -16,11 +16,13 @@ typedef uint32_t u8char_T;
// Opaque handle used by API clients to refer to various objects in vim
typedef int handle_T;
-// Opaque handle to a lua value. Must be free with `executor_free_luaref` when
+// Opaque handle to a lua value. Must be free with `api_free_luaref` when
// not needed anymore! LUA_NOREF represents missing reference, i e to indicate
// absent callback etc.
typedef int LuaRef;
+typedef uint64_t NS;
+
typedef struct expand expand_T;
typedef enum {
diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c
index 9a1988739c..25f45b8fe6 100644
--- a/src/nvim/ui_bridge.c
+++ b/src/nvim/ui_bridge.c
@@ -61,6 +61,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
rv->bridge.suspend = ui_bridge_suspend;
rv->bridge.set_title = ui_bridge_set_title;
rv->bridge.set_icon = ui_bridge_set_icon;
+ rv->bridge.screenshot = ui_bridge_screenshot;
rv->bridge.option_set = ui_bridge_option_set;
rv->bridge.raw_line = ui_bridge_raw_line;
rv->bridge.inspect = ui_bridge_inspect;
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 97018f6c02..6c5a6cdb46 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -878,7 +878,12 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi,
for (;; ) {
int len = undo_read_byte(bi);
- if (len == 0 || len == EOF) {
+ if (len == EOF) {
+ corruption_error("truncated", file_name);
+ u_free_uhp(uhp);
+ return NULL;
+ }
+ if (len == 0) {
break;
}
int what = undo_read_byte(bi);
@@ -3029,8 +3034,6 @@ u_header_T *u_force_get_undo_header(buf_T *buf)
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;
@@ -3038,6 +3041,7 @@ u_header_T *u_force_get_undo_header(buf_T *buf)
abort();
}
}
+ curbuf = save_curbuf;
}
return uhp;
}
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 190f13e74b..32cb0091a3 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -144,7 +144,7 @@ static const int included_patches[] = {
1777,
1776,
1775,
- // 1774,
+ 1774,
1773,
1772,
1771,
@@ -171,9 +171,9 @@ static const int included_patches[] = {
1750,
1749,
1748,
- // 1747,
+ 1747,
1746,
- // 1745,
+ 1745,
// 1744,
// 1743,
1742,
@@ -206,7 +206,7 @@ static const int included_patches[] = {
1715,
1714,
1713,
- // 1712,
+ 1712,
1711,
1710,
1709,
@@ -327,9 +327,9 @@ static const int included_patches[] = {
1594,
1593,
// 1592,
- // 1591,
+ 1591,
1590,
- // 1589,
+ 1589,
// 1588,
1587,
1586,
@@ -374,7 +374,7 @@ static const int included_patches[] = {
1547,
1546,
1545,
- // 1544,
+ 1544,
1543,
1542,
1541,
@@ -387,7 +387,7 @@ static const int included_patches[] = {
1534,
1533,
1532,
- // 1531,
+ 1531,
1530,
1529,
1528,
@@ -464,7 +464,7 @@ static const int included_patches[] = {
1457,
1456,
// 1455,
- // 1454,
+ 1454,
1453,
1452,
1451,
@@ -2119,13 +2119,13 @@ void list_in_columns(char_u **items, int size, int current)
void list_lua_version(void)
{
- typval_T luaver_tv;
- typval_T arg = { .v_type = VAR_UNKNOWN }; // No args.
- char *luaver_expr = "((jit and jit.version) and jit.version or _VERSION)";
- executor_eval_lua(cstr_as_string(luaver_expr), &arg, &luaver_tv);
- assert(luaver_tv.v_type == VAR_STRING);
- MSG(luaver_tv.vval.v_string);
- xfree(luaver_tv.vval.v_string);
+ char *code = "return ((jit and jit.version) and jit.version or _VERSION)";
+ Error err = ERROR_INIT;
+ Object ret = nlua_exec(cstr_as_string(code), (Array)ARRAY_DICT_INIT, &err);
+ assert(!ERROR_SET(&err));
+ assert(ret.type == kObjectTypeString);
+ MSG(ret.data.string.data);
+ api_free_object(ret);
}
void list_version(void)
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index b77b80a5f3..44b6ab5f5a 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -1431,7 +1431,7 @@ static inline void east_set_error(const ParserState *const pstate,
const ParserLine pline = pstate->reader.lines.items[start.line];
ret_ast_err->msg = msg;
ret_ast_err->arg_len = (int)(pline.size - start.col);
- ret_ast_err->arg = pline.data + start.col;
+ ret_ast_err->arg = pline.data ? pline.data + start.col : NULL;
}
/// Set error from the given token and given message
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 0fff93d984..4931221e7a 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -1490,13 +1490,11 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
if (flags & (WSP_TOP | WSP_BOT))
(void)win_comp_pos();
- /*
- * Both windows need redrawing
- */
+ // Both windows need redrawing. Update all status lines, in case they
+ // show something related to the window count or position.
redraw_win_later(wp, NOT_VALID);
- wp->w_redr_status = TRUE;
redraw_win_later(oldwin, NOT_VALID);
- oldwin->w_redr_status = TRUE;
+ status_redraw_all();
if (need_status) {
msg_row = Rows - 1;
@@ -2580,9 +2578,10 @@ int win_close(win_T *win, bool free_buf)
return OK;
}
- /* Free independent synblock before the buffer is freed. */
- if (win->w_buffer != NULL)
+ // Free independent synblock before the buffer is freed.
+ if (win->w_buffer != NULL) {
reset_synblock(win);
+ }
/*
* Close the link to the buffer.
@@ -6019,6 +6018,12 @@ char_u *grab_file_name(long count, linenr_T *file_lnum)
char_u *ptr;
if (get_visual_text(NULL, &ptr, &len) == FAIL)
return NULL;
+ // Only recognize ":123" here
+ if (file_lnum != NULL && ptr[len] == ':' && isdigit(ptr[len + 1])) {
+ char_u *p = ptr + len + 1;
+
+ *file_lnum = getdigits_long(&p, false, 0);
+ }
return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname);
}
return file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
@@ -6986,7 +6991,7 @@ void win_findbuf(typval_T *argvars, list_T *list)
int bufnr = tv_get_number(&argvars[0]);
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer->b_fnum == bufnr) {
+ if (!wp->w_closing && wp->w_buffer->b_fnum == bufnr) {
tv_list_append_number(list, wp->handle);
}
}