diff options
Diffstat (limited to 'src')
232 files changed, 13329 insertions, 18924 deletions
diff --git a/src/clint.py b/src/clint.py index 8dc41fdb93..9b4128a0c9 100755 --- a/src/clint.py +++ b/src/clint.py @@ -350,7 +350,7 @@ def IsErrorInSuppressedErrorsList(category, linenum): category: str, the category of the error. linenum: int, the current line number. Returns: - bool, True iff the error should be suppressed due to presense in + bool, True iff the error should be suppressed due to presence in suppressions file. """ return (category, linenum) in _error_suppressions_2 diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index c7258dde12..8ec087c626 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -55,6 +55,7 @@ set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h) set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua) +set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua) set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua) set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json) set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint") @@ -87,10 +88,6 @@ file(GLOB NVIM_HEADERS *.h) file(GLOB XDIFF_SOURCES xdiff/*.c) file(GLOB XDIFF_HEADERS xdiff/*.h) -file(GLOB TREESITTER_SOURCES ../tree_sitter/*.c) -file(GLOB TS_SOURCE_AMALGAM ../tree_sitter/lib.c) -list(REMOVE_ITEM TREESITTER_SOURCES ${TS_SOURCE_AMALGAM}) - foreach(subdir os api @@ -187,9 +184,6 @@ if(NOT MSVC) set_source_files_properties( eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") 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") endif() if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$") @@ -330,10 +324,12 @@ add_custom_command( COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE} ${LUA_VIM_MODULE_SOURCE} vim_module ${LUA_SHARED_MODULE_SOURCE} shared_module + ${LUA_INSPECT_MODULE_SOURCE} inspect_module DEPENDS ${CHAR_BLOB_GENERATOR} ${LUA_VIM_MODULE_SOURCE} ${LUA_SHARED_MODULE_SOURCE} + ${LUA_INSPECT_MODULE_SOURCE} ) list(APPEND NVIM_GENERATED_SOURCES @@ -449,6 +445,7 @@ list(APPEND NVIM_LINK_LIBRARIES ${LIBTERMKEY_LIBRARIES} ${UNIBILIUM_LIBRARIES} ${UTF8PROC_LIBRARIES} + ${TreeSitter_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) @@ -468,7 +465,7 @@ endif() add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES}) + ${XDIFF_SOURCES} ${XDIFF_HEADERS}) target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) @@ -566,7 +563,7 @@ add_library( EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES} + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ) set_property(TARGET libnvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) @@ -596,7 +593,7 @@ else() EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES} + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${UNIT_TEST_FIXTURES} ) target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) @@ -620,9 +617,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(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(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..4fc0ee4fdf 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" @@ -27,6 +28,7 @@ #include "nvim/map.h" #include "nvim/mark.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/fileio.h" #include "nvim/move.h" #include "nvim/syntax.h" @@ -175,8 +177,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 +214,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 +246,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 +954,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_object_to_bool(v, "force", false, err); + } else if (strequal("unload", k.data)) { + unload = api_object_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 +1084,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 +1160,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 +1227,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 +1246,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 +1259,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 +1300,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 +1314,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 +1356,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 +1371,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_object_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 && decor_state.buf == 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; + } + decor_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 = decor_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 +1540,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 +1594,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 +1607,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, + decor_hl(hl_id), kExtmarkNoUndo); return src_id; } @@ -1470,7 +1652,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 +1670,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 +1679,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. @@ -1584,7 +1729,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, } - VirtText *existing = extmark_find_virttext(buf, (int)line, ns_id); + VirtText *existing = decor_find_virttext(buf, (int)line, ns_id); if (existing) { clear_virttext(existing); @@ -1592,113 +1737,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 +1802,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..2c99d3426c 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" @@ -25,6 +27,7 @@ #include "nvim/map_defs.h" #include "nvim/map.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/version.h" @@ -1198,7 +1201,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 +1582,100 @@ 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_object_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)"; +} + +DecorProvider *get_provider(NS ns_id, bool force) +{ + ssize_t i; + for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) { + DecorProvider *item = &kv_A(decor_providers, i); + if (item->ns_id == ns_id) { + return item; + } else if (item->ns_id > ns_id) { + break; + } + } + + if (!force) { + return NULL; + } + + for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) { + // allocates if needed: + (void)kv_a(decor_providers, (size_t)j+1); + kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j); + } + DecorProvider *item = &kv_a(decor_providers, (size_t)i); + *item = DECORATION_PROVIDER_INIT(ns_id); + + return item; +} diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index df3a263dcf..271fd5b485 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -7,6 +7,7 @@ #include "nvim/vim.h" #include "nvim/getchar.h" #include "nvim/memory.h" +#include "nvim/decoration.h" #include "nvim/ex_eval.h" #include "nvim/lib/kvec.h" @@ -52,7 +53,8 @@ .type = kObjectTypeLuaRef, \ .data.luaref = r }) -#define NIL ((Object) {.type = kObjectTypeNil}) +#define NIL ((Object)OBJECT_INIT) +#define NULL_STRING ((String)STRING_INIT) #define PUT(dict, k, v) \ kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v })) diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 717713b948..51f1af4eb5 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -355,7 +355,7 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err) /// Note that this method is not to be confused with |nvim_ui_pum_set_height()|, /// which sets the number of visible items in the popup menu, while this /// function sets the bounding box of the popup menu, including visual -/// decorations such as boarders and sliders. Floats need not use the same font +/// elements such as borders and sliders. Floats need not use the same font /// size, nor be anchored to exact grid corners, so one can set floating-point /// numbers to the popup menu geometry. /// 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..cf822782d8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -41,7 +41,7 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" -#include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" @@ -199,14 +199,77 @@ Integer nvim_get_hl_id_by_name(String name) return syn_check_group((const char_u *)name.data, (int)name.size); } +Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) +{ + if (ns_id == 0) { + return get_global_hl_defs(); + } + abort(); +} + +/// Set a highlight group. +/// +/// @param ns_id number of namespace for this highlight +/// @param name highlight group name, like ErrorMsg +/// @param val highlight definiton map, like |nvim_get_hl_by_name|. +/// @param[out] err Error details, if any +/// +/// TODO: ns_id = 0, should modify :highlight namespace +/// TODO val should take update vs reset flag +void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) + FUNC_API_SINCE(7) +{ + int hl_id = syn_check_group( (char_u *)(name.data), (int)name.size); + int link_id = -1; + + HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); + if (!ERROR_SET(err)) { + ns_hl_def((NS)ns_id, hl_id, attrs, link_id); + } +} + +/// Set active namespace for highlights. +/// +/// NB: this function can be called from async contexts, but the +/// semantics are not yet well-defined. To start with +/// |nvim_set_decoration_provider| on_win and on_line callbacks +/// are explicitly allowed to change the namespace during a redraw cycle. +/// +/// @param ns_id the namespace to activate +/// @param[out] err Error details, if any +void nvim_set_hl_ns(Integer ns_id, Error *err) + FUNC_API_SINCE(7) + FUNC_API_FAST +{ + if (ns_id >= 0) { + ns_hl_active = (NS)ns_id; + } + + // TODO(bfredl): this is a little bit hackish. Eventually we want a standard + // event path for redraws caused by "fast" events. This could tie in with + // better throttling of async events causing redraws, such as non-batched + // nvim_buf_set_extmark calls from async contexts. + if (!updating_screen && !ns_hl_changed) { + multiqueue_put(main_loop.events, on_redraw_event, 0); + } + ns_hl_changed = true; +} + +static void on_redraw_event(void **argv) + FUNC_API_NOEXPORT +{ + redraw_all_later(NOT_VALID); +} + + /// Sends input-keys to Nvim, subject to various quirks controlled by `mode` /// 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 +538,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 +557,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. @@ -678,7 +741,11 @@ Integer nvim_strwidth(String text, Error *err) ArrayOf(String) nvim_list_runtime_paths(void) FUNC_API_SINCE(1) { + // TODO(bfredl): this should just work: + // return nvim_get_runtime_file(NULL_STRING, true); + Array rv = ARRAY_DICT_INIT; + char_u *rtp = p_rtp; if (*rtp == NUL) { @@ -725,22 +792,30 @@ ArrayOf(String) nvim_list_runtime_paths(void) /// @param name pattern of files to search for /// @param all whether to return all matches or only the first /// @return list of absolute paths to the found files -ArrayOf(String) nvim_get_runtime_file(String name, Boolean all) +ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err) FUNC_API_SINCE(7) + FUNC_API_FAST { Array rv = ARRAY_DICT_INIT; - if (!name.data) { + + // TODO(bfredl): + if (name.size == 0) { + api_set_error(err, kErrorTypeValidation, "not yet implemented"); return rv; } + int flags = DIP_START | (all ? DIP_ALL : 0); - do_in_runtimepath((char_u *)name.data, flags, find_runtime_cb, &rv); + do_in_runtimepath((char_u *)name.data, + flags, find_runtime_cb, &rv); return rv; } static void find_runtime_cb(char_u *fname, void *cookie) { Array *rv = (Array *)cookie; - ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname))); + if (fname != NULL) { + ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname))); + } } String nvim__get_lib_dir(void) @@ -1477,7 +1552,7 @@ void nvim_unsubscribe(uint64_t channel_id, String event) Integer nvim_get_color_by_name(String name) FUNC_API_SINCE(1) { - return name_to_color((char_u *)name.data); + return name_to_color(name.data); } /// Returns a map of color names and RGB values. @@ -2477,7 +2552,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 +2598,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 +2679,112 @@ 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) { + ui_call_screenshot(path); +} + +static void clear_provider(DecorProvider *p) +{ + if (p == NULL) { return; } - int attr = syn_id2attr((int)id); - if (attr == 0) { - return; + 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 decoration 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 +{ + DecorProvider *p = get_provider((NS)ns_id, true); + clear_provider(p); + + // regardless of what happens, it seems good idea to redraw + redraw_all_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 }, + { "_on_hl_def", &p->hl_def }, + { 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/api/window.c b/src/nvim/api/window.c index ba43bc6cb2..f09a03f592 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -142,7 +142,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) // make sure cursor is in visible range even if win != curwin update_topline_win(win); - redraw_win_later(win, VALID); + redraw_later(win, VALID); } /// Gets the window height @@ -471,7 +471,7 @@ void nvim_win_set_config(Window window, Dictionary config, Error *err) if (!win_new_float(win, fconfig, err)) { return; } - redraw_later(NOT_VALID); + redraw_later(win, NOT_VALID); } else { win_config_float(win, fconfig); win->w_pos_changed = true; 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..6be51c504c 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -7,6 +7,7 @@ return { 'BufFilePre', -- before renaming a buffer 'BufHidden', -- just after buffer becomes hidden 'BufLeave', -- before leaving a buffer + 'BufModifiedSet', -- after the 'modified' state of a buffer changes 'BufNew', -- after creating any buffer 'BufNewFile', -- when creating a buffer for a new file 'BufReadCmd', -- read buffer using command @@ -65,7 +66,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. @@ -112,6 +114,7 @@ return { 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window 'WinNew', -- when entering a new window + 'WinScrolled', -- after scrolling a window }, aliases = { BufCreate = 'BufAdd', @@ -122,6 +125,7 @@ return { -- List of nvim-specific events or aliases for the purpose of generating -- syntax file nvim_specific = { + BufModifiedSet=true, DirChanged=true, Signal=true, TabClosed=true, @@ -132,5 +136,6 @@ return { UIEnter=true, UILeave=true, WinClosed=true, + WinScrolled=true, }, } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 86067aceac..0ebe33f2f8 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -220,13 +220,8 @@ int open_buffer( int perm; perm = os_getperm((const char *)curbuf->b_ffname); - if (perm >= 0 && (0 -# ifdef S_ISFIFO - || S_ISFIFO(perm) -# endif -# ifdef S_ISSOCK + if (perm >= 0 && (0 || S_ISFIFO(perm) || S_ISSOCK(perm) -# endif # ifdef OPEN_CHR_FILES || (S_ISCHR(perm) && is_dev_fd_file(curbuf->b_ffname)) @@ -837,7 +832,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); } @@ -1623,7 +1618,7 @@ void enter_buffer(buf_T *buf) } curbuf->b_last_used = time(NULL); - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } // Change to the directory of the current buffer. @@ -1941,6 +1936,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 +2498,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 +2646,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 +2658,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; @@ -3438,31 +3435,17 @@ int build_stl_str_hl( int use_sandbox, char_u fillchar, int maxwidth, - struct stl_hlrec *hltab, - StlClickRecord *tabtab + stl_hlrec_t **hltab, + StlClickRecord **tabtab ) { - int groupitems[STL_MAX_ITEM]; - struct stl_item { - // Where the item starts in the status line output buffer - char_u *start; - // Function to run for ClickFunc items. - char *cmd; - // The minimum width of the item - int minwid; - // The maximum width of the item - int maxwid; - enum { - Normal, - Empty, - Group, - Separate, - Highlight, - TabPage, - ClickFunc, - Trunc - } type; - } items[STL_MAX_ITEM]; + static size_t stl_items_len = 20; // Initial value, grows as needed. + static stl_item_t *stl_items = NULL; + static int *stl_groupitems = NULL; + static stl_hlrec_t *stl_hltab = NULL; + static StlClickRecord *stl_tabtab = NULL; + static int *stl_separator_locations = NULL; + #define TMPLEN 70 char_u buf_tmp[TMPLEN]; char_u win_tmp[TMPLEN]; @@ -3470,6 +3453,14 @@ int build_stl_str_hl( const int save_must_redraw = must_redraw; const int save_redr_type = curwin->w_redr_type; + if (stl_items == NULL) { + stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); + stl_groupitems = xmalloc(sizeof(int) * stl_items_len); + stl_hltab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len); + stl_tabtab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len); + stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); + } + // When the format starts with "%!" then evaluate it as an expression and // use the result as the actual format string. if (fmt[0] == '%' && fmt[1] == '!') { @@ -3538,14 +3529,17 @@ int build_stl_str_hl( // Proceed character by character through the statusline format string // fmt_p is the current positon in the input buffer for (char_u *fmt_p = usefmt; *fmt_p; ) { - if (curitem == STL_MAX_ITEM) { - // There are too many items. Add the error code to the statusline - // to give the user a hint about what went wrong. - if (out_p + 5 < out_end_p) { - memmove(out_p, " E541", (size_t)5); - out_p += 5; - } - break; + if (curitem == (int)stl_items_len) { + size_t new_len = stl_items_len * 3 / 2; + + stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len); + stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len); + stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * new_len); + stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * new_len); + stl_separator_locations = + xrealloc(stl_separator_locations, sizeof(int) * new_len); + + stl_items_len = new_len; } if (*fmt_p != NUL && *fmt_p != '%') { @@ -3589,16 +3583,16 @@ int build_stl_str_hl( if (groupdepth > 0) { continue; } - items[curitem].type = Separate; - items[curitem++].start = out_p; + stl_items[curitem].type = Separate; + stl_items[curitem++].start = out_p; continue; } // STL_TRUNCMARK: Where to begin truncating if the statusline is too long. if (*fmt_p == STL_TRUNCMARK) { fmt_p++; - items[curitem].type = Trunc; - items[curitem++].start = out_p; + stl_items[curitem].type = Trunc; + stl_items[curitem++].start = out_p; continue; } @@ -3614,7 +3608,7 @@ int build_stl_str_hl( // Determine how long the group is. // Note: We set the current output position to null // so `vim_strsize` will work. - char_u *t = items[groupitems[groupdepth]].start; + char_u *t = stl_items[stl_groupitems[groupdepth]].start; *out_p = NUL; long group_len = vim_strsize(t); @@ -3624,34 +3618,40 @@ int build_stl_str_hl( // move the output pointer back to where the group started. // Note: This erases any non-item characters that were in the group. // Otherwise there would be no reason to do this step. - if (curitem > groupitems[groupdepth] + 1 - && items[groupitems[groupdepth]].minwid == 0) { + if (curitem > stl_groupitems[groupdepth] + 1 + && stl_items[stl_groupitems[groupdepth]].minwid == 0) { // remove group if all items are empty and highlight group // doesn't change int group_start_userhl = 0; int group_end_userhl = 0; int n; - for (n = groupitems[groupdepth] - 1; n >= 0; n--) { - if (items[n].type == Highlight) { - group_start_userhl = group_end_userhl = items[n].minwid; + for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) { + if (stl_items[n].type == Highlight) { + group_start_userhl = group_end_userhl = stl_items[n].minwid; break; } } - for (n = groupitems[groupdepth] + 1; n < curitem; n++) { - if (items[n].type == Normal) { + for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + if (stl_items[n].type == Normal) { break; } - if (items[n].type == Highlight) { - group_end_userhl = items[n].minwid; + if (stl_items[n].type == Highlight) { + group_end_userhl = stl_items[n].minwid; } } if (n == curitem && group_start_userhl == group_end_userhl) { + // empty group out_p = t; group_len = 0; - // do not use the highlighting from the removed group - for (n = groupitems[groupdepth] + 1; n < curitem; n++) { - if (items[n].type == Highlight) { - items[n].type = Empty; + for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + // do not use the highlighting from the removed group + if (stl_items[n].type == Highlight) { + stl_items[n].type = Empty; + } + // adjust the start position of TabPage to the next + // item position + if (stl_items[n].type == TabPage) { + stl_items[n].start = out_p; } } } @@ -3659,18 +3659,19 @@ int build_stl_str_hl( // If the group is longer than it is allowed to be // truncate by removing bytes from the start of the group text. - if (group_len > items[groupitems[groupdepth]].maxwid) { + if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) { // { Determine the number of bytes to remove long n; if (has_mbyte) { // Find the first character that should be included. n = 0; - while (group_len >= items[groupitems[groupdepth]].maxwid) { + while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) { group_len -= ptr2cells(t + n); n += (*mb_ptr2len)(t + n); } } else { - n = (long)(out_p - t) - items[groupitems[groupdepth]].maxwid + 1; + n = (long)(out_p - t) + - stl_items[stl_groupitems[groupdepth]].maxwid + 1; } // } @@ -3681,25 +3682,26 @@ int build_stl_str_hl( memmove(t + 1, t + n, (size_t)(out_p - (t + n))); out_p = out_p - n + 1; // Fill up space left over by half a double-wide char. - while (++group_len < items[groupitems[groupdepth]].minwid) { + while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { *out_p++ = fillchar; } // } // correct the start of the items for the truncation - for (int idx = groupitems[groupdepth] + 1; idx < curitem; idx++) { + for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) { // Shift everything back by the number of removed bytes - items[idx].start -= n; + stl_items[idx].start -= n; // If the item was partially or completely truncated, set its // start to the start of the group - if (items[idx].start < t) { - items[idx].start = t; + if (stl_items[idx].start < t) { + stl_items[idx].start = t; } } // If the group is shorter than the minimum width, add padding characters. - } else if (abs(items[groupitems[groupdepth]].minwid) > group_len) { - long min_group_width = items[groupitems[groupdepth]].minwid; + } else if ( + abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { + long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; // If the group is left-aligned, add characters to the right. if (min_group_width < 0) { min_group_width = 0 - min_group_width; @@ -3718,8 +3720,8 @@ int build_stl_str_hl( // } // Adjust item start positions - for (int n = groupitems[groupdepth] + 1; n < curitem; n++) { - items[n].start += group_len; + for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) { + stl_items[n].start += group_len; } // Prepend the fill characters @@ -3755,9 +3757,9 @@ int build_stl_str_hl( // User highlight groups override the min width field // to denote the styling to use. if (*fmt_p == STL_USER_HL) { - items[curitem].type = Highlight; - items[curitem].start = out_p; - items[curitem].minwid = minwid > 9 ? 1 : minwid; + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid > 9 ? 1 : minwid; fmt_p++; curitem++; continue; @@ -3791,8 +3793,8 @@ int build_stl_str_hl( if (minwid == 0) { // %X ends the close label, go back to the previous tab label nr. for (long n = curitem - 1; n >= 0; n--) { - if (items[n].type == TabPage && items[n].minwid >= 0) { - minwid = items[n].minwid; + if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) { + minwid = stl_items[n].minwid; break; } } @@ -3801,9 +3803,9 @@ int build_stl_str_hl( minwid = -minwid; } } - items[curitem].type = TabPage; - items[curitem].start = out_p; - items[curitem].minwid = minwid; + stl_items[curitem].type = TabPage; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid; fmt_p++; curitem++; continue; @@ -3818,10 +3820,10 @@ int build_stl_str_hl( if (*fmt_p != STL_CLICK_FUNC) { break; } - items[curitem].type = ClickFunc; - items[curitem].start = out_p; - items[curitem].cmd = xmemdupz(t, (size_t)(((char *)fmt_p - t))); - items[curitem].minwid = minwid; + stl_items[curitem].type = ClickFunc; + stl_items[curitem].start = out_p; + stl_items[curitem].cmd = xmemdupz(t, (size_t)(((char *)fmt_p - t))); + stl_items[curitem].minwid = minwid; fmt_p++; curitem++; continue; @@ -3842,11 +3844,11 @@ int build_stl_str_hl( // Denotes the start of a new group if (*fmt_p == '(') { - groupitems[groupdepth++] = curitem; - items[curitem].type = Group; - items[curitem].start = out_p; - items[curitem].minwid = minwid; - items[curitem].maxwid = maxwid; + stl_groupitems[groupdepth++] = curitem; + stl_items[curitem].type = Group; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = minwid; + stl_items[curitem].maxwid = maxwid; fmt_p++; curitem++; continue; @@ -4141,9 +4143,9 @@ int build_stl_str_hl( // Create a highlight item based on the name if (*fmt_p == '#') { - items[curitem].type = Highlight; - items[curitem].start = out_p; - items[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t)); + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p; + stl_items[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t)); curitem++; fmt_p++; } @@ -4154,8 +4156,8 @@ int build_stl_str_hl( // If we made it this far, the item is normal and starts at // our current position in the output buffer. // Non-normal items would have `continued`. - items[curitem].start = out_p; - items[curitem].type = Normal; + stl_items[curitem].start = out_p; + stl_items[curitem].type = Normal; // Copy the item string into the output buffer if (str != NULL && *str) { @@ -4313,7 +4315,7 @@ int build_stl_str_hl( // Otherwise, there was nothing to print so mark the item as empty } else { - items[curitem].type = Empty; + stl_items[curitem].type = Empty; } // Only free the string buffer if we allocated it. @@ -4354,13 +4356,13 @@ int build_stl_str_hl( // Otherwise, look for the truncation item } else { // Default to truncating at the first item - trunc_p = items[0].start; + trunc_p = stl_items[0].start; item_idx = 0; for (int i = 0; i < itemcnt; i++) { - if (items[i].type == Trunc) { - // Truncate at %< items. - trunc_p = items[i].start; + if (stl_items[i].type == Trunc) { + // Truncate at %< stl_items. + trunc_p = stl_items[i].start; item_idx = i; break; } @@ -4395,7 +4397,7 @@ int build_stl_str_hl( // Ignore any items in the statusline that occur after // the truncation point for (int i = 0; i < itemcnt; i++) { - if (items[i].start > trunc_p) { + if (stl_items[i].start > trunc_p) { itemcnt = i; break; } @@ -4450,12 +4452,12 @@ int build_stl_str_hl( for (int i = item_idx; i < itemcnt; i++) { // Items starting at or after the end of the truncated section need // to be moved backwards. - if (items[i].start >= trunc_end_p) { - items[i].start -= item_offset; + if (stl_items[i].start >= trunc_end_p) { + stl_items[i].start -= item_offset; // Anything inside the truncated area is set to start // at the `<` truncation character. } else { - items[i].start = trunc_p; + stl_items[i].start = trunc_p; } } // } @@ -4471,7 +4473,7 @@ int build_stl_str_hl( // figuring out how many groups there are. int num_separators = 0; for (int i = 0; i < itemcnt; i++) { - if (items[i].type == Separate) { + if (stl_items[i].type == Separate) { num_separators++; } } @@ -4480,11 +4482,10 @@ int build_stl_str_hl( if (num_separators) { // Create an array of the start location for each // separator mark. - int separator_locations[STL_MAX_ITEM]; int index = 0; for (int i = 0; i < itemcnt; i++) { - if (items[i].type == Separate) { - separator_locations[index] = i; + if (stl_items[i].type == Separate) { + stl_separator_locations[index] = i; index++; } } @@ -4496,16 +4497,17 @@ int build_stl_str_hl( for (int i = 0; i < num_separators; i++) { int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; - char_u *seploc = items[separator_locations[i]].start + dislocation; - STRMOVE(seploc, items[separator_locations[i]].start); - for (char_u *s = items[separator_locations[i]].start; s < seploc; s++) { + char_u *start = stl_items[stl_separator_locations[i]].start; + char_u *seploc = start + dislocation; + STRMOVE(seploc, start); + for (char_u *s = start; s < seploc; s++) { *s = fillchar; } - for (int item_idx = separator_locations[i] + 1; + for (int item_idx = stl_separator_locations[i] + 1; item_idx < itemcnt; item_idx++) { - items[item_idx].start += dislocation; + stl_items[item_idx].start += dislocation; } } @@ -4515,11 +4517,12 @@ int build_stl_str_hl( // Store the info about highlighting. if (hltab != NULL) { - struct stl_hlrec *sp = hltab; + *hltab = stl_hltab; + stl_hlrec_t *sp = stl_hltab; for (long l = 0; l < itemcnt; l++) { - if (items[l].type == Highlight) { - sp->start = items[l].start; - sp->userhl = items[l].minwid; + if (stl_items[l].type == Highlight) { + sp->start = stl_items[l].start; + sp->userhl = stl_items[l].minwid; sp++; } } @@ -4529,16 +4532,17 @@ int build_stl_str_hl( // Store the info about tab pages labels. if (tabtab != NULL) { - StlClickRecord *cur_tab_rec = tabtab; + *tabtab = stl_tabtab; + StlClickRecord *cur_tab_rec = stl_tabtab; for (long l = 0; l < itemcnt; l++) { - if (items[l].type == TabPage) { - cur_tab_rec->start = (char *)items[l].start; - if (items[l].minwid == 0) { + if (stl_items[l].type == TabPage) { + cur_tab_rec->start = (char *)stl_items[l].start; + if (stl_items[l].minwid == 0) { cur_tab_rec->def.type = kStlClickDisabled; cur_tab_rec->def.tabnr = 0; } else { - int tabnr = items[l].minwid; - if (items[l].minwid > 0) { + int tabnr = stl_items[l].minwid; + if (stl_items[l].minwid > 0) { cur_tab_rec->def.type = kStlClickTabSwitch; } else { cur_tab_rec->def.type = kStlClickTabClose; @@ -4548,11 +4552,11 @@ int build_stl_str_hl( } cur_tab_rec->def.func = NULL; cur_tab_rec++; - } else if (items[l].type == ClickFunc) { - cur_tab_rec->start = (char *)items[l].start; + } else if (stl_items[l].type == ClickFunc) { + cur_tab_rec->start = (char *)stl_items[l].start; cur_tab_rec->def.type = kStlClickFuncRun; - cur_tab_rec->def.tabnr = items[l].minwid; - cur_tab_rec->def.func = items[l].cmd; + cur_tab_rec->def.tabnr = stl_items[l].minwid; + cur_tab_rec->def.func = stl_items[l].cmd; cur_tab_rec++; } } @@ -5389,13 +5393,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 { @@ -5414,7 +5416,7 @@ char_u *buf_spname(buf_T *buf) return (char_u *)_("[Scratch]"); } if (buf->b_fname == NULL) { - return (char_u *)_("[No Name]"); + return buf_get_fname(buf); } return NULL; } @@ -5475,6 +5477,16 @@ int buf_signcols(buf_T *buf) return buf->b_signcols; } +// Get "buf->b_fname", use "[No Name]" if it is NULL. +char_u *buf_get_fname(const buf_T *buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + if (buf->b_fname == NULL) { + return (char_u *)_("[No Name]"); + } + return buf->b_fname; +} + /* * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. */ diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 550f8a5e40..93fe37b585 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) @@ -360,14 +361,36 @@ struct mapblock { sctx_T m_script_ctx; // SCTX where map was defined }; -/* - * Used for highlighting in the status line. - */ +/// Used for highlighting in the status line. +typedef struct stl_hlrec stl_hlrec_t; struct stl_hlrec { char_u *start; int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID }; +/// Used for building the status line. +typedef struct stl_item stl_item_t; +struct stl_item { + // Where the item starts in the status line output buffer + char_u *start; + // Function to run for ClickFunc items. + char *cmd; + // The minimum width of the item + int minwid; + // The maximum width of the item + int maxwid; + enum { + Normal, + Empty, + Group, + Separate, + Highlight, + TabPage, + ClickFunc, + Trunc + } type; +}; + // values for b_syn_spell: what to do with toplevel text #define SYNSPL_DEFAULT 0 // spell check if @Spell not defined #define SYNSPL_TOP 1 // spell check toplevel text @@ -451,6 +474,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 @@ -517,6 +541,9 @@ struct file_buffer { int b_changed; // 'modified': Set to true if something in the // file has been changed and not written out. + bool b_changed_invalid; // Set if BufModified autocmd has not been + // triggered since the last time b_changed was + // modified. /// Change-identifier incremented for each change, including undo. /// @@ -543,6 +570,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_decor; // 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 +686,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' @@ -765,6 +798,7 @@ struct file_buffer { int b_ind_cpp_namespace; int b_ind_if_for_while; int b_ind_cpp_extern_c; + int b_ind_pragma; linenr_T b_no_eol_lnum; /* non-zero lnum when last line of next binary * write should not have an end-of-line */ @@ -835,18 +869,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 }; @@ -1204,6 +1233,13 @@ struct window_S { colnr_T w_skipcol; // starting column when a single line // doesn't fit in the window + // "w_last_topline" and "w_last_leftcol" are used to determine if + // a Scroll autocommand should be emitted. + linenr_T w_last_topline; ///< last known value for topline + colnr_T w_last_leftcol; ///< last known value for leftcol + int w_last_width; ///< last known value for width + int w_last_height; ///< last known value for height + // // Layout of the window in the screen. // May need to add "msg_scrolled" to "w_winrow" in rare situations. 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..271d350967 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -129,6 +129,7 @@ void changed(void) void changed_internal(void) { curbuf->b_changed = true; + curbuf->b_changed_invalid = true; ml_setflags(curbuf); check_status(curbuf); redraw_tabline = true; @@ -142,7 +143,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 +170,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; } @@ -295,7 +295,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, // change. if (wp->w_p_rnu || (wp->w_p_cul && lnum <= wp->w_last_cursorline)) { - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); } } } @@ -349,7 +349,7 @@ void changed_bytes(linenr_T lnum, colnr_T col) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changedOneline(wp->w_buffer, wlnum); @@ -362,11 +362,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); @@ -477,7 +476,7 @@ changed_lines( FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changed_lines_buf(wp->w_buffer, wlnum, @@ -504,6 +503,7 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) { if (buf->b_changed || (ff && file_ff_differs(buf, false))) { buf->b_changed = false; + buf->b_changed_invalid = true; ml_setflags(buf); if (ff) { save_file_ff(buf); @@ -1597,7 +1597,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 +1611,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 +1677,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 +1702,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 +1716,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.c b/src/nvim/cursor.c index 036ae32589..d3ffab1759 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -482,7 +482,7 @@ bool leftcol_changed(void) if (retval) curwin->w_set_curswant = true; - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); return retval; } 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/decoration.c b/src/nvim/decoration.c new file mode 100644 index 0000000000..03ce2a37b5 --- /dev/null +++ b/src/nvim/decoration.c @@ -0,0 +1,330 @@ +// 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 + +#include "nvim/vim.h" +#include "nvim/extmark.h" +#include "nvim/decoration.h" +#include "nvim/screen.h" +#include "nvim/syntax.h" +#include "nvim/highlight.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "decoration.c.generated.h" +#endif + +static PMap(uint64_t) *hl_decors; + +void decor_init(void) +{ + hl_decors = pmap_new(uint64_t)(); +} + +/// Add highlighting to a buffer, bounded by two cursor positions, +/// with an offset. +/// +/// TODO(bfredl): make decoration powerful enough so that this +/// can be done with a single ephemeral decoration. +/// +/// @param buf Buffer to add highlights to +/// @param src_id src_id to use or 0 to use a new src_id group, +/// or -1 for ungrouped highlight. +/// @param hl_id Highlight group id +/// @param pos_start Cursor position to start the hightlighting at +/// @param pos_end Cursor position to end the highlighting at +/// @param offset Move the whole highlighting this many columns to the right +void bufhl_add_hl_pos_offset(buf_T *buf, + int src_id, + int hl_id, + lpos_T pos_start, + lpos_T pos_end, + colnr_T offset) +{ + colnr_T hl_start = 0; + colnr_T hl_end = 0; + Decoration *decor = decor_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 ++) { + int end_off = 0; + if (pos_start.lnum < lnum && lnum < pos_end.lnum) { + // TODO(bfredl): This is quite ad-hoc, but the space between |num| and + // text being highlighted is the indication of \n being part of the + // substituted text. But it would be more consistent to highlight + // a space _after_ the previous line instead (like highlight EOL list + // char) + hl_start = MAX(offset-1, 0); + end_off = 1; + hl_end = 0; + } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { + hl_start = pos_start.col + offset; + end_off = 1; + hl_end = 0; + } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { + hl_start = MAX(offset-1, 0); + hl_end = pos_end.col + offset; + } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { + hl_start = pos_start.col + offset; + hl_end = pos_end.col + offset; + } + (void)extmark_set(buf, (uint64_t)src_id, 0, + (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end, + decor, kExtmarkNoUndo); + } +} + +Decoration *decor_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 decor_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 decor_free(Decoration *decor) +{ + if (decor && !decor->shared) { + clear_virttext(&decor->virt_text); + xfree(decor); + } +} + +void clear_virttext(VirtText *text) +{ + for (size_t i = 0; i < kv_size(*text); i++) { + xfree(kv_A(*text, i).text); + } + kv_destroy(*text); + *text = (VirtText)KV_INITIAL_VALUE; +} + +VirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) +{ + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, row, 0, itr); + while (true) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 || mark.row > row) { + break; + } + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id, false); + if (item && (ns_id == 0 || ns_id == item->ns_id) + && item->decor && kv_size(item->decor->virt_text)) { + return &item->decor->virt_text; + } + marktree_itr_next(buf->b_marktree, itr); + } + return NULL; +} + +bool decor_redraw_reset(buf_T *buf, DecorState *state) +{ + state->row = -1; + state->buf = buf; + 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; +} + + +bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) +{ + state->top_row = top_row; + marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); + if (!state->itr->node) { + return false; + } + marktree_itr_rewind(buf->b_marktree, state->itr); + while (true) { + mtmark_t mark = marktree_itr_current(state->itr); + if (mark.row < 0) { // || mark.row > end_row + break; + } + if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) { + goto next_mark; + } + mtpos_t altpos = marktree_lookup(buf->b_marktree, + mark.id^MARKTREE_END_FLAG, NULL); + + 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 + && !kv_size(decor->virt_text)) + || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { + goto next_mark; + } + + 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; + } + marktree_itr_next(buf->b_marktree, state->itr); + } + + return true; // TODO(bfredl): check if available in the region +} + +bool decor_redraw_line(buf_T *buf, int row, DecorState *state) +{ + if (state->row == -1) { + decor_redraw_start(buf, row, state); + } + state->row = row; + state->col_until = -1; + return true; // TODO(bfredl): be more precise +} + +int decor_redraw_col(buf_T *buf, int col, DecorState *state) +{ + if (col <= state->col_until) { + return state->current; + } + state->col_until = MAXCOL; + while (true) { + mtmark_t mark = marktree_itr_current(state->itr); + if (mark.row < 0 || mark.row > state->row) { + break; + } else if (mark.row == state->row && mark.col > col) { + state->col_until = mark.col-1; + break; + } + + if ((mark.id&MARKTREE_END_FLAG)) { + // TODO(bfredl): check decoration flag + goto next_mark; + } + mtpos_t endpos = marktree_lookup(buf->b_marktree, + mark.id|MARKTREE_END_FLAG, NULL); + + 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 (!kv_size(decor->virt_text)) { + goto next_mark; + } + } + + 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); + } + + int attr = 0; + size_t j = 0; + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + bool active = false, keep = true; + if (item.end_row < state->row + || (item.end_row == state->row && item.end_col <= col)) { + if (!(item.start_row >= state->row && item.virt_text)) { + keep = false; + } + } else { + if (item.start_row < state->row + || (item.start_row == state->row && item.start_col <= col)) { + active = true; + if (item.end_row == state->row) { + state->col_until = MIN(state->col_until, item.end_col-1); + } + } else { + if (item.start_row == state->row) { + state->col_until = MIN(state->col_until, item.start_col-1); + } + } + } + if (active && item.attr_id > 0) { + attr = hl_combine_attr(attr, item.attr_id); + } + 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; + state->current = attr; + return attr; +} + +void decor_redraw_end(DecorState *state) +{ + state->buf = NULL; +} + +VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) +{ + decor_redraw_col(buf, MAXCOL, state); + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + if (item.start_row == state->row && item.virt_text) { + return item.virt_text; + } + } + return NULL; +} + +void decor_add_ephemeral(int attr_id, int start_row, int start_col, + int end_row, int end_col, VirtText *virt_text) +{ + kv_push(decor_state.active, + ((HlRange){ start_row, start_col, + end_row, end_col, + attr_id, virt_text, virt_text != NULL })); +} diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h new file mode 100644 index 0000000000..90fdc3dc43 --- /dev/null +++ b/src/nvim/decoration.h @@ -0,0 +1,71 @@ +#ifndef NVIM_DECORATION_H +#define NVIM_DECORATION_H + +#include "nvim/pos.h" +#include "nvim/buffer_defs.h" +#include "nvim/extmark_defs.h" + +// actual Decoration data is in extmark_defs.h + +typedef struct { + char *text; + int hl_id; +} VirtTextChunk; + +typedef kvec_t(VirtTextChunk) VirtText; +#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) + +struct Decoration +{ + int hl_id; // highlight group + VirtText virt_text; + // TODO(bfredl): style, signs, etc + bool shared; // shared decoration, don't free +}; + +typedef struct { + int start_row; + int start_col; + int end_row; + int end_col; + int attr_id; + VirtText *virt_text; + bool virt_text_owned; +} HlRange; + +typedef struct { + MarkTreeIter itr[1]; + kvec_t(HlRange) active; + buf_T *buf; + int top_row; + int row; + int col_until; + int current; + VirtText *virt_text; +} DecorState; + +typedef struct { + NS ns_id; + bool active; + LuaRef redraw_start; + LuaRef redraw_buf; + LuaRef redraw_win; + LuaRef redraw_line; + LuaRef redraw_end; + LuaRef hl_def; + int hl_valid; +} DecorProvider; + +EXTERN kvec_t(DecorProvider) decor_providers INIT(= KV_INITIAL_VALUE); +EXTERN DecorState decor_state INIT(= { 0 }); + +#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ + { ns_id, false, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, -1 } + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "decoration.h.generated.h" +#endif + +#endif // NVIM_DECORATION_H diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 3de5fc49bd..b9c293f6c8 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -643,7 +643,7 @@ void diff_redraw(bool dofold) if (!wp->w_p_diff) { continue; } - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); if (dofold && foldmethodIsDiff(wp)) { foldUpdateAll(wp); } @@ -1415,7 +1415,7 @@ void diff_win_options(win_T *wp, int addbuf) if (addbuf) { diff_buf_add(wp->w_buffer); } - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } /// Set options not to show diffs. For the current window or all windows. 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..9c8d64a6b2 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); @@ -1490,6 +1482,20 @@ static void ins_redraw( } } + // Trigger Scroll if viewport changed. + if (ready && has_event(EVENT_WINSCROLLED) + && win_did_scroll(curwin)) { + do_autocmd_winscrolled(curwin); + } + + // Trigger BufModified if b_changed_invalid is set. + if (ready && has_event(EVENT_BUFMODIFIEDSET) + && curbuf->b_changed_invalid == true + && !pum_visible()) { + apply_autocmds(EVENT_BUFMODIFIEDSET, NULL, NULL, false, curbuf); + curbuf->b_changed_invalid = false; + } + if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin) && conceal_cursor_moved) { redrawWinline(curwin, curwin->w_cursor.lnum); @@ -1919,10 +1925,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 +4185,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 +5578,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 +5783,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 +5866,7 @@ internal_format ( curwin->w_cursor.col = startcol; foundcol = 0; + int skip_pos = 0; /* * Find position to break at. @@ -5915,7 +5936,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 +5949,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 +5965,7 @@ internal_format ( if (curwin->w_cursor.col == 0) break; + ncc = cc; col = curwin->w_cursor.col; dec_cursor(); @@ -5945,17 +5974,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 +6125,7 @@ internal_format ( } } } - first_line = FALSE; + first_line = false; } if (State & VREPLACE_FLAG) { @@ -6244,12 +6312,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 +7765,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) { @@ -8515,7 +8585,7 @@ static void ins_up( if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill ) - redraw_later(VALID); + redraw_later(curwin, VALID); start_arrow(&tpos); can_cindent = true; } else { @@ -8563,7 +8633,7 @@ static void ins_down( if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill ) - redraw_later(VALID); + redraw_later(curwin, VALID); start_arrow(&tpos); can_cindent = true; } else { @@ -8957,7 +9027,7 @@ static int ins_ctrl_ey(int tc) scrolldown_clamp(); else scrollup_clamp(); - redraw_later(VALID); + redraw_later(curwin, VALID); } else { c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1)); if (c != NUL) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 0cad5fd6c1..45d2bf7a91 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); @@ -7104,7 +7152,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -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); } } @@ -10370,7 +10418,7 @@ Channel *find_job(uint64_t id, bool show_error) void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -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/encode.c b/src/nvim/eval/encode.c index 137f099df6..9a9f2e4287 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -159,8 +159,11 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, vim_snprintf((char *)IObuff, IOSIZE, idx_msg, idx); ga_concat(&msg_ga, IObuff); } else { - typval_T key_tv = *TV_LIST_ITEM_TV( - tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list)); + assert(li != NULL); + listitem_T *const first_item = + tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list); + assert(first_item != NULL); + typval_T key_tv = *TV_LIST_ITEM_TV(first_item); char *const key = encode_tv2echo(&key_tv, NULL); vim_snprintf((char *) IObuff, IOSIZE, key_pair_msg, key, idx); xfree(key); diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index e350d09935..679548ab91 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 @@ -202,7 +205,7 @@ static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -859,7 +862,7 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -898,7 +901,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -1477,7 +1480,7 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -1512,7 +1515,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) // dictwatcheradd(dict, key, funcref) function static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -1550,7 +1553,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) // dictwatcherdel(dict, key, funcref) function static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -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); } @@ -3387,63 +3398,23 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) FileInfo file_info; if (os_fileinfo_link(fname, &file_info)) { uint64_t mode = file_info.stat.st_mode; -#ifdef S_ISREG - if (S_ISREG(mode)) + if (S_ISREG(mode)) { t = "file"; - else if (S_ISDIR(mode)) + } else if (S_ISDIR(mode)) { t = "dir"; -# ifdef S_ISLNK - else if (S_ISLNK(mode)) + } else if (S_ISLNK(mode)) { t = "link"; -# endif -# ifdef S_ISBLK - else if (S_ISBLK(mode)) + } else if (S_ISBLK(mode)) { t = "bdev"; -# endif -# ifdef S_ISCHR - else if (S_ISCHR(mode)) + } else if (S_ISCHR(mode)) { t = "cdev"; -# endif -# ifdef S_ISFIFO - else if (S_ISFIFO(mode)) + } else if (S_ISFIFO(mode)) { t = "fifo"; -# endif -# ifdef S_ISSOCK - else if (S_ISSOCK(mode)) + } else if (S_ISSOCK(mode)) { t = "socket"; -# endif - else - t = "other"; -#else -# ifdef S_IFMT - switch (mode & S_IFMT) { - case S_IFREG: t = "file"; break; - case S_IFDIR: t = "dir"; break; -# ifdef S_IFLNK - case S_IFLNK: t = "link"; break; -# endif -# ifdef S_IFBLK - case S_IFBLK: t = "bdev"; break; -# endif -# ifdef S_IFCHR - case S_IFCHR: t = "cdev"; break; -# endif -# ifdef S_IFIFO - case S_IFIFO: t = "fifo"; break; -# endif -# ifdef S_IFSOCK - case S_IFSOCK: t = "socket"; break; -# endif - default: t = "other"; - } -# else - if (os_isdir((const char_u *)fname)) { - t = "dir"; } else { - t = "file"; + t = "other"; } -# endif -#endif type = vim_strsave((char_u *)t); } rettv->vval.v_string = type; @@ -4005,7 +3976,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 +4688,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 */ @@ -4819,7 +4798,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -4843,7 +4822,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -4876,7 +4855,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -5009,7 +4988,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -5042,7 +5021,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER @@ -5260,7 +5239,7 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) rettv->vval.v_string = NULL; } - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -5277,7 +5256,7 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) // input variables char *str_in = (in_type == VAR_STRING) ? (char *)argvars[2].vval.v_string : NULL; - int64_t int_in = argvars[2].vval.v_number; + int int_in = argvars[2].vval.v_number; // output variables char **str_out = (out_type == VAR_STRING) @@ -5471,7 +5450,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); } /* @@ -5963,8 +5942,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) int prot = 0755; // -V536 rettv->vval.v_number = FAIL; - if (check_restricted() || check_secure()) + if (check_secure()) { return; + } char buf[NUMBUFLEN]; const char *const dir = tv_get_string_buf(&argvars[0], buf); @@ -6363,6 +6343,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 */ @@ -6839,7 +6833,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { rettv->vval.v_number = -1; } else { char buf[NUMBUFLEN]; @@ -6918,7 +6912,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(). } @@ -7237,7 +7231,7 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7273,7 +7267,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; const int l_provider_call_nesting = provider_call_nesting; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7370,7 +7364,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7436,7 +7430,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7650,7 +7644,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 +7688,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 +7698,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 +7713,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 +7723,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) { @@ -7899,7 +7892,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; // Address of the new server - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -7941,7 +7934,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "serverstop()" function static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -8124,15 +8117,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 +8137,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 +8165,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 +8189,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 +9151,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]); @@ -9519,7 +9514,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, kListLenMayKnow); if (typeerr) { - return; + goto theend; } regmatch_T regmatch = { @@ -9563,6 +9558,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) vim_regfree(regmatch.regprog); } +theend: p_cpo = save_cpo; } @@ -9937,6 +9933,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); } @@ -10461,7 +10467,7 @@ static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) // "termopen(cmd[, cwd])" function static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -10795,52 +10801,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/typval.c b/src/nvim/eval/typval.c index 8dde78de3d..ada6f78f10 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1358,7 +1358,8 @@ void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item) //{{{2 Alloc/free -/// Allocate an empty dictionary +/// Allocate an empty dictionary. +/// Caller should take care of the reference count. /// /// @return [allocated] new dictionary. dict_T *tv_dict_alloc(void) diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 503a32a81e..1e3e9bd366 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -301,7 +301,8 @@ struct funccall_S { int dbg_tick; ///< Debug_tick when breakpoint was set. int level; ///< Top nesting level of executed function. proftime_T prof_child; ///< Time spent in a child. - funccall_T *caller; ///< Calling function or NULL. + funccall_T *caller; ///< Calling function or NULL; or next funccal in + ///< list pointed to by previous_funccal. int fc_refcount; ///< Number of user functions that reference this funccall. int fc_copyID; ///< CopyID used for garbage collection. garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". 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..17afb33059 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -41,6 +41,7 @@ #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/message.h" @@ -135,7 +136,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 +327,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 +852,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 +891,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 +902,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 +919,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); @@ -1035,13 +1049,13 @@ void do_bang(int addr_count, exarg_T *eap, int forceit, int do_in, int do_out) int len; int scroll_save = msg_scroll; - /* - * Disallow shell commands in restricted mode (-Z) - * Disallow shell commands from .exrc and .vimrc in current directory for - * security reasons. - */ - if (check_restricted() || check_secure()) + // + // Disallow shell commands from .exrc and .vimrc in current directory for + // security reasons. + // + if (check_secure()) { return; + } if (addr_count == 0) { /* :! */ msg_scroll = FALSE; /* don't scroll here */ @@ -1369,10 +1383,9 @@ do_shell( int flags // may be SHELL_DOOUT when output is redirected ) { - // Disallow shell commands in restricted mode (-Z) // Disallow shell commands from .exrc and .vimrc in current directory for // security reasons. - if (check_restricted() || check_secure()) { + if (check_secure()) { msg_end(); return; } @@ -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) @@ -3014,20 +3029,6 @@ void ex_z(exarg_T *eap) ex_no_reprint = true; } -// Check if the restricted flag is set. -// If so, give an error message and return true. -// Otherwise, return false. -bool check_restricted(void) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (restricted) { - EMSG(_("E145: Shell commands and some functionality not allowed" - " in restricted mode")); - return true; - } - return false; -} - /* * Check if the secure flag is set (.exrc or .vimrc in current directory). * If so, give an error message and return TRUE. @@ -3435,13 +3436,13 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, sub_firstline = NULL; - /* - * ~ in the substitute pattern is replaced with the old pattern. - * We do it here once to avoid it to be replaced over and over again. - * But don't do it when it starts with "\=", then it's an expression. - */ - if (!(sub[0] == '\\' && sub[1] == '=')) + // ~ in the substitute pattern is replaced with the old pattern. + // We do it here once to avoid it to be replaced over and over again. + // But don't do it when it starts with "\=", then it's an expression. + assert(sub != NULL); + if (!(sub[0] == '\\' && sub[1] == '=')) { sub = regtilde(sub, p_magic); + } // Check for a match on each line. // If preview: limit to max('cmdwinheight', viewport). @@ -3706,8 +3707,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, update_topline(); validate_cursor(); update_screen(SOME_VALID); - highlight_match = FALSE; - redraw_later(SOME_VALID); + highlight_match = false; + redraw_later(curwin, SOME_VALID); curwin->w_p_fen = save_p_fen; if (msg_row == Rows - 1) @@ -3908,6 +3909,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 +3964,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); + } } @@ -5727,7 +5737,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, } xfree(str); - redraw_later(SOME_VALID); + redraw_later(curwin, SOME_VALID); win_enter(save_curwin, false); // Return to original window update_topline(); 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..713d18b44d 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; + } + } } } @@ -2316,7 +2363,7 @@ void ex_compiler(exarg_T *eap) do_unlet(S_LEN("b:current_compiler"), true); snprintf((char *)buf, bufsize, "compiler/%s.vim", eap->arg); - if (source_runtime(buf, DIP_ALL) == FAIL) { + if (source_in_path(p_rtp, buf, DIP_ALL) == FAIL) { EMSG2(_("E666: compiler not supported: %s"), eap->arg); } xfree(buf); @@ -2534,6 +2581,7 @@ int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback, /// return FAIL when no file could be sourced, OK otherwise. int source_runtime(char_u *name, int flags) { + flags |= (flags & DIP_NORTP) ? 0 : DIP_START; return source_in_path(p_rtp, name, flags); } @@ -4152,7 +4200,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 +4215,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 +4224,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..21db3936b8 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -62,7 +62,6 @@ // curbuf_lock is set #define MODIFY 0x200000 // forbidden in non-'modifiable' buffer #define EXFLAGS 0x400000 // allow flags after count in argument -#define RESTRICT 0x800000L // forbidden in restricted mode #define FILES (XFILE | EXTRA) // multiple extra files allowed #define WORD1 (EXTRA | NOSPC) // one extra word allowed #define FILE1 (FILES | NOSPC) // 1 file allowed, defaults to current file @@ -169,6 +168,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..a491a9d377 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; + const int save_msg_scroll = msg_scroll; 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); } @@ -1786,10 +1503,6 @@ static char_u * do_one_cmd(char_u **cmdlinep, errormsg = (char_u *)_(e_sandbox); goto doend; } - if (restricted != 0 && (ea.argt & RESTRICT)) { - errormsg = (char_u *)_("E981: Command not allowed in restricted mode"); - goto doend; - } if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY) // allow :put in terminals && (!curbuf->terminal || ea.cmdidx != CMD_put)) { @@ -2222,22 +1935,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,9 +1999,285 @@ doend: ? cmdnames[(int)ea.cmdidx].cmd_name : (char_u *)NULL); - if (parsed.verbose_save >= 0) { - p_verbose = parsed.verbose_save; + undo_cmdmod(&ea, save_msg_scroll); + cmdmod = save_cmdmod; + reg_executing = save_reg_executing; + + if (ea.did_sandbox) { + sandbox--; + } + + if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ + ea.nextcmd = NULL; + + --ex_nesting_level; + + 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; +} + +// Undo and free contents of "cmdmod". +static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll) + FUNC_ATTR_NONNULL_ALL +{ + if (eap->verbose_save >= 0) { + p_verbose = eap->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, @@ -2303,20 +2285,15 @@ doend: free_string_option(cmdmod.save_ei); } - if (cmdmod.filter_regmatch.regprog != NULL) { - vim_regfree(cmdmod.filter_regmatch.regprog); - } - - cmdmod = save_cmdmod; - reg_executing = save_reg_executing; + vim_regfree(cmdmod.filter_regmatch.regprog); - if (parsed.save_msg_silent != -1) { + if (eap->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 > eap->save_msg_silent) { + msg_silent = eap->save_msg_silent; } - emsg_silent -= parsed.did_esilent; + emsg_silent -= eap->did_esilent; if (emsg_silent < 0) { emsg_silent = 0; } @@ -2324,27 +2301,19 @@ doend: // message is actually displayed. msg_scroll = save_msg_scroll; - /* "silent reg" or "silent echo x" inside "redir" leaves msg_col - * somewhere in the line. Put it back in the first column. */ - if (redirecting()) + // "silent reg" or "silent echo x" inside "redir" leaves msg_col + // somewhere in the line. Put it back in the first column. + if (redirecting()) { msg_col = 0; + } } - - if (parsed.did_sandbox) { - sandbox--; - } - - if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ - ea.nextcmd = NULL; - - --ex_nesting_level; - - return ea.nextcmd; } + // 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 +2351,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 +2396,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 +3695,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 +3838,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 +3860,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; @@ -4469,6 +4442,9 @@ void separate_nextcmd(exarg_T *eap) else if (p[0] == '`' && p[1] == '=' && (eap->argt & XFILE)) { p += 2; (void)skip_expr(&p); + if (*p == NUL) { // stop at NUL after CTRL-V + break; + } } /* Check for '"': start of comment or '|': next command */ /* :@" does not start a comment! @@ -4949,7 +4925,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 +5002,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 +5057,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 +5153,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 +5239,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 +5264,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 +5399,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 { @@ -6637,25 +6620,22 @@ static void ex_hide(exarg_T *eap) /// ":stop" and ":suspend": Suspend Vim. static void ex_stop(exarg_T *eap) { - // Disallow suspending in restricted mode (-Z) - if (!check_restricted()) { - if (!eap->forceit) { - autowrite_all(); - } - apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); + if (!eap->forceit) { + autowrite_all(); + } + apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); - // TODO(bfredl): the TUI should do this on suspend - ui_cursor_goto(Rows - 1, 0); - ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0); - ui_flush(); - ui_call_suspend(); // call machine specific function + // TODO(bfredl): the TUI should do this on suspend + ui_cursor_goto(Rows - 1, 0); + ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0); + ui_flush(); + ui_call_suspend(); // call machine specific function - ui_flush(); - maketitle(); - resettitle(); // force updating the title - ui_refresh(); // may have resized window - apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); - } + ui_flush(); + maketitle(); + resettitle(); // force updating the title + ui_refresh(); // may have resized window + apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); } // ":exit", ":xit" and ":wq": Write file and quite the current window. @@ -6945,8 +6925,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 +6949,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 +7134,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; } @@ -7396,7 +7375,7 @@ static void ex_syncbind(exarg_T *eap) else scrolldown(-y, TRUE); curwin->w_scbind_pos = topline; - redraw_later(VALID); + redraw_later(curwin, VALID); cursor_correct(); curwin->w_redr_status = TRUE; } @@ -7520,7 +7499,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) shorten_fnames(true); if (trigger_dirchanged) { - do_autocmd_dirchanged(cwd, scope); + do_autocmd_dirchanged(cwd, scope, false); } } @@ -7788,7 +7767,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; @@ -8521,7 +8500,7 @@ static void ex_pedit(exarg_T *eap) if (curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(VALID); + redraw_later(curwin, VALID); win_enter(curwin_save, true); } g_do_tagpreview = 0; @@ -8586,6 +8565,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 +8593,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 +8854,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 +9307,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..53feffd2d7 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, @@ -6184,17 +6483,20 @@ static int open_cmdwin(void) ccline.redraw_state = kCmdRedrawNone; ui_call_cmdline_hide(ccline.level); } - redraw_later(SOME_VALID); + redraw_later(curwin, SOME_VALID); // 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..42a9ef08f9 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; @@ -896,8 +897,8 @@ void ex_mkrc(exarg_T *eap) if (!failed && view_session) { if (put_line(fd, - "let s:so_save = &so | let s:siso_save = &siso" - " | set so=0 siso=0") == FAIL) { + "let s:so_save = &g:so | let s:siso_save = &g:siso" + " | setg so=0 siso=0 | setl so=-1 siso=-1") == FAIL) { failed = true; } if (eap->cmdidx == CMD_mksession) { @@ -948,7 +949,7 @@ void ex_mkrc(exarg_T *eap) } if (fprintf(fd, "%s", - "let &so = s:so_save | let &siso = s:siso_save\n" + "let &g:so = s:so_save | let &g:siso = s:siso_save\n" "doautoall SessionLoadPost\n") < 0) { failed = true; diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 1457a1172d..ba685b158e 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -1,38 +1,39 @@ // 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. +// +// Extmarks are used to implement buffer decoration. Decoration is mostly +// regarded as an application of extmarks, however for practical reasons code +// that deletes an extmark with decoration will call back into the decoration +// code for redrawing the line with the deleted decoration. #include <assert.h> #include "nvim/api/vim.h" #include "nvim/vim.h" #include "nvim/charset.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/buffer_updates.h" #include "nvim/memline.h" #include "nvim/pos.h" @@ -41,8 +42,6 @@ #include "nvim/lib/kbtree.h" #include "nvim/undo.h" #include "nvim/buffer.h" -#include "nvim/syntax.h" -#include "nvim/highlight.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "extmark.c.generated.h" @@ -71,9 +70,11 @@ 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); + assert(ns != NULL); mtpos_t old_pos; uint64_t mark = 0; @@ -82,7 +83,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 +91,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) { + decor_redraw(buf, row, row, it.decor); + decor_free(it.decor); + } mark = marktree_revise(buf->b_marktree, itr); goto revised; } @@ -101,11 +107,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 +126,10 @@ revised: // adding new marks to old undo headers. u_extmark_set(buf, mark, row, col); } + + if (decor) { + decor_redraw(buf, row, end_row > -1 ? end_row : row, decor); + } return id; } @@ -152,27 +168,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) { + decor_redraw(buf, pos.row, pos2.row, item.decor); + decor_free(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 +214,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 +230,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); + decor_redraw(buf, it.row1, mark.row, it.decor); + decor_free(it.decor); } + map_del(uint64_t, ssize_t)(delete_set, mark.id); continue; } @@ -233,15 +249,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) { + decor_redraw(buf, mark.row, mark.row, item.decor); + decor_free(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 +273,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); + decor_redraw(buf, it.row1, pos.row, it.decor); + decor_free(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 +296,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 +347,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 +358,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 +401,7 @@ void extmark_free_all(buf_T *buf) map_foreach(buf->b_extmark_index, id, item, { (void)id; - clear_virttext(&item.virt_text); + decor_free(item.decor); }); map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index); buf->b_extmark_index = NULL; @@ -428,18 +477,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 +507,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 +530,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 +619,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 +652,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 +673,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 +709,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,287 +735,3 @@ 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. -/// -/// @param buf Buffer to add highlights to -/// @param src_id src_id to use or 0 to use a new src_id group, -/// or -1 for ungrouped highlight. -/// @param hl_id Highlight group id -/// @param pos_start Cursor position to start the hightlighting at -/// @param pos_end Cursor position to end the highlighting at -/// @param offset Move the whole highlighting this many columns to the right -void bufhl_add_hl_pos_offset(buf_T *buf, - int src_id, - int hl_id, - lpos_T pos_start, - lpos_T pos_end, - colnr_T offset) -{ - colnr_T hl_start = 0; - colnr_T hl_end = 0; - - // TODO(bfredl): if decoration had blocky mode, we could avoid this loop - for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { - int end_off = 0; - if (pos_start.lnum < lnum && lnum < pos_end.lnum) { - // TODO(bfredl): This is quite ad-hoc, but the space between |num| and - // text being highlighted is the indication of \n being part of the - // substituted text. But it would be more consistent to highlight - // a space _after_ the previous line instead (like highlight EOL list - // char) - hl_start = MAX(offset-1, 0); - end_off = 1; - hl_end = 0; - } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { - hl_start = pos_start.col + offset; - end_off = 1; - hl_end = 0; - } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { - hl_start = MAX(offset-1, 0); - hl_end = pos_end.col + offset; - } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { - 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 clear_virttext(VirtText *text) -{ - for (size_t i = 0; i < kv_size(*text); i++) { - xfree(kv_A(*text, i).text); - } - kv_destroy(*text); - *text = (VirtText)KV_INITIAL_VALUE; -} - -VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id) -{ - MarkTreeIter itr[1] = { 0 }; - marktree_itr_get(buf->b_marktree, row, 0, itr); - while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 || mark.row > row) { - break; - } - 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; - } - marktree_itr_next(buf->b_marktree, itr); - } - return NULL; -} - -bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state) -{ - state->row = -1; - kv_size(state->active) = 0; - return buf->b_extmark_index || buf->b_luahl; -} - - -bool decorations_redraw_start(buf_T *buf, int top_row, - DecorationRedrawState *state) -{ - state->top_row = top_row; - marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); - if (!state->itr->node) { - return false; - } - marktree_itr_rewind(buf->b_marktree, state->itr); - while (true) { - mtmark_t mark = marktree_itr_current(state->itr); - 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; - } - mtpos_t altpos = marktree_lookup(buf->b_marktree, - mark.id^MARKTREE_END_FLAG, NULL); - - uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - start_id, false); - if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row - && item && !kv_size(item->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); - } -next_mark: - if (marktree_itr_node_done(state->itr)) { - break; - } - marktree_itr_next(buf->b_marktree, state->itr); - } - - return true; // TODO(bfredl): check if available in the region -} - -bool decorations_redraw_line(buf_T *buf, int row, DecorationRedrawState *state) -{ - if (state->row == -1) { - decorations_redraw_start(buf, row, state); - } - state->row = row; - state->col_until = -1; - return true; // TODO(bfredl): be more precise -} - -int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state) -{ - if (col <= state->col_until) { - return state->current; - } - state->col_until = MAXCOL; - while (true) { - mtmark_t mark = marktree_itr_current(state->itr); - if (mark.row < 0 || mark.row > state->row) { - break; - } else if (mark.row == state->row && mark.col > col) { - state->col_until = mark.col-1; - break; - } - - if ((mark.id&MARKTREE_END_FLAG)) { - // TODO(bfredl): check decorations flag - goto next_mark; - } - mtpos_t endpos = marktree_lookup(buf->b_marktree, - mark.id|MARKTREE_END_FLAG, NULL); - - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - - if (endpos.row < mark.row - || (endpos.row == mark.row && endpos.col <= mark.col)) { - if (item && !kv_size(item->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 })); - } - -next_mark: - marktree_itr_next(buf->b_marktree, state->itr); - } - - int attr = 0; - size_t j = 0; - for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - bool active = false, keep = true; - if (item.end_row < state->row - || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && item.virt_text)) { - keep = false; - } - } else { - if (item.start_row < state->row - || (item.start_row == state->row && item.start_col <= col)) { - active = true; - if (item.end_row == state->row) { - state->col_until = MIN(state->col_until, item.end_col-1); - } - } else { - if (item.start_row == state->row) { - state->col_until = MIN(state->col_until, item.start_col-1); - } - } - } - if (active && item.attr_id > 0) { - attr = hl_combine_attr(attr, item.attr_id); - } - if (keep) { - kv_A(state->active, j++) = kv_A(state->active, i); - } - } - kv_size(state->active) = j; - state->current = attr; - return attr; -} - -VirtText *decorations_redraw_virt_text(buf_T *buf, DecorationRedrawState *state) -{ - decorations_redraw_col(buf, MAXCOL, state); - for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && item.virt_text) { - return item.virt_text; - } - } - return NULL; -} diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index b5eb0db3b6..1bc42322a3 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 @@ -66,26 +79,6 @@ struct undo_object { }; -typedef struct { - int start_row; - int start_col; - int end_row; - int end_col; - int attr_id; - VirtText *virt_text; -} HlRange; - -typedef struct { - MarkTreeIter itr[1]; - kvec_t(HlRange) active; - int top_row; - int row; - int col_until; - int current; - VirtText *virt_text; -} DecorationRedrawState; - - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "extmark.h.generated.h" #endif diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index c927048981..784280dace 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -1,25 +1,19 @@ #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 { - char *text; - int hl_id; -} VirtTextChunk; - -typedef kvec_t(VirtTextChunk) VirtText; -#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) +typedef struct Decoration Decoration; 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): 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; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 47272df2f0..b1fa0b6779 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1565,7 +1565,7 @@ theend: return file_name; } -void do_autocmd_dirchanged(char *new_dir, CdScope scope) +void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window) { static bool recursive = false; @@ -1601,6 +1601,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope) tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614 tv_dict_add_str(dict, S_LEN("cwd"), new_dir); + tv_dict_add_bool(dict, S_LEN("changed_window"), changed_window); tv_dict_set_keys_readonly(dict); apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, @@ -1633,7 +1634,7 @@ int vim_chdirfile(char_u *fname) slash_adjust((char_u *)dir); #endif if (!strequal(dir, (char *)NameBuff)) { - do_autocmd_dirchanged(dir, kCdScopeWindow); + do_autocmd_dirchanged(dir, kCdScopeWindow, false); } return OK; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 20f0cdccc3..e349f00fba 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 @@ -300,6 +301,7 @@ readfile( int skip_read = false; context_sha256_T sha_ctx; int read_undo_file = false; + int split = 0; // number of split lines linenr_T linecnt; int error = FALSE; /* errors encountered */ int ff_error = EOL_UNKNOWN; /* file format with errors */ @@ -348,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 @@ -442,37 +445,43 @@ 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 ... -# ifdef S_ISFIFO && !S_ISFIFO(perm) // ... or fifo -# endif -# ifdef S_ISSOCK && !S_ISSOCK(perm) // ... or socket -# endif # ifdef OPEN_CHR_FILES && !(S_ISCHR(perm) && is_dev_fd_file(fname)) // ... or a character special file named /dev/fd/<n> # 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); } @@ -480,7 +489,6 @@ readfile( msg_scroll = msg_save; return S_ISDIR(perm) ? NOTDONE : FAIL; } -#endif } /* Set default or forced 'fileformat' and 'binary'. */ @@ -539,13 +547,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; } @@ -603,9 +604,6 @@ readfile( return FAIL; } -#ifndef UNIX - } -#endif /* * Only set the 'ro' flag for readonly files the first time they are @@ -1013,8 +1011,21 @@ retry: */ { if (!skip_read) { - size = 0x10000L; /* use buffer >= 64K */ + // Use buffer >= 64K. Add linerest to double the size if the + // line gets very long, to avoid a lot of copying. But don't + // read more than 1 Mbyte at a time, so we can be interrupted. + size = 0x10000L + linerest; + if (size > 0x100000L) { + size = 0x100000L; + } + } + // Protect against the argument of lalloc() going negative. + if (size < 0 || size + linerest + 1 < 0 || linerest >= MAXCOL) { + split++; + *ptr = NL; // split line by inserting a NL + size = 1; + } else if (!skip_read) { for (; size >= 10; size /= 2) { new_buffer = verbose_try_malloc((size_t)size + (size_t)linerest + 1); if (new_buffer) { @@ -1783,6 +1794,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; @@ -1824,25 +1836,14 @@ failed: c = false; #ifdef UNIX -# ifdef S_ISFIFO - if (S_ISFIFO(perm)) { /* fifo or socket */ - STRCAT(IObuff, _("[fifo/socket]")); - c = TRUE; - } -# else -# ifdef S_IFIFO - if ((perm & S_IFMT) == S_IFIFO) { /* fifo */ + if (S_ISFIFO(perm)) { // fifo STRCAT(IObuff, _("[fifo]")); c = TRUE; } -# endif -# ifdef S_IFSOCK - if ((perm & S_IFMT) == S_IFSOCK) { /* or socket */ + if (S_ISSOCK(perm)) { // or socket STRCAT(IObuff, _("[socket]")); c = TRUE; } -# endif -# endif # ifdef OPEN_CHR_FILES if (S_ISCHR(perm)) { /* or character special */ STRCAT(IObuff, _("[character special]")); @@ -1862,6 +1863,10 @@ failed: STRCAT(IObuff, _("[CR missing]")); c = TRUE; } + if (split) { + STRCAT(IObuff, _("[long lines split]")); + c = true; + } if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); c = TRUE; @@ -3573,7 +3578,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) { /* @@ -5535,7 +5540,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) { @@ -5548,11 +5552,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..24a73a5b9f 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,74 @@ 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++; + } + if (gap->ga_len == 0) { + i = 0; } 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 +666,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 +796,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 +818,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) @@ -834,7 +866,7 @@ void foldUpdateAfterInsert(void) void foldUpdateAll(win_T *win) { win->w_foldinvalid = true; - redraw_win_later(win, NOT_VALID); + redraw_later(win, NOT_VALID); } // foldMoveTo() {{{2 @@ -860,6 +892,9 @@ int foldMoveTo( // that moves the cursor is used. linenr_T lnum_off = 0; garray_T *gap = &curwin->w_folds; + if (gap->ga_len == 0) { + break; + } bool use_level = false; bool maybe_small = false; linenr_T lnum_found = curwin->w_cursor.lnum; @@ -1058,11 +1093,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 +1126,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 +1171,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 +1195,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 +1261,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 +1355,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 +1397,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 +1431,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 +1639,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 +1654,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 +1668,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 +1774,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 +1818,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 +2317,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 +2416,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 +2616,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 +2654,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 +2700,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 +2716,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 +2798,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_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 6e80ad0e5c..b31209e8ff 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -250,7 +250,7 @@ for i = 1, #functions do end output:write('\n } else {') output:write('\n api_set_error(error, kErrorTypeException, \ - "Wrong type for argument '..j..', expecting '..param[1]..'");') + "Wrong type for argument '..j..' when calling '..fn.name..', expecting '..param[1]..'");') output:write('\n goto cleanup;') output:write('\n }\n') else 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..657afeaf4c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -92,6 +92,10 @@ EXTERN struct nvim_stats_s { EXTERN int Rows INIT(= DFLT_ROWS); // nr of rows in the screen EXTERN int Columns INIT(= DFLT_COLS); // nr of columns in the screen +EXTERN NS ns_hl_active INIT(= 0); // current ns that defines highlights +EXTERN bool ns_hl_changed INIT(= false); // highlight need update + + // We use 64-bit file functions here, if available. E.g. ftello() returns // off_t instead of long, which helps if long is 32 bit and off_t is 64 bit. // We assume that when fseeko() is available then ftello() is too. @@ -125,8 +129,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 +210,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 +355,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 @@ -488,9 +492,6 @@ EXTERN int stdout_isatty INIT(= true); // volatile because it is used in a signal handler. EXTERN volatile int full_screen INIT(= false); -// When started in restricted mode (-Z). -EXTERN int restricted INIT(= false); - /// Non-zero when only "safe" commands are allowed, e.g. when sourcing .exrc or /// .vimrc in current directory. EXTERN int secure INIT(= false); @@ -941,8 +942,10 @@ EXTERN char_u e_readonly[] INIT(= N_( EXTERN char_u e_readonlyvar[] INIT(= N_( "E46: Cannot change read-only variable \"%.*s\"")); EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required")); -EXTERN char_u e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s")); -EXTERN char_u e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s")); +EXTERN char_u e_toomanyarg[] INIT(= N_( + "E118: Too many arguments for function: %s")); +EXTERN char_u e_dictkey[] INIT(= N_( + "E716: Key not present in Dictionary: \"%s\"")); EXTERN char_u e_listreq[] INIT(= N_("E714: List required")); EXTERN char_u e_listdictarg[] INIT(= N_( "E712: Argument of %s must be a List or Dictionary")); diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index c6687c8da9..e14aae73d8 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -11,7 +11,7 @@ // The characters and attributes drawn on grids. typedef char_u schar_T[(MAX_MCO+1) * 4 + 1]; -typedef int16_t sattr_T; +typedef int sattr_T; /// ScreenGrid represents a resizable rectuangular grid displayed by UI clients. /// 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/highlight.c b/src/nvim/highlight.c index c0cae54572..898ff4ebfe 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -14,6 +14,7 @@ #include "nvim/ui.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/lua/executor.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "highlight.c.generated.h" @@ -28,12 +29,16 @@ static Map(int, int) *combine_attr_entries; static Map(int, int) *blend_attr_entries; static Map(int, int) *blendthrough_attr_entries; +/// highlight entries private to a namespace +static Map(ColorKey, ColorItem) *ns_hl; + void highlight_init(void) { attr_entry_ids = map_new(HlEntry, int)(); combine_attr_entries = map_new(int, int)(); blend_attr_entries = map_new(int, int)(); blendthrough_attr_entries = map_new(int, int)(); + ns_hl = map_new(ColorKey, ColorItem)(); // index 0 is no attribute, add dummy entry: kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown, @@ -90,7 +95,12 @@ static int get_attr_entry(HlEntry entry) } } - id = (int)kv_size(attr_entries); + size_t next_id = kv_size(attr_entries); + if (next_id > INT_MAX) { + ELOG("The index on attr_entries has overflowed"); + return 0; + } + id = (int)next_id; kv_push(attr_entries, entry); map_put(HlEntry, int)(attr_entry_ids, entry, id); @@ -124,21 +134,114 @@ void ui_send_all_hls(UI *ui) } /// Get attribute code for a syntax group. -int hl_get_syn_attr(int idx, HlAttrs at_en) +int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) { // TODO(bfredl): should we do this unconditionally if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0 || at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1 || at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0 - || at_en.rgb_ae_attr != 0) { + || at_en.rgb_ae_attr != 0 || ns_id != 0) { return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax, - .id1 = idx, .id2 = 0 }); + .id1 = idx, .id2 = ns_id }); } else { // If all the fields are cleared, clear the attr field back to default value return 0; } } +static ColorKey colored_key(NS ns_id, int syn_id) +{ + return (ColorKey){ .ns_id = (int)ns_id, .syn_id = syn_id }; +} + +void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) +{ + DecorProvider *p = get_provider(ns_id, true); + int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); + ColorItem it = { .attr_id = attr_id, + .link_id = link_id, + .version = p->hl_valid }; + map_put(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id), it); +} + +int ns_get_hl(NS ns_id, int hl_id, bool link) +{ + static int recursive = 0; + + if (ns_id < 0) { + if (ns_hl_active <= 0) { + return -1; + } + ns_id = ns_hl_active; + } + + DecorProvider *p = get_provider(ns_id, true); + ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id)); + // TODO(bfredl): map_ref true even this? + bool valid_cache = it.version >= p->hl_valid; + + if (!valid_cache && p->hl_def != LUA_NOREF && !recursive) { + FIXED_TEMP_ARRAY(args, 3); + args.items[0] = INTEGER_OBJ((Integer)ns_id); + args.items[1] = STRING_OBJ(cstr_to_string((char *)syn_id2name(hl_id))); + args.items[2] = BOOLEAN_OBJ(link); + // TODO(bfredl): preload the "global" attr dict? + + Error err = ERROR_INIT; + recursive++; + Object ret = nlua_call_ref(p->hl_def, "hl_def", args, true, &err); + recursive--; + + // TODO(bfredl): or "inherit", combine with global value? + bool fallback = true; + int tmp = false; + HlAttrs attrs = HLATTRS_INIT; + if (ret.type == kObjectTypeDictionary) { + Dictionary dict = ret.data.dictionary; + fallback = false; + attrs = dict2hlattrs(dict, true, &it.link_id, &err); + for (size_t i = 0; i < dict.size; i++) { + char *key = dict.items[i].key.data; + Object val = dict.items[i].value; + bool truthy = api_object_to_bool(val, key, false, &err); + + if (strequal(key, "fallback")) { + fallback = truthy; + } else if (strequal(key, "temp")) { + tmp = truthy; + } + } + if (it.link_id >= 0) { + fallback = true; + } + } + + it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs); + it.version = p->hl_valid-tmp; + map_put(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id), it); + } + + if (link) { + return it.attr_id >= 0 ? -1 : it.link_id; + } else { + return it.attr_id; + } +} + + +bool win_check_ns_hl(win_T *wp) +{ + if (ns_hl_changed) { + highlight_changed(); + if (wp) { + update_window_hl(wp, true); + } + ns_hl_changed = false; + return true; + } + return false; +} + /// Get attribute code for a builtin highlight group. /// /// The final syntax group could be modified by hi-link or 'winhighlight'. @@ -199,6 +302,17 @@ void update_window_hl(win_T *wp, bool invalid) wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; } + // NOOOO! You cannot just pretend that "Normal" is just like any other + // syntax group! It needs at least 10 layers of special casing! Noooooo! + // + // haha, theme engine go brrr + int normality = syn_check_group((const char_u *)S_LEN("Normal")); + int ns_attr = ns_get_hl(-1, normality, false); + if (ns_attr > 0) { + // TODO(bfredl): hantera NormalNC and so on + wp->w_hl_attr_normal = ns_attr; + } + // if blend= attribute is not set, 'winblend' value overrides it. if (wp->w_floating && wp->w_p_winbl > 0) { HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal); @@ -270,6 +384,7 @@ void clear_hl_tables(bool reinit) map_free(int, int)(combine_attr_entries); map_free(int, int)(blend_attr_entries); map_free(int, int)(blendthrough_attr_entries); + map_free(ColorKey, ColorItem)(ns_hl); } } @@ -658,6 +773,103 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) return hl; } +HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) +{ + HlAttrs hlattrs = HLATTRS_INIT; + + int32_t fg = -1, bg = -1, sp = -1; + int16_t mask = 0; + for (size_t i = 0; i < dict.size; i++) { + char *key = dict.items[i].key.data; + Object val = dict.items[i].value; + + struct { + const char *name; + int16_t flag; + } flags[] = { + { "bold", HL_BOLD }, + { "standout", HL_STANDOUT }, + { "underline", HL_UNDERLINE }, + { "undercurl", HL_UNDERCURL }, + { "italic", HL_ITALIC }, + { "reverse", HL_INVERSE }, + { NULL, 0 }, + }; + + int j; + for (j = 0; flags[j].name; j++) { + if (strequal(flags[j].name, key)) { + if (api_object_to_bool(val, key, false, err)) { + mask = mask | flags[j].flag; + } + break; + } + } + + struct { + const char *name; + const char *shortname; + int *dest; + } colors[] = { + { "foreground", "fg", &fg }, + { "background", "bg", &bg }, + { "special", "sp", &sp }, + { NULL, NULL, NULL }, + }; + + int k; + for (k = 0; (!flags[j].name) && colors[k].name; k++) { + if (strequal(colors[k].name, key) || strequal(colors[k].shortname, key)) { + if (val.type == kObjectTypeInteger) { + *colors[k].dest = (int)val.data.integer; + } else if (val.type == kObjectTypeString) { + String str = val.data.string; + // TODO(bfredl): be more fancy with "bg", "fg" etc + if (str.size) { + *colors[k].dest = name_to_color(str.data); + } + } else { + api_set_error(err, kErrorTypeValidation, + "'%s' must be string or integer", key); + } + break; + } + } + + + if (flags[j].name || colors[k].name) { + // handled above + } else if (link_id && strequal(key, "link")) { + if (val.type == kObjectTypeString) { + String str = val.data.string; + *link_id = syn_check_group((const char_u *)str.data, (int)str.size); + } else if (val.type == kObjectTypeInteger) { + // TODO(bfredl): validate range? + *link_id = (int)val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'link' must be string or integer"); + } + } + + if (ERROR_SET(err)) { + return hlattrs; // error set, caller should not use retval + } + } + + if (use_rgb) { + hlattrs.rgb_ae_attr = mask; + hlattrs.rgb_bg_color = bg; + hlattrs.rgb_fg_color = fg; + hlattrs.rgb_sp_color = sp; + } else { + hlattrs.cterm_ae_attr = mask; + hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1; + hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1; + } + + return hlattrs; +} Array hl_inspect(int attr) { Array ret = ARRAY_DICT_INIT; diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 36f3181674..6a5c593ab1 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -4,6 +4,7 @@ #include <inttypes.h> #include "nvim/macros.h" +#include "nvim/types.h" typedef int32_t RgbValue; @@ -180,6 +181,20 @@ typedef struct { HlKind kind; int id1; int id2; + int winid; } HlEntry; +typedef struct { + int ns_id; + int syn_id; +} ColorKey; + +typedef struct { + int attr_id; + int link_id; + int version; +} ColorItem; +#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1 } + + #endif // NVIM_HIGHLIGHT_DEFS_H diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 2af09f10cb..2dad8fb781 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -459,7 +459,7 @@ staterr: int i; // if filename is a directory, append the cscope database name to it - if ((file_info.stat.st_mode & S_IFMT) == S_IFDIR) { + if (S_ISDIR(file_info.stat.st_mode)) { fname2 = (char *)xmalloc(strlen(CSCOPE_DBFILE) + strlen(fname) + 2); while (fname[strlen(fname)-1] == '/' diff --git a/src/nvim/indent.c b/src/nvim/indent.c index fb277b25fd..9e6693afdf 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -295,13 +295,18 @@ int set_indent(int size, int flags) // Replace the line (unless undo fails). if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) { + const colnr_T old_offset = (colnr_T)(p - oldline); + const colnr_T new_offset = (colnr_T)(s - newline); + + // this may free "newline" 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, + old_offset - skipcols, + new_offset - skipcols, + kExtmarkUndo); } if (flags & SIN_CHANGED) { @@ -310,15 +315,14 @@ int set_indent(int size, int flags) // Correct saved cursor position if it is in this line. if (saved_cursor.lnum == curwin->w_cursor.lnum) { - if (saved_cursor.col >= (colnr_T)(p - oldline)) { + if (saved_cursor.col >= old_offset) { // Cursor was after the indent, adjust for the number of // bytes added/removed. - saved_cursor.col += ind_len - (colnr_T)(p - oldline); - - } else if (saved_cursor.col >= (colnr_T)(s - newline)) { + saved_cursor.col += ind_len - old_offset; + } else if (saved_cursor.col >= new_offset) { // Cursor was in the indent, and is now after it, put it back // at the start of the indent (replacing spaces with TAB). - saved_cursor.col = (colnr_T)(s - newline); + saved_cursor.col = new_offset; } } retval = true; diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index bb443161ef..9298e57411 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -1676,6 +1676,9 @@ void parse_cino(buf_T *buf) // Handle C++ extern "C" or "C++" buf->b_ind_cpp_extern_c = 0; + // Handle C #pragma directives + buf->b_ind_pragma = 0; + for (p = buf->b_p_cino; *p; ) { l = p++; if (*p == '-') { @@ -1747,6 +1750,7 @@ void parse_cino(buf_T *buf) case 'N': buf->b_ind_cpp_namespace = n; break; case 'k': buf->b_ind_if_for_while = n; break; case 'E': buf->b_ind_cpp_extern_c = n; break; + case 'P': buf->b_ind_pragma = n; break; } if (*p == ',') ++p; @@ -1858,12 +1862,14 @@ int get_c_indent(void) goto laterend; } - /* - * #defines and so on always go at the left when included in 'cinkeys'. - */ + // #defines and so on go at the left when included in 'cinkeys', + // exluding pragmas when customized in 'cinoptions' if (*theline == '#' && (*linecopy == '#' || in_cinkeys('#', ' ', true))) { - amount = curbuf->b_ind_hash_comment; - goto theend; + const char_u *const directive = skipwhite(theline + 1); + if (curbuf->b_ind_pragma == 0 || STRNCMP(directive, "pragma", 6) != 0) { + amount = curbuf->b_ind_hash_comment; + goto theend; + } } /* 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 0d5622f1e7..0a3c30134b 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); @@ -467,7 +488,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const char *code = (char *)&shared_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua") + if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/shared.lua") || lua_pcall(lstate, 0, 0, 0)) { nlua_error(lstate, _("E5106: Error while creating shared module: %.*s")); return 1; @@ -475,6 +496,22 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL } { + lua_getglobal(lstate, "package"); // [package] + lua_getfield(lstate, -1, "loaded"); // [package, loaded] + + const char *code = (char *)&inspect_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/inspect.lua") + || lua_pcall(lstate, 0, 1, 0)) { + nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s")); + return 1; + } + // [package, loaded, inspect] + + lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded] + lua_pop(lstate, 2); // [] + } + + { const char *code = (char *)&vim_module[0]; if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") || lua_pcall(lstate, 0, 0, 0)) { @@ -528,14 +565,6 @@ static lua_State *nlua_enter(void) // stack: (empty) lua_getglobal(lstate, "vim"); // stack: vim - lua_getfield(lstate, -1, "_update_package_paths"); - // stack: vim, vim._update_package_paths - if (lua_pcall(lstate, 0, 0, 0)) { - // stack: vim, error - nlua_error(lstate, _("E5117: Error while updating package paths: %.*s")); - // stack: vim - } - // stack: vim lua_pop(lstate, 1); // stack: (empty) last_p_rtp = (const void *)p_rtp; @@ -837,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); @@ -862,6 +891,17 @@ LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref) return new_ref; } +LuaRef api_new_luaref(LuaRef original_ref) +{ + if (original_ref == LUA_NOREF) { + return LUA_NOREF; + } + + lua_State *const lstate = nlua_enter(); + return nlua_newref(lstate, original_ref); +} + + /// Evaluate lua string /// /// Used for luaeval(). @@ -871,8 +911,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 (" @@ -894,8 +934,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 " @@ -925,7 +965,7 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, typval_T *const args, int argcount, bool special, typval_T *ret_tv) { - if (check_restricted() || check_secure()) { + if (check_secure()) { if (ret_tv) { ret_tv->v_type = VAR_NUMBER; ret_tv->vval.v_number = 0; @@ -998,14 +1038,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(); @@ -1032,17 +1072,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; @@ -1457,3 +1510,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..0515bbfe53 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -23,11 +23,6 @@ #include "nvim/buffer.h" typedef struct { - TSParser *parser; - TSTree *tree; // internal tree, used for editing/reparsing -} TSLua_parser; - -typedef struct { TSQueryCursor *cursor; int predicated_match; } TSLua_cursor; @@ -39,10 +34,9 @@ typedef struct { static struct luaL_Reg parser_meta[] = { { "__gc", parser_gc }, { "__tostring", parser_tostring }, - { "parse_buf", parser_parse_buf }, - { "edit", parser_edit }, - { "tree", parser_tree }, + { "parse", parser_parse }, { "set_included_ranges", parser_set_ranges }, + { "included_ranges", parser_get_ranges }, { NULL, NULL } }; @@ -50,6 +44,8 @@ static struct luaL_Reg tree_meta[] = { { "__gc", tree_gc }, { "__tostring", tree_tostring }, { "root", tree_root }, + { "edit", tree_edit }, + { "copy", tree_copy }, { NULL, NULL } }; @@ -57,11 +53,13 @@ static struct luaL_Reg node_meta[] = { { "__tostring", node_tostring }, { "__eq", node_eq }, { "__len", node_child_count }, + { "id", node_id }, { "range", node_range }, { "start", node_start }, { "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 +71,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 +83,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 +120,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 +171,15 @@ 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 + || lang_version > TREE_SITTER_LANGUAGE_VERSION) { + return luaL_error( + L, + "ABI version mismatch : expected %d, found %d", + TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, lang_version); + } + pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); lua_pushboolean(L, true); @@ -228,34 +242,32 @@ int tslua_push_parser(lua_State *L) return luaL_error(L, "no such language: %s", lang_name); } - TSParser *parser = ts_parser_new(); - ts_parser_set_language(parser, lang); - TSLua_parser *p = lua_newuserdata(L, sizeof(TSLua_parser)); // [udata] - p->parser = parser; - p->tree = NULL; + TSParser **parser = lua_newuserdata(L, sizeof(TSParser *)); + *parser = ts_parser_new(); + + if (!ts_parser_set_language(*parser, lang)) { + ts_parser_delete(*parser); + return luaL_error(L, "Failed to load language : %s", lang_name); + } lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta] lua_setmetatable(L, -2); // [udata] return 1; } -static TSLua_parser *parser_check(lua_State *L) +static TSParser ** parser_check(lua_State *L, uint16_t index) { - return luaL_checkudata(L, 1, "treesitter_parser"); + return luaL_checkudata(L, index, "treesitter_parser"); } static int parser_gc(lua_State *L) { - TSLua_parser *p = parser_check(L); + TSParser **p = parser_check(L, 1); if (!p) { return 0; } - ts_parser_delete(p->parser); - if (p->tree) { - ts_tree_delete(p->tree); - } - + ts_parser_delete(*p); return 0; } @@ -278,96 +290,133 @@ 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) { - TSLua_parser *p = parser_check(L); - if (!p) { + 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) +{ + TSParser **p = parser_check(L, 1); + if (!p || !(*p)) { return 0; } - long bufnr = lua_tointeger(L, 2); - buf_T *buf = handle_get_buffer(bufnr); + TSTree *old_tree = NULL; + if (!lua_isnil(L, 2)) { + TSTree **tmp = tree_check(L, 2); + old_tree = tmp ? *tmp : NULL; + } - if (!buf) { - return luaL_error(L, "invalid buffer handle: %d", bufnr); + TSTree *new_tree = NULL; + 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, 3)) { + case LUA_TSTRING: + str = lua_tolstring(L, 3, &len); + new_tree = ts_parser_parse_string(*p, old_tree, str, len); + break; + + case LUA_TNUMBER: + bufnr = lua_tointeger(L, 3); + buf = handle_get_buffer(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, old_tree, input); + + break; + + default: + return luaL_error(L, "invalid argument to parser:parse()"); } - TSInput input = { (void *)buf, input_cb, TSInputEncodingUTF8 }; - TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); + // Sometimes parsing fails (timeout, or wrong parser ABI) + // In those case, just return an error. + if (!new_tree) { + return luaL_error(L, "An error occured when parsing."); + } + // The new tree will be pushed to the stack, without copy, owwership is now to + // the lua GC. + // Old tree is still owned by the lua GC. uint32_t n_ranges = 0; - TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree, - &n_ranges) : NULL; - if (p->tree) { - ts_tree_delete(p->tree); - } - p->tree = new_tree; + TSRange *changed = old_tree ? ts_tree_get_changed_ranges( + old_tree, new_tree, &n_ranges) : NULL; - tslua_push_tree(L, p->tree); + tslua_push_tree(L, new_tree, false); // [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); // [tree, ranges] - lua_rawseti(L, -2, i+1); - } xfree(changed); return 2; } -static int parser_tree(lua_State *L) +static int tree_copy(lua_State *L) { - TSLua_parser *p = parser_check(L); - if (!p) { + TSTree **tree = tree_check(L, 1); + if (!(*tree)) { return 0; } - tslua_push_tree(L, p->tree); + tslua_push_tree(L, *tree, true); // [tree] + return 1; } -static int parser_edit(lua_State *L) +static int tree_edit(lua_State *L) { if (lua_gettop(L) < 10) { - lua_pushstring(L, "not enough args to parser:edit()"); + lua_pushstring(L, "not enough args to tree:edit()"); return lua_error(L); } - TSLua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - if (!p->tree) { + TSTree **tree = tree_check(L, 1); + if (!(*tree)) { return 0; } @@ -381,7 +430,7 @@ static int parser_edit(lua_State *L) TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, start_point, old_end_point, new_end_point }; - ts_tree_edit(p->tree, &edit); + ts_tree_edit(*tree, &edit); return 0; } @@ -394,8 +443,8 @@ static int parser_set_ranges(lua_State *L) "not enough args to parser:set_included_ranges()"); } - TSLua_parser *p = parser_check(L); - if (!p || !p->tree) { + TSParser **p = parser_check(L, 1); + if (!p) { return 0; } @@ -431,26 +480,46 @@ static int parser_set_ranges(lua_State *L) } // This memcpies ranges, thus we can free it afterwards - ts_parser_set_included_ranges(p->parser, ranges, tbl_len); + ts_parser_set_included_ranges(*p, ranges, tbl_len); xfree(ranges); return 0; } +static int parser_get_ranges(lua_State *L) +{ + TSParser **p = parser_check(L, 1); + if (!p) { + return 0; + } + + unsigned int len; + const TSRange *ranges = ts_parser_included_ranges(*p, &len); + + push_ranges(L, ranges, len); + return 1; +} + // Tree methods /// push tree interface on lua stack. /// /// This makes a copy of the tree, so ownership of the argument is unaffected. -void tslua_push_tree(lua_State *L, TSTree *tree) +void tslua_push_tree(lua_State *L, TSTree *tree, bool do_copy) { if (tree == NULL) { lua_pushnil(L); return; } TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] - *ud = ts_tree_copy(tree); + + if (do_copy) { + *ud = ts_tree_copy(tree); + } else { + *ud = tree; + } + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] lua_setmetatable(L, -2); // [udata] @@ -463,20 +532,20 @@ void tslua_push_tree(lua_State *L, TSTree *tree) lua_setfenv(L, -2); // [udata] } -static TSTree *tree_check(lua_State *L) +static TSTree **tree_check(lua_State *L, uint16_t index) { - TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree"); - return *ud; + TSTree **ud = luaL_checkudata(L, index, "treesitter_tree"); + return ud; } static int tree_gc(lua_State *L) { - TSTree *tree = tree_check(L); + TSTree **tree = tree_check(L, 1); if (!tree) { return 0; } - ts_tree_delete(tree); + ts_tree_delete(*tree); return 0; } @@ -488,11 +557,11 @@ static int tree_tostring(lua_State *L) static int tree_root(lua_State *L) { - TSTree *tree = tree_check(L); + TSTree **tree = tree_check(L, 1); if (!tree) { return 0; } - TSNode root = ts_tree_root_node(tree); + TSNode root = ts_tree_root_node(*tree); push_node(L, root, 1); return 1; } @@ -560,6 +629,17 @@ static int node_eq(lua_State *L) return 1; } +static int node_id(lua_State *L) +{ + TSNode node; + if (!node_check(L, 1, &node)) { + return 0; + } + + lua_pushlstring(L, (const char *)&node.id, sizeof node.id); + return 1; +} + static int node_range(lua_State *L) { TSNode node; @@ -646,6 +726,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 +854,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..2c7ab46ffe 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -36,6 +36,9 @@ local vim = vim assert(vim) +vim.inspect = package.loaded['vim.inspect'] +assert(vim.inspect) + -- Internal-only until comments in #8107 are addressed. -- Returns: -- {errcode}, {output} @@ -92,67 +95,44 @@ function vim._os_proc_children(ppid) return children end --- TODO(ZyX-I): Create compatibility layer. ---{{{1 package.path updater function --- Last inserted paths. Used to clear out items from package.[c]path when they --- are no longer in &runtimepath. -local last_nvim_paths = {} -function vim._update_package_paths() - local cur_nvim_paths = {} - local rtps = vim.api.nvim_list_runtime_paths() - local sep = package.config:sub(1, 1) - for _, key in ipairs({'path', 'cpath'}) do - local orig_str = package[key] .. ';' - local pathtrails_ordered = {} - local orig = {} - -- Note: ignores trailing item without trailing `;`. Not using something - -- simpler in order to preserve empty items (stand for default path). - for s in orig_str:gmatch('[^;]*;') do - s = s:sub(1, -2) -- Strip trailing semicolon - orig[#orig + 1] = s - end - if key == 'path' then - -- /?.lua and /?/init.lua - pathtrails_ordered = {sep .. '?.lua', sep .. '?' .. sep .. 'init.lua'} - else - local pathtrails = {} - for _, s in ipairs(orig) do - -- Find out path patterns. pathtrail should contain something like - -- /?.so, \?.dll. This allows not to bother determining what correct - -- suffixes are. - local pathtrail = s:match('[/\\][^/\\]*%?.*$') - if pathtrail and not pathtrails[pathtrail] then - pathtrails[pathtrail] = true - pathtrails_ordered[#pathtrails_ordered + 1] = pathtrail - end - end - end - local new = {} - for _, rtp in ipairs(rtps) do - if not rtp:match(';') then - for _, pathtrail in pairs(pathtrails_ordered) do - local new_path = rtp .. sep .. 'lua' .. pathtrail - -- Always keep paths from &runtimepath at the start: - -- append them here disregarding orig possibly containing one of them. - new[#new + 1] = new_path - cur_nvim_paths[new_path] = true - end - end +local pathtrails = {} +vim._so_trails = {} +for s in (package.cpath..';'):gmatch('[^;]*;') do + s = s:sub(1, -2) -- Strip trailing semicolon + -- Find out path patterns. pathtrail should contain something like + -- /?.so, \?.dll. This allows not to bother determining what correct + -- suffixes are. + local pathtrail = s:match('[/\\][^/\\]*%?.*$') + if pathtrail and not pathtrails[pathtrail] then + pathtrails[pathtrail] = true + table.insert(vim._so_trails, pathtrail) + end +end + +function vim._load_package(name) + local basename = name:gsub('%.', '/') + local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"} + for _,path in ipairs(paths) do + local found = vim.api.nvim_get_runtime_file(path, false) + if #found > 0 then + return loadfile(found[1]) end - for _, orig_path in ipairs(orig) do - -- Handle removing obsolete paths originating from &runtimepath: such - -- paths either belong to cur_nvim_paths and were already added above or - -- to last_nvim_paths and should not be added at all if corresponding - -- entry was removed from &runtimepath list. - if not (cur_nvim_paths[orig_path] or last_nvim_paths[orig_path]) then - new[#new + 1] = orig_path - end + end + + for _,trail in ipairs(vim._so_trails) do + local path = "lua/"..trail:gsub('?',basename) + local found = vim.api.nvim_get_runtime_file(path, false) + if #found > 0 then + return package.loadlib(found[1]) end - package[key] = table.concat(new, ';') end - last_nvim_paths = cur_nvim_paths + return nil end +table.insert(package.loaders, 1, vim._load_package) + +-- TODO(ZyX-I): Create compatibility layer. + --- Return a human-readable representation of the given object. --- --@see https://github.com/kikito/inspect.lua @@ -279,10 +259,7 @@ end -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c local function __index(t, key) - if key == 'inspect' then - t.inspect = require('vim.inspect') - return t.inspect - elseif key == 'treesitter' then + if key == 'treesitter' then t.treesitter = require('vim.treesitter') return t.treesitter elseif require('vim.uri')[key] ~= nil then @@ -295,6 +272,9 @@ local function __index(t, key) elseif key == 'highlight' then t.highlight = require('vim.highlight') return t.highlight + elseif key == 'F' then + t.F = require('vim.F') + return t.F end end @@ -489,4 +469,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..6ff5216a84 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/decoration.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(); + decor_init(); eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. @@ -442,7 +446,7 @@ int main(int argc, char **argv) if (exmode_active || use_remote_ui || use_builtin_ui) { // Don't clear the screen when starting in Ex mode, or when a UI might have // displayed messages. - redraw_later(VALID); + redraw_later(curwin, VALID); } else { screenclear(); // clear screen TIME_MSG("clearing screen"); @@ -1008,10 +1012,6 @@ static void command_line_scan(mparm_T *parmp) want_argument = true; break; } - case 'Z': { // "-Z" restricted mode - restricted = true; - break; - } case 'c': { // "-c{command}" or "-c {command}" exec command if (argv[0][argv_idx] != NUL) { diff --git a/src/nvim/map.c b/src/nvim/map.c index cba39f24b3..7d97b7f13d 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -12,6 +12,9 @@ #include <stdbool.h> #include <string.h> +#include <lua.h> +#include <lauxlib.h> + #include "nvim/map.h" #include "nvim/map_defs.h" #include "nvim/vim.h" @@ -173,6 +176,20 @@ static inline bool HlEntry_eq(HlEntry ae1, HlEntry ae2) return memcmp(&ae1, &ae2, sizeof(ae1)) == 0; } +static inline khint_t ColorKey_hash(ColorKey ae) +{ + const uint8_t *data = (const uint8_t *)&ae; + khint_t h = 0; + for (size_t i = 0; i < sizeof(ae); i++) { + h = (h << 5) - h + data[i]; + } + return h; +} + +static inline bool ColorKey_eq(ColorKey ae1, ColorKey ae2) +{ + return memcmp(&ae1, &ae2, sizeof(ae1)) == 0; +} MAP_IMPL(int, int, DEFAULT_INITIALIZER) @@ -183,8 +200,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 } @@ -192,6 +208,7 @@ MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(String, handle_T, 0) +MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER) /// Deletes a key:value pair from a string:pointer map, and frees the /// storage of both key and value. diff --git a/src/nvim/map.h b/src/nvim/map.h index 0ad7865bf0..7bd3d31330 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -55,6 +55,8 @@ MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(HlEntry, int) MAP_DECLS(String, handle_T) +MAP_DECLS(ColorKey, ColorItem) + #define map_new(T, U) map_##T##_##U##_new #define map_free(T, U) map_##T##_##U##_free #define map_get(T, U) map_##T##_##U##_get @@ -73,6 +75,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/mark.c b/src/nvim/mark.c index fa7c7d61c9..1ecfae57ed 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -632,6 +632,7 @@ void ex_marks(exarg_T *eap) char_u *arg = eap->arg; int i; char_u *name; + pos_T *posp, *startp, *endp; if (arg != NULL && *arg == NUL) arg = NULL; @@ -657,8 +658,18 @@ void ex_marks(exarg_T *eap) show_one_mark(']', arg, &curbuf->b_op_end, NULL, true); show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, true); show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true); - show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, true); - show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, true); + + // Show the marks as where they will jump to. + startp = &curbuf->b_visual.vi_start; + endp = &curbuf->b_visual.vi_end; + if ((lt(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0) { + posp = startp; + } else { + posp = endp; + } + show_one_mark('<', arg, posp, NULL, true); + show_one_mark('>', arg, posp == startp ? endp : startp, NULL, true); + show_one_mark(-1, arg, NULL, NULL, false); } diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 6dd452b5af..e9ea2cbba9 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -326,38 +326,37 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) x->n--; // 4. - if (adjustment) { - if (adjustment == 1) { - abort(); - } else { // adjustment == -1 - int ilvl = itr->lvl-1; - mtnode_t *lnode = x; - do { - mtnode_t *p = lnode->parent; - if (ilvl < 0) { - abort(); - } - int i = itr->s[ilvl].i; - assert(p->ptr[i] == lnode); - if (i > 0) { - unrelative(p->key[i-1].pos, &intkey.pos); - } - lnode = p; - ilvl--; - } while (lnode != cur); - - mtkey_t deleted = cur->key[curi]; - cur->key[curi] = intkey; - refkey(b, cur, curi); - relative(intkey.pos, &deleted.pos); - mtnode_t *y = cur->ptr[curi+1]; - if (deleted.pos.row || deleted.pos.col) { - while (y) { - for (int k = 0; k < y->n; k++) { - unrelative(deleted.pos, &y->key[k].pos); - } - y = y->level ? y->ptr[0] : NULL; + // if (adjustment == 1) { + // abort(); + // } + if (adjustment == -1) { + int ilvl = itr->lvl-1; + const mtnode_t *lnode = x; + do { + const mtnode_t *const p = lnode->parent; + if (ilvl < 0) { + abort(); + } + const int i = itr->s[ilvl].i; + assert(p->ptr[i] == lnode); + if (i > 0) { + unrelative(p->key[i-1].pos, &intkey.pos); + } + lnode = p; + ilvl--; + } while (lnode != cur); + + mtkey_t deleted = cur->key[curi]; + cur->key[curi] = intkey; + refkey(b, cur, curi); + relative(intkey.pos, &deleted.pos); + mtnode_t *y = cur->ptr[curi+1]; + if (deleted.pos.row || deleted.pos.col) { + while (y) { + for (int k = 0; k < y->n; k++) { + unrelative(deleted.pos, &y->key[k].pos); } + y = y->level ? y->ptr[0] : NULL; } } } @@ -435,9 +434,10 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) // BONUS STEP: fix the iterator, so that it points to the key afterwards // TODO(bfredl): with "rev" should point before - if (adjustment == 1) { - abort(); - } else if (adjustment == -1) { + // if (adjustment == 1) { + // abort(); + // } + if (adjustment == -1) { // tricky: we stand at the deleted space in the previous leaf node. // But the inner key is now the previous key we stole, so we need // to skip that one as well. 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/menu.c b/src/nvim/menu.c index b060464383..4ba2e36656 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -193,7 +193,7 @@ ex_menu(exarg_T *eap) vimmenu_T **root_menu_ptr = get_root_menu(menu_path); if (root_menu_ptr == &curwin->w_winbar) { // Assume the window toolbar menu will change. - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } if (enable != kNone) { diff --git a/src/nvim/message.c b/src/nvim/message.c index 8999365d32..06ba607323 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++; } @@ -1217,7 +1216,7 @@ void wait_return(int redraw) ui_refresh(); } else if (!skip_redraw) { if (redraw == true || (msg_scrolled != 0 && redraw != -1)) { - redraw_later(VALID); + redraw_later(curwin, VALID); } if (ui_has(kUIMessages)) { msg_ext_clear(true); @@ -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 @@ -2222,7 +2222,7 @@ void msg_scroll_up(bool may_throttle) /// /// Probably message scrollback storage should reimplented as a file_buffer, and /// message scrolling in TUI be reimplemented as a modal floating window. Then -/// we get throttling "for free" using standard redraw_win_later code paths. +/// we get throttling "for free" using standard redraw_later code paths. void msg_scroll_flush(void) { if (msg_grid.throttled) { diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 6dafbafb3e..fcffe64595 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); } @@ -1091,8 +1091,9 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, { char_u *buffer = NULL; - if (check_restricted() || check_secure()) + if (check_secure()) { return NULL; + } // get a name for the temp file char_u *tempname = vim_tempname(); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index fcd9ee4f75..cff88de00b 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -75,6 +75,7 @@ int jump_to_mouse(int flags, int grid = mouse_grid; int mouse_char; int fdc = 0; + ScreenGrid *gp = &default_grid; mouse_past_bottom = false; mouse_past_eol = false; @@ -125,16 +126,6 @@ retnomove: if (flags & MOUSE_SETPOS) goto retnomove; // ugly goto... - // Remember the character under the mouse, might be one of foldclose or - // foldopen fillchars in the fold column. - if (row >= 0 && row < Rows && col >= 0 && col <= Columns - && default_grid.chars != NULL) { - mouse_char = utf_ptr2char(default_grid.chars[default_grid.line_offset[row] - + (unsigned)col]); - } else { - mouse_char = ' '; - } - old_curwin = curwin; old_cursor = curwin->w_cursor; @@ -286,7 +277,7 @@ retnomove: check_topfill(curwin, false); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); - redraw_later(VALID); + redraw_later(curwin, VALID); row = 0; } else if (row >= curwin->w_height_inner) { count = 0; @@ -316,7 +307,7 @@ retnomove: } } check_topfill(curwin, false); - redraw_later(VALID); + redraw_later(curwin, VALID); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); row = curwin->w_height_inner - 1; @@ -333,6 +324,19 @@ retnomove: } } + // Remember the character under the mouse, might be one of foldclose or + // foldopen fillchars in the fold column. + if (ui_has(kUIMultigrid)) { + gp = &curwin->w_grid; + } + if (row >= 0 && row < Rows && col >= 0 && col <= Columns + && gp->chars != NULL) { + mouse_char = utf_ptr2char(gp->chars[gp->line_offset[row] + + (unsigned)col]); + } else { + mouse_char = ' '; + } + // Check for position outside of the fold column. if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc : col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { @@ -450,7 +454,7 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) // skip line number and fold column in front of the line col -= win_col_off(win); - if (col < 0) { + if (col <= 0) { col = 0; } diff --git a/src/nvim/move.c b/src/nvim/move.c index 8a8a639a52..ccd19a81de 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -107,7 +107,7 @@ void redraw_for_cursorline(win_T *wp) && !pum_visible()) { if (wp->w_p_rnu) { // win_line() will redraw the number column only. - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); } if (win_cursorline_standout(wp)) { if (wp->w_redr_type <= VALID && wp->w_last_cursorline != 0) { @@ -117,7 +117,7 @@ void redraw_for_cursorline(win_T *wp) redrawWinline(wp, wp->w_last_cursorline); redrawWinline(wp, wp->w_cursor.lnum); } else { - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); } } } @@ -172,7 +172,7 @@ void update_topline(void) // If the buffer is empty, always set topline to 1. if (BUFEMPTY()) { // special case - file is empty if (curwin->w_topline != 1) { - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } curwin->w_topline = 1; curwin->w_botline = 2; @@ -326,12 +326,14 @@ void update_topline(void) dollar_vcol = -1; if (curwin->w_skipcol != 0) { curwin->w_skipcol = 0; - redraw_later(NOT_VALID); - } else - redraw_later(VALID); - /* May need to set w_skipcol when cursor in w_topline. */ - if (curwin->w_cursor.lnum == curwin->w_topline) + redraw_later(curwin, NOT_VALID); + } else { + redraw_later(curwin, VALID); + } + // May need to set w_skipcol when cursor in w_topline. + if (curwin->w_cursor.lnum == curwin->w_topline) { validate_cursor(); + } } *so_ptr = save_so; @@ -439,7 +441,7 @@ void changed_window_setting_win(win_T *wp) wp->w_lines_valid = 0; changed_line_abv_curs_win(wp); wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP|VALID_TOPLINE); - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } /* @@ -455,8 +457,8 @@ void set_topline(win_T *wp, linenr_T lnum) wp->w_topline_was_set = true; wp->w_topfill = 0; wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_TOPLINE); - /* Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. */ - redraw_later(VALID); + // Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. + redraw_later(wp, VALID); } /* @@ -634,14 +636,14 @@ void validate_virtcol_win(win_T *wp) if (wp->w_p_cuc && !pum_visible() ) - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); } } /* * 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)) { @@ -830,7 +832,7 @@ void curs_columns( curwin->w_leftcol = new_leftcol; win_check_anchored_floats(curwin); // screen has to be redrawn with new curwin->w_leftcol - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } } curwin->w_wcol -= curwin->w_leftcol; @@ -934,15 +936,19 @@ void curs_columns( } else { curwin->w_skipcol = 0; } - if (prev_skipcol != curwin->w_skipcol) - redraw_later(NOT_VALID); + if (prev_skipcol != curwin->w_skipcol) { + redraw_later(curwin, NOT_VALID); + } /* Redraw when w_virtcol changes and 'cursorcolumn' is set */ if (curwin->w_p_cuc && (curwin->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) { - redraw_later(SOME_VALID); + redraw_later(curwin, 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; } @@ -1014,16 +1020,13 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, *ecolp = ecol + coloff; } -/* - * Scroll the current window down by "line_count" logical lines. "CTRL-Y" - */ -void -scrolldown ( - long line_count, - int byfold /* true: count a closed fold as one line */ -) +/// Scroll the current window down by "line_count" logical lines. "CTRL-Y" +/// +/// @param line_count number of lines to scroll +/// @param byfold if true, count a closed fold as one line +bool scrolldown(long line_count, int byfold) { - int done = 0; /* total # of physical lines done */ + int done = 0; // total # of physical lines done /* Make sure w_topline is at the first of a sequence of folded lines. */ (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); @@ -1092,17 +1095,18 @@ scrolldown ( foldAdjustCursor(); coladvance(curwin->w_curswant); } + return moved; } -/* - * Scroll the current window up by "line_count" logical lines. "CTRL-E" - */ -void -scrollup ( - long line_count, - int byfold /* true: count a closed fold as one line */ -) +/// Scroll the current window up by "line_count" logical lines. "CTRL-E" +/// +/// @param line_count number of lines to scroll +/// @param byfold if true, count a closed fold as one line +bool scrollup(long line_count, int byfold) { + linenr_T topline = curwin->w_topline; + linenr_T botline = curwin->w_botline; + if ((byfold && hasAnyFolding(curwin)) || curwin->w_p_diff) { // count each sequence of folded lines as one logical line @@ -1145,6 +1149,11 @@ scrollup ( ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); coladvance(curwin->w_curswant); } + + bool moved = topline != curwin->w_topline + || botline != curwin->w_botline; + + return moved; } /* @@ -2010,7 +2019,7 @@ int onepage(Direction dir, long count) } } - redraw_later(VALID); + redraw_later(curwin, VALID); return retval; } @@ -2196,7 +2205,7 @@ void halfpage(bool flag, linenr_T Prenum) check_topfill(curwin, !flag); cursor_correct(); beginline(BL_SOL | BL_FIX); - redraw_later(VALID); + redraw_later(curwin, VALID); } void do_check_cursorbind(void) @@ -2244,7 +2253,7 @@ void do_check_cursorbind(void) } // Correct cursor for multi-byte character. mb_adjust_cursor(); - redraw_later(VALID); + redraw_later(curwin, VALID); // Only scroll when 'scrollbind' hasn't done this. if (!curwin->w_p_scb) { diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 92ca29209e..68ef4cd41e 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -262,11 +262,9 @@ static void parse_msgpack(Channel *channel) call_set_error(channel, buf, ERROR_LOG_LEVEL); } msgpack_unpacked_destroy(&unpacked); - // Bail out from this event loop iteration - return; + } else { + handle_request(channel, &unpacked.data); } - - handle_request(channel, &unpacked.data); } if (result == MSGPACK_UNPACK_NOMEM_ERROR) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 968cfde388..2805a7d74e 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -903,6 +903,10 @@ normal_end: msg_nowait = false; + if (finish_op) { + set_reg_var(get_default_register_name()); + } + // Reset finish_op, in case it was set s->c = finish_op; finish_op = false; @@ -1193,6 +1197,15 @@ static void normal_check_interrupt(NormalState *s) } } +static void normal_check_window_scrolled(NormalState *s) +{ + // Trigger Scroll if the viewport changed. + if (!finish_op && has_event(EVENT_WINSCROLLED) + && win_did_scroll(curwin)) { + do_autocmd_winscrolled(curwin); + } +} + static void normal_check_cursor_moved(NormalState *s) { // Trigger CursorMoved if the cursor moved. @@ -1216,6 +1229,16 @@ static void normal_check_text_changed(NormalState *s) } } +static void normal_check_buffer_modified(NormalState *s) +{ + // Trigger BufModified if b_modified changed + if (!finish_op && has_event(EVENT_BUFMODIFIEDSET) + && curbuf->b_changed_invalid == true) { + apply_autocmds(EVENT_BUFMODIFIEDSET, NULL, NULL, false, curbuf); + curbuf->b_changed_invalid = false; + } +} + static void normal_check_folds(NormalState *s) { // Include a closed fold completely in the Visual area. @@ -1316,8 +1339,14 @@ static int normal_check(VimState *state) if (skip_redraw || exmode_active) { skip_redraw = false; } else if (do_redraw || stuff_empty()) { + // Need to make sure w_topline and w_leftcol are correct before + // normal_check_window_scrolled() is called. + update_topline(); + normal_check_cursor_moved(s); normal_check_text_changed(s); + normal_check_window_scrolled(s); + normal_check_buffer_modified(s); // Updating diffs from changed() does not always work properly, // esp. updating folds. Do an update just before redrawing if @@ -1977,20 +2006,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 +2512,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 +2619,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; + } } @@ -3599,7 +3630,7 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff) scrolldown(-y, false); } - redraw_later(VALID); + redraw_later(curwin, VALID); cursor_correct(); curwin->w_redr_status = true; } @@ -4105,10 +4136,10 @@ void scroll_redraw(int up, long count) int prev_topfill = curwin->w_topfill; linenr_T prev_lnum = curwin->w_cursor.lnum; - if (up) - scrollup(count, true); - else + bool moved = up ? + scrollup(count, true) : scrolldown(count, true); + if (get_scrolloff_value()) { // Adjust the cursor position for 'scrolloff'. Mark w_topline as // valid, otherwise the screen jumps back at the end of the file. @@ -4138,10 +4169,13 @@ void scroll_redraw(int up, long count) curwin->w_valid |= VALID_TOPLINE; } } - if (curwin->w_cursor.lnum != prev_lnum) + if (curwin->w_cursor.lnum != prev_lnum) { coladvance(curwin->w_curswant); - curwin->w_viewport_invalid = true; - redraw_later(VALID); + } + if (moved) { + curwin->w_viewport_invalid = true; + } + redraw_later(curwin, VALID); } /* @@ -4239,7 +4273,7 @@ dozet: FALLTHROUGH; case 't': scroll_cursor_top(0, true); - redraw_later(VALID); + redraw_later(curwin, VALID); set_fraction(curwin); break; @@ -4248,7 +4282,7 @@ dozet: FALLTHROUGH; case 'z': scroll_cursor_halfway(true); - redraw_later(VALID); + redraw_later(curwin, VALID); set_fraction(curwin); break; @@ -4269,7 +4303,7 @@ dozet: FALLTHROUGH; case 'b': scroll_cursor_bot(0, true); - redraw_later(VALID); + redraw_later(curwin, VALID); set_fraction(curwin); break; @@ -4316,7 +4350,7 @@ dozet: col = 0; if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } } break; @@ -4335,7 +4369,7 @@ dozet: } if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } } break; @@ -4393,51 +4427,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; @@ -4684,7 +4722,7 @@ static void nv_clear(cmdarg_T *cap) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { wp->w_s->b_syn_slow = false; } - redraw_later(CLEAR); + redraw_later(curwin, CLEAR); } } @@ -6835,7 +6873,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..9e7d81fc82 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(); @@ -2641,7 +2652,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) xfree(reg->y_array); } if (curwin->w_p_rnu) { - redraw_later(SOME_VALID); // cursor moved to start + redraw_later(curwin, SOME_VALID); // cursor moved to start } if (message) { // Display message about yank? if (yank_type == kMTCharWise && yanklines == 1) { @@ -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 d789ad3587..fcc051ef1a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -80,6 +80,7 @@ #ifdef WIN32 # include "nvim/os/pty_conpty_win.h" #endif +#include "nvim/lua/executor.h" #include "nvim/api/private/helpers.h" #include "nvim/os/input.h" #include "nvim/os/lang.h" @@ -173,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; @@ -310,11 +312,14 @@ 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", "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", - "yes:9", NULL }; + "yes:9", "number", NULL }; static char *(p_fdc_values[]) = { "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL }; @@ -346,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) { @@ -372,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 @@ -384,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. @@ -403,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) { @@ -416,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)) { @@ -580,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; } @@ -591,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. @@ -1354,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; } @@ -1375,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; } @@ -1711,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] != '\\'))) @@ -2281,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); @@ -2557,7 +2566,7 @@ static bool valid_spellfile(const char_u *val) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { for (const char_u *s = val; *s != NUL; s++) { - if (!vim_isfilec(*s) && *s != ',') { + if (!vim_isfilec(*s) && *s != ',' && *s != ' ') { return false; } } @@ -3086,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; @@ -3108,7 +3121,7 @@ ambw_end: } else { if (curwin->w_status_height) { curwin->w_redr_status = true; - redraw_later(VALID); + redraw_later(curwin, VALID); } curbuf->b_help = (curbuf->b_p_bt[0] == 'h'); redraw_titles(); @@ -3178,11 +3191,25 @@ 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) { errmsg = e_invarg; } + // When changing the 'signcolumn' to or from 'number', recompute the + // width of the number column if 'number' or 'relativenumber' is set. + if (((*oldval == 'n' && *(oldval + 1) == 'u') + || (*curwin->w_p_scl == 'n' && *(curwin->w_p_scl + 1) =='u')) + && (curwin->w_p_nu || curwin->w_p_rnu)) { + curwin->w_nrwidth_line_count = 0; + } } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) { // 'foldcolumn' if (check_opt_strings(*varp, p_fdc_values, false) != OK) { @@ -3417,6 +3444,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 @@ -3719,11 +3747,10 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) /// Return error message or NULL. char_u *check_stl_option(char_u *s) { - int itemcnt = 0; int groupdepth = 0; static char_u errbuf[80]; - while (*s && itemcnt < STL_MAX_ITEM) { + while (*s) { // Check for valid keys after % sequences while (*s && *s != '%') { s++; @@ -3732,9 +3759,6 @@ char_u *check_stl_option(char_u *s) break; } s++; - if (*s != '%' && *s != ')') { - itemcnt++; - } if (*s == '%' || *s == STL_TRUNCMARK || *s == STL_SEPARATE) { s++; continue; @@ -3776,9 +3800,6 @@ char_u *check_stl_option(char_u *s) } } } - if (itemcnt >= STL_MAX_ITEM) { - return (char_u *)N_("E541: too many items"); - } if (groupdepth != 0) { return (char_u *)N_("E542: unbalanced groups"); } @@ -4670,7 +4691,7 @@ static void check_redraw(uint32_t flags) redraw_curbuf_later(NOT_VALID); } if (flags & P_RWINONLY) { - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } if (doclear) { redraw_all_later(CLEAR); @@ -5684,12 +5705,12 @@ void unset_global_local_option(char *name, void *from) case PV_LCS: clear_string_option(&((win_T *)from)->w_p_lcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true); - redraw_win_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, NOT_VALID); break; case PV_FCS: clear_string_option(&((win_T *)from)->w_p_fcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); - redraw_win_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, NOT_VALID); break; } } @@ -5844,6 +5865,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); @@ -5881,6 +5905,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); @@ -6130,6 +6155,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); @@ -6160,6 +6188,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; @@ -6779,7 +6808,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; @@ -7246,7 +7276,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; @@ -7397,7 +7428,10 @@ int win_signcol_count(win_T *wp) int maximum = 1, needed_signcols; const char *scl = (const char *)wp->w_p_scl; - if (*scl == 'n') { + // Note: It checks "no" or "number" in 'signcolumn' option + if (*scl == 'n' + && (*(scl + 1) == 'o' || (*(scl + 1) == 'u' + && (wp->w_p_nu || wp->w_p_rnu)))) { return 0; } needed_signcols = buf_signcols(wp->w_buffer); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index ecaa941082..af0ea7f4a2 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 @@ -187,6 +188,7 @@ enum { #define GO_ASELML 'A' // autoselect modeless selection #define GO_BOT 'b' // use bottom scrollbar #define GO_CONDIALOG 'c' // use console dialog +#define GO_DARKTHEME 'd' // use dark theme variant #define GO_TABLINE 'e' // may show tabline #define GO_FORG 'f' // start GUI in foreground #define GO_GREY 'g' // use grey menu items @@ -204,7 +206,7 @@ enum { #define GO_FOOTER 'F' // add footer #define GO_VERTICAL 'v' // arrange dialog buttons vertically #define GO_KEEPWINSIZE 'k' // keep GUI window size -#define GO_ALL "aAbcefFghilmMprTvk" // all possible flags for 'go' +#define GO_ALL "aAbcdefFghilmMprTvk" // all possible flags for 'go' // flags for 'comments' option #define COM_NEST 'n' // comments strings nest @@ -372,6 +374,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 +458,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 +517,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 +748,7 @@ enum { , BV_CPT , BV_DICT , BV_TSR + , BV_CSL , BV_CFU , BV_DEF , BV_INC @@ -787,6 +793,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 e7c1a3fe88..64a09dc2b4 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1,6 +1,7 @@ -- { -- { -- full_name='aleph', abbreviation='al', +-- short_desc="ASCII code of the letter Aleph (Hebrew)", -- varname='p_aleph', pv_name=nil, -- type='number', list=nil, scope={'global'}, -- deny_duplicates=nil, @@ -52,6 +53,7 @@ return { options={ { full_name='aleph', abbreviation='al', + short_desc=N_("ASCII code of the letter Aleph (Hebrew)"), type='number', scope={'global'}, vi_def=true, redraw={'curswant'}, @@ -60,6 +62,7 @@ return { }, { full_name='arabic', abbreviation='arab', + short_desc=N_("Arabic as a default second language"), type='bool', scope={'window'}, vi_def=true, vim=true, @@ -68,6 +71,7 @@ return { }, { full_name='arabicshape', abbreviation='arshape', + short_desc=N_("do shaping for Arabic characters"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -78,6 +82,7 @@ return { }, { full_name='allowrevins', abbreviation='ari', + short_desc=N_("allow CTRL-_ in Insert and Command-line mode"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -86,6 +91,7 @@ return { }, { full_name='ambiwidth', abbreviation='ambw', + short_desc=N_("what to do with Unicode chars of ambiguous width"), type='string', scope={'global'}, vi_def=true, redraw={'all_windows', 'ui_option'}, @@ -94,6 +100,7 @@ return { }, { full_name='autochdir', abbreviation='acd', + short_desc=N_("change directory to the file in the current window"), type='bool', scope={'global'}, vi_def=true, varname='p_acd', @@ -101,18 +108,21 @@ return { }, { full_name='autoindent', abbreviation='ai', + short_desc=N_("take indent for new line from previous line"), type='bool', scope={'buffer'}, varname='p_ai', defaults={if_true={vi=false, vim=true}} }, { full_name='autoread', abbreviation='ar', + short_desc=N_("autom. read file when changed outside of Vim"), type='bool', scope={'global', 'buffer'}, varname='p_ar', defaults={if_true={vi=false, vim=true}} }, { full_name='autowrite', abbreviation='aw', + short_desc=N_("automatically write file if changed"), type='bool', scope={'global'}, vi_def=true, varname='p_aw', @@ -120,6 +130,7 @@ return { }, { full_name='autowriteall', abbreviation='awa', + short_desc=N_("as 'autowrite', but works with more commands"), type='bool', scope={'global'}, vi_def=true, varname='p_awa', @@ -127,6 +138,7 @@ return { }, { full_name='background', abbreviation='bg', + short_desc=N_("\"dark\" or \"light\", used for highlight colors"), type='string', scope={'global'}, vim=true, redraw={'all_windows'}, @@ -135,6 +147,7 @@ return { }, { full_name='backspace', abbreviation='bs', + short_desc=N_("how backspace works at start of line"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -143,6 +156,7 @@ return { }, { full_name='backup', abbreviation='bk', + short_desc=N_("keep backup file after overwriting a file"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -151,6 +165,7 @@ return { }, { full_name='backupcopy', abbreviation='bkc', + short_desc=N_("make backup as a copy, don't rename the file"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, vim=true, @@ -163,6 +178,7 @@ return { }, { full_name='backupdir', abbreviation='bdir', + short_desc=N_("list of directories for the backup file"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -173,6 +189,7 @@ return { }, { full_name='backupext', abbreviation='bex', + short_desc=N_("extension used for the backup file"), type='string', scope={'global'}, normal_fname_chars=true, vi_def=true, @@ -181,6 +198,7 @@ return { }, { full_name='backupskip', abbreviation='bsk', + short_desc=N_("no backup for files that match these patterns"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -189,6 +207,7 @@ return { }, { full_name='belloff', abbreviation='bo', + short_desc=N_("do not ring the bell for these reasons"), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -197,6 +216,7 @@ return { }, { full_name='binary', abbreviation='bin', + short_desc=N_("read/write/edit file in binary mode"), type='bool', scope={'buffer'}, vi_def=true, redraw={'statuslines'}, @@ -205,6 +225,7 @@ return { }, { full_name='bomb', + short_desc=N_("a Byte Order Mark to the file"), type='bool', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -214,6 +235,7 @@ return { }, { full_name='breakat', abbreviation='brk', + short_desc=N_("characters that may cause a line break"), type='string', list='flags', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -222,6 +244,7 @@ return { }, { full_name='breakindent', abbreviation='bri', + short_desc=N_("wrapped line repeats indent"), type='bool', scope={'window'}, vi_def=true, vim=true, @@ -230,6 +253,7 @@ return { }, { full_name='breakindentopt', abbreviation='briopt', + short_desc=N_("settings for 'breakindent'"), type='string', list='onecomma', scope={'window'}, deny_duplicates=true, vi_def=true, @@ -239,12 +263,14 @@ return { }, { full_name='browsedir', abbreviation='bsdir', + short_desc=N_("which directory to start browsing in"), type='string', scope={'global'}, vi_def=true, enable_if=false, }, { full_name='bufhidden', abbreviation='bh', + short_desc=N_("what to do when buffer is no longer in window"), type='string', scope={'buffer'}, noglob=true, vi_def=true, @@ -254,6 +280,7 @@ return { }, { full_name='buflisted', abbreviation='bl', + short_desc=N_("whether the buffer shows up in the buffer list"), type='bool', scope={'buffer'}, noglob=true, vi_def=true, @@ -262,6 +289,7 @@ return { }, { full_name='buftype', abbreviation='bt', + short_desc=N_("special type of buffer"), type='string', scope={'buffer'}, noglob=true, vi_def=true, @@ -271,6 +299,7 @@ return { }, { full_name='casemap', abbreviation='cmp', + short_desc=N_("specifies how case of letters is changed"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -279,6 +308,7 @@ return { }, { full_name='cdpath', abbreviation='cd', + short_desc=N_("list of directories searched with \":cd\""), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -289,12 +319,14 @@ return { }, { full_name='cedit', + short_desc=N_("used to open the command-line window"), type='string', scope={'global'}, varname='p_cedit', defaults={if_true={vi="", vim=macros('CTRL_F_STR')}} }, { full_name='channel', + short_desc=N_("Channel connected to the buffer"), type='number', scope={'buffer'}, no_mkrc=true, nodefault=true, @@ -303,6 +335,7 @@ return { }, { full_name='charconvert', abbreviation='ccv', + short_desc=N_("expression for character encoding conversion"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -311,6 +344,7 @@ return { }, { full_name='cindent', abbreviation='cin', + short_desc=N_("do C program indenting"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -319,6 +353,7 @@ return { }, { full_name='cinkeys', abbreviation='cink', + short_desc=N_("keys that trigger indent when 'cindent' is set"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -328,6 +363,7 @@ return { }, { full_name='cinoptions', abbreviation='cino', + short_desc=N_("how to do indenting when 'cindent' is set"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -337,6 +373,7 @@ return { }, { full_name='cinwords', abbreviation='cinw', + short_desc=N_("words where 'si' and 'cin' add an indent"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -346,6 +383,7 @@ return { }, { full_name='clipboard', abbreviation='cb', + short_desc=N_("use the clipboard as the unnamed register"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -354,6 +392,7 @@ return { }, { full_name='cmdheight', abbreviation='ch', + short_desc=N_("number of lines to use for the command-line"), type='number', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -362,6 +401,7 @@ return { }, { full_name='cmdwinheight', abbreviation='cwh', + short_desc=N_("height of the command-line window"), type='number', scope={'global'}, vi_def=true, varname='p_cwh', @@ -369,6 +409,7 @@ return { }, { full_name='colorcolumn', abbreviation='cc', + short_desc=N_("columns to highlight"), type='string', list='onecomma', scope={'window'}, deny_duplicates=true, vi_def=true, @@ -377,6 +418,7 @@ return { }, { full_name='columns', abbreviation='co', + short_desc=N_("number of columns in the display"), type='number', scope={'global'}, no_mkrc=true, vi_def=true, @@ -386,6 +428,7 @@ return { }, { full_name='comments', abbreviation='com', + short_desc=N_("patterns that can start a comment line"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -396,6 +439,7 @@ return { }, { full_name='commentstring', abbreviation='cms', + short_desc=N_("template for comments; used for fold marker"), type='string', scope={'buffer'}, vi_def=true, alloced=true, @@ -405,6 +449,7 @@ return { }, { full_name='compatible', abbreviation='cp', + short_desc=N_("No description"), type='bool', scope={'global'}, redraw={'all_windows'}, varname='p_force_off', @@ -414,6 +459,7 @@ return { }, { full_name='complete', abbreviation='cpt', + short_desc=N_("specify how Insert mode completion works"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, alloced=true, @@ -422,6 +468,7 @@ return { }, { full_name='concealcursor', abbreviation='cocu', + short_desc=N_("whether concealable text is hidden in cursor line"), type='string', scope={'window'}, vi_def=true, alloced=true, @@ -430,6 +477,7 @@ return { }, { full_name='conceallevel', abbreviation='cole', + short_desc=N_("whether concealable text is shown or hidden"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -437,6 +485,7 @@ return { }, { full_name='completefunc', abbreviation='cfu', + short_desc=N_("function to be used for Insert mode completion"), type='string', scope={'buffer'}, secure=true, vi_def=true, @@ -446,6 +495,7 @@ return { }, { full_name='completeopt', abbreviation='cot', + short_desc=N_("options for Insert mode completion"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -453,7 +503,17 @@ 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', + short_desc=N_("ask what to do about unsaved/read-only files"), type='bool', scope={'global'}, vi_def=true, varname='p_confirm', @@ -461,6 +521,7 @@ return { }, { full_name='copyindent', abbreviation='ci', + short_desc=N_("make 'autoindent' use existing indent structure"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -469,6 +530,7 @@ return { }, { full_name='cpoptions', abbreviation='cpo', + short_desc=N_("flags for Vi-compatible behavior"), type='string', list='flags', scope={'global'}, vim=true, redraw={'all_windows'}, @@ -477,6 +539,7 @@ return { }, { full_name='cscopepathcomp', abbreviation='cspc', + short_desc=N_("how many components of the path to show"), type='number', scope={'global'}, vi_def=true, vim=true, @@ -485,6 +548,7 @@ return { }, { full_name='cscopeprg', abbreviation='csprg', + short_desc=N_("command to execute cscope"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -494,6 +558,7 @@ return { }, { full_name='cscopequickfix', abbreviation='csqf', + short_desc=N_("use quickfix window for cscope results"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -502,6 +567,7 @@ return { }, { full_name='cscoperelative', abbreviation='csre', + short_desc=N_("Use cscope.out path basename as prefix"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -510,6 +576,7 @@ return { }, { full_name='cscopetag', abbreviation='cst', + short_desc=N_("use cscope for tag commands"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -518,6 +585,7 @@ return { }, { full_name='cscopetagorder', abbreviation='csto', + short_desc=N_("determines \":cstag\" search order"), type='number', scope={'global'}, vi_def=true, vim=true, @@ -526,6 +594,7 @@ return { }, { full_name='cscopeverbose', abbreviation='csverb', + short_desc=N_("give messages when adding a cscope database"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -534,6 +603,7 @@ return { }, { full_name='cursorbind', abbreviation='crb', + short_desc=N_("move cursor in window as it moves in other windows"), type='bool', scope={'window'}, vi_def=true, pv_name='p_crbind', @@ -541,6 +611,7 @@ return { }, { full_name='cursorcolumn', abbreviation='cuc', + short_desc=N_("highlight the screen column of the cursor"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window_only'}, @@ -548,6 +619,7 @@ return { }, { full_name='cursorline', abbreviation='cul', + short_desc=N_("highlight the screen line of the cursor"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window_only'}, @@ -555,6 +627,7 @@ return { }, { full_name='debug', + short_desc=N_("to \"msg\" to see all error messages"), type='string', scope={'global'}, vi_def=true, varname='p_debug', @@ -562,6 +635,7 @@ return { }, { full_name='define', abbreviation='def', + short_desc=N_("pattern to be used to find a macro definition"), type='string', scope={'global', 'buffer'}, vi_def=true, alloced=true, @@ -571,6 +645,7 @@ return { }, { full_name='delcombine', abbreviation='deco', + short_desc=N_("delete combining characters on their own"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -579,6 +654,7 @@ return { }, { full_name='dictionary', abbreviation='dict', + short_desc=N_("list of file names used for keyword completion"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, normal_dname_chars=true, @@ -589,6 +665,7 @@ return { }, { full_name='diff', + short_desc=N_("diff mode for the current window"), type='bool', scope={'window'}, noglob=true, vi_def=true, @@ -597,6 +674,7 @@ return { }, { full_name='diffexpr', abbreviation='dex', + short_desc=N_("expression used to obtain a diff file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -606,6 +684,7 @@ return { }, { full_name='diffopt', abbreviation='dip', + short_desc=N_("options for using diff mode"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -616,6 +695,7 @@ return { }, { full_name='digraph', abbreviation='dg', + short_desc=N_("enable the entering of digraphs in Insert mode"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -624,6 +704,7 @@ return { }, { full_name='directory', abbreviation='dir', + short_desc=N_("list of directory names for the swap file"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -634,6 +715,7 @@ return { }, { full_name='display', abbreviation='dy', + short_desc=N_("list of flags for how to display text"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -643,6 +725,7 @@ return { }, { full_name='eadirection', abbreviation='ead', + short_desc=N_("in which direction 'equalalways' works"), type='string', scope={'global'}, vi_def=true, varname='p_ead', @@ -650,6 +733,7 @@ return { }, { full_name='edcompatible', abbreviation='ed', + short_desc=N_("No description"), type='bool', scope={'global'}, vi_def=true, varname='p_force_off', @@ -657,6 +741,7 @@ return { }, { full_name='emoji', abbreviation='emo', + short_desc=N_("No description"), type='bool', scope={'global'}, vi_def=true, redraw={'all_windows', 'ui_option'}, @@ -665,6 +750,7 @@ return { }, { full_name='encoding', abbreviation='enc', + short_desc=N_("encoding used internally"), type='string', scope={'global'}, deny_in_modelines=true, vi_def=true, @@ -673,6 +759,7 @@ return { }, { full_name='endofline', abbreviation='eol', + short_desc=N_("write <EOL> for last line in file"), type='bool', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -682,6 +769,7 @@ return { }, { full_name='equalalways', abbreviation='ea', + short_desc=N_("windows are automatically made the same size"), type='bool', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -690,6 +778,7 @@ return { }, { full_name='equalprg', abbreviation='ep', + short_desc=N_("external program to use for \"=\" command"), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -699,6 +788,7 @@ return { }, { full_name='errorbells', abbreviation='eb', + short_desc=N_("ring the bell for error messages"), type='bool', scope={'global'}, vi_def=true, varname='p_eb', @@ -706,6 +796,7 @@ return { }, { full_name='errorfile', abbreviation='ef', + short_desc=N_("name of the errorfile for the QuickFix mode"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -715,6 +806,7 @@ return { }, { full_name='errorformat', abbreviation='efm', + short_desc=N_("description of the lines in the error file"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, vi_def=true, @@ -723,6 +815,7 @@ return { }, { full_name='eventignore', abbreviation='ei', + short_desc=N_("autocommand events that are ignored"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -731,6 +824,7 @@ return { }, { full_name='expandtab', abbreviation='et', + short_desc=N_("use spaces when <Tab> is inserted"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -739,6 +833,7 @@ return { }, { full_name='exrc', abbreviation='ex', + short_desc=N_("read .nvimrc and .exrc in the current directory"), type='bool', scope={'global'}, secure=true, vi_def=true, @@ -747,6 +842,7 @@ return { }, { full_name='fileencoding', abbreviation='fenc', + short_desc=N_("file encoding for multi-byte text"), type='string', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -757,6 +853,7 @@ return { }, { full_name='fileencodings', abbreviation='fencs', + short_desc=N_("automatically detected character encodings"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -765,6 +862,7 @@ return { }, { full_name='fileformat', abbreviation='ff', + short_desc=N_("file format used for file I/O"), type='string', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -775,6 +873,7 @@ return { }, { full_name='fileformats', abbreviation='ffs', + short_desc=N_("automatically detected values for 'fileformat'"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -783,6 +882,7 @@ return { }, { full_name='fileignorecase', abbreviation='fic', + short_desc=N_("ignore case when using file names"), type='bool', scope={'global'}, vi_def=true, varname='p_fic', @@ -794,6 +894,7 @@ return { }, { full_name='filetype', abbreviation='ft', + short_desc=N_("type of file, used for autocommands"), type='string', scope={'buffer'}, noglob=true, normal_fname_chars=true, @@ -804,6 +905,7 @@ return { }, { full_name='fillchars', abbreviation='fcs', + short_desc=N_("characters to use for displaying special items"), type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, vi_def=true, @@ -814,6 +916,7 @@ return { }, { full_name='fixendofline', abbreviation='fixeol', + short_desc=N_("make sure last line in file has <EOL>"), type='bool', scope={'buffer'}, vi_def=true, redraw={'statuslines'}, @@ -822,6 +925,7 @@ return { }, { full_name='foldclose', abbreviation='fcl', + short_desc=N_("close a fold when the cursor leaves it"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -831,6 +935,7 @@ return { }, { full_name='foldcolumn', abbreviation='fdc', + short_desc=N_("width of the column used to indicate folds"), type='string', scope={'window'}, vi_def=true, alloced=true, @@ -839,6 +944,7 @@ return { }, { full_name='foldenable', abbreviation='fen', + short_desc=N_("set to display all folds open"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -846,6 +952,7 @@ return { }, { full_name='foldexpr', abbreviation='fde', + short_desc=N_("expression used when 'foldmethod' is \"expr\""), type='string', scope={'window'}, vi_def=true, vim=true, @@ -856,6 +963,7 @@ return { }, { full_name='foldignore', abbreviation='fdi', + short_desc=N_("ignore lines when 'foldmethod' is \"indent\""), type='string', scope={'window'}, vi_def=true, vim=true, @@ -865,6 +973,7 @@ return { }, { full_name='foldlevel', abbreviation='fdl', + short_desc=N_("close folds with a level higher than this"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -872,6 +981,7 @@ return { }, { full_name='foldlevelstart', abbreviation='fdls', + short_desc=N_("'foldlevel' when starting to edit a file"), type='number', scope={'global'}, vi_def=true, redraw={'curswant'}, @@ -880,6 +990,7 @@ return { }, { full_name='foldmarker', abbreviation='fmr', + short_desc=N_("markers used when 'foldmethod' is \"marker\""), type='string', list='onecomma', scope={'window'}, deny_duplicates=true, vi_def=true, @@ -890,6 +1001,7 @@ return { }, { full_name='foldmethod', abbreviation='fdm', + short_desc=N_("folding type"), type='string', scope={'window'}, vi_def=true, vim=true, @@ -899,6 +1011,7 @@ return { }, { full_name='foldminlines', abbreviation='fml', + short_desc=N_("minimum number of lines for a fold to be closed"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -906,6 +1019,7 @@ return { }, { full_name='foldnestmax', abbreviation='fdn', + short_desc=N_("maximum fold depth"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -913,6 +1027,7 @@ return { }, { full_name='foldopen', abbreviation='fdo', + short_desc=N_("for which commands a fold will be opened"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -922,6 +1037,7 @@ return { }, { full_name='foldtext', abbreviation='fdt', + short_desc=N_("expression used to display for a closed fold"), type='string', scope={'window'}, vi_def=true, vim=true, @@ -932,6 +1048,7 @@ return { }, { full_name='formatexpr', abbreviation='fex', + short_desc=N_("expression used with \"gq\" command"), type='string', scope={'buffer'}, vi_def=true, vim=true, @@ -942,6 +1059,7 @@ return { }, { full_name='formatoptions', abbreviation='fo', + short_desc=N_("how automatic formatting is to be done"), type='string', list='flags', scope={'buffer'}, vim=true, alloced=true, @@ -950,6 +1068,7 @@ return { }, { full_name='formatlistpat', abbreviation='flp', + short_desc=N_("pattern used to recognize a list header"), type='string', scope={'buffer'}, vi_def=true, alloced=true, @@ -958,6 +1077,7 @@ return { }, { full_name='formatprg', abbreviation='fp', + short_desc=N_("name of external program used with \"gq\" command"), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -967,6 +1087,7 @@ return { }, { full_name='fsync', abbreviation='fs', + short_desc=N_("whether to invoke fsync() after file write"), type='bool', scope={'global'}, secure=true, vi_def=true, @@ -975,6 +1096,7 @@ return { }, { full_name='gdefault', abbreviation='gd', + short_desc=N_("the \":substitute\" flag 'g' is default on"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -983,6 +1105,7 @@ return { }, { full_name='grepformat', abbreviation='gfm', + short_desc=N_("format of 'grepprg' output"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -991,6 +1114,7 @@ return { }, { full_name='grepprg', abbreviation='gp', + short_desc=N_("program to use for \":grep\""), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -1006,6 +1130,7 @@ return { }, { full_name='guicursor', abbreviation='gcr', + short_desc=N_("GUI: settings for cursor shape and blinking"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1014,6 +1139,7 @@ return { }, { full_name='guifont', abbreviation='gfn', + short_desc=N_("GUI: Name(s) of font(s) to be used"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1022,16 +1148,8 @@ 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', + short_desc=N_("list of font names for double-wide characters"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1041,6 +1159,7 @@ return { }, { full_name='guioptions', abbreviation='go', + short_desc=N_("GUI: Which components and options are used"), type='string', list='flags', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -1048,6 +1167,7 @@ return { }, { full_name='guitablabel', abbreviation='gtl', + short_desc=N_("GUI: custom label for a tab page"), type='string', scope={'global'}, vi_def=true, modelineexpr=true, @@ -1056,6 +1176,7 @@ return { }, { full_name='guitabtooltip', abbreviation='gtt', + short_desc=N_("GUI: custom tooltip for a tab page"), type='string', scope={'global'}, vi_def=true, redraw={'current_window'}, @@ -1063,6 +1184,7 @@ return { }, { full_name='helpfile', abbreviation='hf', + short_desc=N_("full path name of the main help file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1072,6 +1194,7 @@ return { }, { full_name='helpheight', abbreviation='hh', + short_desc=N_("minimum height of a new help window"), type='number', scope={'global'}, vi_def=true, varname='p_hh', @@ -1079,6 +1202,7 @@ return { }, { full_name='helplang', abbreviation='hlg', + short_desc=N_("preferred help languages"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1087,6 +1211,7 @@ return { }, { full_name='hidden', abbreviation='hid', + short_desc=N_("don't unload buffer when it is |abandon|ed"), type='bool', scope={'global'}, vi_def=true, varname='p_hid', @@ -1094,6 +1219,7 @@ return { }, { full_name='highlight', abbreviation='hl', + short_desc=N_("sets highlighting mode for various occasions"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1102,6 +1228,7 @@ return { }, { full_name='history', abbreviation='hi', + short_desc=N_("number of command-lines that are remembered"), type='number', scope={'global'}, vim=true, varname='p_hi', @@ -1109,6 +1236,7 @@ return { }, { full_name='hkmap', abbreviation='hk', + short_desc=N_("Hebrew keyboard mapping"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1117,6 +1245,7 @@ return { }, { full_name='hkmapp', abbreviation='hkp', + short_desc=N_("phonetic Hebrew keyboard mapping"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1125,6 +1254,7 @@ return { }, { full_name='hlsearch', abbreviation='hls', + short_desc=N_("highlight matches with last search pattern"), type='bool', scope={'global'}, vim=true, redraw={'all_windows'}, @@ -1133,6 +1263,7 @@ return { }, { full_name='icon', + short_desc=N_("Vim set the text of the window icon"), type='bool', scope={'global'}, vi_def=true, varname='p_icon', @@ -1140,6 +1271,7 @@ return { }, { full_name='iconstring', + short_desc=N_("to use for the Vim icon text"), type='string', scope={'global'}, vi_def=true, modelineexpr=true, @@ -1148,6 +1280,7 @@ return { }, { full_name='ignorecase', abbreviation='ic', + short_desc=N_("ignore case in search patterns"), type='bool', scope={'global'}, vi_def=true, varname='p_ic', @@ -1155,6 +1288,7 @@ return { }, { full_name='imcmdline', abbreviation='imc', + short_desc=N_("use IM when starting to edit a command line"), type='bool', scope={'global'}, vi_def=true, enable_if=false, @@ -1162,6 +1296,7 @@ return { }, { full_name='imdisable', abbreviation='imd', + short_desc=N_("do not use the IM in any mode"), type='bool', scope={'global'}, vi_def=true, enable_if=false, @@ -1169,6 +1304,7 @@ return { }, { full_name='iminsert', abbreviation='imi', + short_desc=N_("use :lmap or IM in Insert mode"), type='number', scope={'buffer'}, vi_def=true, varname='p_iminsert', pv_name='p_imi', @@ -1178,6 +1314,7 @@ return { }, { full_name='imsearch', abbreviation='ims', + short_desc=N_("use :lmap or IM when typing a search pattern"), type='number', scope={'buffer'}, vi_def=true, varname='p_imsearch', pv_name='p_ims', @@ -1187,6 +1324,7 @@ return { }, { full_name='inccommand', abbreviation='icm', + short_desc=N_("Live preview of substitution"), type='string', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -1195,6 +1333,7 @@ return { }, { full_name='include', abbreviation='inc', + short_desc=N_("pattern to be used to find an include file"), type='string', scope={'global', 'buffer'}, vi_def=true, alloced=true, @@ -1203,6 +1342,7 @@ return { }, { full_name='includeexpr', abbreviation='inex', + short_desc=N_("expression used to process an include line"), type='string', scope={'buffer'}, vi_def=true, modelineexpr=true, @@ -1212,6 +1352,7 @@ return { }, { full_name='incsearch', abbreviation='is', + short_desc=N_("highlight match while typing search pattern"), type='bool', scope={'global'}, vim=true, varname='p_is', @@ -1219,6 +1360,7 @@ return { }, { full_name='indentexpr', abbreviation='inde', + short_desc=N_("expression used to obtain the indent of a line"), type='string', scope={'buffer'}, vi_def=true, vim=true, @@ -1229,6 +1371,7 @@ return { }, { full_name='indentkeys', abbreviation='indk', + short_desc=N_("keys that trigger indenting with 'indentexpr'"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -1238,6 +1381,7 @@ return { }, { full_name='infercase', abbreviation='inf', + short_desc=N_("adjust case of match for keyword completion"), type='bool', scope={'buffer'}, vi_def=true, varname='p_inf', @@ -1245,6 +1389,7 @@ return { }, { full_name='insertmode', abbreviation='im', + short_desc=N_("start the edit of a file in Insert mode"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1253,6 +1398,7 @@ return { }, { full_name='isfname', abbreviation='isf', + short_desc=N_("characters included in file names and pathnames"), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1267,14 +1413,20 @@ return { }, { full_name='isident', abbreviation='isi', + short_desc=N_("characters included in identifiers"), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, varname='p_isi', - defaults={if_true={vi="@,48-57,_,192-255"}} + defaults={ + condition='WIN32', + if_true={vi="@,48-57,_,128-167,224-235"}, + if_false={vi="@,48-57,_,192-255"} + } }, { full_name='iskeyword', abbreviation='isk', + short_desc=N_("characters included in keywords"), type='string', list='comma', scope={'buffer'}, deny_duplicates=true, vim=true, @@ -1284,6 +1436,7 @@ return { }, { full_name='isprint', abbreviation='isp', + short_desc=N_("printable characters"), type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1294,6 +1447,7 @@ return { }, { full_name='joinspaces', abbreviation='js', + short_desc=N_("two spaces after a period with a join command"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1302,6 +1456,7 @@ return { }, { full_name='jumpoptions', abbreviation='jop', + short_desc=N_("Controls the behavior of the jumplist"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, varname='p_jop', @@ -1310,6 +1465,7 @@ return { }, { full_name='keymap', abbreviation='kmp', + short_desc=N_("name of a keyboard mapping"), type='string', scope={'buffer'}, normal_fname_chars=true, pri_mkrc=true, @@ -1321,6 +1477,7 @@ return { }, { full_name='keymodel', abbreviation='km', + short_desc=N_("enable starting/stopping selection with keys"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1329,6 +1486,7 @@ return { }, { full_name='keywordprg', abbreviation='kp', + short_desc=N_("program to use for the \"K\" command"), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -1340,6 +1498,7 @@ return { }, { full_name='langmap', abbreviation='lmap', + short_desc=N_("alphabetic characters for other language mode"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -1349,6 +1508,7 @@ return { }, { full_name='langmenu', abbreviation='lm', + short_desc=N_("language to be used for the menus"), type='string', scope={'global'}, normal_fname_chars=true, vi_def=true, @@ -1357,18 +1517,21 @@ return { }, { full_name='langnoremap', abbreviation='lnr', + short_desc=N_("do not apply 'langmap' to mapped characters"), type='bool', scope={'global'}, varname='p_lnr', defaults={if_true={vi=false, vim=true}} }, { full_name='langremap', abbreviation='lrm', + short_desc=N_('No description'), type='bool', scope={'global'}, varname='p_lrm', defaults={if_true={vi=true, vim=false}} }, { full_name='laststatus', abbreviation='ls', + short_desc=N_("tells when last window has status lines"), type='number', scope={'global'}, vim=true, redraw={'all_windows'}, @@ -1377,6 +1540,7 @@ return { }, { full_name='lazyredraw', abbreviation='lz', + short_desc=N_("don't redraw while executing macros"), type='bool', scope={'global'}, vi_def=true, varname='p_lz', @@ -1384,6 +1548,7 @@ return { }, { full_name='linebreak', abbreviation='lbr', + short_desc=N_("wrap long lines at a blank"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1391,6 +1556,7 @@ return { }, { full_name='lines', + short_desc=N_("of lines in the display"), type='number', scope={'global'}, no_mkrc=true, vi_def=true, @@ -1400,6 +1566,7 @@ return { }, { full_name='linespace', abbreviation='lsp', + short_desc=N_("number of pixel lines to use between characters"), type='number', scope={'global'}, vi_def=true, redraw={'ui_option'}, @@ -1408,6 +1575,7 @@ return { }, { full_name='lisp', + short_desc=N_("indenting for Lisp"), type='bool', scope={'buffer'}, vi_def=true, varname='p_lisp', @@ -1415,6 +1583,7 @@ return { }, { full_name='lispwords', abbreviation='lw', + short_desc=N_("words that change how lisp indenting works"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, vi_def=true, @@ -1423,6 +1592,7 @@ return { }, { full_name='list', + short_desc=N_("<Tab> and <EOL>"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1430,6 +1600,7 @@ return { }, { full_name='listchars', abbreviation='lcs', + short_desc=N_("characters for displaying in list mode"), type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, vim=true, @@ -1440,6 +1611,7 @@ return { }, { full_name='loadplugins', abbreviation='lpl', + short_desc=N_("load plugin scripts when starting up"), type='bool', scope={'global'}, vi_def=true, varname='p_lpl', @@ -1447,6 +1619,7 @@ return { }, { full_name='magic', + short_desc=N_("special characters in search patterns"), type='bool', scope={'global'}, vi_def=true, varname='p_magic', @@ -1454,6 +1627,7 @@ return { }, { full_name='makeef', abbreviation='mef', + short_desc=N_("name of the errorfile for \":make\""), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1463,6 +1637,7 @@ return { }, { full_name='makeencoding', abbreviation='menc', + short_desc=N_("Converts the output of external commands"), type='string', scope={'global', 'buffer'}, vi_def=true, varname='p_menc', @@ -1470,6 +1645,7 @@ return { }, { full_name='makeprg', abbreviation='mp', + short_desc=N_("program to use for the \":make\" command"), type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, @@ -1479,6 +1655,7 @@ return { }, { full_name='matchpairs', abbreviation='mps', + short_desc=N_("pairs of characters that \"%\" can match"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -1488,6 +1665,7 @@ return { }, { full_name='matchtime', abbreviation='mat', + short_desc=N_("tenths of a second to show matching paren"), type='number', scope={'global'}, vi_def=true, varname='p_mat', @@ -1495,6 +1673,7 @@ return { }, { full_name='maxcombine', abbreviation='mco', + short_desc=N_("maximum nr of combining characters displayed"), type='number', scope={'global'}, vi_def=true, varname='p_mco', @@ -1502,6 +1681,7 @@ return { }, { full_name='maxfuncdepth', abbreviation='mfd', + short_desc=N_("maximum recursive depth for user functions"), type='number', scope={'global'}, vi_def=true, varname='p_mfd', @@ -1509,6 +1689,7 @@ return { }, { full_name='maxmapdepth', abbreviation='mmd', + short_desc=N_("maximum recursive depth for mapping"), type='number', scope={'global'}, vi_def=true, varname='p_mmd', @@ -1516,6 +1697,7 @@ return { }, { full_name='maxmempattern', abbreviation='mmp', + short_desc=N_("maximum memory (in Kbyte) used for pattern search"), type='number', scope={'global'}, vi_def=true, varname='p_mmp', @@ -1523,6 +1705,7 @@ return { }, { full_name='menuitems', abbreviation='mis', + short_desc=N_("maximum number of items in a menu"), type='number', scope={'global'}, vi_def=true, varname='p_mis', @@ -1530,6 +1713,7 @@ return { }, { full_name='mkspellmem', abbreviation='msm', + short_desc=N_("memory used before |:mkspell| compresses the tree"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1539,6 +1723,7 @@ return { }, { full_name='modeline', abbreviation='ml', + short_desc=N_("recognize modelines at start or end of file"), type='bool', scope={'buffer'}, vim=true, varname='p_ml', @@ -1546,6 +1731,7 @@ return { }, { full_name='modelineexpr', abbreviation='mle', + short_desc=N_("allow some options to be set in modeline"), type='bool', scope={'global'}, vi_def=true, secure=true, @@ -1554,6 +1740,7 @@ return { }, { full_name='modelines', abbreviation='mls', + short_desc=N_("number of lines checked for modelines"), type='number', scope={'global'}, vi_def=true, varname='p_mls', @@ -1561,6 +1748,7 @@ return { }, { full_name='modifiable', abbreviation='ma', + short_desc=N_("changes to the text are not possible"), type='bool', scope={'buffer'}, noglob=true, vi_def=true, @@ -1569,6 +1757,7 @@ return { }, { full_name='modified', abbreviation='mod', + short_desc=N_("buffer has been modified"), type='bool', scope={'buffer'}, no_mkrc=true, vi_def=true, @@ -1578,6 +1767,7 @@ return { }, { full_name='more', + short_desc=N_("listings when the whole screen is filled"), type='bool', scope={'global'}, vim=true, varname='p_more', @@ -1585,19 +1775,23 @@ return { }, { full_name='mouse', + short_desc=N_("the use of mouse clicks"), type='string', list='flags', scope={'global'}, varname='p_mouse', defaults={if_true={vi="", vim=""}} }, { full_name='mousefocus', abbreviation='mousef', + short_desc=N_("keyboard focus follows the mouse"), type='bool', scope={'global'}, vi_def=true, - enable_if=false, + redraw={'ui_option'}, + varname='p_mousef', defaults={if_true={vi=false}} }, { full_name='mousehide', abbreviation='mh', + short_desc=N_("hide mouse pointer while typing"), type='bool', scope={'global'}, vi_def=true, enable_if=false, @@ -1605,6 +1799,7 @@ return { }, { full_name='mousemodel', abbreviation='mousem', + short_desc=N_("changes meaning of mouse buttons"), type='string', scope={'global'}, vi_def=true, varname='p_mousem', @@ -1612,6 +1807,7 @@ return { }, { full_name='mouseshape', abbreviation='mouses', + short_desc=N_("shape of the mouse pointer in different modes"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1619,6 +1815,7 @@ return { }, { full_name='mousetime', abbreviation='mouset', + short_desc=N_("max time between mouse double-click"), type='number', scope={'global'}, vi_def=true, varname='p_mouset', @@ -1626,6 +1823,7 @@ return { }, { full_name='nrformats', abbreviation='nf', + short_desc=N_("number formats recognized for CTRL-A command"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, alloced=true, @@ -1634,6 +1832,7 @@ return { }, { full_name='number', abbreviation='nu', + short_desc=N_("print the line number in front of each line"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1641,6 +1840,7 @@ return { }, { full_name='numberwidth', abbreviation='nuw', + short_desc=N_("number of columns used for the line number"), type='number', scope={'window'}, vim=true, redraw={'current_window'}, @@ -1648,6 +1848,7 @@ return { }, { full_name='omnifunc', abbreviation='ofu', + short_desc=N_("function for filetype-specific completion"), type='string', scope={'buffer'}, secure=true, vi_def=true, @@ -1657,6 +1858,7 @@ return { }, { full_name='opendevice', abbreviation='odev', + short_desc=N_("allow reading/writing devices on MS-Windows"), type='bool', scope={'global'}, vi_def=true, enable_if=false, @@ -1664,6 +1866,7 @@ return { }, { full_name='operatorfunc', abbreviation='opfunc', + short_desc=N_("function to be called for |g@| operator"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1672,6 +1875,7 @@ return { }, { full_name='packpath', abbreviation='pp', + short_desc=N_("list of directories used for packages"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -1682,6 +1886,7 @@ return { }, { full_name='paragraphs', abbreviation='para', + short_desc=N_("nroff macros that separate paragraphs"), type='string', scope={'global'}, vi_def=true, varname='p_para', @@ -1689,6 +1894,7 @@ return { }, { full_name='paste', + short_desc=N_("pasting text"), type='bool', scope={'global'}, pri_mkrc=true, vi_def=true, @@ -1697,6 +1903,7 @@ return { }, { full_name='pastetoggle', abbreviation='pt', + short_desc=N_("key code that causes 'paste' to toggle"), type='string', scope={'global'}, vi_def=true, varname='p_pt', @@ -1704,6 +1911,7 @@ return { }, { full_name='patchexpr', abbreviation='pex', + short_desc=N_("expression used to patch a file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1712,6 +1920,7 @@ return { }, { full_name='patchmode', abbreviation='pm', + short_desc=N_("keep the oldest version of a file"), type='string', scope={'global'}, normal_fname_chars=true, vi_def=true, @@ -1720,6 +1929,7 @@ return { }, { full_name='path', abbreviation='pa', + short_desc=N_("list of directories searched with \"gf\" et.al."), type='string', list='comma', scope={'global', 'buffer'}, deny_duplicates=true, vi_def=true, @@ -1729,6 +1939,7 @@ return { }, { full_name='preserveindent', abbreviation='pi', + short_desc=N_("preserve the indent structure when reindenting"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -1737,6 +1948,7 @@ return { }, { full_name='previewheight', abbreviation='pvh', + short_desc=N_("height of the preview window"), type='number', scope={'global'}, vi_def=true, varname='p_pvh', @@ -1744,6 +1956,7 @@ return { }, { full_name='previewwindow', abbreviation='pvw', + short_desc=N_("identifies the preview window"), type='bool', scope={'window'}, noglob=true, vi_def=true, @@ -1752,6 +1965,7 @@ return { }, { full_name='printdevice', abbreviation='pdev', + short_desc=N_("name of the printer to be used for :hardcopy"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1760,6 +1974,7 @@ return { }, { full_name='printencoding', abbreviation='penc', + short_desc=N_("encoding to be used for printing"), type='string', scope={'global'}, vi_def=true, varname='p_penc', @@ -1767,6 +1982,7 @@ return { }, { full_name='printexpr', abbreviation='pexpr', + short_desc=N_("expression used to print PostScript for :hardcopy"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -1775,6 +1991,7 @@ return { }, { full_name='printfont', abbreviation='pfn', + short_desc=N_("name of the font to be used for :hardcopy"), type='string', scope={'global'}, vi_def=true, varname='p_pfn', @@ -1782,6 +1999,7 @@ return { }, { full_name='printheader', abbreviation='pheader', + short_desc=N_("format of the header used for :hardcopy"), type='string', scope={'global'}, vi_def=true, varname='p_header', @@ -1789,6 +2007,7 @@ return { }, { full_name='printmbcharset', abbreviation='pmbcs', + short_desc=N_("CJK character set to be used for :hardcopy"), type='string', scope={'global'}, vi_def=true, varname='p_pmcs', @@ -1796,6 +2015,7 @@ return { }, { full_name='printmbfont', abbreviation='pmbfn', + short_desc=N_("font names to be used for CJK output of :hardcopy"), type='string', scope={'global'}, vi_def=true, varname='p_pmfn', @@ -1803,6 +2023,7 @@ return { }, { full_name='printoptions', abbreviation='popt', + short_desc=N_("controls the format of :hardcopy output"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -1811,6 +2032,7 @@ return { }, { full_name='prompt', + short_desc=N_("enable prompt in Ex mode"), type='bool', scope={'global'}, vi_def=true, varname='p_prompt', @@ -1818,6 +2040,7 @@ return { }, { full_name='pumblend', abbreviation='pb', + short_desc=N_("Controls transparency level of popup menu"), type='number', scope={'global'}, vi_def=true, redraw={'ui_option'}, @@ -1826,6 +2049,7 @@ return { }, { full_name='pumheight', abbreviation='ph', + short_desc=N_("maximum height of the popup menu"), type='number', scope={'global'}, vi_def=true, varname='p_ph', @@ -1833,6 +2057,7 @@ return { }, { full_name='pumwidth', abbreviation='pw', + short_desc=N_("minimum width of the popup menu"), type='number', scope={'global'}, vi_def=true, varname='p_pw', @@ -1840,6 +2065,7 @@ return { }, { full_name='pyxversion', abbreviation='pyx', + short_desc=N_("selects default python version to use"), type='number', scope={'global'}, secure=true, vi_def=true, @@ -1848,6 +2074,7 @@ return { }, { full_name='quoteescape', abbreviation='qe', + short_desc=N_("escape characters used in a string"), type='string', scope={'buffer'}, vi_def=true, alloced=true, @@ -1856,6 +2083,7 @@ return { }, { full_name='readonly', abbreviation='ro', + short_desc=N_("disallow writing the buffer"), type='bool', scope={'buffer'}, noglob=true, vi_def=true, @@ -1865,6 +2093,7 @@ return { }, { full_name='redrawdebug', abbreviation='rdb', + short_desc=N_("Changes the way redrawing works (debug)"), type='string', list='onecomma', scope={'global'}, vi_def=true, varname='p_rdb', @@ -1872,6 +2101,7 @@ return { }, { full_name='redrawtime', abbreviation='rdt', + short_desc=N_("timeout for 'hlsearch' and |:match| highlighting"), type='number', scope={'global'}, vi_def=true, varname='p_rdt', @@ -1879,6 +2109,7 @@ return { }, { full_name='regexpengine', abbreviation='re', + short_desc=N_("default regexp engine to use"), type='number', scope={'global'}, vi_def=true, varname='p_re', @@ -1886,6 +2117,7 @@ return { }, { full_name='relativenumber', abbreviation='rnu', + short_desc=N_("show relative line number in front of each line"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1893,6 +2125,7 @@ return { }, { full_name='remap', + short_desc=N_("mappings to work recursively"), type='bool', scope={'global'}, vi_def=true, varname='p_remap', @@ -1900,6 +2133,7 @@ return { }, { full_name='report', + short_desc=N_("for reporting nr. of lines changed"), type='number', scope={'global'}, vi_def=true, varname='p_report', @@ -1907,6 +2141,7 @@ return { }, { full_name='revins', abbreviation='ri', + short_desc=N_("inserting characters will work backwards"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1915,6 +2150,7 @@ return { }, { full_name='rightleft', abbreviation='rl', + short_desc=N_("window is right-to-left oriented"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -1922,6 +2158,7 @@ return { }, { full_name='rightleftcmd', abbreviation='rlc', + short_desc=N_("commands for which editing works right-to-left"), type='string', scope={'window'}, vi_def=true, alloced=true, @@ -1930,6 +2167,7 @@ return { }, { full_name='ruler', abbreviation='ru', + short_desc=N_("show cursor line and column in the status line"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -1939,6 +2177,7 @@ return { }, { full_name='rulerformat', abbreviation='ruf', + short_desc=N_("custom format for the ruler"), type='string', scope={'global'}, vi_def=true, alloced=true, @@ -1949,6 +2188,7 @@ return { }, { full_name='runtimepath', abbreviation='rtp', + short_desc=N_("list of directories used for runtime files"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -1959,6 +2199,7 @@ return { }, { full_name='scroll', abbreviation='scr', + short_desc=N_("lines to scroll with CTRL-U and CTRL-D"), type='number', scope={'window'}, no_mkrc=true, vi_def=true, @@ -1967,6 +2208,7 @@ return { }, { full_name='scrollback', abbreviation='scbk', + short_desc=N_("lines to scroll with CTRL-U and CTRL-D"), type='number', scope={'buffer'}, vi_def=true, varname='p_scbk', @@ -1975,6 +2217,7 @@ return { }, { full_name='scrollbind', abbreviation='scb', + short_desc=N_("scroll in window as other windows scroll"), type='bool', scope={'window'}, vi_def=true, pv_name='p_scbind', @@ -1982,6 +2225,7 @@ return { }, { full_name='scrolljump', abbreviation='sj', + short_desc=N_("minimum number of lines to scroll"), type='number', scope={'global'}, vi_def=true, vim=true, @@ -1990,6 +2234,7 @@ return { }, { full_name='scrolloff', abbreviation='so', + short_desc=N_("minimum nr. of lines above and below cursor"), type='number', scope={'global', 'window'}, vi_def=true, vim=true, @@ -1999,6 +2244,7 @@ return { }, { full_name='scrollopt', abbreviation='sbo', + short_desc=N_("how 'scrollbind' should behave"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2007,6 +2253,7 @@ return { }, { full_name='sections', abbreviation='sect', + short_desc=N_("nroff macros that separate sections"), type='string', scope={'global'}, vi_def=true, varname='p_sections', @@ -2014,6 +2261,7 @@ return { }, { full_name='secure', + short_desc=N_("mode for reading .vimrc in current dir"), type='bool', scope={'global'}, secure=true, vi_def=true, @@ -2022,6 +2270,7 @@ return { }, { full_name='selection', abbreviation='sel', + short_desc=N_("what type of selection to use"), type='string', scope={'global'}, vi_def=true, varname='p_sel', @@ -2029,6 +2278,7 @@ return { }, { full_name='selectmode', abbreviation='slm', + short_desc=N_("when to use Select mode instead of Visual mode"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2037,6 +2287,7 @@ return { }, { full_name='sessionoptions', abbreviation='ssop', + short_desc=N_("options for |:mksession|"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -2048,6 +2299,7 @@ return { }, { full_name='shada', abbreviation='sd', + short_desc=N_("use .shada file upon startup and exiting"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -2056,6 +2308,7 @@ return { }, { full_name='shadafile', abbreviation='sdf', + short_desc=N_("overrides the filename used for shada"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2065,6 +2318,7 @@ return { }, { full_name='shell', abbreviation='sh', + short_desc=N_("name of shell to use for external commands"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2078,6 +2332,7 @@ return { }, { full_name='shellcmdflag', abbreviation='shcf', + short_desc=N_("flag to shell to execute one command"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2090,6 +2345,7 @@ return { }, { full_name='shellpipe', abbreviation='sp', + short_desc=N_("string to put output of \":make\" in error file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2102,6 +2358,7 @@ return { }, { full_name='shellquote', abbreviation='shq', + short_desc=N_("quote character(s) for around shell command"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2110,6 +2367,7 @@ return { }, { full_name='shellredir', abbreviation='srr', + short_desc=N_("string to put output of filter in a temp file"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2122,6 +2380,7 @@ return { }, { full_name='shellslash', abbreviation='ssl', + short_desc=N_("use forward slash for shell file names"), type='bool', scope={'global'}, vi_def=true, varname='p_ssl', @@ -2130,12 +2389,14 @@ return { }, { full_name='shelltemp', abbreviation='stmp', + short_desc=N_("whether to use a temp file for shell commands"), type='bool', scope={'global'}, varname='p_stmp', defaults={if_true={vi=false, vim=true}} }, { full_name='shellxquote', abbreviation='sxq', + short_desc=N_("like 'shellquote', but include redirection"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2148,6 +2409,7 @@ return { }, { full_name='shellxescape', abbreviation='sxe', + short_desc=N_("characters to escape when 'shellxquote' is ("), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2156,6 +2418,7 @@ return { }, { full_name='shiftround', abbreviation='sr', + short_desc=N_("round indent to multiple of shiftwidth"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2164,6 +2427,7 @@ return { }, { full_name='shiftwidth', abbreviation='sw', + short_desc=N_("number of spaces to use for (auto)indent step"), type='number', scope={'buffer'}, vi_def=true, varname='p_sw', @@ -2171,6 +2435,7 @@ return { }, { full_name='shortmess', abbreviation='shm', + short_desc=N_("list of flags, reduce length of messages"), type='string', list='flags', scope={'global'}, vim=true, varname='p_shm', @@ -2178,6 +2443,7 @@ return { }, { full_name='showbreak', abbreviation='sbr', + short_desc=N_("string to use at the start of wrapped lines"), type='string', scope={'global'}, vi_def=true, redraw={'all_windows'}, @@ -2186,6 +2452,7 @@ return { }, { full_name='showcmd', abbreviation='sc', + short_desc=N_("show (partial) command in status line"), type='bool', scope={'global'}, vim=true, varname='p_sc', @@ -2193,6 +2460,7 @@ return { }, { full_name='showfulltag', abbreviation='sft', + short_desc=N_("show full tag pattern when completing tag"), type='bool', scope={'global'}, vi_def=true, varname='p_sft', @@ -2200,6 +2468,7 @@ return { }, { full_name='showmatch', abbreviation='sm', + short_desc=N_("briefly jump to matching bracket if insert one"), type='bool', scope={'global'}, vi_def=true, varname='p_sm', @@ -2207,6 +2476,7 @@ return { }, { full_name='showmode', abbreviation='smd', + short_desc=N_("message on status line to show current mode"), type='bool', scope={'global'}, vim=true, varname='p_smd', @@ -2214,6 +2484,7 @@ return { }, { full_name='showtabline', abbreviation='stal', + short_desc=N_("tells when the tab pages line is displayed"), type='number', scope={'global'}, vi_def=true, redraw={'all_windows', 'ui_option'}, @@ -2222,6 +2493,7 @@ return { }, { full_name='sidescroll', abbreviation='ss', + short_desc=N_("minimum number of columns to scroll horizontal"), type='number', scope={'global'}, vi_def=true, varname='p_ss', @@ -2229,6 +2501,7 @@ return { }, { full_name='sidescrolloff', abbreviation='siso', + short_desc=N_("min. nr. of columns to left and right of cursor"), type='number', scope={'global', 'window'}, vi_def=true, vim=true, @@ -2238,6 +2511,7 @@ return { }, { full_name='signcolumn', abbreviation='scl', + short_desc=N_("when to display the sign column"), type='string', scope={'window'}, vi_def=true, alloced=true, @@ -2246,6 +2520,7 @@ return { }, { full_name='smartcase', abbreviation='scs', + short_desc=N_("no ignore case when pattern has uppercase"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2254,6 +2529,7 @@ return { }, { full_name='smartindent', abbreviation='si', + short_desc=N_("smart autoindenting for C programs"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -2262,6 +2538,7 @@ return { }, { full_name='smarttab', abbreviation='sta', + short_desc=N_("use 'shiftwidth' when inserting <Tab>"), type='bool', scope={'global'}, vim=true, varname='p_sta', @@ -2269,6 +2546,7 @@ return { }, { full_name='softtabstop', abbreviation='sts', + short_desc=N_("number of spaces that <Tab> uses while editing"), type='number', scope={'buffer'}, vi_def=true, vim=true, @@ -2277,6 +2555,7 @@ return { }, { full_name='spell', + short_desc=N_("spell checking"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -2284,6 +2563,7 @@ return { }, { full_name='spellcapcheck', abbreviation='spc', + short_desc=N_("pattern to locate end of a sentence"), type='string', scope={'buffer'}, vi_def=true, alloced=true, @@ -2293,6 +2573,7 @@ return { }, { full_name='spellfile', abbreviation='spf', + short_desc=N_("files where |zg| and |zw| store words"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, secure=true, @@ -2304,6 +2585,7 @@ return { }, { full_name='spelllang', abbreviation='spl', + short_desc=N_("language(s) to do spell checking for"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -2315,6 +2597,7 @@ return { }, { full_name='spellsuggest', abbreviation='sps', + short_desc=N_("method(s) used to suggest spelling corrections"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -2324,7 +2607,18 @@ 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', + short_desc=N_("new window from split is below the current one"), type='bool', scope={'global'}, vi_def=true, varname='p_sb', @@ -2332,6 +2626,7 @@ return { }, { full_name='splitright', abbreviation='spr', + short_desc=N_("new window is put right of the current one"), type='bool', scope={'global'}, vi_def=true, varname='p_spr', @@ -2339,6 +2634,7 @@ return { }, { full_name='startofline', abbreviation='sol', + short_desc=N_("commands move cursor to first non-blank in line"), type='bool', scope={'global'}, vi_def=true, vim=false, @@ -2347,6 +2643,7 @@ return { }, { full_name='statusline', abbreviation='stl', + short_desc=N_("custom format for the status line"), type='string', scope={'global', 'window'}, vi_def=true, alloced=true, @@ -2357,6 +2654,7 @@ return { }, { full_name='suffixes', abbreviation='su', + short_desc=N_("suffixes that are ignored with multiple match"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2365,6 +2663,7 @@ return { }, { full_name='suffixesadd', abbreviation='sua', + short_desc=N_("suffixes added when searching for a file"), type='string', list='onecomma', scope={'buffer'}, deny_duplicates=true, vi_def=true, @@ -2374,6 +2673,7 @@ return { }, { full_name='swapfile', abbreviation='swf', + short_desc=N_("whether to use a swapfile for a buffer"), type='bool', scope={'buffer'}, vi_def=true, redraw={'statuslines'}, @@ -2382,6 +2682,7 @@ return { }, { full_name='switchbuf', abbreviation='swb', + short_desc=N_("sets behavior when switching to another buffer"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2390,6 +2691,7 @@ return { }, { full_name='synmaxcol', abbreviation='smc', + short_desc=N_("maximum column to find syntax items"), type='number', scope={'buffer'}, vi_def=true, redraw={'current_buffer'}, @@ -2398,6 +2700,7 @@ return { }, { full_name='syntax', abbreviation='syn', + short_desc=N_("syntax to be loaded for current buffer"), type='string', scope={'buffer'}, noglob=true, normal_fname_chars=true, @@ -2408,6 +2711,7 @@ return { }, { full_name='tagfunc', abbreviation='tfu', + short_desc=N_("function used to perform tag searches"), type='string', scope={'buffer'}, vim=true, vi_def=true, @@ -2416,6 +2720,7 @@ return { }, { full_name='tabline', abbreviation='tal', + short_desc=N_("custom format for the console tab pages line"), type='string', scope={'global'}, vi_def=true, modelineexpr=true, @@ -2425,6 +2730,7 @@ return { }, { full_name='tabpagemax', abbreviation='tpm', + short_desc=N_("maximum number of tab pages for |-p| and \"tab all\""), type='number', scope={'global'}, vim=true, varname='p_tpm', @@ -2432,6 +2738,7 @@ return { }, { full_name='tabstop', abbreviation='ts', + short_desc=N_("number of spaces that <Tab> in file uses"), type='number', scope={'buffer'}, vi_def=true, redraw={'current_buffer'}, @@ -2440,6 +2747,7 @@ return { }, { full_name='tagbsearch', abbreviation='tbs', + short_desc=N_("use binary searching in tags files"), type='bool', scope={'global'}, vi_def=true, varname='p_tbs', @@ -2447,6 +2755,7 @@ return { }, { full_name='tagcase', abbreviation='tc', + short_desc=N_("how to handle case when searching in tags files"), type='string', scope={'global', 'buffer'}, vim=true, varname='p_tc', @@ -2454,6 +2763,7 @@ return { }, { full_name='taglength', abbreviation='tl', + short_desc=N_("number of significant characters for a tag"), type='number', scope={'global'}, vi_def=true, varname='p_tl', @@ -2461,6 +2771,7 @@ return { }, { full_name='tagrelative', abbreviation='tr', + short_desc=N_("file names in tag file are relative"), type='bool', scope={'global'}, vim=true, varname='p_tr', @@ -2468,6 +2779,7 @@ return { }, { full_name='tags', abbreviation='tag', + short_desc=N_("list of file names used by the tag command"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, vi_def=true, @@ -2477,6 +2789,7 @@ return { }, { full_name='tagstack', abbreviation='tgst', + short_desc=N_("push tags onto the tag stack"), type='bool', scope={'global'}, vi_def=true, varname='p_tgst', @@ -2484,6 +2797,7 @@ return { }, { full_name='termbidi', abbreviation='tbidi', + short_desc=N_("terminal takes care of bi-directionality"), type='bool', scope={'global'}, vi_def=true, varname='p_tbidi', @@ -2491,12 +2805,14 @@ return { }, { full_name='termencoding', abbreviation='tenc', + short_desc=N_("Terminal encodig"), type='string', scope={'global'}, vi_def=true, defaults={if_true={vi=""}} }, { full_name='termguicolors', abbreviation='tgc', + short_desc=N_("Terminal true color support"), type='bool', scope={'global'}, vi_def=false, redraw={'ui_option'}, @@ -2505,6 +2821,7 @@ return { }, { full_name='terse', + short_desc=N_("hides notification of search wrap"), type='bool', scope={'global'}, vi_def=true, varname='p_terse', @@ -2512,6 +2829,7 @@ return { }, { full_name='textwidth', abbreviation='tw', + short_desc=N_("maximum width of text that is being inserted"), type='number', scope={'buffer'}, vi_def=true, vim=true, @@ -2521,6 +2839,7 @@ return { }, { full_name='thesaurus', abbreviation='tsr', + short_desc=N_("list of thesaurus files for keyword completion"), type='string', list='onecomma', scope={'global', 'buffer'}, deny_duplicates=true, normal_dname_chars=true, @@ -2531,6 +2850,7 @@ return { }, { full_name='tildeop', abbreviation='top', + short_desc=N_("tilde command \"~\" behaves like an operator"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2539,6 +2859,7 @@ return { }, { full_name='timeout', abbreviation='to', + short_desc=N_("time out on mappings and key codes"), type='bool', scope={'global'}, vi_def=true, varname='p_timeout', @@ -2546,6 +2867,7 @@ return { }, { full_name='timeoutlen', abbreviation='tm', + short_desc=N_("time out time in milliseconds"), type='number', scope={'global'}, vi_def=true, varname='p_tm', @@ -2553,6 +2875,7 @@ return { }, { full_name='title', + short_desc=N_("Vim set the title of the window"), type='bool', scope={'global'}, vi_def=true, varname='p_title', @@ -2560,6 +2883,7 @@ return { }, { full_name='titlelen', + short_desc=N_("of 'columns' used for window title"), type='number', scope={'global'}, vi_def=true, varname='p_titlelen', @@ -2567,6 +2891,7 @@ return { }, { full_name='titleold', + short_desc=N_("title, restored when exiting"), type='string', scope={'global'}, secure=true, no_mkrc=true, @@ -2576,6 +2901,7 @@ return { }, { full_name='titlestring', + short_desc=N_("to use for the Vim window title"), type='string', scope={'global'}, vi_def=true, modelineexpr=true, @@ -2584,6 +2910,7 @@ return { }, { full_name='ttimeout', + short_desc=N_("out on mappings"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2593,6 +2920,7 @@ return { }, { full_name='ttimeoutlen', abbreviation='ttm', + short_desc=N_("time out time for key codes in milliseconds"), type='number', scope={'global'}, vi_def=true, redraw={'ui_option'}, @@ -2601,6 +2929,7 @@ return { }, { full_name='ttyfast', abbreviation='tf', + short_desc=N_("No description"), type='bool', scope={'global'}, no_mkrc=true, vi_def=true, @@ -2609,6 +2938,7 @@ return { }, { full_name='undodir', abbreviation='udir', + short_desc=N_("where to store undo files"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, secure=true, @@ -2619,6 +2949,7 @@ return { }, { full_name='undofile', abbreviation='udf', + short_desc=N_("save undo information in a file"), type='bool', scope={'buffer'}, vi_def=true, vim=true, @@ -2627,6 +2958,7 @@ return { }, { full_name='undolevels', abbreviation='ul', + short_desc=N_("maximum number of changes that can be undone"), type='number', scope={'global', 'buffer'}, vi_def=true, varname='p_ul', @@ -2634,6 +2966,7 @@ return { }, { full_name='undoreload', abbreviation='ur', + short_desc=N_("max nr of lines to save for undo on a buffer reload"), type='number', scope={'global'}, vi_def=true, varname='p_ur', @@ -2641,6 +2974,7 @@ return { }, { full_name='updatecount', abbreviation='uc', + short_desc=N_("after this many characters flush swap file"), type='number', scope={'global'}, vi_def=true, varname='p_uc', @@ -2648,6 +2982,7 @@ return { }, { full_name='updatetime', abbreviation='ut', + short_desc=N_("after this many milliseconds flush swap file"), type='number', scope={'global'}, vi_def=true, varname='p_ut', @@ -2655,6 +2990,7 @@ return { }, { full_name='verbose', abbreviation='vbs', + short_desc=N_("give informative messages"), type='number', scope={'global'}, vi_def=true, varname='p_verbose', @@ -2662,6 +2998,7 @@ return { }, { full_name='verbosefile', abbreviation='vfile', + short_desc=N_("file to write messages in"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2671,6 +3008,7 @@ return { }, { full_name='viewdir', abbreviation='vdir', + short_desc=N_("directory where to store files with :mkview"), type='string', scope={'global'}, secure=true, vi_def=true, @@ -2680,6 +3018,7 @@ return { }, { full_name='viewoptions', abbreviation='vop', + short_desc=N_("specifies what to save for :mkview"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2689,15 +3028,18 @@ return { { -- Alias for "shada". full_name='viminfo', abbreviation='vi', + short_desc=N_("Alias for shada"), type='string', scope={'global'}, nodefault=true, }, { -- Alias for "shadafile". full_name='viminfofile', abbreviation='vif', + short_desc=N_("Alias for shadafile instead"), type='string', scope={'global'}, nodefault=true, }, { full_name='virtualedit', abbreviation='ve', + short_desc=N_("when to use virtual editing"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2708,6 +3050,7 @@ return { }, { full_name='visualbell', abbreviation='vb', + short_desc=N_("use visual bell instead of beeping"), type='bool', scope={'global'}, vi_def=true, varname='p_vb', @@ -2715,6 +3058,7 @@ return { }, { full_name='warn', + short_desc=N_("for shell command when buffer was changed"), type='bool', scope={'global'}, vi_def=true, varname='p_warn', @@ -2722,6 +3066,7 @@ return { }, { full_name='whichwrap', abbreviation='ww', + short_desc=N_("allow specified keys to cross line boundaries"), type='string', list='flagscomma', scope={'global'}, vim=true, varname='p_ww', @@ -2729,6 +3074,7 @@ return { }, { full_name='wildchar', abbreviation='wc', + short_desc=N_("command-line character for wildcard expansion"), type='number', scope={'global'}, vim=true, varname='p_wc', @@ -2736,6 +3082,7 @@ return { }, { full_name='wildcharm', abbreviation='wcm', + short_desc=N_("like 'wildchar' but also works when mapped"), type='number', scope={'global'}, vi_def=true, varname='p_wcm', @@ -2743,6 +3090,7 @@ return { }, { full_name='wildignore', abbreviation='wig', + short_desc=N_("files matching these patterns are not completed"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -2751,6 +3099,7 @@ return { }, { full_name='wildignorecase', abbreviation='wic', + short_desc=N_("ignore case when completing file names"), type='bool', scope={'global'}, vi_def=true, varname='p_wic', @@ -2758,6 +3107,7 @@ return { }, { full_name='wildmenu', abbreviation='wmnu', + short_desc=N_("use menu for command line completion"), type='bool', scope={'global'}, vim=true, varname='p_wmnu', @@ -2765,6 +3115,7 @@ return { }, { full_name='wildmode', abbreviation='wim', + short_desc=N_("mode for 'wildchar' command-line expansion"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -2773,6 +3124,7 @@ return { }, { full_name='wildoptions', abbreviation='wop', + short_desc=N_("specifies how command line completion is done"), type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vim=true, @@ -2781,6 +3133,7 @@ return { }, { full_name='winaltkeys', abbreviation='wak', + short_desc=N_("when the windows system handles ALT keys"), type='string', scope={'global'}, vi_def=true, varname='p_wak', @@ -2788,6 +3141,7 @@ return { }, { full_name='winblend', abbreviation='winbl', + short_desc=N_("Controls transparency level for floating windows"), type='number', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -2795,6 +3149,7 @@ return { }, { full_name='winhighlight', abbreviation='winhl', + short_desc=N_("Setup window-local highlights"); type='string', scope={'window'}, vi_def=true, alloced=true, @@ -2803,6 +3158,7 @@ return { }, { full_name='window', abbreviation='wi', + short_desc=N_("nr of lines to scroll for CTRL-F and CTRL-B"), type='number', scope={'global'}, vi_def=true, varname='p_window', @@ -2810,6 +3166,7 @@ return { }, { full_name='winheight', abbreviation='wh', + short_desc=N_("minimum number of lines for the current window"), type='number', scope={'global'}, vi_def=true, varname='p_wh', @@ -2817,6 +3174,7 @@ return { }, { full_name='winfixheight', abbreviation='wfh', + short_desc=N_("keep window height when opening/closing windows"), type='bool', scope={'window'}, vi_def=true, redraw={'statuslines'}, @@ -2824,6 +3182,7 @@ return { }, { full_name='winfixwidth', abbreviation='wfw', + short_desc=N_("keep window width when opening/closing windows"), type='bool', scope={'window'}, vi_def=true, redraw={'statuslines'}, @@ -2831,6 +3190,7 @@ return { }, { full_name='winminheight', abbreviation='wmh', + short_desc=N_("minimum number of lines for any window"), type='number', scope={'global'}, vi_def=true, varname='p_wmh', @@ -2838,6 +3198,7 @@ return { }, { full_name='winminwidth', abbreviation='wmw', + short_desc=N_("minimal number of columns for any window"), type='number', scope={'global'}, vi_def=true, varname='p_wmw', @@ -2845,6 +3206,7 @@ return { }, { full_name='winwidth', abbreviation='wiw', + short_desc=N_("minimal number of columns for current window"), type='number', scope={'global'}, vi_def=true, varname='p_wiw', @@ -2852,6 +3214,7 @@ return { }, { full_name='wrap', + short_desc=N_("lines wrap and continue on the next line"), type='bool', scope={'window'}, vi_def=true, redraw={'current_window'}, @@ -2859,6 +3222,7 @@ return { }, { full_name='wrapmargin', abbreviation='wm', + short_desc=N_("chars from the right where wrapping starts"), type='number', scope={'buffer'}, vi_def=true, varname='p_wm', @@ -2866,6 +3230,7 @@ return { }, { full_name='wrapscan', abbreviation='ws', + short_desc=N_("searches wrap around the end of the file"), type='bool', scope={'global'}, vi_def=true, varname='p_ws', @@ -2873,6 +3238,7 @@ return { }, { full_name='write', + short_desc=N_("to a file is allowed"), type='bool', scope={'global'}, vi_def=true, varname='p_write', @@ -2880,6 +3246,7 @@ return { }, { full_name='writeany', abbreviation='wa', + short_desc=N_("write to file with no need for \"!\" override"), type='bool', scope={'global'}, vi_def=true, varname='p_wa', @@ -2887,6 +3254,7 @@ return { }, { full_name='writebackup', abbreviation='wb', + short_desc=N_("make a backup before overwriting a file"), type='bool', scope={'global'}, vi_def=true, vim=true, @@ -2895,6 +3263,7 @@ return { }, { full_name='writedelay', abbreviation='wd', + short_desc=N_("delay this many msec for each char (for debug)"), type='number', scope={'global'}, vi_def=true, varname='p_wd', diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c index 2783411574..8483d316f3 100644 --- a/src/nvim/os/dl.c +++ b/src/nvim/os/dl.c @@ -20,8 +20,8 @@ typedef void (*gen_fn)(void); typedef const char *(*str_str_fn)(const char *str); typedef int (*str_int_fn)(const char *str); -typedef const char *(*int_str_fn)(int64_t i); -typedef int (*int_int_fn)(int64_t i); +typedef const char *(*int_str_fn)(int i); +typedef int (*int_int_fn)(int i); /// os_libcall - call a function in a dynamic loadable library /// @@ -41,7 +41,7 @@ typedef int (*int_int_fn)(int64_t i); bool os_libcall(const char *libname, const char *funcname, const char *argv, - int64_t argi, + int argi, char **str_out, int *int_out) { 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/fs.c b/src/nvim/os/fs.c index 873b611151..a3bef3389c 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -743,7 +743,9 @@ static int os_stat(const char *name, uv_stat_t *statbuf) } uv_fs_t request; int result = uv_fs_stat(&fs_loop, &request, name, NULL); - *statbuf = request.statbuf; + if (result == kLibuvSuccess) { + *statbuf = request.statbuf; + } uv_fs_req_cleanup(&request); return result; } @@ -1009,6 +1011,7 @@ int os_remove(const char *path) bool os_fileinfo(const char *path, FileInfo *file_info) FUNC_ATTR_NONNULL_ARG(2) { + memset(file_info, 0, sizeof(*file_info)); return os_stat(path, &(file_info->stat)) == kLibuvSuccess; } @@ -1020,14 +1023,17 @@ bool os_fileinfo(const char *path, FileInfo *file_info) bool os_fileinfo_link(const char *path, FileInfo *file_info) FUNC_ATTR_NONNULL_ARG(2) { + memset(file_info, 0, sizeof(*file_info)); if (path == NULL) { return false; } uv_fs_t request; - int result = uv_fs_lstat(&fs_loop, &request, path, NULL); - file_info->stat = request.statbuf; + bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess; + if (ok) { + file_info->stat = request.statbuf; + } uv_fs_req_cleanup(&request); - return (result == kLibuvSuccess); + return ok; } /// Get the file information for a given file descriptor @@ -1039,10 +1045,16 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; - int result = uv_fs_fstat(&fs_loop, &request, file_descriptor, NULL); - file_info->stat = request.statbuf; + memset(file_info, 0, sizeof(*file_info)); + bool ok = uv_fs_fstat(&fs_loop, + &request, + file_descriptor, + NULL) == kLibuvSuccess; + if (ok) { + file_info->stat = request.statbuf; + } uv_fs_req_cleanup(&request); - return (result == kLibuvSuccess); + return ok; } /// Compare the inodes of two FileInfos 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/os_defs.h b/src/nvim/os/os_defs.h index c8ac4218f6..1b7859b0d3 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -16,10 +16,11 @@ #define BASENAMELEN (NAME_MAX - 5) // Use the system path length if it makes sense. -#if defined(PATH_MAX) && (PATH_MAX > 1024) +# define DEFAULT_MAXPATHL 4096 +#if defined(PATH_MAX) && (PATH_MAX > DEFAULT_MAXPATHL) # define MAXPATHL PATH_MAX #else -# define MAXPATHL 1024 +# define MAXPATHL DEFAULT_MAXPATHL #endif // Command-processing buffer. Use large buffers for all platforms. @@ -44,4 +45,55 @@ # define os_strtok strtok_r #endif +// stat macros +#ifndef S_ISDIR +# ifdef S_IFDIR +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +# else +# define S_ISDIR(m) 0 +# endif +#endif +#ifndef S_ISREG +# ifdef S_IFREG +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +# else +# define S_ISREG(m) 0 +# endif +#endif +#ifndef S_ISBLK +# ifdef S_IFBLK +# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +# else +# define S_ISBLK(m) 0 +# endif +#endif +#ifndef S_ISSOCK +# ifdef S_IFSOCK +# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) +# else +# define S_ISSOCK(m) 0 +# endif +#endif +#ifndef S_ISFIFO +# ifdef S_IFIFO +# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +# else +# define S_ISFIFO(m) 0 +# endif +#endif +#ifndef S_ISCHR +# ifdef S_IFCHR +# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +# else +# define S_ISCHR(m) 0 +# endif +#endif +#ifndef S_ISLNK +# ifdef S_IFLNK +# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +# else +# define S_ISLNK(m) 0 +# endif +#endif + #endif // NVIM_OS_OS_DEFS_H diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 6294d5e4e2..b5d890bf52 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -150,11 +150,11 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, return FAIL; } - // Don't allow the use of backticks in secure and restricted mode. - if (secure || restricted) { + // Don't allow the use of backticks in secure. + if (secure) { for (i = 0; i < num_pat; i++) { if (vim_strchr(pat[i], '`') != NULL - && (check_restricted() || check_secure())) { + && (check_secure())) { return FAIL; } } 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/os/win_defs.h b/src/nvim/os/win_defs.h index 356094baa1..66d72de08d 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -74,28 +74,6 @@ typedef int mode_t; # define O_NOFOLLOW 0 #endif -#if !defined(S_ISDIR) && defined(S_IFDIR) -# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) -#endif -#if !defined(S_ISREG) && defined(S_IFREG) -# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) -#endif -#if !defined(S_ISLNK) && defined(S_IFLNK) -# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) -#endif -#if !defined(S_ISBLK) && defined(S_IFBLK) -# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) -#endif -#if !defined(S_ISSOCK) && defined(S_IFSOCK) -# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) -#endif -#if !defined(S_ISFIFO) && defined(S_IFIFO) -# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) -#endif -#if !defined(S_ISCHR) && defined(S_IFCHR) -# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) -#endif - #ifndef STDIN_FILENO # define STDIN_FILENO 0 #endif 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..ac0c1f6eb1 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) { @@ -785,7 +786,7 @@ static int pum_set_selected(int n, int repeat) // Return cursor to where we were validate_cursor(); - redraw_later(SOME_VALID); + redraw_later(curwin, SOME_VALID); // When the preview window was resized we need to // update the view on the buffer. Only go back to @@ -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 484168e798..6ca97c3072 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 @@ -3585,7 +3707,7 @@ static void qf_win_goto(win_T *win, linenr_T lnum) curwin->w_cursor.coladd = 0; curwin->w_curswant = 0; update_topline(); // scroll to show the line - redraw_later(VALID); + redraw_later(curwin, VALID); curwin->w_redr_status = true; // update ruler curwin = old_curwin; curbuf = curwin->w_buffer; @@ -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,56 +3872,63 @@ 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; buf_T *errbuf; if (qfp->qf_module != NULL) { - STRCPY(IObuff, qfp->qf_module); + STRLCPY(IObuff, qfp->qf_module, IOSIZE - 1); len = (int)STRLEN(IObuff); } else if (qfp->qf_fnum != 0 && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL && errbuf->b_fname != NULL) { if (qfp->qf_type == 1) { // :helpgrep - STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff)); + 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); } shorten_buf_fname(errbuf, dirname, false); } - STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff)); + STRLCPY(IObuff, errbuf->b_fname, IOSIZE - 1); } len = (int)STRLEN(IObuff); } else { len = 0; } - IObuff[len++] = '|'; - + if (len < IOSIZE - 1) { + IObuff[len++] = '|'; + } if (qfp->qf_lnum > 0) { - snprintf((char *)IObuff + len, sizeof(IObuff), "%" PRId64, + snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%" PRId64, (int64_t)qfp->qf_lnum); len += (int)STRLEN(IObuff + len); if (qfp->qf_col > 0) { - snprintf((char *)IObuff + len, sizeof(IObuff), " col %d", qfp->qf_col); + snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), " col %d", + qfp->qf_col); len += (int)STRLEN(IObuff + len); } - snprintf((char *)IObuff + len, sizeof(IObuff), "%s", + snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s", (char *)qf_types(qfp->qf_type, qfp->qf_nr)); len += (int)STRLEN(IObuff + len); } else if (qfp->qf_pattern != NULL) { qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); len += (int)STRLEN(IObuff + len); } - IObuff[len++] = '|'; - IObuff[len++] = ' '; + if (len < IOSIZE - 2) { + IObuff[len++] = '|'; + IObuff[len++] = ' '; + } // Remove newlines and leading whitespace from the text. // For an unrecognized line keep the indent, the compiler may @@ -3841,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; @@ -3853,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) { @@ -4802,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. @@ -4820,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, @@ -4852,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) { @@ -4894,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)" @@ -5026,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) { @@ -5050,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; @@ -6016,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. @@ -6051,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); @@ -6061,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)) { @@ -6120,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) { @@ -6136,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) { @@ -6364,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(); @@ -6435,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) @@ -6457,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, @@ -6490,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) @@ -6515,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); } @@ -6525,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) { @@ -6534,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); } } @@ -6576,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 34553fcec4..6316129c6a 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -40,11 +40,11 @@ * Named character class support added by Walter Briscoe (1998 Jul 01) */ -/* Uncomment the first if you do not want to see debugging logs or files - * related to regular expressions, even when compiling with -DDEBUG. - * Uncomment the second to get the regexp debugging. */ -/* #undef REGEXP_DEBUG */ -/* #define REGEXP_DEBUG */ +// By default: do not create debugging logs or files related to regular +// expressions, even when compiling with -DDEBUG. +// Uncomment the second line to get the regexp debugging. +// #undef REGEXP_DEBUG +// #define REGEXP_DEBUG #include <assert.h> #include <inttypes.h> @@ -301,8 +301,8 @@ typedef struct { */ typedef struct { union { - char_u *ptr; /* reginput pointer, for single-line regexp */ - lpos_T pos; /* reginput pos, for multi-line regexp */ + char_u *ptr; ///< rex.input pointer, for single-line regexp + lpos_T pos; ///< rex.input pos, for multi-line regexp } rs_u; int rs_len; } regsave_T; @@ -355,7 +355,7 @@ typedef struct regitem_S { union { save_se_T sesave; regsave_T regsave; - } rs_un; // room for saving reginput + } rs_un; ///< room for saving rex.input } regitem_T; @@ -490,6 +490,8 @@ static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here"); static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%["); static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); +static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); + #define NOT_MULTI 0 #define MULTI_ONE 1 #define MULTI_MULT 2 @@ -600,6 +602,12 @@ static int get_char_class(char_u **pp) #define CLASS_BACKSPACE 14 "escape:]", #define CLASS_ESCAPE 15 + "ident:]", +#define CLASS_IDENT 16 + "keyword:]", +#define CLASS_KEYWORD 17 + "fname:]", +#define CLASS_FNAME 18 }; #define CLASS_NONE 99 int i; @@ -633,7 +641,7 @@ static short class_tab[256]; static void init_class_tab(void) { int i; - static int done = FALSE; + static int done = false; if (done) return; @@ -658,7 +666,7 @@ static void init_class_tab(void) } class_tab[' '] |= RI_WHITE; class_tab['\t'] |= RI_WHITE; - done = TRUE; + done = true; } # define ri_digit(c) (c < 0x100 && (class_tab[c] & RI_DIGIT)) @@ -678,26 +686,24 @@ static void init_class_tab(void) #define RF_ICOMBINE 8 /* ignore combining characters */ #define RF_LOOKBH 16 /* uses "\@<=" or "\@<!" */ -/* - * Global work variables for vim_regcomp(). - */ - -static char_u *regparse; /* Input-scan pointer. */ -static int prevchr_len; /* byte length of previous char */ -static int num_complex_braces; /* Complex \{...} count */ -static int regnpar; /* () count. */ -static int regnzpar; /* \z() count. */ -static int re_has_z; /* \z item detected */ -static char_u *regcode; /* Code-emit pointer, or JUST_CALC_SIZE */ -static long regsize; /* Code size. */ -static int reg_toolong; /* TRUE when offset out of range */ -static char_u had_endbrace[NSUBEXP]; /* flags, TRUE if end of () found */ -static unsigned regflags; /* RF_ flags for prog */ -static long brace_min[10]; /* Minimums for complex brace repeats */ -static long brace_max[10]; /* Maximums for complex brace repeats */ -static int brace_count[10]; /* Current counts for complex brace repeats */ -static int had_eol; /* TRUE when EOL found by vim_regcomp() */ -static int one_exactly = FALSE; /* only do one char for EXACTLY */ +// Global work variables for vim_regcomp(). + +static char_u *regparse; ///< Input-scan pointer. +static int prevchr_len; ///< byte length of previous char +static int num_complex_braces; ///< Complex \{...} count +static int regnpar; ///< () count. +static int regnzpar; ///< \z() count. +static int re_has_z; ///< \z item detected +static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE +static long regsize; ///< Code size. +static int reg_toolong; ///< true when offset out of range +static char_u had_endbrace[NSUBEXP]; ///< flags, true if end of () found +static unsigned regflags; ///< RF_ flags for prog +static long brace_min[10]; ///< Minimums for complex brace repeats +static long brace_max[10]; ///< Maximums for complex brace repeats +static int brace_count[10]; ///< Current counts for complex brace repeats +static int had_eol; ///< true when EOL found by vim_regcomp() +static int one_exactly = false; ///< only do one char for EXACTLY static int reg_magic; /* magicness of the pattern: */ #define MAGIC_NONE 1 /* "\V" very unmagic */ @@ -754,10 +760,9 @@ static int nextchr; /* used for ungetchr() */ static regengine_T bt_regengine; static regengine_T nfa_regengine; -/* - * Return TRUE if compiled regular expression "prog" can match a line break. - */ -int re_multiline(regprog_T *prog) +// Return true if compiled regular expression "prog" can match a line break. +int re_multiline(const regprog_T *prog) + FUNC_ATTR_NONNULL_ALL { return prog->regflags & RF_HASNL; } @@ -1211,7 +1216,7 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) return p; } -/// Return TRUE if the back reference is legal. We must have seen the close +/// Return true if the back reference is legal. We must have seen the close /// brace. /// TODO(vim): Should also check that we don't refer to something repeated /// (+*=): what instance of the repetition should we match? @@ -1234,7 +1239,7 @@ static int seen_endbrace(int refnum) return false; } } - return TRUE; + return true; } /* @@ -1265,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(); @@ -1281,6 +1287,7 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) /* Allocate space. */ bt_regprog_T *r = xmalloc(sizeof(bt_regprog_T) + regsize); + r->re_in_use = false; /* * Second pass: emit code. @@ -1394,9 +1401,9 @@ regcomp_start ( regnzpar = 1; re_has_z = 0; regsize = 0L; - reg_toolong = FALSE; + reg_toolong = false; regflags = 0; - had_eol = FALSE; + had_eol = false; } /* @@ -1408,7 +1415,7 @@ int vim_regcomp_had_eol(void) return had_eol; } -// variables for parsing reginput +// variables used for parsing static int at_start; // True when on the first character static int prev_at_start; // True when on the second character @@ -1506,12 +1513,11 @@ reg ( EMSG_RET_NULL(_(e_trailing)); /* "Can't happen". */ /* NOTREACHED */ } - /* - * Here we set the flag allowing back references to this set of - * parentheses. - */ - if (paren == REG_PAREN) - had_endbrace[parno] = TRUE; /* have seen the close paren */ + // Here we set the flag allowing back references to this set of + // parentheses. + if (paren == REG_PAREN) { + had_endbrace[parno] = true; // have seen the close paren + } return ret; } @@ -1565,7 +1571,7 @@ static char_u *regconcat(int *flagp) char_u *chain = NULL; char_u *latest; int flags; - int cont = TRUE; + int cont = true; *flagp = WORST; /* Tentatively. */ @@ -1575,7 +1581,7 @@ static char_u *regconcat(int *flagp) case Magic('|'): case Magic('&'): case Magic(')'): - cont = FALSE; + cont = false; break; case Magic('Z'): regflags |= RF_ICOMBINE; @@ -1802,7 +1808,7 @@ static char_u *regatom(int *flagp) case Magic('$'): ret = regnode(EOL); - had_eol = TRUE; + had_eol = true; break; case Magic('<'): @@ -1821,7 +1827,7 @@ static char_u *regatom(int *flagp) } if (c == '$') { /* "\_$" is end-of-line */ ret = regnode(EOL); - had_eol = TRUE; + had_eol = true; break; } @@ -2069,11 +2075,12 @@ static char_u *regatom(int *flagp) } ungetchr(); - one_exactly = TRUE; + one_exactly = true; lastnode = regatom(flagp); - one_exactly = FALSE; - if (lastnode == NULL) + one_exactly = false; + if (lastnode == NULL) { return NULL; + } } if (ret == NULL) EMSG2_RET_NULL(_(e_empty_sb), @@ -2417,6 +2424,27 @@ collection: case CLASS_ESCAPE: regc(ESC); break; + case CLASS_IDENT: + for (cu = 1; cu <= 255; cu++) { + if (vim_isIDc(cu)) { + regmbc(cu); + } + } + break; + case CLASS_KEYWORD: + for (cu = 1; cu <= 255; cu++) { + if (reg_iswordc(cu)) { + regmbc(cu); + } + } + break; + case CLASS_FNAME: + for (cu = 1; cu <= 255; cu++) { + if (vim_isfilec(cu)) { + regmbc(cu); + } + } + break; } } else { // produce a multibyte character, including any @@ -2514,15 +2542,13 @@ static bool re_mult_next(char *what) return true; } -/* - * Return TRUE if MULTIBYTECODE should be used instead of EXACTLY for - * character "c". - */ -static int use_multibytecode(int c) +// Return true if MULTIBYTECODE should be used instead of EXACTLY for +// character "c". +static bool use_multibytecode(int c) { - return has_mbyte && (*mb_char2len)(c) > 1 + return utf_char2len(c) > 1 && (re_multi_type(peekchr()) != NOT_MULTI - || (enc_utf8 && utf_iscomposing(c))); + || utf_iscomposing(c)); } /* @@ -2667,39 +2693,38 @@ static char_u *re_put_uint32(char_u *p, uint32_t val) return p; } -/* - * Set the next-pointer at the end of a node chain. - */ +// Set the next-pointer at the end of a node chain. static void regtail(char_u *p, char_u *val) { - char_u *scan; - char_u *temp; int offset; - if (p == JUST_CALC_SIZE) + if (p == JUST_CALC_SIZE) { return; + } - /* Find last node. */ - scan = p; + // Find last node. + char_u *scan = p; for (;; ) { - temp = regnext(scan); - if (temp == NULL) + char_u *temp = regnext(scan); + if (temp == NULL) { break; + } scan = temp; } - if (OP(scan) == BACK) + if (OP(scan) == BACK) { offset = (int)(scan - val); - else + } else { offset = (int)(val - scan); - /* When the offset uses more than 16 bits it can no longer fit in the two - * bytes available. Use a global flag to avoid having to check return - * values in too many places. */ - if (offset > 0xffff) - reg_toolong = TRUE; - else { - *(scan + 1) = (char_u) (((unsigned)offset >> 8) & 0377); - *(scan + 2) = (char_u) (offset & 0377); + } + // When the offset uses more than 16 bits it can no longer fit in the two + // bytes available. Use a global flag to avoid having to check return + // values in too many places. + if (offset > 0xffff) { + reg_toolong = true; + } else { + *(scan + 1) = (char_u)(((unsigned)offset >> 8) & 0377); + *(scan + 2) = (char_u)(offset & 0377); } } @@ -2728,8 +2753,8 @@ static void initchr(char_u *str) regparse = str; prevchr_len = 0; curchr = prevprevchr = prevchr = nextchr = -1; - at_start = TRUE; - prev_at_start = FALSE; + at_start = true; + prev_at_start = false; } /* @@ -2771,7 +2796,7 @@ static void restore_parse_state(parse_state_T *ps) */ static int peekchr(void) { - static int after_slash = FALSE; + static int after_slash = false; if (curchr != -1) { return curchr; @@ -2837,8 +2862,8 @@ static int peekchr(void) || (no_Magic(prevchr) == '(' && prevprevchr == Magic('%')))) { curchr = Magic('^'); - at_start = TRUE; - prev_at_start = FALSE; + at_start = true; + prev_at_start = false; } break; case '$': @@ -2889,12 +2914,12 @@ static int peekchr(void) */ curchr = -1; prev_at_start = at_start; - at_start = FALSE; /* be able to say "/\*ptr" */ - ++regparse; - ++after_slash; + at_start = false; // be able to say "/\*ptr" + regparse++; + after_slash++; peekchr(); - --regparse; - --after_slash; + regparse--; + after_slash--; curchr = toggle_Magic(curchr); } else if (vim_strchr(REGEXP_ABBR, c)) { /* @@ -2936,7 +2961,7 @@ static void skipchr(void) } regparse += prevchr_len; prev_at_start = at_start; - at_start = FALSE; + at_start = false; prevprevchr = prevchr; prevchr = curchr; curchr = nextchr; /* use previously unget char, or -1 */ @@ -2980,7 +3005,7 @@ static void ungetchr(void) curchr = prevchr; prevchr = prevprevchr; at_start = prev_at_start; - prev_at_start = FALSE; + prev_at_start = false; /* Backup regparse, so that it's at the same position as before the * getchr(). */ @@ -3101,14 +3126,14 @@ static int coll_get_char(void) */ static int read_limits(long *minval, long *maxval) { - int reverse = FALSE; + int reverse = false; char_u *first_char; long tmp; if (*regparse == '-') { // Starts with '-', so reverse the range later. regparse++; - reverse = TRUE; + reverse = true; } first_char = regparse; *minval = getdigits_long(®parse, false, 0); @@ -3153,17 +3178,6 @@ static int read_limits(long *minval, long *maxval) * Global work variables for vim_regexec(). */ -/* The current match-position is remembered with these variables: */ -static linenr_T reglnum; /* line number, relative to first line */ -static char_u *regline; /* start of current line */ -static char_u *reginput; /* current input, points into "regline" */ - -static int need_clear_subexpr; /* subexpressions still need to be - * cleared */ -static int need_clear_zsubexpr = FALSE; /* extmatch subexpressions - * still need to be cleared */ - - /* Save the sub-expressions before attempting a match. */ #define save_se(savep, posp, pp) \ REG_MULTI ? save_se_multi((savep), (posp)) : save_se_one((savep), (pp)) @@ -3214,18 +3228,42 @@ typedef struct { linenr_T reg_maxline; bool reg_line_lbr; // "\n" in string is line break + // The current match-position is remembered with these variables: + linenr_T lnum; ///< line number, relative to first line + char_u *line; ///< start of current line + char_u *input; ///< current input, points into "regline" + + int need_clear_subexpr; ///< subexpressions still need to be cleared + int need_clear_zsubexpr; ///< extmatch subexpressions still need to be + ///< cleared + + // Internal copy of 'ignorecase'. It is set at each call to vim_regexec(). // Normally it gets the value of "rm_ic" or "rmm_ic", but when the pattern // contains '\c' or '\C' the value is overruled. bool reg_ic; - // Similar to rex.reg_ic, but only for 'combining' characters. Set with \Z + // Similar to "reg_ic", but only for 'combining' characters. Set with \Z // flag in the regexp. Defaults to false, always. bool reg_icombine; // Copy of "rmm_maxcol": maximum column to search for a match. Zero when // there is no maximum. colnr_T reg_maxcol; + + // State for the NFA engine regexec. + int nfa_has_zend; ///< NFA regexp \ze operator encountered. + int nfa_has_backref; ///< NFA regexp \1 .. \9 encountered. + int nfa_nsubexpr; ///< Number of sub expressions actually being used + ///< during execution. 1 if only the whole match + ///< (subexpr 0) is used. + // listid is global, so that it increases on recursive calls to + // nfa_regmatch(), which means we don't have to clear the lastlist field of + // all the states. + int nfa_listid; + int nfa_alt_listid; + + int nfa_has_zsubexpr; ///< NFA regexp has \z( ), set zsubexpr. } regexec_T; static regexec_T rex; @@ -3266,6 +3304,13 @@ void free_regexp_stuff(void) #endif +// Return true if character 'c' is included in 'iskeyword' option for +// "reg_buf" buffer. +static bool reg_iswordc(int c) +{ + return vim_iswordc_buf(c, rex.reg_buf); +} + /* * Get pointer to the line "lnum", which is relative to "reg_firstlnum". */ @@ -3290,7 +3335,7 @@ static char_u *reg_endzp[NSUBEXP]; /* and end of \z(...\) matches */ static lpos_T reg_startzpos[NSUBEXP]; /* idem, beginning pos */ static lpos_T reg_endzpos[NSUBEXP]; /* idem, end pos */ -// TRUE if using multi-line regexp. +// true if using multi-line regexp. #define REG_MULTI (rex.reg_match == NULL) /* @@ -3439,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; } @@ -3491,13 +3536,13 @@ static long bt_regexec_both(char_u *line, } } - regline = line; - reglnum = 0; - reg_toolong = FALSE; + rex.line = line; + rex.lnum = 0; + reg_toolong = false; /* Simplest case: Anchored match need be tried only once. */ if (prog->reganch) { - int c = utf_ptr2char(regline + col); + int c = utf_ptr2char(rex.line + col); if (prog->regstart == NUL || prog->regstart == c || (rex.reg_ic @@ -3514,12 +3559,12 @@ static long bt_regexec_both(char_u *line, while (!got_int) { if (prog->regstart != NUL) { // Skip until the char we know it must start with. - s = cstrchr(regline + col, prog->regstart); + s = cstrchr(rex.line + col, prog->regstart); if (s == NULL) { retval = 0; break; } - col = (int)(s - regline); + col = (int)(s - rex.line); } // Check for maximum column to try. @@ -3533,18 +3578,16 @@ static long bt_regexec_both(char_u *line, break; } - /* if not currently on the first line, get it again */ - if (reglnum != 0) { - reglnum = 0; - regline = reg_getline((linenr_T)0); + // if not currently on the first line, get it again + if (rex.lnum != 0) { + rex.lnum = 0; + rex.line = reg_getline((linenr_T)0); } - if (regline[col] == NUL) + if (rex.line[col] == NUL) { break; - if (has_mbyte) - col += (*mb_ptr2len)(regline + col); - else - ++col; - /* Check for timeout once in a twenty times to avoid overhead. */ + } + col += (*mb_ptr2len)(rex.line + col); + // Check for timeout once in a twenty times to avoid overhead. if (tm != NULL && ++tm_count == 20) { tm_count = 0; if (profile_passed_limit(*tm)) { @@ -3608,18 +3651,17 @@ void unref_extmatch(reg_extmatch_T *em) } } -/// Try match of "prog" with at regline["col"]. +/// Try match of "prog" with at rex.line["col"]. /// @returns 0 for failure, or number of lines contained in the match. static long regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, // timeout limit or NULL int *timed_out) // flag set on timeout or NULL { - reginput = regline + col; - need_clear_subexpr = TRUE; - /* Clear the external match subpointers if necessary. */ - if (prog->reghasz == REX_SET) - need_clear_zsubexpr = TRUE; + rex.input = rex.line + col; + rex.need_clear_subexpr = true; + // Clear the external match subpointers if necessaey. + rex.need_clear_zsubexpr = (prog->reghasz == REX_SET); if (regmatch(prog->program + 1, tm, timed_out) == 0) { return 0; @@ -3632,18 +3674,18 @@ static long regtry(bt_regprog_T *prog, rex.reg_startpos[0].col = col; } if (rex.reg_endpos[0].lnum < 0) { - rex.reg_endpos[0].lnum = reglnum; - rex.reg_endpos[0].col = (int)(reginput - regline); + rex.reg_endpos[0].lnum = rex.lnum; + rex.reg_endpos[0].col = (int)(rex.input - rex.line); } else { // Use line number of "\ze". - reglnum = rex.reg_endpos[0].lnum; + rex.lnum = rex.reg_endpos[0].lnum; } } else { if (rex.reg_startp[0] == NULL) { - rex.reg_startp[0] = regline + col; + rex.reg_startp[0] = rex.line + col; } if (rex.reg_endp[0] == NULL) { - rex.reg_endp[0] = reginput; + rex.reg_endp[0] = rex.input; } } /* Package any found \z(...\) matches for export. Default is none. */ @@ -3675,23 +3717,24 @@ static long regtry(bt_regprog_T *prog, } } } - return 1 + reglnum; + return 1 + rex.lnum; } // Get class of previous character. static int reg_prev_class(void) { - if (reginput > regline) { - return mb_get_class_tab(reginput - 1 - utf_head_off(regline, reginput - 1), - rex.reg_buf->b_chartab); + if (rex.input > rex.line) { + return mb_get_class_tab( + rex.input - 1 - utf_head_off(rex.line, rex.input - 1), + rex.reg_buf->b_chartab); } return -1; } -// Return TRUE if the current reginput position matches the Visual area. -static int reg_match_visual(void) +// Return true if the current rex.input position matches the Visual area. +static bool reg_match_visual(void) { pos_T top, bot; linenr_T lnum; @@ -3725,16 +3768,17 @@ static int reg_match_visual(void) } mode = curbuf->b_visual.vi_mode; } - lnum = reglnum + rex.reg_firstlnum; + lnum = rex.lnum + rex.reg_firstlnum; if (lnum < top.lnum || lnum > bot.lnum) { return false; } if (mode == 'v') { - col = (colnr_T)(reginput - regline); + col = (colnr_T)(rex.input - rex.line); if ((lnum == top.lnum && col < top.col) - || (lnum == bot.lnum && col >= bot.col + (*p_sel != 'e'))) - return FALSE; + || (lnum == bot.lnum && col >= bot.col + (*p_sel != 'e'))) { + return false; + } } else if (mode == Ctrl_V) { getvvcol(wp, &top, &start, NULL, &end); getvvcol(wp, &bot, &start2, NULL, &end2); @@ -3744,17 +3788,18 @@ static int reg_match_visual(void) end = end2; if (top.col == MAXCOL || bot.col == MAXCOL) end = MAXCOL; - unsigned int cols_u = win_linetabsize(wp, regline, - (colnr_T)(reginput - regline)); + unsigned int cols_u = win_linetabsize(wp, rex.line, + (colnr_T)(rex.input - rex.line)); assert(cols_u <= MAXCOL); colnr_T cols = (colnr_T)cols_u; - if (cols < start || cols > end - (*p_sel == 'e')) - return FALSE; + if (cols < start || cols > end - (*p_sel == 'e')) { + return false; + } } - return TRUE; + return true; } -#define ADVANCE_REGINPUT() MB_PTR_ADV(reginput) +#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input) /* * The arguments from BRACE_LIMITS are stored here. They are actually local @@ -3773,11 +3818,11 @@ static long bl_maxval; /// (that don't need to know whether the rest of the match failed) by a nested /// loop. /// -/// Returns TRUE when there is a match. Leaves reginput and reglnum just after -/// the last matched character. -/// Returns FALSE when there is no match. Leaves reginput and reglnum in an +/// Returns true when there is a match. Leaves rex.input and rex.lnum +/// just after the last matched character. +/// Returns false when there is no match. Leaves rex.input and rex.lnum in an /// undefined state! -static int regmatch( +static bool regmatch( char_u *scan, // Current node. proftime_T *tm, // timeout limit or NULL int *timed_out // flag set on timeout or NULL @@ -3860,38 +3905,40 @@ static int regmatch( op = OP(scan); // Check for character class with NL added. if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI - && *reginput == NUL && reglnum <= rex.reg_maxline) { + && *rex.input == NUL && rex.lnum <= rex.reg_maxline) { reg_nextline(); - } else if (rex.reg_line_lbr && WITH_NL(op) && *reginput == '\n') { + } else if (rex.reg_line_lbr && WITH_NL(op) && *rex.input == '\n') { ADVANCE_REGINPUT(); } else { if (WITH_NL(op)) { op -= ADD_NL; } - c = utf_ptr2char(reginput); + c = utf_ptr2char(rex.input); switch (op) { case BOL: - if (reginput != regline) + if (rex.input != rex.line) { status = RA_NOMATCH; + } break; case EOL: - if (c != NUL) + if (c != NUL) { status = RA_NOMATCH; + } break; case RE_BOF: // We're not at the beginning of the file when below the first // line where we started, not at the start of the line or we // didn't start at the first line of the buffer. - if (reglnum != 0 || reginput != regline + if (rex.lnum != 0 || rex.input != rex.line || (REG_MULTI && rex.reg_firstlnum > 1)) { status = RA_NOMATCH; } break; case RE_EOF: - if (reglnum != rex.reg_maxline || c != NUL) { + if (rex.lnum != rex.reg_maxline || c != NUL) { status = RA_NOMATCH; } break; @@ -3900,8 +3947,9 @@ static int regmatch( // Check if the buffer is in a window and compare the // rex.reg_win->w_cursor position to the match position. if (rex.reg_win == NULL - || (reglnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum) - || ((colnr_T)(reginput - regline) != rex.reg_win->w_cursor.col)) { + || (rex.lnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum) + || ((colnr_T)(rex.input - rex.line) != + rex.reg_win->w_cursor.col)) { status = RA_NOMATCH; } break; @@ -3916,13 +3964,13 @@ static int regmatch( pos = getmark_buf(rex.reg_buf, mark, false); if (pos == NULL // mark doesn't exist || pos->lnum <= 0 // mark isn't set in reg_buf - || (pos->lnum == reglnum + rex.reg_firstlnum - ? (pos->col == (colnr_T)(reginput - regline) + || (pos->lnum == rex.lnum + rex.reg_firstlnum + ? (pos->col == (colnr_T)(rex.input - rex.line) ? (cmp == '<' || cmp == '>') - : (pos->col < (colnr_T)(reginput - regline) + : (pos->col < (colnr_T)(rex.input - rex.line) ? cmp != '>' : cmp != '<')) - : (pos->lnum < reglnum + rex.reg_firstlnum + : (pos->lnum < rex.lnum + rex.reg_firstlnum ? cmp != '>' : cmp != '<'))) { status = RA_NOMATCH; @@ -3936,79 +3984,70 @@ static int regmatch( break; case RE_LNUM: - assert(reglnum + rex.reg_firstlnum >= 0 - && (uintmax_t)(reglnum + rex.reg_firstlnum) <= UINT32_MAX); + assert(rex.lnum + rex.reg_firstlnum >= 0 + && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX); if (!REG_MULTI - || !re_num_cmp((uint32_t)(reglnum + rex.reg_firstlnum), scan)) { + || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) { status = RA_NOMATCH; } break; case RE_COL: - assert(reginput - regline + 1 >= 0 - && (uintmax_t)(reginput - regline + 1) <= UINT32_MAX); - if (!re_num_cmp((uint32_t)(reginput - regline + 1), scan)) + assert(rex.input - rex.line + 1 >= 0 + && (uintmax_t)(rex.input - rex.line + 1) <= UINT32_MAX); + if (!re_num_cmp((uint32_t)(rex.input - rex.line + 1), scan)) { status = RA_NOMATCH; + } break; case RE_VCOL: if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win, - regline, - (colnr_T)(reginput - regline)) + 1, + rex.line, + (colnr_T)(rex.input - rex.line)) + 1, scan)) { status = RA_NOMATCH; } break; - case BOW: /* \<word; reginput points to w */ - if (c == NUL) /* Can't match at end of line */ + case BOW: // \<word; rex.input points to w + if (c == NUL) { // Can't match at end of line status = RA_NOMATCH; - else if (has_mbyte) { - int this_class; - + } else { // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); + const int this_class = + mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); if (this_class <= 1) { status = RA_NOMATCH; // Not on a word at all. } else if (reg_prev_class() == this_class) { status = RA_NOMATCH; // Previous char is in same word. } - } else { - if (!vim_iswordc_buf(c, rex.reg_buf) - || (reginput > regline - && vim_iswordc_buf(reginput[-1], rex.reg_buf))) { - status = RA_NOMATCH; - } } break; - case EOW: /* word\>; reginput points after d */ - if (reginput == regline) /* Can't match at start of line */ + case EOW: // word\>; rex.input points after d + if (rex.input == rex.line) { // Can't match at start of line status = RA_NOMATCH; - else if (has_mbyte) { + } else { int this_class, prev_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class - || prev_class == 0 || prev_class == 1) - status = RA_NOMATCH; - } else { - if (!vim_iswordc_buf(reginput[-1], rex.reg_buf) - || (reginput[0] != NUL && vim_iswordc_buf(c, rex.reg_buf))) { + || prev_class == 0 || prev_class == 1) { status = RA_NOMATCH; } } - break; /* Matched with EOW */ + break; // Matched with EOW case ANY: - /* ANY does not match new lines. */ - if (c == NUL) + // ANY does not match new lines. + if (c == NUL) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case IDENT: @@ -4019,14 +4058,15 @@ static int regmatch( break; case SIDENT: - if (ascii_isdigit(*reginput) || !vim_isIDc(c)) + if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case KWORD: - if (!vim_iswordp_buf(reginput, rex.reg_buf)) { + if (!vim_iswordp_buf(rex.input, rex.reg_buf)) { status = RA_NOMATCH; } else { ADVANCE_REGINPUT(); @@ -4034,8 +4074,8 @@ static int regmatch( break; case SKWORD: - if (ascii_isdigit(*reginput) - || !vim_iswordp_buf(reginput, rex.reg_buf)) { + if (ascii_isdigit(*rex.input) + || !vim_iswordp_buf(rex.input, rex.reg_buf)) { status = RA_NOMATCH; } else { ADVANCE_REGINPUT(); @@ -4043,31 +4083,35 @@ static int regmatch( break; case FNAME: - if (!vim_isfilec(c)) + if (!vim_isfilec(c)) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case SFNAME: - if (ascii_isdigit(*reginput) || !vim_isfilec(c)) + if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case PRINT: - if (!vim_isprintc(PTR2CHAR(reginput))) + if (!vim_isprintc(PTR2CHAR(rex.input))) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case SPRINT: - if (ascii_isdigit(*reginput) || !vim_isprintc(PTR2CHAR(reginput))) + if (ascii_isdigit(*rex.input) || !vim_isprintc(PTR2CHAR(rex.input))) { status = RA_NOMATCH; - else + } else { ADVANCE_REGINPUT(); + } break; case WHITE: @@ -4203,10 +4247,10 @@ static int regmatch( opnd = OPERAND(scan); // Inline the first byte, for speed. - if (*opnd != *reginput + if (*opnd != *rex.input && (!rex.reg_ic || (!enc_utf8 - && mb_tolower(*opnd) != mb_tolower(*reginput)))) { + && mb_tolower(*opnd) != mb_tolower(*rex.input)))) { status = RA_NOMATCH; } else if (*opnd == NUL) { // match empty string always works; happens when "~" is @@ -4217,14 +4261,14 @@ static int regmatch( } else { // Need to match first byte again for multi-byte. len = (int)STRLEN(opnd); - if (cstrncmp(opnd, reginput, &len) != 0) { + if (cstrncmp(opnd, rex.input, &len) != 0) { status = RA_NOMATCH; } } // Check for following composing character, unless %C // follows (skips over all composing chars). if (status != RA_NOMATCH && enc_utf8 - && UTF_COMPOSINGLIKE(reginput, reginput + len) + && UTF_COMPOSINGLIKE(rex.input, rex.input + len) && !rex.reg_icombine && OP(next) != RE_COMPOSING) { // raaron: This code makes a composing character get @@ -4233,7 +4277,7 @@ static int regmatch( status = RA_NOMATCH; } if (status != RA_NOMATCH) { - reginput += len; + rex.input += len; } } } @@ -4250,54 +4294,52 @@ static int regmatch( break; case MULTIBYTECODE: - if (has_mbyte) { + { int i, len; - char_u *opnd; - int opndc = 0, inpc; - opnd = OPERAND(scan); + const char_u *opnd = OPERAND(scan); // Safety check (just in case 'encoding' was changed since // compiling the program). if ((len = (*mb_ptr2len)(opnd)) < 2) { status = RA_NOMATCH; break; } - if (enc_utf8) { - opndc = utf_ptr2char(opnd); - } - if (enc_utf8 && utf_iscomposing(opndc)) { - /* When only a composing char is given match at any - * position where that composing char appears. */ + const int opndc = utf_ptr2char(opnd); + if (utf_iscomposing(opndc)) { + // When only a composing char is given match at any + // position where that composing char appears. status = RA_NOMATCH; - for (i = 0; reginput[i] != NUL; i += utf_ptr2len(reginput + i)) { - inpc = utf_ptr2char(reginput + i); + for (i = 0; rex.input[i] != NUL; + i += utf_ptr2len(rex.input + i)) { + const int inpc = utf_ptr2char(rex.input + i); if (!utf_iscomposing(inpc)) { if (i > 0) { break; } } else if (opndc == inpc) { // Include all following composing chars. - len = i + utfc_ptr2len(reginput + i); + len = i + utfc_ptr2len(rex.input + i); status = RA_MATCH; break; } } - } else - for (i = 0; i < len; ++i) - if (opnd[i] != reginput[i]) { + } else { + for (i = 0; i < len; i++) { + if (opnd[i] != rex.input[i]) { status = RA_NOMATCH; break; } - reginput += len; - } else - status = RA_NOMATCH; + } + } + rex.input += len; + } break; case RE_COMPOSING: if (enc_utf8) { // Skip composing characters. - while (utf_iscomposing(utf_ptr2char(reginput))) { - MB_CPTR_ADV(reginput); + while (utf_iscomposing(utf_ptr2char(rex.input))) { + MB_CPTR_ADV(rex.input); } } break; @@ -4460,7 +4502,7 @@ static int regmatch( } else { // Compare current input with back-ref in the same line. len = (int)(rex.reg_endp[no] - rex.reg_startp[no]); - if (cstrncmp(rex.reg_startp[no], reginput, &len) != 0) { + if (cstrncmp(rex.reg_startp[no], rex.input, &len) != 0) { status = RA_NOMATCH; } } @@ -4469,12 +4511,12 @@ static int regmatch( // Backref was not set: Match an empty string. len = 0; } else { - if (rex.reg_startpos[no].lnum == reglnum - && rex.reg_endpos[no].lnum == reglnum) { + if (rex.reg_startpos[no].lnum == rex.lnum + && rex.reg_endpos[no].lnum == rex.lnum) { // Compare back-ref within the current line. len = rex.reg_endpos[no].col - rex.reg_startpos[no].col; - if (cstrncmp(regline + rex.reg_startpos[no].col, - reginput, &len) != 0) { + if (cstrncmp(rex.line + rex.reg_startpos[no].col, + rex.input, &len) != 0) { status = RA_NOMATCH; } } else { @@ -4491,8 +4533,8 @@ static int regmatch( } } - /* Matched the backref, skip over it. */ - reginput += len; + // Matched the backref, skip over it. + rex.input += len; } break; @@ -4506,20 +4548,18 @@ static int regmatch( case ZREF + 8: case ZREF + 9: { - int len; - cleanup_zsubexpr(); no = op - ZREF; if (re_extmatch_in != NULL && re_extmatch_in->matches[no] != NULL) { - len = (int)STRLEN(re_extmatch_in->matches[no]); - if (cstrncmp(re_extmatch_in->matches[no], - reginput, &len) != 0) + int len = (int)STRLEN(re_extmatch_in->matches[no]); + if (cstrncmp(re_extmatch_in->matches[no], rex.input, &len) != 0) { status = RA_NOMATCH; - else - reginput += len; + } else { + rex.input += len; + } } else { - /* Backref was not set: Match an empty string. */ + // Backref was not set: Match an empty string. } } break; @@ -4725,15 +4765,17 @@ static int regmatch( case BHPOS: if (REG_MULTI) { - if (behind_pos.rs_u.pos.col != (colnr_T)(reginput - regline) - || behind_pos.rs_u.pos.lnum != reglnum) + if (behind_pos.rs_u.pos.col != (colnr_T)(rex.input - rex.line) + || behind_pos.rs_u.pos.lnum != rex.lnum) { status = RA_NOMATCH; - } else if (behind_pos.rs_u.ptr != reginput) + } + } else if (behind_pos.rs_u.ptr != rex.input) { status = RA_NOMATCH; + } break; case NEWL: - if ((c != NUL || !REG_MULTI || reglnum > rex.reg_maxline + if ((c != NUL || !REG_MULTI || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) { status = RA_NOMATCH; } else if (rex.reg_line_lbr) { @@ -4748,7 +4790,7 @@ static int regmatch( break; default: - EMSG(_(e_re_corr)); + IEMSG(_(e_re_corr)); #ifdef REGEXP_DEBUG printf("Illegal op code %d\n", op); #endif @@ -4946,7 +4988,7 @@ static int regmatch( if (limit > 0 && ((rp->rs_un.regsave.rs_u.pos.lnum < behind_pos.rs_u.pos.lnum - ? (colnr_T)STRLEN(regline) + ? (colnr_T)STRLEN(rex.line) : behind_pos.rs_u.pos.col) - rp->rs_un.regsave.rs_u.pos.col >= limit)) no = FAIL; @@ -4960,7 +5002,7 @@ static int regmatch( else { reg_restore(&rp->rs_un.regsave, &backpos); rp->rs_un.regsave.rs_u.pos.col = - (colnr_T)STRLEN(regline); + (colnr_T)STRLEN(rex.line); } } else { const char_u *const line = @@ -4972,10 +5014,10 @@ static int regmatch( + 1; } } else { - if (rp->rs_un.regsave.rs_u.ptr == regline) { + if (rp->rs_un.regsave.rs_u.ptr == rex.line) { no = FAIL; } else { - MB_PTR_BACK(regline, rp->rs_un.regsave.rs_u.ptr); + MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr); if (limit > 0 && (long)(behind_pos.rs_u.ptr - rp->rs_un.regsave.rs_u.ptr) > limit) { @@ -5039,18 +5081,18 @@ static int regmatch( * didn't match -- back up one char. */ if (--rst->count < rst->minval) break; - if (reginput == regline) { + if (rex.input == rex.line) { // backup to last char of previous line - reglnum--; - regline = reg_getline(reglnum); + rex.lnum--; + rex.line = reg_getline(rex.lnum); // Just in case regrepeat() didn't count right. - if (regline == NULL) { + if (rex.line == NULL) { break; } - reginput = regline + STRLEN(regline); + rex.input = rex.line + STRLEN(rex.line); fast_breakcheck(); } else { - MB_PTR_BACK(regline, reginput); + MB_PTR_BACK(rex.line, rex.input); } } else { /* Range is backwards, use shortest match first. @@ -5067,9 +5109,9 @@ static int regmatch( } else status = RA_NOMATCH; - /* If it could match, try it. */ - if (rst->nextb == NUL || *reginput == rst->nextb - || *reginput == rst->nextb_ic) { + // If it could match, try it. + if (rst->nextb == NUL || *rex.input == rst->nextb + || *rex.input == rst->nextb_ic) { reg_save(&rp->rs_un.regsave, &backpos); scan = regnext(rp->rs_scan); status = RA_CONT; @@ -5106,7 +5148,7 @@ static int 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 @@ -5156,7 +5198,7 @@ static void regstack_pop(char_u **scan) /* * regrepeat - repeatedly match something simple, return how many. - * Advances reginput (and reglnum) to just after the matched chars. + * Advances rex.input (and rex.lnum) to just after the matched chars. */ static int regrepeat ( @@ -5165,12 +5207,11 @@ regrepeat ( ) { long count = 0; - char_u *scan; char_u *opnd; int mask; int testval = 0; - scan = reginput; /* Make local copy of reginput for speed. */ + char_u *scan = rex.input; // Make local copy of rex.input for speed. opnd = OPERAND(p); switch (OP(p)) { case ANY: @@ -5182,15 +5223,16 @@ regrepeat ( count++; MB_PTR_ADV(scan); } - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr || count == maxcount) { break; } count++; // count the line-break reg_nextline(); - scan = reginput; - if (got_int) + scan = rex.input; + if (got_int) { break; + } } break; @@ -5204,14 +5246,15 @@ regrepeat ( if (vim_isIDc(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) { MB_PTR_ADV(scan); } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; - if (got_int) + scan = rex.input; + if (got_int) { break; + } } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { scan++; } else { @@ -5232,12 +5275,12 @@ regrepeat ( && (testval || !ascii_isdigit(*scan))) { MB_PTR_ADV(scan); } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; + scan = rex.input; if (got_int) { break; } @@ -5260,12 +5303,12 @@ regrepeat ( if (vim_isfilec(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) { MB_PTR_ADV(scan); } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; + scan = rex.input; if (got_int) { break; } @@ -5286,12 +5329,12 @@ regrepeat ( case SPRINT + ADD_NL: while (count < maxcount) { if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; + scan = rex.input; if (got_int) { break; } @@ -5314,14 +5357,15 @@ do_class: while (count < maxcount) { int l; if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; - if (got_int) + scan = rex.input; + if (got_int) { break; + } } else if (has_mbyte && (l = (*mb_ptr2len)(scan)) > 1) { if (testval != 0) break; @@ -5467,12 +5511,12 @@ do_class: while (count < maxcount) { int len; if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline || rex.reg_line_lbr) { break; } reg_nextline(); - scan = reginput; + scan = rex.input; if (got_int) { break; } @@ -5494,7 +5538,7 @@ do_class: case NEWL: while (count < maxcount - && ((*scan == NUL && reglnum <= rex.reg_maxline && !rex.reg_line_lbr + && ((*scan == NUL && rex.lnum <= rex.reg_maxline && !rex.reg_line_lbr && REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) { count++; if (rex.reg_line_lbr) { @@ -5502,21 +5546,22 @@ do_class: } else { reg_nextline(); } - scan = reginput; - if (got_int) + scan = rex.input; + if (got_int) { break; + } } 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 break; } - reginput = scan; + rex.input = scan; return (int)count; } @@ -5546,7 +5591,7 @@ static char_u *regnext(char_u *p) /* * Check the regexp program for its magic number. - * Return TRUE if it's wrong. + * Return true if it's wrong. */ static int prog_magic_wrong(void) { @@ -5560,9 +5605,9 @@ static int prog_magic_wrong(void) if (UCHARAT(((bt_regprog_T *)prog)->program) != REGMAGIC) { EMSG(_(e_re_corr)); - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -5572,7 +5617,7 @@ static int prog_magic_wrong(void) */ static void cleanup_subexpr(void) { - if (need_clear_subexpr) { + if (rex.need_clear_subexpr) { if (REG_MULTI) { // Use 0xff to set lnum to -1 memset(rex.reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP); @@ -5581,13 +5626,13 @@ static void cleanup_subexpr(void) memset(rex.reg_startp, 0, sizeof(char_u *) * NSUBEXP); memset(rex.reg_endp, 0, sizeof(char_u *) * NSUBEXP); } - need_clear_subexpr = FALSE; + rex.need_clear_subexpr = false; } } static void cleanup_zsubexpr(void) { - if (need_clear_zsubexpr) { + if (rex.need_clear_zsubexpr) { if (REG_MULTI) { /* Use 0xff to set lnum to -1 */ memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP); @@ -5596,23 +5641,20 @@ static void cleanup_zsubexpr(void) memset(reg_startzp, 0, sizeof(char_u *) * NSUBEXP); memset(reg_endzp, 0, sizeof(char_u *) * NSUBEXP); } - need_clear_zsubexpr = FALSE; + rex.need_clear_zsubexpr = false; } } -/* - * Save the current subexpr to "bp", so that they can be restored - * later by restore_subexpr(). - */ +// Save the current subexpr to "bp", so that they can be restored +// later by restore_subexpr(). static void save_subexpr(regbehind_T *bp) + FUNC_ATTR_NONNULL_ALL { - int i; - - // When "need_clear_subexpr" is set we don't need to save the values, only + // When "rex.need_clear_subexpr" is set we don't need to save the values, only // remember that this flag needs to be set again when restoring. - bp->save_need_clear_subexpr = need_clear_subexpr; - if (!need_clear_subexpr) { - for (i = 0; i < NSUBEXP; ++i) { + bp->save_need_clear_subexpr = rex.need_clear_subexpr; + if (!rex.need_clear_subexpr) { + for (int i = 0; i < NSUBEXP; i++) { if (REG_MULTI) { bp->save_start[i].se_u.pos = rex.reg_startpos[i]; bp->save_end[i].se_u.pos = rex.reg_endpos[i]; @@ -5624,17 +5666,14 @@ static void save_subexpr(regbehind_T *bp) } } -/* - * Restore the subexpr from "bp". - */ +// Restore the subexpr from "bp". static void restore_subexpr(regbehind_T *bp) + FUNC_ATTR_NONNULL_ALL { - int i; - - /* Only need to restore saved values when they are not to be cleared. */ - need_clear_subexpr = bp->save_need_clear_subexpr; - if (!need_clear_subexpr) { - for (i = 0; i < NSUBEXP; ++i) { + // Only need to restore saved values when they are not to be cleared. + rex.need_clear_subexpr = bp->save_need_clear_subexpr; + if (!rex.need_clear_subexpr) { + for (int i = 0; i < NSUBEXP; i++) { if (REG_MULTI) { rex.reg_startpos[i] = bp->save_start[i].se_u.pos; rex.reg_endpos[i] = bp->save_end[i].se_u.pos; @@ -5646,56 +5685,54 @@ static void restore_subexpr(regbehind_T *bp) } } -/* - * Advance reglnum, regline and reginput to the next line. - */ +// Advance rex.lnum, rex.line and rex.input to the next line. static void reg_nextline(void) { - regline = reg_getline(++reglnum); - reginput = regline; + rex.line = reg_getline(++rex.lnum); + rex.input = rex.line; fast_breakcheck(); } -/* - * Save the input line and position in a regsave_T. - */ +// Save the input line and position in a regsave_T. static void reg_save(regsave_T *save, garray_T *gap) + FUNC_ATTR_NONNULL_ALL { if (REG_MULTI) { - save->rs_u.pos.col = (colnr_T)(reginput - regline); - save->rs_u.pos.lnum = reglnum; - } else - save->rs_u.ptr = reginput; + save->rs_u.pos.col = (colnr_T)(rex.input - rex.line); + save->rs_u.pos.lnum = rex.lnum; + } else { + save->rs_u.ptr = rex.input; + } save->rs_len = gap->ga_len; } -/* - * Restore the input line and position from a regsave_T. - */ +// Restore the input line and position from a regsave_T. static void reg_restore(regsave_T *save, garray_T *gap) + FUNC_ATTR_NONNULL_ALL { if (REG_MULTI) { - if (reglnum != save->rs_u.pos.lnum) { - /* only call reg_getline() when the line number changed to save - * a bit of time */ - reglnum = save->rs_u.pos.lnum; - regline = reg_getline(reglnum); + if (rex.lnum != save->rs_u.pos.lnum) { + // only call reg_getline() when the line number changed to save + // a bit of time + rex.lnum = save->rs_u.pos.lnum; + rex.line = reg_getline(rex.lnum); } - reginput = regline + save->rs_u.pos.col; - } else - reginput = save->rs_u.ptr; + rex.input = rex.line + save->rs_u.pos.col; + } else { + rex.input = save->rs_u.ptr; + } gap->ga_len = save->rs_len; } -/* - * Return TRUE if current position is equal to saved position. - */ -static int reg_save_equal(regsave_T *save) +// Return true if current position is equal to saved position. +static bool reg_save_equal(const regsave_T *save) + FUNC_ATTR_NONNULL_ALL { - if (REG_MULTI) - return reglnum == save->rs_u.pos.lnum - && reginput == regline + save->rs_u.pos.col; - return reginput == save->rs_u.ptr; + if (REG_MULTI) { + return rex.lnum == save->rs_u.pos.lnum + && rex.input == rex.line + save->rs_u.pos.col; + } + return rex.input == save->rs_u.ptr; } /* @@ -5708,14 +5745,14 @@ static int reg_save_equal(regsave_T *save) static void save_se_multi(save_se_T *savep, lpos_T *posp) { savep->se_u.pos = *posp; - posp->lnum = reglnum; - posp->col = (colnr_T)(reginput - regline); + posp->lnum = rex.lnum; + posp->col = (colnr_T)(rex.input - rex.line); } static void save_se_one(save_se_T *savep, char_u **pp) { savep->se_u.ptr = *pp; - *pp = reginput; + *pp = rex.input; } /* @@ -5750,17 +5787,17 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e for (;; ) { /* Since getting one line may invalidate the other, need to make copy. * Slow! */ - if (regline != reg_tofree) { - len = (int)STRLEN(regline); + if (rex.line != reg_tofree) { + len = (int)STRLEN(rex.line); if (reg_tofree == NULL || len >= (int)reg_tofreelen) { len += 50; /* get some extra */ xfree(reg_tofree); reg_tofree = xmalloc(len); reg_tofreelen = len; } - STRCPY(reg_tofree, regline); - reginput = reg_tofree + (reginput - regline); - regline = reg_tofree; + STRCPY(reg_tofree, rex.line); + rex.input = reg_tofree + (rex.input - rex.line); + rex.line = reg_tofree; } /* Get the line to compare with. */ @@ -5772,14 +5809,16 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e else len = (int)STRLEN(p + ccol); - if (cstrncmp(p + ccol, reginput, &len) != 0) - return RA_NOMATCH; /* doesn't match */ - if (bytelen != NULL) + if (cstrncmp(p + ccol, rex.input, &len) != 0) { + return RA_NOMATCH; // doesn't match + } + if (bytelen != NULL) { *bytelen += len; + } if (clnum == end_lnum) { break; // match and at end! } - if (reglnum >= rex.reg_maxline) { + if (rex.lnum >= rex.reg_maxline) { return RA_NOMATCH; // text too short } @@ -5793,8 +5832,8 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e return RA_FAIL; } - /* found a match! Note that regline may now point to a copy of the line, - * that should not matter. */ + // found a match! Note that rex.line may now point to a copy of the line, + // that should not matter. return RA_MATCH; } @@ -6477,7 +6516,7 @@ char_u *regtilde(char_u *source, int magic) return newsub; } -static int can_f_submatch = FALSE; /* TRUE when submatch() can be used */ +static bool can_f_submatch = false; // true when submatch() can be used // These pointers are used for reg_submatch(). Needed for when the // substitution string is an expression that contains a call to substitute() @@ -6534,11 +6573,11 @@ static void clear_submatch_list(staticList10_T *sl) /// vim_regsub() - perform substitutions after a vim_regexec() or /// vim_regexec_multi() match. /// -/// If "copy" is TRUE really copy into "dest". -/// If "copy" is FALSE nothing is copied, this is just to find out the length +/// If "copy" is true really copy into "dest". +/// If "copy" is false nothing is copied, this is just to find out the length /// of the result. /// -/// If "backslash" is TRUE, a backslash will be removed later, need to double +/// If "backslash" is true, a backslash will be removed later, need to double /// them to keep them, and insert a backslash before a CR to avoid it being /// replaced with a line break later. /// @@ -6630,8 +6669,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, if (expr != NULL || (source[0] == '\\' && source[1] == '=')) { // To make sure that the length doesn't change between checking the // length and copying the string, and to speed up things, the - // resulting string is saved from the call with "copy" == FALSE to the - // call with "copy" == TRUE. + // resulting string is saved from the call with "copy" == false to the + // call with "copy" == true. if (copy) { if (eval_result != NULL) { STRCPY(dest, eval_result); @@ -6639,7 +6678,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, XFREE_CLEAR(eval_result); } } else { - int prev_can_f_submatch = can_f_submatch; + const bool prev_can_f_submatch = can_f_submatch; regsubmatch_T rsm_save; xfree(eval_result); @@ -6669,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); } @@ -6700,7 +6739,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } if (eval_result != NULL) { - int had_backslash = FALSE; + int had_backslash = false; for (s = eval_result; *s != NUL; MB_PTR_ADV(s)) { // Change NL to CR, so that it becomes a line break, @@ -6778,22 +6817,24 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } if (c == '\\' && *src != NUL) { - /* Check for abbreviations -- webb */ + // Check for abbreviations -- webb switch (*src) { case 'r': c = CAR; ++src; break; case 'n': c = NL; ++src; break; case 't': c = TAB; ++src; break; - /* Oh no! \e already has meaning in subst pat :-( */ - /* case 'e': c = ESC; ++src; break; */ + // Oh no! \e already has meaning in subst pat :-( + // case 'e': c = ESC; ++src; break; case 'b': c = Ctrl_H; ++src; break; - /* If "backslash" is TRUE the backslash will be removed - * later. Used to insert a literal CR. */ - default: if (backslash) { - if (copy) + // If "backslash" is true the backslash will be removed + // later. Used to insert a literal CR. + default: + if (backslash) { + if (copy) { *dst = '\\'; - ++dst; - } + } + dst++; + } c = *src++; } } else { @@ -6871,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 { @@ -7163,8 +7204,10 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) regexp_engine = AUTOMATIC_ENGINE; } } +#ifdef REGEXP_DEBUG bt_regengine.expr = expr; nfa_regengine.expr = expr; +#endif // reg_iswordc() uses rex.reg_buf rex.reg_buf = curbuf; @@ -7245,24 +7288,33 @@ static void report_re_switch(char_u *pat) /// @param col the column to start looking for match /// @param nl /// -/// @return TRUE if there is a match, FALSE if not. -static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, - bool nl) +/// @return true if there is a match, false if not. +static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, + bool nl) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; + // Cannot use the same prog recursively, it contains state. + if (rmp->regprog->re_in_use) { + EMSG(_(e_recursive)); + return false; + } + rmp->regprog->re_in_use = true; + if (rex_in_use) { // Being called recursively, save the state. rex_save = rex; } rex_in_use = true; + rex.reg_startp = NULL; rex.reg_endp = NULL; rex.reg_startpos = NULL; rex.reg_endpos = NULL; int result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl); + rmp->regprog->re_in_use = false; // NFA engine aborted because it's very slow, use backtracking engine instead. if (rmp->regprog->re_engine == AUTOMATIC_ENGINE @@ -7276,7 +7328,9 @@ static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, report_re_switch(pat); rmp->regprog = vim_regcomp(pat, re_flags); if (rmp->regprog != NULL) { + rmp->regprog->re_in_use = true; result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl); + rmp->regprog->re_in_use = false; } xfree(pat); @@ -7292,27 +7346,27 @@ static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, } // Note: "*prog" may be freed and changed. -// Return TRUE if there is a match, FALSE if not. -int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, +// Return true if there is a match, false if not. +bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, colnr_T col) { regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case }; - int r = vim_regexec_string(®match, line, col, false); + bool r = vim_regexec_string(®match, line, col, false); *prog = regmatch.regprog; return r; } // Note: "rmp->regprog" may be freed and changed. -// Return TRUE if there is a match, FALSE if not. -int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col) +// Return true if there is a match, false if not. +bool vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col) { return vim_regexec_string(rmp, line, col, false); } // Like vim_regexec(), but consider a "\n" in "line" to be a line break. // Note: "rmp->regprog" may be freed and changed. -// Return TRUE if there is a match, FALSE if not. -int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) +// Return true if there is a match, false if not. +bool vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) { return vim_regexec_string(rmp, line, col, true); } @@ -7333,10 +7387,18 @@ 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; + // Cannot use the same prog recursively, it contains state. + if (rmp->regprog->re_in_use) { + EMSG(_(e_recursive)); + return false; + } + rmp->regprog->re_in_use = true; + if (rex_in_use) { // Being called recursively, save the state. rex_save = rex; @@ -7345,6 +7407,7 @@ long vim_regexec_multi( int result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out); + rmp->regprog->re_in_use = false; // NFA engine aborted because it's very slow, use backtracking engine instead. if (rmp->regprog->re_engine == AUTOMATIC_ENGINE @@ -7363,8 +7426,10 @@ long vim_regexec_multi( reg_do_extmatch = 0; if (rmp->regprog != NULL) { + rmp->regprog->re_in_use = true; result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out); + rmp->regprog->re_in_use = false; } xfree(pat); diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 116bfee91e..a729a91555 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -72,6 +72,7 @@ struct regprog { unsigned regflags; unsigned re_engine; ///< Automatic, backtracking or NFA engine. unsigned re_flags; ///< Second argument for vim_regcomp(). + bool re_in_use; ///< prog is being executed }; /* @@ -84,7 +85,8 @@ typedef struct { regengine_T *engine; unsigned regflags; unsigned re_engine; - unsigned re_flags; ///< Second argument for vim_regcomp(). + unsigned re_flags; + bool re_in_use; int regstart; char_u reganch; @@ -114,7 +116,8 @@ typedef struct { regengine_T *engine; unsigned regflags; unsigned re_engine; - unsigned re_flags; ///< Second argument for vim_regcomp(). + unsigned re_flags; + bool re_in_use; nfa_state_T *start; // points into state[] diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 387732fdee..7cd1ae93d2 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -230,7 +230,10 @@ enum { NFA_CLASS_TAB, NFA_CLASS_RETURN, NFA_CLASS_BACKSPACE, - NFA_CLASS_ESCAPE + NFA_CLASS_ESCAPE, + NFA_CLASS_IDENT, + NFA_CLASS_KEYWORD, + NFA_CLASS_FNAME, }; /* Keep in sync with classchars. */ @@ -267,9 +270,9 @@ struct Frag { typedef struct Frag Frag_T; typedef struct { - int in_use; /* number of subexpr with useful info */ + int in_use; ///< number of subexpr with useful info - /* When REG_MULTI is TRUE list.multi is used, otherwise list.line. */ + // When REG_MULTI is true list.multi is used, otherwise list.line. union { struct multipos { linenr_T start_lnum; @@ -310,48 +313,27 @@ typedef struct { regsubs_T subs; /* submatch info, only party used */ } nfa_thread_T; -/* nfa_list_T contains the alternative NFA execution states. */ +// nfa_list_T contains the alternative NFA execution states. typedef struct { - nfa_thread_T *t; /* allocated array of states */ - int n; /* nr of states currently in "t" */ - int len; /* max nr of states in "t" */ - int id; /* ID of the list */ - int has_pim; /* TRUE when any state has a PIM */ + nfa_thread_T *t; ///< allocated array of states + int n; ///< nr of states currently in "t" + int len; ///< max nr of states in "t" + int id; ///< ID of the list + int has_pim; ///< true when any state has a PIM } nfa_list_T; -/// re_flags passed to nfa_regcomp(). -static int nfa_re_flags; - -/* NFA regexp \ze operator encountered. */ -static int nfa_has_zend; - -/* NFA regexp \1 .. \9 encountered. */ -static int nfa_has_backref; - -/* NFA regexp has \z( ), set zsubexpr. */ -static int nfa_has_zsubexpr; - -/* Number of sub expressions actually being used during execution. 1 if only - * the whole match (subexpr 0) is used. */ -static int nfa_nsubexpr; - -static int *post_start; /* holds the postfix form of r.e. */ +// Variables only used in nfa_regcomp() and descendants. +static int nfa_re_flags; ///< re_flags passed to nfa_regcomp(). +static int *post_start; ///< holds the postfix form of r.e. static int *post_end; static int *post_ptr; -static int nstate; /* Number of states in the NFA. Also used when - * executing. */ -static int istate; /* Index in the state vector, used in alloc_state() */ +static int nstate; ///< Number of states in the NFA. Also used when executing. +static int istate; ///< Index in the state vector, used in alloc_state() /* If not NULL match must end at this position */ static save_se_T *nfa_endp = NULL; -/* listid is global, so that it increases on recursive calls to - * nfa_regmatch(), which means we don't have to clear the lastlist field of - * all the states. */ -static int nfa_listid; -static int nfa_alt_listid; - /* 0 for first call to nfa_regmatch(), 1 for recursive call. */ static int nfa_ll_index = 0; @@ -395,8 +377,8 @@ nfa_regcomp_start ( post_start = (int *)xmalloc(postfix_size); post_ptr = post_start; post_end = post_start + nstate_max; - nfa_has_zend = FALSE; - nfa_has_backref = FALSE; + rex.nfa_has_zend = false; + rex.nfa_has_backref = false; /* shared with BT engine */ regcomp_start(expr, re_flags); @@ -605,12 +587,10 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) # define CLASS_o9 0x02 # define CLASS_underscore 0x01 - int newl = FALSE; char_u *p; int config = 0; - if (extra_newl == TRUE) - newl = TRUE; + bool newl = extra_newl == true; if (*end != ']') return FAIL; @@ -655,13 +635,13 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) } p += 3; } else if (p + 1 < end && *p == '\\' && *(p + 1) == 'n') { - newl = TRUE; + newl = true; p += 2; } else if (*p == '_') { config |= CLASS_underscore; p++; } else if (*p == '\n') { - newl = TRUE; + newl = true; p++; } else return FAIL; @@ -670,8 +650,9 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) if (p != end) return FAIL; - if (newl == TRUE) + if (newl == true) { extra_newl = NFA_ADD_NL; + } switch (config) { case CLASS_o9: @@ -1188,7 +1169,7 @@ static int nfa_regatom(void) case Magic('$'): EMIT(NFA_EOL); - had_eol = TRUE; + had_eol = true; break; case Magic('<'): @@ -1210,7 +1191,7 @@ static int nfa_regatom(void) } if (c == '$') { /* "\_$" is end-of-line */ EMIT(NFA_EOL); - had_eol = TRUE; + had_eol = true; break; } @@ -1257,7 +1238,7 @@ static int nfa_regatom(void) if (p == NULL) { if (extra == NFA_ADD_NL) { EMSGN(_(e_ill_char_class), c); - rc_did_emsg = TRUE; + rc_did_emsg = true; return FAIL; } IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c); @@ -1346,7 +1327,7 @@ static int nfa_regatom(void) return FAIL; } EMIT(NFA_BACKREF1 + refnum); - nfa_has_backref = true; + rex.nfa_has_backref = true; } break; @@ -1361,7 +1342,7 @@ static int nfa_regatom(void) break; case 'e': EMIT(NFA_ZEND); - nfa_has_zend = true; + rex.nfa_has_zend = true; if (!re_mult_next("\\zs")) { return false; } @@ -1380,8 +1361,8 @@ static int nfa_regatom(void) EMSG_RET_FAIL(_(e_z1_not_allowed)); } EMIT(NFA_ZREF1 + (no_Magic(c) - '1')); - /* No need to set nfa_has_backref, the sub-matches don't - * change when \z1 .. \z9 matches or not. */ + // No need to set rex.nfa_has_backref, the sub-matches don't + // change when \z1 .. \z9 matches or not. re_has_z = REX_USE; break; case '(': @@ -1598,12 +1579,12 @@ collection: EMIT(NFA_CONCAT); MB_PTR_ADV(regparse); } - /* Emit the OR branches for each character in the [] */ - emit_range = FALSE; + // Emit the OR branches for each character in the [] + emit_range = false; while (regparse < endp) { oldstartc = startc; startc = -1; - got_coll_char = FALSE; + got_coll_char = false; if (*regparse == '[') { /* Check for [: :], [= =], [. .] */ equiclass = collclass = 0; @@ -1665,6 +1646,15 @@ collection: case CLASS_ESCAPE: EMIT(NFA_CLASS_ESCAPE); break; + case CLASS_IDENT: + EMIT(NFA_CLASS_IDENT); + break; + case CLASS_KEYWORD: + EMIT(NFA_CLASS_KEYWORD); + break; + case CLASS_FNAME: + EMIT(NFA_CLASS_FNAME); + break; } EMIT(NFA_CONCAT); continue; @@ -1684,7 +1674,7 @@ collection: /* Try a range like 'a-x' or '\t-z'. Also allows '-' as a * start character. */ if (*regparse == '-' && oldstartc != -1) { - emit_range = TRUE; + emit_range = true; startc = oldstartc; MB_PTR_ADV(regparse); continue; // reading the end of the range @@ -1764,7 +1754,7 @@ collection: EMIT(NFA_CONCAT); } } - emit_range = FALSE; + emit_range = false; startc = -1; } else { /* This char (startc) is not part of a range. Just @@ -1781,10 +1771,11 @@ collection: if (!negated) extra = NFA_ADD_NL; } else { - if (got_coll_char == TRUE && startc == 0) + if (got_coll_char == true && startc == 0) { EMIT(0x0a); - else + } else { EMIT(startc); + } EMIT(NFA_CONCAT); } } @@ -1802,13 +1793,14 @@ collection: regparse = endp; MB_PTR_ADV(regparse); - /* Mark end of the collection. */ - if (negated == TRUE) + // Mark end of the collection. + if (negated == true) { EMIT(NFA_END_NEG_COLL); - else + } else { EMIT(NFA_END_COLL); + } - /* \_[] also matches \n but it's not negated */ + // \_[] also matches \n but it's not negated if (extra == NFA_ADD_NL) { EMIT(reg_string ? NL : NFA_NEWL); EMIT(NFA_OR); @@ -1877,7 +1869,7 @@ static int nfa_regpiece(void) int op; int ret; long minval, maxval; - int greedy = TRUE; /* Braces are prefixed with '-' ? */ + bool greedy = true; // Braces are prefixed with '-' ? parse_state_T old_state; parse_state_T new_state; int64_t c2; @@ -1977,11 +1969,11 @@ static int nfa_regpiece(void) * parenthesis have the same id */ - greedy = TRUE; + greedy = true; c2 = peekchr(); if (c2 == '-' || c2 == Magic('-')) { skipchr(); - greedy = FALSE; + greedy = false; } if (!read_limits(&minval, &maxval)) EMSG_RET_FAIL(_("E870: (NFA regexp) Error reading repetition limits")); @@ -2019,7 +2011,7 @@ static int nfa_regpiece(void) /* Save parse state after the repeated atom and the \{} */ save_parse_state(&new_state); - quest = (greedy == TRUE ? NFA_QUEST : NFA_QUEST_NONGREEDY); + quest = (greedy == true ? NFA_QUEST : NFA_QUEST_NONGREEDY); for (i = 0; i < maxval; i++) { /* Goto beginning of the repeated atom */ restore_parse_state(&old_state); @@ -2073,8 +2065,8 @@ static int nfa_regpiece(void) */ static int nfa_regconcat(void) { - int cont = TRUE; - int first = TRUE; + bool cont = true; + bool first = true; while (cont) { switch (peekchr()) { @@ -2082,7 +2074,7 @@ static int nfa_regconcat(void) case Magic('|'): case Magic('&'): case Magic(')'): - cont = FALSE; + cont = false; break; case Magic('Z'): @@ -2119,12 +2111,14 @@ static int nfa_regconcat(void) break; default: - if (nfa_regpiece() == FAIL) + if (nfa_regpiece() == FAIL) { return FAIL; - if (first == FALSE) + } + if (first == false) { EMIT(NFA_CONCAT); - else - first = FALSE; + } else { + first = false; + } break; } } @@ -2230,15 +2224,14 @@ nfa_reg ( else EMSG_RET_FAIL(_("E873: (NFA regexp) proper termination error")); } - /* - * Here we set the flag allowing back references to this set of - * parentheses. - */ + // Here we set the flag allowing back references to this set of + // parentheses. if (paren == REG_PAREN) { - had_endbrace[parno] = TRUE; /* have seen the close paren */ + had_endbrace[parno] = true; // have seen the close paren EMIT(NFA_MOPEN + parno); - } else if (paren == REG_ZPAREN) + } else if (paren == REG_ZPAREN) { EMIT(NFA_ZOPEN + parno); + } return OK; } @@ -2248,10 +2241,10 @@ static char_u code[50]; static void nfa_set_code(int c) { - int addnl = FALSE; + int addnl = false; if (c >= NFA_FIRST_NL && c <= NFA_LAST_NL) { - addnl = TRUE; + addnl = true; c -= NFA_ADD_NL; } @@ -2426,6 +2419,9 @@ static void nfa_set_code(int c) case NFA_CLASS_RETURN: STRCPY(code, "NFA_CLASS_RETURN"); break; case NFA_CLASS_BACKSPACE: STRCPY(code, "NFA_CLASS_BACKSPACE"); break; case NFA_CLASS_ESCAPE: STRCPY(code, "NFA_CLASS_ESCAPE"); break; + case NFA_CLASS_IDENT: STRCPY(code, "NFA_CLASS_IDENT"); break; + case NFA_CLASS_KEYWORD: STRCPY(code, "NFA_CLASS_KEYWORD"); break; + case NFA_CLASS_FNAME: STRCPY(code, "NFA_CLASS_FNAME"); break; case NFA_ANY: STRCPY(code, "NFA_ANY"); break; case NFA_IDENT: STRCPY(code, "NFA_IDENT"); break; @@ -2464,9 +2460,9 @@ static void nfa_set_code(int c) code[5] = c; } - if (addnl == TRUE) + if (addnl == true) { STRCAT(code, " + NEWLINE "); - + } } static FILE *log_fd; @@ -2848,11 +2844,8 @@ static int nfa_max_width(nfa_state_T *startstate, int depth) case NFA_UPPER_IC: case NFA_NUPPER_IC: case NFA_ANY_COMPOSING: - /* possibly non-ascii */ - if (has_mbyte) - len += 3; - else - ++len; + // possibly non-ascii + len += 3; break; case NFA_START_INVISIBLE: @@ -3019,12 +3012,12 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) for (p = postfix; p < end; ++p) { switch (*p) { case NFA_CONCAT: - /* Concatenation. - * Pay attention: this operator does not exist in the r.e. itself - * (it is implicit, really). It is added when r.e. is translated - * to postfix form in re2post(). */ - if (nfa_calc_size == TRUE) { - /* nstate += 0; */ + // Concatenation. + // Pay attention: this operator does not exist in the r.e. itself + // (it is implicit, really). It is added when r.e. is translated + // to postfix form in re2post(). + if (nfa_calc_size == true) { + // nstate += 0; break; } e2 = POP(); @@ -3034,8 +3027,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_OR: - /* Alternation */ - if (nfa_calc_size == TRUE) { + // Alternation + if (nfa_calc_size == true) { nstate++; break; } @@ -3048,8 +3041,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_STAR: - /* Zero or more, prefer more */ - if (nfa_calc_size == TRUE) { + // Zero or more, prefer more + if (nfa_calc_size == true) { nstate++; break; } @@ -3062,8 +3055,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_STAR_NONGREEDY: - /* Zero or more, prefer zero */ - if (nfa_calc_size == TRUE) { + // Zero or more, prefer zero + if (nfa_calc_size == true) { nstate++; break; } @@ -3076,8 +3069,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_QUEST: - /* one or zero atoms=> greedy match */ - if (nfa_calc_size == TRUE) { + // one or zero atoms=> greedy match + if (nfa_calc_size == true) { nstate++; break; } @@ -3089,8 +3082,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_QUEST_NONGREEDY: - /* zero or one atoms => non-greedy match */ - if (nfa_calc_size == TRUE) { + // zero or one atoms => non-greedy match + if (nfa_calc_size == true) { nstate++; break; } @@ -3106,7 +3099,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) /* On the stack is the sequence starting with NFA_START_COLL or * NFA_START_NEG_COLL and all possible characters. Patch it to * add the output to the start. */ - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate++; break; } @@ -3120,10 +3113,10 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_RANGE: - /* Before this are two characters, the low and high end of a - * range. Turn them into two states with MIN and MAX. */ - if (nfa_calc_size == TRUE) { - /* nstate += 0; */ + // Before this are two characters, the low and high end of a + // range. Turn them into two states with MIN and MAX. + if (nfa_calc_size == true) { + // nstate += 0; break; } e2 = POP(); @@ -3137,8 +3130,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) break; case NFA_EMPTY: - /* 0-length, used in a repetition with max/min count of 0 */ - if (nfa_calc_size == TRUE) { + // 0-length, used in a repetition with max/min count of 0 + if (nfa_calc_size == true) { nstate++; break; } @@ -3152,20 +3145,19 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) { int n; - /* \%[abc] implemented as: - * NFA_SPLIT - * +-CHAR(a) - * | +-NFA_SPLIT - * | +-CHAR(b) - * | | +-NFA_SPLIT - * | | +-CHAR(c) - * | | | +-next - * | | +- next - * | +- next - * +- next - */ - n = *++p; /* get number of characters */ - if (nfa_calc_size == TRUE) { + // \%[abc] implemented as: + // NFA_SPLIT + // +-CHAR(a) + // | +-NFA_SPLIT + // | +-CHAR(b) + // | | +-NFA_SPLIT + // | | +-CHAR(c) + // | | | +-next + // | | +- next + // | +- next + // +- next + n = *++p; // get number of characters + if (nfa_calc_size == true) { nstate += n; break; } @@ -3235,7 +3227,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) * Surrounds the preceding atom with START_INVISIBLE and * END_INVISIBLE, similarly to MOPEN. */ - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate += pattern ? 4 : 2; break; } @@ -3297,8 +3289,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) case NFA_ZOPEN7: case NFA_ZOPEN8: case NFA_ZOPEN9: - case NFA_NOPEN: /* \%( \) "Invisible Submatch" */ - if (nfa_calc_size == TRUE) { + case NFA_NOPEN: // \%( \) "Invisible Submatch" + if (nfa_calc_size == true) { nstate += 2; break; } @@ -3376,7 +3368,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) case NFA_ZREF7: case NFA_ZREF8: case NFA_ZREF9: - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate += 2; break; } @@ -3405,7 +3397,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) { int n = *++p; /* lnum, col or mark name */ - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate += 1; break; } @@ -3420,8 +3412,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) case NFA_ZSTART: case NFA_ZEND: default: - /* Operands */ - if (nfa_calc_size == TRUE) { + // Operands + if (nfa_calc_size == true) { nstate++; break; } @@ -3435,7 +3427,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) } /* for(p = postfix; *p; ++p) */ - if (nfa_calc_size == TRUE) { + if (nfa_calc_size == true) { nstate++; goto theend; /* Return value when counting size is ignored anyway */ } @@ -3489,11 +3481,11 @@ static void nfa_postprocess(nfa_regprog_T *prog) || c == NFA_START_INVISIBLE_BEFORE_NEG) { int directly; - /* Do it directly when what follows is possibly the end of the - * match. */ - if (match_follows(prog->state[i].out1->out, 0)) - directly = TRUE; - else { + // Do it directly when what follows is possibly the end of the + // match. + if (match_follows(prog->state[i].out1->out, 0)) { + directly = true; + } else { int ch_invisible = failure_chance(prog->state[i].out, 0); int ch_follows = failure_chance(prog->state[i].out1->out, 0); @@ -3505,10 +3497,11 @@ static void nfa_postprocess(nfa_regprog_T *prog) * unbounded, always prefer what follows then, * unless what follows will always match. * Otherwise strongly prefer what follows. */ - if (prog->state[i].val <= 0 && ch_follows > 0) - directly = FALSE; - else + if (prog->state[i].val <= 0 && ch_follows > 0) { + directly = false; + } else { directly = ch_follows * 10 < ch_invisible; + } } else { /* normal invisible, first do the one with the * highest failure chance */ @@ -3537,8 +3530,9 @@ static void nfa_postprocess(nfa_regprog_T *prog) static void log_subsexpr(regsubs_T *subs) { log_subexpr(&subs->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { log_subexpr(&subs->synt); + } } static void log_subexpr(regsub_T *sub) @@ -3564,15 +3558,17 @@ static void log_subexpr(regsub_T *sub) } } -static char *pim_info(nfa_pim_T *pim) +static char *pim_info(const nfa_pim_T *pim) { static char buf[30]; - if (pim == NULL || pim->result == NFA_PIM_UNUSED) + if (pim == NULL || pim->result == NFA_PIM_UNUSED) { buf[0] = NUL; - else { - sprintf(buf, " PIM col %d", REG_MULTI ? (int)pim->end.pos.col - : (int)(pim->end.ptr - reginput)); + } else { + snprintf(buf, sizeof(buf), " PIM col %d", + REG_MULTI + ? (int)pim->end.pos.col + : (int)(pim->end.ptr - rex.input)); } return buf; } @@ -3591,19 +3587,21 @@ static void copy_pim(nfa_pim_T *to, nfa_pim_T *from) to->result = from->result; to->state = from->state; copy_sub(&to->subs.norm, &from->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&to->subs.synt, &from->subs.synt); + } to->end = from->end; } static void clear_sub(regsub_T *sub) { - if (REG_MULTI) - /* Use 0xff to set lnum to -1 */ + if (REG_MULTI) { + // Use 0xff to set lnum to -1 memset(sub->list.multi, 0xff, - sizeof(struct multipos) * nfa_nsubexpr); - else - memset(sub->list.line, 0, sizeof(struct linepos) * nfa_nsubexpr); + sizeof(struct multipos) * rex.nfa_nsubexpr); + } else { + memset(sub->list.line, 0, sizeof(struct linepos) * rex.nfa_nsubexpr); + } sub->in_use = 0; } @@ -3651,7 +3649,7 @@ static void copy_sub_off(regsub_T *to, regsub_T *from) */ static void copy_ze_off(regsub_T *to, regsub_T *from) { - if (nfa_has_zend) { + if (rex.nfa_has_zend) { if (REG_MULTI) { if (from->list.multi[0].end_lnum >= 0){ to->list.multi[0].end_lnum = from->list.multi[0].end_lnum; @@ -3664,9 +3662,9 @@ static void copy_ze_off(regsub_T *to, regsub_T *from) } } -// Return TRUE if "sub1" and "sub2" have the same start positions. +// Return true if "sub1" and "sub2" have the same start positions. // When using back-references also check the end position. -static int sub_equal(regsub_T *sub1, regsub_T *sub2) +static bool sub_equal(regsub_T *sub1, regsub_T *sub2) { int i; int todo; @@ -3677,22 +3675,25 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2) todo = sub1->in_use > sub2->in_use ? sub1->in_use : sub2->in_use; if (REG_MULTI) { - for (i = 0; i < todo; ++i) { - if (i < sub1->in_use) + for (i = 0; i < todo; i++) { + if (i < sub1->in_use) { s1 = sub1->list.multi[i].start_lnum; - else + } else { s1 = -1; - if (i < sub2->in_use) + } + if (i < sub2->in_use) { s2 = sub2->list.multi[i].start_lnum; - else + } else { s2 = -1; - if (s1 != s2) - return FALSE; + } + if (s1 != s2) { + return false; + } if (s1 != -1 && sub1->list.multi[i].start_col - != sub2->list.multi[i].start_col) - return FALSE; - - if (nfa_has_backref) { + != sub2->list.multi[i].start_col) { + return false; + } + if (rex.nfa_has_backref) { if (i < sub1->in_use) { s1 = sub1->list.multi[i].end_lnum; } else { @@ -3704,28 +3705,30 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2) s2 = -1; } if (s1 != s2) { - return FALSE; + return false; } if (s1 != -1 && sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) { - return FALSE; + return false; } } } } else { - for (i = 0; i < todo; ++i) { - if (i < sub1->in_use) + for (i = 0; i < todo; i++) { + if (i < sub1->in_use) { sp1 = sub1->list.line[i].start; - else + } else { sp1 = NULL; - if (i < sub2->in_use) + } + if (i < sub2->in_use) { sp2 = sub2->list.line[i].start; - else + } else { sp2 = NULL; - if (sp1 != sp2) - return FALSE; - - if (nfa_has_backref) { + } + if (sp1 != sp2) { + return false; + } + if (rex.nfa_has_backref) { if (i < sub1->in_use) { sp1 = sub1->list.line[i].end; } else { @@ -3737,13 +3740,13 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2) sp2 = NULL; } if (sp1 != sp2) { - return FALSE; + return false; } } } } - return TRUE; + return true; } #ifdef REGEXP_DEBUG @@ -3754,83 +3757,81 @@ static void report_state(char *action, nfa_pim_T *pim) { int col; - if (sub->in_use <= 0) + if (sub->in_use <= 0) { col = -1; - else if (REG_MULTI) + } else if (REG_MULTI) { col = sub->list.multi[0].start_col; - else - col = (int)(sub->list.line[0].start - regline); + } else { + col = (int)(sub->list.line[0].start - rex.line); + } nfa_set_code(state->c); fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n", - action, abs(state->id), lid, state->c, code, col, - pim_info(pim)); + action, abs(state->id), lid, state->c, code, col, + pim_info(pim)); } #endif -/* - * Return TRUE if the same state is already in list "l" with the same - * positions as "subs". - */ -static int -has_state_with_pos ( - nfa_list_T *l, /* runtime state list */ - nfa_state_T *state, /* state to update */ - regsubs_T *subs, /* pointers to subexpressions */ - nfa_pim_T *pim /* postponed match or NULL */ +// Return true if the same state is already in list "l" with the same +// positions as "subs". +static bool has_state_with_pos( + nfa_list_T *l, // runtime state list + nfa_state_T *state, // state to update + regsubs_T *subs, // pointers to subexpressions + nfa_pim_T *pim // postponed match or NULL ) + FUNC_ATTR_NONNULL_ARG(1, 2, 3) { - nfa_thread_T *thread; - int i; - - for (i = 0; i < l->n; ++i) { - thread = &l->t[i]; + for (int i = 0; i < l->n; i++) { + nfa_thread_T *thread = &l->t[i]; if (thread->state->id == state->id && sub_equal(&thread->subs.norm, &subs->norm) - && (!nfa_has_zsubexpr + && (!rex.nfa_has_zsubexpr || sub_equal(&thread->subs.synt, &subs->synt)) - && pim_equal(&thread->pim, pim)) - return TRUE; + && pim_equal(&thread->pim, pim)) { + return true; + } } - return FALSE; + return false; } -/* - * Return TRUE if "one" and "two" are equal. That includes when both are not - * set. - */ -static int pim_equal(nfa_pim_T *one, nfa_pim_T *two) +// Return true if "one" and "two" are equal. That includes when both are not +// set. +static bool pim_equal(const nfa_pim_T *one, const nfa_pim_T *two) { - int one_unused = (one == NULL || one->result == NFA_PIM_UNUSED); - int two_unused = (two == NULL || two->result == NFA_PIM_UNUSED); + const bool one_unused = (one == NULL || one->result == NFA_PIM_UNUSED); + const bool two_unused = (two == NULL || two->result == NFA_PIM_UNUSED); - if (one_unused) - /* one is unused: equal when two is also unused */ + if (one_unused) { + // one is unused: equal when two is also unused return two_unused; - if (two_unused) - /* one is used and two is not: not equal */ - return FALSE; - /* compare the state id */ - if (one->state->id != two->state->id) - return FALSE; - /* compare the position */ - if (REG_MULTI) + } + if (two_unused) { + // one is used and two is not: not equal + return false; + } + // compare the state id + if (one->state->id != two->state->id) { + return false; + } + // compare the position + if (REG_MULTI) { return one->end.pos.lnum == two->end.pos.lnum && one->end.pos.col == two->end.pos.col; + } return one->end.ptr == two->end.ptr; } -/* - * Return TRUE if "state" leads to a NFA_MATCH without advancing the input. - */ -static int match_follows(nfa_state_T *startstate, int depth) +// Return true if "state" leads to a NFA_MATCH without advancing the input. +static bool match_follows(const nfa_state_T *startstate, int depth) + FUNC_ATTR_NONNULL_ALL { - nfa_state_T *state = startstate; - - /* avoid too much recursion */ - if (depth > 10) - return FALSE; + const nfa_state_T *state = startstate; + // avoid too much recursion + if (depth > 10) { + return false; + } while (state != NULL) { switch (state->c) { case NFA_MATCH: @@ -3838,7 +3839,7 @@ static int match_follows(nfa_state_T *startstate, int depth) case NFA_END_INVISIBLE: case NFA_END_INVISIBLE_NEG: case NFA_END_PATTERN: - return TRUE; + return true; case NFA_SPLIT: return match_follows(state->out, depth + 1) @@ -3892,39 +3893,38 @@ static int match_follows(nfa_state_T *startstate, int depth) case NFA_START_COLL: case NFA_START_NEG_COLL: case NFA_NEWL: - /* state will advance input */ - return FALSE; + // state will advance input + return false; default: - if (state->c > 0) - /* state will advance input */ - return FALSE; - - /* Others: zero-width or possibly zero-width, might still find - * a match at the same position, keep looking. */ + if (state->c > 0) { + // state will advance input + return false; + } + // Others: zero-width or possibly zero-width, might still find + // a match at the same position, keep looking. break; } state = state->out; } - return FALSE; + return false; } -/* - * Return TRUE if "state" is already in list "l". - */ -static int -state_in_list ( - nfa_list_T *l, /* runtime state list */ - nfa_state_T *state, /* state to update */ - regsubs_T *subs /* pointers to subexpressions */ +// Return true if "state" is already in list "l". +static bool state_in_list( + nfa_list_T *l, // runtime state list + nfa_state_T *state, // state to update + regsubs_T *subs // pointers to subexpressions ) + FUNC_ATTR_NONNULL_ALL { if (state->lastlist[nfa_ll_index] == l->id) { - if (!nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) - return TRUE; + if (!rex.nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) { + return true; + } } - return FALSE; + return false; } // Offset used for "off" by addstate_here(). @@ -3943,10 +3943,10 @@ static regsubs_T *addstate( { int subidx; int off = off_arg; - int add_here = FALSE; + int add_here = false; int listindex = 0; int k; - int found = FALSE; + int found = false; nfa_thread_T *thread; struct multipos save_multipos; int save_in_use; @@ -3956,7 +3956,7 @@ static regsubs_T *addstate( regsubs_T *subs = subs_arg; static regsubs_T temp_subs; #ifdef REGEXP_DEBUG - int did_print = FALSE; + int did_print = false; #endif static int depth = 0; @@ -4005,15 +4005,16 @@ static regsubs_T *addstate( case NFA_BOL: case NFA_BOF: - /* "^" won't match past end-of-line, don't bother trying. - * Except when at the end of the line, or when we are going to the - * next line for a look-behind match. */ - if (reginput > regline - && *reginput != NUL + // "^" won't match past end-of-line, don't bother trying. + // Except when at the end of the line, or when we are going to the + // next line for a look-behind match. + if (rex.input > rex.line + && *rex.input != NUL && (nfa_endp == NULL || !REG_MULTI - || reglnum == nfa_endp->se_u.pos.lnum)) + || rex.lnum == nfa_endp->se_u.pos.lnum)) { goto skip_add; + } FALLTHROUGH; case NFA_MOPEN1: @@ -4047,7 +4048,7 @@ static regsubs_T *addstate( * unless it is an MOPEN that is used for a backreference or * when there is a PIM. For NFA_MATCH check the position, * lower position is preferred. */ - if (!nfa_has_backref && pim == NULL && !l->has_pim + if (!rex.nfa_has_backref && pim == NULL && !l->has_pim && state->c != NFA_MATCH) { /* When called from addstate_here() do insert before @@ -4055,7 +4056,7 @@ static regsubs_T *addstate( if (add_here) { for (k = 0; k < l->n && k < listindex; ++k) { if (l->t[k].state->id == state->id) { - found = TRUE; + found = true; break; } } @@ -4092,11 +4093,12 @@ skip_add: return NULL; } if (subs != &temp_subs) { - /* "subs" may point into the current array, need to make a - * copy before it becomes invalid. */ + // "subs" may point into the current array, need to make a + // copy before it becomes invalid. copy_sub(&temp_subs.norm, &subs->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&temp_subs.synt, &subs->synt); + } subs = &temp_subs; } @@ -4113,14 +4115,15 @@ skip_add: thread->pim.result = NFA_PIM_UNUSED; else { copy_pim(&thread->pim, pim); - l->has_pim = TRUE; + l->has_pim = true; } copy_sub(&thread->subs.norm, &subs->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&thread->subs.synt, &subs->synt); + } #ifdef REGEXP_DEBUG report_state("Adding", &thread->subs.norm, state, l->id, pim); - did_print = TRUE; + did_print = true; #endif } @@ -4195,13 +4198,12 @@ skip_add: sub->in_use = subidx + 1; } if (off == -1) { - sub->list.multi[subidx].start_lnum = reglnum + 1; + sub->list.multi[subidx].start_lnum = rex.lnum + 1; sub->list.multi[subidx].start_col = 0; } else { - - sub->list.multi[subidx].start_lnum = reglnum; + sub->list.multi[subidx].start_lnum = rex.lnum; sub->list.multi[subidx].start_col = - (colnr_T)(reginput - regline + off); + (colnr_T)(rex.input - rex.line + off); } sub->list.multi[subidx].end_lnum = -1; } else { @@ -4216,7 +4218,7 @@ skip_add: } sub->in_use = subidx + 1; } - sub->list.line[subidx].start = reginput + off; + sub->list.line[subidx].start = rex.input + off; } subs = addstate(l, state->out, subs, pim, off_arg); @@ -4241,9 +4243,10 @@ skip_add: break; case NFA_MCLOSE: - if (nfa_has_zend && (REG_MULTI - ? subs->norm.list.multi[0].end_lnum >= 0 - : subs->norm.list.line[0].end != NULL)) { + if (rex.nfa_has_zend + && (REG_MULTI + ? subs->norm.list.multi[0].end_lnum >= 0 + : subs->norm.list.line[0].end != NULL)) { // Do not overwrite the position set by \ze. subs = addstate(l, state->out, subs, pim, off_arg); break; @@ -4288,18 +4291,18 @@ skip_add: if (REG_MULTI) { save_multipos = sub->list.multi[subidx]; if (off == -1) { - sub->list.multi[subidx].end_lnum = reglnum + 1; + sub->list.multi[subidx].end_lnum = rex.lnum + 1; sub->list.multi[subidx].end_col = 0; } else { - sub->list.multi[subidx].end_lnum = reglnum; + sub->list.multi[subidx].end_lnum = rex.lnum; sub->list.multi[subidx].end_col = - (colnr_T)(reginput - regline + off); + (colnr_T)(rex.input - rex.line + off); } /* avoid compiler warnings */ save_ptr = NULL; } else { save_ptr = sub->list.line[subidx].end; - sub->list.line[subidx].end = reginput + off; + sub->list.line[subidx].end = rex.input + off; // avoid compiler warnings memset(&save_multipos, 0, sizeof(save_multipos)); } @@ -4486,6 +4489,21 @@ static int check_char_class(int class, int c) return OK; } break; + case NFA_CLASS_IDENT: + if (vim_isIDc(c)) { + return OK; + } + break; + case NFA_CLASS_KEYWORD: + if (reg_iswordc(c)) { + return OK; + } + break; + case NFA_CLASS_FNAME: + if (vim_isfilec(c)) { + return OK; + } + break; default: // should not be here :P @@ -4497,7 +4515,7 @@ static int check_char_class(int class, int c) /* * Check for a match with subexpression "subidx". - * Return TRUE if it matches. + * Return true if it matches. */ static int match_backref ( @@ -4512,49 +4530,49 @@ match_backref ( retempty: /* backref was not set, match an empty string */ *bytelen = 0; - return TRUE; + return true; } if (REG_MULTI) { if (sub->list.multi[subidx].start_lnum < 0 || sub->list.multi[subidx].end_lnum < 0) goto retempty; - if (sub->list.multi[subidx].start_lnum == reglnum - && sub->list.multi[subidx].end_lnum == reglnum) { + if (sub->list.multi[subidx].start_lnum == rex.lnum + && sub->list.multi[subidx].end_lnum == rex.lnum) { len = sub->list.multi[subidx].end_col - sub->list.multi[subidx].start_col; - if (cstrncmp(regline + sub->list.multi[subidx].start_col, - reginput, &len) == 0) { + if (cstrncmp(rex.line + sub->list.multi[subidx].start_col, + rex.input, &len) == 0) { *bytelen = len; - return TRUE; + return true; } } else { - if (match_with_backref( - sub->list.multi[subidx].start_lnum, - sub->list.multi[subidx].start_col, - sub->list.multi[subidx].end_lnum, - sub->list.multi[subidx].end_col, - bytelen) == RA_MATCH) - return TRUE; + if (match_with_backref(sub->list.multi[subidx].start_lnum, + sub->list.multi[subidx].start_col, + sub->list.multi[subidx].end_lnum, + sub->list.multi[subidx].end_col, + bytelen) == RA_MATCH) { + return true; + } } } else { if (sub->list.line[subidx].start == NULL || sub->list.line[subidx].end == NULL) goto retempty; len = (int)(sub->list.line[subidx].end - sub->list.line[subidx].start); - if (cstrncmp(sub->list.line[subidx].start, reginput, &len) == 0) { + if (cstrncmp(sub->list.line[subidx].start, rex.input, &len) == 0) { *bytelen = len; - return TRUE; + return true; } } - return FALSE; + return false; } /* * Check for a match with \z subexpression "subidx". - * Return TRUE if it matches. + * Return true if it matches. */ static int match_zref ( @@ -4568,15 +4586,15 @@ match_zref ( if (re_extmatch_in == NULL || re_extmatch_in->matches[subidx] == NULL) { /* backref was not set, match an empty string */ *bytelen = 0; - return TRUE; + return true; } len = (int)STRLEN(re_extmatch_in->matches[subidx]); - if (cstrncmp(re_extmatch_in->matches[subidx], reginput, &len) == 0) { + if (cstrncmp(re_extmatch_in->matches[subidx], rex.input, &len) == 0) { *bytelen = len; - return TRUE; + return true; } - return FALSE; + return false; } /* @@ -4629,74 +4647,79 @@ static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos) static int recursive_regmatch( nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog, regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7) { - int save_reginput_col = (int)(reginput - regline); - int save_reglnum = reglnum; - int save_nfa_match = nfa_match; - int save_nfa_listid = nfa_listid; - save_se_T *save_nfa_endp = nfa_endp; + const int save_reginput_col = (int)(rex.input - rex.line); + const int save_reglnum = rex.lnum; + const int save_nfa_match = nfa_match; + const int save_nfa_listid = rex.nfa_listid; + save_se_T *const save_nfa_endp = nfa_endp; save_se_T endpos; save_se_T *endposp = NULL; - int result; - int need_restore = FALSE; + int need_restore = false; if (pim != NULL) { - /* start at the position where the postponed match was */ - if (REG_MULTI) - reginput = regline + pim->end.pos.col; - else - reginput = pim->end.ptr; + // start at the position where the postponed match was + if (REG_MULTI) { + rex.input = rex.line + pim->end.pos.col; + } else { + rex.input = pim->end.ptr; + } } if (state->c == NFA_START_INVISIBLE_BEFORE || state->c == NFA_START_INVISIBLE_BEFORE_FIRST || state->c == NFA_START_INVISIBLE_BEFORE_NEG || state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) { - /* The recursive match must end at the current position. When "pim" is - * not NULL it specifies the current position. */ + // The recursive match must end at the current position. When "pim" is + // not NULL it specifies the current position. endposp = &endpos; if (REG_MULTI) { if (pim == NULL) { - endpos.se_u.pos.col = (int)(reginput - regline); - endpos.se_u.pos.lnum = reglnum; - } else + endpos.se_u.pos.col = (int)(rex.input - rex.line); + endpos.se_u.pos.lnum = rex.lnum; + } else { endpos.se_u.pos = pim->end.pos; + } } else { - if (pim == NULL) - endpos.se_u.ptr = reginput; - else + if (pim == NULL) { + endpos.se_u.ptr = rex.input; + } else { endpos.se_u.ptr = pim->end.ptr; + } } - /* Go back the specified number of bytes, or as far as the - * start of the previous line, to try matching "\@<=" or - * not matching "\@<!". This is very inefficient, limit the number of - * bytes if possible. */ + // Go back the specified number of bytes, or as far as the + // start of the previous line, to try matching "\@<=" or + // not matching "\@<!". This is very inefficient, limit the number of + // bytes if possible. if (state->val <= 0) { if (REG_MULTI) { - regline = reg_getline(--reglnum); - if (regline == NULL) - /* can't go before the first line */ - regline = reg_getline(++reglnum); + rex.line = reg_getline(--rex.lnum); + if (rex.line == NULL) { + // can't go before the first line + rex.line = reg_getline(++rex.lnum); + } } - reginput = regline; + rex.input = rex.line; } else { - if (REG_MULTI && (int)(reginput - regline) < state->val) { - /* Not enough bytes in this line, go to end of - * previous line. */ - regline = reg_getline(--reglnum); - if (regline == NULL) { - /* can't go before the first line */ - regline = reg_getline(++reglnum); - reginput = regline; - } else - reginput = regline + STRLEN(regline); + if (REG_MULTI && (int)(rex.input - rex.line) < state->val) { + // Not enough bytes in this line, go to end of + // previous line. + rex.line = reg_getline(--rex.lnum); + if (rex.line == NULL) { + // can't go before the first line + rex.line = reg_getline(++rex.lnum); + rex.input = rex.line; + } else { + rex.input = rex.line + STRLEN(rex.line); + } } - if ((int)(reginput - regline) >= state->val) { - reginput -= state->val; - reginput -= utf_head_off(regline, reginput); + if ((int)(rex.input - rex.line) >= state->val) { + rex.input -= state->val; + rex.input -= utf_head_off(rex.line, rex.input); } else { - reginput = regline; + rex.input = rex.line; } } } @@ -4706,48 +4729,50 @@ static int recursive_regmatch( fclose(log_fd); log_fd = NULL; #endif - /* Have to clear the lastlist field of the NFA nodes, so that - * nfa_regmatch() and addstate() can run properly after recursion. */ + // Have to clear the lastlist field of the NFA nodes, so that + // nfa_regmatch() and addstate() can run properly after recursion. if (nfa_ll_index == 1) { - /* Already calling nfa_regmatch() recursively. Save the lastlist[1] - * values and clear them. */ - if (*listids == NULL || *listids_len < nstate) { + // Already calling nfa_regmatch() recursively. Save the lastlist[1] + // values and clear them. + if (*listids == NULL || *listids_len < prog->nstate) { xfree(*listids); - *listids = xmalloc(sizeof(**listids) * nstate); - *listids_len = nstate; + *listids = xmalloc(sizeof(**listids) * prog->nstate); + *listids_len = prog->nstate; } nfa_save_listids(prog, *listids); - need_restore = TRUE; - /* any value of nfa_listid will do */ + need_restore = true; + // any value of rex.nfa_listid will do } else { - /* First recursive nfa_regmatch() call, switch to the second lastlist - * entry. Make sure nfa_listid is different from a previous recursive - * call, because some states may still have this ID. */ - ++nfa_ll_index; - if (nfa_listid <= nfa_alt_listid) - nfa_listid = nfa_alt_listid; + // First recursive nfa_regmatch() call, switch to the second lastlist + // entry. Make sure rex.nfa_listid is different from a previous + // recursive call, because some states may still have this ID. + nfa_ll_index++; + if (rex.nfa_listid <= rex.nfa_alt_listid) { + rex.nfa_listid = rex.nfa_alt_listid; + } } - /* Call nfa_regmatch() to check if the current concat matches at this - * position. The concat ends with the node NFA_END_INVISIBLE */ + // Call nfa_regmatch() to check if the current concat matches at this + // position. The concat ends with the node NFA_END_INVISIBLE nfa_endp = endposp; - result = nfa_regmatch(prog, state->out, submatch, m); + const int result = nfa_regmatch(prog, state->out, submatch, m); - if (need_restore) + if (need_restore) { nfa_restore_listids(prog, *listids); - else { - --nfa_ll_index; - nfa_alt_listid = nfa_listid; + } else { + nfa_ll_index--; + rex.nfa_alt_listid = rex.nfa_listid; } - /* restore position in input text */ - reglnum = save_reglnum; - if (REG_MULTI) - regline = reg_getline(reglnum); - reginput = regline + save_reginput_col; + // restore position in input text + rex.lnum = save_reglnum; + if (REG_MULTI) { + rex.line = reg_getline(rex.lnum); + } + rex.input = rex.line + save_reginput_col; if (result != NFA_TOO_EXPENSIVE) { nfa_match = save_nfa_match; - nfa_listid = save_nfa_listid; + rex.nfa_listid = save_nfa_listid; } nfa_endp = save_nfa_endp; @@ -4756,7 +4781,7 @@ static int recursive_regmatch( if (log_fd != NULL) { fprintf(log_fd, "****************************\n"); fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n"); - fprintf(log_fd, "MATCH = %s\n", !result ? "FALSE" : "OK"); + fprintf(log_fd, "MATCH = %s\n", !result ? "false" : "OK"); fprintf(log_fd, "****************************\n"); } else { EMSG(_(e_log_open_failed)); @@ -4930,11 +4955,11 @@ static int failure_chance(nfa_state_T *state, int depth) */ static int skip_to_start(int c, colnr_T *colp) { - const char_u *const s = cstrchr(regline + *colp, c); + const char_u *const s = cstrchr(rex.line + *colp, c); if (s == NULL) { return FAIL; } - *colp = (int)(s - regline); + *colp = (int)(s - rex.line); return OK; } @@ -4948,12 +4973,12 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) #define PTR2LEN(x) utf_ptr2len(x) colnr_T col = startcol; - int regstart_len = PTR2LEN(regline + startcol); + int regstart_len = PTR2LEN(rex.line + startcol); for (;;) { bool match = true; char_u *s1 = match_text; - char_u *s2 = regline + col + regstart_len; // skip regstart + char_u *s2 = rex.line + col + regstart_len; // skip regstart while (*s1) { int c1_len = PTR2LEN(s1); int c1 = PTR2CHAR(s1); @@ -4973,12 +4998,12 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) && !(enc_utf8 && utf_iscomposing(PTR2CHAR(s2)))) { cleanup_subexpr(); if (REG_MULTI) { - rex.reg_startpos[0].lnum = reglnum; + rex.reg_startpos[0].lnum = rex.lnum; rex.reg_startpos[0].col = col; - rex.reg_endpos[0].lnum = reglnum; - rex.reg_endpos[0].col = s2 - regline; + rex.reg_endpos[0].lnum = rex.lnum; + rex.reg_endpos[0].col = s2 - rex.line; } else { - rex.reg_startp[0] = regline + col; + rex.reg_startp[0] = rex.line + col; rex.reg_endp[0] = s2; } return 1L; @@ -5008,17 +5033,18 @@ static int nfa_did_time_out(void) /// Main matching routine. /// -/// Run NFA to determine whether it matches reginput. +/// Run NFA to determine whether it matches rex.input. /// /// When "nfa_endp" is not NULL it is a required end-of-match position. /// -/// Return TRUE if there is a match, FALSE if there is no match, +/// Return true if there is a match, false if there is no match, /// NFA_TOO_EXPENSIVE if we end up with too many states. /// When there is a match "submatch" contains the positions. /// /// Note: Caller must ensure that: start != NULL. static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *submatch, regsubs_T *m) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) { int result = false; int flag = 0; @@ -5063,11 +5089,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, nfa_match = false; // Allocate memory for the lists of nodes. - size_t size = (nstate + 1) * sizeof(nfa_thread_T); + size_t size = (prog->nstate + 1) * sizeof(nfa_thread_T); list[0].t = xmalloc(size); - list[0].len = nstate + 1; + list[0].len = prog->nstate + 1; list[1].t = xmalloc(size); - list[1].len = nstate + 1; + list[1].len = prog->nstate + 1; #ifdef REGEXP_DEBUG log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); @@ -5085,23 +5111,24 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, thislist = &list[0]; thislist->n = 0; - thislist->has_pim = FALSE; + thislist->has_pim = false; nextlist = &list[1]; nextlist->n = 0; - nextlist->has_pim = FALSE; + nextlist->has_pim = false; #ifdef REGEXP_DEBUG fprintf(log_fd, "(---) STARTSTATE first\n"); #endif - thislist->id = nfa_listid + 1; + thislist->id = rex.nfa_listid + 1; - /* Inline optimized code for addstate(thislist, start, m, 0) if we know - * it's the first MOPEN. */ + // Inline optimized code for addstate(thislist, start, m, 0) if we know + // it's the first MOPEN. if (toplevel) { if (REG_MULTI) { - m->norm.list.multi[0].start_lnum = reglnum; - m->norm.list.multi[0].start_col = (colnr_T)(reginput - regline); - } else - m->norm.list.line[0].start = reginput; + m->norm.list.multi[0].start_lnum = rex.lnum; + m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line); + } else { + m->norm.list.line[0].start = rex.input; + } m->norm.in_use = 1; r = addstate(thislist, start->out, m, NULL, 0); } else { @@ -5122,8 +5149,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, * Run for each character. */ for (;; ) { - int curc = utf_ptr2char(reginput); - int clen = utfc_ptr2len(reginput); + int curc = utf_ptr2char(rex.input); + int clen = utfc_ptr2len(rex.input); if (curc == NUL) { clen = 0; go_to_nextline = false; @@ -5134,20 +5161,20 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, nextlist = &list[flag ^= 1]; nextlist->n = 0; // clear nextlist nextlist->has_pim = false; - nfa_listid++; + rex.nfa_listid++; if (prog->re_engine == AUTOMATIC_ENGINE - && (nfa_listid >= NFA_MAX_STATES)) { + && (rex.nfa_listid >= NFA_MAX_STATES)) { // Too many states, retry with old engine. nfa_match = NFA_TOO_EXPENSIVE; goto theend; } - thislist->id = nfa_listid; - nextlist->id = nfa_listid + 1; + thislist->id = rex.nfa_listid; + nextlist->id = rex.nfa_listid + 1; #ifdef REGEXP_DEBUG fprintf(log_fd, "------------------------------------------\n"); - fprintf(log_fd, ">>> Reginput is \"%s\"\n", reginput); + fprintf(log_fd, ">>> Reginput is \"%s\"\n", rex.input); fprintf(log_fd, ">>> Advanced one character... Current char is %c (code %d) \n", curc, @@ -5200,7 +5227,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } else if (REG_MULTI) { col = t->subs.norm.list.multi[0].start_col; } else { - col = (int)(t->subs.norm.list.line[0].start - regline); + col = (int)(t->subs.norm.list.line[0].start - rex.line); } nfa_set_code(t->state->c); fprintf(log_fd, "(%d) char %d %s (start col %d)%s... \n", @@ -5226,64 +5253,66 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } nfa_match = true; copy_sub(&submatch->norm, &t->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&submatch->synt, &t->subs.synt); + } #ifdef REGEXP_DEBUG log_subsexpr(&t->subs); #endif - /* Found the left-most longest match, do not look at any other - * states at this position. When the list of states is going - * to be empty quit without advancing, so that "reginput" is - * correct. */ - if (nextlist->n == 0) + // Found the left-most longest match, do not look at any other + // states at this position. When the list of states is going + // to be empty quit without advancing, so that "rex.input" is + // correct. + if (nextlist->n == 0) { clen = 0; + } goto nextchar; } case NFA_END_INVISIBLE: case NFA_END_INVISIBLE_NEG: case NFA_END_PATTERN: - /* - * This is only encountered after a NFA_START_INVISIBLE or - * NFA_START_INVISIBLE_BEFORE node. - * They surround a zero-width group, used with "\@=", "\&", - * "\@!", "\@<=" and "\@<!". - * If we got here, it means that the current "invisible" group - * finished successfully, so return control to the parent - * nfa_regmatch(). For a look-behind match only when it ends - * in the position in "nfa_endp". - * Submatches are stored in *m, and used in the parent call. - */ + // This is only encountered after a NFA_START_INVISIBLE or + // NFA_START_INVISIBLE_BEFORE node. + // They surround a zero-width group, used with "\@=", "\&", + // "\@!", "\@<=" and "\@<!". + // If we got here, it means that the current "invisible" group + // finished successfully, so return control to the parent + // nfa_regmatch(). For a look-behind match only when it ends + // in the position in "nfa_endp". + // Submatches are stored in *m, and used in the parent call. #ifdef REGEXP_DEBUG if (nfa_endp != NULL) { - if (REG_MULTI) - fprintf( - log_fd, - "Current lnum: %d, endp lnum: %d; current col: %d, endp col: %d\n", - (int)reglnum, - (int)nfa_endp->se_u.pos.lnum, - (int)(reginput - regline), - nfa_endp->se_u.pos.col); - else + if (REG_MULTI) { + fprintf(log_fd, + "Current lnum: %d, endp lnum: %d;" + " current col: %d, endp col: %d\n", + (int)rex.lnum, + (int)nfa_endp->se_u.pos.lnum, + (int)(rex.input - rex.line), + nfa_endp->se_u.pos.col); + } else { fprintf(log_fd, "Current col: %d, endp col: %d\n", - (int)(reginput - regline), - (int)(nfa_endp->se_u.ptr - reginput)); + (int)(rex.input - rex.line), + (int)(nfa_endp->se_u.ptr - rex.input)); + } } #endif - /* If "nfa_endp" is set it's only a match if it ends at - * "nfa_endp" */ - if (nfa_endp != NULL && (REG_MULTI - ? (reglnum != nfa_endp->se_u.pos.lnum - || (int)(reginput - regline) - != nfa_endp->se_u.pos.col) - : reginput != nfa_endp->se_u.ptr)) + // If "nfa_endp" is set it's only a match if it ends at + // "nfa_endp" + if (nfa_endp != NULL + && (REG_MULTI + ? (rex.lnum != nfa_endp->se_u.pos.lnum + || (int)(rex.input - rex.line) != nfa_endp->se_u.pos.col) + : rex.input != nfa_endp->se_u.ptr)) { break; - - /* do not set submatches for \@! */ + } + // do not set submatches for \@! if (t->state->c != NFA_END_INVISIBLE_NEG) { copy_sub(&m->norm, &t->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub(&m->synt, &t->subs.synt); + } } #ifdef REGEXP_DEBUG fprintf(log_fd, "Match found:\n"); @@ -5322,9 +5351,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Copy submatch info for the recursive call, opposite // of what happens on success below. copy_sub_off(&m->norm, &t->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub_off(&m->synt, &t->subs.synt); - + } // First try matching the invisible match, then what // follows. result = recursive_regmatch(t->state, NULL, prog, submatch, m, @@ -5335,7 +5364,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } // for \@! and \@<! it is a match when the result is - // FALSE + // false if (result != (t->state->c == NFA_START_INVISIBLE_NEG || t->state->c == NFA_START_INVISIBLE_NEG_FIRST || t->state->c @@ -5344,8 +5373,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) { // Copy submatch info from the recursive call copy_sub_off(&t->subs.norm, &m->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub_off(&t->subs.synt, &m->synt); + } // If the pattern has \ze and it matched in the // sub pattern, use it. copy_ze_off(&t->subs.norm, &m->norm); @@ -5369,11 +5399,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, pim.subs.norm.in_use = 0; pim.subs.synt.in_use = 0; if (REG_MULTI) { - pim.end.pos.col = (int)(reginput - regline); - pim.end.pos.lnum = reglnum; - } else - pim.end.ptr = reginput; - + pim.end.pos.col = (int)(rex.input - rex.line); + pim.end.pos.lnum = rex.lnum; + } else { + pim.end.ptr = rex.input; + } // t->state->out1 is the corresponding END_INVISIBLE // node; Add its out to the current list (zero-width // match). @@ -5426,7 +5456,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Copy submatch info to the recursive call, opposite of what // happens afterwards. copy_sub_off(&m->norm, &t->subs.norm); - if (nfa_has_zsubexpr) { + if (rex.nfa_has_zsubexpr) { copy_sub_off(&m->synt, &t->subs.synt); } @@ -5446,7 +5476,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, #endif // Copy submatch info from the recursive call copy_sub_off(&t->subs.norm, &m->norm); - if (nfa_has_zsubexpr) { + if (rex.nfa_has_zsubexpr) { copy_sub_off(&t->subs.synt, &m->synt); } // Now we need to skip over the matched text and then @@ -5454,9 +5484,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, if (REG_MULTI) { // TODO(RE): multi-line match bytelen = m->norm.list.multi[0].end_col - - (int)(reginput - regline); + - (int)(rex.input - rex.line); } else { - bytelen = (int)(m->norm.list.line[0].end - reginput); + bytelen = (int)(m->norm.list.line[0].end - rex.input); } #ifdef REGEXP_DEBUG @@ -5485,7 +5515,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } case NFA_BOL: - if (reginput == regline) { + if (rex.input == rex.line) { add_here = true; add_state = t->state->out; } @@ -5503,20 +5533,16 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, if (curc == NUL) { result = false; - } else if (has_mbyte) { + } else { int this_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); if (this_class <= 1) { result = false; } else if (reg_prev_class() == this_class) { result = false; } - } else if (!vim_iswordc_buf(curc, rex.reg_buf) - || (reginput > regline - && vim_iswordc_buf(reginput[-1], rex.reg_buf))) { - result = false; } if (result) { add_here = true; @@ -5526,22 +5552,18 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_EOW: result = true; - if (reginput == regline) { + if (rex.input == rex.line) { result = false; - } else if (has_mbyte) { + } else { int this_class, prev_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class || prev_class == 0 || prev_class == 1) { result = false; } - } else if (!vim_iswordc_buf(reginput[-1], rex.reg_buf) - || (reginput[0] != NUL - && vim_iswordc_buf(curc, rex.reg_buf))) { - result = false; } if (result) { add_here = true; @@ -5550,7 +5572,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_BOF: - if (reglnum == 0 && reginput == regline + if (rex.lnum == 0 && rex.input == rex.line && (!REG_MULTI || rex.reg_firstlnum == 1)) { add_here = true; add_state = t->state->out; @@ -5558,7 +5580,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_EOF: - if (reglnum == rex.reg_maxline && curc == NUL) { + if (rex.lnum == rex.reg_maxline && curc == NUL) { add_here = true; add_state = t->state->out; } @@ -5603,7 +5625,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // We don't care about the order of composing characters. // Get them into cchars[] first. while (len < clen) { - mc = utf_ptr2char(reginput + len); + mc = utf_ptr2char(rex.input + len); cchars[ccount++] = mc; len += mb_char2len(mc); if (ccount == MAX_MCO) @@ -5634,7 +5656,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_NEWL: if (curc == NUL && !rex.reg_line_lbr && REG_MULTI - && reglnum <= rex.reg_maxline) { + && rex.lnum <= rex.reg_maxline) { go_to_nextline = true; // Pass -1 for the offset, which means taking the position // at the start of the next line. @@ -5688,7 +5710,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, for (; c1 <= c2; c1++) { if (utf_fold(c1) == curc_low) { result = result_if_matched; - done = TRUE; + done = true; break; } } @@ -5746,13 +5768,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_KWORD: // \k - result = vim_iswordp_buf(reginput, rex.reg_buf); + result = vim_iswordp_buf(rex.input, rex.reg_buf); ADD_STATE_IF_MATCH(t->state); break; case NFA_SKWORD: // \K result = !ascii_isdigit(curc) - && vim_iswordp_buf(reginput, rex.reg_buf); + && vim_iswordp_buf(rex.input, rex.reg_buf); ADD_STATE_IF_MATCH(t->state); break; @@ -5767,12 +5789,12 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, break; case NFA_PRINT: // \p - result = vim_isprintc(PTR2CHAR(reginput)); + result = vim_isprintc(PTR2CHAR(rex.input)); ADD_STATE_IF_MATCH(t->state); break; case NFA_SPRINT: // \P - result = !ascii_isdigit(curc) && vim_isprintc(PTR2CHAR(reginput)); + result = !ascii_isdigit(curc) && vim_isprintc(PTR2CHAR(rex.input)); ADD_STATE_IF_MATCH(t->state); break; @@ -5959,14 +5981,14 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_LNUM_LT: assert(t->state->val >= 0 && !((rex.reg_firstlnum > 0 - && reglnum > LONG_MAX - rex.reg_firstlnum) + && rex.lnum > LONG_MAX - rex.reg_firstlnum) || (rex.reg_firstlnum < 0 - && reglnum < LONG_MIN + rex.reg_firstlnum)) - && reglnum + rex.reg_firstlnum >= 0); + && rex.lnum < LONG_MIN + rex.reg_firstlnum)) + && rex.lnum + rex.reg_firstlnum >= 0); result = (REG_MULTI && nfa_re_num_cmp((uintmax_t)t->state->val, t->state->c - NFA_LNUM, - (uintmax_t)(reglnum + rex.reg_firstlnum))); + (uintmax_t)(rex.lnum + rex.reg_firstlnum))); if (result) { add_here = true; add_state = t->state->out; @@ -5977,11 +5999,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_COL_GT: case NFA_COL_LT: assert(t->state->val >= 0 - && reginput >= regline - && (uintmax_t)(reginput - regline) <= UINTMAX_MAX - 1); + && rex.input >= rex.line + && (uintmax_t)(rex.input - rex.line) <= UINTMAX_MAX - 1); result = nfa_re_num_cmp((uintmax_t)t->state->val, t->state->c - NFA_COL, - (uintmax_t)(reginput - regline + 1)); + (uintmax_t)(rex.input - rex.line + 1)); if (result) { add_here = true; add_state = t->state->out; @@ -5993,7 +6015,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_VCOL_LT: { int op = t->state->c - NFA_VCOL; - colnr_T col = (colnr_T)(reginput - regline); + colnr_T col = (colnr_T)(rex.input - rex.line); // Bail out quickly when there can't be a match, avoid the overhead of // win_linetabsize() on long lines. @@ -6014,7 +6036,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, result = col > t->state->val * ts; } if (!result) { - uintmax_t lts = win_linetabsize(wp, regline, col); + uintmax_t lts = win_linetabsize(wp, rex.line, col); assert(t->state->val >= 0); result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1); } @@ -6034,13 +6056,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Compare the mark position to the match position. result = (pos != NULL // mark doesn't exist && pos->lnum > 0 // mark isn't set in reg_buf - && (pos->lnum == reglnum + rex.reg_firstlnum - ? (pos->col == (colnr_T)(reginput - regline) + && (pos->lnum == rex.lnum + rex.reg_firstlnum + ? (pos->col == (colnr_T)(rex.input - rex.line) ? t->state->c == NFA_MARK - : (pos->col < (colnr_T)(reginput - regline) + : (pos->col < (colnr_T)(rex.input - rex.line) ? t->state->c == NFA_MARK_GT : t->state->c == NFA_MARK_LT)) - : (pos->lnum < reglnum + rex.reg_firstlnum + : (pos->lnum < rex.lnum + rex.reg_firstlnum ? t->state->c == NFA_MARK_GT : t->state->c == NFA_MARK_LT))); if (result) { @@ -6051,10 +6073,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } case NFA_CURSOR: - result = (rex.reg_win != NULL - && (reglnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum) - && ((colnr_T)(reginput - regline) - == rex.reg_win->w_cursor.col)); + result = rex.reg_win != NULL + && (rex.lnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum) + && ((colnr_T)(rex.input - rex.line) == rex.reg_win->w_cursor.col); if (result) { add_here = true; add_state = t->state->out; @@ -6112,7 +6133,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // If rex.reg_icombine is not set only skip over the character // itself. When it is set skip over composing characters. if (result && enc_utf8 && !rex.reg_icombine) { - clen = utf_ptr2len(reginput); + clen = utf_ptr2len(rex.input); } ADD_STATE_IF_MATCH(t->state); @@ -6143,7 +6164,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, &listids, &listids_len); pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH; // for \@! and \@<! it is a match when the result is - // FALSE + // false if (result != (pim->state->c == NFA_START_INVISIBLE_NEG || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST || pim->state->c @@ -6152,8 +6173,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) { // Copy submatch info from the recursive call copy_sub_off(&pim->subs.norm, &m->norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub_off(&pim->subs.synt, &m->synt); + } } } else { result = (pim->result == NFA_PIM_MATCH); @@ -6163,12 +6185,12 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, log_fd, "Using previous recursive nfa_regmatch() result, result == %d\n", pim->result); - fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "FALSE"); + fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "false"); fprintf(log_fd, "\n"); #endif } - // for \@! and \@<! it is a match when result is FALSE + // for \@! and \@<! it is a match when result is false if (result != (pim->state->c == NFA_START_INVISIBLE_NEG || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST || pim->state->c @@ -6177,8 +6199,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) { // Copy submatch info from the recursive call copy_sub_off(&t->subs.norm, &pim->subs.norm); - if (nfa_has_zsubexpr) + if (rex.nfa_has_zsubexpr) { copy_sub_off(&t->subs.synt, &pim->subs.synt); + } } else { // look-behind match failed, don't add the state continue; @@ -6222,29 +6245,28 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // Also don't start a match past the first line. if (!nfa_match && ((toplevel - && reglnum == 0 + && rex.lnum == 0 && clen != 0 && (rex.reg_maxcol == 0 - || (colnr_T)(reginput - regline) < rex.reg_maxcol)) + || (colnr_T)(rex.input - rex.line) < rex.reg_maxcol)) || (nfa_endp != NULL && (REG_MULTI - ? (reglnum < nfa_endp->se_u.pos.lnum - || (reglnum == nfa_endp->se_u.pos.lnum - && (int)(reginput - regline) + ? (rex.lnum < nfa_endp->se_u.pos.lnum + || (rex.lnum == nfa_endp->se_u.pos.lnum + && (int)(rex.input - rex.line) < nfa_endp->se_u.pos.col)) - : reginput < nfa_endp->se_u.ptr)))) { + : rex.input < nfa_endp->se_u.ptr)))) { #ifdef REGEXP_DEBUG fprintf(log_fd, "(---) STARTSTATE\n"); #endif // Inline optimized code for addstate() if we know the state is // the first MOPEN. if (toplevel) { - int add = TRUE; - int c; + int add = true; if (prog->regstart != NUL && clen != 0) { if (nextlist->n == 0) { - colnr_T col = (colnr_T)(reginput - regline) + clen; + colnr_T col = (colnr_T)(rex.input - rex.line) + clen; // Nextlist is empty, we can skip ahead to the // character that must appear at the start. @@ -6253,13 +6275,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } #ifdef REGEXP_DEBUG fprintf(log_fd, " Skipping ahead %d bytes to regstart\n", - col - ((colnr_T)(reginput - regline) + clen)); + col - ((colnr_T)(rex.input - rex.line) + clen)); #endif - reginput = regline + col - clen; + rex.input = rex.line + col - clen; } else { // Checking if the required start character matches is // cheaper than adding a state that won't match. - c = PTR2CHAR(reginput + clen); + const int c = PTR2CHAR(rex.input + clen); if (c != prog->regstart && (!rex.reg_ic || utf_fold(c) != utf_fold(prog->regstart))) { @@ -6267,17 +6289,18 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, fprintf(log_fd, " Skipping start state, regstart does not match\n"); #endif - add = FALSE; + add = false; } } } if (add) { - if (REG_MULTI) + if (REG_MULTI) { m->norm.list.multi[0].start_col = - (colnr_T)(reginput - regline) + clen; - else - m->norm.list.line[0].start = reginput + clen; + (colnr_T)(rex.input - rex.line) + clen; + } else { + m->norm.list.line[0].start = rex.input + clen; + } if (addstate(nextlist, start->out, m, NULL, clen) == NULL) { nfa_match = NFA_TOO_EXPENSIVE; goto theend; @@ -6306,9 +6329,9 @@ nextchar: // Advance to the next character, or advance to the next line, or // finish. if (clen != 0) { - reginput += clen; + rex.input += clen; } else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI - && reglnum < nfa_endp->se_u.pos.lnum)) { + && rex.lnum < nfa_endp->se_u.pos.lnum)) { reg_nextline(); } else { break; @@ -6347,7 +6370,7 @@ theend: return nfa_match; } -// Try match of "prog" with at regline["col"]. +// Try match of "prog" with at rex.line["col"]. // Returns <= 0 for failure, number of lines contained in the match otherwise. static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, @@ -6361,7 +6384,7 @@ static long nfa_regtry(nfa_regprog_T *prog, FILE *f; #endif - reginput = regline + col; + rex.input = rex.line + col; nfa_time_limit = tm; nfa_timed_out = timed_out; nfa_time_count = 0; @@ -6374,7 +6397,7 @@ static long nfa_regtry(nfa_regprog_T *prog, #ifdef REGEXP_DEBUG fprintf(f, "\tRegexp is \"%s\"\n", nfa_regengine.expr); #endif - fprintf(f, "\tInput text is \"%s\" \n", reginput); + fprintf(f, "\tInput text is \"%s\" \n", rex.input); fprintf(f, "\t=======================================================\n\n"); nfa_print_state(f, start); fprintf(f, "\n\n"); @@ -6412,11 +6435,11 @@ static long nfa_regtry(nfa_regprog_T *prog, } if (rex.reg_endpos[0].lnum < 0) { // pattern has a \ze but it didn't match, use current end - rex.reg_endpos[0].lnum = reglnum; - rex.reg_endpos[0].col = (int)(reginput - regline); + rex.reg_endpos[0].lnum = rex.lnum; + rex.reg_endpos[0].col = (int)(rex.input - rex.line); } else { // Use line number of "\ze". - reglnum = rex.reg_endpos[0].lnum; + rex.lnum = rex.reg_endpos[0].lnum; } } else { for (i = 0; i < subs.norm.in_use; i++) { @@ -6425,10 +6448,10 @@ static long nfa_regtry(nfa_regprog_T *prog, } if (rex.reg_startp[0] == NULL) { - rex.reg_startp[0] = regline + col; + rex.reg_startp[0] = rex.line + col; } if (rex.reg_endp[0] == NULL) { - rex.reg_endp[0] = reginput; + rex.reg_endp[0] = rex.input; } } @@ -6463,7 +6486,7 @@ static long nfa_regtry(nfa_regprog_T *prog, } } - return 1 + reglnum; + return 1 + rex.lnum; } /// Match a regexp against a string ("line" points to the string) or multiple @@ -6481,7 +6504,6 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, { nfa_regprog_T *prog; long retval = 0L; - int i; colnr_T col = startcol; if (REG_MULTI) { @@ -6497,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; } @@ -6513,26 +6535,30 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, rex.reg_icombine = true; } - regline = line; - reglnum = 0; /* relative to line */ + rex.line = line; + rex.lnum = 0; // relative to line - nfa_has_zend = prog->has_zend; - nfa_has_backref = prog->has_backref; - nfa_nsubexpr = prog->nsubexp; - nfa_listid = 1; - nfa_alt_listid = 2; + rex.nfa_has_zend = prog->has_zend; + rex.nfa_has_backref = prog->has_backref; + rex.nfa_nsubexpr = prog->nsubexp; + rex.nfa_listid = 1; + rex.nfa_alt_listid = 2; +#ifdef REGEXP_DEBUG nfa_regengine.expr = prog->pattern; +#endif if (prog->reganch && col > 0) return 0L; - need_clear_subexpr = TRUE; - /* Clear the external match subpointers if necessary. */ + rex.need_clear_subexpr = true; + // Clear the external match subpointers if necessary. if (prog->reghasz == REX_SET) { - nfa_has_zsubexpr = TRUE; - need_clear_zsubexpr = TRUE; - } else - nfa_has_zsubexpr = FALSE; + rex.nfa_has_zsubexpr = true; + rex.need_clear_zsubexpr = true; + } else { + rex.nfa_has_zsubexpr = false; + rex.need_clear_zsubexpr = false; + } if (prog->regstart != NUL) { /* Skip ahead until a character we know the match must start with. @@ -6552,8 +6578,10 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, goto theend; } - nstate = prog->nstate; - for (i = 0; i < nstate; ++i) { + // Set the "nstate" used by nfa_regcomp() to zero to trigger an error when + // it's accidentally used during execution. + nstate = 0; + for (int i = 0; i < prog->nstate; i++) { prog->state[i].id = i; prog->state[i].lastlist[0] = 0; prog->state[i].lastlist[1] = 0; @@ -6561,7 +6589,9 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, retval = nfa_regtry(prog, col, tm, timed_out); +#ifdef REGEXP_DEBUG nfa_regengine.expr = NULL; +#endif theend: return retval; @@ -6579,7 +6609,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) if (expr == NULL) return NULL; +#ifdef REGEXP_DEBUG nfa_regengine.expr = expr; +#endif nfa_re_flags = re_flags; init_class_tab(); @@ -6616,26 +6648,27 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) * PASS 1 * Count number of NFA states in "nstate". Do not build the NFA. */ - post2nfa(postfix, post_ptr, TRUE); + post2nfa(postfix, post_ptr, true); /* allocate the regprog with space for the compiled regexp */ size_t prog_size = sizeof(nfa_regprog_T) + sizeof(nfa_state_T) * (nstate - 1); prog = xmalloc(prog_size); state_ptr = prog->state; + prog->re_in_use = false; /* * PASS 2 * Build the NFA */ - prog->start = post2nfa(postfix, post_ptr, FALSE); - if (prog->start == NULL) + prog->start = post2nfa(postfix, post_ptr, false); + if (prog->start == NULL) { goto fail; - + } prog->regflags = regflags; prog->engine = &nfa_regengine; prog->nstate = nstate; - prog->has_zend = nfa_has_zend; - prog->has_backref = nfa_has_backref; + prog->has_zend = rex.nfa_has_zend; + prog->has_backref = rex.nfa_has_backref; prog->nsubexp = regnpar; nfa_postprocess(prog); @@ -6651,7 +6684,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) /* Remember whether this pattern has any \z specials in it. */ prog->reghasz = re_has_z; prog->pattern = vim_strsave(expr); +#ifdef REGEXP_DEBUG nfa_regengine.expr = NULL; +#endif out: xfree(post_start); @@ -6663,8 +6698,8 @@ fail: XFREE_CLEAR(prog); #ifdef REGEXP_DEBUG nfa_postfix_dump(expr, FAIL); -#endif nfa_regengine.expr = NULL; +#endif goto out; } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 7bed747e9a..8998f9037e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -25,7 +25,7 @@ // // Commands that scroll a window change w_topline and must call // check_cursor() to move the cursor into the visible part of the window, and -// call redraw_later(VALID) to have the window displayed by update_screen() +// call redraw_later(wp, VALID) to have the window displayed by update_screen() // later. // // Commands that change text in the buffer must call changed_bytes() or @@ -37,7 +37,7 @@ // // Commands that change how a window is displayed (e.g., setting 'list') or // invalidate the contents of a window in another way (e.g., change fold -// settings), must call redraw_later(NOT_VALID) to have the whole window +// settings), must call redraw_later(wp, NOT_VALID) to have the whole window // redisplayed by update_screen() later. // // Commands that change how a buffer is displayed (e.g., setting 'tabstop') @@ -45,11 +45,11 @@ // buffer redisplayed by update_screen() later. // // Commands that change highlighting and possibly cause a scroll too must call -// redraw_later(SOME_VALID) to update the whole window but still use scrolling -// to avoid redrawing everything. But the length of displayed lines must not -// change, use NOT_VALID then. +// redraw_later(wp, SOME_VALID) to update the whole window but still use +// scrolling to avoid redrawing everything. But the length of displayed lines +// must not change, use NOT_VALID then. // -// Commands that move the window position must call redraw_later(NOT_VALID). +// Commands that move the window position must call redraw_later(wp, NOT_VALID). // TODO(neovim): should minimize redrawing by scrolling when possible. // // Commands that change everything (e.g., resizing the screen) must call @@ -88,6 +88,7 @@ #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/extmark.h" +#include "nvim/decoration.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -119,10 +120,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(DecorProvider *, 4) Providers; // temporary buffer for rendering a single screenline, so it can be // compared with previous contents to calculate smallest delta. @@ -133,8 +136,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,22 +159,48 @@ 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 -/* - * Redraw the current window later, with update_screen(type). - * Set must_redraw only if not already set to a higher value. - * e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing. - */ -void redraw_later(int type) +static char * provider_first_error = NULL; + +static bool provider_invoke(NS ns_id, const char *name, LuaRef ref, + Array args, bool default_true) { - redraw_win_later(curwin, type); + Error err = ERROR_INIT; + + textlock++; + Object ret = nlua_call_ref(ref, name, args, true, &err); + textlock--; + + if (!ERROR_SET(&err) + && api_object_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; } -void redraw_win_later(win_T *wp, int type) +/// Redraw a window later, with update_screen(type). +/// +/// Set must_redraw only if not already set to a higher value. +/// e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing. +void redraw_later(win_T *wp, int type) FUNC_ATTR_NONNULL_ALL { if (!exiting && wp->w_redr_type < type) { @@ -191,7 +218,7 @@ void redraw_win_later(win_T *wp, int type) void redraw_all_later(int type) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redraw_win_later(wp, type); + redraw_later(wp, type); } // This may be needed when switching tabs. if (must_redraw < type) { @@ -202,7 +229,7 @@ void redraw_all_later(int type) void screen_invalidate_highlights(void) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); wp->w_grid.valid = false; } } @@ -219,7 +246,7 @@ void redraw_buf_later(buf_T *buf, int type) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == buf) { - redraw_win_later(wp, type); + redraw_later(wp, type); } } } @@ -245,7 +272,7 @@ void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { wp->w_redraw_bot = lastline; } - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); } } } @@ -273,7 +300,7 @@ redrawWinline( if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { wp->w_redraw_bot = lnum; } - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); } } @@ -327,7 +354,6 @@ int update_screen(int type) /* Postpone the redrawing when it's not needed and when being called * recursively. */ if (!redrawing() || updating_screen) { - redraw_later(type); /* remember type for next time */ must_redraw = type; if (type > INVERTED_ALL) { curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now @@ -446,6 +472,35 @@ int update_screen(int type) ui_comp_set_screen_valid(true); + Providers providers; + kvi_init(providers); + for (size_t i = 0; i < kv_size(decor_providers); i++) { + DecorProvider *p = &kv_A(decor_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); + } + } + + // "start" callback could have changed highlights for global elements + if (win_check_ns_hl(NULL)) { + redraw_cmdline = true; + redraw_tabline = true; + } + 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_decor < display_tick) { + for (size_t i = 0; i < kv_size(providers); i++) { + DecorProvider *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_decor = display_tick; } } } @@ -541,7 +590,7 @@ 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 */ @@ -578,6 +627,21 @@ int update_screen(int type) maybe_intro_message(); did_intro = TRUE; + for (size_t i = 0; i < kv_size(providers); i++) { + DecorProvider *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; @@ -634,17 +698,6 @@ bool win_cursorline_standout(const win_T *wp) || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(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) -{ - kv_push(decorations.active, - ((HlRange){ start_row, start_col, end_row, end_col, attr_id, NULL })); -} - /* * Update a single window. * @@ -672,7 +725,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 +750,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 +764,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 +953,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 +1282,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,28 +1291,32 @@ 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); + decor_redraw_reset(buf, &decor_state); - 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++) { + DecorProvider *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); + } } } + win_check_ns_hl(wp); + for (;; ) { /* stop updating when reached the end of the window (check for _past_ @@ -1448,24 +1507,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 +1528,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 +1557,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 +1664,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,21 +1692,55 @@ 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; } } + /* restore got_int, unless CTRL-C was hit while redrawing */ if (!got_int) got_int = save_got_int; @@ -1741,31 +1836,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 +1900,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 +1914,7 @@ static size_t fill_foldcolumn( char_u *p, win_T *wp, - int closed, + foldinfo_T foldinfo, linenr_T lnum ) { @@ -2120,10 +1925,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 +1939,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 +1971,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) @@ -2281,9 +2088,11 @@ win_line ( int prev_c1 = 0; // first composing char for prev_c bool search_attr_from_match = false; // if search_attr is from :match - bool has_decorations = false; // this buffer has decorations + bool has_decor = false; // this buffer has decoration 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 +2131,7 @@ win_line ( row = startrow; - char *luatext = NULL; + char *err_text = NULL; buf_T *buf = wp->w_buffer; @@ -2347,37 +2156,37 @@ win_line ( } } - if (decorations_active) { - if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) { - Error err = ERROR_INIT; + has_decor = decor_redraw_line(wp->w_buffer, lnum-1, + &decor_state); + + for (size_t k = 0; k < kv_size(*providers); k++) { + DecorProvider *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_decor = 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; + win_check_ns_hl(wp); } } + if (has_decor) { + 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'. color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols; if (color_cols != NULL) { @@ -2554,6 +2363,7 @@ win_line ( } // If this line has a sign with line highlighting set line_attr. + // TODO(bfredl, vigoux): this should not take priority over decoration! v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1); if (v != 0) { line_attr = sign_get_attr((int)v, SIGN_LINEHL); @@ -2816,6 +2626,7 @@ win_line ( for (;; ) { int has_match_conc = 0; ///< match wants to conceal bool did_decrement_ptr = false; + // Skip this quickly when working on the text. if (draw_state != WL_LINE) { if (draw_state == WL_CMDLINE - 1 && n_extra == 0) { @@ -2838,7 +2649,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; @@ -2854,47 +2665,12 @@ win_line ( * buffer or when using Netbeans. */ int count = win_signcol_count(wp); if (count > 0) { - int text_sign; - // Draw cells with the sign value or blank. - c_extra = ' '; - c_final = NUL; - char_attr = win_hl_attr(wp, HLF_SC); - n_extra = win_signcol_width(wp); - - if (row == startrow + filler_lines && filler_todo <= 0) { - text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT, - sign_idx, count); - if (text_sign != 0) { - p_extra = sign_get_text(text_sign); - if (p_extra != NULL) { - int symbol_blen = (int)STRLEN(p_extra); - - c_extra = NUL; - c_final = NUL; - - // TODO(oni-link): Is sign text already extended to - // full cell width? - assert((size_t)win_signcol_width(wp) - >= mb_string2cells(p_extra)); - // symbol(s) bytes + (filling spaces) (one byte each) - n_extra = symbol_blen + - (win_signcol_width(wp) - mb_string2cells(p_extra)); - - assert(sizeof(extra) > (size_t)symbol_blen); - memset(extra, ' ', sizeof(extra)); - memcpy(extra, p_extra, symbol_blen); - - p_extra = extra; - p_extra[n_extra] = NUL; - } - char_attr = sign_get_attr(text_sign, SIGN_TEXT); - } - } - - sign_idx++; - if (sign_idx < count) { - draw_state = WL_SIGN - 1; - } + get_sign_display_info( + false, wp, lnum, row, + startrow, filler_lines, filler_todo, count, + &c_extra, &c_final, extra, sizeof(extra), + &p_extra, &n_extra, + &char_attr, &draw_state, &sign_idx); } } @@ -2903,65 +2679,78 @@ win_line ( /* Display the absolute or relative line number. After the * first fill with blanks when the 'n' flag isn't in 'cpo' */ if ((wp->w_p_nu || wp->w_p_rnu) - && (row == startrow - + filler_lines + && (row == startrow + filler_lines || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) { - /* Draw the line number (empty space after wrapping). */ - if (row == startrow - + filler_lines - ) { - long num; - char *fmt = "%*ld "; - - 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' */ - num = lnum; - fmt = "%-*ld "; + // If 'signcolumn' is set to 'number' and a sign is present + // in 'lnum', then display the sign instead of the line + // number. + if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' + && buf_findsign_id(wp->w_buffer, lnum, (char_u *)"*") != 0) { + int count = win_signcol_count(wp); + get_sign_display_info( + true, wp, lnum, row, + startrow, filler_lines, filler_todo, count, + &c_extra, &c_final, extra, sizeof(extra), + &p_extra, &n_extra, + &char_attr, &draw_state, &sign_idx); + } else { + if (row == startrow + filler_lines) { + // Draw the line number (empty space after wrapping). */ + long num; + char *fmt = "%*ld "; + + 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' + num = lnum; + fmt = "%-*ld "; + } } - } - sprintf((char *)extra, fmt, - number_width(wp), num); - if (wp->w_skipcol > 0) - for (p_extra = extra; *p_extra == ' '; ++p_extra) - *p_extra = '-'; - if (wp->w_p_rl) { // reverse line numbers - // like rl_mirror(), but keep the space at the end - char_u *p2 = skiptowhite(extra) - 1; - for (char_u *p1 = extra; p1 < p2; p1++, p2--) { - const int t = *p1; - *p1 = *p2; - *p2 = t; + snprintf((char *)extra, sizeof(extra), + fmt, number_width(wp), num); + if (wp->w_skipcol > 0) { + for (p_extra = extra; *p_extra == ' '; p_extra++) { + *p_extra = '-'; + } + } + if (wp->w_p_rl) { // reverse line numbers + // like rl_mirror(), but keep the space at the end + char_u *p2 = skiptowhite(extra) - 1; + for (char_u *p1 = extra; p1 < p2; p1++, p2--) { + const int t = *p1; + *p1 = *p2; + *p2 = t; + } } + p_extra = extra; + c_extra = NUL; + c_final = NUL; + } else { + c_extra = ' '; + c_final = NUL; + } + n_extra = number_width(wp) + 1; + char_attr = win_hl_attr(wp, HLF_N); + + int num_sign = buf_getsigntype( + wp->w_buffer, lnum, SIGN_NUMHL, 0, 1); + if (num_sign != 0) { + // :sign defined with "numhl" highlight. + char_attr = sign_get_attr(num_sign, SIGN_NUMHL); + } else if ((wp->w_p_cul || wp->w_p_rnu) + && lnum == wp->w_cursor.lnum) { + // When 'cursorline' is set highlight the line number of + // the current line differently. + // TODO(vim): Can we use CursorLine instead of CursorLineNr + // when CursorLineNr isn't set? + char_attr = win_hl_attr(wp, HLF_CLN); } - p_extra = extra; - c_extra = NUL; - c_final = NUL; - } else { - c_extra = ' '; - c_final = NUL; - } - n_extra = number_width(wp) + 1; - char_attr = win_hl_attr(wp, HLF_N); - - int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL, - 0, 1); - if (num_sign != 0) { - // :sign defined with "numhl" highlight. - char_attr = sign_get_attr(num_sign, SIGN_NUMHL); - } else if ((wp->w_p_cul || wp->w_p_rnu) - && lnum == wp->w_cursor.lnum) { - // When 'cursorline' is set highlight the line number of - // the current line differently. - // TODO(vim): Can we use CursorLine instead of CursorLineNr - // when CursorLineNr isn't set? - char_attr = win_hl_attr(wp, HLF_CLN); } } } @@ -3068,10 +2857,12 @@ win_line ( } // When still displaying '$' of change command, stop at cursor - if ((dollar_vcol >= 0 && wp == curwin - && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol - && filler_todo <= 0) - || (number_only && draw_state > WL_NR)) { + if (((dollar_vcol >= 0 + && wp == curwin + && lnum == wp->w_cursor.lnum + && vcol >= (long)wp->w_virtcol) + || (number_only && draw_state > WL_NR)) + && filler_todo <= 0) { grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp, wp->w_hl_attr_normal, false); // Pretend we have finished updating the window. Except when @@ -3084,6 +2875,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 @@ -3312,6 +3148,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; @@ -3476,6 +3316,7 @@ win_line ( * Only do this when there is no syntax highlighting, the * @Spell cluster is not used or the current syntax item * contains the @Spell cluster. */ + v = (long)(ptr - line); if (has_spell && v >= word_end && v > cur_checked_col) { spell_attr = 0; if (!attr_pri) { @@ -3547,9 +3388,9 @@ win_line ( char_attr = hl_combine_attr(spell_attr, char_attr); } - if (has_decorations && v > 0) { - int extmark_attr = decorations_redraw_col(wp->w_buffer, (colnr_T)v-1, - &decorations); + if (has_decor && v > 0) { + int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1, + &decor_state); if (extmark_attr != 0) { if (!attr_pri) { char_attr = hl_combine_attr(char_attr, extmark_attr); @@ -3782,7 +3623,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; } @@ -3879,7 +3720,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. */ @@ -3936,7 +3777,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 @@ -4046,11 +3887,13 @@ 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); + } else if (has_decor) { + VirtText *vp = decor_redraw_virt_text(wp->w_buffer, &decor_state); if (vp) { virt_text = *vp; do_virttext = true; @@ -4189,11 +4032,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; } @@ -4382,6 +4224,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 @@ -4467,7 +4310,7 @@ win_line ( } xfree(p_extra_free); - xfree(luatext); + xfree(err_text); return row; } @@ -4494,6 +4337,88 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) } } +// Get information needed to display the sign in line 'lnum' in window 'wp'. +// If 'nrcol' is TRUE, the sign is going to be displayed in the number column. +// Otherwise the sign is going to be displayed in the sign column. +static void get_sign_display_info( + bool nrcol, + win_T *wp, + linenr_T lnum, + int row, + int startrow, + int filler_lines, + int filler_todo, + int count, + int *c_extrap, + int *c_finalp, + char_u *extra, + size_t extra_size, + char_u **pp_extra, + int *n_extrap, + int *char_attrp, + int *draw_statep, + int *sign_idxp +) +{ + int text_sign; + + // Draw cells with the sign value or blank. + *c_extrap = ' '; + *c_finalp = NUL; + if (nrcol) { + *n_extrap = number_width(wp) + 1; + } else { + *char_attrp = win_hl_attr(wp, HLF_SC); + *n_extrap = win_signcol_width(wp); + } + + if (row == startrow + filler_lines && filler_todo <= 0) { + text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT, + *sign_idxp, count); + if (text_sign != 0) { + *pp_extra = sign_get_text(text_sign); + if (*pp_extra != NULL) { + *c_extrap = NUL; + *c_finalp = NUL; + + if (nrcol) { + int n, width = number_width(wp) - 2; + for (n = 0; n < width; n++) { + extra[n] = ' '; + } + extra[n] = NUL; + STRCAT(extra, *pp_extra); + STRCAT(extra, " "); + *pp_extra = extra; + *n_extrap = (int)STRLEN(*pp_extra); + } else { + int symbol_blen = (int)STRLEN(*pp_extra); + + // TODO(oni-link): Is sign text already extended to + // full cell width? + assert((size_t)win_signcol_width(wp) >= mb_string2cells(*pp_extra)); + // symbol(s) bytes + (filling spaces) (one byte each) + *n_extrap = symbol_blen + + (win_signcol_width(wp) - mb_string2cells(*pp_extra)); + + assert(extra_size > (size_t)symbol_blen); + memset(extra, ' ', extra_size); + memcpy(extra, *pp_extra, symbol_blen); + + *pp_extra = extra; + (*pp_extra)[*n_extrap] = NUL; + } + } + *char_attrp = sign_get_attr(text_sign, SIGN_TEXT); + } + } + + (*sign_idxp)++; + if (*sign_idxp < count) { + *draw_statep = WL_SIGN - 1; + } +} + /* * Check whether the given character needs redrawing: @@ -4713,8 +4638,8 @@ void status_redraw_all(void) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_status_height) { - wp->w_redr_status = TRUE; - redraw_later(VALID); + wp->w_redr_status = true; + redraw_later(wp, VALID); } } } @@ -4731,7 +4656,7 @@ void status_redraw_buf(buf_T *buf) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_status_height != 0 && wp->w_buffer == buf) { wp->w_redr_status = true; - redraw_later(VALID); + redraw_later(wp, VALID); } } } @@ -5255,8 +5180,8 @@ win_redr_custom ( char_u buf[MAXPATHL]; char_u *stl; char_u *p; - struct stl_hlrec hltab[STL_MAX_ITEM]; - StlClickRecord tabtab[STL_MAX_ITEM]; + stl_hlrec_t *hltab; + StlClickRecord *tabtab; int use_sandbox = false; win_T *ewp; int p_crb_save; @@ -5334,9 +5259,9 @@ win_redr_custom ( /* Make a copy, because the statusline may include a function call that * might change the option value and free the memory. */ stl = vim_strsave(stl); - width = build_stl_str_hl(ewp, buf, sizeof(buf), - stl, use_sandbox, - fillchar, maxwidth, hltab, tabtab); + width = + build_stl_str_hl(ewp, buf, sizeof(buf), stl, use_sandbox, + fillchar, maxwidth, &hltab, &tabtab); xfree(stl); ewp->w_p_crb = p_crb_save; @@ -5854,6 +5779,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. @@ -6146,7 +6077,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. @@ -7344,9 +7275,17 @@ int number_width(win_T *wp) ++n; } while (lnum > 0); - /* 'numberwidth' gives the minimal width plus one */ - if (n < wp->w_p_nuw - 1) + // 'numberwidth' gives the minimal width plus one + if (n < wp->w_p_nuw - 1) { n = wp->w_p_nuw - 1; + } + + // If 'signcolumn' is set to 'number' and there is a sign to display, then + // the minimal width for the number column is 2. + if (n < 2 && (wp->w_buffer->b_signlist != NULL) + && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) { + n = 2; + } wp->w_nrwidth_width = n; return n; @@ -7508,3 +7447,5 @@ win_T *get_win_by_grid_handle(handle_T handle) } return NULL; } + + diff --git a/src/nvim/search.c b/src/nvim/search.c index b105d99d7c..b25333c9fa 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; } @@ -4857,7 +4923,7 @@ search_line: && curwin != curwin_save && win_valid(curwin_save)) { /* Return cursor to where we were */ validate_cursor(); - redraw_later(VALID); + redraw_later(curwin, VALID); win_enter(curwin_save, true); } break; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 95257fe945..2444910bb3 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2207,8 +2207,12 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&entry); break; } - hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true, - true); + if (wms->hms[entry.data.history_item.histtype].hmll.size != 0) { + hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true, + true); + } else { + shada_free_shada_entry(&entry); + } break; } case kSDItemRegister: { @@ -4144,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/sign.c b/src/nvim/sign.c index ab5d04d39b..ffe51287c5 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -881,6 +881,17 @@ int sign_undefine_by_name(const char_u *name) return OK; } +static void may_force_numberwidth_recompute(buf_T *buf, int unplace) +{ + FOR_ALL_TAB_WINDOWS(tp, wp) + if (wp->w_buffer == buf + && (wp->w_p_nu || wp->w_p_rnu) + && (unplace || wp->w_nrwidth_width < 2) + && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) { + wp->w_nrwidth_line_count = 0; + } +} + /// List the signs matching 'name' static void sign_list_by_name(char_u *name) { @@ -935,6 +946,10 @@ int sign_place( } if (lnum > 0) { redraw_buf_line_later(buf, lnum); + + // When displaying signs in the 'number' column, if the width of the + // number column is less than 2, then force recomputing the width. + may_force_numberwidth_recompute(buf, false); } else { EMSG2(_("E885: Not possible to change sign %s"), sign_name); return FAIL; @@ -964,6 +979,13 @@ int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum) redraw_buf_line_later(buf, lnum); } + // When all the signs in a buffer are removed, force recomputing the + // number column width (if enabled) in all the windows displaying the + // buffer if 'signcolumn' is set to 'number' in that window. + if (buf->b_signlist == NULL) { + may_force_numberwidth_recompute(buf, true); + } + return OK; } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 95948dac78..636c71657d 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; @@ -2237,7 +2258,7 @@ char_u *did_set_spelllang(win_T *wp) theend: xfree(spl_copy); recursive = false; - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); return ret_msg; } @@ -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); } @@ -6855,7 +6877,7 @@ void ex_spelldump(exarg_T *eap) if (curbuf->b_ml.ml_line_count > 1) { ml_delete(curbuf->b_ml.ml_line_count, false); } - redraw_later(NOT_VALID); + redraw_later(curwin, NOT_VALID); } // Go through all possible words and: diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 034c580b3e..05667f060e 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -119,6 +119,7 @@ struct slang_S { bool sl_add; // true if it's a .add file. char_u *sl_fbyts; // case-folded word bytes + long sl_fbyts_len; // length of sl_fbyts idx_T *sl_fidxs; // case-folded word indexes char_u *sl_kbyts; // keep-case word bytes idx_T *sl_kidxs; // keep-case word indexes diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 6b9348e55d..b415a4635b 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; @@ -763,20 +764,24 @@ truncerr: } // <LWORDTREE> - res = spell_read_tree(fd, &lp->sl_fbyts, &lp->sl_fidxs, false, 0); - if (res != 0) + res = spell_read_tree(fd, &lp->sl_fbyts, &lp->sl_fbyts_len, + &lp->sl_fidxs, false, 0); + if (res != 0) { goto someerror; + } // <KWORDTREE> - res = spell_read_tree(fd, &lp->sl_kbyts, &lp->sl_kidxs, false, 0); - if (res != 0) + res = spell_read_tree(fd, &lp->sl_kbyts, NULL, &lp->sl_kidxs, false, 0); + if (res != 0) { goto someerror; + } // <PREFIXTREE> - res = spell_read_tree(fd, &lp->sl_pbyts, &lp->sl_pidxs, true, - lp->sl_prefixcnt); - if (res != 0) + res = spell_read_tree(fd, &lp->sl_pbyts, NULL, &lp->sl_pidxs, true, + lp->sl_prefixcnt); + if (res != 0) { goto someerror; + } // For a new file link it in the list of spell files. if (old_lp == NULL && lang != NULL) { @@ -919,8 +924,8 @@ void suggest_load_files(void) // <SUGWORDTREE>: <wordtree> // Read the trie with the soundfolded words. - if (spell_read_tree(fd, &slang->sl_sbyts, &slang->sl_sidxs, - false, 0) != 0) { + if (spell_read_tree(fd, &slang->sl_sbyts, NULL, &slang->sl_sidxs, + false, 0) != 0) { someerror: EMSG2(_("E782: error while reading .sug file: %s"), slang->sl_fname); @@ -983,15 +988,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) @@ -1627,10 +1634,12 @@ static int spell_read_tree ( FILE *fd, char_u **bytsp, + long *bytsp_len, idx_T **idxsp, bool prefixtree, // true for the prefix tree int prefixcnt // when "prefixtree" is true: prefix count ) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) { int idx; char_u *bp; @@ -1650,6 +1659,9 @@ spell_read_tree ( // Allocate the byte array. bp = xmalloc(len); *bytsp = bp; + if (bytsp_len != NULL) { + *bytsp_len = len; + } // Allocate the index array. ip = xcalloc(len, sizeof(*ip)); @@ -3037,9 +3049,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 @@ -4847,10 +4859,10 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) spin->si_blocks_cnt = 0; // Skip over any other NUL bytes (same word with different - // flags). - while (byts[n + 1] == 0) { - ++n; - ++curi[depth]; + // flags). But don't go over the end. + while (n + 1 < slang->sl_fbyts_len && byts[n + 1] == 0) { + n++; + curi[depth]++; } } else { // Normal char, go one level deeper. diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 4aa7c21ce4..5ce126a593 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -73,9 +73,9 @@ struct hl_group { RgbValue sg_rgb_fg; ///< RGB foreground color RgbValue sg_rgb_bg; ///< RGB background color RgbValue sg_rgb_sp; ///< RGB special color - uint8_t *sg_rgb_fg_name; ///< RGB foreground color name - uint8_t *sg_rgb_bg_name; ///< RGB background color name - uint8_t *sg_rgb_sp_name; ///< RGB special color name + char *sg_rgb_fg_name; ///< RGB foreground color name + char *sg_rgb_bg_name; ///< RGB background color name + char *sg_rgb_sp_name; ///< RGB special color name int sg_blend; ///< blend level (0-100 inclusive), -1 if unset }; @@ -3147,7 +3147,7 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) } // assume spell checking changed, force a redraw - redraw_win_later(curwin, NOT_VALID); + redraw_later(curwin, NOT_VALID); } /// Handle ":syntax iskeyword" command. @@ -3187,7 +3187,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) curbuf->b_p_isk = save_isk; } } - redraw_win_later(curwin, NOT_VALID); + redraw_later(curwin, NOT_VALID); } /* @@ -4296,8 +4296,9 @@ static void syn_cmd_include(exarg_T *eap, int syncing) current_syn_inc_tag = ++running_syn_inc_tag; prev_toplvl_grp = curwin->w_s->b_syn_topgrp; curwin->w_s->b_syn_topgrp = sgl_id; - if (source ? do_source(eap->arg, false, DOSO_NONE) == FAIL - : source_runtime(eap->arg, DIP_ALL) == FAIL) { + if (source + ? do_source(eap->arg, false, DOSO_NONE) == FAIL + : source_in_path(p_rtp, eap->arg, DIP_ALL) == FAIL) { EMSG2(_(e_notopen), eap->arg); } curwin->w_s->b_syn_topgrp = prev_toplvl_grp; @@ -5588,9 +5589,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); } @@ -6897,7 +6900,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } } else if (strcmp(key, "GUIFG") == 0) { - char_u **const namep = &HL_TABLE()[idx].sg_rgb_fg_name; + char **namep = &HL_TABLE()[idx].sg_rgb_fg_name; if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) { @@ -6907,8 +6910,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (*namep == NULL || STRCMP(*namep, arg) != 0) { xfree(*namep); if (strcmp(arg, "NONE") != 0) { - *namep = (char_u *)xstrdup(arg); - HL_TABLE()[idx].sg_rgb_fg = name_to_color((char_u *)arg); + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); } else { *namep = NULL; HL_TABLE()[idx].sg_rgb_fg = -1; @@ -6921,7 +6924,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) normal_fg = HL_TABLE()[idx].sg_rgb_fg; } } else if (STRCMP(key, "GUIBG") == 0) { - char_u **const namep = &HL_TABLE()[idx].sg_rgb_bg_name; + char **const namep = &HL_TABLE()[idx].sg_rgb_bg_name; if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) { @@ -6931,8 +6934,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (*namep == NULL || STRCMP(*namep, arg) != 0) { xfree(*namep); if (STRCMP(arg, "NONE") != 0) { - *namep = (char_u *)xstrdup(arg); - HL_TABLE()[idx].sg_rgb_bg = name_to_color((char_u *)arg); + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); } else { *namep = NULL; HL_TABLE()[idx].sg_rgb_bg = -1; @@ -6945,7 +6948,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) normal_bg = HL_TABLE()[idx].sg_rgb_bg; } } else if (strcmp(key, "GUISP") == 0) { - char_u **const namep = &HL_TABLE()[idx].sg_rgb_sp_name; + char **const namep = &HL_TABLE()[idx].sg_rgb_sp_name; if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) { @@ -6955,8 +6958,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (*namep == NULL || STRCMP(*namep, arg) != 0) { xfree(*namep); if (strcmp(arg, "NONE") != 0) { - *namep = (char_u *)xstrdup(arg); - HL_TABLE()[idx].sg_rgb_sp = name_to_color((char_u *)arg); + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); } else { *namep = NULL; HL_TABLE()[idx].sg_rgb_sp = -1; @@ -7150,13 +7153,32 @@ static void highlight_list_one(const int id) msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); } - if (!didh) - highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", ""); + if (!didh) { + highlight_list_arg(id, didh, LIST_STRING, 0, "cleared", ""); + } if (p_verbose > 0) { last_set_msg(sgp->sg_script_ctx); } } +Dictionary get_global_hl_defs(void) +{ + Dictionary rv = ARRAY_DICT_INIT; + for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { + Dictionary attrs = ARRAY_DICT_INIT; + struct hl_group *h = &HL_TABLE()[i - 1]; + if (h->sg_attr > 0) { + attrs = hlattrs2dict(syn_attr2entry(h->sg_attr), true); + } else if (h->sg_link > 0) { + const char *link = (const char *)HL_TABLE()[h->sg_link - 1].sg_name; + PUT(attrs, "link", STRING_OBJ(cstr_to_string(link))); + } + PUT(rv, (const char *)h->sg_name, DICTIONARY_OBJ(attrs)); + } + + return rv; +} + /// Outputs a highlight when doing ":hi MyHighlight" /// /// @param type one of \ref LIST_XXX @@ -7164,15 +7186,15 @@ static void highlight_list_one(const int id) /// @param sarg string used if \p type == LIST_STRING static bool highlight_list_arg( const int id, bool didh, const int type, int iarg, - char_u *const sarg, const char *const name) + char *const sarg, const char *const name) { - char_u buf[100]; + char buf[100]; if (got_int) { return false; } if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) { - char_u *ts = buf; + char *ts = buf; if (type == LIST_INT) { snprintf((char *)buf, sizeof(buf), "%d", iarg - 1); } else if (type == LIST_STRING) { @@ -7189,15 +7211,15 @@ static bool highlight_list_arg( } } - (void)syn_list_header(didh, (int)(vim_strsize(ts) + STRLEN(name) + 1), id, - false); + (void)syn_list_header(didh, (int)(vim_strsize((char_u *)ts) + STRLEN(name) + + 1), id, false); didh = true; if (!got_int) { if (*name != NUL) { MSG_PUTS_ATTR(name, HL_ATTR(HLF_D)); MSG_PUTS_ATTR("=", HL_ATTR(HLF_D)); } - msg_outtrans(ts); + msg_outtrans((char_u *)ts); } } return didh; @@ -7376,7 +7398,7 @@ static void set_hl_attr(int idx) at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1; at_en.hl_blend = sgp->sg_blend; - sgp->sg_attr = hl_get_syn_attr(idx+1, at_en); + sgp->sg_attr = hl_get_syn_attr(0, idx+1, at_en); // a cursor style uses this syn_id, make sure its attribute is updated. if (cursor_mode_uses_syn_id(idx+1)) { @@ -7539,11 +7561,17 @@ int syn_id2attr(int hl_id) struct hl_group *sgp; hl_id = syn_get_final_id(hl_id); - sgp = &HL_TABLE()[hl_id - 1]; /* index is ID minus one */ + int attr = ns_get_hl(-1, hl_id, false); + if (attr >= 0) { + return attr; + } + sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one return sgp->sg_attr; } + + /* * Translate a group ID to the final group ID (following links). */ @@ -7560,9 +7588,22 @@ int syn_get_final_id(int hl_id) * Look out for loops! Break after 100 links. */ for (count = 100; --count >= 0; ) { - sgp = &HL_TABLE()[hl_id - 1]; /* index is ID minus one */ - if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) + // ACHTUNG: when using "tmp" attribute (no link) the function might be + // called twice. it needs be smart enough to remember attr only to + // syn_id2attr time + int check = ns_get_hl(-1, hl_id, true); + if (check == 0) { + return 0; // how dare! it broke the link! + } else if (check > 0) { + hl_id = check; + continue; + } + + + sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one + if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { break; + } hl_id = sgp->sg_link; } @@ -8492,7 +8533,7 @@ color_name_table_T color_name_table[] = { /// /// @param[in] name string value to convert to RGB /// return the hex value or -1 if could not find a correct value -RgbValue name_to_color(const char_u *name) +RgbValue name_to_color(const char *name) { if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 81d1ef4c9f..c6b1a0d04c 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -2903,7 +2903,7 @@ static int jumpto_tag( && curwin != curwin_save && win_valid(curwin_save)) { /* Return cursor to where we were */ validate_cursor(); - redraw_later(VALID); + redraw_later(curwin, VALID); win_enter(curwin_save, true); } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 52d3eef810..39e2ca6171 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -233,7 +233,7 @@ Terminal *terminal_open(TerminalOptions opts) snprintf(var, sizeof(var), "terminal_color_%d", i); char *name = get_config_string(var); if (name) { - color_val = name_to_color((uint8_t *)name); + color_val = name_to_color(name); xfree(name); if (color_val != -1) { @@ -1060,7 +1060,7 @@ static bool send_mouse_event(Terminal *term, int c) curwin->w_redr_status = true; curwin = save_curwin; curbuf = curwin->w_buffer; - redraw_win_later(mouse_win, NOT_VALID); + redraw_later(mouse_win, NOT_VALID); invalidate_terminal(term, -1, -1); // Only need to exit focus if the scrolled window is the terminal window return mouse_win == curwin; diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim new file mode 100644 index 0000000000..7f6b7dcfec --- /dev/null +++ b/src/nvim/testdir/check.vim @@ -0,0 +1,99 @@ +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() +func CheckScreendump() + if !CanRunVimInTerminal() + 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/runnvim.sh b/src/nvim/testdir/runnvim.sh index 72f9254635..25cb8437b4 100755 --- a/src/nvim/testdir/runnvim.sh +++ b/src/nvim/testdir/runnvim.sh @@ -66,7 +66,7 @@ main() {( fi fi if test "$FAILED" = 1 ; then - travis_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name" + ci_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name" fi valgrind_check . if test -n "$LOG_DIR" ; then @@ -78,7 +78,7 @@ main() {( fi rm -f "$tlog" if test "$FAILED" = 1 ; then - travis_fold end "$NVIM_TEST_CURRENT_SUITE/$test_name" + ci_fold end "$NVIM_TEST_CURRENT_SUITE/$test_name" fi if test "$FAILED" = 1 ; then echo "Test $test_name failed, see output above and summary for more details" >> test.log diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index e249d499c4..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,13 @@ 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' +endif + " Prepare for calling test_garbagecollect_now(). let v:testing = 1 @@ -131,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. @@ -191,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 @@ -213,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= @@ -221,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 @@ -237,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) @@ -252,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) @@ -271,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 @@ -306,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()', \ ] @@ -330,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. @@ -383,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..0e20ac1593 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"}) @@ -240,7 +243,7 @@ func s:feedkeys(timer) call feedkeys('x', 'nt') endfunc -" Get $VIMPROG to run Vim executable. +" Get $VIMPROG to run the Vim executable. " The Makefile writes it as the first line in the "vimcmd" file. " Nvim: uses $NVIM_TEST_ARG0. func GetVimProg() @@ -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=' @@ -288,12 +291,22 @@ func GetVimCommandClean() let cmd = substitute(cmd, '-u NONE', '--clean', '') let cmd = substitute(cmd, '--headless', '', '') + " Force using utf-8, Vim may pick up something else from the environment. + " let cmd ..= ' --cmd "set enc=utf8" ' + " Optionally run Vim under valgrind " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd 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 +343,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_backup.vim b/src/nvim/testdir/test_backup.vim index fa10430613..ce2bfe72bc 100644 --- a/src/nvim/testdir/test_backup.vim +++ b/src/nvim/testdir/test_backup.vim @@ -1,7 +1,7 @@ " Tests for the backup function func Test_backup() - set backup backupdir=. + set backup backupdir=. backupskip= new call setline(1, ['line1', 'line2']) :f Xbackup.txt @@ -12,13 +12,13 @@ func Test_backup() let l = readfile('Xbackup.txt~') call assert_equal(['line1', 'line2'], l) bw! - set backup&vim backupdir&vim + set backup&vim backupdir&vim backupskip&vim call delete('Xbackup.txt') call delete('Xbackup.txt~') endfunc func Test_backup2() - set backup backupdir=.// + set backup backupdir=.// backupskip= new call setline(1, ['line1', 'line2', 'line3']) :f Xbackup.txt @@ -29,16 +29,16 @@ func Test_backup2() sp *Xbackup.txt~ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) let f=expand('%') - call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + call assert_match('%testdir%Xbackup.txt\~', f) bw! bw! call delete('Xbackup.txt') call delete(f) - set backup&vim backupdir&vim + set backup&vim backupdir&vim backupskip&vim endfunc func Test_backup2_backupcopy() - set backup backupdir=.// backupcopy=yes + set backup backupdir=.// backupcopy=yes backupskip= new call setline(1, ['line1', 'line2', 'line3']) :f Xbackup.txt @@ -49,10 +49,10 @@ func Test_backup2_backupcopy() sp *Xbackup.txt~ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) let f=expand('%') - call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + call assert_match('%testdir%Xbackup.txt\~', f) bw! bw! call delete('Xbackup.txt') call delete(f) - set backup&vim backupdir&vim backupcopy&vim + set backup&vim backupdir&vim backupcopy&vim backupskip&vim endfunc diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index debc9da46d..b6c2d1467e 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -127,4 +127,40 @@ func Test_cindent_case() bwipe! endfunc +func Test_cindent_pragma() + new + setl cindent ts=4 sw=4 + setl cino=Ps + + let code =<< trim [CODE] + { + #pragma omp parallel + { + #pragma omp task + foo(); + # pragma omp taskwait + } + } + [CODE] + + call append(0, code) + normal gg + normal =G + + let expected =<< trim [CODE] + { + #pragma omp parallel + { + #pragma omp task + foo(); + # pragma omp taskwait + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +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..e3c42a4fe3 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/legacy/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)) @@ -818,6 +842,25 @@ func Test_buffers_lastused() bwipeout bufc endfunc +func Test_cmdlineclear_tabenter() + " See test/functional/legacy/cmdline_spec.lua + CheckScreendump + + let lines =<< trim [SCRIPT] + call setline(1, range(30)) + [SCRIPT] + + call writefile(lines, 'XtestCmdlineClearTabenter') + let buf = RunVimInTerminal('-S XtestCmdlineClearTabenter', #{rows: 10}) + call term_wait(buf, 50) + " in one tab make the command line higher with CTRL-W - + call term_sendkeys(buf, ":tabnew\<cr>\<C-w>-\<C-w>-gtgt") + call VerifyScreenDump(buf, 'Test_cmdlineclear_tabenter', {}) + + call StopVimInTerminal(buf) + call delete('XtestCmdlineClearTabenter') +endfunc + " test that ";" works to find a match at the start of the first line func Test_zero_line_search() new 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 49bbe84869..f09a64c329 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -1,6 +1,7 @@ " Tests for diff mode source shared.vim source screendump.vim +source check.vim func Test_diff_fold_sync() enew! @@ -723,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', @@ -750,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"])', @@ -801,6 +964,34 @@ func Test_diff_closeoff() enew! endfunc +func Test_diff_rnu() + CheckScreendump + + let content =<< trim END + call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b']) + vnew + call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b']) + call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b']) + vnew + call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b']) + windo diffthis + setlocal number rnu foldcolumn=0 + END + call writefile(content, 'Xtest_diff_rnu') + let buf = RunVimInTerminal('-S Xtest_diff_rnu', {}) + + call VerifyScreenDump(buf, 'Test_diff_rnu_01', {}) + + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_diff_rnu_02', {}) + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_diff_rnu_03', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_diff_rnu') +endfunc + func Test_diff_and_scroll() " this was causing an ml_get error set ls=2 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..abad6983dc 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,75 @@ 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 + +func Test_read_invalid() + " set encoding=latin1 + " This was not properly checking for going past the end. + call assert_fails('r`=', 'E484') + set encoding=utf-8 +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 d440bdcb1e..9f7e153955 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -54,6 +54,7 @@ let s:filename_checks = { \ 'acedb': ['file.wrm'], \ 'ada': ['file.adb', 'file.ads', 'file.ada', 'file.gpr'], \ 'ahdl': ['file.tdf'], + \ 'aidl': ['file.aidl'], \ 'alsaconf': ['.asoundrc', '/usr/share/alsa/alsa.conf', '/etc/asound.conf'], \ 'aml': ['file.aml'], \ 'ampl': ['file.run'], @@ -72,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'], @@ -107,7 +109,7 @@ let s:filename_checks = { \ 'conaryrecipe': ['file.recipe'], \ 'conf': ['auto.master'], \ 'config': ['configure.in', 'configure.ac', 'Pipfile'], - \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi'], + \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'], \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], \ 'crm': ['file.crm'], \ 'cs': ['file.cs'], @@ -126,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'], @@ -139,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'], @@ -324,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'], @@ -453,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'], @@ -470,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'], @@ -524,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 @@ -543,6 +549,7 @@ func CheckItems(checks) bwipe! endfor endfor + set swapfile& endfunc func Test_filetype_detection() @@ -595,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_fold.vim b/src/nvim/testdir/test_fold.vim index 692f6e4780..3c90c45952 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -795,3 +795,24 @@ func Test_fold_delete_first_line() bwipe! set foldmethod& endfunc + +" this was crashing +func Test_move_no_folds() + new + fold + setlocal fdm=expr + normal zj + bwipe! +endfunc + +" this was crashing +func Test_fold_create_delete_create() + new + fold + fold + normal zd + fold + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab 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..8e2a987e74 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -199,9 +199,9 @@ func Test_dict_big() try let n = d[1500] catch - let str=substitute(v:exception, '\v(.{14}).*( \d{4}).*', '\1\2', '') + let str = substitute(v:exception, '\v(.{14}).*( "\d{4}").*', '\1\2', '') endtry - call assert_equal('Vim(let):E716: 1500', str) + call assert_equal('Vim(let):E716: "1500"', str) " lookup each items for i in range(1500) @@ -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_marks.vim b/src/nvim/testdir/test_marks.vim index 06b9dc9dab..66df57ea39 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -94,33 +94,43 @@ func Test_marks_cmd() new Xtwo call setline(1, ['ccc', 'ddd']) norm! $mcGmD + exe "norm! GVgg\<Esc>G" w! b Xone let a = split(execute('marks'), "\n") call assert_equal(9, len(a)) - call assert_equal('mark line col file/text', a[0]) - call assert_equal(" ' 2 0 bbb", a[1]) - call assert_equal(' a 1 0 aaa', a[2]) - call assert_equal(' B 2 2 bbb', a[3]) - call assert_equal(' D 2 0 Xtwo', a[4]) - call assert_equal(' " 1 0 aaa', a[5]) - call assert_equal(' [ 1 0 aaa', a[6]) - call assert_equal(' ] 2 0 bbb', a[7]) - call assert_equal(' . 2 0 bbb', a[8]) + call assert_equal(['mark line col file/text', + \ " ' 2 0 bbb", + \ ' a 1 0 aaa', + \ ' B 2 2 bbb', + \ ' D 2 0 Xtwo', + \ ' " 1 0 aaa', + \ ' [ 1 0 aaa', + \ ' ] 2 0 bbb', + \ ' . 2 0 bbb'], a) b Xtwo let a = split(execute('marks'), "\n") - call assert_equal(9, len(a)) - call assert_equal('mark line col file/text', a[0]) - call assert_equal(" ' 1 0 ccc", a[1]) - call assert_equal(' c 1 2 ccc', a[2]) - call assert_equal(' B 2 2 Xone', a[3]) - call assert_equal(' D 2 0 ddd', a[4]) - call assert_equal(' " 2 0 ddd', a[5]) - call assert_equal(' [ 1 0 ccc', a[6]) - call assert_equal(' ] 2 0 ddd', a[7]) - call assert_equal(' . 2 0 ddd', a[8]) + call assert_equal(11, len(a)) + call assert_equal(['mark line col file/text', + \ " ' 1 0 ccc", + \ ' c 1 2 ccc', + \ ' B 2 2 Xone', + \ ' D 2 0 ddd', + \ ' " 2 0 ddd', + \ ' [ 1 0 ccc', + \ ' ] 2 0 ddd', + \ ' . 2 0 ddd', + \ ' < 1 0 ccc', + \ ' > 2 0 ddd'], a) + norm! Gdd + w! + let a = split(execute('marks <>'), "\n") + call assert_equal(3, len(a)) + call assert_equal(['mark line col file/text', + \ ' < 1 0 ccc', + \ ' > 2 0 -invalid-'], a) b Xone delmarks aB 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..30239a90c2 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -1,20 +1,16 @@ " Tests for :messages, :echomsg, :echoerr -function Test_messages() +source shared.vim + +func Test_messages() let oldmore = &more try set nomore - " Avoid the "message maintainer" line. - let $LANG = '' - let $LC_ALL = '' - let $LC_MESSAGES = '' - let $LC_COLLATE = '' let arr = map(range(10), '"hello" . v:val') for s in arr echomsg s | redraw endfor - let result = '' " get last two messages redir => result @@ -25,22 +21,17 @@ function Test_messages() " clear messages without last one 1messages clear - redir => result - redraw | messages - redir END - let msg_list = split(result, "\n") + let msg_list = GetMessages() call assert_equal(['hello9'], msg_list) " clear all messages messages clear - redir => result - redraw | messages - redir END - call assert_equal('', result) + let msg_list = GetMessages() + call assert_equal([], msg_list) finally let &more = oldmore endtry -endfunction +endfunc " Patch 7.4.1696 defined the "clearmode()" command for clearing the mode " indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message @@ -74,6 +65,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..215065f941 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 @@ -349,4 +470,17 @@ func Test_mkvimrc() call delete('Xtestvimrc') endfunc +func Test_scrolloff() + set sessionoptions+=localoptions + setlocal so=1 siso=1 + mksession! Xtest_mks.out + setlocal so=-1 siso=-1 + source Xtest_mks.out + call assert_equal(1, &l:so) + call assert_equal(1, &l:siso) + call delete('Xtest_mks.out') + setlocal so& siso& + set sessionoptions& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 04a5c62f66..10e16f4198 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -267,7 +267,6 @@ func Test_set_errors() call assert_fails('set commentstring=x', 'E537:') call assert_fails('set complete=x', 'E539:') call assert_fails('set statusline=%{', 'E540:') - call assert_fails('set statusline=' . repeat("%p", 81), 'E541:') call assert_fails('set statusline=%(', 'E542:') if has('cursorshape') " This invalid value for 'guicursor' used to cause Vim to crash. @@ -576,3 +575,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 35555ca9d3..049163890e 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) @@ -554,6 +553,33 @@ func s:test_xhelpgrep(cchar) " This wipes out the buffer, make sure that doesn't cause trouble. Xclose + " When the current window is vertically split, jumping to a help match + " should open the help window at the top. + only | enew + let w1 = win_getid() + vert new + let w2 = win_getid() + Xnext + let w3 = win_getid() + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + " See jump_to_help_window() for details + let w2_width = winwidth(w2) + if w2_width != &columns && w2_width < 80 + call assert_equal(['col', [['leaf', w3], + \ ['row', [['leaf', w2], ['leaf', w1]]]]], winlayout()) + else + call assert_equal(['row', [['col', [['leaf', w3], ['leaf', w2]]], + \ ['leaf', w1]]] , winlayout()) + endif + + new | only + set buftype=help + set modified + call assert_fails('Xnext', 'E37:') + set nomodified + new | only + if a:cchar == 'l' " When a help window is present, running :lhelpgrep should reuse the " help window and not the current window @@ -1257,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') @@ -1570,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) @@ -1847,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() @@ -2097,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() @@ -2104,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) @@ -2423,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) @@ -3315,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 @@ -3405,6 +3595,41 @@ 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:') + " run the test with a help window already open + help + wincmd w + 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 @@ -3430,6 +3655,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 @@ -3730,6 +3967,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 @@ -3781,11 +4064,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() @@ -3994,4 +4368,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_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index f48458566b..4466ad436a 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -32,6 +32,9 @@ func Test_equivalence_re2() endfunc func s:classes_test() + if has('win32') + set iskeyword=@,48-57,_,192-255 + endif set isprint=@,161-255 call assert_equal('Motörhead', matchstr('Motörhead', '[[:print:]]\+')) @@ -51,6 +54,12 @@ func s:classes_test() let tabchar = '' let upperchars = '' let xdigitchars = '' + let identchars = '' + let identchars1 = '' + let kwordchars = '' + let kwordchars1 = '' + let fnamechars = '' + let fnamechars1 = '' let i = 1 while i <= 255 let c = nr2char(i) @@ -102,6 +111,24 @@ func s:classes_test() if c =~ '[[:xdigit:]]' let xdigitchars .= c endif + if c =~ '[[:ident:]]' + let identchars .= c + endif + if c =~ '\i' + let identchars1 .= c + endif + if c =~ '[[:keyword:]]' + let kwordchars .= c + endif + if c =~ '\k' + let kwordchars1 .= c + endif + if c =~ '[[:fname:]]' + let fnamechars .= c + endif + if c =~ '\f' + let fnamechars1 .= c + endif let i += 1 endwhile @@ -121,6 +148,37 @@ func s:classes_test() call assert_equal("\t\n\x0b\f\r ", spacechars) call assert_equal("\t", tabchar) call assert_equal('0123456789ABCDEFabcdef', xdigitchars) + + if has('win32') + let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
¡¢£¤¥¦§µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' + let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + elseif has('ebcdic') + let identchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + let kwordchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + else + let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + endif + + if has('win32') + let fnamechars_ok = '!#$%+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]_abcdefghijklmnopqrstuvwxyz{}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + elseif has('amiga') + let fnamechars_ok = '$+,-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + elseif has('vms') + let fnamechars_ok = '#$%+,-./0123456789:;<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + elseif has('ebcdic') + let fnamechars_ok = '#$%+,-./=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + else + let fnamechars_ok = '#$%+,-./0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' + endif + + call assert_equal(identchars_ok, identchars) + call assert_equal(kwordchars_ok, kwordchars) + call assert_equal(fnamechars_ok, fnamechars) + + call assert_equal(identchars1, identchars) + call assert_equal(kwordchars1, kwordchars) + call assert_equal(fnamechars1, fnamechars) endfunc func Test_classes_re1() @@ -351,4 +409,128 @@ func Test_regexp_ignore_case() set regexpengine& endfunc +" Tests for regexp with multi-byte encoding and various magic settings +func Run_regexp_multibyte_magic() + let text =<< trim END + 1 a aa abb abbccc + 2 d dd dee deefff + 3 g gg ghh ghhiii + 4 j jj jkk jkklll + 5 m mm mnn mnnooo + 6 x ^aa$ x + 7 (a)(b) abbaa + 8 axx [ab]xx + 9 หม่x อมx + a อมx หม่x + b ちカヨは + c x ¬€x + d 天使x + e y + f z + g a啷bb + j 0123❤x + k combinations + l äö üᾱ̆́ + END + + new + call setline(1, text) + exe 'normal /a*b\{2}c\+/e' .. "\<CR>x" + call assert_equal('1 a aa abb abbcc', getline('.')) + exe 'normal /\Md\*e\{2}f\+/e' .. "\<CR>x" + call assert_equal('2 d dd dee deeff', getline('.')) + set nomagic + exe 'normal /g\*h\{2}i\+/e' .. "\<CR>x" + call assert_equal('3 g gg ghh ghhii', getline('.')) + exe 'normal /\mj*k\{2}l\+/e' .. "\<CR>x" + call assert_equal('4 j jj jkk jkkll', getline('.')) + exe 'normal /\vm*n{2}o+/e' .. "\<CR>x" + call assert_equal('5 m mm mnn mnnoo', getline('.')) + exe 'normal /\V^aa$/' .. "\<CR>x" + call assert_equal('6 x aa$ x', getline('.')) + set magic + exe 'normal /\v(a)(b)\2\1\1/e' .. "\<CR>x" + call assert_equal('7 (a)(b) abba', getline('.')) + exe 'normal /\V[ab]\(\[xy]\)\1' .. "\<CR>x" + call assert_equal('8 axx ab]xx', getline('.')) + + " search for multi-byte without composing char + exe 'normal /ม' .. "\<CR>x" + call assert_equal('9 หม่x อx', getline('.')) + + " search for multi-byte with composing char + exe 'normal /ม่' .. "\<CR>x" + call assert_equal('a อมx หx', getline('.')) + + " find word by change of word class + exe 'normal /ち\<カヨ\>は' .. "\<CR>x" + call assert_equal('b カヨは', getline('.')) + + " Test \%u, [\u] and friends + " c + exe 'normal /\%u20ac' .. "\<CR>x" + call assert_equal('c x ¬x', getline('.')) + " d + exe 'normal /[\u4f7f\u5929]\+' .. "\<CR>x" + call assert_equal('d 使x', getline('.')) + " e + exe 'normal /\%U12345678' .. "\<CR>x" + call assert_equal('e y', getline('.')) + " f + exe 'normal /[\U1234abcd\u1234\uabcd]' .. "\<CR>x" + call assert_equal('f z', getline('.')) + " g + exe 'normal /\%d21879b' .. "\<CR>x" + call assert_equal('g abb', getline('.')) + + " j Test backwards search from a multi-byte char + exe "normal /x\<CR>x?.\<CR>x" + call assert_equal('j 012❤', getline('.')) + " k + let @w=':%s#comb[i]nations#œ̄ṣ́m̥̄ᾱ̆́#g' + @w + call assert_equal('k œ̄ṣ́m̥̄ᾱ̆́', getline(18)) + + close! +endfunc + +func Test_regexp_multibyte_magic() + set regexpengine=1 + call Run_regexp_multibyte_magic() + set regexpengine=2 + call Run_regexp_multibyte_magic() + set regexpengine& +endfunc + +" Test for 7.3.192 +" command ":s/ \?/ /g" splits multi-byte characters into bytes +func Test_split_multibyte_to_bytes() + new + call setline(1, 'l äö üᾱ̆́') + s/ \?/ /g + call assert_equal(' l ä ö ü ᾱ̆́', getline(1)) + close! +endfunc + +" Test for matchstr() with multibyte characters +func Test_matchstr_multibyte() + new + call assert_equal('ב', matchstr("אבגד", ".", 0, 2)) + call assert_equal('בג', matchstr("אבגד", "..", 0, 2)) + call assert_equal('א', matchstr("אבגד", ".", 0, 0)) + call assert_equal('ג', matchstr("אבגד", ".", 4, -1)) + close! +endfunc + +" Test for 7.4.636 +" A search with end offset gets stuck at end of file. +func Test_search_with_end_offset() + new + call setline(1, ['', 'dog(a', 'cat(']) + exe "normal /(/e+" .. "\<CR>" + normal "ayn + call assert_equal("a\ncat(", @a) + close! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index d20f8d1eef..19a7c6c9d0 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -167,6 +167,75 @@ func Test_set_register() enew! endfunc +func Test_v_register() + enew + call setline(1, 'nothing') + + func s:Put() + let s:register = v:register + exec 'normal! "' .. v:register .. 'P' + endfunc + nnoremap <buffer> <plug>(test) :<c-u>call s:Put()<cr> + nmap <buffer> S <plug>(test) + + let @z = "testz\n" + let @" = "test@\n" + + let s:register = '' + call feedkeys('"_ddS', 'mx') + call assert_equal('test@', getline('.')) " fails before 8.2.0929 + call assert_equal('"', s:register) " fails before 8.2.0929 + + let s:register = '' + call feedkeys('"zS', 'mx') + call assert_equal('z', s:register) + + let s:register = '' + call feedkeys('"zSS', 'mx') + call assert_equal('"', s:register) + + let s:register = '' + call feedkeys('"_S', 'mx') + call assert_equal('_', s:register) + + let s:register = '' + normal "_ddS + call assert_equal('"', s:register) " fails before 8.2.0929 + call assert_equal('test@', getline('.')) " fails before 8.2.0929 + + let s:register = '' + execute 'normal "z:call' "s:Put()\n" + call assert_equal('z', s:register) + call assert_equal('testz', getline('.')) + + " Test operator and omap + let @b = 'testb' + func s:OpFunc(...) + let s:register2 = v:register + endfunc + set opfunc=s:OpFunc + + normal "bg@l + normal S + call assert_equal('"', s:register) " fails before 8.2.0929 + call assert_equal('b', s:register2) + + func s:Motion() + let s:register1 = v:register + normal! l + endfunc + onoremap <buffer> Q :<c-u>call s:Motion()<cr> + + normal "bg@Q + normal S + call assert_equal('"', s:register) + call assert_equal('b', s:register1) + call assert_equal('"', s:register2) + + set opfunc& + bwipe! +endfunc + func Test_ve_blockpaste() new set ve=all diff --git a/src/nvim/testdir/test_restricted.vim b/src/nvim/testdir/test_restricted.vim deleted file mode 100644 index a29f7c33d3..0000000000 --- a/src/nvim/testdir/test_restricted.vim +++ /dev/null @@ -1,103 +0,0 @@ -" Test for "rvim" or "vim -Z" - -source shared.vim - -"if has('win32') && has('gui') -" " Win32 GUI shows a dialog instead of displaying the error in the last line. -" finish -"endif - -func Test_restricted() - call Run_restricted_test('!ls', 'E145:') -endfunc - -func Run_restricted_test(ex_cmd, error) - let cmd = GetVimCommand('Xrestricted') - if cmd == '' - return - endif - - " Use a VimEnter autocommand to avoid that the error message is displayed in - " a dialog with an OK button. - call writefile([ - \ "func Init()", - \ " silent! " . a:ex_cmd, - \ " call writefile([v:errmsg], 'Xrestrout')", - \ " qa!", - \ "endfunc", - \ "au VimEnter * call Init()", - \ ], 'Xrestricted') - call system(cmd . ' -Z') - call assert_match(a:error, join(readfile('Xrestrout'))) - - call delete('Xrestricted') - call delete('Xrestrout') -endfunc - -func Test_restricted_lua() - if !has('lua') - throw 'Skipped: Lua is not supported' - endif - call Run_restricted_test('lua print("Hello, Vim!")', 'E981:') - call Run_restricted_test('luado return "hello"', 'E981:') - call Run_restricted_test('luafile somefile', 'E981:') - call Run_restricted_test('call luaeval("expression")', 'E145:') -endfunc - -func Test_restricted_mzscheme() - if !has('mzscheme') - throw 'Skipped: MzScheme is not supported' - endif - call Run_restricted_test('mzscheme statement', 'E981:') - call Run_restricted_test('mzfile somefile', 'E981:') - call Run_restricted_test('call mzeval("expression")', 'E145:') -endfunc - -func Test_restricted_perl() - if !has('perl') - throw 'Skipped: Perl is not supported' - endif - " TODO: how to make Safe mode fail? - " call Run_restricted_test('perl system("ls")', 'E981:') - " call Run_restricted_test('perldo system("hello")', 'E981:') - " call Run_restricted_test('perlfile somefile', 'E981:') - " call Run_restricted_test('call perleval("system(\"ls\")")', 'E145:') -endfunc - -func Test_restricted_python() - if !has('python') - throw 'Skipped: Python is not supported' - endif - call Run_restricted_test('python print "hello"', 'E981:') - call Run_restricted_test('pydo return "hello"', 'E981:') - call Run_restricted_test('pyfile somefile', 'E981:') - call Run_restricted_test('call pyeval("expression")', 'E145:') -endfunc - -func Test_restricted_python3() - if !has('python3') - throw 'Skipped: Python3 is not supported' - endif - call Run_restricted_test('py3 print "hello"', 'E981:') - call Run_restricted_test('py3do return "hello"', 'E981:') - call Run_restricted_test('py3file somefile', 'E981:') - call Run_restricted_test('call py3eval("expression")', 'E145:') -endfunc - -func Test_restricted_ruby() - if !has('ruby') - throw 'Skipped: Ruby is not supported' - endif - call Run_restricted_test('ruby print "Hello"', 'E981:') - call Run_restricted_test('rubydo print "Hello"', 'E981:') - call Run_restricted_test('rubyfile somefile', 'E981:') -endfunc - -func Test_restricted_tcl() - if !has('tcl') - throw 'Skipped: Tcl is not supported' - endif - call Run_restricted_test('tcl puts "Hello"', 'E981:') - call Run_restricted_test('tcldo puts "Hello"', 'E981:') - call Run_restricted_test('tclfile somefile', 'E981:') -endfunc diff --git a/src/nvim/testdir/test_ruby.vim b/src/nvim/testdir/test_ruby.vim index 64199570a9..9c74c35049 100644 --- a/src/nvim/testdir/test_ruby.vim +++ b/src/nvim/testdir/test_ruby.vim @@ -1,8 +1,7 @@ " Tests for ruby interface -if !has('ruby') - finish -end +source check.vim +CheckFeature ruby func Test_ruby_change_buffer() call setline(line('$'), ['1 line 1']) @@ -11,37 +10,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 +24,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() @@ -68,15 +35,359 @@ func Test_rubyfile() call delete(tempfile) endfunc -func Test_set_cursor() +func Test_ruby_set_cursor() " Check that setting the cursor position works. new call setline(1, ['first line', 'second line']) 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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_buffer_line_number() + new + call setline(1, ['one', 'two', 'three']) + 2 + call assert_equal(2, rubyeval('$curbuf.line_number')) + + bwipe! +endfunc + +func Test_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_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')) + + if has('float') + call assert_equal(1.23, rubyeval('Vim::evaluate("1.23")')) + call assert_equal('Float', rubyeval('Vim::evaluate("1.23").class')) + endif + + 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_ruby_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_ruby_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_ruby_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_ruby_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_ruby_p() + ruby p 'Just a test' + let messages = GetMessages() + 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 = GetMessages() + 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_signs.vim b/src/nvim/testdir/test_signs.vim index 8b1927e4f0..2b7672f1af 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -1742,3 +1742,117 @@ func Test_sign_cursor_position() call StopVimInTerminal(buf) call delete('XtestSigncolumn') endfunc + +" Return the 'len' characters in screen starting from (row,col) +func s:ScreenLine(row, col, len) + let s = '' + for i in range(a:len) + let s .= nr2char(screenchar(a:row, a:col + i)) + endfor + return s +endfunc + +" Test for 'signcolumn' set to 'number'. +func Test_sign_numcol() + new + call append(0, "01234") + " With 'signcolumn' set to 'number', make sure sign is displayed in the + " number column and line number is not displayed. + set numberwidth=2 + set number + set signcolumn=number + sign define sign1 text==> + sign place 10 line=1 name=sign1 + sign define sign2 text=V + redraw! + call assert_equal("=> 01234", s:ScreenLine(1, 1, 8)) + + " With 'signcolumn' set to 'number', when there is no sign, make sure line + " number is displayed in the number column + sign unplace 10 + redraw! + call assert_equal("1 01234", s:ScreenLine(1, 1, 7)) + + " Disable number column. Check whether sign is displayed in the sign column + set numberwidth=4 + set nonumber + sign place 10 line=1 name=sign1 + redraw! + call assert_equal("=>01234", s:ScreenLine(1, 1, 7)) + + " Enable number column. Check whether sign is displayed in the number column + set number + redraw! + call assert_equal(" => 01234", s:ScreenLine(1, 1, 9)) + + " Disable sign column. Make sure line number is displayed + set signcolumn=no + redraw! + call assert_equal(" 1 01234", s:ScreenLine(1, 1, 9)) + + " Enable auto sign column. Make sure both sign and line number are displayed + set signcolumn=auto + redraw! + call assert_equal("=> 1 01234", s:ScreenLine(1, 1, 11)) + + " Test displaying signs in the number column with width 1 + call sign_unplace('*') + call append(1, "abcde") + call append(2, "01234") + " Enable number column with width 1 + set number numberwidth=1 signcolumn=auto + redraw! + call assert_equal("3 01234", s:ScreenLine(3, 1, 7)) + " Place a sign and make sure number column width remains the same + sign place 20 line=2 name=sign1 + redraw! + call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9)) + " Set 'signcolumn' to 'number', make sure the number column width increases + set signcolumn=number + redraw! + call assert_equal("=> abcde", s:ScreenLine(2, 1, 8)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8)) + " Set 'signcolumn' to 'auto', make sure the number column width is 1. + set signcolumn=auto + redraw! + call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9)) + " Set 'signcolumn' to 'number', make sure the number column width is 2. + set signcolumn=number + redraw! + call assert_equal("=> abcde", s:ScreenLine(2, 1, 8)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8)) + " Disable 'number' column + set nonumber + redraw! + call assert_equal("=>abcde", s:ScreenLine(2, 1, 7)) + call assert_equal(" 01234", s:ScreenLine(3, 1, 7)) + " Enable 'number' column + set number + redraw! + call assert_equal("=> abcde", s:ScreenLine(2, 1, 8)) + call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8)) + " Remove the sign and make sure the width of the number column is 1. + call sign_unplace('', {'id' : 20}) + redraw! + call assert_equal("3 01234", s:ScreenLine(3, 1, 7)) + " When the first sign is placed with 'signcolumn' set to number, verify that + " the number column width increases + sign place 30 line=1 name=sign1 + redraw! + call assert_equal("=> 01234", s:ScreenLine(1, 1, 8)) + call assert_equal(" 2 abcde", s:ScreenLine(2, 1, 8)) + " Add sign with multi-byte text + set numberwidth=4 + sign place 40 line=2 name=sign2 + redraw! + call assert_equal(" => 01234", s:ScreenLine(1, 1, 9)) + call assert_equal(" V abcde", s:ScreenLine(2, 1, 9)) + + sign unplace * group=* + sign undefine sign1 + set signcolumn& + set number& + enew! | close +endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index e5eaa01e92..ab8a998bb8 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -1,10 +1,13 @@ " Test spell checking " Note: this file uses latin1 encoding, but is used with utf-8 encoding. +source check.vim if !has('spell') finish endif +source screendump.vim + func TearDown() set nospell call delete('Xtest.aff') @@ -76,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')) @@ -110,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 @@ -477,6 +522,44 @@ func RunGoodBad(good, bad, expected_words, expected_bad_words) bwipe! endfunc +func Test_spell_screendump() + CheckScreendump + + let lines =<< trim END + call setline(1, [ + \ "This is some text without any spell errors. Everything", + \ "should just be black, nothing wrong here.", + \ "", + \ "This line has a sepll error. and missing caps.", + \ "And and this is the the duplication.", + \ "with missing caps here.", + \ ]) + set spell spelllang=en_nz + END + call writefile(lines, 'XtestSpell') + let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_spell_1', {}) + + let lines =<< trim END + call setline(1, [ + \ "This is some text without any spell errors. Everything", + \ "should just be black, nothing wrong here.", + \ "", + \ "This line has a sepll error. and missing caps.", + \ "And and this is the the duplication.", + \ "with missing caps here.", + \ ]) + set spell spelllang=en_nz + END + call writefile(lines, 'XtestSpell') + let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_spell_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestSpell') +endfunc + let g:test_data_aff1 = [ \"SET ISO8859-1", \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", diff --git a/src/nvim/testdir/test_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..4e38f7ebd8 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -354,6 +354,21 @@ func Test_statusline() delfunc GetNested delfunc GetStatusLine + " Test statusline works with 80+ items + function! StatusLabel() + redrawstatus + return '[label]' + endfunc + let statusline = '%{StatusLabel()}' + for i in range(150) + let statusline .= '%#TabLine' . (i % 2 == 0 ? 'Fill' : 'Sel') . '#' . string(i)[0] + endfor + let &statusline = statusline + redrawstatus + set statusline& + delfunc StatusLabel + + " Check statusline in current and non-current window " with the 'fillchars' option. set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:- @@ -412,3 +427,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_tabline.vim b/src/nvim/testdir/test_tabline.vim index f24552088b..117d962d08 100644 --- a/src/nvim/testdir/test_tabline.vim +++ b/src/nvim/testdir/test_tabline.vim @@ -64,3 +64,28 @@ func Test_redrawtabline() let &showtabline = showtabline_save au! Bufadd endfunc + +function EmptyTabname() + return "" +endfunction + +function MakeTabLine() abort + let titles = map(range(1, tabpagenr('$')), '"%( %" . v:val . "T%{EmptyTabname()}%T %)"') + let sep = 'あ' + let tabpages = join(titles, sep) + return tabpages .. sep .. '%=%999X X' +endfunction + +func Test_tabline_empty_group() + " this was reading invalid memory + set tabline=%!MakeTabLine() + tabnew + redraw! + + tabclose + set tabline= +endfunc + + + +" vim: shiftwidth=2 sts=2 expandtab 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/testdir/test_windows_home.vim b/src/nvim/testdir/test_windows_home.vim index 2e311b9aa5..3c2db01444 100644 --- a/src/nvim/testdir/test_windows_home.vim +++ b/src/nvim/testdir/test_windows_home.vim @@ -1,8 +1,7 @@ " Test for $HOME on Windows. -if !has('win32') - finish -endif +source check.vim +CheckMSWindows let s:env = {} 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..17f7e16740 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 handle_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/ui_compositor.c b/src/nvim/ui_compositor.c index bc7fee7e96..9d3ec21949 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -26,6 +26,7 @@ #include "nvim/screen.h" #include "nvim/syntax.h" #include "nvim/api/private/helpers.h" +#include "nvim/lua/executor.h" #include "nvim/os/os.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 97018f6c02..a8b8f7aa50 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); @@ -2450,7 +2455,7 @@ static void u_undo_end( { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == curbuf && wp->w_p_cole > 0) { - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } } } @@ -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..7296c74109 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, @@ -1970,11 +1970,21 @@ bool has_nvim_version(const char *const version_str) /// /// @return true if patch `n` has been included. bool has_vim_patch(int n) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (int i = 0; included_patches[i] != 0; i++) { - if (included_patches[i] == n) { + // Perform a binary search. + int l = 0; + int h = (int)(ARRAY_SIZE(included_patches)) - 1; + while (l < h) { + const int m = (l + h) / 2; + if (included_patches[m] == n) { return true; } + if (included_patches[m] < n) { + h = m; + } else { + l = m + 1; + } } return false; } @@ -2119,13 +2129,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/vim.h b/src/nvim/vim.h index 832703e83d..900f2acd81 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -259,7 +259,6 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() #define PERROR(msg) (void) emsgf("%s: %s", msg, strerror(errno)) #define SHOWCMD_COLS 10 // columns needed by shown command -#define STL_MAX_ITEM 80 // max nr of %<flag> in statusline #include "nvim/path.h" @@ -316,7 +315,8 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() #define LOWEST_WIN_ID 1000 // BSD is supposed to cover FreeBSD and similar systems. -#if (defined(BSD) || defined(__FreeBSD_kernel__)) && defined(S_ISCHR) +#if (defined(BSD) || defined(__FreeBSD_kernel__)) \ + && (defined(S_ISCHR) || defined(S_IFCHR)) # define OPEN_CHR_FILES #endif 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..3429e3df70 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -606,7 +606,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) win_config_float(wp, fconfig); wp->w_pos_changed = true; - redraw_win_later(wp, VALID); + redraw_later(wp, VALID); return wp; } @@ -679,7 +679,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig) wp->w_pos_changed = true; if (change_external) { wp->w_hl_needs_update = true; - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } } @@ -764,7 +764,7 @@ static void ui_ext_win_position(win_T *wp) wp->w_grid.focusable = wp->w_float_config.focusable; if (!valid) { wp->w_grid.valid = false; - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } } } else { @@ -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 - */ - redraw_win_later(wp, NOT_VALID); - wp->w_redr_status = TRUE; - redraw_win_later(oldwin, NOT_VALID); - oldwin->w_redr_status = TRUE; + // Both windows need redrawing. Update all status lines, in case they + // show something related to the window count or position. + redraw_later(wp, NOT_VALID); + redraw_later(oldwin, NOT_VALID); + status_redraw_all(); if (need_status) { msg_row = Rows - 1; @@ -1825,8 +1823,8 @@ static void win_exchange(long Prenum) (void)win_comp_pos(); /* recompute window positions */ win_enter(wp, true); - redraw_later(NOT_VALID); - redraw_win_later(wp, NOT_VALID); + redraw_later(curwin, NOT_VALID); + redraw_later(wp, NOT_VALID); } // rotate windows: if upwards true the second window becomes the first one @@ -1998,8 +1996,8 @@ void win_move_after(win_T *win1, win_T *win2) win_append(win2, win1); frame_append(win2->w_frame, win1->w_frame); - (void)win_comp_pos(); /* recompute w_winrow for all windows */ - redraw_later(NOT_VALID); + (void)win_comp_pos(); // recompute w_winrow for all windows + redraw_later(curwin, NOT_VALID); } win_enter(win1, false); @@ -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. @@ -3636,7 +3635,7 @@ void curwin_init(void) void win_init_empty(win_T *wp) { - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); wp->w_lines_valid = 0; wp->w_cursor.lnum = 1; wp->w_curswant = wp->w_cursor.col = 0; @@ -4050,7 +4049,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au prevwin = next_prevwin; last_status(false); // status line may appear or disappear - (void)win_comp_pos(); // recompute w_winrow for all windows + const int row = win_comp_pos(); // recompute w_winrow for all windows diff_need_scrollbind = true; /* The tabpage line may have appeared or disappeared, may need to resize @@ -4061,11 +4060,20 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au clear_cmdline = true; } p_ch = curtab->tp_ch_used; - if (curtab->tp_old_Rows != Rows || (old_off != firstwin->w_winrow - )) + + // When cmdheight is changed in a tab page with '<C-w>-', cmdline_row is + // changed but p_ch and tp_ch_used are not changed. Thus we also need to + // check cmdline_row. + if ((row < cmdline_row) && (cmdline_row <= Rows - p_ch)) { + clear_cmdline = true; + } + + if (curtab->tp_old_Rows != Rows || (old_off != firstwin->w_winrow)) { shell_new_rows(); - if (curtab->tp_old_Columns != Columns && starting == 0) - shell_new_columns(); /* update window widths */ + } + if (curtab->tp_old_Columns != Columns && starting == 0) { + shell_new_columns(); // update window widths + } lastused_tabpage = old_curtab; @@ -4559,7 +4567,7 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, if (os_chdir(new_dir) == 0) { if (!p_acd && !strequal(new_dir, cwd)) { do_autocmd_dirchanged(new_dir, curwin->w_localdir - ? kCdScopeWindow : kCdScopeTab); + ? kCdScopeWindow : kCdScopeTab, true); } shorten_fnames(true); } @@ -4568,7 +4576,7 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, // directory: Change to the global directory. if (os_chdir((char *)globaldir) == 0) { if (!p_acd && !strequal((char *)globaldir, cwd)) { - do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal); + do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, true); } } XFREE_CLEAR(globaldir); @@ -4588,10 +4596,11 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, } maketitle(); - curwin->w_redr_status = TRUE; - redraw_tabline = TRUE; - if (restart_edit) - redraw_later(VALID); /* causes status line redraw */ + curwin->w_redr_status = true; + redraw_tabline = true; + if (restart_edit) { + redraw_later(curwin, VALID); // causes status line redraw + } if (HL_ATTR(HLF_INACTIVE) || (prevwin && prevwin->w_hl_ids[HLF_INACTIVE]) @@ -4966,6 +4975,27 @@ void shell_new_columns(void) win_reconfig_floats(); // The size of floats might change } +/// Check if "wp" has scrolled since last time it was checked +/// @param wp the window to check +bool win_did_scroll(win_T *wp) +{ + return (curwin->w_last_topline != curwin->w_topline + || curwin->w_last_leftcol != curwin->w_leftcol + || curwin->w_last_width != curwin->w_width + || curwin->w_last_height != curwin->w_height); +} + +/// Trigger WinScrolled autocmd +void do_autocmd_winscrolled(win_T *wp) +{ + apply_autocmds(EVENT_WINSCROLLED, NULL, NULL, false, curbuf); + + wp->w_last_topline = wp->w_topline; + wp->w_last_leftcol = wp->w_leftcol; + wp->w_last_width = wp->w_width; + wp->w_last_height = wp->w_height; +} + /* * Save the size of all windows in "gap". */ @@ -5057,7 +5087,7 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) /* position changed, redraw */ wp->w_winrow = *row; wp->w_wincol = *col; - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); wp->w_redr_status = true; wp->w_pos_changed = true; } @@ -5110,7 +5140,7 @@ void win_setheight_win(int height, win_T *win) if (win->w_floating) { win->w_float_config.height = height; win_config_float(win, win->w_float_config); - redraw_win_later(win, NOT_VALID); + redraw_later(win, NOT_VALID); } else { frame_setheight(win->w_frame, height + win->w_status_height); @@ -5313,7 +5343,7 @@ void win_setwidth_win(int width, win_T *wp) if (wp->w_floating) { wp->w_float_config.width = width; win_config_float(wp, wp->w_float_config); - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } else { frame_setwidth(wp->w_frame, width + wp->w_vsep_width); @@ -5846,8 +5876,8 @@ void scroll_to_fraction(win_T *wp, int prev_height) } win_comp_scroll(wp); - redraw_win_later(wp, SOME_VALID); - wp->w_redr_status = TRUE; + redraw_later(wp, SOME_VALID); + wp->w_redr_status = true; invalidate_botline_win(wp); } @@ -5886,7 +5916,7 @@ void win_set_inner_size(win_T *wp) if (!exiting) { scroll_to_fraction(wp, prev_height); } - redraw_win_later(wp, NOT_VALID); // SOME_VALID?? + redraw_later(wp, NOT_VALID); // SOME_VALID?? } if (width != wp->w_width_inner) { @@ -5898,7 +5928,7 @@ void win_set_inner_size(win_T *wp) update_topline(); curs_columns(true); // validate w_wrow } - redraw_win_later(wp, NOT_VALID); + redraw_later(wp, NOT_VALID); } if (wp->w_buffer->terminal) { @@ -6019,6 +6049,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); @@ -6736,7 +6772,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, prev->next = m; m->next = cur; - redraw_win_later(wp, rtype); + redraw_later(wp, rtype); return id; fail: @@ -6794,7 +6830,7 @@ int match_delete(win_T *wp, int id, int perr) rtype = VALID; } xfree(cur); - redraw_win_later(wp, rtype); + redraw_later(wp, rtype); return 0; } @@ -6812,7 +6848,7 @@ void clear_matches(win_T *wp) xfree(wp->w_match_head); wp->w_match_head = m; } - redraw_win_later(wp, SOME_VALID); + redraw_later(wp, SOME_VALID); } /* @@ -6986,7 +7022,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); } } diff --git a/src/tree_sitter/LICENSE b/src/tree_sitter/LICENSE deleted file mode 100644 index 971b81f9a8..0000000000 --- a/src/tree_sitter/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Max Brunsfeld - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/tree_sitter/README.md b/src/tree_sitter/README.md deleted file mode 100644 index 20cb35e7c3..0000000000 --- a/src/tree_sitter/README.md +++ /dev/null @@ -1,16 +0,0 @@ -Tree-sitter vendor runtime -========================== - -This is the vendor runtime code for treesitter. - -The original code can be found [here](https://github.com/tree-sitter/tree-sitter). - -As this code is not ours, if you find any bugs, feel free to open an issue, so that we can -investigate and determine if this should go upstream. - -# Updating - -To update the treesitter runtime, use the `update-ts-runtime.sh` script in the `scripts` directory: -```sh -./scripts/update-ts-runtime.sh <commit you want to update to> -``` diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h deleted file mode 100644 index d3c6b5eca8..0000000000 --- a/src/tree_sitter/alloc.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef TREE_SITTER_ALLOC_H_ -#define TREE_SITTER_ALLOC_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdlib.h> -#include <stdbool.h> -#include <stdio.h> - -#include "nvim/memory.h" - -#if 1 - -static inline bool ts_toggle_allocation_recording(bool value) { - return false; -} - -#define ts_malloc xmalloc -#define ts_calloc xcalloc -#define ts_realloc xrealloc -#define ts_free xfree - -#elif defined(TREE_SITTER_TEST) - -void *ts_record_malloc(size_t); -void *ts_record_calloc(size_t, size_t); -void *ts_record_realloc(void *, size_t); -void ts_record_free(void *); -bool ts_toggle_allocation_recording(bool); - -static inline void *ts_malloc(size_t size) { - return ts_record_malloc(size); -} - -static inline void *ts_calloc(size_t count, size_t size) { - return ts_record_calloc(count, size); -} - -static inline void *ts_realloc(void *buffer, size_t size) { - return ts_record_realloc(buffer, size); -} - -static inline void ts_free(void *buffer) { - ts_record_free(buffer); -} - -#else - -#include <stdlib.h> - -static inline bool ts_toggle_allocation_recording(bool value) { - (void)value; - return false; -} - -static inline void *ts_malloc(size_t size) { - void *result = malloc(size); - if (size > 0 && !result) { - fprintf(stderr, "tree-sitter failed to allocate %lu bytes", size); - exit(1); - } - return result; -} - -static inline void *ts_calloc(size_t count, size_t size) { - void *result = calloc(count, size); - if (count > 0 && !result) { - fprintf(stderr, "tree-sitter failed to allocate %lu bytes", count * size); - exit(1); - } - return result; -} - -static inline void *ts_realloc(void *buffer, size_t size) { - void *result = realloc(buffer, size); - if (size > 0 && !result) { - fprintf(stderr, "tree-sitter failed to reallocate %lu bytes", size); - exit(1); - } - return result; -} - -static inline void ts_free(void *buffer) { - free(buffer); -} - -#endif - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_ALLOC_H_ diff --git a/src/tree_sitter/api.h b/src/tree_sitter/api.h deleted file mode 100644 index 9d832e6ec4..0000000000 --- a/src/tree_sitter/api.h +++ /dev/null @@ -1,876 +0,0 @@ -#ifndef TREE_SITTER_API_H_ -#define TREE_SITTER_API_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <stdbool.h> - -/****************************/ -/* Section - ABI Versioning */ -/****************************/ - -/** - * The latest ABI version that is supported by the current version of the - * library. When Languages are generated by the Tree-sitter CLI, they are - * assigned an ABI version number that corresponds to the current CLI version. - * The Tree-sitter library is generally backwards-compatible with languages - * generated using older CLI versions, but is not forwards-compatible. - */ -#define TREE_SITTER_LANGUAGE_VERSION 11 - -/** - * The earliest ABI version that is supported by the current version of the - * library. - */ -#define TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION 9 - -/*******************/ -/* Section - Types */ -/*******************/ - -typedef uint16_t TSSymbol; -typedef uint16_t TSFieldId; -typedef struct TSLanguage TSLanguage; -typedef struct TSParser TSParser; -typedef struct TSTree TSTree; -typedef struct TSQuery TSQuery; -typedef struct TSQueryCursor TSQueryCursor; - -typedef enum { - TSInputEncodingUTF8, - TSInputEncodingUTF16, -} TSInputEncoding; - -typedef enum { - TSSymbolTypeRegular, - TSSymbolTypeAnonymous, - TSSymbolTypeAuxiliary, -} TSSymbolType; - -typedef struct { - uint32_t row; - uint32_t column; -} TSPoint; - -typedef struct { - TSPoint start_point; - TSPoint end_point; - uint32_t start_byte; - uint32_t end_byte; -} TSRange; - -typedef struct { - void *payload; - const char *(*read)(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read); - TSInputEncoding encoding; -} TSInput; - -typedef enum { - TSLogTypeParse, - TSLogTypeLex, -} TSLogType; - -typedef struct { - void *payload; - void (*log)(void *payload, TSLogType, const char *); -} TSLogger; - -typedef struct { - uint32_t start_byte; - uint32_t old_end_byte; - uint32_t new_end_byte; - TSPoint start_point; - TSPoint old_end_point; - TSPoint new_end_point; -} TSInputEdit; - -typedef struct { - uint32_t context[4]; - const void *id; - const TSTree *tree; -} TSNode; - -typedef struct { - const void *tree; - const void *id; - uint32_t context[2]; -} TSTreeCursor; - -typedef struct { - TSNode node; - uint32_t index; -} TSQueryCapture; - -typedef struct { - uint32_t id; - uint16_t pattern_index; - uint16_t capture_count; - const TSQueryCapture *captures; -} TSQueryMatch; - -typedef enum { - TSQueryPredicateStepTypeDone, - TSQueryPredicateStepTypeCapture, - TSQueryPredicateStepTypeString, -} TSQueryPredicateStepType; - -typedef struct { - TSQueryPredicateStepType type; - uint32_t value_id; -} TSQueryPredicateStep; - -typedef enum { - TSQueryErrorNone = 0, - TSQueryErrorSyntax, - TSQueryErrorNodeType, - TSQueryErrorField, - TSQueryErrorCapture, -} TSQueryError; - -/********************/ -/* Section - Parser */ -/********************/ - -/** - * Create a new parser. - */ -TSParser *ts_parser_new(void); - -/** - * Delete the parser, freeing all of the memory that it used. - */ -void ts_parser_delete(TSParser *parser); - -/** - * Set the language that the parser should use for parsing. - * - * Returns a boolean indicating whether or not the language was successfully - * assigned. True means assignment succeeded. False means there was a version - * mismatch: the language was generated with an incompatible version of the - * Tree-sitter CLI. Check the language's version using `ts_language_version` - * and compare it to this library's `TREE_SITTER_LANGUAGE_VERSION` and - * `TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION` constants. - */ -bool ts_parser_set_language(TSParser *self, const TSLanguage *language); - -/** - * Get the parser's current language. - */ -const TSLanguage *ts_parser_language(const TSParser *self); - -/** - * Set the ranges of text that the parser should include when parsing. - * - * By default, the parser will always include entire documents. This function - * allows you to parse only a *portion* of a document but still return a syntax - * tree whose ranges match up with the document as a whole. You can also pass - * multiple disjoint ranges. - * - * The second and third parameters specify the location and length of an array - * of ranges. The parser does *not* take ownership of these ranges; it copies - * the data, so it doesn't matter how these ranges are allocated. - * - * If `length` is zero, then the entire document will be parsed. Otherwise, - * the given ranges must be ordered from earliest to latest in the document, - * and they must not overlap. That is, the following must hold for all - * `i` < `length - 1`: - * - * ranges[i].end_byte <= ranges[i + 1].start_byte - * - * If this requirement is not satisfied, the operation will fail, the ranges - * will not be assigned, and this function will return `false`. On success, - * this function returns `true` - */ -bool ts_parser_set_included_ranges( - TSParser *self, - const TSRange *ranges, - uint32_t length -); - -/** - * Get the ranges of text that the parser will include when parsing. - * - * The returned pointer is owned by the parser. The caller should not free it - * or write to it. The length of the array will be written to the given - * `length` pointer. - */ -const TSRange *ts_parser_included_ranges( - const TSParser *self, - uint32_t *length -); - -/** - * Use the parser to parse some source code and create a syntax tree. - * - * If you are parsing this document for the first time, pass `NULL` for the - * `old_tree` parameter. Otherwise, if you have already parsed an earlier - * version of this document and the document has since been edited, pass the - * previous syntax tree so that the unchanged parts of it can be reused. - * This will save time and memory. For this to work correctly, you must have - * already edited the old syntax tree using the `ts_tree_edit` function in a - * way that exactly matches the source code changes. - * - * The `TSInput` parameter lets you specify how to read the text. It has the - * following three fields: - * 1. `read`: A function to retrieve a chunk of text at a given byte offset - * and (row, column) position. The function should return a pointer to the - * text and write its length to the the `bytes_read` pointer. The parser - * does not take ownership of this buffer; it just borrows it until it has - * finished reading it. The function should write a zero value to the - * `bytes_read` pointer to indicate the end of the document. - * 2. `payload`: An arbitrary pointer that will be passed to each invocation - * of the `read` function. - * 3. `encoding`: An indication of how the text is encoded. Either - * `TSInputEncodingUTF8` or `TSInputEncodingUTF16`. - * - * This function returns a syntax tree on success, and `NULL` on failure. There - * are three possible reasons for failure: - * 1. The parser does not have a language assigned. Check for this using the - `ts_parser_language` function. - * 2. Parsing was cancelled due to a timeout that was set by an earlier call to - * the `ts_parser_set_timeout_micros` function. You can resume parsing from - * where the parser left out by calling `ts_parser_parse` again with the - * same arguments. Or you can start parsing from scratch by first calling - * `ts_parser_reset`. - * 3. Parsing was cancelled using a cancellation flag that was set by an - * earlier call to `ts_parser_set_cancellation_flag`. You can resume parsing - * from where the parser left out by calling `ts_parser_parse` again with - * the same arguments. - */ -TSTree *ts_parser_parse( - TSParser *self, - const TSTree *old_tree, - TSInput input -); - -/** - * Use the parser to parse some source code stored in one contiguous buffer. - * The first two parameters are the same as in the `ts_parser_parse` function - * above. The second two parameters indicate the location of the buffer and its - * length in bytes. - */ -TSTree *ts_parser_parse_string( - TSParser *self, - const TSTree *old_tree, - const char *string, - uint32_t length -); - -/** - * Use the parser to parse some source code stored in one contiguous buffer with - * a given encoding. The first four parameters work the same as in the - * `ts_parser_parse_string` method above. The final parameter indicates whether - * the text is encoded as UTF8 or UTF16. - */ -TSTree *ts_parser_parse_string_encoding( - TSParser *self, - const TSTree *old_tree, - const char *string, - uint32_t length, - TSInputEncoding encoding -); - -/** - * Instruct the parser to start the next parse from the beginning. - * - * If the parser previously failed because of a timeout or a cancellation, then - * by default, it will resume where it left off on the next call to - * `ts_parser_parse` or other parsing functions. If you don't want to resume, - * and instead intend to use this parser to parse some other document, you must - * call `ts_parser_reset` first. - */ -void ts_parser_reset(TSParser *self); - -/** - * Set the maximum duration in microseconds that parsing should be allowed to - * take before halting. - * - * If parsing takes longer than this, it will halt early, returning NULL. - * See `ts_parser_parse` for more information. - */ -void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout); - -/** - * Get the duration in microseconds that parsing is allowed to take. - */ -uint64_t ts_parser_timeout_micros(const TSParser *self); - -/** - * Set the parser's current cancellation flag pointer. - * - * If a non-null pointer is assigned, then the parser will periodically read - * from this pointer during parsing. If it reads a non-zero value, it will - * halt early, returning NULL. See `ts_parser_parse` for more information. - */ -void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag); - -/** - * Get the parser's current cancellation flag pointer. - */ -const size_t *ts_parser_cancellation_flag(const TSParser *self); - -/** - * Set the logger that a parser should use during parsing. - * - * The parser does not take ownership over the logger payload. If a logger was - * previously assigned, the caller is responsible for releasing any memory - * owned by the previous logger. - */ -void ts_parser_set_logger(TSParser *self, TSLogger logger); - -/** - * Get the parser's current logger. - */ -TSLogger ts_parser_logger(const TSParser *self); - -/** - * Set the file descriptor to which the parser should write debugging graphs - * during parsing. The graphs are formatted in the DOT language. You may want - * to pipe these graphs directly to a `dot(1)` process in order to generate - * SVG output. You can turn off this logging by passing a negative number. - */ -void ts_parser_print_dot_graphs(TSParser *self, int file); - -/******************/ -/* Section - Tree */ -/******************/ - -/** - * Create a shallow copy of the syntax tree. This is very fast. - * - * You need to copy a syntax tree in order to use it on more than one thread at - * a time, as syntax trees are not thread safe. - */ -TSTree *ts_tree_copy(const TSTree *self); - -/** - * Delete the syntax tree, freeing all of the memory that it used. - */ -void ts_tree_delete(TSTree *self); - -/** - * Get the root node of the syntax tree. - */ -TSNode ts_tree_root_node(const TSTree *self); - -/** - * Get the language that was used to parse the syntax tree. - */ -const TSLanguage *ts_tree_language(const TSTree *); - -/** - * Edit the syntax tree to keep it in sync with source code that has been - * edited. - * - * You must describe the edit both in terms of byte offsets and in terms of - * (row, column) coordinates. - */ -void ts_tree_edit(TSTree *self, const TSInputEdit *edit); - -/** - * Compare an old edited syntax tree to a new syntax tree representing the same - * document, returning an array of ranges whose syntactic structure has changed. - * - * For this to work correctly, the old syntax tree must have been edited such - * that its ranges match up to the new tree. Generally, you'll want to call - * this function right after calling one of the `ts_parser_parse` functions. - * You need to pass the old tree that was passed to parse, as well as the new - * tree that was returned from that function. - * - * The returned array is allocated using `malloc` and the caller is responsible - * for freeing it using `free`. The length of the array will be written to the - * given `length` pointer. - */ -TSRange *ts_tree_get_changed_ranges( - const TSTree *old_tree, - const TSTree *new_tree, - uint32_t *length -); - -/** - * Write a DOT graph describing the syntax tree to the given file. - */ -void ts_tree_print_dot_graph(const TSTree *, FILE *); - -/******************/ -/* Section - Node */ -/******************/ - -/** - * Get the node's type as a null-terminated string. - */ -const char *ts_node_type(TSNode); - -/** - * Get the node's type as a numerical id. - */ -TSSymbol ts_node_symbol(TSNode); - -/** - * Get the node's start byte. - */ -uint32_t ts_node_start_byte(TSNode); - -/** - * Get the node's start position in terms of rows and columns. - */ -TSPoint ts_node_start_point(TSNode); - -/** - * Get the node's end byte. - */ -uint32_t ts_node_end_byte(TSNode); - -/** - * Get the node's end position in terms of rows and columns. - */ -TSPoint ts_node_end_point(TSNode); - -/** - * Get an S-expression representing the node as a string. - * - * This string is allocated with `malloc` and the caller is responsible for - * freeing it using `free`. - */ -char *ts_node_string(TSNode); - -/** - * Check if the node is null. Functions like `ts_node_child` and - * `ts_node_next_sibling` will return a null node to indicate that no such node - * was found. - */ -bool ts_node_is_null(TSNode); - -/** - * Check if the node is *named*. Named nodes correspond to named rules in the - * grammar, whereas *anonymous* nodes correspond to string literals in the - * grammar. - */ -bool ts_node_is_named(TSNode); - -/** - * Check if the node is *missing*. Missing nodes are inserted by the parser in - * order to recover from certain kinds of syntax errors. - */ -bool ts_node_is_missing(TSNode); - -/** - * Check if the node is *extra*. Extra nodes represent things like comments, - * which are not required the grammar, but can appear anywhere. - */ -bool ts_node_is_extra(TSNode); - -/** - * Check if a syntax node has been edited. - */ -bool ts_node_has_changes(TSNode); - -/** - * Check if the node is a syntax error or contains any syntax errors. - */ -bool ts_node_has_error(TSNode); - -/** - * Get the node's immediate parent. - */ -TSNode ts_node_parent(TSNode); - -/** - * Get the node's child at the given index, where zero represents the first - * child. - */ -TSNode ts_node_child(TSNode, uint32_t); - -/** - * Get the node's number of children. - */ -uint32_t ts_node_child_count(TSNode); - -/** - * Get the node's *named* child at the given index. - * - * See also `ts_node_is_named`. - */ -TSNode ts_node_named_child(TSNode, uint32_t); - -/** - * Get the node's number of *named* children. - * - * See also `ts_node_is_named`. - */ -uint32_t ts_node_named_child_count(TSNode); - -/** - * Get the node's child with the given field name. - */ -TSNode ts_node_child_by_field_name( - TSNode self, - const char *field_name, - uint32_t field_name_length -); - -/** - * Get the node's child with the given numerical field id. - * - * You can convert a field name to an id using the - * `ts_language_field_id_for_name` function. - */ -TSNode ts_node_child_by_field_id(TSNode, TSFieldId); - -/** - * Get the node's next / previous sibling. - */ -TSNode ts_node_next_sibling(TSNode); -TSNode ts_node_prev_sibling(TSNode); - -/** - * Get the node's next / previous *named* sibling. - */ -TSNode ts_node_next_named_sibling(TSNode); -TSNode ts_node_prev_named_sibling(TSNode); - -/** - * Get the node's first child that extends beyond the given byte offset. - */ -TSNode ts_node_first_child_for_byte(TSNode, uint32_t); - -/** - * Get the node's first named child that extends beyond the given byte offset. - */ -TSNode ts_node_first_named_child_for_byte(TSNode, uint32_t); - -/** - * Get the smallest node within this node that spans the given range of bytes - * or (row, column) positions. - */ -TSNode ts_node_descendant_for_byte_range(TSNode, uint32_t, uint32_t); -TSNode ts_node_descendant_for_point_range(TSNode, TSPoint, TSPoint); - -/** - * Get the smallest named node within this node that spans the given range of - * bytes or (row, column) positions. - */ -TSNode ts_node_named_descendant_for_byte_range(TSNode, uint32_t, uint32_t); -TSNode ts_node_named_descendant_for_point_range(TSNode, TSPoint, TSPoint); - -/** - * Edit the node to keep it in-sync with source code that has been edited. - * - * This function is only rarely needed. When you edit a syntax tree with the - * `ts_tree_edit` function, all of the nodes that you retrieve from the tree - * afterward will already reflect the edit. You only need to use `ts_node_edit` - * when you have a `TSNode` instance that you want to keep and continue to use - * after an edit. - */ -void ts_node_edit(TSNode *, const TSInputEdit *); - -/** - * Check if two nodes are identical. - */ -bool ts_node_eq(TSNode, TSNode); - -/************************/ -/* Section - TreeCursor */ -/************************/ - -/** - * Create a new tree cursor starting from the given node. - * - * A tree cursor allows you to walk a syntax tree more efficiently than is - * possible using the `TSNode` functions. It is a mutable object that is always - * on a certain syntax node, and can be moved imperatively to different nodes. - */ -TSTreeCursor ts_tree_cursor_new(TSNode); - -/** - * Delete a tree cursor, freeing all of the memory that it used. - */ -void ts_tree_cursor_delete(TSTreeCursor *); - -/** - * Re-initialize a tree cursor to start at a different node. - */ -void ts_tree_cursor_reset(TSTreeCursor *, TSNode); - -/** - * Get the tree cursor's current node. - */ -TSNode ts_tree_cursor_current_node(const TSTreeCursor *); - -/** - * Get the field name of the tree cursor's current node. - * - * This returns `NULL` if the current node doesn't have a field. - * See also `ts_node_child_by_field_name`. - */ -const char *ts_tree_cursor_current_field_name(const TSTreeCursor *); - -/** - * Get the field name of the tree cursor's current node. - * - * This returns zero if the current node doesn't have a field. - * See also `ts_node_child_by_field_id`, `ts_language_field_id_for_name`. - */ -TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *); - -/** - * Move the cursor to the parent of its current node. - * - * This returns `true` if the cursor successfully moved, and returns `false` - * if there was no parent node (the cursor was already on the root node). - */ -bool ts_tree_cursor_goto_parent(TSTreeCursor *); - -/** - * Move the cursor to the next sibling of its current node. - * - * This returns `true` if the cursor successfully moved, and returns `false` - * if there was no next sibling node. - */ -bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *); - -/** - * Move the cursor to the first child of its current node. - * - * This returns `true` if the cursor successfully moved, and returns `false` - * if there were no children. - */ -bool ts_tree_cursor_goto_first_child(TSTreeCursor *); - -/** - * Move the cursor to the first child of its current node that extends beyond - * the given byte offset. - * - * This returns the index of the child node if one was found, and returns -1 - * if no such child was found. - */ -int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *, uint32_t); - -TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *); - -/*******************/ -/* Section - Query */ -/*******************/ - -/** - * Create a new query from a string containing one or more S-expression - * patterns. The query is associated with a particular language, and can - * only be run on syntax nodes parsed with that language. - * - * If all of the given patterns are valid, this returns a `TSQuery`. - * If a pattern is invalid, this returns `NULL`, and provides two pieces - * of information about the problem: - * 1. The byte offset of the error is written to the `error_offset` parameter. - * 2. The type of error is written to the `error_type` parameter. - */ -TSQuery *ts_query_new( - const TSLanguage *language, - const char *source, - uint32_t source_len, - uint32_t *error_offset, - TSQueryError *error_type -); - -/** - * Delete a query, freeing all of the memory that it used. - */ -void ts_query_delete(TSQuery *); - -/** - * Get the number of patterns, captures, or string literals in the query. - */ -uint32_t ts_query_pattern_count(const TSQuery *); -uint32_t ts_query_capture_count(const TSQuery *); -uint32_t ts_query_string_count(const TSQuery *); - -/** - * Get the byte offset where the given pattern starts in the query's source. - * - * This can be useful when combining queries by concatenating their source - * code strings. - */ -uint32_t ts_query_start_byte_for_pattern(const TSQuery *, uint32_t); - -/** - * Get all of the predicates for the given pattern in the query. - * - * The predicates are represented as a single array of steps. There are three - * types of steps in this array, which correspond to the three legal values for - * the `type` field: - * - `TSQueryPredicateStepTypeCapture` - Steps with this type represent names - * of captures. Their `value_id` can be used with the - * `ts_query_capture_name_for_id` function to obtain the name of the capture. - * - `TSQueryPredicateStepTypeString` - Steps with this type represent literal - * strings. Their `value_id` can be used with the - * `ts_query_string_value_for_id` function to obtain their string value. - * - `TSQueryPredicateStepTypeDone` - Steps with this type are *sentinels* - * that represent the end of an individual predicate. If a pattern has two - * predicates, then there will be two steps with this `type` in the array. - */ -const TSQueryPredicateStep *ts_query_predicates_for_pattern( - const TSQuery *self, - uint32_t pattern_index, - uint32_t *length -); - -/** - * Get the name and length of one of the query's captures, or one of the - * query's string literals. Each capture and string is associated with a - * numeric id based on the order that it appeared in the query's source. - */ -const char *ts_query_capture_name_for_id( - const TSQuery *, - uint32_t id, - uint32_t *length -); -const char *ts_query_string_value_for_id( - const TSQuery *, - uint32_t id, - uint32_t *length -); - -/** - * Disable a certain capture within a query. - * - * This prevents the capture from being returned in matches, and also avoids - * any resource usage associated with recording the capture. Currently, there - * is no way to undo this. - */ -void ts_query_disable_capture(TSQuery *, const char *, uint32_t); - -/** - * Disable a certain pattern within a query. - * - * This prevents the pattern from matching and removes most of the overhead - * associated with the pattern. Currently, there is no way to undo this. - */ -void ts_query_disable_pattern(TSQuery *, uint32_t); - -/** - * Create a new cursor for executing a given query. - * - * The cursor stores the state that is needed to iteratively search - * for matches. To use the query cursor, first call `ts_query_cursor_exec` - * to start running a given query on a given syntax node. Then, there are - * two options for consuming the results of the query: - * 1. Repeatedly call `ts_query_cursor_next_match` to iterate over all of the - * the *matches* in the order that they were found. Each match contains the - * index of the pattern that matched, and an array of captures. Because - * multiple patterns can match the same set of nodes, one match may contain - * captures that appear *before* some of the captures from a previous match. - * 2. Repeatedly call `ts_query_cursor_next_capture` to iterate over all of the - * individual *captures* in the order that they appear. This is useful if - * don't care about which pattern matched, and just want a single ordered - * sequence of captures. - * - * If you don't care about consuming all of the results, you can stop calling - * `ts_query_cursor_next_match` or `ts_query_cursor_next_capture` at any point. - * You can then start executing another query on another node by calling - * `ts_query_cursor_exec` again. - */ -TSQueryCursor *ts_query_cursor_new(void); - -/** - * Delete a query cursor, freeing all of the memory that it used. - */ -void ts_query_cursor_delete(TSQueryCursor *); - -/** - * Start running a given query on a given node. - */ -void ts_query_cursor_exec(TSQueryCursor *, const TSQuery *, TSNode); - -/** - * Set the range of bytes or (row, column) positions in which the query - * will be executed. - */ -void ts_query_cursor_set_byte_range(TSQueryCursor *, uint32_t, uint32_t); -void ts_query_cursor_set_point_range(TSQueryCursor *, TSPoint, TSPoint); - -/** - * Advance to the next match of the currently running query. - * - * If there is a match, write it to `*match` and return `true`. - * Otherwise, return `false`. - */ -bool ts_query_cursor_next_match(TSQueryCursor *, TSQueryMatch *match); -void ts_query_cursor_remove_match(TSQueryCursor *, uint32_t id); - -/** - * Advance to the next capture of the currently running query. - * - * If there is a capture, write its match to `*match` and its index within - * the matche's capture list to `*capture_index`. Otherwise, return `false`. - */ -bool ts_query_cursor_next_capture( - TSQueryCursor *, - TSQueryMatch *match, - uint32_t *capture_index -); - -/**********************/ -/* Section - Language */ -/**********************/ - -/** - * Get the number of distinct node types in the language. - */ -uint32_t ts_language_symbol_count(const TSLanguage *); - -/** - * Get a node type string for the given numerical id. - */ -const char *ts_language_symbol_name(const TSLanguage *, TSSymbol); - -/** - * Get the numerical id for the given node type string. - */ -TSSymbol ts_language_symbol_for_name( - const TSLanguage *self, - const char *string, - uint32_t length, - bool is_named -); - -/** - * Get the number of distinct field names in the language. - */ -uint32_t ts_language_field_count(const TSLanguage *); - -/** - * Get the field name string for the given numerical id. - */ -const char *ts_language_field_name_for_id(const TSLanguage *, TSFieldId); - -/** - * Get the numerical id for the given field name string. - */ -TSFieldId ts_language_field_id_for_name(const TSLanguage *, const char *, uint32_t); - -/** - * Check whether the given node type id belongs to named nodes, anonymous nodes, - * or a hidden nodes. - * - * See also `ts_node_is_named`. Hidden nodes are never returned from the API. - */ -TSSymbolType ts_language_symbol_type(const TSLanguage *, TSSymbol); - -/** - * Get the ABI version number for this language. This version number is used - * to ensure that languages were generated by a compatible version of - * Tree-sitter. - * - * See also `ts_parser_set_language`. - */ -uint32_t ts_language_version(const TSLanguage *); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_API_H_ diff --git a/src/tree_sitter/array.h b/src/tree_sitter/array.h deleted file mode 100644 index 26cb8448f1..0000000000 --- a/src/tree_sitter/array.h +++ /dev/null @@ -1,158 +0,0 @@ -#ifndef TREE_SITTER_ARRAY_H_ -#define TREE_SITTER_ARRAY_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <string.h> -#include <stdlib.h> -#include <stdint.h> -#include <assert.h> -#include <stdbool.h> -#include "./alloc.h" - -#define Array(T) \ - struct { \ - T *contents; \ - uint32_t size; \ - uint32_t capacity; \ - } - -#define array_init(self) \ - ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) - -#define array_new() \ - { NULL, 0, 0 } - -#define array_get(self, index) \ - (assert((uint32_t)index < (self)->size), &(self)->contents[index]) - -#define array_front(self) array_get(self, 0) - -#define array_back(self) array_get(self, (self)->size - 1) - -#define array_clear(self) ((self)->size = 0) - -#define array_reserve(self, new_capacity) \ - array__reserve((VoidArray *)(self), array__elem_size(self), new_capacity) - -#define array_erase(self, index) \ - array__erase((VoidArray *)(self), array__elem_size(self), index) - -#define array_delete(self) array__delete((VoidArray *)self) - -#define array_push(self, element) \ - (array__grow((VoidArray *)(self), 1, array__elem_size(self)), \ - (self)->contents[(self)->size++] = (element)) - -#define array_grow_by(self, count) \ - (array__grow((VoidArray *)(self), count, array__elem_size(self)), \ - memset((self)->contents + (self)->size, 0, (count) * array__elem_size(self)), \ - (self)->size += (count)) - -#define array_push_all(self, other) \ - array_splice((self), (self)->size, 0, (other)->size, (other)->contents) - -#define array_splice(self, index, old_count, new_count, new_contents) \ - array__splice((VoidArray *)(self), array__elem_size(self), index, old_count, \ - new_count, new_contents) - -#define array_insert(self, index, element) \ - array__splice((VoidArray *)(self), array__elem_size(self), index, 0, 1, &element) - -#define array_pop(self) ((self)->contents[--(self)->size]) - -#define array_assign(self, other) \ - array__assign((VoidArray *)(self), (const VoidArray *)(other), array__elem_size(self)) - -// Private - -typedef Array(void) VoidArray; - -#define array__elem_size(self) sizeof(*(self)->contents) - -static inline void array__delete(VoidArray *self) { - ts_free(self->contents); - self->contents = NULL; - self->size = 0; - self->capacity = 0; -} - -static inline void array__erase(VoidArray *self, size_t element_size, - uint32_t index) { - assert(index < self->size); - char *contents = (char *)self->contents; - memmove(contents + index * element_size, contents + (index + 1) * element_size, - (self->size - index - 1) * element_size); - self->size--; -} - -static inline void array__reserve(VoidArray *self, size_t element_size, uint32_t new_capacity) { - if (new_capacity > self->capacity) { - if (self->contents) { - self->contents = ts_realloc(self->contents, new_capacity * element_size); - } else { - self->contents = ts_calloc(new_capacity, element_size); - } - self->capacity = new_capacity; - } -} - -static inline void array__assign(VoidArray *self, const VoidArray *other, size_t element_size) { - array__reserve(self, element_size, other->size); - self->size = other->size; - memcpy(self->contents, other->contents, self->size * element_size); -} - -static inline void array__grow(VoidArray *self, size_t count, size_t element_size) { - size_t new_size = self->size + count; - if (new_size > self->capacity) { - size_t new_capacity = self->capacity * 2; - if (new_capacity < 8) new_capacity = 8; - if (new_capacity < new_size) new_capacity = new_size; - array__reserve(self, element_size, new_capacity); - } -} - -static inline void array__splice(VoidArray *self, size_t element_size, - uint32_t index, uint32_t old_count, - uint32_t new_count, const void *elements) { - uint32_t new_size = self->size + new_count - old_count; - uint32_t old_end = index + old_count; - uint32_t new_end = index + new_count; - assert(old_end <= self->size); - - array__reserve(self, element_size, new_size); - - char *contents = (char *)self->contents; - if (self->size > old_end) { - memmove( - contents + new_end * element_size, - contents + old_end * element_size, - (self->size - old_end) * element_size - ); - } - if (new_count > 0) { - if (elements) { - memcpy( - (contents + index * element_size), - elements, - new_count * element_size - ); - } else { - memset( - (contents + index * element_size), - 0, - new_count * element_size - ); - } - } - self->size += new_count - old_count; -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_ARRAY_H_ diff --git a/src/tree_sitter/atomic.h b/src/tree_sitter/atomic.h deleted file mode 100644 index 7bd0e850a9..0000000000 --- a/src/tree_sitter/atomic.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef TREE_SITTER_ATOMIC_H_ -#define TREE_SITTER_ATOMIC_H_ - -#include <stdint.h> - -#ifdef _WIN32 - -#include <windows.h> - -static inline size_t atomic_load(const volatile size_t *p) { - return *p; -} - -static inline uint32_t atomic_inc(volatile uint32_t *p) { - return InterlockedIncrement((long volatile *)p); -} - -static inline uint32_t atomic_dec(volatile uint32_t *p) { - return InterlockedDecrement((long volatile *)p); -} - -#else - -static inline size_t atomic_load(const volatile size_t *p) { -#ifdef __ATOMIC_RELAXED - return __atomic_load_n(p, __ATOMIC_RELAXED); -#else - return __sync_fetch_and_add((volatile size_t *)p, 0); -#endif -} - -static inline uint32_t atomic_inc(volatile uint32_t *p) { - return __sync_add_and_fetch(p, 1u); -} - -static inline uint32_t atomic_dec(volatile uint32_t *p) { - return __sync_sub_and_fetch(p, 1u); -} - -#endif - -#endif // TREE_SITTER_ATOMIC_H_ diff --git a/src/tree_sitter/bits.h b/src/tree_sitter/bits.h deleted file mode 100644 index ce7a715567..0000000000 --- a/src/tree_sitter/bits.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef TREE_SITTER_BITS_H_ -#define TREE_SITTER_BITS_H_ - -#include <stdint.h> - -static inline uint32_t bitmask_for_index(uint16_t id) { - return (1u << (31 - id)); -} - -#if defined _WIN32 && !defined __GNUC__ - -#include <intrin.h> - -static inline uint32_t count_leading_zeros(uint32_t x) { - if (x == 0) return 32; - uint32_t result; - _BitScanReverse(&result, x); - return 31 - result; -} - -#else - -static inline uint32_t count_leading_zeros(uint32_t x) { - if (x == 0) return 32; - return __builtin_clz(x); -} - -#endif -#endif // TREE_SITTER_BITS_H_ diff --git a/src/tree_sitter/clock.h b/src/tree_sitter/clock.h deleted file mode 100644 index 94545f3566..0000000000 --- a/src/tree_sitter/clock.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef TREE_SITTER_CLOCK_H_ -#define TREE_SITTER_CLOCK_H_ - -#include <stdint.h> - -typedef uint64_t TSDuration; - -#ifdef _WIN32 - -// Windows: -// * Represent a time as a performance counter value. -// * Represent a duration as a number of performance counter ticks. - -#include <windows.h> -typedef uint64_t TSClock; - -static inline TSDuration duration_from_micros(uint64_t micros) { - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - return micros * (uint64_t)frequency.QuadPart / 1000000; -} - -static inline uint64_t duration_to_micros(TSDuration self) { - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - return self * 1000000 / (uint64_t)frequency.QuadPart; -} - -static inline TSClock clock_null(void) { - return 0; -} - -static inline TSClock clock_now(void) { - LARGE_INTEGER result; - QueryPerformanceCounter(&result); - return (uint64_t)result.QuadPart; -} - -static inline TSClock clock_after(TSClock base, TSDuration duration) { - return base + duration; -} - -static inline bool clock_is_null(TSClock self) { - return !self; -} - -static inline bool clock_is_gt(TSClock self, TSClock other) { - return self > other; -} - -#elif defined(CLOCK_MONOTONIC) && !defined(__APPLE__) - -// POSIX with monotonic clock support (Linux) -// * Represent a time as a monotonic (seconds, nanoseconds) pair. -// * Represent a duration as a number of microseconds. -// -// On these platforms, parse timeouts will correspond accurately to -// real time, regardless of what other processes are running. - -#include <time.h> -typedef struct timespec TSClock; - -static inline TSDuration duration_from_micros(uint64_t micros) { - return micros; -} - -static inline uint64_t duration_to_micros(TSDuration self) { - return self; -} - -static inline TSClock clock_now(void) { - TSClock result; - clock_gettime(CLOCK_MONOTONIC, &result); - return result; -} - -static inline TSClock clock_null(void) { - return (TSClock) {0, 0}; -} - -static inline TSClock clock_after(TSClock base, TSDuration duration) { - TSClock result = base; - result.tv_sec += duration / 1000000; - result.tv_nsec += (duration % 1000000) * 1000; - return result; -} - -static inline bool clock_is_null(TSClock self) { - return !self.tv_sec; -} - -static inline bool clock_is_gt(TSClock self, TSClock other) { - if (self.tv_sec > other.tv_sec) return true; - if (self.tv_sec < other.tv_sec) return false; - return self.tv_nsec > other.tv_nsec; -} - -#else - -// macOS or POSIX without monotonic clock support -// * Represent a time as a process clock value. -// * Represent a duration as a number of process clock ticks. -// -// On these platforms, parse timeouts may be affected by other processes, -// which is not ideal, but is better than using a non-monotonic time API -// like `gettimeofday`. - -#include <time.h> -typedef uint64_t TSClock; - -static inline TSDuration duration_from_micros(uint64_t micros) { - return micros * (uint64_t)CLOCKS_PER_SEC / 1000000; -} - -static inline uint64_t duration_to_micros(TSDuration self) { - return self * 1000000 / (uint64_t)CLOCKS_PER_SEC; -} - -static inline TSClock clock_null(void) { - return 0; -} - -static inline TSClock clock_now(void) { - return (uint64_t)clock(); -} - -static inline TSClock clock_after(TSClock base, TSDuration duration) { - return base + duration; -} - -static inline bool clock_is_null(TSClock self) { - return !self; -} - -static inline bool clock_is_gt(TSClock self, TSClock other) { - return self > other; -} - -#endif - -#endif // TREE_SITTER_CLOCK_H_ diff --git a/src/tree_sitter/error_costs.h b/src/tree_sitter/error_costs.h deleted file mode 100644 index 32d3666a66..0000000000 --- a/src/tree_sitter/error_costs.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef TREE_SITTER_ERROR_COSTS_H_ -#define TREE_SITTER_ERROR_COSTS_H_ - -#define ERROR_STATE 0 -#define ERROR_COST_PER_RECOVERY 500 -#define ERROR_COST_PER_MISSING_TREE 110 -#define ERROR_COST_PER_SKIPPED_TREE 100 -#define ERROR_COST_PER_SKIPPED_LINE 30 -#define ERROR_COST_PER_SKIPPED_CHAR 1 - -#endif diff --git a/src/tree_sitter/get_changed_ranges.c b/src/tree_sitter/get_changed_ranges.c deleted file mode 100644 index 5bd1d814bd..0000000000 --- a/src/tree_sitter/get_changed_ranges.c +++ /dev/null @@ -1,482 +0,0 @@ -#include "./get_changed_ranges.h" -#include "./subtree.h" -#include "./language.h" -#include "./error_costs.h" -#include "./tree_cursor.h" -#include <assert.h> - -// #define DEBUG_GET_CHANGED_RANGES - -static void ts_range_array_add(TSRangeArray *self, Length start, Length end) { - if (self->size > 0) { - TSRange *last_range = array_back(self); - if (start.bytes <= last_range->end_byte) { - last_range->end_byte = end.bytes; - last_range->end_point = end.extent; - return; - } - } - - if (start.bytes < end.bytes) { - TSRange range = { start.extent, end.extent, start.bytes, end.bytes }; - array_push(self, range); - } -} - -bool ts_range_array_intersects(const TSRangeArray *self, unsigned start_index, - uint32_t start_byte, uint32_t end_byte) { - for (unsigned i = start_index; i < self->size; i++) { - TSRange *range = &self->contents[i]; - if (range->end_byte > start_byte) { - if (range->start_byte >= end_byte) break; - return true; - } - } - return false; -} - -void ts_range_array_get_changed_ranges( - const TSRange *old_ranges, unsigned old_range_count, - const TSRange *new_ranges, unsigned new_range_count, - TSRangeArray *differences -) { - unsigned new_index = 0; - unsigned old_index = 0; - Length current_position = length_zero(); - bool in_old_range = false; - bool in_new_range = false; - - while (old_index < old_range_count || new_index < new_range_count) { - const TSRange *old_range = &old_ranges[old_index]; - const TSRange *new_range = &new_ranges[new_index]; - - Length next_old_position; - if (in_old_range) { - next_old_position = (Length) {old_range->end_byte, old_range->end_point}; - } else if (old_index < old_range_count) { - next_old_position = (Length) {old_range->start_byte, old_range->start_point}; - } else { - next_old_position = LENGTH_MAX; - } - - Length next_new_position; - if (in_new_range) { - next_new_position = (Length) {new_range->end_byte, new_range->end_point}; - } else if (new_index < new_range_count) { - next_new_position = (Length) {new_range->start_byte, new_range->start_point}; - } else { - next_new_position = LENGTH_MAX; - } - - if (next_old_position.bytes < next_new_position.bytes) { - if (in_old_range != in_new_range) { - ts_range_array_add(differences, current_position, next_old_position); - } - if (in_old_range) old_index++; - current_position = next_old_position; - in_old_range = !in_old_range; - } else if (next_new_position.bytes < next_old_position.bytes) { - if (in_old_range != in_new_range) { - ts_range_array_add(differences, current_position, next_new_position); - } - if (in_new_range) new_index++; - current_position = next_new_position; - in_new_range = !in_new_range; - } else { - if (in_old_range != in_new_range) { - ts_range_array_add(differences, current_position, next_new_position); - } - if (in_old_range) old_index++; - if (in_new_range) new_index++; - in_old_range = !in_old_range; - in_new_range = !in_new_range; - current_position = next_new_position; - } - } -} - -typedef struct { - TreeCursor cursor; - const TSLanguage *language; - unsigned visible_depth; - bool in_padding; -} Iterator; - -static Iterator iterator_new(TreeCursor *cursor, const Subtree *tree, const TSLanguage *language) { - array_clear(&cursor->stack); - array_push(&cursor->stack, ((TreeCursorEntry){ - .subtree = tree, - .position = length_zero(), - .child_index = 0, - .structural_child_index = 0, - })); - return (Iterator) { - .cursor = *cursor, - .language = language, - .visible_depth = 1, - .in_padding = false, - }; -} - -static bool iterator_done(Iterator *self) { - return self->cursor.stack.size == 0; -} - -static Length iterator_start_position(Iterator *self) { - TreeCursorEntry entry = *array_back(&self->cursor.stack); - if (self->in_padding) { - return entry.position; - } else { - return length_add(entry.position, ts_subtree_padding(*entry.subtree)); - } -} - -static Length iterator_end_position(Iterator *self) { - TreeCursorEntry entry = *array_back(&self->cursor.stack); - Length result = length_add(entry.position, ts_subtree_padding(*entry.subtree)); - if (self->in_padding) { - return result; - } else { - return length_add(result, ts_subtree_size(*entry.subtree)); - } -} - -static bool iterator_tree_is_visible(const Iterator *self) { - TreeCursorEntry entry = *array_back(&self->cursor.stack); - if (ts_subtree_visible(*entry.subtree)) return true; - if (self->cursor.stack.size > 1) { - Subtree parent = *self->cursor.stack.contents[self->cursor.stack.size - 2].subtree; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->language, - parent.ptr->production_id - ); - return alias_sequence && alias_sequence[entry.structural_child_index] != 0; - } - return false; -} - -static void iterator_get_visible_state(const Iterator *self, Subtree *tree, - TSSymbol *alias_symbol, uint32_t *start_byte) { - uint32_t i = self->cursor.stack.size - 1; - - if (self->in_padding) { - if (i == 0) return; - i--; - } - - for (; i + 1 > 0; i--) { - TreeCursorEntry entry = self->cursor.stack.contents[i]; - - if (i > 0) { - const Subtree *parent = self->cursor.stack.contents[i - 1].subtree; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->language, - parent->ptr->production_id - ); - if (alias_sequence) { - *alias_symbol = alias_sequence[entry.structural_child_index]; - } - } - - if (ts_subtree_visible(*entry.subtree) || *alias_symbol) { - *tree = *entry.subtree; - *start_byte = entry.position.bytes; - break; - } - } -} - -static void iterator_ascend(Iterator *self) { - if (iterator_done(self)) return; - if (iterator_tree_is_visible(self) && !self->in_padding) self->visible_depth--; - if (array_back(&self->cursor.stack)->child_index > 0) self->in_padding = false; - self->cursor.stack.size--; -} - -static bool iterator_descend(Iterator *self, uint32_t goal_position) { - if (self->in_padding) return false; - - bool did_descend; - do { - did_descend = false; - TreeCursorEntry entry = *array_back(&self->cursor.stack); - Length position = entry.position; - uint32_t structural_child_index = 0; - for (uint32_t i = 0, n = ts_subtree_child_count(*entry.subtree); i < n; i++) { - const Subtree *child = &entry.subtree->ptr->children[i]; - Length child_left = length_add(position, ts_subtree_padding(*child)); - Length child_right = length_add(child_left, ts_subtree_size(*child)); - - if (child_right.bytes > goal_position) { - array_push(&self->cursor.stack, ((TreeCursorEntry){ - .subtree = child, - .position = position, - .child_index = i, - .structural_child_index = structural_child_index, - })); - - if (iterator_tree_is_visible(self)) { - if (child_left.bytes > goal_position) { - self->in_padding = true; - } else { - self->visible_depth++; - } - return true; - } - - did_descend = true; - break; - } - - position = child_right; - if (!ts_subtree_extra(*child)) structural_child_index++; - } - } while (did_descend); - - return false; -} - -static void iterator_advance(Iterator *self) { - if (self->in_padding) { - self->in_padding = false; - if (iterator_tree_is_visible(self)) { - self->visible_depth++; - } else { - iterator_descend(self, 0); - } - return; - } - - for (;;) { - if (iterator_tree_is_visible(self)) self->visible_depth--; - TreeCursorEntry entry = array_pop(&self->cursor.stack); - if (iterator_done(self)) return; - - const Subtree *parent = array_back(&self->cursor.stack)->subtree; - uint32_t child_index = entry.child_index + 1; - if (ts_subtree_child_count(*parent) > child_index) { - Length position = length_add(entry.position, ts_subtree_total_size(*entry.subtree)); - uint32_t structural_child_index = entry.structural_child_index; - if (!ts_subtree_extra(*entry.subtree)) structural_child_index++; - const Subtree *next_child = &parent->ptr->children[child_index]; - - array_push(&self->cursor.stack, ((TreeCursorEntry){ - .subtree = next_child, - .position = position, - .child_index = child_index, - .structural_child_index = structural_child_index, - })); - - if (iterator_tree_is_visible(self)) { - if (ts_subtree_padding(*next_child).bytes > 0) { - self->in_padding = true; - } else { - self->visible_depth++; - } - } else { - iterator_descend(self, 0); - } - break; - } - } -} - -typedef enum { - IteratorDiffers, - IteratorMayDiffer, - IteratorMatches, -} IteratorComparison; - -static IteratorComparison iterator_compare(const Iterator *old_iter, const Iterator *new_iter) { - Subtree old_tree = NULL_SUBTREE; - Subtree new_tree = NULL_SUBTREE; - uint32_t old_start = 0; - uint32_t new_start = 0; - TSSymbol old_alias_symbol = 0; - TSSymbol new_alias_symbol = 0; - iterator_get_visible_state(old_iter, &old_tree, &old_alias_symbol, &old_start); - iterator_get_visible_state(new_iter, &new_tree, &new_alias_symbol, &new_start); - - if (!old_tree.ptr && !new_tree.ptr) return IteratorMatches; - if (!old_tree.ptr || !new_tree.ptr) return IteratorDiffers; - - if ( - old_alias_symbol == new_alias_symbol && - ts_subtree_symbol(old_tree) == ts_subtree_symbol(new_tree) - ) { - if (old_start == new_start && - !ts_subtree_has_changes(old_tree) && - ts_subtree_symbol(old_tree) != ts_builtin_sym_error && - ts_subtree_size(old_tree).bytes == ts_subtree_size(new_tree).bytes && - ts_subtree_parse_state(old_tree) != TS_TREE_STATE_NONE && - ts_subtree_parse_state(new_tree) != TS_TREE_STATE_NONE && - (ts_subtree_parse_state(old_tree) == ERROR_STATE) == - (ts_subtree_parse_state(new_tree) == ERROR_STATE)) { - return IteratorMatches; - } else { - return IteratorMayDiffer; - } - } - - return IteratorDiffers; -} - -#ifdef DEBUG_GET_CHANGED_RANGES -static inline void iterator_print_state(Iterator *self) { - TreeCursorEntry entry = *array_back(&self->cursor.stack); - TSPoint start = iterator_start_position(self).extent; - TSPoint end = iterator_end_position(self).extent; - const char *name = ts_language_symbol_name(self->language, ts_subtree_symbol(*entry.subtree)); - printf( - "(%-25s %s\t depth:%u [%u, %u] - [%u, %u])", - name, self->in_padding ? "(p)" : " ", - self->visible_depth, - start.row + 1, start.column, - end.row + 1, end.column - ); -} -#endif - -unsigned ts_subtree_get_changed_ranges(const Subtree *old_tree, const Subtree *new_tree, - TreeCursor *cursor1, TreeCursor *cursor2, - const TSLanguage *language, - const TSRangeArray *included_range_differences, - TSRange **ranges) { - TSRangeArray results = array_new(); - - Iterator old_iter = iterator_new(cursor1, old_tree, language); - Iterator new_iter = iterator_new(cursor2, new_tree, language); - - unsigned included_range_difference_index = 0; - - Length position = iterator_start_position(&old_iter); - Length next_position = iterator_start_position(&new_iter); - if (position.bytes < next_position.bytes) { - ts_range_array_add(&results, position, next_position); - position = next_position; - } else if (position.bytes > next_position.bytes) { - ts_range_array_add(&results, next_position, position); - next_position = position; - } - - do { - #ifdef DEBUG_GET_CHANGED_RANGES - printf("At [%-2u, %-2u] Compare ", position.extent.row + 1, position.extent.column); - iterator_print_state(&old_iter); - printf("\tvs\t"); - iterator_print_state(&new_iter); - puts(""); - #endif - - // Compare the old and new subtrees. - IteratorComparison comparison = iterator_compare(&old_iter, &new_iter); - - // Even if the two subtrees appear to be identical, they could differ - // internally if they contain a range of text that was previously - // excluded from the parse, and is now included, or vice-versa. - if (comparison == IteratorMatches && ts_range_array_intersects( - included_range_differences, - included_range_difference_index, - position.bytes, - iterator_end_position(&old_iter).bytes - )) { - comparison = IteratorMayDiffer; - } - - bool is_changed = false; - switch (comparison) { - // If the subtrees are definitely identical, move to the end - // of both subtrees. - case IteratorMatches: - next_position = iterator_end_position(&old_iter); - break; - - // If the subtrees might differ internally, descend into both - // subtrees, finding the first child that spans the current position. - case IteratorMayDiffer: - if (iterator_descend(&old_iter, position.bytes)) { - if (!iterator_descend(&new_iter, position.bytes)) { - is_changed = true; - next_position = iterator_end_position(&old_iter); - } - } else if (iterator_descend(&new_iter, position.bytes)) { - is_changed = true; - next_position = iterator_end_position(&new_iter); - } else { - next_position = length_min( - iterator_end_position(&old_iter), - iterator_end_position(&new_iter) - ); - } - break; - - // If the subtrees are different, record a change and then move - // to the end of both subtrees. - case IteratorDiffers: - is_changed = true; - next_position = length_min( - iterator_end_position(&old_iter), - iterator_end_position(&new_iter) - ); - break; - } - - // Ensure that both iterators are caught up to the current position. - while ( - !iterator_done(&old_iter) && - iterator_end_position(&old_iter).bytes <= next_position.bytes - ) iterator_advance(&old_iter); - while ( - !iterator_done(&new_iter) && - iterator_end_position(&new_iter).bytes <= next_position.bytes - ) iterator_advance(&new_iter); - - // Ensure that both iterators are at the same depth in the tree. - while (old_iter.visible_depth > new_iter.visible_depth) { - iterator_ascend(&old_iter); - } - while (new_iter.visible_depth > old_iter.visible_depth) { - iterator_ascend(&new_iter); - } - - if (is_changed) { - #ifdef DEBUG_GET_CHANGED_RANGES - printf( - " change: [[%u, %u] - [%u, %u]]\n", - position.extent.row + 1, position.extent.column, - next_position.extent.row + 1, next_position.extent.column - ); - #endif - - ts_range_array_add(&results, position, next_position); - } - - position = next_position; - - // Keep track of the current position in the included range differences - // array in order to avoid scanning the entire array on each iteration. - while (included_range_difference_index < included_range_differences->size) { - const TSRange *range = &included_range_differences->contents[ - included_range_difference_index - ]; - if (range->end_byte <= position.bytes) { - included_range_difference_index++; - } else { - break; - } - } - } while (!iterator_done(&old_iter) && !iterator_done(&new_iter)); - - Length old_size = ts_subtree_total_size(*old_tree); - Length new_size = ts_subtree_total_size(*new_tree); - if (old_size.bytes < new_size.bytes) { - ts_range_array_add(&results, old_size, new_size); - } else if (new_size.bytes < old_size.bytes) { - ts_range_array_add(&results, new_size, old_size); - } - - *cursor1 = old_iter.cursor; - *cursor2 = new_iter.cursor; - *ranges = results.contents; - return results.size; -} diff --git a/src/tree_sitter/get_changed_ranges.h b/src/tree_sitter/get_changed_ranges.h deleted file mode 100644 index a1f1dbb430..0000000000 --- a/src/tree_sitter/get_changed_ranges.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef TREE_SITTER_GET_CHANGED_RANGES_H_ -#define TREE_SITTER_GET_CHANGED_RANGES_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./tree_cursor.h" -#include "./subtree.h" - -typedef Array(TSRange) TSRangeArray; - -void ts_range_array_get_changed_ranges( - const TSRange *old_ranges, unsigned old_range_count, - const TSRange *new_ranges, unsigned new_range_count, - TSRangeArray *differences -); - -bool ts_range_array_intersects( - const TSRangeArray *self, unsigned start_index, - uint32_t start_byte, uint32_t end_byte -); - -unsigned ts_subtree_get_changed_ranges( - const Subtree *old_tree, const Subtree *new_tree, - TreeCursor *cursor1, TreeCursor *cursor2, - const TSLanguage *language, - const TSRangeArray *included_range_differences, - TSRange **ranges -); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_GET_CHANGED_RANGES_H_ diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c deleted file mode 100644 index c00c49e3c0..0000000000 --- a/src/tree_sitter/language.c +++ /dev/null @@ -1,149 +0,0 @@ -#include "./language.h" -#include "./subtree.h" -#include "./error_costs.h" -#include <string.h> - -uint32_t ts_language_symbol_count(const TSLanguage *self) { - return self->symbol_count + self->alias_count; -} - -uint32_t ts_language_version(const TSLanguage *self) { - return self->version; -} - -uint32_t ts_language_field_count(const TSLanguage *self) { - if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS) { - return self->field_count; - } else { - return 0; - } -} - -void ts_language_table_entry( - const TSLanguage *self, - TSStateId state, - TSSymbol symbol, - TableEntry *result -) { - if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) { - result->action_count = 0; - result->is_reusable = false; - result->actions = NULL; - } else { - assert(symbol < self->token_count); - uint32_t action_index = ts_language_lookup(self, state, symbol); - const TSParseActionEntry *entry = &self->parse_actions[action_index]; - result->action_count = entry->entry.count; - result->is_reusable = entry->entry.reusable; - result->actions = (const TSParseAction *)(entry + 1); - } -} - -TSSymbolMetadata ts_language_symbol_metadata( - const TSLanguage *self, - TSSymbol symbol -) { - if (symbol == ts_builtin_sym_error) { - return (TSSymbolMetadata){.visible = true, .named = true}; - } else if (symbol == ts_builtin_sym_error_repeat) { - return (TSSymbolMetadata){.visible = false, .named = false}; - } else { - return self->symbol_metadata[symbol]; - } -} - -TSSymbol ts_language_public_symbol( - const TSLanguage *self, - TSSymbol symbol -) { - if (symbol == ts_builtin_sym_error) return symbol; - if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) { - return self->public_symbol_map[symbol]; - } else { - return symbol; - } -} - -const char *ts_language_symbol_name( - const TSLanguage *self, - TSSymbol symbol -) { - if (symbol == ts_builtin_sym_error) { - return "ERROR"; - } else if (symbol == ts_builtin_sym_error_repeat) { - return "_ERROR"; - } else if (symbol < ts_language_symbol_count(self)) { - return self->symbol_names[symbol]; - } else { - return NULL; - } -} - -TSSymbol ts_language_symbol_for_name( - const TSLanguage *self, - const char *string, - uint32_t length, - bool is_named -) { - if (!strncmp(string, "ERROR", length)) return ts_builtin_sym_error; - uint32_t count = ts_language_symbol_count(self); - for (TSSymbol i = 0; i < count; i++) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(self, i); - if (!metadata.visible || metadata.named != is_named) continue; - const char *symbol_name = self->symbol_names[i]; - if (!strncmp(symbol_name, string, length) && !symbol_name[length]) { - if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) { - return self->public_symbol_map[i]; - } else { - return i; - } - } - } - return 0; -} - -TSSymbolType ts_language_symbol_type( - const TSLanguage *self, - TSSymbol symbol -) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(self, symbol); - if (metadata.named) { - return TSSymbolTypeRegular; - } else if (metadata.visible) { - return TSSymbolTypeAnonymous; - } else { - return TSSymbolTypeAuxiliary; - } -} - -const char *ts_language_field_name_for_id( - const TSLanguage *self, - TSFieldId id -) { - uint32_t count = ts_language_field_count(self); - if (count && id <= count) { - return self->field_names[id]; - } else { - return NULL; - } -} - -TSFieldId ts_language_field_id_for_name( - const TSLanguage *self, - const char *name, - uint32_t name_length -) { - uint32_t count = ts_language_field_count(self); - for (TSSymbol i = 1; i < count + 1; i++) { - switch (strncmp(name, self->field_names[i], name_length)) { - case 0: - if (self->field_names[i][name_length] == 0) return i; - break; - case -1: - return 0; - default: - break; - } - } - return 0; -} diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h deleted file mode 100644 index 341f0f85af..0000000000 --- a/src/tree_sitter/language.h +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef TREE_SITTER_LANGUAGE_H_ -#define TREE_SITTER_LANGUAGE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./subtree.h" -#include "tree_sitter/parser.h" - -#define ts_builtin_sym_error_repeat (ts_builtin_sym_error - 1) -#define TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS 10 -#define TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING 11 -#define TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES 11 - -typedef struct { - const TSParseAction *actions; - uint32_t action_count; - bool is_reusable; -} TableEntry; - -void ts_language_table_entry(const TSLanguage *, TSStateId, TSSymbol, TableEntry *); - -TSSymbolMetadata ts_language_symbol_metadata(const TSLanguage *, TSSymbol); - -TSSymbol ts_language_public_symbol(const TSLanguage *, TSSymbol); - -static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymbol symbol) { - return 0 < symbol && symbol < self->external_token_count + 1; -} - -static inline const TSParseAction *ts_language_actions( - const TSLanguage *self, - TSStateId state, - TSSymbol symbol, - uint32_t *count -) { - TableEntry entry; - ts_language_table_entry(self, state, symbol, &entry); - *count = entry.action_count; - return entry.actions; -} - -static inline bool ts_language_has_actions(const TSLanguage *self, - TSStateId state, - TSSymbol symbol) { - TableEntry entry; - ts_language_table_entry(self, state, symbol, &entry); - return entry.action_count > 0; -} - -static inline bool ts_language_has_reduce_action(const TSLanguage *self, - TSStateId state, - TSSymbol symbol) { - TableEntry entry; - ts_language_table_entry(self, state, symbol, &entry); - return entry.action_count > 0 && entry.actions[0].type == TSParseActionTypeReduce; -} - -static inline uint16_t ts_language_lookup( - const TSLanguage *self, - TSStateId state, - TSSymbol symbol -) { - if ( - self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES && - state >= self->large_state_count - ) { - uint32_t index = self->small_parse_table_map[state - self->large_state_count]; - const uint16_t *data = &self->small_parse_table[index]; - uint16_t section_count = *(data++); - for (unsigned i = 0; i < section_count; i++) { - uint16_t section_value = *(data++); - uint16_t symbol_count = *(data++); - for (unsigned i = 0; i < symbol_count; i++) { - if (*(data++) == symbol) return section_value; - } - } - return 0; - } else { - return self->parse_table[state * self->symbol_count + symbol]; - } -} - -static inline TSStateId ts_language_next_state(const TSLanguage *self, - TSStateId state, - TSSymbol symbol) { - if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) { - return 0; - } else if (symbol < self->token_count) { - uint32_t count; - const TSParseAction *actions = ts_language_actions(self, state, symbol, &count); - if (count > 0) { - TSParseAction action = actions[count - 1]; - if (action.type == TSParseActionTypeShift) { - return action.params.shift.extra ? state : action.params.shift.state; - } - } - return 0; - } else { - return ts_language_lookup(self, state, symbol); - } -} - -static inline const bool * -ts_language_enabled_external_tokens(const TSLanguage *self, - unsigned external_scanner_state) { - if (external_scanner_state == 0) { - return NULL; - } else { - return self->external_scanner.states + self->external_token_count * external_scanner_state; - } -} - -static inline const TSSymbol * -ts_language_alias_sequence(const TSLanguage *self, uint32_t production_id) { - return production_id > 0 ? - self->alias_sequences + production_id * self->max_alias_sequence_length : - NULL; -} - -static inline void ts_language_field_map( - const TSLanguage *self, - uint32_t production_id, - const TSFieldMapEntry **start, - const TSFieldMapEntry **end -) { - if (self->version < TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS || self->field_count == 0) { - *start = NULL; - *end = NULL; - return; - } - - TSFieldMapSlice slice = self->field_map_slices[production_id]; - *start = &self->field_map_entries[slice.index]; - *end = &self->field_map_entries[slice.index] + slice.length; -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_LANGUAGE_H_ diff --git a/src/tree_sitter/length.h b/src/tree_sitter/length.h deleted file mode 100644 index 61de9fc1d5..0000000000 --- a/src/tree_sitter/length.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef TREE_SITTER_LENGTH_H_ -#define TREE_SITTER_LENGTH_H_ - -#include <stdlib.h> -#include <stdbool.h> -#include "./point.h" -#include "tree_sitter/api.h" - -typedef struct { - uint32_t bytes; - TSPoint extent; -} Length; - -static const Length LENGTH_UNDEFINED = {0, {0, 1}}; -static const Length LENGTH_MAX = {UINT32_MAX, {UINT32_MAX, UINT32_MAX}}; - -static inline bool length_is_undefined(Length length) { - return length.bytes == 0 && length.extent.column != 0; -} - -static inline Length length_min(Length len1, Length len2) { - return (len1.bytes < len2.bytes) ? len1 : len2; -} - -static inline Length length_add(Length len1, Length len2) { - Length result; - result.bytes = len1.bytes + len2.bytes; - result.extent = point_add(len1.extent, len2.extent); - return result; -} - -static inline Length length_sub(Length len1, Length len2) { - Length result; - result.bytes = len1.bytes - len2.bytes; - result.extent = point_sub(len1.extent, len2.extent); - return result; -} - -static inline Length length_zero(void) { - Length result = {0, {0, 0}}; - return result; -} - -#endif diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c deleted file mode 100644 index 3f8a4c0ae8..0000000000 --- a/src/tree_sitter/lexer.c +++ /dev/null @@ -1,391 +0,0 @@ -#include <stdio.h> -#include "./lexer.h" -#include "./subtree.h" -#include "./length.h" -#include "./unicode.h" - -#define LOG(message, character) \ - if (self->logger.log) { \ - snprintf( \ - self->debug_buffer, \ - TREE_SITTER_SERIALIZATION_BUFFER_SIZE, \ - 32 <= character && character < 127 ? \ - message " character:'%c'" : \ - message " character:%d", \ - character \ - ); \ - self->logger.log( \ - self->logger.payload, \ - TSLogTypeLex, \ - self->debug_buffer \ - ); \ - } - -static const int32_t BYTE_ORDER_MARK = 0xFEFF; - -static const TSRange DEFAULT_RANGE = { - .start_point = { - .row = 0, - .column = 0, - }, - .end_point = { - .row = UINT32_MAX, - .column = UINT32_MAX, - }, - .start_byte = 0, - .end_byte = UINT32_MAX -}; - -// Check if the lexer has reached EOF. This state is stored -// by setting the lexer's `current_included_range_index` such that -// it has consumed all of its available ranges. -static bool ts_lexer__eof(const TSLexer *_self) { - Lexer *self = (Lexer *)_self; - return self->current_included_range_index == self->included_range_count; -} - -// Clear the currently stored chunk of source code, because the lexer's -// position has changed. -static void ts_lexer__clear_chunk(Lexer *self) { - self->chunk = NULL; - self->chunk_size = 0; - self->chunk_start = 0; -} - -// Call the lexer's input callback to obtain a new chunk of source code -// for the current position. -static void ts_lexer__get_chunk(Lexer *self) { - self->chunk_start = self->current_position.bytes; - self->chunk = self->input.read( - self->input.payload, - self->current_position.bytes, - self->current_position.extent, - &self->chunk_size - ); - if (!self->chunk_size) { - self->current_included_range_index = self->included_range_count; - self->chunk = NULL; - } -} - -// Decode the next unicode character in the current chunk of source code. -// This assumes that the lexer has already retrieved a chunk of source -// code that spans the current position. -static void ts_lexer__get_lookahead(Lexer *self) { - uint32_t position_in_chunk = self->current_position.bytes - self->chunk_start; - const uint8_t *chunk = (const uint8_t *)self->chunk + position_in_chunk; - uint32_t size = self->chunk_size - position_in_chunk; - - if (size == 0) { - self->lookahead_size = 1; - self->data.lookahead = '\0'; - return; - } - - UnicodeDecodeFunction decode = self->input.encoding == TSInputEncodingUTF8 - ? ts_decode_utf8 - : ts_decode_utf16; - - self->lookahead_size = decode(chunk, size, &self->data.lookahead); - - // If this chunk ended in the middle of a multi-byte character, - // try again with a fresh chunk. - if (self->data.lookahead == TS_DECODE_ERROR && size < 4) { - ts_lexer__get_chunk(self); - chunk = (const uint8_t *)self->chunk; - size = self->chunk_size; - self->lookahead_size = decode(chunk, size, &self->data.lookahead); - } - - if (self->data.lookahead == TS_DECODE_ERROR) { - self->lookahead_size = 1; - } -} - -// Advance to the next character in the source code, retrieving a new -// chunk of source code if needed. -static void ts_lexer__advance(TSLexer *_self, bool skip) { - Lexer *self = (Lexer *)_self; - if (!self->chunk) return; - - if (skip) { - LOG("skip", self->data.lookahead); - } else { - LOG("consume", self->data.lookahead); - } - - if (self->lookahead_size) { - self->current_position.bytes += self->lookahead_size; - if (self->data.lookahead == '\n') { - self->current_position.extent.row++; - self->current_position.extent.column = 0; - } else { - self->current_position.extent.column += self->lookahead_size; - } - } - - const TSRange *current_range = NULL; - if (self->current_included_range_index < self->included_range_count) { - current_range = &self->included_ranges[self->current_included_range_index]; - if (self->current_position.bytes == current_range->end_byte) { - self->current_included_range_index++; - if (self->current_included_range_index < self->included_range_count) { - current_range++; - self->current_position = (Length) { - current_range->start_byte, - current_range->start_point, - }; - } else { - current_range = NULL; - } - } - } - - if (skip) self->token_start_position = self->current_position; - - if (current_range) { - if (self->current_position.bytes >= self->chunk_start + self->chunk_size) { - ts_lexer__get_chunk(self); - } - ts_lexer__get_lookahead(self); - } else { - ts_lexer__clear_chunk(self); - self->data.lookahead = '\0'; - self->lookahead_size = 1; - } -} - -// Mark that a token match has completed. This can be called multiple -// times if a longer match is found later. -static void ts_lexer__mark_end(TSLexer *_self) { - Lexer *self = (Lexer *)_self; - if (!ts_lexer__eof(&self->data)) { - // If the lexer is right at the beginning of included range, - // then the token should be considered to end at the *end* of the - // previous included range, rather than here. - TSRange *current_included_range = &self->included_ranges[ - self->current_included_range_index - ]; - if ( - self->current_included_range_index > 0 && - self->current_position.bytes == current_included_range->start_byte - ) { - TSRange *previous_included_range = current_included_range - 1; - self->token_end_position = (Length) { - previous_included_range->end_byte, - previous_included_range->end_point, - }; - return; - } - } - self->token_end_position = self->current_position; -} - -static uint32_t ts_lexer__get_column(TSLexer *_self) { - Lexer *self = (Lexer *)_self; - uint32_t goal_byte = self->current_position.bytes; - - self->current_position.bytes -= self->current_position.extent.column; - self->current_position.extent.column = 0; - - if (self->current_position.bytes < self->chunk_start) { - ts_lexer__get_chunk(self); - } - - uint32_t result = 0; - while (self->current_position.bytes < goal_byte) { - ts_lexer__advance(&self->data, false); - result++; - } - - return result; -} - -// Is the lexer at a boundary between two disjoint included ranges of -// source code? This is exposed as an API because some languages' external -// scanners need to perform custom actions at these bounaries. -static bool ts_lexer__is_at_included_range_start(const TSLexer *_self) { - const Lexer *self = (const Lexer *)_self; - if (self->current_included_range_index < self->included_range_count) { - TSRange *current_range = &self->included_ranges[self->current_included_range_index]; - return self->current_position.bytes == current_range->start_byte; - } else { - return false; - } -} - -void ts_lexer_init(Lexer *self) { - *self = (Lexer) { - .data = { - // The lexer's methods are stored as struct fields so that generated - // parsers can call them without needing to be linked against this - // library. - .advance = ts_lexer__advance, - .mark_end = ts_lexer__mark_end, - .get_column = ts_lexer__get_column, - .is_at_included_range_start = ts_lexer__is_at_included_range_start, - .eof = ts_lexer__eof, - .lookahead = 0, - .result_symbol = 0, - }, - .chunk = NULL, - .chunk_size = 0, - .chunk_start = 0, - .current_position = {0, {0, 0}}, - .logger = { - .payload = NULL, - .log = NULL - }, - .included_ranges = NULL, - .included_range_count = 0, - .current_included_range_index = 0, - }; - ts_lexer_set_included_ranges(self, NULL, 0); -} - -void ts_lexer_delete(Lexer *self) { - ts_free(self->included_ranges); -} - -static void ts_lexer_goto(Lexer *self, Length position) { - self->current_position = position; - bool found_included_range = false; - - // Move to the first valid position at or after the given position. - for (unsigned i = 0; i < self->included_range_count; i++) { - TSRange *included_range = &self->included_ranges[i]; - if (included_range->end_byte > position.bytes) { - if (included_range->start_byte > position.bytes) { - self->current_position = (Length) { - .bytes = included_range->start_byte, - .extent = included_range->start_point, - }; - } - - self->current_included_range_index = i; - found_included_range = true; - break; - } - } - - if (found_included_range) { - // If the current position is outside of the current chunk of text, - // then clear out the current chunk of text. - if (self->chunk && ( - position.bytes < self->chunk_start || - position.bytes >= self->chunk_start + self->chunk_size - )) { - ts_lexer__clear_chunk(self); - } - - self->lookahead_size = 0; - self->data.lookahead = '\0'; - } - - // If the given position is beyond any of included ranges, move to the EOF - // state - past the end of the included ranges. - else { - self->current_included_range_index = self->included_range_count; - TSRange *last_included_range = &self->included_ranges[self->included_range_count - 1]; - self->current_position = (Length) { - .bytes = last_included_range->end_byte, - .extent = last_included_range->end_point, - }; - ts_lexer__clear_chunk(self); - self->lookahead_size = 1; - self->data.lookahead = '\0'; - } -} - -void ts_lexer_set_input(Lexer *self, TSInput input) { - self->input = input; - ts_lexer__clear_chunk(self); - ts_lexer_goto(self, self->current_position); -} - -// Move the lexer to the given position. This doesn't do any work -// if the parser is already at the given position. -void ts_lexer_reset(Lexer *self, Length position) { - if (position.bytes != self->current_position.bytes) { - ts_lexer_goto(self, position); - } -} - -void ts_lexer_start(Lexer *self) { - self->token_start_position = self->current_position; - self->token_end_position = LENGTH_UNDEFINED; - self->data.result_symbol = 0; - if (!ts_lexer__eof(&self->data)) { - if (!self->chunk_size) ts_lexer__get_chunk(self); - if (!self->lookahead_size) ts_lexer__get_lookahead(self); - if ( - self->current_position.bytes == 0 && - self->data.lookahead == BYTE_ORDER_MARK - ) ts_lexer__advance(&self->data, true); - } -} - -void ts_lexer_finish(Lexer *self, uint32_t *lookahead_end_byte) { - if (length_is_undefined(self->token_end_position)) { - ts_lexer__mark_end(&self->data); - } - - uint32_t current_lookahead_end_byte = self->current_position.bytes + 1; - - // In order to determine that a byte sequence is invalid UTF8 or UTF16, - // the character decoding algorithm may have looked at the following byte. - // Therefore, the next byte *after* the current (invalid) character - // affects the interpretation of the current character. - if (self->data.lookahead == TS_DECODE_ERROR) { - current_lookahead_end_byte++; - } - - if (current_lookahead_end_byte > *lookahead_end_byte) { - *lookahead_end_byte = current_lookahead_end_byte; - } -} - -void ts_lexer_advance_to_end(Lexer *self) { - while (self->chunk) { - ts_lexer__advance(&self->data, false); - } -} - -void ts_lexer_mark_end(Lexer *self) { - ts_lexer__mark_end(&self->data); -} - -bool ts_lexer_set_included_ranges( - Lexer *self, - const TSRange *ranges, - uint32_t count -) { - if (count == 0 || !ranges) { - ranges = &DEFAULT_RANGE; - count = 1; - } else { - uint32_t previous_byte = 0; - for (unsigned i = 0; i < count; i++) { - const TSRange *range = &ranges[i]; - if ( - range->start_byte < previous_byte || - range->end_byte < range->start_byte - ) return false; - previous_byte = range->end_byte; - } - } - - size_t size = count * sizeof(TSRange); - self->included_ranges = ts_realloc(self->included_ranges, size); - memcpy(self->included_ranges, ranges, size); - self->included_range_count = count; - ts_lexer_goto(self, self->current_position); - return true; -} - -TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count) { - *count = self->included_range_count; - return self->included_ranges; -} - -#undef LOG diff --git a/src/tree_sitter/lexer.h b/src/tree_sitter/lexer.h deleted file mode 100644 index 5e39294529..0000000000 --- a/src/tree_sitter/lexer.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef TREE_SITTER_LEXER_H_ -#define TREE_SITTER_LEXER_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./length.h" -#include "./subtree.h" -#include "tree_sitter/api.h" -#include "tree_sitter/parser.h" - -typedef struct { - TSLexer data; - Length current_position; - Length token_start_position; - Length token_end_position; - - TSRange *included_ranges; - size_t included_range_count; - size_t current_included_range_index; - - const char *chunk; - uint32_t chunk_start; - uint32_t chunk_size; - uint32_t lookahead_size; - - TSInput input; - TSLogger logger; - char debug_buffer[TREE_SITTER_SERIALIZATION_BUFFER_SIZE]; -} Lexer; - -void ts_lexer_init(Lexer *); -void ts_lexer_delete(Lexer *); -void ts_lexer_set_input(Lexer *, TSInput); -void ts_lexer_reset(Lexer *, Length); -void ts_lexer_start(Lexer *); -void ts_lexer_finish(Lexer *, uint32_t *); -void ts_lexer_advance_to_end(Lexer *); -void ts_lexer_mark_end(Lexer *); -bool ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count); -TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_LEXER_H_ diff --git a/src/tree_sitter/lib.c b/src/tree_sitter/lib.c deleted file mode 100644 index 289d32f4c5..0000000000 --- a/src/tree_sitter/lib.c +++ /dev/null @@ -1,17 +0,0 @@ -// The Tree-sitter library can be built by compiling this one source file. -// -// The following directories must be added to the include path: -// - include - -#define _POSIX_C_SOURCE 200112L - -#include "./get_changed_ranges.c" -#include "./language.c" -#include "./lexer.c" -#include "./node.c" -#include "./parser.c" -#include "./query.c" -#include "./stack.c" -#include "./subtree.c" -#include "./tree_cursor.c" -#include "./tree.c" diff --git a/src/tree_sitter/node.c b/src/tree_sitter/node.c deleted file mode 100644 index 576f3ef38e..0000000000 --- a/src/tree_sitter/node.c +++ /dev/null @@ -1,677 +0,0 @@ -#include <stdbool.h> -#include "./subtree.h" -#include "./tree.h" -#include "./language.h" - -typedef struct { - Subtree parent; - const TSTree *tree; - Length position; - uint32_t child_index; - uint32_t structural_child_index; - const TSSymbol *alias_sequence; -} NodeChildIterator; - -// TSNode - constructors - -TSNode ts_node_new( - const TSTree *tree, - const Subtree *subtree, - Length position, - TSSymbol alias -) { - return (TSNode) { - {position.bytes, position.extent.row, position.extent.column, alias}, - subtree, - tree, - }; -} - -static inline TSNode ts_node__null(void) { - return ts_node_new(NULL, NULL, length_zero(), 0); -} - -// TSNode - accessors - -uint32_t ts_node_start_byte(TSNode self) { - return self.context[0]; -} - -TSPoint ts_node_start_point(TSNode self) { - return (TSPoint) {self.context[1], self.context[2]}; -} - -static inline uint32_t ts_node__alias(const TSNode *self) { - return self->context[3]; -} - -static inline Subtree ts_node__subtree(TSNode self) { - return *(const Subtree *)self.id; -} - -// NodeChildIterator - -static inline NodeChildIterator ts_node_iterate_children(const TSNode *node) { - Subtree subtree = ts_node__subtree(*node); - if (ts_subtree_child_count(subtree) == 0) { - return (NodeChildIterator) {NULL_SUBTREE, node->tree, length_zero(), 0, 0, NULL}; - } - const TSSymbol *alias_sequence = ts_language_alias_sequence( - node->tree->language, - subtree.ptr->production_id - ); - return (NodeChildIterator) { - .tree = node->tree, - .parent = subtree, - .position = {ts_node_start_byte(*node), ts_node_start_point(*node)}, - .child_index = 0, - .structural_child_index = 0, - .alias_sequence = alias_sequence, - }; -} - -static inline bool ts_node_child_iterator_done(NodeChildIterator *self) { - return self->child_index == self->parent.ptr->child_count; -} - -static inline bool ts_node_child_iterator_next( - NodeChildIterator *self, - TSNode *result -) { - if (!self->parent.ptr || ts_node_child_iterator_done(self)) return false; - const Subtree *child = &self->parent.ptr->children[self->child_index]; - TSSymbol alias_symbol = 0; - if (!ts_subtree_extra(*child)) { - if (self->alias_sequence) { - alias_symbol = self->alias_sequence[self->structural_child_index]; - } - self->structural_child_index++; - } - if (self->child_index > 0) { - self->position = length_add(self->position, ts_subtree_padding(*child)); - } - *result = ts_node_new( - self->tree, - child, - self->position, - alias_symbol - ); - self->position = length_add(self->position, ts_subtree_size(*child)); - self->child_index++; - return true; -} - -// TSNode - private - -static inline bool ts_node__is_relevant(TSNode self, bool include_anonymous) { - Subtree tree = ts_node__subtree(self); - if (include_anonymous) { - return ts_subtree_visible(tree) || ts_node__alias(&self); - } else { - TSSymbol alias = ts_node__alias(&self); - if (alias) { - return ts_language_symbol_metadata(self.tree->language, alias).named; - } else { - return ts_subtree_visible(tree) && ts_subtree_named(tree); - } - } -} - -static inline uint32_t ts_node__relevant_child_count( - TSNode self, - bool include_anonymous -) { - Subtree tree = ts_node__subtree(self); - if (ts_subtree_child_count(tree) > 0) { - if (include_anonymous) { - return tree.ptr->visible_child_count; - } else { - return tree.ptr->named_child_count; - } - } else { - return 0; - } -} - -static inline TSNode ts_node__child( - TSNode self, - uint32_t child_index, - bool include_anonymous -) { - TSNode result = self; - bool did_descend = true; - - while (did_descend) { - did_descend = false; - - TSNode child; - uint32_t index = 0; - NodeChildIterator iterator = ts_node_iterate_children(&result); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (ts_node__is_relevant(child, include_anonymous)) { - if (index == child_index) { - if (ts_node__is_relevant(self, true)) { - ts_tree_set_cached_parent(self.tree, &child, &self); - } - return child; - } - index++; - } else { - uint32_t grandchild_index = child_index - index; - uint32_t grandchild_count = ts_node__relevant_child_count(child, include_anonymous); - if (grandchild_index < grandchild_count) { - did_descend = true; - result = child; - child_index = grandchild_index; - break; - } - index += grandchild_count; - } - } - } - - return ts_node__null(); -} - -static bool ts_subtree_has_trailing_empty_descendant( - Subtree self, - Subtree other -) { - for (unsigned i = ts_subtree_child_count(self) - 1; i + 1 > 0; i--) { - Subtree child = self.ptr->children[i]; - if (ts_subtree_total_bytes(child) > 0) break; - if (child.ptr == other.ptr || ts_subtree_has_trailing_empty_descendant(child, other)) { - return true; - } - } - return false; -} - -static inline TSNode ts_node__prev_sibling(TSNode self, bool include_anonymous) { - Subtree self_subtree = ts_node__subtree(self); - bool self_is_empty = ts_subtree_total_bytes(self_subtree) == 0; - uint32_t target_end_byte = ts_node_end_byte(self); - - TSNode node = ts_node_parent(self); - TSNode earlier_node = ts_node__null(); - bool earlier_node_is_relevant = false; - - while (!ts_node_is_null(node)) { - TSNode earlier_child = ts_node__null(); - bool earlier_child_is_relevant = false; - bool found_child_containing_target = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (child.id == self.id) break; - if (iterator.position.bytes > target_end_byte) { - found_child_containing_target = true; - break; - } - - if (iterator.position.bytes == target_end_byte && - (!self_is_empty || - ts_subtree_has_trailing_empty_descendant(ts_node__subtree(child), self_subtree))) { - found_child_containing_target = true; - break; - } - - if (ts_node__is_relevant(child, include_anonymous)) { - earlier_child = child; - earlier_child_is_relevant = true; - } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) { - earlier_child = child; - earlier_child_is_relevant = false; - } - } - - if (found_child_containing_target) { - if (!ts_node_is_null(earlier_child)) { - earlier_node = earlier_child; - earlier_node_is_relevant = earlier_child_is_relevant; - } - node = child; - } else if (earlier_child_is_relevant) { - return earlier_child; - } else if (!ts_node_is_null(earlier_child)) { - node = earlier_child; - } else if (earlier_node_is_relevant) { - return earlier_node; - } else { - node = earlier_node; - } - } - - return ts_node__null(); -} - -static inline TSNode ts_node__next_sibling(TSNode self, bool include_anonymous) { - uint32_t target_end_byte = ts_node_end_byte(self); - - TSNode node = ts_node_parent(self); - TSNode later_node = ts_node__null(); - bool later_node_is_relevant = false; - - while (!ts_node_is_null(node)) { - TSNode later_child = ts_node__null(); - bool later_child_is_relevant = false; - TSNode child_containing_target = ts_node__null(); - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (iterator.position.bytes < target_end_byte) continue; - if (ts_node_start_byte(child) <= ts_node_start_byte(self)) { - if (ts_node__subtree(child).ptr != ts_node__subtree(self).ptr) { - child_containing_target = child; - } - } else if (ts_node__is_relevant(child, include_anonymous)) { - later_child = child; - later_child_is_relevant = true; - break; - } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) { - later_child = child; - later_child_is_relevant = false; - break; - } - } - - if (!ts_node_is_null(child_containing_target)) { - if (!ts_node_is_null(later_child)) { - later_node = later_child; - later_node_is_relevant = later_child_is_relevant; - } - node = child_containing_target; - } else if (later_child_is_relevant) { - return later_child; - } else if (!ts_node_is_null(later_child)) { - node = later_child; - } else if (later_node_is_relevant) { - return later_node; - } else { - node = later_node; - } - } - - return ts_node__null(); -} - -static inline TSNode ts_node__first_child_for_byte( - TSNode self, - uint32_t goal, - bool include_anonymous -) { - TSNode node = self; - bool did_descend = true; - - while (did_descend) { - did_descend = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (ts_node_end_byte(child) > goal) { - if (ts_node__is_relevant(child, include_anonymous)) { - return child; - } else if (ts_node_child_count(child) > 0) { - did_descend = true; - node = child; - break; - } - } - } - } - - return ts_node__null(); -} - -static inline TSNode ts_node__descendant_for_byte_range( - TSNode self, - uint32_t range_start, - uint32_t range_end, - bool include_anonymous -) { - TSNode node = self; - TSNode last_visible_node = self; - - bool did_descend = true; - while (did_descend) { - did_descend = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - uint32_t node_end = iterator.position.bytes; - - // The end of this node must extend far enough forward to touch - // the end of the range and exceed the start of the range. - if (node_end < range_end) continue; - if (node_end <= range_start) continue; - - // The start of this node must extend far enough backward to - // touch the start of the range. - if (range_start < ts_node_start_byte(child)) break; - - node = child; - if (ts_node__is_relevant(node, include_anonymous)) { - ts_tree_set_cached_parent(self.tree, &child, &last_visible_node); - last_visible_node = node; - } - did_descend = true; - break; - } - } - - return last_visible_node; -} - -static inline TSNode ts_node__descendant_for_point_range( - TSNode self, - TSPoint range_start, - TSPoint range_end, - bool include_anonymous -) { - TSNode node = self; - TSNode last_visible_node = self; - - bool did_descend = true; - while (did_descend) { - did_descend = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - TSPoint node_end = iterator.position.extent; - - // The end of this node must extend far enough forward to touch - // the end of the range and exceed the start of the range. - if (point_lt(node_end, range_end)) continue; - if (point_lte(node_end, range_start)) continue; - - // The start of this node must extend far enough backward to - // touch the start of the range. - if (point_lt(range_start, ts_node_start_point(child))) break; - - node = child; - if (ts_node__is_relevant(node, include_anonymous)) { - ts_tree_set_cached_parent(self.tree, &child, &last_visible_node); - last_visible_node = node; - } - did_descend = true; - break; - } - } - - return last_visible_node; -} - -// TSNode - public - -uint32_t ts_node_end_byte(TSNode self) { - return ts_node_start_byte(self) + ts_subtree_size(ts_node__subtree(self)).bytes; -} - -TSPoint ts_node_end_point(TSNode self) { - return point_add(ts_node_start_point(self), ts_subtree_size(ts_node__subtree(self)).extent); -} - -TSSymbol ts_node_symbol(TSNode self) { - TSSymbol symbol = ts_node__alias(&self); - if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self)); - return ts_language_public_symbol(self.tree->language, symbol); -} - -const char *ts_node_type(TSNode self) { - TSSymbol symbol = ts_node__alias(&self); - if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self)); - return ts_language_symbol_name(self.tree->language, symbol); -} - -char *ts_node_string(TSNode self) { - return ts_subtree_string(ts_node__subtree(self), self.tree->language, false); -} - -bool ts_node_eq(TSNode self, TSNode other) { - return self.tree == other.tree && self.id == other.id; -} - -bool ts_node_is_null(TSNode self) { - return self.id == 0; -} - -bool ts_node_is_extra(TSNode self) { - return ts_subtree_extra(ts_node__subtree(self)); -} - -bool ts_node_is_named(TSNode self) { - TSSymbol alias = ts_node__alias(&self); - return alias - ? ts_language_symbol_metadata(self.tree->language, alias).named - : ts_subtree_named(ts_node__subtree(self)); -} - -bool ts_node_is_missing(TSNode self) { - return ts_subtree_missing(ts_node__subtree(self)); -} - -bool ts_node_has_changes(TSNode self) { - return ts_subtree_has_changes(ts_node__subtree(self)); -} - -bool ts_node_has_error(TSNode self) { - return ts_subtree_error_cost(ts_node__subtree(self)) > 0; -} - -TSNode ts_node_parent(TSNode self) { - TSNode node = ts_tree_get_cached_parent(self.tree, &self); - if (node.id) return node; - - node = ts_tree_root_node(self.tree); - uint32_t end_byte = ts_node_end_byte(self); - if (node.id == self.id) return ts_node__null(); - - TSNode last_visible_node = node; - bool did_descend = true; - while (did_descend) { - did_descend = false; - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&node); - while (ts_node_child_iterator_next(&iterator, &child)) { - if ( - ts_node_start_byte(child) > ts_node_start_byte(self) || - child.id == self.id - ) break; - if (iterator.position.bytes >= end_byte) { - node = child; - if (ts_node__is_relevant(child, true)) { - ts_tree_set_cached_parent(self.tree, &node, &last_visible_node); - last_visible_node = node; - } - did_descend = true; - break; - } - } - } - - return last_visible_node; -} - -TSNode ts_node_child(TSNode self, uint32_t child_index) { - return ts_node__child(self, child_index, true); -} - -TSNode ts_node_named_child(TSNode self, uint32_t child_index) { - return ts_node__child(self, child_index, false); -} - -TSNode ts_node_child_by_field_id(TSNode self, TSFieldId field_id) { -recur: - if (!field_id || ts_node_child_count(self) == 0) return ts_node__null(); - - const TSFieldMapEntry *field_map, *field_map_end; - ts_language_field_map( - self.tree->language, - ts_node__subtree(self).ptr->production_id, - &field_map, - &field_map_end - ); - if (field_map == field_map_end) return ts_node__null(); - - // The field mappings are sorted by their field id. Scan all - // the mappings to find the ones for the given field id. - while (field_map->field_id < field_id) { - field_map++; - if (field_map == field_map_end) return ts_node__null(); - } - while (field_map_end[-1].field_id > field_id) { - field_map_end--; - if (field_map == field_map_end) return ts_node__null(); - } - - TSNode child; - NodeChildIterator iterator = ts_node_iterate_children(&self); - while (ts_node_child_iterator_next(&iterator, &child)) { - if (!ts_subtree_extra(ts_node__subtree(child))) { - uint32_t index = iterator.structural_child_index - 1; - if (index < field_map->child_index) continue; - - // Hidden nodes' fields are "inherited" by their visible parent. - if (field_map->inherited) { - - // If this is the *last* possible child node for this field, - // then perform a tail call to avoid recursion. - if (field_map + 1 == field_map_end) { - self = child; - goto recur; - } - - // Otherwise, descend into this child, but if it doesn't contain - // the field, continue searching subsequent children. - else { - TSNode result = ts_node_child_by_field_id(child, field_id); - if (result.id) return result; - field_map++; - if (field_map == field_map_end) return ts_node__null(); - } - } - - else if (ts_node__is_relevant(child, true)) { - return child; - } - - // If the field refers to a hidden node, return its first visible - // child. - else { - return ts_node_child(child, 0); - } - } - } - - return ts_node__null(); -} - -TSNode ts_node_child_by_field_name( - TSNode self, - const char *name, - uint32_t name_length -) { - TSFieldId field_id = ts_language_field_id_for_name( - self.tree->language, - name, - name_length - ); - return ts_node_child_by_field_id(self, field_id); -} - -uint32_t ts_node_child_count(TSNode self) { - Subtree tree = ts_node__subtree(self); - if (ts_subtree_child_count(tree) > 0) { - return tree.ptr->visible_child_count; - } else { - return 0; - } -} - -uint32_t ts_node_named_child_count(TSNode self) { - Subtree tree = ts_node__subtree(self); - if (ts_subtree_child_count(tree) > 0) { - return tree.ptr->named_child_count; - } else { - return 0; - } -} - -TSNode ts_node_next_sibling(TSNode self) { - return ts_node__next_sibling(self, true); -} - -TSNode ts_node_next_named_sibling(TSNode self) { - return ts_node__next_sibling(self, false); -} - -TSNode ts_node_prev_sibling(TSNode self) { - return ts_node__prev_sibling(self, true); -} - -TSNode ts_node_prev_named_sibling(TSNode self) { - return ts_node__prev_sibling(self, false); -} - -TSNode ts_node_first_child_for_byte(TSNode self, uint32_t byte) { - return ts_node__first_child_for_byte(self, byte, true); -} - -TSNode ts_node_first_named_child_for_byte(TSNode self, uint32_t byte) { - return ts_node__first_child_for_byte(self, byte, false); -} - -TSNode ts_node_descendant_for_byte_range( - TSNode self, - uint32_t start, - uint32_t end -) { - return ts_node__descendant_for_byte_range(self, start, end, true); -} - -TSNode ts_node_named_descendant_for_byte_range( - TSNode self, - uint32_t start, - uint32_t end -) { - return ts_node__descendant_for_byte_range(self, start, end, false); -} - -TSNode ts_node_descendant_for_point_range( - TSNode self, - TSPoint start, - TSPoint end -) { - return ts_node__descendant_for_point_range(self, start, end, true); -} - -TSNode ts_node_named_descendant_for_point_range( - TSNode self, - TSPoint start, - TSPoint end -) { - return ts_node__descendant_for_point_range(self, start, end, false); -} - -void ts_node_edit(TSNode *self, const TSInputEdit *edit) { - uint32_t start_byte = ts_node_start_byte(*self); - TSPoint start_point = ts_node_start_point(*self); - - if (start_byte >= edit->old_end_byte) { - start_byte = edit->new_end_byte + (start_byte - edit->old_end_byte); - start_point = point_add(edit->new_end_point, point_sub(start_point, edit->old_end_point)); - } else if (start_byte > edit->start_byte) { - start_byte = edit->new_end_byte; - start_point = edit->new_end_point; - } - - self->context[0] = start_byte; - self->context[1] = start_point.row; - self->context[2] = start_point.column; -} diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c deleted file mode 100644 index dd222cd3c4..0000000000 --- a/src/tree_sitter/parser.c +++ /dev/null @@ -1,1879 +0,0 @@ -#include <time.h> -#include <assert.h> -#include <stdio.h> -#include <limits.h> -#include <stdbool.h> -#include "tree_sitter/api.h" -#include "./alloc.h" -#include "./array.h" -#include "./atomic.h" -#include "./clock.h" -#include "./error_costs.h" -#include "./get_changed_ranges.h" -#include "./language.h" -#include "./length.h" -#include "./lexer.h" -#include "./reduce_action.h" -#include "./reusable_node.h" -#include "./stack.h" -#include "./subtree.h" -#include "./tree.h" - -#define LOG(...) \ - if (self->lexer.logger.log || self->dot_graph_file) { \ - snprintf(self->lexer.debug_buffer, TREE_SITTER_SERIALIZATION_BUFFER_SIZE, __VA_ARGS__); \ - ts_parser__log(self); \ - } - -#define LOG_STACK() \ - if (self->dot_graph_file) { \ - ts_stack_print_dot_graph(self->stack, self->language, self->dot_graph_file); \ - fputs("\n\n", self->dot_graph_file); \ - } - -#define LOG_TREE(tree) \ - if (self->dot_graph_file) { \ - ts_subtree_print_dot_graph(tree, self->language, self->dot_graph_file); \ - fputs("\n", self->dot_graph_file); \ - } - -#define SYM_NAME(symbol) ts_language_symbol_name(self->language, symbol) - -#define TREE_NAME(tree) SYM_NAME(ts_subtree_symbol(tree)) - -static const unsigned MAX_VERSION_COUNT = 6; -static const unsigned MAX_VERSION_COUNT_OVERFLOW = 4; -static const unsigned MAX_SUMMARY_DEPTH = 16; -static const unsigned MAX_COST_DIFFERENCE = 16 * ERROR_COST_PER_SKIPPED_TREE; -static const unsigned OP_COUNT_PER_TIMEOUT_CHECK = 100; - -typedef struct { - Subtree token; - Subtree last_external_token; - uint32_t byte_index; -} TokenCache; - -struct TSParser { - Lexer lexer; - Stack *stack; - SubtreePool tree_pool; - const TSLanguage *language; - ReduceActionSet reduce_actions; - Subtree finished_tree; - SubtreeHeapData scratch_tree_data; - MutableSubtree scratch_tree; - TokenCache token_cache; - ReusableNode reusable_node; - void *external_scanner_payload; - FILE *dot_graph_file; - TSClock end_clock; - TSDuration timeout_duration; - unsigned accept_count; - unsigned operation_count; - const volatile size_t *cancellation_flag; - Subtree old_tree; - TSRangeArray included_range_differences; - unsigned included_range_difference_index; -}; - -typedef struct { - unsigned cost; - unsigned node_count; - int dynamic_precedence; - bool is_in_error; -} ErrorStatus; - -typedef enum { - ErrorComparisonTakeLeft, - ErrorComparisonPreferLeft, - ErrorComparisonNone, - ErrorComparisonPreferRight, - ErrorComparisonTakeRight, -} ErrorComparison; - -typedef struct { - const char *string; - uint32_t length; -} TSStringInput; - -// StringInput - -static const char *ts_string_input_read( - void *_self, - uint32_t byte, - TSPoint pt, - uint32_t *length -) { - (void)pt; - TSStringInput *self = (TSStringInput *)_self; - if (byte >= self->length) { - *length = 0; - return ""; - } else { - *length = self->length - byte; - return self->string + byte; - } -} - -// Parser - Private - -static void ts_parser__log(TSParser *self) { - if (self->lexer.logger.log) { - self->lexer.logger.log( - self->lexer.logger.payload, - TSLogTypeParse, - self->lexer.debug_buffer - ); - } - - if (self->dot_graph_file) { - fprintf(self->dot_graph_file, "graph {\nlabel=\""); - for (char *c = &self->lexer.debug_buffer[0]; *c != 0; c++) { - if (*c == '"') fputc('\\', self->dot_graph_file); - fputc(*c, self->dot_graph_file); - } - fprintf(self->dot_graph_file, "\"\n}\n\n"); - } -} - -static bool ts_parser__breakdown_top_of_stack( - TSParser *self, - StackVersion version -) { - bool did_break_down = false; - bool pending = false; - - do { - StackSliceArray pop = ts_stack_pop_pending(self->stack, version); - if (!pop.size) break; - - did_break_down = true; - pending = false; - for (uint32_t i = 0; i < pop.size; i++) { - StackSlice slice = pop.contents[i]; - TSStateId state = ts_stack_state(self->stack, slice.version); - Subtree parent = *array_front(&slice.subtrees); - - for (uint32_t j = 0, n = ts_subtree_child_count(parent); j < n; j++) { - Subtree child = parent.ptr->children[j]; - pending = ts_subtree_child_count(child) > 0; - - if (ts_subtree_is_error(child)) { - state = ERROR_STATE; - } else if (!ts_subtree_extra(child)) { - state = ts_language_next_state(self->language, state, ts_subtree_symbol(child)); - } - - ts_subtree_retain(child); - ts_stack_push(self->stack, slice.version, child, pending, state); - } - - for (uint32_t j = 1; j < slice.subtrees.size; j++) { - Subtree tree = slice.subtrees.contents[j]; - ts_stack_push(self->stack, slice.version, tree, false, state); - } - - ts_subtree_release(&self->tree_pool, parent); - array_delete(&slice.subtrees); - - LOG("breakdown_top_of_stack tree:%s", TREE_NAME(parent)); - LOG_STACK(); - } - } while (pending); - - return did_break_down; -} - -static void ts_parser__breakdown_lookahead( - TSParser *self, - Subtree *lookahead, - TSStateId state, - ReusableNode *reusable_node -) { - bool did_descend = false; - Subtree tree = reusable_node_tree(reusable_node); - while (ts_subtree_child_count(tree) > 0 && ts_subtree_parse_state(tree) != state) { - LOG("state_mismatch sym:%s", TREE_NAME(tree)); - reusable_node_descend(reusable_node); - tree = reusable_node_tree(reusable_node); - did_descend = true; - } - - if (did_descend) { - ts_subtree_release(&self->tree_pool, *lookahead); - *lookahead = tree; - ts_subtree_retain(*lookahead); - } -} - -static ErrorComparison ts_parser__compare_versions( - TSParser *self, - ErrorStatus a, - ErrorStatus b -) { - (void)self; - if (!a.is_in_error && b.is_in_error) { - if (a.cost < b.cost) { - return ErrorComparisonTakeLeft; - } else { - return ErrorComparisonPreferLeft; - } - } - - if (a.is_in_error && !b.is_in_error) { - if (b.cost < a.cost) { - return ErrorComparisonTakeRight; - } else { - return ErrorComparisonPreferRight; - } - } - - if (a.cost < b.cost) { - if ((b.cost - a.cost) * (1 + a.node_count) > MAX_COST_DIFFERENCE) { - return ErrorComparisonTakeLeft; - } else { - return ErrorComparisonPreferLeft; - } - } - - if (b.cost < a.cost) { - if ((a.cost - b.cost) * (1 + b.node_count) > MAX_COST_DIFFERENCE) { - return ErrorComparisonTakeRight; - } else { - return ErrorComparisonPreferRight; - } - } - - if (a.dynamic_precedence > b.dynamic_precedence) return ErrorComparisonPreferLeft; - if (b.dynamic_precedence > a.dynamic_precedence) return ErrorComparisonPreferRight; - return ErrorComparisonNone; -} - -static ErrorStatus ts_parser__version_status( - TSParser *self, - StackVersion version -) { - unsigned cost = ts_stack_error_cost(self->stack, version); - bool is_paused = ts_stack_is_paused(self->stack, version); - if (is_paused) cost += ERROR_COST_PER_SKIPPED_TREE; - return (ErrorStatus) { - .cost = cost, - .node_count = ts_stack_node_count_since_error(self->stack, version), - .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version), - .is_in_error = is_paused || ts_stack_state(self->stack, version) == ERROR_STATE - }; -} - -static bool ts_parser__better_version_exists( - TSParser *self, - StackVersion version, - bool is_in_error, - unsigned cost -) { - if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) <= cost) { - return true; - } - - Length position = ts_stack_position(self->stack, version); - ErrorStatus status = { - .cost = cost, - .is_in_error = is_in_error, - .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version), - .node_count = ts_stack_node_count_since_error(self->stack, version), - }; - - for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) { - if (i == version || - !ts_stack_is_active(self->stack, i) || - ts_stack_position(self->stack, i).bytes < position.bytes) continue; - ErrorStatus status_i = ts_parser__version_status(self, i); - switch (ts_parser__compare_versions(self, status, status_i)) { - case ErrorComparisonTakeRight: - return true; - case ErrorComparisonPreferRight: - if (ts_stack_can_merge(self->stack, i, version)) return true; - default: - break; - } - } - - return false; -} - -static void ts_parser__restore_external_scanner( - TSParser *self, - Subtree external_token -) { - if (external_token.ptr) { - self->language->external_scanner.deserialize( - self->external_scanner_payload, - ts_external_scanner_state_data(&external_token.ptr->external_scanner_state), - external_token.ptr->external_scanner_state.length - ); - } else { - self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0); - } -} - -static bool ts_parser__can_reuse_first_leaf( - TSParser *self, - TSStateId state, - Subtree tree, - TableEntry *table_entry -) { - TSLexMode current_lex_mode = self->language->lex_modes[state]; - TSSymbol leaf_symbol = ts_subtree_leaf_symbol(tree); - TSStateId leaf_state = ts_subtree_leaf_parse_state(tree); - TSLexMode leaf_lex_mode = self->language->lex_modes[leaf_state]; - - // At the end of a non-terminal extra node, the lexer normally returns - // NULL, which indicates that the parser should look for a reduce action - // at symbol `0`. Avoid reusing tokens in this situation to ensure that - // the same thing happens when incrementally reparsing. - if (current_lex_mode.lex_state == (uint16_t)(-1)) return false; - - // If the token was created in a state with the same set of lookaheads, it is reusable. - if ( - table_entry->action_count > 0 && - memcmp(&leaf_lex_mode, ¤t_lex_mode, sizeof(TSLexMode)) == 0 && - ( - leaf_symbol != self->language->keyword_capture_token || - (!ts_subtree_is_keyword(tree) && ts_subtree_parse_state(tree) == state) - ) - ) return true; - - // Empty tokens are not reusable in states with different lookaheads. - if (ts_subtree_size(tree).bytes == 0 && leaf_symbol != ts_builtin_sym_end) return false; - - // If the current state allows external tokens or other tokens that conflict with this - // token, this token is not reusable. - return current_lex_mode.external_lex_state == 0 && table_entry->is_reusable; -} - -static Subtree ts_parser__lex( - TSParser *self, - StackVersion version, - TSStateId parse_state -) { - Length start_position = ts_stack_position(self->stack, version); - Subtree external_token = ts_stack_last_external_token(self->stack, version); - TSLexMode lex_mode = self->language->lex_modes[parse_state]; - if (lex_mode.lex_state == (uint16_t)-1) return NULL_SUBTREE; - const bool *valid_external_tokens = ts_language_enabled_external_tokens( - self->language, - lex_mode.external_lex_state - ); - - bool found_external_token = false; - bool error_mode = parse_state == ERROR_STATE; - bool skipped_error = false; - int32_t first_error_character = 0; - Length error_start_position = length_zero(); - Length error_end_position = length_zero(); - uint32_t lookahead_end_byte = 0; - ts_lexer_reset(&self->lexer, start_position); - - for (;;) { - Length current_position = self->lexer.current_position; - - if (valid_external_tokens) { - LOG( - "lex_external state:%d, row:%u, column:%u", - lex_mode.external_lex_state, - current_position.extent.row + 1, - current_position.extent.column - ); - ts_lexer_start(&self->lexer); - ts_parser__restore_external_scanner(self, external_token); - bool found_token = self->language->external_scanner.scan( - self->external_scanner_payload, - &self->lexer.data, - valid_external_tokens - ); - ts_lexer_finish(&self->lexer, &lookahead_end_byte); - - // Zero-length external tokens are generally allowed, but they're not - // allowed right after a syntax error. This is for two reasons: - // 1. After a syntax error, the lexer is looking for any possible token, - // as opposed to the specific set of tokens that are valid in some - // parse state. In this situation, it's very easy for an external - // scanner to produce unwanted zero-length tokens. - // 2. The parser sometimes inserts *missing* tokens to recover from - // errors. These tokens are also zero-length. If we allow more - // zero-length tokens to be created after missing tokens, it - // can lead to infinite loops. Forbidding zero-length tokens - // right at the point of error recovery is a conservative strategy - // for preventing this kind of infinite loop. - if (found_token && ( - self->lexer.token_end_position.bytes > current_position.bytes || - (!error_mode && ts_stack_has_advanced_since_error(self->stack, version)) - )) { - found_external_token = true; - break; - } - - ts_lexer_reset(&self->lexer, current_position); - } - - LOG( - "lex_internal state:%d, row:%u, column:%u", - lex_mode.lex_state, - current_position.extent.row + 1, - current_position.extent.column - ); - ts_lexer_start(&self->lexer); - bool found_token = self->language->lex_fn(&self->lexer.data, lex_mode.lex_state); - ts_lexer_finish(&self->lexer, &lookahead_end_byte); - if (found_token) break; - - if (!error_mode) { - error_mode = true; - lex_mode = self->language->lex_modes[ERROR_STATE]; - valid_external_tokens = ts_language_enabled_external_tokens( - self->language, - lex_mode.external_lex_state - ); - ts_lexer_reset(&self->lexer, start_position); - continue; - } - - if (!skipped_error) { - LOG("skip_unrecognized_character"); - skipped_error = true; - error_start_position = self->lexer.token_start_position; - error_end_position = self->lexer.token_start_position; - first_error_character = self->lexer.data.lookahead; - } - - if (self->lexer.current_position.bytes == error_end_position.bytes) { - if (self->lexer.data.eof(&self->lexer.data)) { - self->lexer.data.result_symbol = ts_builtin_sym_error; - break; - } - self->lexer.data.advance(&self->lexer.data, false); - } - - error_end_position = self->lexer.current_position; - } - - Subtree result; - if (skipped_error) { - Length padding = length_sub(error_start_position, start_position); - Length size = length_sub(error_end_position, error_start_position); - uint32_t lookahead_bytes = lookahead_end_byte - error_end_position.bytes; - result = ts_subtree_new_error( - &self->tree_pool, - first_error_character, - padding, - size, - lookahead_bytes, - parse_state, - self->language - ); - - LOG( - "lexed_lookahead sym:%s, size:%u, character:'%c'", - SYM_NAME(ts_subtree_symbol(result)), - ts_subtree_total_size(result).bytes, - first_error_character - ); - } else { - if (self->lexer.token_end_position.bytes < self->lexer.token_start_position.bytes) { - self->lexer.token_start_position = self->lexer.token_end_position; - } - - bool is_keyword = false; - TSSymbol symbol = self->lexer.data.result_symbol; - Length padding = length_sub(self->lexer.token_start_position, start_position); - Length size = length_sub(self->lexer.token_end_position, self->lexer.token_start_position); - uint32_t lookahead_bytes = lookahead_end_byte - self->lexer.token_end_position.bytes; - - if (found_external_token) { - symbol = self->language->external_scanner.symbol_map[symbol]; - } else if (symbol == self->language->keyword_capture_token && symbol != 0) { - uint32_t end_byte = self->lexer.token_end_position.bytes; - ts_lexer_reset(&self->lexer, self->lexer.token_start_position); - ts_lexer_start(&self->lexer); - if ( - self->language->keyword_lex_fn(&self->lexer.data, 0) && - self->lexer.token_end_position.bytes == end_byte && - ts_language_has_actions(self->language, parse_state, self->lexer.data.result_symbol) - ) { - is_keyword = true; - symbol = self->lexer.data.result_symbol; - } - } - - result = ts_subtree_new_leaf( - &self->tree_pool, - symbol, - padding, - size, - lookahead_bytes, - parse_state, - found_external_token, - is_keyword, - self->language - ); - - if (found_external_token) { - unsigned length = self->language->external_scanner.serialize( - self->external_scanner_payload, - self->lexer.debug_buffer - ); - ts_external_scanner_state_init( - &((SubtreeHeapData *)result.ptr)->external_scanner_state, - self->lexer.debug_buffer, - length - ); - } - - LOG( - "lexed_lookahead sym:%s, size:%u", - SYM_NAME(ts_subtree_symbol(result)), - ts_subtree_total_size(result).bytes - ); - } - - return result; -} - -static Subtree ts_parser__get_cached_token( - TSParser *self, - TSStateId state, - size_t position, - Subtree last_external_token, - TableEntry *table_entry -) { - TokenCache *cache = &self->token_cache; - if ( - cache->token.ptr && cache->byte_index == position && - ts_subtree_external_scanner_state_eq(cache->last_external_token, last_external_token) - ) { - ts_language_table_entry(self->language, state, ts_subtree_symbol(cache->token), table_entry); - if (ts_parser__can_reuse_first_leaf(self, state, cache->token, table_entry)) { - ts_subtree_retain(cache->token); - return cache->token; - } - } - return NULL_SUBTREE; -} - -static void ts_parser__set_cached_token( - TSParser *self, - size_t byte_index, - Subtree last_external_token, - Subtree token -) { - TokenCache *cache = &self->token_cache; - if (token.ptr) ts_subtree_retain(token); - if (last_external_token.ptr) ts_subtree_retain(last_external_token); - if (cache->token.ptr) ts_subtree_release(&self->tree_pool, cache->token); - if (cache->last_external_token.ptr) ts_subtree_release(&self->tree_pool, cache->last_external_token); - cache->token = token; - cache->byte_index = byte_index; - cache->last_external_token = last_external_token; -} - -static bool ts_parser__has_included_range_difference( - const TSParser *self, - uint32_t start_position, - uint32_t end_position -) { - return ts_range_array_intersects( - &self->included_range_differences, - self->included_range_difference_index, - start_position, - end_position - ); -} - -static Subtree ts_parser__reuse_node( - TSParser *self, - StackVersion version, - TSStateId *state, - uint32_t position, - Subtree last_external_token, - TableEntry *table_entry -) { - Subtree result; - while ((result = reusable_node_tree(&self->reusable_node)).ptr) { - uint32_t byte_offset = reusable_node_byte_offset(&self->reusable_node); - uint32_t end_byte_offset = byte_offset + ts_subtree_total_bytes(result); - - // Do not reuse an EOF node if the included ranges array has changes - // later on in the file. - if (ts_subtree_is_eof(result)) end_byte_offset = UINT32_MAX; - - if (byte_offset > position) { - LOG("before_reusable_node symbol:%s", TREE_NAME(result)); - break; - } - - if (byte_offset < position) { - LOG("past_reusable_node symbol:%s", TREE_NAME(result)); - if (end_byte_offset <= position || !reusable_node_descend(&self->reusable_node)) { - reusable_node_advance(&self->reusable_node); - } - continue; - } - - if (!ts_subtree_external_scanner_state_eq(self->reusable_node.last_external_token, last_external_token)) { - LOG("reusable_node_has_different_external_scanner_state symbol:%s", TREE_NAME(result)); - reusable_node_advance(&self->reusable_node); - continue; - } - - const char *reason = NULL; - if (ts_subtree_has_changes(result)) { - reason = "has_changes"; - } else if (ts_subtree_is_error(result)) { - reason = "is_error"; - } else if (ts_subtree_missing(result)) { - reason = "is_missing"; - } else if (ts_subtree_is_fragile(result)) { - reason = "is_fragile"; - } else if (ts_parser__has_included_range_difference(self, byte_offset, end_byte_offset)) { - reason = "contains_different_included_range"; - } - - if (reason) { - LOG("cant_reuse_node_%s tree:%s", reason, TREE_NAME(result)); - if (!reusable_node_descend(&self->reusable_node)) { - reusable_node_advance(&self->reusable_node); - ts_parser__breakdown_top_of_stack(self, version); - *state = ts_stack_state(self->stack, version); - } - continue; - } - - TSSymbol leaf_symbol = ts_subtree_leaf_symbol(result); - ts_language_table_entry(self->language, *state, leaf_symbol, table_entry); - if (!ts_parser__can_reuse_first_leaf(self, *state, result, table_entry)) { - LOG( - "cant_reuse_node symbol:%s, first_leaf_symbol:%s", - TREE_NAME(result), - SYM_NAME(leaf_symbol) - ); - reusable_node_advance_past_leaf(&self->reusable_node); - break; - } - - LOG("reuse_node symbol:%s", TREE_NAME(result)); - ts_subtree_retain(result); - return result; - } - - return NULL_SUBTREE; -} - -static bool ts_parser__select_tree(TSParser *self, Subtree left, Subtree right) { - if (!left.ptr) return true; - if (!right.ptr) return false; - - if (ts_subtree_error_cost(right) < ts_subtree_error_cost(left)) { - LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left)); - return true; - } - - if (ts_subtree_error_cost(left) < ts_subtree_error_cost(right)) { - LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right)); - return false; - } - - if (ts_subtree_dynamic_precedence(right) > ts_subtree_dynamic_precedence(left)) { - LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u", - TREE_NAME(right), ts_subtree_dynamic_precedence(right), TREE_NAME(left), - ts_subtree_dynamic_precedence(left)); - return true; - } - - if (ts_subtree_dynamic_precedence(left) > ts_subtree_dynamic_precedence(right)) { - LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u", - TREE_NAME(left), ts_subtree_dynamic_precedence(left), TREE_NAME(right), - ts_subtree_dynamic_precedence(right)); - return false; - } - - if (ts_subtree_error_cost(left) > 0) return true; - - int comparison = ts_subtree_compare(left, right); - switch (comparison) { - case -1: - LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right)); - return false; - break; - case 1: - LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left)); - return true; - default: - LOG("select_existing symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right)); - return false; - } -} - -static void ts_parser__shift( - TSParser *self, - StackVersion version, - TSStateId state, - Subtree lookahead, - bool extra -) { - Subtree subtree_to_push; - if (extra != ts_subtree_extra(lookahead)) { - MutableSubtree result = ts_subtree_make_mut(&self->tree_pool, lookahead); - ts_subtree_set_extra(&result); - subtree_to_push = ts_subtree_from_mut(result); - } else { - subtree_to_push = lookahead; - } - - bool is_pending = ts_subtree_child_count(subtree_to_push) > 0; - ts_stack_push(self->stack, version, subtree_to_push, is_pending, state); - if (ts_subtree_has_external_tokens(subtree_to_push)) { - ts_stack_set_last_external_token( - self->stack, version, ts_subtree_last_external_token(subtree_to_push) - ); - } -} - -static bool ts_parser__replace_children( - TSParser *self, - MutableSubtree *tree, - SubtreeArray *children -) { - *self->scratch_tree.ptr = *tree->ptr; - self->scratch_tree.ptr->child_count = 0; - ts_subtree_set_children(self->scratch_tree, children->contents, children->size, self->language); - if (ts_parser__select_tree(self, ts_subtree_from_mut(*tree), ts_subtree_from_mut(self->scratch_tree))) { - *tree->ptr = *self->scratch_tree.ptr; - return true; - } else { - return false; - } -} - -static StackVersion ts_parser__reduce( - TSParser *self, - StackVersion version, - TSSymbol symbol, - uint32_t count, - int dynamic_precedence, - uint16_t production_id, - bool is_fragile, - bool is_extra -) { - uint32_t initial_version_count = ts_stack_version_count(self->stack); - uint32_t removed_version_count = 0; - StackSliceArray pop = ts_stack_pop_count(self->stack, version, count); - - for (uint32_t i = 0; i < pop.size; i++) { - StackSlice slice = pop.contents[i]; - StackVersion slice_version = slice.version - removed_version_count; - - // Error recovery can sometimes cause lots of stack versions to merge, - // such that a single pop operation can produce a lots of slices. - // Avoid creating too many stack versions in that situation. - if (i > 0 && slice_version > MAX_VERSION_COUNT + MAX_VERSION_COUNT_OVERFLOW) { - ts_stack_remove_version(self->stack, slice_version); - ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); - removed_version_count++; - while (i + 1 < pop.size) { - StackSlice next_slice = pop.contents[i + 1]; - if (next_slice.version != slice.version) break; - ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees); - i++; - } - continue; - } - - // Extra tokens on top of the stack should not be included in this new parent - // node. They will be re-pushed onto the stack after the parent node is - // created and pushed. - SubtreeArray children = slice.subtrees; - while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) { - children.size--; - } - - MutableSubtree parent = ts_subtree_new_node(&self->tree_pool, - symbol, &children, production_id, self->language - ); - - // This pop operation may have caused multiple stack versions to collapse - // into one, because they all diverged from a common state. In that case, - // choose one of the arrays of trees to be the parent node's children, and - // delete the rest of the tree arrays. - while (i + 1 < pop.size) { - StackSlice next_slice = pop.contents[i + 1]; - if (next_slice.version != slice.version) break; - i++; - - SubtreeArray children = next_slice.subtrees; - while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) { - children.size--; - } - - if (ts_parser__replace_children(self, &parent, &children)) { - ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); - slice = next_slice; - } else { - ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees); - } - } - - parent.ptr->dynamic_precedence += dynamic_precedence; - parent.ptr->production_id = production_id; - - TSStateId state = ts_stack_state(self->stack, slice_version); - TSStateId next_state = ts_language_next_state(self->language, state, symbol); - if (is_extra) parent.ptr->extra = true; - if (is_fragile || pop.size > 1 || initial_version_count > 1) { - parent.ptr->fragile_left = true; - parent.ptr->fragile_right = true; - parent.ptr->parse_state = TS_TREE_STATE_NONE; - } else { - parent.ptr->parse_state = state; - } - - // Push the parent node onto the stack, along with any extra tokens that - // were previously on top of the stack. - ts_stack_push(self->stack, slice_version, ts_subtree_from_mut(parent), false, next_state); - for (uint32_t j = parent.ptr->child_count; j < slice.subtrees.size; j++) { - ts_stack_push(self->stack, slice_version, slice.subtrees.contents[j], false, next_state); - } - - for (StackVersion j = 0; j < slice_version; j++) { - if (j == version) continue; - if (ts_stack_merge(self->stack, j, slice_version)) { - removed_version_count++; - break; - } - } - } - - // Return the first new stack version that was created. - return ts_stack_version_count(self->stack) > initial_version_count - ? initial_version_count - : STACK_VERSION_NONE; -} - -static void ts_parser__accept( - TSParser *self, - StackVersion version, - Subtree lookahead -) { - assert(ts_subtree_is_eof(lookahead)); - ts_stack_push(self->stack, version, lookahead, false, 1); - - StackSliceArray pop = ts_stack_pop_all(self->stack, version); - for (uint32_t i = 0; i < pop.size; i++) { - SubtreeArray trees = pop.contents[i].subtrees; - - Subtree root = NULL_SUBTREE; - for (uint32_t j = trees.size - 1; j + 1 > 0; j--) { - Subtree child = trees.contents[j]; - if (!ts_subtree_extra(child)) { - assert(!child.data.is_inline); - uint32_t child_count = ts_subtree_child_count(child); - for (uint32_t k = 0; k < child_count; k++) { - ts_subtree_retain(child.ptr->children[k]); - } - array_splice(&trees, j, 1, child_count, child.ptr->children); - root = ts_subtree_from_mut(ts_subtree_new_node( - &self->tree_pool, - ts_subtree_symbol(child), - &trees, - child.ptr->production_id, - self->language - )); - ts_subtree_release(&self->tree_pool, child); - break; - } - } - - assert(root.ptr); - self->accept_count++; - - if (self->finished_tree.ptr) { - if (ts_parser__select_tree(self, self->finished_tree, root)) { - ts_subtree_release(&self->tree_pool, self->finished_tree); - self->finished_tree = root; - } else { - ts_subtree_release(&self->tree_pool, root); - } - } else { - self->finished_tree = root; - } - } - - ts_stack_remove_version(self->stack, pop.contents[0].version); - ts_stack_halt(self->stack, version); -} - -static bool ts_parser__do_all_potential_reductions( - TSParser *self, - StackVersion starting_version, - TSSymbol lookahead_symbol -) { - uint32_t initial_version_count = ts_stack_version_count(self->stack); - - bool can_shift_lookahead_symbol = false; - StackVersion version = starting_version; - for (unsigned i = 0; true; i++) { - uint32_t version_count = ts_stack_version_count(self->stack); - if (version >= version_count) break; - - bool merged = false; - for (StackVersion i = initial_version_count; i < version; i++) { - if (ts_stack_merge(self->stack, i, version)) { - merged = true; - break; - } - } - if (merged) continue; - - TSStateId state = ts_stack_state(self->stack, version); - bool has_shift_action = false; - array_clear(&self->reduce_actions); - - TSSymbol first_symbol, end_symbol; - if (lookahead_symbol != 0) { - first_symbol = lookahead_symbol; - end_symbol = lookahead_symbol + 1; - } else { - first_symbol = 1; - end_symbol = self->language->token_count; - } - - for (TSSymbol symbol = first_symbol; symbol < end_symbol; symbol++) { - TableEntry entry; - ts_language_table_entry(self->language, state, symbol, &entry); - for (uint32_t i = 0; i < entry.action_count; i++) { - TSParseAction action = entry.actions[i]; - switch (action.type) { - case TSParseActionTypeShift: - case TSParseActionTypeRecover: - if (!action.params.shift.extra && !action.params.shift.repetition) has_shift_action = true; - break; - case TSParseActionTypeReduce: - if (action.params.reduce.child_count > 0) - ts_reduce_action_set_add(&self->reduce_actions, (ReduceAction){ - .symbol = action.params.reduce.symbol, - .count = action.params.reduce.child_count, - .dynamic_precedence = action.params.reduce.dynamic_precedence, - .production_id = action.params.reduce.production_id, - }); - default: - break; - } - } - } - - StackVersion reduction_version = STACK_VERSION_NONE; - for (uint32_t i = 0; i < self->reduce_actions.size; i++) { - ReduceAction action = self->reduce_actions.contents[i]; - - reduction_version = ts_parser__reduce( - self, version, action.symbol, action.count, - action.dynamic_precedence, action.production_id, - true, false - ); - } - - if (has_shift_action) { - can_shift_lookahead_symbol = true; - } else if (reduction_version != STACK_VERSION_NONE && i < MAX_VERSION_COUNT) { - ts_stack_renumber_version(self->stack, reduction_version, version); - continue; - } else if (lookahead_symbol != 0) { - ts_stack_remove_version(self->stack, version); - } - - if (version == starting_version) { - version = version_count; - } else { - version++; - } - } - - return can_shift_lookahead_symbol; -} - -static void ts_parser__handle_error( - TSParser *self, - StackVersion version, - TSSymbol lookahead_symbol -) { - uint32_t previous_version_count = ts_stack_version_count(self->stack); - - // Perform any reductions that can happen in this state, regardless of the lookahead. After - // skipping one or more invalid tokens, the parser might find a token that would have allowed - // a reduction to take place. - ts_parser__do_all_potential_reductions(self, version, 0); - uint32_t version_count = ts_stack_version_count(self->stack); - Length position = ts_stack_position(self->stack, version); - - // Push a discontinuity onto the stack. Merge all of the stack versions that - // were created in the previous step. - bool did_insert_missing_token = false; - for (StackVersion v = version; v < version_count;) { - if (!did_insert_missing_token) { - TSStateId state = ts_stack_state(self->stack, v); - for (TSSymbol missing_symbol = 1; - missing_symbol < self->language->token_count; - missing_symbol++) { - TSStateId state_after_missing_symbol = ts_language_next_state( - self->language, state, missing_symbol - ); - if (state_after_missing_symbol == 0 || state_after_missing_symbol == state) { - continue; - } - - if (ts_language_has_reduce_action( - self->language, - state_after_missing_symbol, - lookahead_symbol - )) { - // In case the parser is currently outside of any included range, the lexer will - // snap to the beginning of the next included range. The missing token's padding - // must be assigned to position it within the next included range. - ts_lexer_reset(&self->lexer, position); - ts_lexer_mark_end(&self->lexer); - Length padding = length_sub(self->lexer.token_end_position, position); - - StackVersion version_with_missing_tree = ts_stack_copy_version(self->stack, v); - Subtree missing_tree = ts_subtree_new_missing_leaf( - &self->tree_pool, missing_symbol, padding, self->language - ); - ts_stack_push( - self->stack, version_with_missing_tree, - missing_tree, false, - state_after_missing_symbol - ); - - if (ts_parser__do_all_potential_reductions( - self, version_with_missing_tree, - lookahead_symbol - )) { - LOG( - "recover_with_missing symbol:%s, state:%u", - SYM_NAME(missing_symbol), - ts_stack_state(self->stack, version_with_missing_tree) - ); - did_insert_missing_token = true; - break; - } - } - } - } - - ts_stack_push(self->stack, v, NULL_SUBTREE, false, ERROR_STATE); - v = (v == version) ? previous_version_count : v + 1; - } - - for (unsigned i = previous_version_count; i < version_count; i++) { - bool did_merge = ts_stack_merge(self->stack, version, previous_version_count); - assert(did_merge); - } - - ts_stack_record_summary(self->stack, version, MAX_SUMMARY_DEPTH); - LOG_STACK(); -} - -static bool ts_parser__recover_to_state( - TSParser *self, - StackVersion version, - unsigned depth, - TSStateId goal_state -) { - StackSliceArray pop = ts_stack_pop_count(self->stack, version, depth); - StackVersion previous_version = STACK_VERSION_NONE; - - for (unsigned i = 0; i < pop.size; i++) { - StackSlice slice = pop.contents[i]; - - if (slice.version == previous_version) { - ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); - array_erase(&pop, i--); - continue; - } - - if (ts_stack_state(self->stack, slice.version) != goal_state) { - ts_stack_halt(self->stack, slice.version); - ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); - array_erase(&pop, i--); - continue; - } - - SubtreeArray error_trees = ts_stack_pop_error(self->stack, slice.version); - if (error_trees.size > 0) { - assert(error_trees.size == 1); - Subtree error_tree = error_trees.contents[0]; - uint32_t error_child_count = ts_subtree_child_count(error_tree); - if (error_child_count > 0) { - array_splice(&slice.subtrees, 0, 0, error_child_count, error_tree.ptr->children); - for (unsigned j = 0; j < error_child_count; j++) { - ts_subtree_retain(slice.subtrees.contents[j]); - } - } - ts_subtree_array_delete(&self->tree_pool, &error_trees); - } - - SubtreeArray trailing_extras = ts_subtree_array_remove_trailing_extras(&slice.subtrees); - - if (slice.subtrees.size > 0) { - Subtree error = ts_subtree_new_error_node(&self->tree_pool, &slice.subtrees, true, self->language); - ts_stack_push(self->stack, slice.version, error, false, goal_state); - } else { - array_delete(&slice.subtrees); - } - - for (unsigned j = 0; j < trailing_extras.size; j++) { - Subtree tree = trailing_extras.contents[j]; - ts_stack_push(self->stack, slice.version, tree, false, goal_state); - } - - previous_version = slice.version; - array_delete(&trailing_extras); - } - - return previous_version != STACK_VERSION_NONE; -} - -static void ts_parser__recover( - TSParser *self, - StackVersion version, - Subtree lookahead -) { - bool did_recover = false; - unsigned previous_version_count = ts_stack_version_count(self->stack); - Length position = ts_stack_position(self->stack, version); - StackSummary *summary = ts_stack_get_summary(self->stack, version); - unsigned node_count_since_error = ts_stack_node_count_since_error(self->stack, version); - unsigned current_error_cost = ts_stack_error_cost(self->stack, version); - - // When the parser is in the error state, there are two strategies for recovering with a - // given lookahead token: - // 1. Find a previous state on the stack in which that lookahead token would be valid. Then, - // create a new stack version that is in that state again. This entails popping all of the - // subtrees that have been pushed onto the stack since that previous state, and wrapping - // them in an ERROR node. - // 2. Wrap the lookahead token in an ERROR node, push that ERROR node onto the stack, and - // move on to the next lookahead token, remaining in the error state. - // - // First, try the strategy 1. Upon entering the error state, the parser recorded a summary - // of the previous parse states and their depths. Look at each state in the summary, to see - // if the current lookahead token would be valid in that state. - if (summary && !ts_subtree_is_error(lookahead)) { - for (unsigned i = 0; i < summary->size; i++) { - StackSummaryEntry entry = summary->contents[i]; - - if (entry.state == ERROR_STATE) continue; - if (entry.position.bytes == position.bytes) continue; - unsigned depth = entry.depth; - if (node_count_since_error > 0) depth++; - - // Do not recover in ways that create redundant stack versions. - bool would_merge = false; - for (unsigned j = 0; j < previous_version_count; j++) { - if ( - ts_stack_state(self->stack, j) == entry.state && - ts_stack_position(self->stack, j).bytes == position.bytes - ) { - would_merge = true; - break; - } - } - if (would_merge) continue; - - // Do not recover if the result would clearly be worse than some existing stack version. - unsigned new_cost = - current_error_cost + - entry.depth * ERROR_COST_PER_SKIPPED_TREE + - (position.bytes - entry.position.bytes) * ERROR_COST_PER_SKIPPED_CHAR + - (position.extent.row - entry.position.extent.row) * ERROR_COST_PER_SKIPPED_LINE; - if (ts_parser__better_version_exists(self, version, false, new_cost)) break; - - // If the current lookahead token is valid in some previous state, recover to that state. - // Then stop looking for further recoveries. - if (ts_language_has_actions(self->language, entry.state, ts_subtree_symbol(lookahead))) { - if (ts_parser__recover_to_state(self, version, depth, entry.state)) { - did_recover = true; - LOG("recover_to_previous state:%u, depth:%u", entry.state, depth); - LOG_STACK(); - break; - } - } - } - } - - // In the process of attemping to recover, some stack versions may have been created - // and subsequently halted. Remove those versions. - for (unsigned i = previous_version_count; i < ts_stack_version_count(self->stack); i++) { - if (!ts_stack_is_active(self->stack, i)) { - ts_stack_remove_version(self->stack, i--); - } - } - - // If strategy 1 succeeded, a new stack version will have been created which is able to handle - // the current lookahead token. Now, in addition, try strategy 2 described above: skip the - // current lookahead token by wrapping it in an ERROR node. - - // Don't pursue this additional strategy if there are already too many stack versions. - if (did_recover && ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) { - ts_stack_halt(self->stack, version); - ts_subtree_release(&self->tree_pool, lookahead); - return; - } - - // If the parser is still in the error state at the end of the file, just wrap everything - // in an ERROR node and terminate. - if (ts_subtree_is_eof(lookahead)) { - LOG("recover_eof"); - SubtreeArray children = array_new(); - Subtree parent = ts_subtree_new_error_node(&self->tree_pool, &children, false, self->language); - ts_stack_push(self->stack, version, parent, false, 1); - ts_parser__accept(self, version, lookahead); - return; - } - - // Do not recover if the result would clearly be worse than some existing stack version. - unsigned new_cost = - current_error_cost + ERROR_COST_PER_SKIPPED_TREE + - ts_subtree_total_bytes(lookahead) * ERROR_COST_PER_SKIPPED_CHAR + - ts_subtree_total_size(lookahead).extent.row * ERROR_COST_PER_SKIPPED_LINE; - if (ts_parser__better_version_exists(self, version, false, new_cost)) { - ts_stack_halt(self->stack, version); - ts_subtree_release(&self->tree_pool, lookahead); - return; - } - - // If the current lookahead token is an extra token, mark it as extra. This means it won't - // be counted in error cost calculations. - unsigned n; - const TSParseAction *actions = ts_language_actions(self->language, 1, ts_subtree_symbol(lookahead), &n); - if (n > 0 && actions[n - 1].type == TSParseActionTypeShift && actions[n - 1].params.shift.extra) { - MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead); - ts_subtree_set_extra(&mutable_lookahead); - lookahead = ts_subtree_from_mut(mutable_lookahead); - } - - // Wrap the lookahead token in an ERROR. - LOG("skip_token symbol:%s", TREE_NAME(lookahead)); - SubtreeArray children = array_new(); - array_reserve(&children, 1); - array_push(&children, lookahead); - MutableSubtree error_repeat = ts_subtree_new_node( - &self->tree_pool, - ts_builtin_sym_error_repeat, - &children, - 0, - self->language - ); - - // If other tokens have already been skipped, so there is already an ERROR at the top of the - // stack, then pop that ERROR off the stack and wrap the two ERRORs together into one larger - // ERROR. - if (node_count_since_error > 0) { - StackSliceArray pop = ts_stack_pop_count(self->stack, version, 1); - - // TODO: Figure out how to make this condition occur. - // See https://github.com/atom/atom/issues/18450#issuecomment-439579778 - // If multiple stack versions have merged at this point, just pick one of the errors - // arbitrarily and discard the rest. - if (pop.size > 1) { - for (unsigned i = 1; i < pop.size; i++) { - ts_subtree_array_delete(&self->tree_pool, &pop.contents[i].subtrees); - } - while (ts_stack_version_count(self->stack) > pop.contents[0].version + 1) { - ts_stack_remove_version(self->stack, pop.contents[0].version + 1); - } - } - - ts_stack_renumber_version(self->stack, pop.contents[0].version, version); - array_push(&pop.contents[0].subtrees, ts_subtree_from_mut(error_repeat)); - error_repeat = ts_subtree_new_node( - &self->tree_pool, - ts_builtin_sym_error_repeat, - &pop.contents[0].subtrees, - 0, - self->language - ); - } - - // Push the new ERROR onto the stack. - ts_stack_push(self->stack, version, ts_subtree_from_mut(error_repeat), false, ERROR_STATE); - if (ts_subtree_has_external_tokens(lookahead)) { - ts_stack_set_last_external_token( - self->stack, version, ts_subtree_last_external_token(lookahead) - ); - } -} - -static bool ts_parser__advance( - TSParser *self, - StackVersion version, - bool allow_node_reuse -) { - TSStateId state = ts_stack_state(self->stack, version); - uint32_t position = ts_stack_position(self->stack, version).bytes; - Subtree last_external_token = ts_stack_last_external_token(self->stack, version); - - bool did_reuse = true; - Subtree lookahead = NULL_SUBTREE; - TableEntry table_entry = {.action_count = 0}; - - // If possible, reuse a node from the previous syntax tree. - if (allow_node_reuse) { - lookahead = ts_parser__reuse_node( - self, version, &state, position, last_external_token, &table_entry - ); - } - - // If no node from the previous syntax tree could be reused, then try to - // reuse the token previously returned by the lexer. - if (!lookahead.ptr) { - did_reuse = false; - lookahead = ts_parser__get_cached_token( - self, state, position, last_external_token, &table_entry - ); - } - - // Otherwise, re-run the lexer. - if (!lookahead.ptr) { - lookahead = ts_parser__lex(self, version, state); - if (lookahead.ptr) { - ts_parser__set_cached_token(self, position, last_external_token, lookahead); - ts_language_table_entry(self->language, state, ts_subtree_symbol(lookahead), &table_entry); - } - - // When parsing a non-terminal extra, a null lookahead indicates the - // end of the rule. The reduction is stored in the EOF table entry. - // After the reduction, the lexer needs to be run again. - else { - ts_language_table_entry(self->language, state, ts_builtin_sym_end, &table_entry); - } - } - - for (;;) { - // If a cancellation flag or a timeout was provided, then check every - // time a fixed number of parse actions has been processed. - if (++self->operation_count == OP_COUNT_PER_TIMEOUT_CHECK) { - self->operation_count = 0; - } - if ( - self->operation_count == 0 && - ((self->cancellation_flag && atomic_load(self->cancellation_flag)) || - (!clock_is_null(self->end_clock) && clock_is_gt(clock_now(), self->end_clock))) - ) { - ts_subtree_release(&self->tree_pool, lookahead); - return false; - } - - // Process each parse action for the current lookahead token in - // the current state. If there are multiple actions, then this is - // an ambiguous state. REDUCE actions always create a new stack - // version, whereas SHIFT actions update the existing stack version - // and terminate this loop. - StackVersion last_reduction_version = STACK_VERSION_NONE; - for (uint32_t i = 0; i < table_entry.action_count; i++) { - TSParseAction action = table_entry.actions[i]; - - switch (action.type) { - case TSParseActionTypeShift: { - if (action.params.shift.repetition) break; - TSStateId next_state; - if (action.params.shift.extra) { - - // TODO: remove when TREE_SITTER_LANGUAGE_VERSION 9 is out. - if (state == ERROR_STATE) continue; - - next_state = state; - LOG("shift_extra"); - } else { - next_state = action.params.shift.state; - LOG("shift state:%u", next_state); - } - - if (ts_subtree_child_count(lookahead) > 0) { - ts_parser__breakdown_lookahead(self, &lookahead, state, &self->reusable_node); - next_state = ts_language_next_state(self->language, state, ts_subtree_symbol(lookahead)); - } - - ts_parser__shift(self, version, next_state, lookahead, action.params.shift.extra); - if (did_reuse) reusable_node_advance(&self->reusable_node); - return true; - } - - case TSParseActionTypeReduce: { - bool is_fragile = table_entry.action_count > 1; - bool is_extra = lookahead.ptr == NULL; - LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.reduce.symbol), action.params.reduce.child_count); - StackVersion reduction_version = ts_parser__reduce( - self, version, action.params.reduce.symbol, action.params.reduce.child_count, - action.params.reduce.dynamic_precedence, action.params.reduce.production_id, - is_fragile, is_extra - ); - if (reduction_version != STACK_VERSION_NONE) { - last_reduction_version = reduction_version; - } - break; - } - - case TSParseActionTypeAccept: { - LOG("accept"); - ts_parser__accept(self, version, lookahead); - return true; - } - - case TSParseActionTypeRecover: { - if (ts_subtree_child_count(lookahead) > 0) { - ts_parser__breakdown_lookahead(self, &lookahead, ERROR_STATE, &self->reusable_node); - } - - ts_parser__recover(self, version, lookahead); - if (did_reuse) reusable_node_advance(&self->reusable_node); - return true; - } - } - } - - // If a reduction was performed, then replace the current stack version - // with one of the stack versions created by a reduction, and continue - // processing this version of the stack with the same lookahead symbol. - if (last_reduction_version != STACK_VERSION_NONE) { - ts_stack_renumber_version(self->stack, last_reduction_version, version); - LOG_STACK(); - state = ts_stack_state(self->stack, version); - - // At the end of a non-terminal extra rule, the lexer will return a - // null subtree, because the parser needs to perform a fixed reduction - // regardless of the lookahead node. After performing that reduction, - // (and completing the non-terminal extra rule) run the lexer again based - // on the current parse state. - if (!lookahead.ptr) { - lookahead = ts_parser__lex(self, version, state); - } - ts_language_table_entry( - self->language, - state, - ts_subtree_leaf_symbol(lookahead), - &table_entry - ); - continue; - } - - // If there were no parse actions for the current lookahead token, then - // it is not valid in this state. If the current lookahead token is a - // keyword, then switch to treating it as the normal word token if that - // token is valid in this state. - if ( - ts_subtree_is_keyword(lookahead) && - ts_subtree_symbol(lookahead) != self->language->keyword_capture_token - ) { - ts_language_table_entry(self->language, state, self->language->keyword_capture_token, &table_entry); - if (table_entry.action_count > 0) { - LOG( - "switch from_keyword:%s, to_word_token:%s", - TREE_NAME(lookahead), - SYM_NAME(self->language->keyword_capture_token) - ); - - MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead); - ts_subtree_set_symbol(&mutable_lookahead, self->language->keyword_capture_token, self->language); - lookahead = ts_subtree_from_mut(mutable_lookahead); - continue; - } - } - - // If the current lookahead token is not valid and the parser is - // already in the error state, restart the error recovery process. - // TODO - can this be unified with the other `RECOVER` case above? - if (state == ERROR_STATE) { - ts_parser__recover(self, version, lookahead); - return true; - } - - // If the current lookahead token is not valid and the previous - // subtree on the stack was reused from an old tree, it isn't actually - // valid to reuse it. Remove it from the stack, and in its place, - // push each of its children. Then try again to process the current - // lookahead. - if (ts_parser__breakdown_top_of_stack(self, version)) { - continue; - } - - // At this point, the current lookahead token is definitely not valid - // for this parse stack version. Mark this version as paused and continue - // processing any other stack versions that might exist. If some other - // version advances successfully, then this version can simply be removed. - // But if all versions end up paused, then error recovery is needed. - LOG("detect_error"); - ts_stack_pause(self->stack, version, ts_subtree_leaf_symbol(lookahead)); - ts_subtree_release(&self->tree_pool, lookahead); - return true; - } -} - -static unsigned ts_parser__condense_stack(TSParser *self) { - bool made_changes = false; - unsigned min_error_cost = UINT_MAX; - for (StackVersion i = 0; i < ts_stack_version_count(self->stack); i++) { - // Prune any versions that have been marked for removal. - if (ts_stack_is_halted(self->stack, i)) { - ts_stack_remove_version(self->stack, i); - i--; - continue; - } - - // Keep track of the minimum error cost of any stack version so - // that it can be returned. - ErrorStatus status_i = ts_parser__version_status(self, i); - if (!status_i.is_in_error && status_i.cost < min_error_cost) { - min_error_cost = status_i.cost; - } - - // Examine each pair of stack versions, removing any versions that - // are clearly worse than another version. Ensure that the versions - // are ordered from most promising to least promising. - for (StackVersion j = 0; j < i; j++) { - ErrorStatus status_j = ts_parser__version_status(self, j); - - switch (ts_parser__compare_versions(self, status_j, status_i)) { - case ErrorComparisonTakeLeft: - made_changes = true; - ts_stack_remove_version(self->stack, i); - i--; - j = i; - break; - - case ErrorComparisonPreferLeft: - case ErrorComparisonNone: - if (ts_stack_merge(self->stack, j, i)) { - made_changes = true; - i--; - j = i; - } - break; - - case ErrorComparisonPreferRight: - made_changes = true; - if (ts_stack_merge(self->stack, j, i)) { - i--; - j = i; - } else { - ts_stack_swap_versions(self->stack, i, j); - } - break; - - case ErrorComparisonTakeRight: - made_changes = true; - ts_stack_remove_version(self->stack, j); - i--; - j--; - break; - } - } - } - - // Enfore a hard upper bound on the number of stack versions by - // discarding the least promising versions. - while (ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) { - ts_stack_remove_version(self->stack, MAX_VERSION_COUNT); - made_changes = true; - } - - // If the best-performing stack version is currently paused, or all - // versions are paused, then resume the best paused version and begin - // the error recovery process. Otherwise, remove the paused versions. - if (ts_stack_version_count(self->stack) > 0) { - bool has_unpaused_version = false; - for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) { - if (ts_stack_is_paused(self->stack, i)) { - if (!has_unpaused_version && self->accept_count < MAX_VERSION_COUNT) { - LOG("resume version:%u", i); - min_error_cost = ts_stack_error_cost(self->stack, i); - TSSymbol lookahead_symbol = ts_stack_resume(self->stack, i); - ts_parser__handle_error(self, i, lookahead_symbol); - has_unpaused_version = true; - } else { - ts_stack_remove_version(self->stack, i); - i--; - n--; - } - } else { - has_unpaused_version = true; - } - } - } - - if (made_changes) { - LOG("condense"); - LOG_STACK(); - } - - return min_error_cost; -} - -static bool ts_parser_has_outstanding_parse(TSParser *self) { - return ( - ts_stack_state(self->stack, 0) != 1 || - ts_stack_node_count_since_error(self->stack, 0) != 0 - ); -} - -// Parser - Public - -TSParser *ts_parser_new(void) { - TSParser *self = ts_calloc(1, sizeof(TSParser)); - ts_lexer_init(&self->lexer); - array_init(&self->reduce_actions); - array_reserve(&self->reduce_actions, 4); - self->tree_pool = ts_subtree_pool_new(32); - self->stack = ts_stack_new(&self->tree_pool); - self->finished_tree = NULL_SUBTREE; - self->reusable_node = reusable_node_new(); - self->dot_graph_file = NULL; - self->cancellation_flag = NULL; - self->timeout_duration = 0; - self->end_clock = clock_null(); - self->operation_count = 0; - self->old_tree = NULL_SUBTREE; - self->scratch_tree.ptr = &self->scratch_tree_data; - self->included_range_differences = (TSRangeArray) array_new(); - self->included_range_difference_index = 0; - ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); - return self; -} - -void ts_parser_delete(TSParser *self) { - if (!self) return; - - ts_parser_set_language(self, NULL); - ts_stack_delete(self->stack); - if (self->reduce_actions.contents) { - array_delete(&self->reduce_actions); - } - if (self->included_range_differences.contents) { - array_delete(&self->included_range_differences); - } - if (self->old_tree.ptr) { - ts_subtree_release(&self->tree_pool, self->old_tree); - self->old_tree = NULL_SUBTREE; - } - ts_lexer_delete(&self->lexer); - ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); - ts_subtree_pool_delete(&self->tree_pool); - reusable_node_delete(&self->reusable_node); - ts_free(self); -} - -const TSLanguage *ts_parser_language(const TSParser *self) { - return self->language; -} - -bool ts_parser_set_language(TSParser *self, const TSLanguage *language) { - if (language) { - if (language->version > TREE_SITTER_LANGUAGE_VERSION) return false; - if (language->version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) return false; - } - - if (self->external_scanner_payload && self->language->external_scanner.destroy) { - self->language->external_scanner.destroy(self->external_scanner_payload); - } - - if (language && language->external_scanner.create) { - self->external_scanner_payload = language->external_scanner.create(); - } else { - self->external_scanner_payload = NULL; - } - - self->language = language; - ts_parser_reset(self); - return true; -} - -TSLogger ts_parser_logger(const TSParser *self) { - return self->lexer.logger; -} - -void ts_parser_set_logger(TSParser *self, TSLogger logger) { - self->lexer.logger = logger; -} - -void ts_parser_print_dot_graphs(TSParser *self, int fd) { - if (self->dot_graph_file) { - fclose(self->dot_graph_file); - } - - if (fd >= 0) { - self->dot_graph_file = fdopen(fd, "a"); - } else { - self->dot_graph_file = NULL; - } -} - -const size_t *ts_parser_cancellation_flag(const TSParser *self) { - return (const size_t *)self->cancellation_flag; -} - -void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag) { - self->cancellation_flag = (const volatile size_t *)flag; -} - -uint64_t ts_parser_timeout_micros(const TSParser *self) { - return duration_to_micros(self->timeout_duration); -} - -void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout_micros) { - self->timeout_duration = duration_from_micros(timeout_micros); -} - -bool ts_parser_set_included_ranges( - TSParser *self, - const TSRange *ranges, - uint32_t count -) { - return ts_lexer_set_included_ranges(&self->lexer, ranges, count); -} - -const TSRange *ts_parser_included_ranges(const TSParser *self, uint32_t *count) { - return ts_lexer_included_ranges(&self->lexer, count); -} - -void ts_parser_reset(TSParser *self) { - if (self->language && self->language->external_scanner.deserialize) { - self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0); - } - - if (self->old_tree.ptr) { - ts_subtree_release(&self->tree_pool, self->old_tree); - self->old_tree = NULL_SUBTREE; - } - - reusable_node_clear(&self->reusable_node); - ts_lexer_reset(&self->lexer, length_zero()); - ts_stack_clear(self->stack); - ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); - if (self->finished_tree.ptr) { - ts_subtree_release(&self->tree_pool, self->finished_tree); - self->finished_tree = NULL_SUBTREE; - } - self->accept_count = 0; -} - -TSTree *ts_parser_parse( - TSParser *self, - const TSTree *old_tree, - TSInput input -) { - if (!self->language || !input.read) return NULL; - - ts_lexer_set_input(&self->lexer, input); - - array_clear(&self->included_range_differences); - self->included_range_difference_index = 0; - - if (ts_parser_has_outstanding_parse(self)) { - LOG("resume_parsing"); - } else if (old_tree) { - ts_subtree_retain(old_tree->root); - self->old_tree = old_tree->root; - ts_range_array_get_changed_ranges( - old_tree->included_ranges, old_tree->included_range_count, - self->lexer.included_ranges, self->lexer.included_range_count, - &self->included_range_differences - ); - reusable_node_reset(&self->reusable_node, old_tree->root); - LOG("parse_after_edit"); - LOG_TREE(self->old_tree); - for (unsigned i = 0; i < self->included_range_differences.size; i++) { - TSRange *range = &self->included_range_differences.contents[i]; - LOG("different_included_range %u - %u", range->start_byte, range->end_byte); - } - } else { - reusable_node_clear(&self->reusable_node); - LOG("new_parse"); - } - - uint32_t position = 0, last_position = 0, version_count = 0; - self->operation_count = 0; - if (self->timeout_duration) { - self->end_clock = clock_after(clock_now(), self->timeout_duration); - } else { - self->end_clock = clock_null(); - } - - do { - for (StackVersion version = 0; - version_count = ts_stack_version_count(self->stack), version < version_count; - version++) { - bool allow_node_reuse = version_count == 1; - while (ts_stack_is_active(self->stack, version)) { - LOG("process version:%d, version_count:%u, state:%d, row:%u, col:%u", - version, ts_stack_version_count(self->stack), - ts_stack_state(self->stack, version), - ts_stack_position(self->stack, version).extent.row + 1, - ts_stack_position(self->stack, version).extent.column); - - if (!ts_parser__advance(self, version, allow_node_reuse)) return NULL; - LOG_STACK(); - - position = ts_stack_position(self->stack, version).bytes; - if (position > last_position || (version > 0 && position == last_position)) { - last_position = position; - break; - } - } - } - - unsigned min_error_cost = ts_parser__condense_stack(self); - if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) < min_error_cost) { - break; - } - - while (self->included_range_difference_index < self->included_range_differences.size) { - TSRange *range = &self->included_range_differences.contents[self->included_range_difference_index]; - if (range->end_byte <= position) { - self->included_range_difference_index++; - } else { - break; - } - } - } while (version_count != 0); - - ts_subtree_balance(self->finished_tree, &self->tree_pool, self->language); - LOG("done"); - LOG_TREE(self->finished_tree); - - TSTree *result = ts_tree_new( - self->finished_tree, - self->language, - self->lexer.included_ranges, - self->lexer.included_range_count - ); - self->finished_tree = NULL_SUBTREE; - ts_parser_reset(self); - return result; -} - -TSTree *ts_parser_parse_string( - TSParser *self, - const TSTree *old_tree, - const char *string, - uint32_t length -) { - return ts_parser_parse_string_encoding(self, old_tree, string, length, TSInputEncodingUTF8); -} - -TSTree *ts_parser_parse_string_encoding(TSParser *self, const TSTree *old_tree, - const char *string, uint32_t length, TSInputEncoding encoding) { - TSStringInput input = {string, length}; - return ts_parser_parse(self, old_tree, (TSInput) { - &input, - ts_string_input_read, - encoding, - }); -} - -#undef LOG diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h deleted file mode 100644 index 11bf4fc42a..0000000000 --- a/src/tree_sitter/parser.h +++ /dev/null @@ -1,235 +0,0 @@ -#ifndef TREE_SITTER_PARSER_H_ -#define TREE_SITTER_PARSER_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> - -#define ts_builtin_sym_error ((TSSymbol)-1) -#define ts_builtin_sym_end 0 -#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 - -#ifndef TREE_SITTER_API_H_ -typedef uint16_t TSSymbol; -typedef uint16_t TSFieldId; -typedef struct TSLanguage TSLanguage; -#endif - -typedef struct { - TSFieldId field_id; - uint8_t child_index; - bool inherited; -} TSFieldMapEntry; - -typedef struct { - uint16_t index; - uint16_t length; -} TSFieldMapSlice; - -typedef uint16_t TSStateId; - -typedef struct { - bool visible : 1; - bool named : 1; -} TSSymbolMetadata; - -typedef struct TSLexer TSLexer; - -struct TSLexer { - int32_t lookahead; - TSSymbol result_symbol; - void (*advance)(TSLexer *, bool); - void (*mark_end)(TSLexer *); - uint32_t (*get_column)(TSLexer *); - bool (*is_at_included_range_start)(const TSLexer *); - bool (*eof)(const TSLexer *); -}; - -typedef enum { - TSParseActionTypeShift, - TSParseActionTypeReduce, - TSParseActionTypeAccept, - TSParseActionTypeRecover, -} TSParseActionType; - -typedef struct { - union { - struct { - TSStateId state; - bool extra : 1; - bool repetition : 1; - } shift; - struct { - TSSymbol symbol; - int16_t dynamic_precedence; - uint8_t child_count; - uint8_t production_id; - } reduce; - } params; - TSParseActionType type : 4; -} TSParseAction; - -typedef struct { - uint16_t lex_state; - uint16_t external_lex_state; -} TSLexMode; - -typedef union { - TSParseAction action; - struct { - uint8_t count; - bool reusable : 1; - } entry; -} TSParseActionEntry; - -struct TSLanguage { - uint32_t version; - uint32_t symbol_count; - uint32_t alias_count; - uint32_t token_count; - uint32_t external_token_count; - const char **symbol_names; - const TSSymbolMetadata *symbol_metadata; - const uint16_t *parse_table; - const TSParseActionEntry *parse_actions; - const TSLexMode *lex_modes; - const TSSymbol *alias_sequences; - uint16_t max_alias_sequence_length; - bool (*lex_fn)(TSLexer *, TSStateId); - bool (*keyword_lex_fn)(TSLexer *, TSStateId); - TSSymbol keyword_capture_token; - struct { - const bool *states; - const TSSymbol *symbol_map; - void *(*create)(void); - void (*destroy)(void *); - bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); - unsigned (*serialize)(void *, char *); - void (*deserialize)(void *, const char *, unsigned); - } external_scanner; - uint32_t field_count; - const TSFieldMapSlice *field_map_slices; - const TSFieldMapEntry *field_map_entries; - const char **field_names; - uint32_t large_state_count; - const uint16_t *small_parse_table; - const uint32_t *small_parse_table_map; - const TSSymbol *public_symbol_map; -}; - -/* - * Lexer Macros - */ - -#define START_LEXER() \ - bool result = false; \ - bool skip = false; \ - bool eof = false; \ - int32_t lookahead; \ - goto start; \ - next_state: \ - lexer->advance(lexer, skip); \ - start: \ - skip = false; \ - lookahead = lexer->lookahead; - -#define ADVANCE(state_value) \ - { \ - state = state_value; \ - goto next_state; \ - } - -#define SKIP(state_value) \ - { \ - skip = true; \ - state = state_value; \ - goto next_state; \ - } - -#define ACCEPT_TOKEN(symbol_value) \ - result = true; \ - lexer->result_symbol = symbol_value; \ - lexer->mark_end(lexer); - -#define END_STATE() return result; - -/* - * Parse Table Macros - */ - -#define SMALL_STATE(id) id - LARGE_STATE_COUNT - -#define STATE(id) id - -#define ACTIONS(id) id - -#define SHIFT(state_value) \ - { \ - { \ - .params = { \ - .shift = { \ - .state = state_value \ - } \ - }, \ - .type = TSParseActionTypeShift \ - } \ - } - -#define SHIFT_REPEAT(state_value) \ - { \ - { \ - .params = { \ - .shift = { \ - .state = state_value, \ - .repetition = true \ - } \ - }, \ - .type = TSParseActionTypeShift \ - } \ - } - -#define RECOVER() \ - { \ - { .type = TSParseActionTypeRecover } \ - } - -#define SHIFT_EXTRA() \ - { \ - { \ - .params = { \ - .shift = { \ - .extra = true \ - } \ - }, \ - .type = TSParseActionTypeShift \ - } \ - } - -#define REDUCE(symbol_val, child_count_val, ...) \ - { \ - { \ - .params = { \ - .reduce = { \ - .symbol = symbol_val, \ - .child_count = child_count_val, \ - __VA_ARGS__ \ - }, \ - }, \ - .type = TSParseActionTypeReduce \ - } \ - } - -#define ACCEPT_INPUT() \ - { \ - { .type = TSParseActionTypeAccept } \ - } - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_PARSER_H_ diff --git a/src/tree_sitter/point.h b/src/tree_sitter/point.h deleted file mode 100644 index a50d20214b..0000000000 --- a/src/tree_sitter/point.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef TREE_SITTER_POINT_H_ -#define TREE_SITTER_POINT_H_ - -#include "tree_sitter/api.h" - -#define POINT_ZERO ((TSPoint) {0, 0}) -#define POINT_MAX ((TSPoint) {UINT32_MAX, UINT32_MAX}) - -static inline TSPoint point__new(unsigned row, unsigned column) { - TSPoint result = {row, column}; - return result; -} - -static inline TSPoint point_add(TSPoint a, TSPoint b) { - if (b.row > 0) - return point__new(a.row + b.row, b.column); - else - return point__new(a.row, a.column + b.column); -} - -static inline TSPoint point_sub(TSPoint a, TSPoint b) { - if (a.row > b.row) - return point__new(a.row - b.row, a.column); - else - return point__new(0, a.column - b.column); -} - -static inline bool point_lte(TSPoint a, TSPoint b) { - return (a.row < b.row) || (a.row == b.row && a.column <= b.column); -} - -static inline bool point_lt(TSPoint a, TSPoint b) { - return (a.row < b.row) || (a.row == b.row && a.column < b.column); -} - -static inline bool point_eq(TSPoint a, TSPoint b) { - return a.row == b.row && a.column == b.column; -} - -static inline TSPoint point_min(TSPoint a, TSPoint b) { - if (a.row < b.row || (a.row == b.row && a.column < b.column)) - return a; - else - return b; -} - -static inline TSPoint point_max(TSPoint a, TSPoint b) { - if (a.row > b.row || (a.row == b.row && a.column > b.column)) - return a; - else - return b; -} - -#endif diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c deleted file mode 100644 index 59902dee3b..0000000000 --- a/src/tree_sitter/query.c +++ /dev/null @@ -1,2035 +0,0 @@ -#include "tree_sitter/api.h" -#include "./alloc.h" -#include "./array.h" -#include "./bits.h" -#include "./language.h" -#include "./point.h" -#include "./tree_cursor.h" -#include "./unicode.h" -#include <wctype.h> - -// #define LOG(...) fprintf(stderr, __VA_ARGS__) -#define LOG(...) - -#define MAX_STATE_COUNT 256 -#define MAX_CAPTURE_LIST_COUNT 32 -#define MAX_STEP_CAPTURE_COUNT 3 - -/* - * Stream - A sequence of unicode characters derived from a UTF8 string. - * This struct is used in parsing queries from S-expressions. - */ -typedef struct { - const char *input; - const char *end; - int32_t next; - uint8_t next_size; -} Stream; - -/* - * QueryStep - A step in the process of matching a query. Each node within - * a query S-expression maps to one of these steps. An entire pattern is - * represented as a sequence of these steps. Fields: - * - * - `symbol` - The grammar symbol to match. A zero value represents the - * wildcard symbol, '_'. - * - `field` - The field name to match. A zero value means that a field name - * was not specified. - * - `capture_ids` - An array of integers representing the names of captures - * associated with this node in the pattern, terminated by a `NONE` value. - * - `depth` - The depth where this node occurs in the pattern. The root node - * of the pattern has depth zero. - * - `alternative_index` - The index of a different query step that serves as - * an alternative to this step. - */ -typedef struct { - TSSymbol symbol; - TSFieldId field; - uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT]; - uint16_t alternative_index; - uint16_t depth; - bool contains_captures: 1; - bool is_pattern_start: 1; - bool is_immediate: 1; - bool is_last_child: 1; - bool is_pass_through: 1; - bool is_dead_end: 1; - bool alternative_is_immediate: 1; -} QueryStep; - -/* - * Slice - A slice of an external array. Within a query, capture names, - * literal string values, and predicate step informations are stored in three - * contiguous arrays. Individual captures, string values, and predicates are - * represented as slices of these three arrays. - */ -typedef struct { - uint32_t offset; - uint32_t length; -} Slice; - -/* - * SymbolTable - a two-way mapping of strings to ids. - */ -typedef struct { - Array(char) characters; - Array(Slice) slices; -} SymbolTable; - -/* - * PatternEntry - Information about the starting point for matching a - * particular pattern, consisting of the index of the pattern within the query, - * and the index of the patter's first step in the shared `steps` array. These - * entries are stored in a 'pattern map' - a sorted array that makes it - * possible to efficiently lookup patterns based on the symbol for their first - * step. - */ -typedef struct { - uint16_t step_index; - uint16_t pattern_index; -} PatternEntry; - -/* - * QueryState - The state of an in-progress match of a particular pattern - * in a query. While executing, a `TSQueryCursor` must keep track of a number - * of possible in-progress matches. Each of those possible matches is - * represented as one of these states. Fields: - * - `id` - A numeric id that is exposed to the public API. This allows the - * caller to remove a given match, preventing any more of its captures - * from being returned. - * - `start_depth` - The depth in the tree where the first step of the state's - * pattern was matched. - * - `pattern_index` - The pattern that the state is matching. - * - `consumed_capture_count` - The number of captures from this match that - * have already been returned. - * - `capture_list_id` - A numeric id that can be used to retrieve the state's - * list of captures from the `CaptureListPool`. - * - `seeking_immediate_match` - A flag that indicates that the state's next - * step must be matched by the very next sibling. This is used when - * processing repetitions. - * - `has_in_progress_alternatives` - A flag that indicates that there is are - * other states that have the same captures as this state, but are at - * different steps in their pattern. This means that in order to obey the - * 'longest-match' rule, this state should not be returned as a match until - * it is clear that there can be no longer match. - */ -typedef struct { - uint32_t id; - uint16_t start_depth; - uint16_t step_index; - uint16_t pattern_index; - uint16_t capture_list_id; - uint16_t consumed_capture_count: 14; - bool seeking_immediate_match: 1; - bool has_in_progress_alternatives: 1; -} QueryState; - -typedef Array(TSQueryCapture) CaptureList; - -/* - * CaptureListPool - A collection of *lists* of captures. Each QueryState - * needs to maintain its own list of captures. To avoid repeated allocations, - * the reuses a fixed set of capture lists, and keeps track of which ones - * are currently in use. - */ -typedef struct { - CaptureList list[MAX_CAPTURE_LIST_COUNT]; - CaptureList empty_list; - uint32_t usage_map; -} CaptureListPool; - -/* - * TSQuery - A tree query, compiled from a string of S-expressions. The query - * itself is immutable. The mutable state used in the process of executing the - * query is stored in a `TSQueryCursor`. - */ -struct TSQuery { - SymbolTable captures; - SymbolTable predicate_values; - Array(QueryStep) steps; - Array(PatternEntry) pattern_map; - Array(TSQueryPredicateStep) predicate_steps; - Array(Slice) predicates_by_pattern; - Array(uint32_t) start_bytes_by_pattern; - const TSLanguage *language; - uint16_t wildcard_root_pattern_count; - TSSymbol *symbol_map; -}; - -/* - * TSQueryCursor - A stateful struct used to execute a query on a tree. - */ -struct TSQueryCursor { - const TSQuery *query; - TSTreeCursor cursor; - Array(QueryState) states; - Array(QueryState) finished_states; - CaptureListPool capture_list_pool; - uint32_t depth; - uint32_t start_byte; - uint32_t end_byte; - uint32_t next_state_id; - TSPoint start_point; - TSPoint end_point; - bool ascending; -}; - -static const TSQueryError PARENT_DONE = -1; -static const uint16_t PATTERN_DONE_MARKER = UINT16_MAX; -static const uint16_t NONE = UINT16_MAX; -static const TSSymbol WILDCARD_SYMBOL = 0; -static const TSSymbol NAMED_WILDCARD_SYMBOL = UINT16_MAX - 1; - -/********** - * Stream - **********/ - -// Advance to the next unicode code point in the stream. -static bool stream_advance(Stream *self) { - self->input += self->next_size; - if (self->input < self->end) { - uint32_t size = ts_decode_utf8( - (const uint8_t *)self->input, - self->end - self->input, - &self->next - ); - if (size > 0) { - self->next_size = size; - return true; - } - } else { - self->next_size = 0; - self->next = '\0'; - } - return false; -} - -// Reset the stream to the given input position, represented as a pointer -// into the input string. -static void stream_reset(Stream *self, const char *input) { - self->input = input; - self->next_size = 0; - stream_advance(self); -} - -static Stream stream_new(const char *string, uint32_t length) { - Stream self = { - .next = 0, - .input = string, - .end = string + length, - }; - stream_advance(&self); - return self; -} - -static void stream_skip_whitespace(Stream *stream) { - for (;;) { - if (iswspace(stream->next)) { - stream_advance(stream); - } else if (stream->next == ';') { - // skip over comments - stream_advance(stream); - while (stream->next && stream->next != '\n') { - if (!stream_advance(stream)) break; - } - } else { - break; - } - } -} - -static bool stream_is_ident_start(Stream *stream) { - return iswalnum(stream->next) || stream->next == '_' || stream->next == '-'; -} - -static void stream_scan_identifier(Stream *stream) { - do { - stream_advance(stream); - } while ( - iswalnum(stream->next) || - stream->next == '_' || - stream->next == '-' || - stream->next == '.' || - stream->next == '?' || - stream->next == '!' - ); -} - -/****************** - * CaptureListPool - ******************/ - -static CaptureListPool capture_list_pool_new(void) { - return (CaptureListPool) { - .empty_list = array_new(), - .usage_map = UINT32_MAX, - }; -} - -static void capture_list_pool_reset(CaptureListPool *self) { - self->usage_map = UINT32_MAX; - for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) { - array_clear(&self->list[i]); - } -} - -static void capture_list_pool_delete(CaptureListPool *self) { - for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) { - array_delete(&self->list[i]); - } -} - -static const CaptureList *capture_list_pool_get(const CaptureListPool *self, uint16_t id) { - if (id >= MAX_CAPTURE_LIST_COUNT) return &self->empty_list; - return &self->list[id]; -} - -static CaptureList *capture_list_pool_get_mut(CaptureListPool *self, uint16_t id) { - assert(id < MAX_CAPTURE_LIST_COUNT); - return &self->list[id]; -} - -static bool capture_list_pool_is_empty(const CaptureListPool *self) { - return self->usage_map == 0; -} - -static uint16_t capture_list_pool_acquire(CaptureListPool *self) { - // In the usage_map bitmask, ones represent free lists, and zeros represent - // lists that are in use. A free list id can quickly be found by counting - // the leading zeros in the usage map. An id of zero corresponds to the - // highest-order bit in the bitmask. - uint16_t id = count_leading_zeros(self->usage_map); - if (id >= MAX_CAPTURE_LIST_COUNT) return NONE; - self->usage_map &= ~bitmask_for_index(id); - array_clear(&self->list[id]); - return id; -} - -static void capture_list_pool_release(CaptureListPool *self, uint16_t id) { - if (id >= MAX_CAPTURE_LIST_COUNT) return; - array_clear(&self->list[id]); - self->usage_map |= bitmask_for_index(id); -} - -/************** - * SymbolTable - **************/ - -static SymbolTable symbol_table_new(void) { - return (SymbolTable) { - .characters = array_new(), - .slices = array_new(), - }; -} - -static void symbol_table_delete(SymbolTable *self) { - array_delete(&self->characters); - array_delete(&self->slices); -} - -static int symbol_table_id_for_name( - const SymbolTable *self, - const char *name, - uint32_t length -) { - for (unsigned i = 0; i < self->slices.size; i++) { - Slice slice = self->slices.contents[i]; - if ( - slice.length == length && - !strncmp(&self->characters.contents[slice.offset], name, length) - ) return i; - } - return -1; -} - -static const char *symbol_table_name_for_id( - const SymbolTable *self, - uint16_t id, - uint32_t *length -) { - Slice slice = self->slices.contents[id]; - *length = slice.length; - return &self->characters.contents[slice.offset]; -} - -static uint16_t symbol_table_insert_name( - SymbolTable *self, - const char *name, - uint32_t length -) { - int id = symbol_table_id_for_name(self, name, length); - if (id >= 0) return (uint16_t)id; - Slice slice = { - .offset = self->characters.size, - .length = length, - }; - array_grow_by(&self->characters, length + 1); - memcpy(&self->characters.contents[slice.offset], name, length); - self->characters.contents[self->characters.size - 1] = 0; - array_push(&self->slices, slice); - return self->slices.size - 1; -} - -static uint16_t symbol_table_insert_name_with_escapes( - SymbolTable *self, - const char *escaped_name, - uint32_t escaped_length -) { - Slice slice = { - .offset = self->characters.size, - .length = 0, - }; - array_grow_by(&self->characters, escaped_length + 1); - - // Copy the contents of the literal into the characters buffer, processing escape - // sequences like \n and \". This needs to be done before checking if the literal - // is already present, in order to do the string comparison. - bool is_escaped = false; - for (unsigned i = 0; i < escaped_length; i++) { - const char *src = &escaped_name[i]; - char *dest = &self->characters.contents[slice.offset + slice.length]; - if (is_escaped) { - switch (*src) { - case 'n': - *dest = '\n'; - break; - case 'r': - *dest = '\r'; - break; - case 't': - *dest = '\t'; - break; - case '0': - *dest = '\0'; - break; - default: - *dest = *src; - break; - } - is_escaped = false; - slice.length++; - } else { - if (*src == '\\') { - is_escaped = true; - } else { - *dest = *src; - slice.length++; - } - } - } - - // If the string is already present, remove the redundant content from the characters - // buffer and return the existing id. - int id = symbol_table_id_for_name(self, &self->characters.contents[slice.offset], slice.length); - if (id >= 0) { - self->characters.size -= (escaped_length + 1); - return id; - } - - self->characters.contents[slice.offset + slice.length] = 0; - array_push(&self->slices, slice); - return self->slices.size - 1; -} - -/************ - * QueryStep - ************/ - -static QueryStep query_step__new( - TSSymbol symbol, - uint16_t depth, - bool is_immediate -) { - return (QueryStep) { - .symbol = symbol, - .depth = depth, - .field = 0, - .capture_ids = {NONE, NONE, NONE}, - .alternative_index = NONE, - .contains_captures = false, - .is_last_child = false, - .is_pattern_start = false, - .is_pass_through = false, - .is_dead_end = false, - .is_immediate = is_immediate, - .alternative_is_immediate = false, - }; -} - -static void query_step__add_capture(QueryStep *self, uint16_t capture_id) { - for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) { - if (self->capture_ids[i] == NONE) { - self->capture_ids[i] = capture_id; - break; - } - } -} - -static void query_step__remove_capture(QueryStep *self, uint16_t capture_id) { - for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) { - if (self->capture_ids[i] == capture_id) { - self->capture_ids[i] = NONE; - while (i + 1 < MAX_STEP_CAPTURE_COUNT) { - if (self->capture_ids[i + 1] == NONE) break; - self->capture_ids[i] = self->capture_ids[i + 1]; - self->capture_ids[i + 1] = NONE; - i++; - } - break; - } - } -} - -/********* - * Query - *********/ - -// The `pattern_map` contains a mapping from TSSymbol values to indices in the -// `steps` array. For a given syntax node, the `pattern_map` makes it possible -// to quickly find the starting steps of all of the patterns whose root matches -// that node. Each entry has two fields: a `pattern_index`, which identifies one -// of the patterns in the query, and a `step_index`, which indicates the start -// offset of that pattern's steps within the `steps` array. -// -// The entries are sorted by the patterns' root symbols, and lookups use a -// binary search. This ensures that the cost of this initial lookup step -// scales logarithmically with the number of patterns in the query. -// -// This returns `true` if the symbol is present and `false` otherwise. -// If the symbol is not present `*result` is set to the index where the -// symbol should be inserted. -static inline bool ts_query__pattern_map_search( - const TSQuery *self, - TSSymbol needle, - uint32_t *result -) { - uint32_t base_index = self->wildcard_root_pattern_count; - uint32_t size = self->pattern_map.size - base_index; - if (size == 0) { - *result = base_index; - return false; - } - while (size > 1) { - uint32_t half_size = size / 2; - uint32_t mid_index = base_index + half_size; - TSSymbol mid_symbol = self->steps.contents[ - self->pattern_map.contents[mid_index].step_index - ].symbol; - if (needle > mid_symbol) base_index = mid_index; - size -= half_size; - } - - TSSymbol symbol = self->steps.contents[ - self->pattern_map.contents[base_index].step_index - ].symbol; - - if (needle > symbol) { - base_index++; - if (base_index < self->pattern_map.size) { - symbol = self->steps.contents[ - self->pattern_map.contents[base_index].step_index - ].symbol; - } - } - - *result = base_index; - return needle == symbol; -} - -// Insert a new pattern's start index into the pattern map, maintaining -// the pattern map's ordering invariant. -static inline void ts_query__pattern_map_insert( - TSQuery *self, - TSSymbol symbol, - uint32_t start_step_index, - uint32_t pattern_index -) { - uint32_t index; - ts_query__pattern_map_search(self, symbol, &index); - array_insert(&self->pattern_map, index, ((PatternEntry) { - .step_index = start_step_index, - .pattern_index = pattern_index, - })); -} - -static void ts_query__finalize_steps(TSQuery *self) { - for (unsigned i = 0; i < self->steps.size; i++) { - QueryStep *step = &self->steps.contents[i]; - uint32_t depth = step->depth; - if (step->capture_ids[0] != NONE) { - step->contains_captures = true; - } else { - step->contains_captures = false; - for (unsigned j = i + 1; j < self->steps.size; j++) { - QueryStep *s = &self->steps.contents[j]; - if (s->depth == PATTERN_DONE_MARKER || s->depth <= depth) break; - if (s->capture_ids[0] != NONE) step->contains_captures = true; - } - } - } -} - -// Parse a single predicate associated with a pattern, adding it to the -// query's internal `predicate_steps` array. Predicates are arbitrary -// S-expressions associated with a pattern which are meant to be handled at -// a higher level of abstraction, such as the Rust/JavaScript bindings. They -// can contain '@'-prefixed capture names, double-quoted strings, and bare -// symbols, which also represent strings. -static TSQueryError ts_query__parse_predicate( - TSQuery *self, - Stream *stream -) { - if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; - const char *predicate_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - predicate_name; - uint16_t id = symbol_table_insert_name( - &self->predicate_values, - predicate_name, - length - ); - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeString, - .value_id = id, - })); - stream_skip_whitespace(stream); - - for (;;) { - if (stream->next == ')') { - stream_advance(stream); - stream_skip_whitespace(stream); - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeDone, - .value_id = 0, - })); - break; - } - - // Parse an '@'-prefixed capture name - else if (stream->next == '@') { - stream_advance(stream); - - // Parse the capture name - if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; - const char *capture_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - capture_name; - - // Add the capture id to the first step of the pattern - int capture_id = symbol_table_id_for_name( - &self->captures, - capture_name, - length - ); - if (capture_id == -1) { - stream_reset(stream, capture_name); - return TSQueryErrorCapture; - } - - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeCapture, - .value_id = capture_id, - })); - } - - // Parse a string literal - else if (stream->next == '"') { - stream_advance(stream); - - // Parse the string content - bool is_escaped = false; - const char *string_content = stream->input; - for (;;) { - if (is_escaped) { - is_escaped = false; - } else { - if (stream->next == '\\') { - is_escaped = true; - } else if (stream->next == '"') { - break; - } else if (stream->next == '\n') { - stream_reset(stream, string_content - 1); - return TSQueryErrorSyntax; - } - } - if (!stream_advance(stream)) { - stream_reset(stream, string_content - 1); - return TSQueryErrorSyntax; - } - } - uint32_t length = stream->input - string_content; - - // Add a step for the node - uint16_t id = symbol_table_insert_name_with_escapes( - &self->predicate_values, - string_content, - length - ); - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeString, - .value_id = id, - })); - - if (stream->next != '"') return TSQueryErrorSyntax; - stream_advance(stream); - } - - // Parse a bare symbol - else if (stream_is_ident_start(stream)) { - const char *symbol_start = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - symbol_start; - uint16_t id = symbol_table_insert_name( - &self->predicate_values, - symbol_start, - length - ); - array_back(&self->predicates_by_pattern)->length++; - array_push(&self->predicate_steps, ((TSQueryPredicateStep) { - .type = TSQueryPredicateStepTypeString, - .value_id = id, - })); - } - - else { - return TSQueryErrorSyntax; - } - - stream_skip_whitespace(stream); - } - - return 0; -} - -// Read one S-expression pattern from the stream, and incorporate it into -// the query's internal state machine representation. For nested patterns, -// this function calls itself recursively. -static TSQueryError ts_query__parse_pattern( - TSQuery *self, - Stream *stream, - uint32_t depth, - uint32_t *capture_count, - bool is_immediate -) { - uint32_t starting_step_index = self->steps.size; - - if (stream->next == 0) return TSQueryErrorSyntax; - - // Finish the parent S-expression. - if (stream->next == ')' || stream->next == ']') { - return PARENT_DONE; - } - - // An open bracket is the start of an alternation. - else if (stream->next == '[') { - stream_advance(stream); - stream_skip_whitespace(stream); - - // Parse each branch, and add a placeholder step in between the branches. - Array(uint32_t) branch_step_indices = array_new(); - for (;;) { - uint32_t start_index = self->steps.size; - TSQueryError e = ts_query__parse_pattern( - self, - stream, - depth, - capture_count, - is_immediate - ); - - if (e == PARENT_DONE && stream->next == ']' && branch_step_indices.size > 0) { - stream_advance(stream); - break; - } else if (e) { - array_delete(&branch_step_indices); - return e; - } - - array_push(&branch_step_indices, start_index); - array_push(&self->steps, query_step__new(0, depth, false)); - } - (void)array_pop(&self->steps); - - // For all of the branches except for the last one, add the subsequent branch as an - // alternative, and link the end of the branch to the current end of the steps. - for (unsigned i = 0; i < branch_step_indices.size - 1; i++) { - uint32_t step_index = branch_step_indices.contents[i]; - uint32_t next_step_index = branch_step_indices.contents[i + 1]; - QueryStep *start_step = &self->steps.contents[step_index]; - QueryStep *end_step = &self->steps.contents[next_step_index - 1]; - start_step->alternative_index = next_step_index; - end_step->alternative_index = self->steps.size; - end_step->is_dead_end = true; - } - - array_delete(&branch_step_indices); - } - - // An open parenthesis can be the start of three possible constructs: - // * A grouped sequence - // * A predicate - // * A named node - else if (stream->next == '(') { - stream_advance(stream); - stream_skip_whitespace(stream); - - // If this parenthesis is followed by a node, then it represents a grouped sequence. - if (stream->next == '(' || stream->next == '"' || stream->next == '[') { - bool child_is_immediate = false; - for (;;) { - if (stream->next == '.') { - child_is_immediate = true; - stream_advance(stream); - stream_skip_whitespace(stream); - } - TSQueryError e = ts_query__parse_pattern( - self, - stream, - depth, - capture_count, - child_is_immediate - ); - if (e == PARENT_DONE && stream->next == ')') { - stream_advance(stream); - break; - } else if (e) { - return e; - } - - child_is_immediate = false; - } - } - - // A pound character indicates the start of a predicate. - else if (stream->next == '#') { - stream_advance(stream); - return ts_query__parse_predicate(self, stream); - } - - // Otherwise, this parenthesis is the start of a named node. - else { - TSSymbol symbol; - - // Parse the wildcard symbol - if ( - stream->next == '_' || - - // TODO - remove. - // For temporary backward compatibility, handle '*' as a wildcard. - stream->next == '*' - ) { - symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL; - stream_advance(stream); - } - - // Parse a normal node name - else if (stream_is_ident_start(stream)) { - const char *node_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - node_name; - - // TODO - remove. - // For temporary backward compatibility, handle predicates without the leading '#' sign. - if (length > 0 && (node_name[length - 1] == '!' || node_name[length - 1] == '?')) { - stream_reset(stream, node_name); - return ts_query__parse_predicate(self, stream); - } - - symbol = ts_language_symbol_for_name( - self->language, - node_name, - length, - true - ); - if (!symbol) { - stream_reset(stream, node_name); - return TSQueryErrorNodeType; - } - } else { - return TSQueryErrorSyntax; - } - - // Add a step for the node. - array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); - - // Parse the child patterns - stream_skip_whitespace(stream); - bool child_is_immediate = false; - uint16_t child_start_step_index = self->steps.size; - for (;;) { - if (stream->next == '.') { - child_is_immediate = true; - stream_advance(stream); - stream_skip_whitespace(stream); - } - - TSQueryError e = ts_query__parse_pattern( - self, - stream, - depth + 1, - capture_count, - child_is_immediate - ); - if (e == PARENT_DONE && stream->next == ')') { - if (child_is_immediate) { - self->steps.contents[child_start_step_index].is_last_child = true; - } - stream_advance(stream); - break; - } else if (e) { - return e; - } - - child_is_immediate = false; - } - } - } - - // Parse a wildcard pattern - else if ( - stream->next == '_' || - - // TODO remove. - // For temporary backward compatibility, handle '*' as a wildcard. - stream->next == '*' - ) { - stream_advance(stream); - stream_skip_whitespace(stream); - - // Add a step that matches any kind of node - array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate)); - } - - // Parse a double-quoted anonymous leaf node expression - else if (stream->next == '"') { - stream_advance(stream); - - // Parse the string content - const char *string_content = stream->input; - while (stream->next != '"') { - if (!stream_advance(stream)) { - stream_reset(stream, string_content - 1); - return TSQueryErrorSyntax; - } - } - uint32_t length = stream->input - string_content; - - // Add a step for the node - TSSymbol symbol = ts_language_symbol_for_name( - self->language, - string_content, - length, - false - ); - if (!symbol) { - stream_reset(stream, string_content); - return TSQueryErrorNodeType; - } - array_push(&self->steps, query_step__new(symbol, depth, is_immediate)); - - if (stream->next != '"') return TSQueryErrorSyntax; - stream_advance(stream); - } - - // Parse a field-prefixed pattern - else if (stream_is_ident_start(stream)) { - // Parse the field name - const char *field_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - field_name; - stream_skip_whitespace(stream); - - if (stream->next != ':') { - stream_reset(stream, field_name); - return TSQueryErrorSyntax; - } - stream_advance(stream); - stream_skip_whitespace(stream); - - // Parse the pattern - uint32_t step_index = self->steps.size; - TSQueryError e = ts_query__parse_pattern( - self, - stream, - depth, - capture_count, - is_immediate - ); - if (e == PARENT_DONE) return TSQueryErrorSyntax; - if (e) return e; - - // Add the field name to the first step of the pattern - TSFieldId field_id = ts_language_field_id_for_name( - self->language, - field_name, - length - ); - if (!field_id) { - stream->input = field_name; - return TSQueryErrorField; - } - self->steps.contents[step_index].field = field_id; - } - - else { - return TSQueryErrorSyntax; - } - - stream_skip_whitespace(stream); - - // Parse suffixes modifiers for this pattern - for (;;) { - QueryStep *step = &self->steps.contents[starting_step_index]; - - // Parse the one-or-more operator. - if (stream->next == '+') { - stream_advance(stream); - stream_skip_whitespace(stream); - - QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false); - repeat_step.alternative_index = starting_step_index; - repeat_step.is_pass_through = true; - repeat_step.alternative_is_immediate = true; - array_push(&self->steps, repeat_step); - } - - // Parse the zero-or-more repetition operator. - else if (stream->next == '*') { - stream_advance(stream); - stream_skip_whitespace(stream); - - QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false); - repeat_step.alternative_index = starting_step_index; - repeat_step.is_pass_through = true; - repeat_step.alternative_is_immediate = true; - array_push(&self->steps, repeat_step); - - while (step->alternative_index != NONE) { - step = &self->steps.contents[step->alternative_index]; - } - step->alternative_index = self->steps.size; - } - - // Parse the optional operator. - else if (stream->next == '?') { - stream_advance(stream); - stream_skip_whitespace(stream); - - while (step->alternative_index != NONE) { - step = &self->steps.contents[step->alternative_index]; - } - step->alternative_index = self->steps.size; - } - - // Parse an '@'-prefixed capture pattern - else if (stream->next == '@') { - stream_advance(stream); - if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; - const char *capture_name = stream->input; - stream_scan_identifier(stream); - uint32_t length = stream->input - capture_name; - stream_skip_whitespace(stream); - - // Add the capture id to the first step of the pattern - uint16_t capture_id = symbol_table_insert_name( - &self->captures, - capture_name, - length - ); - - for (;;) { - query_step__add_capture(step, capture_id); - if ( - step->alternative_index != NONE && - step->alternative_index > starting_step_index && - step->alternative_index < self->steps.size - ) { - starting_step_index = step->alternative_index; - step = &self->steps.contents[starting_step_index]; - } else { - break; - } - } - - (*capture_count)++; - } - - // No more suffix modifiers - else { - break; - } - } - - return 0; -} - -TSQuery *ts_query_new( - const TSLanguage *language, - const char *source, - uint32_t source_len, - uint32_t *error_offset, - TSQueryError *error_type -) { - TSSymbol *symbol_map; - if (ts_language_version(language) >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) { - symbol_map = NULL; - } else { - // Work around the fact that multiple symbols can currently be - // associated with the same name, due to "simple aliases". - // In the next language ABI version, this map will be contained - // in the language's `public_symbol_map` field. - uint32_t symbol_count = ts_language_symbol_count(language); - symbol_map = ts_malloc(sizeof(TSSymbol) * symbol_count); - for (unsigned i = 0; i < symbol_count; i++) { - const char *name = ts_language_symbol_name(language, i); - const TSSymbolType symbol_type = ts_language_symbol_type(language, i); - - symbol_map[i] = i; - - for (unsigned j = 0; j < i; j++) { - if (ts_language_symbol_type(language, j) == symbol_type) { - if (!strcmp(name, ts_language_symbol_name(language, j))) { - symbol_map[i] = j; - break; - } - } - } - } - } - - TSQuery *self = ts_malloc(sizeof(TSQuery)); - *self = (TSQuery) { - .steps = array_new(), - .pattern_map = array_new(), - .captures = symbol_table_new(), - .predicate_values = symbol_table_new(), - .predicate_steps = array_new(), - .predicates_by_pattern = array_new(), - .symbol_map = symbol_map, - .wildcard_root_pattern_count = 0, - .language = language, - }; - - // Parse all of the S-expressions in the given string. - Stream stream = stream_new(source, source_len); - stream_skip_whitespace(&stream); - while (stream.input < stream.end) { - uint32_t pattern_index = self->predicates_by_pattern.size; - uint32_t start_step_index = self->steps.size; - uint32_t capture_count = 0; - array_push(&self->start_bytes_by_pattern, stream.input - source); - array_push(&self->predicates_by_pattern, ((Slice) { - .offset = self->predicate_steps.size, - .length = 0, - })); - *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count, false); - array_push(&self->steps, query_step__new(0, PATTERN_DONE_MARKER, false)); - - // If any pattern could not be parsed, then report the error information - // and terminate. - if (*error_type) { - if (*error_type == PARENT_DONE) *error_type = TSQueryErrorSyntax; - *error_offset = stream.input - source; - ts_query_delete(self); - return NULL; - } - - // If a pattern has a wildcard at its root, optimize the matching process - // by skipping matching the wildcard. - if ( - self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL - ) { - QueryStep *second_step = &self->steps.contents[start_step_index + 1]; - if (second_step->symbol != WILDCARD_SYMBOL && second_step->depth != PATTERN_DONE_MARKER) { - start_step_index += 1; - } - } - - // Maintain a map that can look up patterns for a given root symbol. - for (;;) { - QueryStep *step = &self->steps.contents[start_step_index]; - step->is_pattern_start = true; - ts_query__pattern_map_insert(self, step->symbol, start_step_index, pattern_index); - if (step->symbol == WILDCARD_SYMBOL) { - self->wildcard_root_pattern_count++; - } - - // If there are alternatives or options at the root of the pattern, - // then add multiple entries to the pattern map. - if (step->alternative_index != NONE) { - start_step_index = step->alternative_index; - } else { - break; - } - } - } - - ts_query__finalize_steps(self); - return self; -} - -void ts_query_delete(TSQuery *self) { - if (self) { - array_delete(&self->steps); - array_delete(&self->pattern_map); - array_delete(&self->predicate_steps); - array_delete(&self->predicates_by_pattern); - array_delete(&self->start_bytes_by_pattern); - symbol_table_delete(&self->captures); - symbol_table_delete(&self->predicate_values); - ts_free(self->symbol_map); - ts_free(self); - } -} - -uint32_t ts_query_pattern_count(const TSQuery *self) { - return self->predicates_by_pattern.size; -} - -uint32_t ts_query_capture_count(const TSQuery *self) { - return self->captures.slices.size; -} - -uint32_t ts_query_string_count(const TSQuery *self) { - return self->predicate_values.slices.size; -} - -const char *ts_query_capture_name_for_id( - const TSQuery *self, - uint32_t index, - uint32_t *length -) { - return symbol_table_name_for_id(&self->captures, index, length); -} - -const char *ts_query_string_value_for_id( - const TSQuery *self, - uint32_t index, - uint32_t *length -) { - return symbol_table_name_for_id(&self->predicate_values, index, length); -} - -const TSQueryPredicateStep *ts_query_predicates_for_pattern( - const TSQuery *self, - uint32_t pattern_index, - uint32_t *step_count -) { - Slice slice = self->predicates_by_pattern.contents[pattern_index]; - *step_count = slice.length; - return &self->predicate_steps.contents[slice.offset]; -} - -uint32_t ts_query_start_byte_for_pattern( - const TSQuery *self, - uint32_t pattern_index -) { - return self->start_bytes_by_pattern.contents[pattern_index]; -} - -void ts_query_disable_capture( - TSQuery *self, - const char *name, - uint32_t length -) { - // Remove capture information for any pattern step that previously - // captured with the given name. - int id = symbol_table_id_for_name(&self->captures, name, length); - if (id != -1) { - for (unsigned i = 0; i < self->steps.size; i++) { - QueryStep *step = &self->steps.contents[i]; - query_step__remove_capture(step, id); - } - ts_query__finalize_steps(self); - } -} - -void ts_query_disable_pattern( - TSQuery *self, - uint32_t pattern_index -) { - // Remove the given pattern from the pattern map. Its steps will still - // be in the `steps` array, but they will never be read. - for (unsigned i = 0; i < self->pattern_map.size; i++) { - PatternEntry *pattern = &self->pattern_map.contents[i]; - if (pattern->pattern_index == pattern_index) { - array_erase(&self->pattern_map, i); - i--; - } - } -} - -/*************** - * QueryCursor - ***************/ - -TSQueryCursor *ts_query_cursor_new(void) { - TSQueryCursor *self = ts_malloc(sizeof(TSQueryCursor)); - *self = (TSQueryCursor) { - .ascending = false, - .states = array_new(), - .finished_states = array_new(), - .capture_list_pool = capture_list_pool_new(), - .start_byte = 0, - .end_byte = UINT32_MAX, - .start_point = {0, 0}, - .end_point = POINT_MAX, - }; - array_reserve(&self->states, MAX_STATE_COUNT); - array_reserve(&self->finished_states, MAX_CAPTURE_LIST_COUNT); - return self; -} - -void ts_query_cursor_delete(TSQueryCursor *self) { - array_delete(&self->states); - array_delete(&self->finished_states); - ts_tree_cursor_delete(&self->cursor); - capture_list_pool_delete(&self->capture_list_pool); - ts_free(self); -} - -void ts_query_cursor_exec( - TSQueryCursor *self, - const TSQuery *query, - TSNode node -) { - array_clear(&self->states); - array_clear(&self->finished_states); - ts_tree_cursor_reset(&self->cursor, node); - capture_list_pool_reset(&self->capture_list_pool); - self->next_state_id = 0; - self->depth = 0; - self->ascending = false; - self->query = query; -} - -void ts_query_cursor_set_byte_range( - TSQueryCursor *self, - uint32_t start_byte, - uint32_t end_byte -) { - if (end_byte == 0) { - start_byte = 0; - end_byte = UINT32_MAX; - } - self->start_byte = start_byte; - self->end_byte = end_byte; -} - -void ts_query_cursor_set_point_range( - TSQueryCursor *self, - TSPoint start_point, - TSPoint end_point -) { - if (end_point.row == 0 && end_point.column == 0) { - start_point = POINT_ZERO; - end_point = POINT_MAX; - } - self->start_point = start_point; - self->end_point = end_point; -} - -// Search through all of the in-progress states, and find the captured -// node that occurs earliest in the document. -static bool ts_query_cursor__first_in_progress_capture( - TSQueryCursor *self, - uint32_t *state_index, - uint32_t *byte_offset, - uint32_t *pattern_index -) { - bool result = false; - *state_index = UINT32_MAX; - *byte_offset = UINT32_MAX; - *pattern_index = UINT32_MAX; - for (unsigned i = 0; i < self->states.size; i++) { - const QueryState *state = &self->states.contents[i]; - const CaptureList *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - if (captures->size > 0) { - uint32_t capture_byte = ts_node_start_byte(captures->contents[0].node); - if ( - !result || - capture_byte < *byte_offset || - (capture_byte == *byte_offset && state->pattern_index < *pattern_index) - ) { - result = true; - *state_index = i; - *byte_offset = capture_byte; - *pattern_index = state->pattern_index; - } - } - } - return result; -} - -// Determine which node is first in a depth-first traversal -int ts_query_cursor__compare_nodes(TSNode left, TSNode right) { - if (left.id != right.id) { - uint32_t left_start = ts_node_start_byte(left); - uint32_t right_start = ts_node_start_byte(right); - if (left_start < right_start) return -1; - if (left_start > right_start) return 1; - uint32_t left_node_count = ts_node_end_byte(left); - uint32_t right_node_count = ts_node_end_byte(right); - if (left_node_count > right_node_count) return -1; - if (left_node_count < right_node_count) return 1; - } - return 0; -} - -// Determine if either state contains a superset of the other state's captures. -void ts_query_cursor__compare_captures( - TSQueryCursor *self, - QueryState *left_state, - QueryState *right_state, - bool *left_contains_right, - bool *right_contains_left -) { - const CaptureList *left_captures = capture_list_pool_get( - &self->capture_list_pool, - left_state->capture_list_id - ); - const CaptureList *right_captures = capture_list_pool_get( - &self->capture_list_pool, - right_state->capture_list_id - ); - *left_contains_right = true; - *right_contains_left = true; - unsigned i = 0, j = 0; - for (;;) { - if (i < left_captures->size) { - if (j < right_captures->size) { - TSQueryCapture *left = &left_captures->contents[i]; - TSQueryCapture *right = &right_captures->contents[j]; - if (left->node.id == right->node.id && left->index == right->index) { - i++; - j++; - } else { - switch (ts_query_cursor__compare_nodes(left->node, right->node)) { - case -1: - *right_contains_left = false; - i++; - break; - case 1: - *left_contains_right = false; - j++; - break; - default: - *right_contains_left = false; - *left_contains_right = false; - i++; - j++; - break; - } - } - } else { - *right_contains_left = false; - break; - } - } else { - if (j < right_captures->size) { - *left_contains_right = false; - } - break; - } - } -} - -static bool ts_query_cursor__add_state( - TSQueryCursor *self, - const PatternEntry *pattern -) { - if (self->states.size >= MAX_STATE_COUNT) { - LOG(" too many states"); - return false; - } - LOG( - " start state. pattern:%u, step:%u\n", - pattern->pattern_index, - pattern->step_index - ); - QueryStep *step = &self->query->steps.contents[pattern->step_index]; - array_push(&self->states, ((QueryState) { - .capture_list_id = NONE, - .step_index = pattern->step_index, - .pattern_index = pattern->pattern_index, - .start_depth = self->depth - step->depth, - .consumed_capture_count = 0, - .seeking_immediate_match = false, - })); - return true; -} - -// Duplicate the given state and insert the newly-created state immediately after -// the given state in the `states` array. -static QueryState *ts_query__cursor_copy_state( - TSQueryCursor *self, - const QueryState *state -) { - if (self->states.size >= MAX_STATE_COUNT) { - LOG(" too many states"); - return NULL; - } - - // If the state has captures, copy its capture list. - QueryState copy = *state; - copy.capture_list_id = state->capture_list_id; - if (state->capture_list_id != NONE) { - copy.capture_list_id = capture_list_pool_acquire(&self->capture_list_pool); - if (copy.capture_list_id == NONE) { - LOG(" too many capture lists"); - return NULL; - } - const CaptureList *old_captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - CaptureList *new_captures = capture_list_pool_get_mut( - &self->capture_list_pool, - copy.capture_list_id - ); - array_push_all(new_captures, old_captures); - } - - uint32_t index = (state - self->states.contents) + 1; - array_insert(&self->states, index, copy); - return &self->states.contents[index]; -} - -// Walk the tree, processing patterns until at least one pattern finishes, -// If one or more patterns finish, return `true` and store their states in the -// `finished_states` array. Multiple patterns can finish on the same node. If -// there are no more matches, return `false`. -static inline bool ts_query_cursor__advance(TSQueryCursor *self) { - do { - if (self->ascending) { - LOG("leave node. type:%s\n", ts_node_type(ts_tree_cursor_current_node(&self->cursor))); - - // Leave this node by stepping to its next sibling or to its parent. - bool did_move = true; - if (ts_tree_cursor_goto_next_sibling(&self->cursor)) { - self->ascending = false; - } else if (ts_tree_cursor_goto_parent(&self->cursor)) { - self->depth--; - } else { - did_move = false; - } - - // After leaving a node, remove any states that cannot make further progress. - uint32_t deleted_count = 0; - for (unsigned i = 0, n = self->states.size; i < n; i++) { - QueryState *state = &self->states.contents[i]; - QueryStep *step = &self->query->steps.contents[state->step_index]; - - // If a state completed its pattern inside of this node, but was deferred from finishing - // in order to search for longer matches, mark it as finished. - if (step->depth == PATTERN_DONE_MARKER) { - if (state->start_depth > self->depth || !did_move) { - LOG(" finish pattern %u\n", state->pattern_index); - state->id = self->next_state_id++; - array_push(&self->finished_states, *state); - deleted_count++; - continue; - } - } - - // If a state needed to match something within this node, then remove that state - // as it has failed to match. - else if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) { - LOG( - " failed to match. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - capture_list_pool_release( - &self->capture_list_pool, - state->capture_list_id - ); - deleted_count++; - continue; - } - - if (deleted_count > 0) { - self->states.contents[i - deleted_count] = *state; - } - } - self->states.size -= deleted_count; - - if (!did_move) { - return self->finished_states.size > 0; - } - } else { - // If this node is before the selected range, then avoid descending into it. - TSNode node = ts_tree_cursor_current_node(&self->cursor); - if ( - ts_node_end_byte(node) <= self->start_byte || - point_lte(ts_node_end_point(node), self->start_point) - ) { - if (!ts_tree_cursor_goto_next_sibling(&self->cursor)) { - self->ascending = true; - } - continue; - } - - // If this node is after the selected range, then stop walking. - if ( - self->end_byte <= ts_node_start_byte(node) || - point_lte(self->end_point, ts_node_start_point(node)) - ) return false; - - // Get the properties of the current node. - TSSymbol symbol = ts_node_symbol(node); - bool is_named = ts_node_is_named(node); - if (symbol != ts_builtin_sym_error && self->query->symbol_map) { - symbol = self->query->symbol_map[symbol]; - } - bool can_have_later_siblings; - bool can_have_later_siblings_with_this_field; - TSFieldId field_id = ts_tree_cursor_current_status( - &self->cursor, - &can_have_later_siblings, - &can_have_later_siblings_with_this_field - ); - LOG( - "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u\n", - ts_node_type(node), - ts_language_field_name_for_id(self->query->language, field_id), - ts_node_start_point(node).row, - self->states.size, - self->finished_states.size - ); - - // Add new states for any patterns whose root node is a wildcard. - for (unsigned i = 0; i < self->query->wildcard_root_pattern_count; i++) { - PatternEntry *pattern = &self->query->pattern_map.contents[i]; - QueryStep *step = &self->query->steps.contents[pattern->step_index]; - - // If this node matches the first step of the pattern, then add a new - // state at the start of this pattern. - if (step->field && field_id != step->field) continue; - if (!ts_query_cursor__add_state(self, pattern)) break; - } - - // Add new states for any patterns whose root node matches this node. - unsigned i; - if (ts_query__pattern_map_search(self->query, symbol, &i)) { - PatternEntry *pattern = &self->query->pattern_map.contents[i]; - QueryStep *step = &self->query->steps.contents[pattern->step_index]; - do { - // If this node matches the first step of the pattern, then add a new - // state at the start of this pattern. - if (step->field && field_id != step->field) continue; - if (!ts_query_cursor__add_state(self, pattern)) break; - - // Advance to the next pattern whose root node matches this node. - i++; - if (i == self->query->pattern_map.size) break; - pattern = &self->query->pattern_map.contents[i]; - step = &self->query->steps.contents[pattern->step_index]; - } while (step->symbol == symbol); - } - - // Update all of the in-progress states with current node. - for (unsigned i = 0, copy_count = 0; i < self->states.size; i += 1 + copy_count) { - QueryState *state = &self->states.contents[i]; - QueryStep *step = &self->query->steps.contents[state->step_index]; - state->has_in_progress_alternatives = false; - copy_count = 0; - - // Check that the node matches all of the criteria for the next - // step of the pattern. - if ((uint32_t)state->start_depth + (uint32_t)step->depth != self->depth) continue; - - // Determine if this node matches this step of the pattern, and also - // if this node can have later siblings that match this step of the - // pattern. - bool node_does_match = - step->symbol == symbol || - step->symbol == WILDCARD_SYMBOL || - (step->symbol == NAMED_WILDCARD_SYMBOL && is_named); - bool later_sibling_can_match = can_have_later_siblings; - if ((step->is_immediate && is_named) || state->seeking_immediate_match) { - later_sibling_can_match = false; - } - if (step->is_last_child && can_have_later_siblings) { - node_does_match = false; - } - if (step->field) { - if (step->field == field_id) { - if (!can_have_later_siblings_with_this_field) { - later_sibling_can_match = false; - } - } else { - node_does_match = false; - } - } - - // Remove states immediately if it is ever clear that they cannot match. - if (!node_does_match) { - if (!later_sibling_can_match) { - LOG( - " discard state. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - capture_list_pool_release( - &self->capture_list_pool, - state->capture_list_id - ); - array_erase(&self->states, i); - i--; - } - continue; - } - - // Some patterns can match their root node in multiple ways, capturing different - // children. If this pattern step could match later children within the same - // parent, then this query state cannot simply be updated in place. It must be - // split into two states: one that matches this node, and one which skips over - // this node, to preserve the possibility of matching later siblings. - if ( - later_sibling_can_match && - !step->is_pattern_start && - step->contains_captures - ) { - if (ts_query__cursor_copy_state(self, state)) { - LOG( - " split state for capture. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - copy_count++; - } - } - - // If the current node is captured in this pattern, add it to the capture list. - // For the first capture in a pattern, lazily acquire a capture list. - if (step->capture_ids[0] != NONE) { - if (state->capture_list_id == NONE) { - state->capture_list_id = capture_list_pool_acquire(&self->capture_list_pool); - - // If there are no capture lists left in the pool, then terminate whichever - // state has captured the earliest node in the document, and steal its - // capture list. - if (state->capture_list_id == NONE) { - uint32_t state_index, byte_offset, pattern_index; - if (ts_query_cursor__first_in_progress_capture( - self, - &state_index, - &byte_offset, - &pattern_index - )) { - LOG( - " abandon state. index:%u, pattern:%u, offset:%u.\n", - state_index, pattern_index, byte_offset - ); - state->capture_list_id = self->states.contents[state_index].capture_list_id; - array_erase(&self->states, state_index); - if (state_index < i) { - i--; - state--; - } - } else { - LOG(" too many finished states.\n"); - array_erase(&self->states, i); - i--; - continue; - } - } - } - - CaptureList *capture_list = capture_list_pool_get_mut( - &self->capture_list_pool, - state->capture_list_id - ); - for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) { - uint16_t capture_id = step->capture_ids[j]; - if (step->capture_ids[j] == NONE) break; - array_push(capture_list, ((TSQueryCapture) { node, capture_id })); - LOG( - " capture node. pattern:%u, capture_id:%u, capture_count:%u\n", - state->pattern_index, - capture_id, - capture_list->size - ); - } - } - - // Advance this state to the next step of its pattern. - state->step_index++; - state->seeking_immediate_match = false; - LOG( - " advance state. pattern:%u, step:%u\n", - state->pattern_index, - state->step_index - ); - - // If this state's next step has an 'alternative' step (the step is either optional, - // or is the end of a repetition), then copy the state in order to pursue both - // alternatives. The alternative step itself may have an alternative, so this is - // an interative process. - unsigned end_index = i + 1; - for (unsigned j = i; j < end_index; j++) { - QueryState *state = &self->states.contents[j]; - QueryStep *next_step = &self->query->steps.contents[state->step_index]; - if (next_step->alternative_index != NONE) { - if (next_step->is_dead_end) { - state->step_index = next_step->alternative_index; - j--; - continue; - } - - QueryState *copy = ts_query__cursor_copy_state(self, state); - if (next_step->is_pass_through) { - state->step_index++; - j--; - } - if (copy) { - copy_count++; - end_index++; - copy->step_index = next_step->alternative_index; - if (next_step->alternative_is_immediate) { - copy->seeking_immediate_match = true; - } - LOG( - " split state for branch. pattern:%u, step:%u, step:%u, immediate:%d\n", - copy->pattern_index, - state->step_index, - copy->step_index, - copy->seeking_immediate_match - ); - } - } - } - } - - for (unsigned i = 0; i < self->states.size; i++) { - QueryState *state = &self->states.contents[i]; - bool did_remove = false; - - // Enfore the longest-match criteria. When a query pattern contains optional or - // repeated nodes, this is necesssary to avoid multiple redundant states, where - // one state has a strict subset of another state's captures. - for (unsigned j = i + 1; j < self->states.size; j++) { - QueryState *other_state = &self->states.contents[j]; - if ( - state->pattern_index == other_state->pattern_index && - state->start_depth == other_state->start_depth - ) { - bool left_contains_right, right_contains_left; - ts_query_cursor__compare_captures( - self, - state, - other_state, - &left_contains_right, - &right_contains_left - ); - if (left_contains_right) { - if (state->step_index == other_state->step_index) { - LOG( - " drop shorter state. pattern: %u, step_index: %u\n", - state->pattern_index, - state->step_index - ); - capture_list_pool_release(&self->capture_list_pool, other_state->capture_list_id); - array_erase(&self->states, j); - j--; - continue; - } - other_state->has_in_progress_alternatives = true; - } - if (right_contains_left) { - if (state->step_index == other_state->step_index) { - LOG( - " drop shorter state. pattern: %u, step_index: %u\n", - state->pattern_index, - state->step_index - ); - capture_list_pool_release(&self->capture_list_pool, state->capture_list_id); - array_erase(&self->states, i); - did_remove = true; - break; - } - state->has_in_progress_alternatives = true; - } - } - } - - // If there the state is at the end of its pattern, remove it from the list - // of in-progress states and add it to the list of finished states. - if (!did_remove) { - QueryStep *next_step = &self->query->steps.contents[state->step_index]; - if (next_step->depth == PATTERN_DONE_MARKER) { - if (state->has_in_progress_alternatives) { - LOG(" defer finishing pattern %u\n", state->pattern_index); - } else { - LOG(" finish pattern %u\n", state->pattern_index); - state->id = self->next_state_id++; - array_push(&self->finished_states, *state); - array_erase(&self->states, state - self->states.contents); - i--; - } - } - } - } - - // Continue descending if possible. - if (ts_tree_cursor_goto_first_child(&self->cursor)) { - self->depth++; - } else { - self->ascending = true; - } - } - } while (self->finished_states.size == 0); - - return true; -} - -bool ts_query_cursor_next_match( - TSQueryCursor *self, - TSQueryMatch *match -) { - if (self->finished_states.size == 0) { - if (!ts_query_cursor__advance(self)) { - return false; - } - } - - QueryState *state = &self->finished_states.contents[0]; - match->id = state->id; - match->pattern_index = state->pattern_index; - const CaptureList *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - match->captures = captures->contents; - match->capture_count = captures->size; - capture_list_pool_release(&self->capture_list_pool, state->capture_list_id); - array_erase(&self->finished_states, 0); - return true; -} - -void ts_query_cursor_remove_match( - TSQueryCursor *self, - uint32_t match_id -) { - for (unsigned i = 0; i < self->finished_states.size; i++) { - const QueryState *state = &self->finished_states.contents[i]; - if (state->id == match_id) { - capture_list_pool_release( - &self->capture_list_pool, - state->capture_list_id - ); - array_erase(&self->finished_states, i); - return; - } - } -} - -bool ts_query_cursor_next_capture( - TSQueryCursor *self, - TSQueryMatch *match, - uint32_t *capture_index -) { - for (;;) { - // The goal here is to return captures in order, even though they may not - // be discovered in order, because patterns can overlap. If there are any - // finished patterns, then try to find one that contains a capture that - // is *definitely* before any capture in an *unfinished* pattern. - if (self->finished_states.size > 0) { - // First, identify the position of the earliest capture in an unfinished - // match. For a finished capture to be returned, it must be *before* - // this position. - uint32_t first_unfinished_capture_byte; - uint32_t first_unfinished_pattern_index; - uint32_t first_unfinished_state_index; - ts_query_cursor__first_in_progress_capture( - self, - &first_unfinished_state_index, - &first_unfinished_capture_byte, - &first_unfinished_pattern_index - ); - - // Find the earliest capture in a finished match. - int first_finished_state_index = -1; - uint32_t first_finished_capture_byte = first_unfinished_capture_byte; - uint32_t first_finished_pattern_index = first_unfinished_pattern_index; - for (unsigned i = 0; i < self->finished_states.size; i++) { - const QueryState *state = &self->finished_states.contents[i]; - const CaptureList *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - if (captures->size > state->consumed_capture_count) { - uint32_t capture_byte = ts_node_start_byte( - captures->contents[state->consumed_capture_count].node - ); - if ( - capture_byte < first_finished_capture_byte || - ( - capture_byte == first_finished_capture_byte && - state->pattern_index < first_finished_pattern_index - ) - ) { - first_finished_state_index = i; - first_finished_capture_byte = capture_byte; - first_finished_pattern_index = state->pattern_index; - } - } else { - capture_list_pool_release( - &self->capture_list_pool, - state->capture_list_id - ); - array_erase(&self->finished_states, i); - i--; - } - } - - // If there is finished capture that is clearly before any unfinished - // capture, then return its match, and its capture index. Internally - // record the fact that the capture has been 'consumed'. - if (first_finished_state_index != -1) { - QueryState *state = &self->finished_states.contents[ - first_finished_state_index - ]; - match->id = state->id; - match->pattern_index = state->pattern_index; - const CaptureList *captures = capture_list_pool_get( - &self->capture_list_pool, - state->capture_list_id - ); - match->captures = captures->contents; - match->capture_count = captures->size; - *capture_index = state->consumed_capture_count; - state->consumed_capture_count++; - return true; - } - - if (capture_list_pool_is_empty(&self->capture_list_pool)) { - LOG( - " abandon state. index:%u, pattern:%u, offset:%u.\n", - first_unfinished_state_index, - first_unfinished_pattern_index, - first_unfinished_capture_byte - ); - capture_list_pool_release( - &self->capture_list_pool, - self->states.contents[first_unfinished_state_index].capture_list_id - ); - array_erase(&self->states, first_unfinished_state_index); - } - } - - // If there are no finished matches that are ready to be returned, then - // continue finding more matches. - if (!ts_query_cursor__advance(self)) return false; - } -} - -#undef LOG diff --git a/src/tree_sitter/reduce_action.h b/src/tree_sitter/reduce_action.h deleted file mode 100644 index 72aff08d73..0000000000 --- a/src/tree_sitter/reduce_action.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef TREE_SITTER_REDUCE_ACTION_H_ -#define TREE_SITTER_REDUCE_ACTION_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./array.h" -#include "tree_sitter/api.h" - -typedef struct { - uint32_t count; - TSSymbol symbol; - int dynamic_precedence; - unsigned short production_id; -} ReduceAction; - -typedef Array(ReduceAction) ReduceActionSet; - -static inline void ts_reduce_action_set_add(ReduceActionSet *self, - ReduceAction new_action) { - for (uint32_t i = 0; i < self->size; i++) { - ReduceAction action = self->contents[i]; - if (action.symbol == new_action.symbol && action.count == new_action.count) - return; - } - array_push(self, new_action); -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_REDUCE_ACTION_H_ diff --git a/src/tree_sitter/reusable_node.h b/src/tree_sitter/reusable_node.h deleted file mode 100644 index 9cba951909..0000000000 --- a/src/tree_sitter/reusable_node.h +++ /dev/null @@ -1,88 +0,0 @@ -#include "./subtree.h" - -typedef struct { - Subtree tree; - uint32_t child_index; - uint32_t byte_offset; -} StackEntry; - -typedef struct { - Array(StackEntry) stack; - Subtree last_external_token; -} ReusableNode; - -static inline ReusableNode reusable_node_new(void) { - return (ReusableNode) {array_new(), NULL_SUBTREE}; -} - -static inline void reusable_node_clear(ReusableNode *self) { - array_clear(&self->stack); - self->last_external_token = NULL_SUBTREE; -} - -static inline void reusable_node_reset(ReusableNode *self, Subtree tree) { - reusable_node_clear(self); - array_push(&self->stack, ((StackEntry) { - .tree = tree, - .child_index = 0, - .byte_offset = 0, - })); -} - -static inline Subtree reusable_node_tree(ReusableNode *self) { - return self->stack.size > 0 - ? self->stack.contents[self->stack.size - 1].tree - : NULL_SUBTREE; -} - -static inline uint32_t reusable_node_byte_offset(ReusableNode *self) { - return self->stack.size > 0 - ? self->stack.contents[self->stack.size - 1].byte_offset - : UINT32_MAX; -} - -static inline void reusable_node_delete(ReusableNode *self) { - array_delete(&self->stack); -} - -static inline void reusable_node_advance(ReusableNode *self) { - StackEntry last_entry = *array_back(&self->stack); - uint32_t byte_offset = last_entry.byte_offset + ts_subtree_total_bytes(last_entry.tree); - if (ts_subtree_has_external_tokens(last_entry.tree)) { - self->last_external_token = ts_subtree_last_external_token(last_entry.tree); - } - - Subtree tree; - uint32_t next_index; - do { - StackEntry popped_entry = array_pop(&self->stack); - next_index = popped_entry.child_index + 1; - if (self->stack.size == 0) return; - tree = array_back(&self->stack)->tree; - } while (ts_subtree_child_count(tree) <= next_index); - - array_push(&self->stack, ((StackEntry) { - .tree = tree.ptr->children[next_index], - .child_index = next_index, - .byte_offset = byte_offset, - })); -} - -static inline bool reusable_node_descend(ReusableNode *self) { - StackEntry last_entry = *array_back(&self->stack); - if (ts_subtree_child_count(last_entry.tree) > 0) { - array_push(&self->stack, ((StackEntry) { - .tree = last_entry.tree.ptr->children[0], - .child_index = 0, - .byte_offset = last_entry.byte_offset, - })); - return true; - } else { - return false; - } -} - -static inline void reusable_node_advance_past_leaf(ReusableNode *self) { - while (reusable_node_descend(self)) {} - reusable_node_advance(self); -} diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c deleted file mode 100644 index 6ceee2577f..0000000000 --- a/src/tree_sitter/stack.c +++ /dev/null @@ -1,848 +0,0 @@ -#include "./alloc.h" -#include "./language.h" -#include "./subtree.h" -#include "./array.h" -#include "./stack.h" -#include "./length.h" -#include <assert.h> -#include <stdio.h> - -#define MAX_LINK_COUNT 8 -#define MAX_NODE_POOL_SIZE 50 -#define MAX_ITERATOR_COUNT 64 - -#if defined _WIN32 && !defined __GNUC__ -#define inline __forceinline -#else -#define inline static inline __attribute__((always_inline)) -#endif - -typedef struct StackNode StackNode; - -typedef struct { - StackNode *node; - Subtree subtree; - bool is_pending; -} StackLink; - -struct StackNode { - TSStateId state; - Length position; - StackLink links[MAX_LINK_COUNT]; - short unsigned int link_count; - uint32_t ref_count; - unsigned error_cost; - unsigned node_count; - int dynamic_precedence; -}; - -typedef struct { - StackNode *node; - SubtreeArray subtrees; - uint32_t subtree_count; - bool is_pending; -} StackIterator; - -typedef struct { - void *payload; - StackIterateCallback callback; -} StackIterateSession; - -typedef Array(StackNode *) StackNodeArray; - -typedef enum { - StackStatusActive, - StackStatusPaused, - StackStatusHalted, -} StackStatus; - -typedef struct { - StackNode *node; - Subtree last_external_token; - StackSummary *summary; - unsigned node_count_at_last_error; - TSSymbol lookahead_when_paused; - StackStatus status; -} StackHead; - -struct Stack { - Array(StackHead) heads; - StackSliceArray slices; - Array(StackIterator) iterators; - StackNodeArray node_pool; - StackNode *base_node; - SubtreePool *subtree_pool; -}; - -typedef unsigned StackAction; -enum { - StackActionNone, - StackActionStop = 1, - StackActionPop = 2, -}; - -typedef StackAction (*StackCallback)(void *, const StackIterator *); - -static void stack_node_retain(StackNode *self) { - if (!self) - return; - assert(self->ref_count > 0); - self->ref_count++; - assert(self->ref_count != 0); -} - -static void stack_node_release(StackNode *self, StackNodeArray *pool, SubtreePool *subtree_pool) { -recur: - assert(self->ref_count != 0); - self->ref_count--; - if (self->ref_count > 0) return; - - StackNode *first_predecessor = NULL; - if (self->link_count > 0) { - for (unsigned i = self->link_count - 1; i > 0; i--) { - StackLink link = self->links[i]; - if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree); - stack_node_release(link.node, pool, subtree_pool); - } - StackLink link = self->links[0]; - if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree); - first_predecessor = self->links[0].node; - } - - if (pool->size < MAX_NODE_POOL_SIZE) { - array_push(pool, self); - } else { - ts_free(self); - } - - if (first_predecessor) { - self = first_predecessor; - goto recur; - } -} - -static StackNode *stack_node_new(StackNode *previous_node, Subtree subtree, - bool is_pending, TSStateId state, StackNodeArray *pool) { - StackNode *node = pool->size > 0 ? - array_pop(pool) : - ts_malloc(sizeof(StackNode)); - *node = (StackNode){.ref_count = 1, .link_count = 0, .state = state}; - - if (previous_node) { - node->link_count = 1; - node->links[0] = (StackLink){ - .node = previous_node, - .subtree = subtree, - .is_pending = is_pending, - }; - - node->position = previous_node->position; - node->error_cost = previous_node->error_cost; - node->dynamic_precedence = previous_node->dynamic_precedence; - node->node_count = previous_node->node_count; - - if (subtree.ptr) { - node->error_cost += ts_subtree_error_cost(subtree); - node->position = length_add(node->position, ts_subtree_total_size(subtree)); - node->node_count += ts_subtree_node_count(subtree); - node->dynamic_precedence += ts_subtree_dynamic_precedence(subtree); - } - } else { - node->position = length_zero(); - node->error_cost = 0; - } - - return node; -} - -static bool stack__subtree_is_equivalent(Subtree left, Subtree right) { - return - left.ptr == right.ptr || - (left.ptr && right.ptr && - ts_subtree_symbol(left) == ts_subtree_symbol(right) && - ((ts_subtree_error_cost(left) > 0 && ts_subtree_error_cost(right) > 0) || - (ts_subtree_padding(left).bytes == ts_subtree_padding(right).bytes && - ts_subtree_size(left).bytes == ts_subtree_size(right).bytes && - ts_subtree_child_count(left) == ts_subtree_child_count(right) && - ts_subtree_extra(left) == ts_subtree_extra(right) && - ts_subtree_external_scanner_state_eq(left, right)))); -} - -static void stack_node_add_link(StackNode *self, StackLink link, SubtreePool *subtree_pool) { - if (link.node == self) return; - - for (int i = 0; i < self->link_count; i++) { - StackLink *existing_link = &self->links[i]; - if (stack__subtree_is_equivalent(existing_link->subtree, link.subtree)) { - // In general, we preserve ambiguities until they are removed from the stack - // during a pop operation where multiple paths lead to the same node. But in - // the special case where two links directly connect the same pair of nodes, - // we can safely remove the ambiguity ahead of time without changing behavior. - if (existing_link->node == link.node) { - if ( - ts_subtree_dynamic_precedence(link.subtree) > - ts_subtree_dynamic_precedence(existing_link->subtree) - ) { - ts_subtree_retain(link.subtree); - ts_subtree_release(subtree_pool, existing_link->subtree); - existing_link->subtree = link.subtree; - self->dynamic_precedence = - link.node->dynamic_precedence + ts_subtree_dynamic_precedence(link.subtree); - } - return; - } - - // If the previous nodes are mergeable, merge them recursively. - if (existing_link->node->state == link.node->state && - existing_link->node->position.bytes == link.node->position.bytes) { - for (int j = 0; j < link.node->link_count; j++) { - stack_node_add_link(existing_link->node, link.node->links[j], subtree_pool); - } - int32_t dynamic_precedence = link.node->dynamic_precedence; - if (link.subtree.ptr) { - dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree); - } - if (dynamic_precedence > self->dynamic_precedence) { - self->dynamic_precedence = dynamic_precedence; - } - return; - } - } - } - - if (self->link_count == MAX_LINK_COUNT) return; - - stack_node_retain(link.node); - unsigned node_count = link.node->node_count; - int dynamic_precedence = link.node->dynamic_precedence; - self->links[self->link_count++] = link; - - if (link.subtree.ptr) { - ts_subtree_retain(link.subtree); - node_count += ts_subtree_node_count(link.subtree); - dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree); - } - - if (node_count > self->node_count) self->node_count = node_count; - if (dynamic_precedence > self->dynamic_precedence) self->dynamic_precedence = dynamic_precedence; -} - -static void stack_head_delete(StackHead *self, StackNodeArray *pool, SubtreePool *subtree_pool) { - if (self->node) { - if (self->last_external_token.ptr) { - ts_subtree_release(subtree_pool, self->last_external_token); - } - if (self->summary) { - array_delete(self->summary); - ts_free(self->summary); - } - stack_node_release(self->node, pool, subtree_pool); - } -} - -static StackVersion ts_stack__add_version(Stack *self, StackVersion original_version, - StackNode *node) { - StackHead head = { - .node = node, - .node_count_at_last_error = self->heads.contents[original_version].node_count_at_last_error, - .last_external_token = self->heads.contents[original_version].last_external_token, - .status = StackStatusActive, - .lookahead_when_paused = 0, - }; - array_push(&self->heads, head); - stack_node_retain(node); - if (head.last_external_token.ptr) ts_subtree_retain(head.last_external_token); - return (StackVersion)(self->heads.size - 1); -} - -static void ts_stack__add_slice(Stack *self, StackVersion original_version, - StackNode *node, SubtreeArray *subtrees) { - for (uint32_t i = self->slices.size - 1; i + 1 > 0; i--) { - StackVersion version = self->slices.contents[i].version; - if (self->heads.contents[version].node == node) { - StackSlice slice = {*subtrees, version}; - array_insert(&self->slices, i + 1, slice); - return; - } - } - - StackVersion version = ts_stack__add_version(self, original_version, node); - StackSlice slice = { *subtrees, version }; - array_push(&self->slices, slice); -} - -inline StackSliceArray stack__iter(Stack *self, StackVersion version, - StackCallback callback, void *payload, - int goal_subtree_count) { - array_clear(&self->slices); - array_clear(&self->iterators); - - StackHead *head = array_get(&self->heads, version); - StackIterator iterator = { - .node = head->node, - .subtrees = array_new(), - .subtree_count = 0, - .is_pending = true, - }; - - bool include_subtrees = false; - if (goal_subtree_count >= 0) { - include_subtrees = true; - array_reserve(&iterator.subtrees, goal_subtree_count); - } - - array_push(&self->iterators, iterator); - - while (self->iterators.size > 0) { - for (uint32_t i = 0, size = self->iterators.size; i < size; i++) { - StackIterator *iterator = &self->iterators.contents[i]; - StackNode *node = iterator->node; - - StackAction action = callback(payload, iterator); - bool should_pop = action & StackActionPop; - bool should_stop = action & StackActionStop || node->link_count == 0; - - if (should_pop) { - SubtreeArray subtrees = iterator->subtrees; - if (!should_stop) - ts_subtree_array_copy(subtrees, &subtrees); - ts_subtree_array_reverse(&subtrees); - ts_stack__add_slice( - self, - version, - node, - &subtrees - ); - } - - if (should_stop) { - if (!should_pop) - ts_subtree_array_delete(self->subtree_pool, &iterator->subtrees); - array_erase(&self->iterators, i); - i--, size--; - continue; - } - - for (uint32_t j = 1; j <= node->link_count; j++) { - StackIterator *next_iterator; - StackLink link; - if (j == node->link_count) { - link = node->links[0]; - next_iterator = &self->iterators.contents[i]; - } else { - if (self->iterators.size >= MAX_ITERATOR_COUNT) continue; - link = node->links[j]; - StackIterator current_iterator = self->iterators.contents[i]; - array_push(&self->iterators, current_iterator); - next_iterator = array_back(&self->iterators); - ts_subtree_array_copy(next_iterator->subtrees, &next_iterator->subtrees); - } - - next_iterator->node = link.node; - if (link.subtree.ptr) { - if (include_subtrees) { - array_push(&next_iterator->subtrees, link.subtree); - ts_subtree_retain(link.subtree); - } - - if (!ts_subtree_extra(link.subtree)) { - next_iterator->subtree_count++; - if (!link.is_pending) { - next_iterator->is_pending = false; - } - } - } else { - next_iterator->subtree_count++; - next_iterator->is_pending = false; - } - } - } - } - - return self->slices; -} - -Stack *ts_stack_new(SubtreePool *subtree_pool) { - Stack *self = ts_calloc(1, sizeof(Stack)); - - array_init(&self->heads); - array_init(&self->slices); - array_init(&self->iterators); - array_init(&self->node_pool); - array_reserve(&self->heads, 4); - array_reserve(&self->slices, 4); - array_reserve(&self->iterators, 4); - array_reserve(&self->node_pool, MAX_NODE_POOL_SIZE); - - self->subtree_pool = subtree_pool; - self->base_node = stack_node_new(NULL, NULL_SUBTREE, false, 1, &self->node_pool); - ts_stack_clear(self); - - return self; -} - -void ts_stack_delete(Stack *self) { - if (self->slices.contents) - array_delete(&self->slices); - if (self->iterators.contents) - array_delete(&self->iterators); - stack_node_release(self->base_node, &self->node_pool, self->subtree_pool); - for (uint32_t i = 0; i < self->heads.size; i++) { - stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool); - } - array_clear(&self->heads); - if (self->node_pool.contents) { - for (uint32_t i = 0; i < self->node_pool.size; i++) - ts_free(self->node_pool.contents[i]); - array_delete(&self->node_pool); - } - array_delete(&self->heads); - ts_free(self); -} - -uint32_t ts_stack_version_count(const Stack *self) { - return self->heads.size; -} - -TSStateId ts_stack_state(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->node->state; -} - -Length ts_stack_position(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->node->position; -} - -Subtree ts_stack_last_external_token(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->last_external_token; -} - -void ts_stack_set_last_external_token(Stack *self, StackVersion version, Subtree token) { - StackHead *head = array_get(&self->heads, version); - if (token.ptr) ts_subtree_retain(token); - if (head->last_external_token.ptr) ts_subtree_release(self->subtree_pool, head->last_external_token); - head->last_external_token = token; -} - -unsigned ts_stack_error_cost(const Stack *self, StackVersion version) { - StackHead *head = array_get(&self->heads, version); - unsigned result = head->node->error_cost; - if ( - head->status == StackStatusPaused || - (head->node->state == ERROR_STATE && !head->node->links[0].subtree.ptr)) { - result += ERROR_COST_PER_RECOVERY; - } - return result; -} - -unsigned ts_stack_node_count_since_error(const Stack *self, StackVersion version) { - StackHead *head = array_get(&self->heads, version); - if (head->node->node_count < head->node_count_at_last_error) { - head->node_count_at_last_error = head->node->node_count; - } - return head->node->node_count - head->node_count_at_last_error; -} - -void ts_stack_push(Stack *self, StackVersion version, Subtree subtree, - bool pending, TSStateId state) { - StackHead *head = array_get(&self->heads, version); - StackNode *new_node = stack_node_new(head->node, subtree, pending, state, &self->node_pool); - if (!subtree.ptr) head->node_count_at_last_error = new_node->node_count; - head->node = new_node; -} - -inline StackAction iterate_callback(void *payload, const StackIterator *iterator) { - StackIterateSession *session = payload; - session->callback( - session->payload, - iterator->node->state, - iterator->subtree_count - ); - return StackActionNone; -} - -void ts_stack_iterate(Stack *self, StackVersion version, - StackIterateCallback callback, void *payload) { - StackIterateSession session = {payload, callback}; - stack__iter(self, version, iterate_callback, &session, -1); -} - -inline StackAction pop_count_callback(void *payload, const StackIterator *iterator) { - unsigned *goal_subtree_count = payload; - if (iterator->subtree_count == *goal_subtree_count) { - return StackActionPop | StackActionStop; - } else { - return StackActionNone; - } -} - -StackSliceArray ts_stack_pop_count(Stack *self, StackVersion version, uint32_t count) { - return stack__iter(self, version, pop_count_callback, &count, count); -} - -inline StackAction pop_pending_callback(void *payload, const StackIterator *iterator) { - (void)payload; - if (iterator->subtree_count >= 1) { - if (iterator->is_pending) { - return StackActionPop | StackActionStop; - } else { - return StackActionStop; - } - } else { - return StackActionNone; - } -} - -StackSliceArray ts_stack_pop_pending(Stack *self, StackVersion version) { - StackSliceArray pop = stack__iter(self, version, pop_pending_callback, NULL, 0); - if (pop.size > 0) { - ts_stack_renumber_version(self, pop.contents[0].version, version); - pop.contents[0].version = version; - } - return pop; -} - -inline StackAction pop_error_callback(void *payload, const StackIterator *iterator) { - if (iterator->subtrees.size > 0) { - bool *found_error = payload; - if (!*found_error && ts_subtree_is_error(iterator->subtrees.contents[0])) { - *found_error = true; - return StackActionPop | StackActionStop; - } else { - return StackActionStop; - } - } else { - return StackActionNone; - } -} - -SubtreeArray ts_stack_pop_error(Stack *self, StackVersion version) { - StackNode *node = array_get(&self->heads, version)->node; - for (unsigned i = 0; i < node->link_count; i++) { - if (node->links[i].subtree.ptr && ts_subtree_is_error(node->links[i].subtree)) { - bool found_error = false; - StackSliceArray pop = stack__iter(self, version, pop_error_callback, &found_error, 1); - if (pop.size > 0) { - assert(pop.size == 1); - ts_stack_renumber_version(self, pop.contents[0].version, version); - return pop.contents[0].subtrees; - } - break; - } - } - return (SubtreeArray){.size = 0}; -} - -inline StackAction pop_all_callback(void *payload, const StackIterator *iterator) { - (void)payload; - return iterator->node->link_count == 0 ? StackActionPop : StackActionNone; -} - -StackSliceArray ts_stack_pop_all(Stack *self, StackVersion version) { - return stack__iter(self, version, pop_all_callback, NULL, 0); -} - -typedef struct { - StackSummary *summary; - unsigned max_depth; -} SummarizeStackSession; - -inline StackAction summarize_stack_callback(void *payload, const StackIterator *iterator) { - SummarizeStackSession *session = payload; - TSStateId state = iterator->node->state; - unsigned depth = iterator->subtree_count; - if (depth > session->max_depth) return StackActionStop; - for (unsigned i = session->summary->size - 1; i + 1 > 0; i--) { - StackSummaryEntry entry = session->summary->contents[i]; - if (entry.depth < depth) break; - if (entry.depth == depth && entry.state == state) return StackActionNone; - } - array_push(session->summary, ((StackSummaryEntry){ - .position = iterator->node->position, - .depth = depth, - .state = state, - })); - return StackActionNone; -} - -void ts_stack_record_summary(Stack *self, StackVersion version, unsigned max_depth) { - SummarizeStackSession session = { - .summary = ts_malloc(sizeof(StackSummary)), - .max_depth = max_depth - }; - array_init(session.summary); - stack__iter(self, version, summarize_stack_callback, &session, -1); - self->heads.contents[version].summary = session.summary; -} - -StackSummary *ts_stack_get_summary(Stack *self, StackVersion version) { - return array_get(&self->heads, version)->summary; -} - -int ts_stack_dynamic_precedence(Stack *self, StackVersion version) { - return array_get(&self->heads, version)->node->dynamic_precedence; -} - -bool ts_stack_has_advanced_since_error(const Stack *self, StackVersion version) { - const StackHead *head = array_get(&self->heads, version); - const StackNode *node = head->node; - if (node->error_cost == 0) return true; - while (node) { - if (node->link_count > 0) { - Subtree subtree = node->links[0].subtree; - if (subtree.ptr) { - if (ts_subtree_total_bytes(subtree) > 0) { - return true; - } else if ( - node->node_count > head->node_count_at_last_error && - ts_subtree_error_cost(subtree) == 0 - ) { - node = node->links[0].node; - continue; - } - } - } - break; - } - return false; -} - -void ts_stack_remove_version(Stack *self, StackVersion version) { - stack_head_delete(array_get(&self->heads, version), &self->node_pool, self->subtree_pool); - array_erase(&self->heads, version); -} - -void ts_stack_renumber_version(Stack *self, StackVersion v1, StackVersion v2) { - if (v1 == v2) return; - assert(v2 < v1); - assert((uint32_t)v1 < self->heads.size); - StackHead *source_head = &self->heads.contents[v1]; - StackHead *target_head = &self->heads.contents[v2]; - if (target_head->summary && !source_head->summary) { - source_head->summary = target_head->summary; - target_head->summary = NULL; - } - stack_head_delete(target_head, &self->node_pool, self->subtree_pool); - *target_head = *source_head; - array_erase(&self->heads, v1); -} - -void ts_stack_swap_versions(Stack *self, StackVersion v1, StackVersion v2) { - StackHead temporary_head = self->heads.contents[v1]; - self->heads.contents[v1] = self->heads.contents[v2]; - self->heads.contents[v2] = temporary_head; -} - -StackVersion ts_stack_copy_version(Stack *self, StackVersion version) { - assert(version < self->heads.size); - array_push(&self->heads, self->heads.contents[version]); - StackHead *head = array_back(&self->heads); - stack_node_retain(head->node); - if (head->last_external_token.ptr) ts_subtree_retain(head->last_external_token); - head->summary = NULL; - return self->heads.size - 1; -} - -bool ts_stack_merge(Stack *self, StackVersion version1, StackVersion version2) { - if (!ts_stack_can_merge(self, version1, version2)) return false; - StackHead *head1 = &self->heads.contents[version1]; - StackHead *head2 = &self->heads.contents[version2]; - for (uint32_t i = 0; i < head2->node->link_count; i++) { - stack_node_add_link(head1->node, head2->node->links[i], self->subtree_pool); - } - if (head1->node->state == ERROR_STATE) { - head1->node_count_at_last_error = head1->node->node_count; - } - ts_stack_remove_version(self, version2); - return true; -} - -bool ts_stack_can_merge(Stack *self, StackVersion version1, StackVersion version2) { - StackHead *head1 = &self->heads.contents[version1]; - StackHead *head2 = &self->heads.contents[version2]; - return - head1->status == StackStatusActive && - head2->status == StackStatusActive && - head1->node->state == head2->node->state && - head1->node->position.bytes == head2->node->position.bytes && - head1->node->error_cost == head2->node->error_cost && - ts_subtree_external_scanner_state_eq(head1->last_external_token, head2->last_external_token); -} - -void ts_stack_halt(Stack *self, StackVersion version) { - array_get(&self->heads, version)->status = StackStatusHalted; -} - -void ts_stack_pause(Stack *self, StackVersion version, TSSymbol lookahead) { - StackHead *head = array_get(&self->heads, version); - head->status = StackStatusPaused; - head->lookahead_when_paused = lookahead; - head->node_count_at_last_error = head->node->node_count; -} - -bool ts_stack_is_active(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->status == StackStatusActive; -} - -bool ts_stack_is_halted(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->status == StackStatusHalted; -} - -bool ts_stack_is_paused(const Stack *self, StackVersion version) { - return array_get(&self->heads, version)->status == StackStatusPaused; -} - -TSSymbol ts_stack_resume(Stack *self, StackVersion version) { - StackHead *head = array_get(&self->heads, version); - assert(head->status == StackStatusPaused); - TSSymbol result = head->lookahead_when_paused; - head->status = StackStatusActive; - head->lookahead_when_paused = 0; - return result; -} - -void ts_stack_clear(Stack *self) { - stack_node_retain(self->base_node); - for (uint32_t i = 0; i < self->heads.size; i++) { - stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool); - } - array_clear(&self->heads); - array_push(&self->heads, ((StackHead){ - .node = self->base_node, - .last_external_token = NULL_SUBTREE, - .status = StackStatusActive, - .lookahead_when_paused = 0, - })); -} - -bool ts_stack_print_dot_graph(Stack *self, const TSLanguage *language, FILE *f) { - array_reserve(&self->iterators, 32); - bool was_recording_allocations = ts_toggle_allocation_recording(false); - if (!f) f = stderr; - - fprintf(f, "digraph stack {\n"); - fprintf(f, "rankdir=\"RL\";\n"); - fprintf(f, "edge [arrowhead=none]\n"); - - Array(StackNode *) visited_nodes = array_new(); - - array_clear(&self->iterators); - for (uint32_t i = 0; i < self->heads.size; i++) { - StackHead *head = &self->heads.contents[i]; - if (head->status == StackStatusHalted) continue; - - fprintf(f, "node_head_%u [shape=none, label=\"\"]\n", i); - fprintf(f, "node_head_%u -> node_%p [", i, head->node); - - if (head->status == StackStatusPaused) { - fprintf(f, "color=red "); - } - fprintf(f, - "label=%u, fontcolor=blue, weight=10000, labeltooltip=\"node_count: %u\nerror_cost: %u", - i, - ts_stack_node_count_since_error(self, i), - ts_stack_error_cost(self, i) - ); - - if (head->last_external_token.ptr) { - const ExternalScannerState *state = &head->last_external_token.ptr->external_scanner_state; - const char *data = ts_external_scanner_state_data(state); - fprintf(f, "\nexternal_scanner_state:"); - for (uint32_t j = 0; j < state->length; j++) fprintf(f, " %2X", data[j]); - } - - fprintf(f, "\"]\n"); - array_push(&self->iterators, ((StackIterator){.node = head->node })); - } - - bool all_iterators_done = false; - while (!all_iterators_done) { - all_iterators_done = true; - - for (uint32_t i = 0; i < self->iterators.size; i++) { - StackIterator iterator = self->iterators.contents[i]; - StackNode *node = iterator.node; - - for (uint32_t j = 0; j < visited_nodes.size; j++) { - if (visited_nodes.contents[j] == node) { - node = NULL; - break; - } - } - - if (!node) continue; - all_iterators_done = false; - - fprintf(f, "node_%p [", node); - if (node->state == ERROR_STATE) { - fprintf(f, "label=\"?\""); - } else if ( - node->link_count == 1 && - node->links[0].subtree.ptr && - ts_subtree_extra(node->links[0].subtree) - ) { - fprintf(f, "shape=point margin=0 label=\"\""); - } else { - fprintf(f, "label=\"%d\"", node->state); - } - - fprintf( - f, - " tooltip=\"position: %u,%u\nnode_count:%u\nerror_cost: %u\ndynamic_precedence: %d\"];\n", - node->position.extent.row + 1, - node->position.extent.column, - node->node_count, - node->error_cost, - node->dynamic_precedence - ); - - for (int j = 0; j < node->link_count; j++) { - StackLink link = node->links[j]; - fprintf(f, "node_%p -> node_%p [", node, link.node); - if (link.is_pending) fprintf(f, "style=dashed "); - if (link.subtree.ptr && ts_subtree_extra(link.subtree)) fprintf(f, "fontcolor=gray "); - - if (!link.subtree.ptr) { - fprintf(f, "color=red"); - } else { - fprintf(f, "label=\""); - bool quoted = ts_subtree_visible(link.subtree) && !ts_subtree_named(link.subtree); - if (quoted) fprintf(f, "'"); - const char *name = ts_language_symbol_name(language, ts_subtree_symbol(link.subtree)); - for (const char *c = name; *c; c++) { - if (*c == '\"' || *c == '\\') fprintf(f, "\\"); - fprintf(f, "%c", *c); - } - if (quoted) fprintf(f, "'"); - fprintf(f, "\""); - fprintf( - f, - "labeltooltip=\"error_cost: %u\ndynamic_precedence: %u\"", - ts_subtree_error_cost(link.subtree), - ts_subtree_dynamic_precedence(link.subtree) - ); - } - - fprintf(f, "];\n"); - - StackIterator *next_iterator; - if (j == 0) { - next_iterator = &self->iterators.contents[i]; - } else { - array_push(&self->iterators, iterator); - next_iterator = array_back(&self->iterators); - } - next_iterator->node = link.node; - } - - array_push(&visited_nodes, node); - } - } - - fprintf(f, "}\n"); - - array_delete(&visited_nodes); - ts_toggle_allocation_recording(was_recording_allocations); - return true; -} - -#undef inline diff --git a/src/tree_sitter/stack.h b/src/tree_sitter/stack.h deleted file mode 100644 index ec7a69d2b4..0000000000 --- a/src/tree_sitter/stack.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef TREE_SITTER_PARSE_STACK_H_ -#define TREE_SITTER_PARSE_STACK_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "./array.h" -#include "./subtree.h" -#include "./error_costs.h" -#include <stdio.h> - -typedef struct Stack Stack; - -typedef unsigned StackVersion; -#define STACK_VERSION_NONE ((StackVersion)-1) - -typedef struct { - SubtreeArray subtrees; - StackVersion version; -} StackSlice; -typedef Array(StackSlice) StackSliceArray; - -typedef struct { - Length position; - unsigned depth; - TSStateId state; -} StackSummaryEntry; -typedef Array(StackSummaryEntry) StackSummary; - -// Create a stack. -Stack *ts_stack_new(SubtreePool *); - -// Release the memory reserved for a given stack. -void ts_stack_delete(Stack *); - -// Get the stack's current number of versions. -uint32_t ts_stack_version_count(const Stack *); - -// Get the state at the top of the given version of the stack. If the stack is -// empty, this returns the initial state, 0. -TSStateId ts_stack_state(const Stack *, StackVersion); - -// Get the last external token associated with a given version of the stack. -Subtree ts_stack_last_external_token(const Stack *, StackVersion); - -// Set the last external token associated with a given version of the stack. -void ts_stack_set_last_external_token(Stack *, StackVersion, Subtree ); - -// Get the position of the given version of the stack within the document. -Length ts_stack_position(const Stack *, StackVersion); - -// Push a tree and state onto the given version of the stack. -// -// This transfers ownership of the tree to the Stack. Callers that -// need to retain ownership of the tree for their own purposes should -// first retain the tree. -void ts_stack_push(Stack *, StackVersion, Subtree , bool, TSStateId); - -// Pop the given number of entries from the given version of the stack. This -// operation can increase the number of stack versions by revealing multiple -// versions which had previously been merged. It returns an array that -// specifies the index of each revealed version and the trees that were -// removed from that version. -StackSliceArray ts_stack_pop_count(Stack *, StackVersion, uint32_t count); - -// Remove an error at the top of the given version of the stack. -SubtreeArray ts_stack_pop_error(Stack *, StackVersion); - -// Remove any pending trees from the top of the given version of the stack. -StackSliceArray ts_stack_pop_pending(Stack *, StackVersion); - -// Remove any all trees from the given version of the stack. -StackSliceArray ts_stack_pop_all(Stack *, StackVersion); - -// Get the maximum number of tree nodes reachable from this version of the stack -// since the last error was detected. -unsigned ts_stack_node_count_since_error(const Stack *, StackVersion); - -int ts_stack_dynamic_precedence(Stack *, StackVersion); - -bool ts_stack_has_advanced_since_error(const Stack *, StackVersion); - -// Compute a summary of all the parse states near the top of the given -// version of the stack and store the summary for later retrieval. -void ts_stack_record_summary(Stack *, StackVersion, unsigned max_depth); - -// Retrieve a summary of all the parse states near the top of the -// given version of the stack. -StackSummary *ts_stack_get_summary(Stack *, StackVersion); - -// Get the total cost of all errors on the given version of the stack. -unsigned ts_stack_error_cost(const Stack *, StackVersion version); - -// Merge the given two stack versions if possible, returning true -// if they were successfully merged and false otherwise. -bool ts_stack_merge(Stack *, StackVersion, StackVersion); - -// Determine whether the given two stack versions can be merged. -bool ts_stack_can_merge(Stack *, StackVersion, StackVersion); - -TSSymbol ts_stack_resume(Stack *, StackVersion); - -void ts_stack_pause(Stack *, StackVersion, TSSymbol); - -void ts_stack_halt(Stack *, StackVersion); - -bool ts_stack_is_active(const Stack *, StackVersion); - -bool ts_stack_is_paused(const Stack *, StackVersion); - -bool ts_stack_is_halted(const Stack *, StackVersion); - -void ts_stack_renumber_version(Stack *, StackVersion, StackVersion); - -void ts_stack_swap_versions(Stack *, StackVersion, StackVersion); - -StackVersion ts_stack_copy_version(Stack *, StackVersion); - -// Remove the given version from the stack. -void ts_stack_remove_version(Stack *, StackVersion); - -void ts_stack_clear(Stack *); - -bool ts_stack_print_dot_graph(Stack *, const TSLanguage *, FILE *); - -typedef void (*StackIterateCallback)(void *, TSStateId, uint32_t); - -void ts_stack_iterate(Stack *, StackVersion, StackIterateCallback, void *); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_PARSE_STACK_H_ diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c deleted file mode 100644 index ef92a32fe4..0000000000 --- a/src/tree_sitter/subtree.c +++ /dev/null @@ -1,982 +0,0 @@ -#include <assert.h> -#include <ctype.h> -#include <limits.h> -#include <stdbool.h> -#include <string.h> -#include <stdio.h> -#include "./alloc.h" -#include "./atomic.h" -#include "./subtree.h" -#include "./length.h" -#include "./language.h" -#include "./error_costs.h" -#include <stddef.h> - -typedef struct { - Length start; - Length old_end; - Length new_end; -} Edit; - -#define TS_MAX_INLINE_TREE_LENGTH UINT8_MAX -#define TS_MAX_TREE_POOL_SIZE 32 - -static const ExternalScannerState empty_state = {{.short_data = {0}}, .length = 0}; - -// ExternalScannerState - -void ts_external_scanner_state_init(ExternalScannerState *self, const char *data, unsigned length) { - self->length = length; - if (length > sizeof(self->short_data)) { - self->long_data = ts_malloc(length); - memcpy(self->long_data, data, length); - } else { - memcpy(self->short_data, data, length); - } -} - -ExternalScannerState ts_external_scanner_state_copy(const ExternalScannerState *self) { - ExternalScannerState result = *self; - if (self->length > sizeof(self->short_data)) { - result.long_data = ts_malloc(self->length); - memcpy(result.long_data, self->long_data, self->length); - } - return result; -} - -void ts_external_scanner_state_delete(ExternalScannerState *self) { - if (self->length > sizeof(self->short_data)) { - ts_free(self->long_data); - } -} - -const char *ts_external_scanner_state_data(const ExternalScannerState *self) { - if (self->length > sizeof(self->short_data)) { - return self->long_data; - } else { - return self->short_data; - } -} - -bool ts_external_scanner_state_eq(const ExternalScannerState *a, const ExternalScannerState *b) { - return a == b || ( - a->length == b->length && - !memcmp(ts_external_scanner_state_data(a), ts_external_scanner_state_data(b), a->length) - ); -} - -// SubtreeArray - -void ts_subtree_array_copy(SubtreeArray self, SubtreeArray *dest) { - dest->size = self.size; - dest->capacity = self.capacity; - dest->contents = self.contents; - if (self.capacity > 0) { - dest->contents = ts_calloc(self.capacity, sizeof(Subtree)); - memcpy(dest->contents, self.contents, self.size * sizeof(Subtree)); - for (uint32_t i = 0; i < self.size; i++) { - ts_subtree_retain(dest->contents[i]); - } - } -} - -void ts_subtree_array_delete(SubtreePool *pool, SubtreeArray *self) { - for (uint32_t i = 0; i < self->size; i++) { - ts_subtree_release(pool, self->contents[i]); - } - array_delete(self); -} - -SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *self) { - SubtreeArray result = array_new(); - - uint32_t i = self->size - 1; - for (; i + 1 > 0; i--) { - Subtree child = self->contents[i]; - if (!ts_subtree_extra(child)) break; - array_push(&result, child); - } - - self->size = i + 1; - ts_subtree_array_reverse(&result); - return result; -} - -void ts_subtree_array_reverse(SubtreeArray *self) { - for (uint32_t i = 0, limit = self->size / 2; i < limit; i++) { - size_t reverse_index = self->size - 1 - i; - Subtree swap = self->contents[i]; - self->contents[i] = self->contents[reverse_index]; - self->contents[reverse_index] = swap; - } -} - -// SubtreePool - -SubtreePool ts_subtree_pool_new(uint32_t capacity) { - SubtreePool self = {array_new(), array_new()}; - array_reserve(&self.free_trees, capacity); - return self; -} - -void ts_subtree_pool_delete(SubtreePool *self) { - if (self->free_trees.contents) { - for (unsigned i = 0; i < self->free_trees.size; i++) { - ts_free(self->free_trees.contents[i].ptr); - } - array_delete(&self->free_trees); - } - if (self->tree_stack.contents) array_delete(&self->tree_stack); -} - -static SubtreeHeapData *ts_subtree_pool_allocate(SubtreePool *self) { - if (self->free_trees.size > 0) { - return array_pop(&self->free_trees).ptr; - } else { - return ts_malloc(sizeof(SubtreeHeapData)); - } -} - -static void ts_subtree_pool_free(SubtreePool *self, SubtreeHeapData *tree) { - if (self->free_trees.capacity > 0 && self->free_trees.size + 1 <= TS_MAX_TREE_POOL_SIZE) { - array_push(&self->free_trees, (MutableSubtree) {.ptr = tree}); - } else { - ts_free(tree); - } -} - -// Subtree - -static inline bool ts_subtree_can_inline(Length padding, Length size, uint32_t lookahead_bytes) { - return - padding.bytes < TS_MAX_INLINE_TREE_LENGTH && - padding.extent.row < 16 && - padding.extent.column < TS_MAX_INLINE_TREE_LENGTH && - size.extent.row == 0 && - size.extent.column < TS_MAX_INLINE_TREE_LENGTH && - lookahead_bytes < 16; -} - -Subtree ts_subtree_new_leaf( - SubtreePool *pool, TSSymbol symbol, Length padding, Length size, - uint32_t lookahead_bytes, TSStateId parse_state, bool has_external_tokens, - bool is_keyword, const TSLanguage *language -) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); - bool extra = symbol == ts_builtin_sym_end; - - bool is_inline = ( - symbol <= UINT8_MAX && - !has_external_tokens && - ts_subtree_can_inline(padding, size, lookahead_bytes) - ); - - if (is_inline) { - return (Subtree) {{ - .parse_state = parse_state, - .symbol = symbol, - .padding_bytes = padding.bytes, - .padding_rows = padding.extent.row, - .padding_columns = padding.extent.column, - .size_bytes = size.bytes, - .lookahead_bytes = lookahead_bytes, - .visible = metadata.visible, - .named = metadata.named, - .extra = extra, - .has_changes = false, - .is_missing = false, - .is_keyword = is_keyword, - .is_inline = true, - }}; - } else { - SubtreeHeapData *data = ts_subtree_pool_allocate(pool); - *data = (SubtreeHeapData) { - .ref_count = 1, - .padding = padding, - .size = size, - .lookahead_bytes = lookahead_bytes, - .error_cost = 0, - .child_count = 0, - .symbol = symbol, - .parse_state = parse_state, - .visible = metadata.visible, - .named = metadata.named, - .extra = extra, - .fragile_left = false, - .fragile_right = false, - .has_changes = false, - .has_external_tokens = has_external_tokens, - .is_missing = false, - .is_keyword = is_keyword, - {{.first_leaf = {.symbol = 0, .parse_state = 0}}} - }; - return (Subtree) {.ptr = data}; - } -} - -void ts_subtree_set_symbol( - MutableSubtree *self, - TSSymbol symbol, - const TSLanguage *language -) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); - if (self->data.is_inline) { - assert(symbol < UINT8_MAX); - self->data.symbol = symbol; - self->data.named = metadata.named; - self->data.visible = metadata.visible; - } else { - self->ptr->symbol = symbol; - self->ptr->named = metadata.named; - self->ptr->visible = metadata.visible; - } -} - -Subtree ts_subtree_new_error( - SubtreePool *pool, int32_t lookahead_char, Length padding, Length size, - uint32_t bytes_scanned, TSStateId parse_state, const TSLanguage *language -) { - Subtree result = ts_subtree_new_leaf( - pool, ts_builtin_sym_error, padding, size, bytes_scanned, - parse_state, false, false, language - ); - SubtreeHeapData *data = (SubtreeHeapData *)result.ptr; - data->fragile_left = true; - data->fragile_right = true; - data->lookahead_char = lookahead_char; - return result; -} - -MutableSubtree ts_subtree_make_mut(SubtreePool *pool, Subtree self) { - if (self.data.is_inline) return (MutableSubtree) {self.data}; - if (self.ptr->ref_count == 1) return ts_subtree_to_mut_unsafe(self); - - SubtreeHeapData *result = ts_subtree_pool_allocate(pool); - memcpy(result, self.ptr, sizeof(SubtreeHeapData)); - if (result->child_count > 0) { - result->children = ts_calloc(self.ptr->child_count, sizeof(Subtree)); - memcpy(result->children, self.ptr->children, result->child_count * sizeof(Subtree)); - for (uint32_t i = 0; i < result->child_count; i++) { - ts_subtree_retain(result->children[i]); - } - } else if (result->has_external_tokens) { - result->external_scanner_state = ts_external_scanner_state_copy(&self.ptr->external_scanner_state); - } - result->ref_count = 1; - ts_subtree_release(pool, self); - return (MutableSubtree) {.ptr = result}; -} - -static void ts_subtree__compress(MutableSubtree self, unsigned count, const TSLanguage *language, - MutableSubtreeArray *stack) { - unsigned initial_stack_size = stack->size; - - MutableSubtree tree = self; - TSSymbol symbol = tree.ptr->symbol; - for (unsigned i = 0; i < count; i++) { - if (tree.ptr->ref_count > 1 || tree.ptr->child_count < 2) break; - - MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]); - if ( - child.data.is_inline || - child.ptr->child_count < 2 || - child.ptr->ref_count > 1 || - child.ptr->symbol != symbol - ) break; - - MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[0]); - if ( - grandchild.data.is_inline || - grandchild.ptr->child_count < 2 || - grandchild.ptr->ref_count > 1 || - grandchild.ptr->symbol != symbol - ) break; - - tree.ptr->children[0] = ts_subtree_from_mut(grandchild); - child.ptr->children[0] = grandchild.ptr->children[grandchild.ptr->child_count - 1]; - grandchild.ptr->children[grandchild.ptr->child_count - 1] = ts_subtree_from_mut(child); - array_push(stack, tree); - tree = grandchild; - } - - while (stack->size > initial_stack_size) { - tree = array_pop(stack); - MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]); - MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[child.ptr->child_count - 1]); - ts_subtree_set_children(grandchild, grandchild.ptr->children, grandchild.ptr->child_count, language); - ts_subtree_set_children(child, child.ptr->children, child.ptr->child_count, language); - ts_subtree_set_children(tree, tree.ptr->children, tree.ptr->child_count, language); - } -} - -void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *language) { - array_clear(&pool->tree_stack); - - if (ts_subtree_child_count(self) > 0 && self.ptr->ref_count == 1) { - array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self)); - } - - while (pool->tree_stack.size > 0) { - MutableSubtree tree = array_pop(&pool->tree_stack); - - if (tree.ptr->repeat_depth > 0) { - Subtree child1 = tree.ptr->children[0]; - Subtree child2 = tree.ptr->children[tree.ptr->child_count - 1]; - long repeat_delta = (long)ts_subtree_repeat_depth(child1) - (long)ts_subtree_repeat_depth(child2); - if (repeat_delta > 0) { - unsigned n = repeat_delta; - for (unsigned i = n / 2; i > 0; i /= 2) { - ts_subtree__compress(tree, i, language, &pool->tree_stack); - n -= i; - } - } - } - - for (uint32_t i = 0; i < tree.ptr->child_count; i++) { - Subtree child = tree.ptr->children[i]; - if (ts_subtree_child_count(child) > 0 && child.ptr->ref_count == 1) { - array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child)); - } - } - } -} - -void ts_subtree_set_children( - MutableSubtree self, Subtree *children, uint32_t child_count, const TSLanguage *language -) { - assert(!self.data.is_inline); - - if (self.ptr->child_count > 0 && children != self.ptr->children) { - ts_free(self.ptr->children); - } - - self.ptr->child_count = child_count; - self.ptr->children = children; - self.ptr->named_child_count = 0; - self.ptr->visible_child_count = 0; - self.ptr->error_cost = 0; - self.ptr->repeat_depth = 0; - self.ptr->node_count = 1; - self.ptr->has_external_tokens = false; - self.ptr->dynamic_precedence = 0; - - uint32_t non_extra_index = 0; - const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id); - uint32_t lookahead_end_byte = 0; - - for (uint32_t i = 0; i < self.ptr->child_count; i++) { - Subtree child = self.ptr->children[i]; - - if (i == 0) { - self.ptr->padding = ts_subtree_padding(child); - self.ptr->size = ts_subtree_size(child); - } else { - self.ptr->size = length_add(self.ptr->size, ts_subtree_total_size(child)); - } - - uint32_t child_lookahead_end_byte = - self.ptr->padding.bytes + - self.ptr->size.bytes + - ts_subtree_lookahead_bytes(child); - if (child_lookahead_end_byte > lookahead_end_byte) lookahead_end_byte = child_lookahead_end_byte; - - if (ts_subtree_symbol(child) != ts_builtin_sym_error_repeat) { - self.ptr->error_cost += ts_subtree_error_cost(child); - } - - self.ptr->dynamic_precedence += ts_subtree_dynamic_precedence(child); - self.ptr->node_count += ts_subtree_node_count(child); - - if (alias_sequence && alias_sequence[non_extra_index] != 0 && !ts_subtree_extra(child)) { - self.ptr->visible_child_count++; - if (ts_language_symbol_metadata(language, alias_sequence[non_extra_index]).named) { - self.ptr->named_child_count++; - } - } else if (ts_subtree_visible(child)) { - self.ptr->visible_child_count++; - if (ts_subtree_named(child)) self.ptr->named_child_count++; - } else if (ts_subtree_child_count(child) > 0) { - self.ptr->visible_child_count += child.ptr->visible_child_count; - self.ptr->named_child_count += child.ptr->named_child_count; - } - - if (ts_subtree_has_external_tokens(child)) self.ptr->has_external_tokens = true; - - if (ts_subtree_is_error(child)) { - self.ptr->fragile_left = self.ptr->fragile_right = true; - self.ptr->parse_state = TS_TREE_STATE_NONE; - } - - if (!ts_subtree_extra(child)) non_extra_index++; - } - - self.ptr->lookahead_bytes = lookahead_end_byte - self.ptr->size.bytes - self.ptr->padding.bytes; - - if (self.ptr->symbol == ts_builtin_sym_error || self.ptr->symbol == ts_builtin_sym_error_repeat) { - self.ptr->error_cost += - ERROR_COST_PER_RECOVERY + - ERROR_COST_PER_SKIPPED_CHAR * self.ptr->size.bytes + - ERROR_COST_PER_SKIPPED_LINE * self.ptr->size.extent.row; - for (uint32_t i = 0; i < self.ptr->child_count; i++) { - Subtree child = self.ptr->children[i]; - uint32_t grandchild_count = ts_subtree_child_count(child); - if (ts_subtree_extra(child)) continue; - if (ts_subtree_is_error(child) && grandchild_count == 0) continue; - if (ts_subtree_visible(child)) { - self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE; - } else if (grandchild_count > 0) { - self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE * child.ptr->visible_child_count; - } - } - } - - if (self.ptr->child_count > 0) { - Subtree first_child = self.ptr->children[0]; - Subtree last_child = self.ptr->children[self.ptr->child_count - 1]; - - self.ptr->first_leaf.symbol = ts_subtree_leaf_symbol(first_child); - self.ptr->first_leaf.parse_state = ts_subtree_leaf_parse_state(first_child); - - if (ts_subtree_fragile_left(first_child)) self.ptr->fragile_left = true; - if (ts_subtree_fragile_right(last_child)) self.ptr->fragile_right = true; - - if ( - self.ptr->child_count >= 2 && - !self.ptr->visible && - !self.ptr->named && - ts_subtree_symbol(first_child) == self.ptr->symbol - ) { - if (ts_subtree_repeat_depth(first_child) > ts_subtree_repeat_depth(last_child)) { - self.ptr->repeat_depth = ts_subtree_repeat_depth(first_child) + 1; - } else { - self.ptr->repeat_depth = ts_subtree_repeat_depth(last_child) + 1; - } - } - } -} - -MutableSubtree ts_subtree_new_node(SubtreePool *pool, TSSymbol symbol, - SubtreeArray *children, unsigned production_id, - const TSLanguage *language) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); - bool fragile = symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat; - SubtreeHeapData *data = ts_subtree_pool_allocate(pool); - *data = (SubtreeHeapData) { - .ref_count = 1, - .symbol = symbol, - .visible = metadata.visible, - .named = metadata.named, - .has_changes = false, - .fragile_left = fragile, - .fragile_right = fragile, - .is_keyword = false, - {{ - .node_count = 0, - .production_id = production_id, - .first_leaf = {.symbol = 0, .parse_state = 0}, - }} - }; - MutableSubtree result = {.ptr = data}; - ts_subtree_set_children(result, children->contents, children->size, language); - return result; -} - -Subtree ts_subtree_new_error_node(SubtreePool *pool, SubtreeArray *children, - bool extra, const TSLanguage *language) { - MutableSubtree result = ts_subtree_new_node( - pool, ts_builtin_sym_error, children, 0, language - ); - result.ptr->extra = extra; - return ts_subtree_from_mut(result); -} - -Subtree ts_subtree_new_missing_leaf(SubtreePool *pool, TSSymbol symbol, Length padding, - const TSLanguage *language) { - Subtree result = ts_subtree_new_leaf( - pool, symbol, padding, length_zero(), 0, - 0, false, false, language - ); - - if (result.data.is_inline) { - result.data.is_missing = true; - } else { - ((SubtreeHeapData *)result.ptr)->is_missing = true; - } - - return result; -} - -void ts_subtree_retain(Subtree self) { - if (self.data.is_inline) return; - assert(self.ptr->ref_count > 0); - atomic_inc((volatile uint32_t *)&self.ptr->ref_count); - assert(self.ptr->ref_count != 0); -} - -void ts_subtree_release(SubtreePool *pool, Subtree self) { - if (self.data.is_inline) return; - array_clear(&pool->tree_stack); - - assert(self.ptr->ref_count > 0); - if (atomic_dec((volatile uint32_t *)&self.ptr->ref_count) == 0) { - array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self)); - } - - while (pool->tree_stack.size > 0) { - MutableSubtree tree = array_pop(&pool->tree_stack); - if (tree.ptr->child_count > 0) { - for (uint32_t i = 0; i < tree.ptr->child_count; i++) { - Subtree child = tree.ptr->children[i]; - if (child.data.is_inline) continue; - assert(child.ptr->ref_count > 0); - if (atomic_dec((volatile uint32_t *)&child.ptr->ref_count) == 0) { - array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child)); - } - } - ts_free(tree.ptr->children); - } else if (tree.ptr->has_external_tokens) { - ts_external_scanner_state_delete(&tree.ptr->external_scanner_state); - } - ts_subtree_pool_free(pool, tree.ptr); - } -} - -bool ts_subtree_eq(Subtree self, Subtree other) { - if (self.data.is_inline || other.data.is_inline) { - return memcmp(&self, &other, sizeof(SubtreeInlineData)) == 0; - } - - if (self.ptr) { - if (!other.ptr) return false; - } else { - return !other.ptr; - } - - if (self.ptr->symbol != other.ptr->symbol) return false; - if (self.ptr->visible != other.ptr->visible) return false; - if (self.ptr->named != other.ptr->named) return false; - if (self.ptr->padding.bytes != other.ptr->padding.bytes) return false; - if (self.ptr->size.bytes != other.ptr->size.bytes) return false; - if (self.ptr->symbol == ts_builtin_sym_error) return self.ptr->lookahead_char == other.ptr->lookahead_char; - if (self.ptr->child_count != other.ptr->child_count) return false; - if (self.ptr->child_count > 0) { - if (self.ptr->visible_child_count != other.ptr->visible_child_count) return false; - if (self.ptr->named_child_count != other.ptr->named_child_count) return false; - - for (uint32_t i = 0; i < self.ptr->child_count; i++) { - if (!ts_subtree_eq(self.ptr->children[i], other.ptr->children[i])) { - return false; - } - } - } - return true; -} - -int ts_subtree_compare(Subtree left, Subtree right) { - if (ts_subtree_symbol(left) < ts_subtree_symbol(right)) return -1; - if (ts_subtree_symbol(right) < ts_subtree_symbol(left)) return 1; - if (ts_subtree_child_count(left) < ts_subtree_child_count(right)) return -1; - if (ts_subtree_child_count(right) < ts_subtree_child_count(left)) return 1; - for (uint32_t i = 0, n = ts_subtree_child_count(left); i < n; i++) { - Subtree left_child = left.ptr->children[i]; - Subtree right_child = right.ptr->children[i]; - switch (ts_subtree_compare(left_child, right_child)) { - case -1: return -1; - case 1: return 1; - default: break; - } - } - return 0; -} - -static inline void ts_subtree_set_has_changes(MutableSubtree *self) { - if (self->data.is_inline) { - self->data.has_changes = true; - } else { - self->ptr->has_changes = true; - } -} - -Subtree ts_subtree_edit(Subtree self, const TSInputEdit *edit, SubtreePool *pool) { - typedef struct { - Subtree *tree; - Edit edit; - } StackEntry; - - Array(StackEntry) stack = array_new(); - array_push(&stack, ((StackEntry) { - .tree = &self, - .edit = (Edit) { - .start = {edit->start_byte, edit->start_point}, - .old_end = {edit->old_end_byte, edit->old_end_point}, - .new_end = {edit->new_end_byte, edit->new_end_point}, - }, - })); - - while (stack.size) { - StackEntry entry = array_pop(&stack); - Edit edit = entry.edit; - bool is_noop = edit.old_end.bytes == edit.start.bytes && edit.new_end.bytes == edit.start.bytes; - bool is_pure_insertion = edit.old_end.bytes == edit.start.bytes; - - Length size = ts_subtree_size(*entry.tree); - Length padding = ts_subtree_padding(*entry.tree); - uint32_t lookahead_bytes = ts_subtree_lookahead_bytes(*entry.tree); - uint32_t end_byte = padding.bytes + size.bytes + lookahead_bytes; - if (edit.start.bytes > end_byte || (is_noop && edit.start.bytes == end_byte)) continue; - - // If the edit is entirely within the space before this subtree, then shift this - // subtree over according to the edit without changing its size. - if (edit.old_end.bytes <= padding.bytes) { - padding = length_add(edit.new_end, length_sub(padding, edit.old_end)); - } - - // If the edit starts in the space before this subtree and extends into this subtree, - // shrink the subtree's content to compensate for the change in the space before it. - else if (edit.start.bytes < padding.bytes) { - size = length_sub(size, length_sub(edit.old_end, padding)); - padding = edit.new_end; - } - - // If the edit is a pure insertion right at the start of the subtree, - // shift the subtree over according to the insertion. - else if (edit.start.bytes == padding.bytes && is_pure_insertion) { - padding = edit.new_end; - } - - // If the edit is within this subtree, resize the subtree to reflect the edit. - else { - uint32_t total_bytes = padding.bytes + size.bytes; - if (edit.start.bytes < total_bytes || - (edit.start.bytes == total_bytes && is_pure_insertion)) { - size = length_add( - length_sub(edit.new_end, padding), - length_sub(size, length_sub(edit.old_end, padding)) - ); - } - } - - MutableSubtree result = ts_subtree_make_mut(pool, *entry.tree); - - if (result.data.is_inline) { - if (ts_subtree_can_inline(padding, size, lookahead_bytes)) { - result.data.padding_bytes = padding.bytes; - result.data.padding_rows = padding.extent.row; - result.data.padding_columns = padding.extent.column; - result.data.size_bytes = size.bytes; - } else { - SubtreeHeapData *data = ts_subtree_pool_allocate(pool); - data->ref_count = 1; - data->padding = padding; - data->size = size; - data->lookahead_bytes = lookahead_bytes; - data->error_cost = 0; - data->child_count = 0; - data->symbol = result.data.symbol; - data->parse_state = result.data.parse_state; - data->visible = result.data.visible; - data->named = result.data.named; - data->extra = result.data.extra; - data->fragile_left = false; - data->fragile_right = false; - data->has_changes = false; - data->has_external_tokens = false; - data->is_missing = result.data.is_missing; - data->is_keyword = result.data.is_keyword; - result.ptr = data; - } - } else { - result.ptr->padding = padding; - result.ptr->size = size; - } - - ts_subtree_set_has_changes(&result); - *entry.tree = ts_subtree_from_mut(result); - - Length child_left, child_right = length_zero(); - for (uint32_t i = 0, n = ts_subtree_child_count(*entry.tree); i < n; i++) { - Subtree *child = &result.ptr->children[i]; - Length child_size = ts_subtree_total_size(*child); - child_left = child_right; - child_right = length_add(child_left, child_size); - - // If this child ends before the edit, it is not affected. - if (child_right.bytes + ts_subtree_lookahead_bytes(*child) < edit.start.bytes) continue; - - // If this child starts after the edit, then we're done processing children. - if (child_left.bytes > edit.old_end.bytes || - (child_left.bytes == edit.old_end.bytes && child_size.bytes > 0 && i > 0)) break; - - // Transform edit into the child's coordinate space. - Edit child_edit = { - .start = length_sub(edit.start, child_left), - .old_end = length_sub(edit.old_end, child_left), - .new_end = length_sub(edit.new_end, child_left), - }; - - // Clamp child_edit to the child's bounds. - if (edit.start.bytes < child_left.bytes) child_edit.start = length_zero(); - if (edit.old_end.bytes < child_left.bytes) child_edit.old_end = length_zero(); - if (edit.new_end.bytes < child_left.bytes) child_edit.new_end = length_zero(); - if (edit.old_end.bytes > child_right.bytes) child_edit.old_end = child_size; - - // Interpret all inserted text as applying to the *first* child that touches the edit. - // Subsequent children are only never have any text inserted into them; they are only - // shrunk to compensate for the edit. - if (child_right.bytes > edit.start.bytes || - (child_right.bytes == edit.start.bytes && is_pure_insertion)) { - edit.new_end = edit.start; - } - - // Children that occur before the edit are not reshaped by the edit. - else { - child_edit.old_end = child_edit.start; - child_edit.new_end = child_edit.start; - } - - // Queue processing of this child's subtree. - array_push(&stack, ((StackEntry) { - .tree = child, - .edit = child_edit, - })); - } - } - - array_delete(&stack); - return self; -} - -Subtree ts_subtree_last_external_token(Subtree tree) { - if (!ts_subtree_has_external_tokens(tree)) return NULL_SUBTREE; - while (tree.ptr->child_count > 0) { - for (uint32_t i = tree.ptr->child_count - 1; i + 1 > 0; i--) { - Subtree child = tree.ptr->children[i]; - if (ts_subtree_has_external_tokens(child)) { - tree = child; - break; - } - } - } - return tree; -} - -static size_t ts_subtree__write_char_to_string(char *s, size_t n, int32_t c) { - if (c == -1) - return snprintf(s, n, "INVALID"); - else if (c == '\0') - return snprintf(s, n, "'\\0'"); - else if (c == '\n') - return snprintf(s, n, "'\\n'"); - else if (c == '\t') - return snprintf(s, n, "'\\t'"); - else if (c == '\r') - return snprintf(s, n, "'\\r'"); - else if (0 < c && c < 128 && isprint(c)) - return snprintf(s, n, "'%c'", c); - else - return snprintf(s, n, "%d", c); -} - -static void ts_subtree__write_dot_string(FILE *f, const char *string) { - for (const char *c = string; *c; c++) { - if (*c == '"') { - fputs("\\\"", f); - } else if (*c == '\n') { - fputs("\\n", f); - } else { - fputc(*c, f); - } - } -} - -static const char *ROOT_FIELD = "__ROOT__"; - -static size_t ts_subtree__write_to_string( - Subtree self, char *string, size_t limit, - const TSLanguage *language, bool include_all, - TSSymbol alias_symbol, bool alias_is_named, const char *field_name -) { - if (!self.ptr) return snprintf(string, limit, "(NULL)"); - - char *cursor = string; - char **writer = (limit > 0) ? &cursor : &string; - bool is_root = field_name == ROOT_FIELD; - bool is_visible = - include_all || - ts_subtree_missing(self) || - ( - alias_symbol - ? alias_is_named - : ts_subtree_visible(self) && ts_subtree_named(self) - ); - - if (is_visible) { - if (!is_root) { - cursor += snprintf(*writer, limit, " "); - if (field_name) { - cursor += snprintf(*writer, limit, "%s: ", field_name); - } - } - - if (ts_subtree_is_error(self) && ts_subtree_child_count(self) == 0 && self.ptr->size.bytes > 0) { - cursor += snprintf(*writer, limit, "(UNEXPECTED "); - cursor += ts_subtree__write_char_to_string(*writer, limit, self.ptr->lookahead_char); - } else { - TSSymbol symbol = alias_symbol ? alias_symbol : ts_subtree_symbol(self); - const char *symbol_name = ts_language_symbol_name(language, symbol); - if (ts_subtree_missing(self)) { - cursor += snprintf(*writer, limit, "(MISSING "); - if (alias_is_named || ts_subtree_named(self)) { - cursor += snprintf(*writer, limit, "%s", symbol_name); - } else { - cursor += snprintf(*writer, limit, "\"%s\"", symbol_name); - } - } else { - cursor += snprintf(*writer, limit, "(%s", symbol_name); - } - } - } else if (is_root) { - TSSymbol symbol = ts_subtree_symbol(self); - const char *symbol_name = ts_language_symbol_name(language, symbol); - cursor += snprintf(*writer, limit, "(\"%s\")", symbol_name); - } - - if (ts_subtree_child_count(self)) { - const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id); - const TSFieldMapEntry *field_map, *field_map_end; - ts_language_field_map( - language, - self.ptr->production_id, - &field_map, - &field_map_end - ); - - uint32_t structural_child_index = 0; - for (uint32_t i = 0; i < self.ptr->child_count; i++) { - Subtree child = self.ptr->children[i]; - if (ts_subtree_extra(child)) { - cursor += ts_subtree__write_to_string( - child, *writer, limit, - language, include_all, - 0, false, NULL - ); - } else { - TSSymbol alias_symbol = alias_sequence - ? alias_sequence[structural_child_index] - : 0; - bool alias_is_named = alias_symbol - ? ts_language_symbol_metadata(language, alias_symbol).named - : false; - - const char *child_field_name = is_visible ? NULL : field_name; - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (!i->inherited && i->child_index == structural_child_index) { - child_field_name = language->field_names[i->field_id]; - break; - } - } - - cursor += ts_subtree__write_to_string( - child, *writer, limit, - language, include_all, - alias_symbol, alias_is_named, child_field_name - ); - structural_child_index++; - } - } - } - - if (is_visible) cursor += snprintf(*writer, limit, ")"); - - return cursor - string; -} - -char *ts_subtree_string( - Subtree self, - const TSLanguage *language, - bool include_all -) { - char scratch_string[1]; - size_t size = ts_subtree__write_to_string( - self, scratch_string, 0, - language, include_all, - 0, false, ROOT_FIELD - ) + 1; - char *result = malloc(size * sizeof(char)); - ts_subtree__write_to_string( - self, result, size, - language, include_all, - 0, false, ROOT_FIELD - ); - return result; -} - -void ts_subtree__print_dot_graph(const Subtree *self, uint32_t start_offset, - const TSLanguage *language, TSSymbol alias_symbol, - FILE *f) { - TSSymbol subtree_symbol = ts_subtree_symbol(*self); - TSSymbol symbol = alias_symbol ? alias_symbol : subtree_symbol; - uint32_t end_offset = start_offset + ts_subtree_total_bytes(*self); - fprintf(f, "tree_%p [label=\"", self); - ts_subtree__write_dot_string(f, ts_language_symbol_name(language, symbol)); - fprintf(f, "\""); - - if (ts_subtree_child_count(*self) == 0) fprintf(f, ", shape=plaintext"); - if (ts_subtree_extra(*self)) fprintf(f, ", fontcolor=gray"); - - fprintf(f, ", tooltip=\"" - "range: %u - %u\n" - "state: %d\n" - "error-cost: %u\n" - "has-changes: %u\n" - "repeat-depth: %u\n" - "lookahead-bytes: %u", - start_offset, end_offset, - ts_subtree_parse_state(*self), - ts_subtree_error_cost(*self), - ts_subtree_has_changes(*self), - ts_subtree_repeat_depth(*self), - ts_subtree_lookahead_bytes(*self) - ); - - if (ts_subtree_is_error(*self) && ts_subtree_child_count(*self) == 0) { - fprintf(f, "\ncharacter: '%c'", self->ptr->lookahead_char); - } - - fprintf(f, "\"]\n"); - - uint32_t child_start_offset = start_offset; - uint32_t child_info_offset = - language->max_alias_sequence_length * - ts_subtree_production_id(*self); - for (uint32_t i = 0, n = ts_subtree_child_count(*self); i < n; i++) { - const Subtree *child = &self->ptr->children[i]; - TSSymbol alias_symbol = 0; - if (!ts_subtree_extra(*child) && child_info_offset) { - alias_symbol = language->alias_sequences[child_info_offset]; - child_info_offset++; - } - ts_subtree__print_dot_graph(child, child_start_offset, language, alias_symbol, f); - fprintf(f, "tree_%p -> tree_%p [tooltip=%u]\n", self, child, i); - child_start_offset += ts_subtree_total_bytes(*child); - } -} - -void ts_subtree_print_dot_graph(Subtree self, const TSLanguage *language, FILE *f) { - fprintf(f, "digraph tree {\n"); - fprintf(f, "edge [arrowhead=none]\n"); - ts_subtree__print_dot_graph(&self, 0, language, 0, f); - fprintf(f, "}\n"); -} - -bool ts_subtree_external_scanner_state_eq(Subtree self, Subtree other) { - const ExternalScannerState *state1 = &empty_state; - const ExternalScannerState *state2 = &empty_state; - if (self.ptr && ts_subtree_has_external_tokens(self) && !self.ptr->child_count) { - state1 = &self.ptr->external_scanner_state; - } - if (other.ptr && ts_subtree_has_external_tokens(other) && !other.ptr->child_count) { - state2 = &other.ptr->external_scanner_state; - } - return ts_external_scanner_state_eq(state1, state2); -} diff --git a/src/tree_sitter/subtree.h b/src/tree_sitter/subtree.h deleted file mode 100644 index 18c48dcbd0..0000000000 --- a/src/tree_sitter/subtree.h +++ /dev/null @@ -1,285 +0,0 @@ -#ifndef TREE_SITTER_SUBTREE_H_ -#define TREE_SITTER_SUBTREE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <limits.h> -#include <stdbool.h> -#include <stdio.h> -#include "./length.h" -#include "./array.h" -#include "./error_costs.h" -#include "tree_sitter/api.h" -#include "tree_sitter/parser.h" - -static const TSStateId TS_TREE_STATE_NONE = USHRT_MAX; -#define NULL_SUBTREE ((Subtree) {.ptr = NULL}) - -typedef union Subtree Subtree; -typedef union MutableSubtree MutableSubtree; - -typedef struct { - union { - char *long_data; - char short_data[24]; - }; - uint32_t length; -} ExternalScannerState; - -typedef struct { - bool is_inline : 1; - bool visible : 1; - bool named : 1; - bool extra : 1; - bool has_changes : 1; - bool is_missing : 1; - bool is_keyword : 1; - uint8_t symbol; - uint8_t padding_bytes; - uint8_t size_bytes; - uint8_t padding_columns; - uint8_t padding_rows : 4; - uint8_t lookahead_bytes : 4; - uint16_t parse_state; -} SubtreeInlineData; - -typedef struct { - volatile uint32_t ref_count; - Length padding; - Length size; - uint32_t lookahead_bytes; - uint32_t error_cost; - uint32_t child_count; - TSSymbol symbol; - TSStateId parse_state; - - bool visible : 1; - bool named : 1; - bool extra : 1; - bool fragile_left : 1; - bool fragile_right : 1; - bool has_changes : 1; - bool has_external_tokens : 1; - bool is_missing : 1; - bool is_keyword : 1; - - union { - // Non-terminal subtrees (`child_count > 0`) - struct { - Subtree *children; - uint32_t visible_child_count; - uint32_t named_child_count; - uint32_t node_count; - uint32_t repeat_depth; - int32_t dynamic_precedence; - uint16_t production_id; - struct { - TSSymbol symbol; - TSStateId parse_state; - } first_leaf; - }; - - // External terminal subtrees (`child_count == 0 && has_external_tokens`) - ExternalScannerState external_scanner_state; - - // Error terminal subtrees (`child_count == 0 && symbol == ts_builtin_sym_error`) - int32_t lookahead_char; - }; -} SubtreeHeapData; - -union Subtree { - SubtreeInlineData data; - const SubtreeHeapData *ptr; -}; - -union MutableSubtree { - SubtreeInlineData data; - SubtreeHeapData *ptr; -}; - -typedef Array(Subtree) SubtreeArray; -typedef Array(MutableSubtree) MutableSubtreeArray; - -typedef struct { - MutableSubtreeArray free_trees; - MutableSubtreeArray tree_stack; -} SubtreePool; - -void ts_external_scanner_state_init(ExternalScannerState *, const char *, unsigned); -const char *ts_external_scanner_state_data(const ExternalScannerState *); - -void ts_subtree_array_copy(SubtreeArray, SubtreeArray *); -void ts_subtree_array_delete(SubtreePool *, SubtreeArray *); -SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *); -void ts_subtree_array_reverse(SubtreeArray *); - -SubtreePool ts_subtree_pool_new(uint32_t capacity); -void ts_subtree_pool_delete(SubtreePool *); - -Subtree ts_subtree_new_leaf( - SubtreePool *, TSSymbol, Length, Length, uint32_t, - TSStateId, bool, bool, const TSLanguage * -); -Subtree ts_subtree_new_error( - SubtreePool *, int32_t, Length, Length, uint32_t, TSStateId, const TSLanguage * -); -MutableSubtree ts_subtree_new_node(SubtreePool *, TSSymbol, SubtreeArray *, unsigned, const TSLanguage *); -Subtree ts_subtree_new_error_node(SubtreePool *, SubtreeArray *, bool, const TSLanguage *); -Subtree ts_subtree_new_missing_leaf(SubtreePool *, TSSymbol, Length, const TSLanguage *); -MutableSubtree ts_subtree_make_mut(SubtreePool *, Subtree); -void ts_subtree_retain(Subtree); -void ts_subtree_release(SubtreePool *, Subtree); -bool ts_subtree_eq(Subtree, Subtree); -int ts_subtree_compare(Subtree, Subtree); -void ts_subtree_set_symbol(MutableSubtree *, TSSymbol, const TSLanguage *); -void ts_subtree_set_children(MutableSubtree, Subtree *, uint32_t, const TSLanguage *); -void ts_subtree_balance(Subtree, SubtreePool *, const TSLanguage *); -Subtree ts_subtree_edit(Subtree, const TSInputEdit *edit, SubtreePool *); -char *ts_subtree_string(Subtree, const TSLanguage *, bool include_all); -void ts_subtree_print_dot_graph(Subtree, const TSLanguage *, FILE *); -Subtree ts_subtree_last_external_token(Subtree); -bool ts_subtree_external_scanner_state_eq(Subtree, Subtree); - -#define SUBTREE_GET(self, name) (self.data.is_inline ? self.data.name : self.ptr->name) - -static inline TSSymbol ts_subtree_symbol(Subtree self) { return SUBTREE_GET(self, symbol); } -static inline bool ts_subtree_visible(Subtree self) { return SUBTREE_GET(self, visible); } -static inline bool ts_subtree_named(Subtree self) { return SUBTREE_GET(self, named); } -static inline bool ts_subtree_extra(Subtree self) { return SUBTREE_GET(self, extra); } -static inline bool ts_subtree_has_changes(Subtree self) { return SUBTREE_GET(self, has_changes); } -static inline bool ts_subtree_missing(Subtree self) { return SUBTREE_GET(self, is_missing); } -static inline bool ts_subtree_is_keyword(Subtree self) { return SUBTREE_GET(self, is_keyword); } -static inline TSStateId ts_subtree_parse_state(Subtree self) { return SUBTREE_GET(self, parse_state); } -static inline uint32_t ts_subtree_lookahead_bytes(Subtree self) { return SUBTREE_GET(self, lookahead_bytes); } - -#undef SUBTREE_GET - -static inline void ts_subtree_set_extra(MutableSubtree *self) { - if (self->data.is_inline) { - self->data.extra = true; - } else { - self->ptr->extra = true; - } -} - -static inline TSSymbol ts_subtree_leaf_symbol(Subtree self) { - if (self.data.is_inline) return self.data.symbol; - if (self.ptr->child_count == 0) return self.ptr->symbol; - return self.ptr->first_leaf.symbol; -} - -static inline TSStateId ts_subtree_leaf_parse_state(Subtree self) { - if (self.data.is_inline) return self.data.parse_state; - if (self.ptr->child_count == 0) return self.ptr->parse_state; - return self.ptr->first_leaf.parse_state; -} - -static inline Length ts_subtree_padding(Subtree self) { - if (self.data.is_inline) { - Length result = {self.data.padding_bytes, {self.data.padding_rows, self.data.padding_columns}}; - return result; - } else { - return self.ptr->padding; - } -} - -static inline Length ts_subtree_size(Subtree self) { - if (self.data.is_inline) { - Length result = {self.data.size_bytes, {0, self.data.size_bytes}}; - return result; - } else { - return self.ptr->size; - } -} - -static inline Length ts_subtree_total_size(Subtree self) { - return length_add(ts_subtree_padding(self), ts_subtree_size(self)); -} - -static inline uint32_t ts_subtree_total_bytes(Subtree self) { - return ts_subtree_total_size(self).bytes; -} - -static inline uint32_t ts_subtree_child_count(Subtree self) { - return self.data.is_inline ? 0 : self.ptr->child_count; -} - -static inline uint32_t ts_subtree_repeat_depth(Subtree self) { - return self.data.is_inline ? 0 : self.ptr->repeat_depth; -} - -static inline uint32_t ts_subtree_node_count(Subtree self) { - return (self.data.is_inline || self.ptr->child_count == 0) ? 1 : self.ptr->node_count; -} - -static inline uint32_t ts_subtree_visible_child_count(Subtree self) { - if (ts_subtree_child_count(self) > 0) { - return self.ptr->visible_child_count; - } else { - return 0; - } -} - -static inline uint32_t ts_subtree_error_cost(Subtree self) { - if (ts_subtree_missing(self)) { - return ERROR_COST_PER_MISSING_TREE + ERROR_COST_PER_RECOVERY; - } else { - return self.data.is_inline ? 0 : self.ptr->error_cost; - } -} - -static inline int32_t ts_subtree_dynamic_precedence(Subtree self) { - return (self.data.is_inline || self.ptr->child_count == 0) ? 0 : self.ptr->dynamic_precedence; -} - -static inline uint16_t ts_subtree_production_id(Subtree self) { - if (ts_subtree_child_count(self) > 0) { - return self.ptr->production_id; - } else { - return 0; - } -} - -static inline bool ts_subtree_fragile_left(Subtree self) { - return self.data.is_inline ? false : self.ptr->fragile_left; -} - -static inline bool ts_subtree_fragile_right(Subtree self) { - return self.data.is_inline ? false : self.ptr->fragile_right; -} - -static inline bool ts_subtree_has_external_tokens(Subtree self) { - return self.data.is_inline ? false : self.ptr->has_external_tokens; -} - -static inline bool ts_subtree_is_fragile(Subtree self) { - return self.data.is_inline ? false : (self.ptr->fragile_left || self.ptr->fragile_right); -} - -static inline bool ts_subtree_is_error(Subtree self) { - return ts_subtree_symbol(self) == ts_builtin_sym_error; -} - -static inline bool ts_subtree_is_eof(Subtree self) { - return ts_subtree_symbol(self) == ts_builtin_sym_end; -} - -static inline Subtree ts_subtree_from_mut(MutableSubtree self) { - Subtree result; - result.data = self.data; - return result; -} - -static inline MutableSubtree ts_subtree_to_mut_unsafe(Subtree self) { - MutableSubtree result; - result.data = self.data; - return result; -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_SUBTREE_H_ diff --git a/src/tree_sitter/tree.c b/src/tree_sitter/tree.c deleted file mode 100644 index 391fa7f592..0000000000 --- a/src/tree_sitter/tree.c +++ /dev/null @@ -1,148 +0,0 @@ -#include "tree_sitter/api.h" -#include "./array.h" -#include "./get_changed_ranges.h" -#include "./subtree.h" -#include "./tree_cursor.h" -#include "./tree.h" - -static const unsigned PARENT_CACHE_CAPACITY = 32; - -TSTree *ts_tree_new( - Subtree root, const TSLanguage *language, - const TSRange *included_ranges, unsigned included_range_count -) { - TSTree *result = ts_malloc(sizeof(TSTree)); - result->root = root; - result->language = language; - result->parent_cache = NULL; - result->parent_cache_start = 0; - result->parent_cache_size = 0; - result->included_ranges = ts_calloc(included_range_count, sizeof(TSRange)); - memcpy(result->included_ranges, included_ranges, included_range_count * sizeof(TSRange)); - result->included_range_count = included_range_count; - return result; -} - -TSTree *ts_tree_copy(const TSTree *self) { - ts_subtree_retain(self->root); - return ts_tree_new(self->root, self->language, self->included_ranges, self->included_range_count); -} - -void ts_tree_delete(TSTree *self) { - if (!self) return; - - SubtreePool pool = ts_subtree_pool_new(0); - ts_subtree_release(&pool, self->root); - ts_subtree_pool_delete(&pool); - ts_free(self->included_ranges); - if (self->parent_cache) ts_free(self->parent_cache); - ts_free(self); -} - -TSNode ts_tree_root_node(const TSTree *self) { - return ts_node_new(self, &self->root, ts_subtree_padding(self->root), 0); -} - -const TSLanguage *ts_tree_language(const TSTree *self) { - return self->language; -} - -void ts_tree_edit(TSTree *self, const TSInputEdit *edit) { - for (unsigned i = 0; i < self->included_range_count; i++) { - TSRange *range = &self->included_ranges[i]; - if (range->end_byte >= edit->old_end_byte) { - if (range->end_byte != UINT32_MAX) { - range->end_byte = edit->new_end_byte + (range->end_byte - edit->old_end_byte); - range->end_point = point_add( - edit->new_end_point, - point_sub(range->end_point, edit->old_end_point) - ); - if (range->end_byte < edit->new_end_byte) { - range->end_byte = UINT32_MAX; - range->end_point = POINT_MAX; - } - } - if (range->start_byte >= edit->old_end_byte) { - range->start_byte = edit->new_end_byte + (range->start_byte - edit->old_end_byte); - range->start_point = point_add( - edit->new_end_point, - point_sub(range->start_point, edit->old_end_point) - ); - if (range->start_byte < edit->new_end_byte) { - range->start_byte = UINT32_MAX; - range->start_point = POINT_MAX; - } - } - } - } - - SubtreePool pool = ts_subtree_pool_new(0); - self->root = ts_subtree_edit(self->root, edit, &pool); - self->parent_cache_start = 0; - self->parent_cache_size = 0; - ts_subtree_pool_delete(&pool); -} - -TSRange *ts_tree_get_changed_ranges(const TSTree *self, const TSTree *other, uint32_t *count) { - TreeCursor cursor1 = {NULL, array_new()}; - TreeCursor cursor2 = {NULL, array_new()}; - ts_tree_cursor_init(&cursor1, ts_tree_root_node(self)); - ts_tree_cursor_init(&cursor2, ts_tree_root_node(other)); - - TSRangeArray included_range_differences = array_new(); - ts_range_array_get_changed_ranges( - self->included_ranges, self->included_range_count, - other->included_ranges, other->included_range_count, - &included_range_differences - ); - - TSRange *result; - *count = ts_subtree_get_changed_ranges( - &self->root, &other->root, &cursor1, &cursor2, - self->language, &included_range_differences, &result - ); - - array_delete(&included_range_differences); - array_delete(&cursor1.stack); - array_delete(&cursor2.stack); - return result; -} - -void ts_tree_print_dot_graph(const TSTree *self, FILE *file) { - ts_subtree_print_dot_graph(self->root, self->language, file); -} - -TSNode ts_tree_get_cached_parent(const TSTree *self, const TSNode *node) { - for (uint32_t i = 0; i < self->parent_cache_size; i++) { - uint32_t index = (self->parent_cache_start + i) % PARENT_CACHE_CAPACITY; - ParentCacheEntry *entry = &self->parent_cache[index]; - if (entry->child == node->id) { - return ts_node_new(self, entry->parent, entry->position, entry->alias_symbol); - } - } - return ts_node_new(NULL, NULL, length_zero(), 0); -} - -void ts_tree_set_cached_parent(const TSTree *_self, const TSNode *node, const TSNode *parent) { - TSTree *self = (TSTree *)_self; - if (!self->parent_cache) { - self->parent_cache = ts_calloc(PARENT_CACHE_CAPACITY, sizeof(ParentCacheEntry)); - } - - uint32_t index = (self->parent_cache_start + self->parent_cache_size) % PARENT_CACHE_CAPACITY; - self->parent_cache[index] = (ParentCacheEntry) { - .child = node->id, - .parent = (const Subtree *)parent->id, - .position = { - parent->context[0], - {parent->context[1], parent->context[2]} - }, - .alias_symbol = parent->context[3], - }; - - if (self->parent_cache_size == PARENT_CACHE_CAPACITY) { - self->parent_cache_start++; - } else { - self->parent_cache_size++; - } -} diff --git a/src/tree_sitter/tree.h b/src/tree_sitter/tree.h deleted file mode 100644 index 92a7e64179..0000000000 --- a/src/tree_sitter/tree.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef TREE_SITTER_TREE_H_ -#define TREE_SITTER_TREE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - const Subtree *child; - const Subtree *parent; - Length position; - TSSymbol alias_symbol; -} ParentCacheEntry; - -struct TSTree { - Subtree root; - const TSLanguage *language; - ParentCacheEntry *parent_cache; - uint32_t parent_cache_start; - uint32_t parent_cache_size; - TSRange *included_ranges; - unsigned included_range_count; -}; - -TSTree *ts_tree_new(Subtree root, const TSLanguage *language, const TSRange *, unsigned); -TSNode ts_node_new(const TSTree *, const Subtree *, Length, TSSymbol); -TSNode ts_tree_get_cached_parent(const TSTree *, const TSNode *); -void ts_tree_set_cached_parent(const TSTree *, const TSNode *, const TSNode *); - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_TREE_H_ diff --git a/src/tree_sitter/tree_cursor.c b/src/tree_sitter/tree_cursor.c deleted file mode 100644 index 00b9679d73..0000000000 --- a/src/tree_sitter/tree_cursor.c +++ /dev/null @@ -1,367 +0,0 @@ -#include "tree_sitter/api.h" -#include "./alloc.h" -#include "./tree_cursor.h" -#include "./language.h" -#include "./tree.h" - -typedef struct { - Subtree parent; - const TSTree *tree; - Length position; - uint32_t child_index; - uint32_t structural_child_index; - const TSSymbol *alias_sequence; -} CursorChildIterator; - -// CursorChildIterator - -static inline CursorChildIterator ts_tree_cursor_iterate_children(const TreeCursor *self) { - TreeCursorEntry *last_entry = array_back(&self->stack); - if (ts_subtree_child_count(*last_entry->subtree) == 0) { - return (CursorChildIterator) {NULL_SUBTREE, self->tree, length_zero(), 0, 0, NULL}; - } - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - last_entry->subtree->ptr->production_id - ); - return (CursorChildIterator) { - .tree = self->tree, - .parent = *last_entry->subtree, - .position = last_entry->position, - .child_index = 0, - .structural_child_index = 0, - .alias_sequence = alias_sequence, - }; -} - -static inline bool ts_tree_cursor_child_iterator_next(CursorChildIterator *self, - TreeCursorEntry *result, - bool *visible) { - if (!self->parent.ptr || self->child_index == self->parent.ptr->child_count) return false; - const Subtree *child = &self->parent.ptr->children[self->child_index]; - *result = (TreeCursorEntry) { - .subtree = child, - .position = self->position, - .child_index = self->child_index, - .structural_child_index = self->structural_child_index, - }; - *visible = ts_subtree_visible(*child); - bool extra = ts_subtree_extra(*child); - if (!extra && self->alias_sequence) { - *visible |= self->alias_sequence[self->structural_child_index]; - self->structural_child_index++; - } - - self->position = length_add(self->position, ts_subtree_size(*child)); - self->child_index++; - - if (self->child_index < self->parent.ptr->child_count) { - Subtree next_child = self->parent.ptr->children[self->child_index]; - self->position = length_add(self->position, ts_subtree_padding(next_child)); - } - - return true; -} - -// TSTreeCursor - lifecycle - -TSTreeCursor ts_tree_cursor_new(TSNode node) { - TSTreeCursor self = {NULL, NULL, {0, 0}}; - ts_tree_cursor_init((TreeCursor *)&self, node); - return self; -} - -void ts_tree_cursor_reset(TSTreeCursor *_self, TSNode node) { - ts_tree_cursor_init((TreeCursor *)_self, node); -} - -void ts_tree_cursor_init(TreeCursor *self, TSNode node) { - self->tree = node.tree; - array_clear(&self->stack); - array_push(&self->stack, ((TreeCursorEntry) { - .subtree = (const Subtree *)node.id, - .position = { - ts_node_start_byte(node), - ts_node_start_point(node) - }, - .child_index = 0, - .structural_child_index = 0, - })); -} - -void ts_tree_cursor_delete(TSTreeCursor *_self) { - TreeCursor *self = (TreeCursor *)_self; - array_delete(&self->stack); -} - -// TSTreeCursor - walking the tree - -bool ts_tree_cursor_goto_first_child(TSTreeCursor *_self) { - TreeCursor *self = (TreeCursor *)_self; - - bool did_descend; - do { - did_descend = false; - - bool visible; - TreeCursorEntry entry; - CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); - while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { - if (visible) { - array_push(&self->stack, entry); - return true; - } - - if (ts_subtree_visible_child_count(*entry.subtree) > 0) { - array_push(&self->stack, entry); - did_descend = true; - break; - } - } - } while (did_descend); - - return false; -} - -int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *_self, uint32_t goal_byte) { - TreeCursor *self = (TreeCursor *)_self; - uint32_t initial_size = self->stack.size; - uint32_t visible_child_index = 0; - - bool did_descend; - do { - did_descend = false; - - bool visible; - TreeCursorEntry entry; - CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); - while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { - uint32_t end_byte = entry.position.bytes + ts_subtree_size(*entry.subtree).bytes; - bool at_goal = end_byte > goal_byte; - uint32_t visible_child_count = ts_subtree_visible_child_count(*entry.subtree); - - if (at_goal) { - if (visible) { - array_push(&self->stack, entry); - return visible_child_index; - } - - if (visible_child_count > 0) { - array_push(&self->stack, entry); - did_descend = true; - break; - } - } else if (visible) { - visible_child_index++; - } else { - visible_child_index += visible_child_count; - } - } - } while (did_descend); - - if (self->stack.size > initial_size && - ts_tree_cursor_goto_next_sibling((TSTreeCursor *)self)) { - return visible_child_index; - } - - self->stack.size = initial_size; - return -1; -} - -bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *_self) { - TreeCursor *self = (TreeCursor *)_self; - uint32_t initial_size = self->stack.size; - - while (self->stack.size > 1) { - TreeCursorEntry entry = array_pop(&self->stack); - CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); - iterator.child_index = entry.child_index; - iterator.structural_child_index = entry.structural_child_index; - iterator.position = entry.position; - - bool visible = false; - ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible); - if (visible && self->stack.size + 1 < initial_size) break; - - while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { - if (visible) { - array_push(&self->stack, entry); - return true; - } - - if (ts_subtree_visible_child_count(*entry.subtree)) { - array_push(&self->stack, entry); - ts_tree_cursor_goto_first_child(_self); - return true; - } - } - } - - self->stack.size = initial_size; - return false; -} - -bool ts_tree_cursor_goto_parent(TSTreeCursor *_self) { - TreeCursor *self = (TreeCursor *)_self; - for (unsigned i = self->stack.size - 2; i + 1 > 0; i--) { - TreeCursorEntry *entry = &self->stack.contents[i]; - bool is_aliased = false; - if (i > 0) { - TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - parent_entry->subtree->ptr->production_id - ); - is_aliased = alias_sequence && alias_sequence[entry->structural_child_index]; - } - if (ts_subtree_visible(*entry->subtree) || is_aliased) { - self->stack.size = i + 1; - return true; - } - } - return false; -} - -TSNode ts_tree_cursor_current_node(const TSTreeCursor *_self) { - const TreeCursor *self = (const TreeCursor *)_self; - TreeCursorEntry *last_entry = array_back(&self->stack); - TSSymbol alias_symbol = 0; - if (self->stack.size > 1) { - TreeCursorEntry *parent_entry = &self->stack.contents[self->stack.size - 2]; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - parent_entry->subtree->ptr->production_id - ); - if (alias_sequence && !ts_subtree_extra(*last_entry->subtree)) { - alias_symbol = alias_sequence[last_entry->structural_child_index]; - } - } - return ts_node_new( - self->tree, - last_entry->subtree, - last_entry->position, - alias_symbol - ); -} - -TSFieldId ts_tree_cursor_current_status( - const TSTreeCursor *_self, - bool *can_have_later_siblings, - bool *can_have_later_siblings_with_this_field -) { - const TreeCursor *self = (const TreeCursor *)_self; - TSFieldId result = 0; - *can_have_later_siblings = false; - *can_have_later_siblings_with_this_field = false; - - // Walk up the tree, visiting the current node and its invisible ancestors, - // because fields can refer to nodes through invisible *wrapper* nodes, - for (unsigned i = self->stack.size - 1; i > 0; i--) { - TreeCursorEntry *entry = &self->stack.contents[i]; - TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; - - // Stop walking up when a visible ancestor is found. - if (i != self->stack.size - 1) { - if (ts_subtree_visible(*entry->subtree)) break; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - parent_entry->subtree->ptr->production_id - ); - if (alias_sequence && alias_sequence[entry->structural_child_index]) { - break; - } - } - - if (ts_subtree_child_count(*parent_entry->subtree) > entry->child_index + 1) { - *can_have_later_siblings = true; - } - - if (ts_subtree_extra(*entry->subtree)) break; - - const TSFieldMapEntry *field_map, *field_map_end; - ts_language_field_map( - self->tree->language, - parent_entry->subtree->ptr->production_id, - &field_map, &field_map_end - ); - - // Look for a field name associated with the current node. - if (!result) { - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (!i->inherited && i->child_index == entry->structural_child_index) { - result = i->field_id; - *can_have_later_siblings_with_this_field = false; - break; - } - } - } - - // Determine if there other later siblings with the same field name. - if (result) { - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (i->field_id == result && i->child_index > entry->structural_child_index) { - *can_have_later_siblings_with_this_field = true; - break; - } - } - } - } - - return result; -} - -TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *_self) { - const TreeCursor *self = (const TreeCursor *)_self; - - // Walk up the tree, visiting the current node and its invisible ancestors. - for (unsigned i = self->stack.size - 1; i > 0; i--) { - TreeCursorEntry *entry = &self->stack.contents[i]; - TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; - - // Stop walking up when another visible node is found. - if (i != self->stack.size - 1) { - if (ts_subtree_visible(*entry->subtree)) break; - const TSSymbol *alias_sequence = ts_language_alias_sequence( - self->tree->language, - parent_entry->subtree->ptr->production_id - ); - if (alias_sequence && alias_sequence[entry->structural_child_index]) { - break; - } - } - - if (ts_subtree_extra(*entry->subtree)) break; - - const TSFieldMapEntry *field_map, *field_map_end; - ts_language_field_map( - self->tree->language, - parent_entry->subtree->ptr->production_id, - &field_map, &field_map_end - ); - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (!i->inherited && i->child_index == entry->structural_child_index) { - return i->field_id; - } - } - } - return 0; -} - -const char *ts_tree_cursor_current_field_name(const TSTreeCursor *_self) { - TSFieldId id = ts_tree_cursor_current_field_id(_self); - if (id) { - const TreeCursor *self = (const TreeCursor *)_self; - return self->tree->language->field_names[id]; - } else { - return NULL; - } -} - -TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *_cursor) { - const TreeCursor *cursor = (const TreeCursor *)_cursor; - TSTreeCursor res = {NULL, NULL, {0, 0}}; - TreeCursor *copy = (TreeCursor *)&res; - copy->tree = cursor->tree; - array_push_all(©->stack, &cursor->stack); - return res; -} diff --git a/src/tree_sitter/tree_cursor.h b/src/tree_sitter/tree_cursor.h deleted file mode 100644 index 5a39dd278c..0000000000 --- a/src/tree_sitter/tree_cursor.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef TREE_SITTER_TREE_CURSOR_H_ -#define TREE_SITTER_TREE_CURSOR_H_ - -#include "./subtree.h" - -typedef struct { - const Subtree *subtree; - Length position; - uint32_t child_index; - uint32_t structural_child_index; -} TreeCursorEntry; - -typedef struct { - const TSTree *tree; - Array(TreeCursorEntry) stack; -} TreeCursor; - -void ts_tree_cursor_init(TreeCursor *, TSNode); -TSFieldId ts_tree_cursor_current_status(const TSTreeCursor *, bool *, bool *); - -#endif // TREE_SITTER_TREE_CURSOR_H_ diff --git a/src/tree_sitter/treesitter_commit_hash.txt b/src/tree_sitter/treesitter_commit_hash.txt deleted file mode 100644 index bd7fcfbe76..0000000000 --- a/src/tree_sitter/treesitter_commit_hash.txt +++ /dev/null @@ -1 +0,0 @@ -81d533d2d1b580fdb507accabc91ceddffb5b6f0 diff --git a/src/tree_sitter/unicode.h b/src/tree_sitter/unicode.h deleted file mode 100644 index 2ab51c2a3a..0000000000 --- a/src/tree_sitter/unicode.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef TREE_SITTER_UNICODE_H_ -#define TREE_SITTER_UNICODE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <limits.h> -#include <stdint.h> - -#define U_EXPORT -#define U_EXPORT2 -#include "./unicode/utf8.h" -#include "./unicode/utf16.h" - -static const int32_t TS_DECODE_ERROR = U_SENTINEL; - -// These functions read one unicode code point from the given string, -// returning the number of bytes consumed. -typedef uint32_t (*UnicodeDecodeFunction)( - const uint8_t *string, - uint32_t length, - int32_t *code_point -); - -static inline uint32_t ts_decode_utf8( - const uint8_t *string, - uint32_t length, - int32_t *code_point -) { - uint32_t i = 0; - U8_NEXT(string, i, length, *code_point); - return i; -} - -static inline uint32_t ts_decode_utf16( - const uint8_t *string, - uint32_t length, - int32_t *code_point -) { - uint32_t i = 0; - U16_NEXT(((uint16_t *)string), i, length, *code_point); - return i * 2; -} - -#ifdef __cplusplus -} -#endif - -#endif // TREE_SITTER_UNICODE_H_ diff --git a/src/tree_sitter/unicode/ICU_SHA b/src/tree_sitter/unicode/ICU_SHA deleted file mode 100644 index 3622283ba3..0000000000 --- a/src/tree_sitter/unicode/ICU_SHA +++ /dev/null @@ -1 +0,0 @@ -552b01f61127d30d6589aa4bf99468224979b661 diff --git a/src/tree_sitter/unicode/LICENSE b/src/tree_sitter/unicode/LICENSE deleted file mode 100644 index 2e01e36876..0000000000 --- a/src/tree_sitter/unicode/LICENSE +++ /dev/null @@ -1,414 +0,0 @@ -COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later) - -Copyright © 1991-2019 Unicode, Inc. All rights reserved. -Distributed under the Terms of Use in https://www.unicode.org/copyright.html. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Unicode data files and any associated documentation -(the "Data Files") or Unicode software and any associated documentation -(the "Software") to deal in the Data Files or Software -without restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, and/or sell copies of -the Data Files or Software, and to permit persons to whom the Data Files -or Software are furnished to do so, provided that either -(a) this copyright and permission notice appear with all copies -of the Data Files or Software, or -(b) this copyright and permission notice appear in associated -Documentation. - -THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT OF THIRD PARTY RIGHTS. -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS -NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL -DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, -DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THE DATA FILES OR SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, -use or other dealings in these Data Files or Software without prior -written authorization of the copyright holder. - ---------------------- - -Third-Party Software Licenses - -This section contains third-party software notices and/or additional -terms for licensed third-party software components included within ICU -libraries. - -1. ICU License - ICU 1.8.1 to ICU 57.1 - -COPYRIGHT AND PERMISSION NOTICE - -Copyright (c) 1995-2016 International Business Machines Corporation and others -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, and/or sell copies of the Software, and to permit persons -to whom the Software is furnished to do so, provided that the above -copyright notice(s) and this permission notice appear in all copies of -the Software and that both the above copyright notice(s) and this -permission notice appear in supporting documentation. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY -SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER -RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, use -or other dealings in this Software without prior written authorization -of the copyright holder. - -All trademarks and registered trademarks mentioned herein are the -property of their respective owners. - -2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt) - - # The Google Chrome software developed by Google is licensed under - # the BSD license. Other software included in this distribution is - # provided under other licenses, as set forth below. - # - # The BSD License - # http://opensource.org/licenses/bsd-license.php - # Copyright (C) 2006-2008, Google Inc. - # - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions are met: - # - # Redistributions of source code must retain the above copyright notice, - # this list of conditions and the following disclaimer. - # Redistributions in binary form must reproduce the above - # copyright notice, this list of conditions and the following - # disclaimer in the documentation and/or other materials provided with - # the distribution. - # Neither the name of Google Inc. nor the names of its - # contributors may be used to endorse or promote products derived from - # this software without specific prior written permission. - # - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - # - # - # The word list in cjdict.txt are generated by combining three word lists - # listed below with further processing for compound word breaking. The - # frequency is generated with an iterative training against Google web - # corpora. - # - # * Libtabe (Chinese) - # - https://sourceforge.net/project/?group_id=1519 - # - Its license terms and conditions are shown below. - # - # * IPADIC (Japanese) - # - http://chasen.aist-nara.ac.jp/chasen/distribution.html - # - Its license terms and conditions are shown below. - # - # ---------COPYING.libtabe ---- BEGIN-------------------- - # - # /* - # * Copyright (c) 1999 TaBE Project. - # * Copyright (c) 1999 Pai-Hsiang Hsiao. - # * All rights reserved. - # * - # * Redistribution and use in source and binary forms, with or without - # * modification, are permitted provided that the following conditions - # * are met: - # * - # * . Redistributions of source code must retain the above copyright - # * notice, this list of conditions and the following disclaimer. - # * . Redistributions in binary form must reproduce the above copyright - # * notice, this list of conditions and the following disclaimer in - # * the documentation and/or other materials provided with the - # * distribution. - # * . Neither the name of the TaBE Project nor the names of its - # * contributors may be used to endorse or promote products derived - # * from this software without specific prior written permission. - # * - # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # * OF THE POSSIBILITY OF SUCH DAMAGE. - # */ - # - # /* - # * Copyright (c) 1999 Computer Systems and Communication Lab, - # * Institute of Information Science, Academia - # * Sinica. All rights reserved. - # * - # * Redistribution and use in source and binary forms, with or without - # * modification, are permitted provided that the following conditions - # * are met: - # * - # * . Redistributions of source code must retain the above copyright - # * notice, this list of conditions and the following disclaimer. - # * . Redistributions in binary form must reproduce the above copyright - # * notice, this list of conditions and the following disclaimer in - # * the documentation and/or other materials provided with the - # * distribution. - # * . Neither the name of the Computer Systems and Communication Lab - # * nor the names of its contributors may be used to endorse or - # * promote products derived from this software without specific - # * prior written permission. - # * - # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # * OF THE POSSIBILITY OF SUCH DAMAGE. - # */ - # - # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, - # University of Illinois - # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 - # - # ---------------COPYING.libtabe-----END-------------------------------- - # - # - # ---------------COPYING.ipadic-----BEGIN------------------------------- - # - # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science - # and Technology. All Rights Reserved. - # - # Use, reproduction, and distribution of this software is permitted. - # Any copy of this software, whether in its original form or modified, - # must include both the above copyright notice and the following - # paragraphs. - # - # Nara Institute of Science and Technology (NAIST), - # the copyright holders, disclaims all warranties with regard to this - # software, including all implied warranties of merchantability and - # fitness, in no event shall NAIST be liable for - # any special, indirect or consequential damages or any damages - # whatsoever resulting from loss of use, data or profits, whether in an - # action of contract, negligence or other tortuous action, arising out - # of or in connection with the use or performance of this software. - # - # A large portion of the dictionary entries - # originate from ICOT Free Software. The following conditions for ICOT - # Free Software applies to the current dictionary as well. - # - # Each User may also freely distribute the Program, whether in its - # original form or modified, to any third party or parties, PROVIDED - # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear - # on, or be attached to, the Program, which is distributed substantially - # in the same form as set out herein and that such intended - # distribution, if actually made, will neither violate or otherwise - # contravene any of the laws and regulations of the countries having - # jurisdiction over the User or the intended distribution itself. - # - # NO WARRANTY - # - # The program was produced on an experimental basis in the course of the - # research and development conducted during the project and is provided - # to users as so produced on an experimental basis. Accordingly, the - # program is provided without any warranty whatsoever, whether express, - # implied, statutory or otherwise. The term "warranty" used herein - # includes, but is not limited to, any warranty of the quality, - # performance, merchantability and fitness for a particular purpose of - # the program and the nonexistence of any infringement or violation of - # any right of any third party. - # - # Each user of the program will agree and understand, and be deemed to - # have agreed and understood, that there is no warranty whatsoever for - # the program and, accordingly, the entire risk arising from or - # otherwise connected with the program is assumed by the user. - # - # Therefore, neither ICOT, the copyright holder, or any other - # organization that participated in or was otherwise related to the - # development of the program and their respective officials, directors, - # officers and other employees shall be held liable for any and all - # damages, including, without limitation, general, special, incidental - # and consequential damages, arising out of or otherwise in connection - # with the use or inability to use the program or any product, material - # or result produced or otherwise obtained by using the program, - # regardless of whether they have been advised of, or otherwise had - # knowledge of, the possibility of such damages at any time during the - # project or thereafter. Each user will be deemed to have agreed to the - # foregoing by his or her commencement of use of the program. The term - # "use" as used herein includes, but is not limited to, the use, - # modification, copying and distribution of the program and the - # production of secondary products from the program. - # - # In the case where the program, whether in its original form or - # modified, was distributed or delivered to or received by a user from - # any person, organization or entity other than ICOT, unless it makes or - # grants independently of ICOT any specific warranty to the user in - # writing, such person, organization or entity, will also be exempted - # from and not be held liable to the user for any such damages as noted - # above as far as the program is concerned. - # - # ---------------COPYING.ipadic-----END---------------------------------- - -3. Lao Word Break Dictionary Data (laodict.txt) - - # Copyright (c) 2013 International Business Machines Corporation - # and others. All Rights Reserved. - # - # Project: http://code.google.com/p/lao-dictionary/ - # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt - # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt - # (copied below) - # - # This file is derived from the above dictionary, with slight - # modifications. - # ---------------------------------------------------------------------- - # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, - # are permitted provided that the following conditions are met: - # - # - # Redistributions of source code must retain the above copyright notice, this - # list of conditions and the following disclaimer. Redistributions in - # binary form must reproduce the above copyright notice, this list of - # conditions and the following disclaimer in the documentation and/or - # other materials provided with the distribution. - # - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - # OF THE POSSIBILITY OF SUCH DAMAGE. - # -------------------------------------------------------------------------- - -4. Burmese Word Break Dictionary Data (burmesedict.txt) - - # Copyright (c) 2014 International Business Machines Corporation - # and others. All Rights Reserved. - # - # This list is part of a project hosted at: - # github.com/kanyawtech/myanmar-karen-word-lists - # - # -------------------------------------------------------------------------- - # Copyright (c) 2013, LeRoy Benjamin Sharon - # All rights reserved. - # - # Redistribution and use in source and binary forms, with or without - # modification, are permitted provided that the following conditions - # are met: Redistributions of source code must retain the above - # copyright notice, this list of conditions and the following - # disclaimer. Redistributions in binary form must reproduce the - # above copyright notice, this list of conditions and the following - # disclaimer in the documentation and/or other materials provided - # with the distribution. - # - # Neither the name Myanmar Karen Word Lists, nor the names of its - # contributors may be used to endorse or promote products derived - # from this software without specific prior written permission. - # - # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS - # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR - # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - # SUCH DAMAGE. - # -------------------------------------------------------------------------- - -5. Time Zone Database - - ICU uses the public domain data and code derived from Time Zone -Database for its time zone support. The ownership of the TZ database -is explained in BCP 175: Procedure for Maintaining the Time Zone -Database section 7. - - # 7. Database Ownership - # - # The TZ database itself is not an IETF Contribution or an IETF - # document. Rather it is a pre-existing and regularly updated work - # that is in the public domain, and is intended to remain in the - # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do - # not apply to the TZ Database or contributions that individuals make - # to it. Should any claims be made and substantiated against the TZ - # Database, the organization that is providing the IANA - # Considerations defined in this RFC, under the memorandum of - # understanding with the IETF, currently ICANN, may act in accordance - # with all competent court orders. No ownership claims will be made - # by ICANN or the IETF Trust on the database or the code. Any person - # making a contribution to the database or code waives all rights to - # future claims in that contribution or in the TZ Database. - -6. Google double-conversion - -Copyright 2006-2011, the V8 project authors. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/tree_sitter/unicode/README.md b/src/tree_sitter/unicode/README.md deleted file mode 100644 index 623b8e3843..0000000000 --- a/src/tree_sitter/unicode/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# ICU Parts - -This directory contains a small subset of files from the Unicode organization's [ICU repository](https://github.com/unicode-org/icu). - -### License - -The license for these files is contained in the `LICENSE` file within this directory. - -### Contents - -* Source files taken from the [`icu4c/source/common/unicode`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c/source/common/unicode) directory: - * `utf8.h` - * `utf16.h` - * `umachine.h` -* Empty source files that are referenced by the above source files, but whose original contents in `libicu` are not needed: - * `ptypes.h` - * `urename.h` - * `utf.h` -* `ICU_SHA` - File containing the Git SHA of the commit in the `icu` repository from which the files were obtained. -* `LICENSE` - The license file from the [`icu4c`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c) directory of the `icu` repository. -* `README.md` - This text file. - -### Updating ICU - -To incorporate changes from the upstream `icu` repository: - -* Update `ICU_SHA` with the new Git SHA. -* Update `LICENSE` with the license text from the directory mentioned above. -* Update `utf8.h`, `utf16.h`, and `umachine.h` with their new contents in the `icu` repository. diff --git a/src/tree_sitter/unicode/ptypes.h b/src/tree_sitter/unicode/ptypes.h deleted file mode 100644 index ac79ad0f98..0000000000 --- a/src/tree_sitter/unicode/ptypes.h +++ /dev/null @@ -1 +0,0 @@ -// This file must exist in order for `utf8.h` and `utf16.h` to be used. diff --git a/src/tree_sitter/unicode/umachine.h b/src/tree_sitter/unicode/umachine.h deleted file mode 100644 index bbf6ef9c8b..0000000000 --- a/src/tree_sitter/unicode/umachine.h +++ /dev/null @@ -1,448 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -****************************************************************************** -* -* Copyright (C) 1999-2015, International Business Machines -* Corporation and others. All Rights Reserved. -* -****************************************************************************** -* file name: umachine.h -* encoding: UTF-8 -* tab size: 8 (not used) -* indentation:4 -* -* created on: 1999sep13 -* created by: Markus W. Scherer -* -* This file defines basic types and constants for ICU to be -* platform-independent. umachine.h and utf.h are included into -* utypes.h to provide all the general definitions for ICU. -* All of these definitions used to be in utypes.h before -* the UTF-handling macros made this unmaintainable. -*/ - -#ifndef __UMACHINE_H__ -#define __UMACHINE_H__ - - -/** - * \file - * \brief Basic types and constants for UTF - * - * <h2> Basic types and constants for UTF </h2> - * This file defines basic types and constants for utf.h to be - * platform-independent. umachine.h and utf.h are included into - * utypes.h to provide all the general definitions for ICU. - * All of these definitions used to be in utypes.h before - * the UTF-handling macros made this unmaintainable. - * - */ -/*==========================================================================*/ -/* Include platform-dependent definitions */ -/* which are contained in the platform-specific file platform.h */ -/*==========================================================================*/ - -#include "./ptypes.h" /* platform.h is included in ptypes.h */ - -/* - * ANSI C headers: - * stddef.h defines wchar_t - */ -#include <stddef.h> - -/*==========================================================================*/ -/* For C wrappers, we use the symbol U_STABLE. */ -/* This works properly if the includer is C or C++. */ -/* Functions are declared U_STABLE return-type U_EXPORT2 function-name()... */ -/*==========================================================================*/ - -/** - * \def U_CFUNC - * This is used in a declaration of a library private ICU C function. - * @stable ICU 2.4 - */ - -/** - * \def U_CDECL_BEGIN - * This is used to begin a declaration of a library private ICU C API. - * @stable ICU 2.4 - */ - -/** - * \def U_CDECL_END - * This is used to end a declaration of a library private ICU C API - * @stable ICU 2.4 - */ - -#ifdef __cplusplus -# define U_CFUNC extern "C" -# define U_CDECL_BEGIN extern "C" { -# define U_CDECL_END } -#else -# define U_CFUNC extern -# define U_CDECL_BEGIN -# define U_CDECL_END -#endif - -#ifndef U_ATTRIBUTE_DEPRECATED -/** - * \def U_ATTRIBUTE_DEPRECATED - * This is used for GCC specific attributes - * @internal - */ -#if U_GCC_MAJOR_MINOR >= 302 -# define U_ATTRIBUTE_DEPRECATED __attribute__ ((deprecated)) -/** - * \def U_ATTRIBUTE_DEPRECATED - * This is used for Visual C++ specific attributes - * @internal - */ -#elif defined(_MSC_VER) && (_MSC_VER >= 1400) -# define U_ATTRIBUTE_DEPRECATED __declspec(deprecated) -#else -# define U_ATTRIBUTE_DEPRECATED -#endif -#endif - -/** This is used to declare a function as a public ICU C API @stable ICU 2.0*/ -#define U_CAPI U_CFUNC U_EXPORT -/** This is used to declare a function as a stable public ICU C API*/ -#define U_STABLE U_CAPI -/** This is used to declare a function as a draft public ICU C API */ -#define U_DRAFT U_CAPI -/** This is used to declare a function as a deprecated public ICU C API */ -#define U_DEPRECATED U_CAPI U_ATTRIBUTE_DEPRECATED -/** This is used to declare a function as an obsolete public ICU C API */ -#define U_OBSOLETE U_CAPI -/** This is used to declare a function as an internal ICU C API */ -#define U_INTERNAL U_CAPI - -/** - * \def U_OVERRIDE - * Defined to the C++11 "override" keyword if available. - * Denotes a class or member which is an override of the base class. - * May result in an error if it applied to something not an override. - * @internal - */ -#ifndef U_OVERRIDE -#define U_OVERRIDE override -#endif - -/** - * \def U_FINAL - * Defined to the C++11 "final" keyword if available. - * Denotes a class or member which may not be overridden in subclasses. - * May result in an error if subclasses attempt to override. - * @internal - */ -#if !defined(U_FINAL) || defined(U_IN_DOXYGEN) -#define U_FINAL final -#endif - -// Before ICU 65, function-like, multi-statement ICU macros were just defined as -// series of statements wrapped in { } blocks and the caller could choose to -// either treat them as if they were actual functions and end the invocation -// with a trailing ; creating an empty statement after the block or else omit -// this trailing ; using the knowledge that the macro would expand to { }. -// -// But doing so doesn't work well with macros that look like functions and -// compiler warnings about empty statements (ICU-20601) and ICU 65 therefore -// switches to the standard solution of wrapping such macros in do { } while. -// -// This will however break existing code that depends on being able to invoke -// these macros without a trailing ; so to be able to remain compatible with -// such code the wrapper is itself defined as macros so that it's possible to -// build ICU 65 and later with the old macro behaviour, like this: -// -// CPPFLAGS='-DUPRV_BLOCK_MACRO_BEGIN="" -DUPRV_BLOCK_MACRO_END=""' -// runConfigureICU ... - -/** - * \def UPRV_BLOCK_MACRO_BEGIN - * Defined as the "do" keyword by default. - * @internal - */ -#ifndef UPRV_BLOCK_MACRO_BEGIN -#define UPRV_BLOCK_MACRO_BEGIN do -#endif - -/** - * \def UPRV_BLOCK_MACRO_END - * Defined as "while (FALSE)" by default. - * @internal - */ -#ifndef UPRV_BLOCK_MACRO_END -#define UPRV_BLOCK_MACRO_END while (FALSE) -#endif - -/*==========================================================================*/ -/* limits for int32_t etc., like in POSIX inttypes.h */ -/*==========================================================================*/ - -#ifndef INT8_MIN -/** The smallest value an 8 bit signed integer can hold @stable ICU 2.0 */ -# define INT8_MIN ((int8_t)(-128)) -#endif -#ifndef INT16_MIN -/** The smallest value a 16 bit signed integer can hold @stable ICU 2.0 */ -# define INT16_MIN ((int16_t)(-32767-1)) -#endif -#ifndef INT32_MIN -/** The smallest value a 32 bit signed integer can hold @stable ICU 2.0 */ -# define INT32_MIN ((int32_t)(-2147483647-1)) -#endif - -#ifndef INT8_MAX -/** The largest value an 8 bit signed integer can hold @stable ICU 2.0 */ -# define INT8_MAX ((int8_t)(127)) -#endif -#ifndef INT16_MAX -/** The largest value a 16 bit signed integer can hold @stable ICU 2.0 */ -# define INT16_MAX ((int16_t)(32767)) -#endif -#ifndef INT32_MAX -/** The largest value a 32 bit signed integer can hold @stable ICU 2.0 */ -# define INT32_MAX ((int32_t)(2147483647)) -#endif - -#ifndef UINT8_MAX -/** The largest value an 8 bit unsigned integer can hold @stable ICU 2.0 */ -# define UINT8_MAX ((uint8_t)(255U)) -#endif -#ifndef UINT16_MAX -/** The largest value a 16 bit unsigned integer can hold @stable ICU 2.0 */ -# define UINT16_MAX ((uint16_t)(65535U)) -#endif -#ifndef UINT32_MAX -/** The largest value a 32 bit unsigned integer can hold @stable ICU 2.0 */ -# define UINT32_MAX ((uint32_t)(4294967295U)) -#endif - -#if defined(U_INT64_T_UNAVAILABLE) -# error int64_t is required for decimal format and rule-based number format. -#else -# ifndef INT64_C -/** - * Provides a platform independent way to specify a signed 64-bit integer constant. - * note: may be wrong for some 64 bit platforms - ensure your compiler provides INT64_C - * @stable ICU 2.8 - */ -# define INT64_C(c) c ## LL -# endif -# ifndef UINT64_C -/** - * Provides a platform independent way to specify an unsigned 64-bit integer constant. - * note: may be wrong for some 64 bit platforms - ensure your compiler provides UINT64_C - * @stable ICU 2.8 - */ -# define UINT64_C(c) c ## ULL -# endif -# ifndef U_INT64_MIN -/** The smallest value a 64 bit signed integer can hold @stable ICU 2.8 */ -# define U_INT64_MIN ((int64_t)(INT64_C(-9223372036854775807)-1)) -# endif -# ifndef U_INT64_MAX -/** The largest value a 64 bit signed integer can hold @stable ICU 2.8 */ -# define U_INT64_MAX ((int64_t)(INT64_C(9223372036854775807))) -# endif -# ifndef U_UINT64_MAX -/** The largest value a 64 bit unsigned integer can hold @stable ICU 2.8 */ -# define U_UINT64_MAX ((uint64_t)(UINT64_C(18446744073709551615))) -# endif -#endif - -/*==========================================================================*/ -/* Boolean data type */ -/*==========================================================================*/ - -/** The ICU boolean type @stable ICU 2.0 */ -typedef int8_t UBool; - -#ifndef TRUE -/** The TRUE value of a UBool @stable ICU 2.0 */ -# define TRUE 1 -#endif -#ifndef FALSE -/** The FALSE value of a UBool @stable ICU 2.0 */ -# define FALSE 0 -#endif - - -/*==========================================================================*/ -/* Unicode data types */ -/*==========================================================================*/ - -/* wchar_t-related definitions -------------------------------------------- */ - -/* - * \def U_WCHAR_IS_UTF16 - * Defined if wchar_t uses UTF-16. - * - * @stable ICU 2.0 - */ -/* - * \def U_WCHAR_IS_UTF32 - * Defined if wchar_t uses UTF-32. - * - * @stable ICU 2.0 - */ -#if !defined(U_WCHAR_IS_UTF16) && !defined(U_WCHAR_IS_UTF32) -# ifdef __STDC_ISO_10646__ -# if (U_SIZEOF_WCHAR_T==2) -# define U_WCHAR_IS_UTF16 -# elif (U_SIZEOF_WCHAR_T==4) -# define U_WCHAR_IS_UTF32 -# endif -# elif defined __UCS2__ -# if (U_PF_OS390 <= U_PLATFORM && U_PLATFORM <= U_PF_OS400) && (U_SIZEOF_WCHAR_T==2) -# define U_WCHAR_IS_UTF16 -# endif -# elif defined(__UCS4__) || (U_PLATFORM == U_PF_OS400 && defined(__UTF32__)) -# if (U_SIZEOF_WCHAR_T==4) -# define U_WCHAR_IS_UTF32 -# endif -# elif U_PLATFORM_IS_DARWIN_BASED || (U_SIZEOF_WCHAR_T==4 && U_PLATFORM_IS_LINUX_BASED) -# define U_WCHAR_IS_UTF32 -# elif U_PLATFORM_HAS_WIN32_API -# define U_WCHAR_IS_UTF16 -# endif -#endif - -/* UChar and UChar32 definitions -------------------------------------------- */ - -/** Number of bytes in a UChar. @stable ICU 2.0 */ -#define U_SIZEOF_UCHAR 2 - -/** - * \def U_CHAR16_IS_TYPEDEF - * If 1, then char16_t is a typedef and not a real type (yet) - * @internal - */ -#if (U_PLATFORM == U_PF_AIX) && defined(__cplusplus) &&(U_CPLUSPLUS_VERSION < 11) -// for AIX, uchar.h needs to be included -# include <uchar.h> -# define U_CHAR16_IS_TYPEDEF 1 -#elif defined(_MSC_VER) && (_MSC_VER < 1900) -// Versions of Visual Studio/MSVC below 2015 do not support char16_t as a real type, -// and instead use a typedef. https://msdn.microsoft.com/library/bb531344.aspx -# define U_CHAR16_IS_TYPEDEF 1 -#else -# define U_CHAR16_IS_TYPEDEF 0 -#endif - - -/** - * \var UChar - * - * The base type for UTF-16 code units and pointers. - * Unsigned 16-bit integer. - * Starting with ICU 59, C++ API uses char16_t directly, while C API continues to use UChar. - * - * UChar is configurable by defining the macro UCHAR_TYPE - * on the preprocessor or compiler command line: - * -DUCHAR_TYPE=uint16_t or -DUCHAR_TYPE=wchar_t (if U_SIZEOF_WCHAR_T==2) etc. - * (The UCHAR_TYPE can also be \#defined earlier in this file, for outside the ICU library code.) - * This is for transitional use from application code that uses uint16_t or wchar_t for UTF-16. - * - * The default is UChar=char16_t. - * - * C++11 defines char16_t as bit-compatible with uint16_t, but as a distinct type. - * - * In C, char16_t is a simple typedef of uint_least16_t. - * ICU requires uint_least16_t=uint16_t for data memory mapping. - * On macOS, char16_t is not available because the uchar.h standard header is missing. - * - * @stable ICU 4.4 - */ - -#if 1 - // #if 1 is normal. UChar defaults to char16_t in C++. - // For configuration testing of UChar=uint16_t temporarily change this to #if 0. - // The intltest Makefile #defines UCHAR_TYPE=char16_t, - // so we only #define it to uint16_t if it is undefined so far. -#elif !defined(UCHAR_TYPE) -# define UCHAR_TYPE uint16_t -#endif - -#if defined(U_COMBINED_IMPLEMENTATION) || defined(U_COMMON_IMPLEMENTATION) || \ - defined(U_I18N_IMPLEMENTATION) || defined(U_IO_IMPLEMENTATION) - // Inside the ICU library code, never configurable. - typedef char16_t UChar; -#elif defined(UCHAR_TYPE) - typedef UCHAR_TYPE UChar; -#elif defined(__cplusplus) - typedef char16_t UChar; -#else - typedef uint16_t UChar; -#endif - -/** - * \var OldUChar - * Default ICU 58 definition of UChar. - * A base type for UTF-16 code units and pointers. - * Unsigned 16-bit integer. - * - * Define OldUChar to be wchar_t if that is 16 bits wide. - * If wchar_t is not 16 bits wide, then define UChar to be uint16_t. - * - * This makes the definition of OldUChar platform-dependent - * but allows direct string type compatibility with platforms with - * 16-bit wchar_t types. - * - * This is how UChar was defined in ICU 58, for transition convenience. - * Exception: ICU 58 UChar was defined to UCHAR_TYPE if that macro was defined. - * The current UChar responds to UCHAR_TYPE but OldUChar does not. - * - * @stable ICU 59 - */ -#if U_SIZEOF_WCHAR_T==2 - typedef wchar_t OldUChar; -#elif defined(__CHAR16_TYPE__) - typedef __CHAR16_TYPE__ OldUChar; -#else - typedef uint16_t OldUChar; -#endif - -/** - * Define UChar32 as a type for single Unicode code points. - * UChar32 is a signed 32-bit integer (same as int32_t). - * - * The Unicode code point range is 0..0x10ffff. - * All other values (negative or >=0x110000) are illegal as Unicode code points. - * They may be used as sentinel values to indicate "done", "error" - * or similar non-code point conditions. - * - * Before ICU 2.4 (Jitterbug 2146), UChar32 was defined - * to be wchar_t if that is 32 bits wide (wchar_t may be signed or unsigned) - * or else to be uint32_t. - * That is, the definition of UChar32 was platform-dependent. - * - * @see U_SENTINEL - * @stable ICU 2.4 - */ -typedef int32_t UChar32; - -/** - * This value is intended for sentinel values for APIs that - * (take or) return single code points (UChar32). - * It is outside of the Unicode code point range 0..0x10ffff. - * - * For example, a "done" or "error" value in a new API - * could be indicated with U_SENTINEL. - * - * ICU APIs designed before ICU 2.4 usually define service-specific "done" - * values, mostly 0xffff. - * Those may need to be distinguished from - * actual U+ffff text contents by calling functions like - * CharacterIterator::hasNext() or UnicodeString::length(). - * - * @return -1 - * @see UChar32 - * @stable ICU 2.4 - */ -#define U_SENTINEL (-1) - -#include "./urename.h" - -#endif diff --git a/src/tree_sitter/unicode/urename.h b/src/tree_sitter/unicode/urename.h deleted file mode 100644 index ac79ad0f98..0000000000 --- a/src/tree_sitter/unicode/urename.h +++ /dev/null @@ -1 +0,0 @@ -// This file must exist in order for `utf8.h` and `utf16.h` to be used. diff --git a/src/tree_sitter/unicode/utf.h b/src/tree_sitter/unicode/utf.h deleted file mode 100644 index ac79ad0f98..0000000000 --- a/src/tree_sitter/unicode/utf.h +++ /dev/null @@ -1 +0,0 @@ -// This file must exist in order for `utf8.h` and `utf16.h` to be used. diff --git a/src/tree_sitter/unicode/utf16.h b/src/tree_sitter/unicode/utf16.h deleted file mode 100644 index b547922441..0000000000 --- a/src/tree_sitter/unicode/utf16.h +++ /dev/null @@ -1,733 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* -* Copyright (C) 1999-2012, International Business Machines -* Corporation and others. All Rights Reserved. -* -******************************************************************************* -* file name: utf16.h -* encoding: UTF-8 -* tab size: 8 (not used) -* indentation:4 -* -* created on: 1999sep09 -* created by: Markus W. Scherer -*/ - -/** - * \file - * \brief C API: 16-bit Unicode handling macros - * - * This file defines macros to deal with 16-bit Unicode (UTF-16) code units and strings. - * - * For more information see utf.h and the ICU User Guide Strings chapter - * (http://userguide.icu-project.org/strings). - * - * <em>Usage:</em> - * ICU coding guidelines for if() statements should be followed when using these macros. - * Compound statements (curly braces {}) must be used for if-else-while... - * bodies and all macro statements should be terminated with semicolon. - */ - -#ifndef __UTF16_H__ -#define __UTF16_H__ - -#include "./umachine.h" -#ifndef __UTF_H__ -# include "./utf.h" -#endif - -/* single-code point definitions -------------------------------------------- */ - -/** - * Does this code unit alone encode a code point (BMP, not a surrogate)? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_SINGLE(c) !U_IS_SURROGATE(c) - -/** - * Is this code unit a lead surrogate (U+d800..U+dbff)? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_LEAD(c) (((c)&0xfffffc00)==0xd800) - -/** - * Is this code unit a trail surrogate (U+dc00..U+dfff)? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_TRAIL(c) (((c)&0xfffffc00)==0xdc00) - -/** - * Is this code unit a surrogate (U+d800..U+dfff)? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_SURROGATE(c) U_IS_SURROGATE(c) - -/** - * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)), - * is it a lead surrogate? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U16_IS_SURROGATE_LEAD(c) (((c)&0x400)==0) - -/** - * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)), - * is it a trail surrogate? - * @param c 16-bit code unit - * @return TRUE or FALSE - * @stable ICU 4.2 - */ -#define U16_IS_SURROGATE_TRAIL(c) (((c)&0x400)!=0) - -/** - * Helper constant for U16_GET_SUPPLEMENTARY. - * @internal - */ -#define U16_SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000) - -/** - * Get a supplementary code point value (U+10000..U+10ffff) - * from its lead and trail surrogates. - * The result is undefined if the input values are not - * lead and trail surrogates. - * - * @param lead lead surrogate (U+d800..U+dbff) - * @param trail trail surrogate (U+dc00..U+dfff) - * @return supplementary code point (U+10000..U+10ffff) - * @stable ICU 2.4 - */ -#define U16_GET_SUPPLEMENTARY(lead, trail) \ - (((UChar32)(lead)<<10UL)+(UChar32)(trail)-U16_SURROGATE_OFFSET) - - -/** - * Get the lead surrogate (0xd800..0xdbff) for a - * supplementary code point (0x10000..0x10ffff). - * @param supplementary 32-bit code point (U+10000..U+10ffff) - * @return lead surrogate (U+d800..U+dbff) for supplementary - * @stable ICU 2.4 - */ -#define U16_LEAD(supplementary) (UChar)(((supplementary)>>10)+0xd7c0) - -/** - * Get the trail surrogate (0xdc00..0xdfff) for a - * supplementary code point (0x10000..0x10ffff). - * @param supplementary 32-bit code point (U+10000..U+10ffff) - * @return trail surrogate (U+dc00..U+dfff) for supplementary - * @stable ICU 2.4 - */ -#define U16_TRAIL(supplementary) (UChar)(((supplementary)&0x3ff)|0xdc00) - -/** - * How many 16-bit code units are used to encode this Unicode code point? (1 or 2) - * The result is not defined if c is not a Unicode code point (U+0000..U+10ffff). - * @param c 32-bit code point - * @return 1 or 2 - * @stable ICU 2.4 - */ -#define U16_LENGTH(c) ((uint32_t)(c)<=0xffff ? 1 : 2) - -/** - * The maximum number of 16-bit code units per Unicode code point (U+0000..U+10ffff). - * @return 2 - * @stable ICU 2.4 - */ -#define U16_MAX_LENGTH 2 - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * The offset may point to either the lead or trail surrogate unit - * for a supplementary code point, in which case the macro will read - * the adjacent matching surrogate as well. - * The result is undefined if the offset points to a single, unpaired surrogate. - * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT. - * - * @param s const UChar * string - * @param i string offset - * @param c output UChar32 variable - * @see U16_GET - * @stable ICU 2.4 - */ -#define U16_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[i]; \ - if(U16_IS_SURROGATE(c)) { \ - if(U16_IS_SURROGATE_LEAD(c)) { \ - (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)+1]); \ - } else { \ - (c)=U16_GET_SUPPLEMENTARY((s)[(i)-1], (c)); \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The offset may point to either the lead or trail surrogate unit - * for a supplementary code point, in which case the macro will read - * the adjacent matching surrogate as well. - * - * The length can be negative for a NUL-terminated string. - * - * If the offset points to a single, unpaired surrogate, then - * c is set to that unpaired surrogate. - * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<=i<length - * @param length string length - * @param c output UChar32 variable - * @see U16_GET_UNSAFE - * @stable ICU 2.4 - */ -#define U16_GET(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[i]; \ - if(U16_IS_SURROGATE(c)) { \ - uint16_t __c2; \ - if(U16_IS_SURROGATE_LEAD(c)) { \ - if((i)+1!=(length) && U16_IS_TRAIL(__c2=(s)[(i)+1])) { \ - (c)=U16_GET_SUPPLEMENTARY((c), __c2); \ - } \ - } else { \ - if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ - (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The offset may point to either the lead or trail surrogate unit - * for a supplementary code point, in which case the macro will read - * the adjacent matching surrogate as well. - * - * The length can be negative for a NUL-terminated string. - * - * If the offset points to a single, unpaired surrogate, then - * c is set to U+FFFD. - * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT_OR_FFFD. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<=i<length - * @param length string length - * @param c output UChar32 variable - * @see U16_GET_UNSAFE - * @stable ICU 60 - */ -#define U16_GET_OR_FFFD(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[i]; \ - if(U16_IS_SURROGATE(c)) { \ - uint16_t __c2; \ - if(U16_IS_SURROGATE_LEAD(c)) { \ - if((i)+1!=(length) && U16_IS_TRAIL(__c2=(s)[(i)+1])) { \ - (c)=U16_GET_SUPPLEMENTARY((c), __c2); \ - } else { \ - (c)=0xfffd; \ - } \ - } else { \ - if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ - (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ - } else { \ - (c)=0xfffd; \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/* definitions with forward iteration --------------------------------------- */ - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Unsafe" macro, assumes well-formed UTF-16. - * - * The offset may point to the lead surrogate unit - * for a supplementary code point, in which case the macro will read - * the following trail surrogate as well. - * If the offset points to a trail surrogate, then that itself - * will be returned as the code point. - * The result is undefined if the offset points to a single, unpaired lead surrogate. - * - * @param s const UChar * string - * @param i string offset - * @param c output UChar32 variable - * @see U16_NEXT - * @stable ICU 2.4 - */ -#define U16_NEXT_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[(i)++]; \ - if(U16_IS_LEAD(c)) { \ - (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)++]); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * The offset may point to the lead surrogate unit - * for a supplementary code point, in which case the macro will read - * the following trail surrogate as well. - * If the offset points to a trail surrogate or - * to a single, unpaired lead surrogate, then c is set to that unpaired surrogate. - * - * @param s const UChar * string - * @param i string offset, must be i<length - * @param length string length - * @param c output UChar32 variable - * @see U16_NEXT_UNSAFE - * @stable ICU 2.4 - */ -#define U16_NEXT(s, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[(i)++]; \ - if(U16_IS_LEAD(c)) { \ - uint16_t __c2; \ - if((i)!=(length) && U16_IS_TRAIL(__c2=(s)[(i)])) { \ - ++(i); \ - (c)=U16_GET_SUPPLEMENTARY((c), __c2); \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * The offset may point to the lead surrogate unit - * for a supplementary code point, in which case the macro will read - * the following trail surrogate as well. - * If the offset points to a trail surrogate or - * to a single, unpaired lead surrogate, then c is set to U+FFFD. - * - * @param s const UChar * string - * @param i string offset, must be i<length - * @param length string length - * @param c output UChar32 variable - * @see U16_NEXT_UNSAFE - * @stable ICU 60 - */ -#define U16_NEXT_OR_FFFD(s, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[(i)++]; \ - if(U16_IS_SURROGATE(c)) { \ - uint16_t __c2; \ - if(U16_IS_SURROGATE_LEAD(c) && (i)!=(length) && U16_IS_TRAIL(__c2=(s)[(i)])) { \ - ++(i); \ - (c)=U16_GET_SUPPLEMENTARY((c), __c2); \ - } else { \ - (c)=0xfffd; \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Append a code point to a string, overwriting 1 or 2 code units. - * The offset points to the current end of the string contents - * and is advanced (post-increment). - * "Unsafe" macro, assumes a valid code point and sufficient space in the string. - * Otherwise, the result is undefined. - * - * @param s const UChar * string buffer - * @param i string offset - * @param c code point to append - * @see U16_APPEND - * @stable ICU 2.4 - */ -#define U16_APPEND_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - if((uint32_t)(c)<=0xffff) { \ - (s)[(i)++]=(uint16_t)(c); \ - } else { \ - (s)[(i)++]=(uint16_t)(((c)>>10)+0xd7c0); \ - (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Append a code point to a string, overwriting 1 or 2 code units. - * The offset points to the current end of the string contents - * and is advanced (post-increment). - * "Safe" macro, checks for a valid code point. - * If a surrogate pair is written, checks for sufficient space in the string. - * If the code point is not valid or a trail surrogate does not fit, - * then isError is set to TRUE. - * - * @param s const UChar * string buffer - * @param i string offset, must be i<capacity - * @param capacity size of the string buffer - * @param c code point to append - * @param isError output UBool set to TRUE if an error occurs, otherwise not modified - * @see U16_APPEND_UNSAFE - * @stable ICU 2.4 - */ -#define U16_APPEND(s, i, capacity, c, isError) UPRV_BLOCK_MACRO_BEGIN { \ - if((uint32_t)(c)<=0xffff) { \ - (s)[(i)++]=(uint16_t)(c); \ - } else if((uint32_t)(c)<=0x10ffff && (i)+1<(capacity)) { \ - (s)[(i)++]=(uint16_t)(((c)>>10)+0xd7c0); \ - (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \ - } else /* c>0x10ffff or not enough space */ { \ - (isError)=TRUE; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the next. - * (Post-incrementing iteration.) - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @see U16_FWD_1 - * @stable ICU 2.4 - */ -#define U16_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_LEAD((s)[(i)++])) { \ - ++(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the next. - * (Post-incrementing iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const UChar * string - * @param i string offset, must be i<length - * @param length string length - * @see U16_FWD_1_UNSAFE - * @stable ICU 2.4 - */ -#define U16_FWD_1(s, i, length) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_LEAD((s)[(i)++]) && (i)!=(length) && U16_IS_TRAIL((s)[i])) { \ - ++(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the n-th next one, - * i.e., move forward by n code points. - * (Post-incrementing iteration.) - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @param n number of code points to skip - * @see U16_FWD_N - * @stable ICU 2.4 - */ -#define U16_FWD_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0) { \ - U16_FWD_1_UNSAFE(s, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the n-th next one, - * i.e., move forward by n code points. - * (Post-incrementing iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const UChar * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @param n number of code points to skip - * @see U16_FWD_N_UNSAFE - * @stable ICU 2.4 - */ -#define U16_FWD_N(s, i, length, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \ - U16_FWD_1(s, i, length); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary - * at the start of a code point. - * If the offset points to the trail surrogate of a surrogate pair, - * then the offset is decremented. - * Otherwise, it is not modified. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @see U16_SET_CP_START - * @stable ICU 2.4 - */ -#define U16_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_TRAIL((s)[i])) { \ - --(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary - * at the start of a code point. - * If the offset points to the trail surrogate of a surrogate pair, - * then the offset is decremented. - * Otherwise, it is not modified. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<=i - * @see U16_SET_CP_START_UNSAFE - * @stable ICU 2.4 - */ -#define U16_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_TRAIL((s)[i]) && (i)>(start) && U16_IS_LEAD((s)[(i)-1])) { \ - --(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/* definitions with backward iteration -------------------------------------- */ - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Unsafe" macro, assumes well-formed UTF-16. - * - * The input offset may be the same as the string length. - * If the offset is behind a trail surrogate unit - * for a supplementary code point, then the macro will read - * the preceding lead surrogate as well. - * If the offset is behind a lead surrogate, then that itself - * will be returned as the code point. - * The result is undefined if the offset is behind a single, unpaired trail surrogate. - * - * @param s const UChar * string - * @param i string offset - * @param c output UChar32 variable - * @see U16_PREV - * @stable ICU 2.4 - */ -#define U16_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[--(i)]; \ - if(U16_IS_TRAIL(c)) { \ - (c)=U16_GET_SUPPLEMENTARY((s)[--(i)], (c)); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The input offset may be the same as the string length. - * If the offset is behind a trail surrogate unit - * for a supplementary code point, then the macro will read - * the preceding lead surrogate as well. - * If the offset is behind a lead surrogate or behind a single, unpaired - * trail surrogate, then c is set to that unpaired surrogate. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<i - * @param c output UChar32 variable - * @see U16_PREV_UNSAFE - * @stable ICU 2.4 - */ -#define U16_PREV(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[--(i)]; \ - if(U16_IS_TRAIL(c)) { \ - uint16_t __c2; \ - if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ - --(i); \ - (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The input offset may be the same as the string length. - * If the offset is behind a trail surrogate unit - * for a supplementary code point, then the macro will read - * the preceding lead surrogate as well. - * If the offset is behind a lead surrogate or behind a single, unpaired - * trail surrogate, then c is set to U+FFFD. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<i - * @param c output UChar32 variable - * @see U16_PREV_UNSAFE - * @stable ICU 60 - */ -#define U16_PREV_OR_FFFD(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(s)[--(i)]; \ - if(U16_IS_SURROGATE(c)) { \ - uint16_t __c2; \ - if(U16_IS_SURROGATE_TRAIL(c) && (i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ - --(i); \ - (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ - } else { \ - (c)=0xfffd; \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @see U16_BACK_1 - * @stable ICU 2.4 - */ -#define U16_BACK_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_TRAIL((s)[--(i)])) { \ - --(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * @param s const UChar * string - * @param start starting string offset (usually 0) - * @param i string offset, must be start<i - * @see U16_BACK_1_UNSAFE - * @stable ICU 2.4 - */ -#define U16_BACK_1(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_TRAIL((s)[--(i)]) && (i)>(start) && U16_IS_LEAD((s)[(i)-1])) { \ - --(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the n-th one before it, - * i.e., move backward by n code points. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @param n number of code points to skip - * @see U16_BACK_N - * @stable ICU 2.4 - */ -#define U16_BACK_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0) { \ - U16_BACK_1_UNSAFE(s, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the n-th one before it, - * i.e., move backward by n code points. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * @param s const UChar * string - * @param start start of string - * @param i string offset, must be start<i - * @param n number of code points to skip - * @see U16_BACK_N_UNSAFE - * @stable ICU 2.4 - */ -#define U16_BACK_N(s, start, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0 && (i)>(start)) { \ - U16_BACK_1(s, start, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary after a code point. - * If the offset is behind the lead surrogate of a surrogate pair, - * then the offset is incremented. - * Otherwise, it is not modified. - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-16. - * - * @param s const UChar * string - * @param i string offset - * @see U16_SET_CP_LIMIT - * @stable ICU 2.4 - */ -#define U16_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U16_IS_LEAD((s)[(i)-1])) { \ - ++(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary after a code point. - * If the offset is behind the lead surrogate of a surrogate pair, - * then the offset is incremented. - * Otherwise, it is not modified. - * The input offset may be the same as the string length. - * "Safe" macro, handles unpaired surrogates and checks for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const UChar * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, start<=i<=length - * @param length int32_t string length - * @see U16_SET_CP_LIMIT_UNSAFE - * @stable ICU 2.4 - */ -#define U16_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \ - if((start)<(i) && ((i)<(length) || (length)<0) && U16_IS_LEAD((s)[(i)-1]) && U16_IS_TRAIL((s)[i])) { \ - ++(i); \ - } \ -} UPRV_BLOCK_MACRO_END - -#endif diff --git a/src/tree_sitter/unicode/utf8.h b/src/tree_sitter/unicode/utf8.h deleted file mode 100644 index 3b37873e37..0000000000 --- a/src/tree_sitter/unicode/utf8.h +++ /dev/null @@ -1,881 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* -* Copyright (C) 1999-2015, International Business Machines -* Corporation and others. All Rights Reserved. -* -******************************************************************************* -* file name: utf8.h -* encoding: UTF-8 -* tab size: 8 (not used) -* indentation:4 -* -* created on: 1999sep13 -* created by: Markus W. Scherer -*/ - -/** - * \file - * \brief C API: 8-bit Unicode handling macros - * - * This file defines macros to deal with 8-bit Unicode (UTF-8) code units (bytes) and strings. - * - * For more information see utf.h and the ICU User Guide Strings chapter - * (http://userguide.icu-project.org/strings). - * - * <em>Usage:</em> - * ICU coding guidelines for if() statements should be followed when using these macros. - * Compound statements (curly braces {}) must be used for if-else-while... - * bodies and all macro statements should be terminated with semicolon. - */ - -#ifndef __UTF8_H__ -#define __UTF8_H__ - -#include "./umachine.h" -#ifndef __UTF_H__ -# include "./utf.h" -#endif - -/* internal definitions ----------------------------------------------------- */ - -/** - * Counts the trail bytes for a UTF-8 lead byte. - * Returns 0 for 0..0xc1 as well as for 0xf5..0xff. - * leadByte might be evaluated multiple times. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is called by public macros in this file and thus must remain stable. - * - * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff. - * @internal - */ -#define U8_COUNT_TRAIL_BYTES(leadByte) \ - (U8_IS_LEAD(leadByte) ? \ - ((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0)+1 : 0) - -/** - * Counts the trail bytes for a UTF-8 lead byte of a valid UTF-8 sequence. - * Returns 0 for 0..0xc1. Undefined for 0xf5..0xff. - * leadByte might be evaluated multiple times. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is called by public macros in this file and thus must remain stable. - * - * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff. - * @internal - */ -#define U8_COUNT_TRAIL_BYTES_UNSAFE(leadByte) \ - (((uint8_t)(leadByte)>=0xc2)+((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0)) - -/** - * Mask a UTF-8 lead byte, leave only the lower bits that form part of the code point value. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is called by public macros in this file and thus must remain stable. - * @internal - */ -#define U8_MASK_LEAD_BYTE(leadByte, countTrailBytes) ((leadByte)&=(1<<(6-(countTrailBytes)))-1) - -/** - * Internal bit vector for 3-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD3_AND_T1. - * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence. - * Lead byte E0..EF bits 3..0 are used as byte index, - * first trail byte bits 7..5 are used as bit index into that byte. - * @see U8_IS_VALID_LEAD3_AND_T1 - * @internal - */ -#define U8_LEAD3_T1_BITS "\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30" - -/** - * Internal 3-byte UTF-8 validity check. - * Non-zero if lead byte E0..EF and first trail byte 00..FF start a valid sequence. - * @internal - */ -#define U8_IS_VALID_LEAD3_AND_T1(lead, t1) (U8_LEAD3_T1_BITS[(lead)&0xf]&(1<<((uint8_t)(t1)>>5))) - -/** - * Internal bit vector for 4-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD4_AND_T1. - * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence. - * First trail byte bits 7..4 are used as byte index, - * lead byte F0..F4 bits 2..0 are used as bit index into that byte. - * @see U8_IS_VALID_LEAD4_AND_T1 - * @internal - */ -#define U8_LEAD4_T1_BITS "\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00" - -/** - * Internal 4-byte UTF-8 validity check. - * Non-zero if lead byte F0..F4 and first trail byte 00..FF start a valid sequence. - * @internal - */ -#define U8_IS_VALID_LEAD4_AND_T1(lead, t1) (U8_LEAD4_T1_BITS[(uint8_t)(t1)>>4]&(1<<((lead)&7))) - -/** - * Function for handling "next code point" with error-checking. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this - * file and thus must remain stable, and should not be hidden when other internal - * functions are hidden (otherwise public macros would fail to compile). - * @internal - */ -U_STABLE UChar32 U_EXPORT2 -utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict); - -/** - * Function for handling "append code point" with error-checking. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this - * file and thus must remain stable, and should not be hidden when other internal - * functions are hidden (otherwise public macros would fail to compile). - * @internal - */ -U_STABLE int32_t U_EXPORT2 -utf8_appendCharSafeBody(uint8_t *s, int32_t i, int32_t length, UChar32 c, UBool *pIsError); - -/** - * Function for handling "previous code point" with error-checking. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this - * file and thus must remain stable, and should not be hidden when other internal - * functions are hidden (otherwise public macros would fail to compile). - * @internal - */ -U_STABLE UChar32 U_EXPORT2 -utf8_prevCharSafeBody(const uint8_t *s, int32_t start, int32_t *pi, UChar32 c, UBool strict); - -/** - * Function for handling "skip backward one code point" with error-checking. - * - * This is internal since it is not meant to be called directly by external clients; - * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this - * file and thus must remain stable, and should not be hidden when other internal - * functions are hidden (otherwise public macros would fail to compile). - * @internal - */ -U_STABLE int32_t U_EXPORT2 -utf8_back1SafeBody(const uint8_t *s, int32_t start, int32_t i); - -/* single-code point definitions -------------------------------------------- */ - -/** - * Does this code unit (byte) encode a code point by itself (US-ASCII 0..0x7f)? - * @param c 8-bit code unit (byte) - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U8_IS_SINGLE(c) (((c)&0x80)==0) - -/** - * Is this code unit (byte) a UTF-8 lead byte? (0xC2..0xF4) - * @param c 8-bit code unit (byte) - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U8_IS_LEAD(c) ((uint8_t)((c)-0xc2)<=0x32) -// 0x32=0xf4-0xc2 - -/** - * Is this code unit (byte) a UTF-8 trail byte? (0x80..0xBF) - * @param c 8-bit code unit (byte) - * @return TRUE or FALSE - * @stable ICU 2.4 - */ -#define U8_IS_TRAIL(c) ((int8_t)(c)<-0x40) - -/** - * How many code units (bytes) are used for the UTF-8 encoding - * of this Unicode code point? - * @param c 32-bit code point - * @return 1..4, or 0 if c is a surrogate or not a Unicode code point - * @stable ICU 2.4 - */ -#define U8_LENGTH(c) \ - ((uint32_t)(c)<=0x7f ? 1 : \ - ((uint32_t)(c)<=0x7ff ? 2 : \ - ((uint32_t)(c)<=0xd7ff ? 3 : \ - ((uint32_t)(c)<=0xdfff || (uint32_t)(c)>0x10ffff ? 0 : \ - ((uint32_t)(c)<=0xffff ? 3 : 4)\ - ) \ - ) \ - ) \ - ) - -/** - * The maximum number of UTF-8 code units (bytes) per Unicode code point (U+0000..U+10ffff). - * @return 4 - * @stable ICU 2.4 - */ -#define U8_MAX_LENGTH 4 - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * The offset may point to either the lead byte or one of the trail bytes - * for a code point, in which case the macro will read all of the bytes - * for the code point. - * The result is undefined if the offset points to an illegal UTF-8 - * byte sequence. - * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT. - * - * @param s const uint8_t * string - * @param i string offset - * @param c output UChar32 variable - * @see U8_GET - * @stable ICU 2.4 - */ -#define U8_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t _u8_get_unsafe_index=(int32_t)(i); \ - U8_SET_CP_START_UNSAFE(s, _u8_get_unsafe_index); \ - U8_NEXT_UNSAFE(s, _u8_get_unsafe_index, c); \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * The offset may point to either the lead byte or one of the trail bytes - * for a code point, in which case the macro will read all of the bytes - * for the code point. - * - * The length can be negative for a NUL-terminated string. - * - * If the offset points to an illegal UTF-8 byte sequence, then - * c is set to a negative value. - * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset - * @param i int32_t string offset, must be start<=i<length - * @param length int32_t string length - * @param c output UChar32 variable, set to <0 in case of an error - * @see U8_GET_UNSAFE - * @stable ICU 2.4 - */ -#define U8_GET(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t _u8_get_index=(i); \ - U8_SET_CP_START(s, start, _u8_get_index); \ - U8_NEXT(s, _u8_get_index, length, c); \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a random-access offset, - * without changing the offset. - * The offset may point to either the lead byte or one of the trail bytes - * for a code point, in which case the macro will read all of the bytes - * for the code point. - * - * The length can be negative for a NUL-terminated string. - * - * If the offset points to an illegal UTF-8 byte sequence, then - * c is set to U+FFFD. - * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT_OR_FFFD. - * - * This macro does not distinguish between a real U+FFFD in the text - * and U+FFFD returned for an ill-formed sequence. - * Use U8_GET() if that distinction is important. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset - * @param i int32_t string offset, must be start<=i<length - * @param length int32_t string length - * @param c output UChar32 variable, set to U+FFFD in case of an error - * @see U8_GET - * @stable ICU 51 - */ -#define U8_GET_OR_FFFD(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t _u8_get_index=(i); \ - U8_SET_CP_START(s, start, _u8_get_index); \ - U8_NEXT_OR_FFFD(s, _u8_get_index, length, c); \ -} UPRV_BLOCK_MACRO_END - -/* definitions with forward iteration --------------------------------------- */ - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Unsafe" macro, assumes well-formed UTF-8. - * - * The offset may point to the lead byte of a multi-byte sequence, - * in which case the macro will read the whole sequence. - * The result is undefined if the offset points to a trail byte - * or an illegal UTF-8 sequence. - * - * @param s const uint8_t * string - * @param i string offset - * @param c output UChar32 variable - * @see U8_NEXT - * @stable ICU 2.4 - */ -#define U8_NEXT_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[(i)++]; \ - if(!U8_IS_SINGLE(c)) { \ - if((c)<0xe0) { \ - (c)=(((c)&0x1f)<<6)|((s)[(i)++]&0x3f); \ - } else if((c)<0xf0) { \ - /* no need for (c&0xf) because the upper bits are truncated after <<12 in the cast to (UChar) */ \ - (c)=(UChar)(((c)<<12)|(((s)[i]&0x3f)<<6)|((s)[(i)+1]&0x3f)); \ - (i)+=2; \ - } else { \ - (c)=(((c)&7)<<18)|(((s)[i]&0x3f)<<12)|(((s)[(i)+1]&0x3f)<<6)|((s)[(i)+2]&0x3f); \ - (i)+=3; \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * The offset may point to the lead byte of a multi-byte sequence, - * in which case the macro will read the whole sequence. - * If the offset points to a trail byte or an illegal UTF-8 sequence, then - * c is set to a negative value. - * - * @param s const uint8_t * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @param c output UChar32 variable, set to <0 in case of an error - * @see U8_NEXT_UNSAFE - * @stable ICU 2.4 - */ -#define U8_NEXT(s, i, length, c) U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, U_SENTINEL) - -/** - * Get a code point from a string at a code point boundary offset, - * and advance the offset to the next code point boundary. - * (Post-incrementing forward iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * The offset may point to the lead byte of a multi-byte sequence, - * in which case the macro will read the whole sequence. - * If the offset points to a trail byte or an illegal UTF-8 sequence, then - * c is set to U+FFFD. - * - * This macro does not distinguish between a real U+FFFD in the text - * and U+FFFD returned for an ill-formed sequence. - * Use U8_NEXT() if that distinction is important. - * - * @param s const uint8_t * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @param c output UChar32 variable, set to U+FFFD in case of an error - * @see U8_NEXT - * @stable ICU 51 - */ -#define U8_NEXT_OR_FFFD(s, i, length, c) U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, 0xfffd) - -/** @internal */ -#define U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, sub) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[(i)++]; \ - if(!U8_IS_SINGLE(c)) { \ - uint8_t __t = 0; \ - if((i)!=(length) && \ - /* fetch/validate/assemble all but last trail byte */ \ - ((c)>=0xe0 ? \ - ((c)<0xf0 ? /* U+0800..U+FFFF except surrogates */ \ - U8_LEAD3_T1_BITS[(c)&=0xf]&(1<<((__t=(s)[i])>>5)) && \ - (__t&=0x3f, 1) \ - : /* U+10000..U+10FFFF */ \ - ((c)-=0xf0)<=4 && \ - U8_LEAD4_T1_BITS[(__t=(s)[i])>>4]&(1<<(c)) && \ - ((c)=((c)<<6)|(__t&0x3f), ++(i)!=(length)) && \ - (__t=(s)[i]-0x80)<=0x3f) && \ - /* valid second-to-last trail byte */ \ - ((c)=((c)<<6)|__t, ++(i)!=(length)) \ - : /* U+0080..U+07FF */ \ - (c)>=0xc2 && ((c)&=0x1f, 1)) && \ - /* last trail byte */ \ - (__t=(s)[i]-0x80)<=0x3f && \ - ((c)=((c)<<6)|__t, ++(i), 1)) { \ - } else { \ - (c)=(sub); /* ill-formed*/ \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Append a code point to a string, overwriting 1 to 4 bytes. - * The offset points to the current end of the string contents - * and is advanced (post-increment). - * "Unsafe" macro, assumes a valid code point and sufficient space in the string. - * Otherwise, the result is undefined. - * - * @param s const uint8_t * string buffer - * @param i string offset - * @param c code point to append - * @see U8_APPEND - * @stable ICU 2.4 - */ -#define U8_APPEND_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - uint32_t __uc=(c); \ - if(__uc<=0x7f) { \ - (s)[(i)++]=(uint8_t)__uc; \ - } else { \ - if(__uc<=0x7ff) { \ - (s)[(i)++]=(uint8_t)((__uc>>6)|0xc0); \ - } else { \ - if(__uc<=0xffff) { \ - (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \ - } else { \ - (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \ - (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \ - } \ - (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \ - } \ - (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Append a code point to a string, overwriting 1 to 4 bytes. - * The offset points to the current end of the string contents - * and is advanced (post-increment). - * "Safe" macro, checks for a valid code point. - * If a non-ASCII code point is written, checks for sufficient space in the string. - * If the code point is not valid or trail bytes do not fit, - * then isError is set to TRUE. - * - * @param s const uint8_t * string buffer - * @param i int32_t string offset, must be i<capacity - * @param capacity int32_t size of the string buffer - * @param c UChar32 code point to append - * @param isError output UBool set to TRUE if an error occurs, otherwise not modified - * @see U8_APPEND_UNSAFE - * @stable ICU 2.4 - */ -#define U8_APPEND(s, i, capacity, c, isError) UPRV_BLOCK_MACRO_BEGIN { \ - uint32_t __uc=(c); \ - if(__uc<=0x7f) { \ - (s)[(i)++]=(uint8_t)__uc; \ - } else if(__uc<=0x7ff && (i)+1<(capacity)) { \ - (s)[(i)++]=(uint8_t)((__uc>>6)|0xc0); \ - (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ - } else if((__uc<=0xd7ff || (0xe000<=__uc && __uc<=0xffff)) && (i)+2<(capacity)) { \ - (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \ - (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \ - (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ - } else if(0xffff<__uc && __uc<=0x10ffff && (i)+3<(capacity)) { \ - (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \ - (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \ - (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \ - (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ - } else { \ - (isError)=TRUE; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the next. - * (Post-incrementing iteration.) - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @see U8_FWD_1 - * @stable ICU 2.4 - */ -#define U8_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - (i)+=1+U8_COUNT_TRAIL_BYTES_UNSAFE((s)[i]); \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the next. - * (Post-incrementing iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const uint8_t * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @see U8_FWD_1_UNSAFE - * @stable ICU 2.4 - */ -#define U8_FWD_1(s, i, length) UPRV_BLOCK_MACRO_BEGIN { \ - uint8_t __b=(s)[(i)++]; \ - if(U8_IS_LEAD(__b) && (i)!=(length)) { \ - uint8_t __t1=(s)[i]; \ - if((0xe0<=__b && __b<0xf0)) { \ - if(U8_IS_VALID_LEAD3_AND_T1(__b, __t1) && \ - ++(i)!=(length) && U8_IS_TRAIL((s)[i])) { \ - ++(i); \ - } \ - } else if(__b<0xe0) { \ - if(U8_IS_TRAIL(__t1)) { \ - ++(i); \ - } \ - } else /* c>=0xf0 */ { \ - if(U8_IS_VALID_LEAD4_AND_T1(__b, __t1) && \ - ++(i)!=(length) && U8_IS_TRAIL((s)[i]) && \ - ++(i)!=(length) && U8_IS_TRAIL((s)[i])) { \ - ++(i); \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the n-th next one, - * i.e., move forward by n code points. - * (Post-incrementing iteration.) - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @param n number of code points to skip - * @see U8_FWD_N - * @stable ICU 2.4 - */ -#define U8_FWD_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0) { \ - U8_FWD_1_UNSAFE(s, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Advance the string offset from one code point boundary to the n-th next one, - * i.e., move forward by n code points. - * (Post-incrementing iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const uint8_t * string - * @param i int32_t string offset, must be i<length - * @param length int32_t string length - * @param n number of code points to skip - * @see U8_FWD_N_UNSAFE - * @stable ICU 2.4 - */ -#define U8_FWD_N(s, i, length, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \ - U8_FWD_1(s, i, length); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary - * at the start of a code point. - * If the offset points to a UTF-8 trail byte, - * then the offset is moved backward to the corresponding lead byte. - * Otherwise, it is not modified. - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @see U8_SET_CP_START - * @stable ICU 2.4 - */ -#define U8_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - while(U8_IS_TRAIL((s)[i])) { --(i); } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary - * at the start of a code point. - * If the offset points to a UTF-8 trail byte, - * then the offset is moved backward to the corresponding lead byte. - * Otherwise, it is not modified. - * - * "Safe" macro, checks for illegal sequences and for string boundaries. - * Unlike U8_TRUNCATE_IF_INCOMPLETE(), this macro always reads s[i]. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<=i - * @see U8_SET_CP_START_UNSAFE - * @see U8_TRUNCATE_IF_INCOMPLETE - * @stable ICU 2.4 - */ -#define U8_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U8_IS_TRAIL((s)[(i)])) { \ - (i)=utf8_back1SafeBody(s, start, (i)); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * If the string ends with a UTF-8 byte sequence that is valid so far - * but incomplete, then reduce the length of the string to end before - * the lead byte of that incomplete sequence. - * For example, if the string ends with E1 80, the length is reduced by 2. - * - * In all other cases (the string ends with a complete sequence, or it is not - * possible for any further trail byte to extend the trailing sequence) - * the length remains unchanged. - * - * Useful for processing text split across multiple buffers - * (save the incomplete sequence for later) - * and for optimizing iteration - * (check for string length only once per character). - * - * "Safe" macro, checks for illegal sequences and for string boundaries. - * Unlike U8_SET_CP_START(), this macro never reads s[length]. - * - * (In UTF-16, simply check for U16_IS_LEAD(last code unit).) - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param length int32_t string length (usually start<=length) - * @see U8_SET_CP_START - * @stable ICU 61 - */ -#define U8_TRUNCATE_IF_INCOMPLETE(s, start, length) UPRV_BLOCK_MACRO_BEGIN { \ - if((length)>(start)) { \ - uint8_t __b1=s[(length)-1]; \ - if(U8_IS_SINGLE(__b1)) { \ - /* common ASCII character */ \ - } else if(U8_IS_LEAD(__b1)) { \ - --(length); \ - } else if(U8_IS_TRAIL(__b1) && ((length)-2)>=(start)) { \ - uint8_t __b2=s[(length)-2]; \ - if(0xe0<=__b2 && __b2<=0xf4) { \ - if(__b2<0xf0 ? U8_IS_VALID_LEAD3_AND_T1(__b2, __b1) : \ - U8_IS_VALID_LEAD4_AND_T1(__b2, __b1)) { \ - (length)-=2; \ - } \ - } else if(U8_IS_TRAIL(__b2) && ((length)-3)>=(start)) { \ - uint8_t __b3=s[(length)-3]; \ - if(0xf0<=__b3 && __b3<=0xf4 && U8_IS_VALID_LEAD4_AND_T1(__b3, __b2)) { \ - (length)-=3; \ - } \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/* definitions with backward iteration -------------------------------------- */ - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Unsafe" macro, assumes well-formed UTF-8. - * - * The input offset may be the same as the string length. - * If the offset is behind a multi-byte sequence, then the macro will read - * the whole sequence. - * If the offset is behind a lead byte, then that itself - * will be returned as the code point. - * The result is undefined if the offset is behind an illegal UTF-8 sequence. - * - * @param s const uint8_t * string - * @param i string offset - * @param c output UChar32 variable - * @see U8_PREV - * @stable ICU 2.4 - */ -#define U8_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[--(i)]; \ - if(U8_IS_TRAIL(c)) { \ - uint8_t __b, __count=1, __shift=6; \ -\ - /* c is a trail byte */ \ - (c)&=0x3f; \ - for(;;) { \ - __b=(s)[--(i)]; \ - if(__b>=0xc0) { \ - U8_MASK_LEAD_BYTE(__b, __count); \ - (c)|=(UChar32)__b<<__shift; \ - break; \ - } else { \ - (c)|=(UChar32)(__b&0x3f)<<__shift; \ - ++__count; \ - __shift+=6; \ - } \ - } \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The input offset may be the same as the string length. - * If the offset is behind a multi-byte sequence, then the macro will read - * the whole sequence. - * If the offset is behind a lead byte, then that itself - * will be returned as the code point. - * If the offset is behind an illegal UTF-8 sequence, then c is set to a negative value. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<i - * @param c output UChar32 variable, set to <0 in case of an error - * @see U8_PREV_UNSAFE - * @stable ICU 2.4 - */ -#define U8_PREV(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[--(i)]; \ - if(!U8_IS_SINGLE(c)) { \ - (c)=utf8_prevCharSafeBody((const uint8_t *)s, start, &(i), c, -1); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one - * and get the code point between them. - * (Pre-decrementing backward iteration.) - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The input offset may be the same as the string length. - * If the offset is behind a multi-byte sequence, then the macro will read - * the whole sequence. - * If the offset is behind a lead byte, then that itself - * will be returned as the code point. - * If the offset is behind an illegal UTF-8 sequence, then c is set to U+FFFD. - * - * This macro does not distinguish between a real U+FFFD in the text - * and U+FFFD returned for an ill-formed sequence. - * Use U8_PREV() if that distinction is important. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<i - * @param c output UChar32 variable, set to U+FFFD in case of an error - * @see U8_PREV - * @stable ICU 51 - */ -#define U8_PREV_OR_FFFD(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \ - (c)=(uint8_t)(s)[--(i)]; \ - if(!U8_IS_SINGLE(c)) { \ - (c)=utf8_prevCharSafeBody((const uint8_t *)s, start, &(i), c, -3); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @see U8_BACK_1 - * @stable ICU 2.4 - */ -#define U8_BACK_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - while(U8_IS_TRAIL((s)[--(i)])) {} \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the previous one. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<i - * @see U8_BACK_1_UNSAFE - * @stable ICU 2.4 - */ -#define U8_BACK_1(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ - if(U8_IS_TRAIL((s)[--(i)])) { \ - (i)=utf8_back1SafeBody(s, start, (i)); \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the n-th one before it, - * i.e., move backward by n code points. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @param n number of code points to skip - * @see U8_BACK_N - * @stable ICU 2.4 - */ -#define U8_BACK_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0) { \ - U8_BACK_1_UNSAFE(s, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Move the string offset from one code point boundary to the n-th one before it, - * i.e., move backward by n code points. - * (Pre-decrementing backward iteration.) - * The input offset may be the same as the string length. - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * @param s const uint8_t * string - * @param start int32_t index of the start of the string - * @param i int32_t string offset, must be start<i - * @param n number of code points to skip - * @see U8_BACK_N_UNSAFE - * @stable ICU 2.4 - */ -#define U8_BACK_N(s, start, i, n) UPRV_BLOCK_MACRO_BEGIN { \ - int32_t __N=(n); \ - while(__N>0 && (i)>(start)) { \ - U8_BACK_1(s, start, i); \ - --__N; \ - } \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary after a code point. - * If the offset is behind a partial multi-byte sequence, - * then the offset is incremented to behind the whole sequence. - * Otherwise, it is not modified. - * The input offset may be the same as the string length. - * "Unsafe" macro, assumes well-formed UTF-8. - * - * @param s const uint8_t * string - * @param i string offset - * @see U8_SET_CP_LIMIT - * @stable ICU 2.4 - */ -#define U8_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ - U8_BACK_1_UNSAFE(s, i); \ - U8_FWD_1_UNSAFE(s, i); \ -} UPRV_BLOCK_MACRO_END - -/** - * Adjust a random-access offset to a code point boundary after a code point. - * If the offset is behind a partial multi-byte sequence, - * then the offset is incremented to behind the whole sequence. - * Otherwise, it is not modified. - * The input offset may be the same as the string length. - * "Safe" macro, checks for illegal sequences and for string boundaries. - * - * The length can be negative for a NUL-terminated string. - * - * @param s const uint8_t * string - * @param start int32_t starting string offset (usually 0) - * @param i int32_t string offset, must be start<=i<=length - * @param length int32_t string length - * @see U8_SET_CP_LIMIT_UNSAFE - * @stable ICU 2.4 - */ -#define U8_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \ - if((start)<(i) && ((i)<(length) || (length)<0)) { \ - U8_BACK_1(s, start, i); \ - U8_FWD_1(s, i, length); \ - } \ -} UPRV_BLOCK_MACRO_END - -#endif |