diff options
Diffstat (limited to 'src/nvim')
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, ®_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, ®_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, ®name, 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, ®type, 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, ®match, tomatch, + found_match = vgr_match_buflines(qf_get_curlist(qi), + fname, buf, ®match, &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, ®match, lang); + hgr_search_in_rtp(qfl, ®match, 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(®match, 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(®match); @@ -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 a /\%u61\Z "\u0061" + " ' ̀' U+0300 Dec:768 COMBINING GRAVE ACCENT ̀ /\%u300\Z "\u0300" + " ' ́' U+0301 Dec:769 COMBINING ACUTE ACCENT ́ /\%u301\Z "\u0301" + " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE ̇ /\%u307\Z "\u0307" + " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW ̣ /\%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); } } |