diff options
Diffstat (limited to 'src')
339 files changed, 45501 insertions, 24883 deletions
diff --git a/src/cjson/lua_cjson.c b/src/cjson/lua_cjson.c index cf9e82c38e..c243f93c05 100644 --- a/src/cjson/lua_cjson.c +++ b/src/cjson/lua_cjson.c @@ -776,7 +776,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, if (has_metatable) { - nlua_pushref(l, nlua_empty_dict_ref); + nlua_pushref(l, nlua_get_empty_dict_ref(l)); if (lua_rawequal(l, -2, -1)) { as_empty_dict = true; } else { @@ -822,7 +822,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, } break; case LUA_TUSERDATA: - nlua_pushref(l, nlua_nil_ref); + nlua_pushref(l, nlua_get_nil_ref(l)); bool is_nil = lua_rawequal(l, -2, -1); lua_pop(l, 1); if (is_nil) { @@ -1110,7 +1110,7 @@ static int json_is_invalid_number(json_parse_t *json) /* Reject numbers starting with 0x, or leading zeros */ if (*p == '0') { - int ch2 = *(p + 1); + char ch2 = *(p + 1); if ((ch2 | 0x20) == 'x' || /* Hex */ ('0' <= ch2 && ch2 <= '9')) /* Leading zero */ @@ -1285,7 +1285,7 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json) /* Handle empty objects */ if (token.type == T_OBJ_END) { - nlua_pushref(l, nlua_empty_dict_ref); \ + nlua_pushref(l, nlua_get_empty_dict_ref(l)); \ lua_setmetatable(l, -2); \ json_decode_ascend(json); return; @@ -1392,7 +1392,7 @@ static void json_process_value(lua_State *l, json_parse_t *json, if (use_luanil) { lua_pushnil(l); } else { - nlua_pushref(l, nlua_nil_ref); + nlua_pushref(l, nlua_get_nil_ref(l)); } break;; default: @@ -1549,7 +1549,15 @@ int lua_cjson_new(lua_State *l) }; /* Initialise number conversions */ - fpconv_init(); + lua_getfield(l, LUA_REGISTRYINDEX, "nvim.thread"); + bool is_thread = lua_toboolean(l, -1); + lua_pop(l, 1); + + // Since fpconv_init does not need to be called multiple times and is not + // thread safe, it should only be called in the main thread. + if (!is_thread) { + fpconv_init(); + } /* Test if array metatables are in registry */ lua_pushlightuserdata(l, json_lightudata_mask(&json_empty_array)); @@ -1582,7 +1590,7 @@ int lua_cjson_new(lua_State *l) compat_luaL_setfuncs(l, reg, 1); /* Set cjson.null */ - nlua_pushref(l, nlua_nil_ref); + nlua_pushref(l, nlua_get_nil_ref(l)); lua_setfield(l, -2, "null"); /* Set cjson.empty_array_mt */ diff --git a/src/clint.py b/src/clint.py index 4b7bf002e6..75aaba176d 100755 --- a/src/clint.py +++ b/src/clint.py @@ -191,7 +191,6 @@ _ERROR_CATEGORIES = [ 'readability/fn_size', 'readability/multiline_comment', 'readability/multiline_string', - 'readability/nolint', 'readability/nul', 'readability/todo', 'readability/utf8', @@ -298,9 +297,6 @@ def ParseNolintSuppressions(filename, raw_line, linenum, error): if category in _ERROR_CATEGORIES: _error_suppressions.setdefault( category, set()).add(linenum) - else: - error(filename, linenum, 'readability/nolint', 5, - 'Unknown NOLINT error category: %s' % category) def ParseKnownErrorSuppressions(filename, raw_lines, linenum): @@ -373,7 +369,7 @@ def Search(pattern, s): return _regexp_compile_cache[pattern].search(s) -class _IncludeState(dict): +class _IncludeState(dict): # lgtm [py/missing-equals] """Tracks line numbers for includes, and the order in which includes appear. @@ -3191,8 +3187,8 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, r'|li_(?:next|prev|tv))\b', line) if match: error(filename, linenum, 'runtime/deprecated', 4, - 'Accessing list_T internals directly is prohibited ' - '(hint: see commit d46e37cb4c71)') + 'Accessing list_T internals directly is prohibited; ' + 'see https://github.com/neovim/neovim/wiki/List-management-in-Neovim') # Check for suspicious usage of "if" like # } if (a == b) { diff --git a/src/mpack/lmpack.c b/src/mpack/lmpack.c index 87acf46592..53d7092a0c 100644 --- a/src/mpack/lmpack.c +++ b/src/mpack/lmpack.c @@ -246,7 +246,7 @@ static mpack_uint32_t lmpack_objlen(lua_State *L, int *is_array) } end: - if ((size_t)-1 > (mpack_uint32_t)-1 && len > (mpack_uint32_t)-1) + if ((size_t)-1 > (mpack_uint32_t)-1 && len > (mpack_uint32_t)-1) // -V560 /* msgpack spec doesn't allow lengths > 32 bits */ len = (mpack_uint32_t)-1; assert(top == lua_gettop(L)); @@ -595,6 +595,7 @@ static void lmpack_unparse_enter(mpack_parser_t *parser, mpack_node_t *node) /* push the pair */ result = lua_next(L, -2); assert(result); /* should not be here if the map was fully processed */ + (void)result; /* ignore unused warning */ if (parent->key_visited) { /* release the current key */ lmpack_unref(L, packer->reg, (int)parent->data[1].i); @@ -1010,6 +1011,7 @@ static int lmpack_session_reply(lua_State *L) "invalid request id"); result = mpack_rpc_reply(session->session, &b, &bl, (mpack_uint32_t)id); assert(result == MPACK_OK); + (void)result; /* ignore unused warning */ lua_pushlstring(L, buf, sizeof(buf) - bl); return 1; } @@ -1027,6 +1029,7 @@ static int lmpack_session_notify(lua_State *L) session = lmpack_check_session(L, 1); result = mpack_rpc_notify(session->session, &b, &bl); assert(result == MPACK_OK); + (void)result; /* ignore unused warning */ lua_pushlstring(L, buf, sizeof(buf) - bl); return 1; } diff --git a/src/mpack/object.h b/src/mpack/object.h index 5327e56e18..e69821f9de 100644 --- a/src/mpack/object.h +++ b/src/mpack/object.h @@ -22,7 +22,7 @@ enum { }; /* Storing integer in pointers in undefined behavior according to the C - * standard. Define a union type to accomodate arbitrary user data associated + * standard. Define a union type to accommodate arbitrary user data associated * with nodes(and with requests in rpc.h). */ typedef union { void *p; diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 185d55daed..b7f9292de1 100644..100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -39,6 +39,7 @@ set(GENERATED_UI_EVENTS ${GENERATED_DIR}/ui_events.generated.h) set(GENERATED_UI_EVENTS_CALL ${GENERATED_DIR}/ui_events_call.generated.h) set(GENERATED_UI_EVENTS_REMOTE ${GENERATED_DIR}/ui_events_remote.generated.h) set(GENERATED_UI_EVENTS_BRIDGE ${GENERATED_DIR}/ui_events_bridge.generated.h) +set(GENERATED_UI_EVENTS_CLIENT ${GENERATED_DIR}/ui_events_client.generated.h) set(GENERATED_UI_EVENTS_METADATA ${GENERATED_DIR}/api/private/ui_events_metadata.generated.h) set(GENERATED_EX_CMDS_ENUM ${GENERATED_INCLUDES_DIR}/ex_cmds_enum.generated.h) set(GENERATED_EX_CMDS_DEFS ${GENERATED_DIR}/ex_cmds_defs.generated.h) @@ -57,11 +58,14 @@ set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua) set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode) set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h) -set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) +set(LUA_EDITOR_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_editor.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(LUA_F_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/F.lua) set(LUA_META_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_meta.lua) +set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.lua) +set(LUA_INIT_PACKAGES_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_init_packages.lua) +set(LUA_KEYMAP_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/keymap.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") @@ -132,6 +136,9 @@ foreach(sfile ${NVIM_SOURCES}) if(${f} MATCHES "^(regexp_nfa.c)$") list(APPEND to_remove ${sfile}) endif() + if(${f} MATCHES "^(regexp_bt.c)$") + list(APPEND to_remove ${sfile}) + endif() if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$") list(APPEND to_remove ${sfile}) endif() @@ -175,12 +182,15 @@ foreach(sfile ${CONV_SOURCES}) message(FATAL_ERROR "${sfile} doesn't exist (it was added to CONV_SOURCES)") endif() endforeach() -# xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306 -list(APPEND CONV_SOURCES ${EXTERNAL_SOURCES}) if(NOT MSVC) set_source_files_properties( ${CONV_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") + + # xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306 + set_source_files_properties( + ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion") + # gperf generates ANSI-C with incorrect linkage, ignore it. check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE) if(HAS_WSTATIC_IN_INLINE) @@ -258,12 +268,14 @@ endif() # NVIM_GENERATED_SOURCES: generated source files # These lists must be mutually exclusive. foreach(sfile ${NVIM_SOURCES} + "${CMAKE_CURRENT_LIST_DIR}/regexp_bt.c" "${CMAKE_CURRENT_LIST_DIR}/regexp_nfa.c" ${GENERATED_API_DISPATCH} "${GENERATED_UI_EVENTS_CALL}" "${GENERATED_UI_EVENTS_REMOTE}" "${GENERATED_UI_EVENTS_BRIDGE}" "${GENERATED_KEYSETS}" + "${GENERATED_UI_EVENTS_CLIENT}" ) get_filename_component(full_d ${sfile} PATH) file(RELATIVE_PATH d "${CMAKE_CURRENT_LIST_DIR}" "${full_d}") @@ -326,19 +338,29 @@ add_custom_command( add_custom_command( OUTPUT ${VIM_MODULE_FILE} - 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 - ${LUA_F_MODULE_SOURCE} lua_F_module - ${LUA_META_MODULE_SOURCE} lua_meta_module + COMMAND ${CMAKE_COMMAND} -E env + "LUAC_PRG=${LUAC_PRG}" + ${LUA_PRG} ${CHAR_BLOB_GENERATOR} -c ${VIM_MODULE_FILE} + ${LUA_INIT_PACKAGES_MODULE_SOURCE} "vim._init_packages" + ${LUA_INSPECT_MODULE_SOURCE} "vim.inspect" + ${LUA_EDITOR_MODULE_SOURCE} "vim._editor" + ${LUA_SHARED_MODULE_SOURCE} "vim.shared" + ${LUA_F_MODULE_SOURCE} "vim.F" + ${LUA_META_MODULE_SOURCE} "vim._meta" + ${LUA_FILETYPE_MODULE_SOURCE} "vim.filetype" + ${LUA_KEYMAP_MODULE_SOURCE} "vim.keymap" DEPENDS ${CHAR_BLOB_GENERATOR} - ${LUA_VIM_MODULE_SOURCE} + ${LUA_INIT_PACKAGES_MODULE_SOURCE} + ${LUA_EDITOR_MODULE_SOURCE} ${LUA_SHARED_MODULE_SOURCE} ${LUA_INSPECT_MODULE_SOURCE} ${LUA_F_MODULE_SOURCE} ${LUA_META_MODULE_SOURCE} + ${LUA_FILETYPE_MODULE_SOURCE} + ${LUA_LOAD_PACKAGE_MODULE_SOURCE} + ${LUA_KEYMAP_MODULE_SOURCE} + VERBATIM ) list(APPEND NVIM_GENERATED_SOURCES @@ -351,6 +373,7 @@ add_custom_command( ${GENERATED_UI_EVENTS_REMOTE} ${GENERATED_UI_EVENTS_BRIDGE} ${GENERATED_UI_EVENTS_METADATA} + ${GENERATED_UI_EVENTS_CLIENT} COMMAND ${LUA_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h ${GENERATED_UI_EVENTS} @@ -358,6 +381,7 @@ add_custom_command( ${GENERATED_UI_EVENTS_REMOTE} ${GENERATED_UI_EVENTS_BRIDGE} ${GENERATED_UI_EVENTS_METADATA} + ${GENERATED_UI_EVENTS_CLIENT} DEPENDS ${API_UI_EVENTS_GENERATOR} ${GENERATOR_C_GRAMMAR} @@ -468,9 +492,11 @@ list(APPEND NVIM_LINK_LIBRARIES if(UNIX) list(APPEND NVIM_LINK_LIBRARIES - m - util - ) + m) + if (NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS") + list(APPEND NVIM_LINK_LIBRARIES + util) + endif() endif() set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUA_PREFERRED_LIBRARIES}) @@ -485,6 +511,9 @@ add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}) target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) +if(MSVC) + install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) +endif() set_property(TARGET nvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c new file mode 100644 index 0000000000..ccf4ae3d02 --- /dev/null +++ b/src/nvim/api/autocmd.c @@ -0,0 +1,979 @@ +// 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 <stdbool.h> +#include <stdio.h> + +#include "lauxlib.h" +#include "nvim/api/autocmd.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/eval/typval.h" +#include "nvim/fileio.h" +#include "nvim/lua/executor.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/autocmd.c.generated.h" +#endif + +#define AUCMD_MAX_PATTERNS 256 + +// Copy string or array of strings into an empty array. +// Get the event number, unless it is an error. Then goto `goto_name`. +#define GET_ONE_EVENT(event_nr, event_str, goto_name) \ + char_u *__next_ev; \ + event_T event_nr = \ + event_name2nr((char_u *)event_str.data.string.data, &__next_ev); \ + if (event_nr >= NUM_EVENTS) { \ + api_set_error(err, kErrorTypeValidation, "unexpected event"); \ + goto goto_name; \ + } + + +// ID for associating autocmds created via nvim_create_autocmd +// Used to delete autocmds from nvim_del_autocmd +static int64_t next_autocmd_id = 1; + +/// Get all autocommands that match the corresponding {opts}. +/// +/// These examples will get autocommands matching ALL the given criteria: +/// <pre> +/// -- Matches all criteria +/// autocommands = vim.api.nvim_get_autocmds({ +/// group = "MyGroup", +/// event = {"BufEnter", "BufWinEnter"}, +/// pattern = {"*.c", "*.h"} +/// }) +/// +/// -- All commands from one group +/// autocommands = vim.api.nvim_get_autocmds({ +/// group = "MyGroup", +/// }) +/// </pre> +/// +/// NOTE: When multiple patterns or events are provided, it will find all the autocommands that +/// match any combination of them. +/// +/// @param opts Dictionary with at least one of the following: +/// - group (string|integer): the autocommand group name or id to match against. +/// - event (string|array): event or events to match against |autocmd-events|. +/// - pattern (string|array): pattern or patterns to match against |autocmd-pattern|. +/// @return Array of autocommands matching the criteria, with each item +/// containing the following fields: +/// - id (number): the autocommand id (only when defined with the API). +/// - group (integer): the autocommand group id. +/// - desc (string): the autocommand description. +/// - event (string): the autocommand event. +/// - command (string): the autocommand command. +/// - once (boolean): whether the autocommand is only run once. +/// - pattern (string): the autocommand pattern. +/// If the autocommand is buffer local |autocmd-buffer-local|: +/// - buflocal (boolean): true if the autocommand is buffer local. +/// - buffer (number): the buffer number. +Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) + FUNC_API_SINCE(9) +{ + // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... }) + + Array autocmd_list = ARRAY_DICT_INIT; + char_u *pattern_filters[AUCMD_MAX_PATTERNS]; + char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + + Array buffers = ARRAY_DICT_INIT; + + bool event_set[NUM_EVENTS] = { false }; + bool check_event = false; + + int group = 0; + + switch (opts->group.type) { + case kObjectTypeNil: + break; + case kObjectTypeString: + group = augroup_find(opts->group.data.string.data); + if (group < 0) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + goto cleanup; + } + break; + case kObjectTypeInteger: + group = (int)opts->group.data.integer; + char *name = augroup_name(group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "group must be a string or an integer."); + goto cleanup; + } + + if (opts->event.type != kObjectTypeNil) { + check_event = true; + + Object v = opts->event; + if (v.type == kObjectTypeString) { + GET_ONE_EVENT(event_nr, v, cleanup); + event_set[event_nr] = true; + } else if (v.type == kObjectTypeArray) { + FOREACH_ITEM(v.data.array, event_v, { + if (event_v.type != kObjectTypeString) { + api_set_error(err, + kErrorTypeValidation, + "Every event must be a string in 'event'"); + goto cleanup; + } + + GET_ONE_EVENT(event_nr, event_v, cleanup); + event_set[event_nr] = true; + }) + } else { + api_set_error(err, + kErrorTypeValidation, + "Not a valid 'event' value. Must be a string or an array"); + goto cleanup; + } + } + + if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Cannot use both 'pattern' and 'buffer'"); + goto cleanup; + } + + int pattern_filter_count = 0; + if (opts->pattern.type != kObjectTypeNil) { + Object v = opts->pattern; + if (v.type == kObjectTypeString) { + pattern_filters[pattern_filter_count] = (char_u *)v.data.string.data; + pattern_filter_count += 1; + } else if (v.type == kObjectTypeArray) { + if (v.data.array.size > AUCMD_MAX_PATTERNS) { + api_set_error(err, kErrorTypeValidation, + "Too many patterns. Please limit yourself to %d or fewer", + AUCMD_MAX_PATTERNS); + goto cleanup; + } + + FOREACH_ITEM(v.data.array, item, { + if (item.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'pattern': must be a string"); + goto cleanup; + } + + pattern_filters[pattern_filter_count] = (char_u *)item.data.string.data; + pattern_filter_count += 1; + }); + } else { + api_set_error(err, kErrorTypeValidation, + "Not a valid 'pattern' value. Must be a string or an array"); + goto cleanup; + } + } + + if (opts->buffer.type == kObjectTypeInteger || opts->buffer.type == kObjectTypeBuffer) { + buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err); + if (ERROR_SET(err)) { + goto cleanup; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle); + ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal)); + } else if (opts->buffer.type == kObjectTypeArray) { + if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) { + api_set_error(err, + kErrorTypeValidation, + "Too many buffers. Please limit yourself to %d or fewer", AUCMD_MAX_PATTERNS); + goto cleanup; + } + + FOREACH_ITEM(opts->buffer.data.array, bufnr, { + if (bufnr.type != kObjectTypeInteger && bufnr.type != kObjectTypeBuffer) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'buffer': must be an integer"); + goto cleanup; + } + + buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err); + if (ERROR_SET(err)) { + goto cleanup; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle); + ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal)); + }); + } else if (opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Invalid value for 'buffer': must be an integer or array of integers"); + goto cleanup; + } + + FOREACH_ITEM(buffers, bufnr, { + pattern_filters[pattern_filter_count] = (char_u *)bufnr.data.string.data; + pattern_filter_count += 1; + }); + + FOR_ALL_AUEVENTS(event) { + if (check_event && !event_set[event]) { + continue; + } + + for (AutoPat *ap = au_get_autopat_for_event(event); ap != NULL; ap = ap->next) { + if (ap->cmds == NULL) { + continue; + } + + // Skip autocmds from invalid groups if passed. + if (group != 0 && ap->group != group) { + continue; + } + + // Skip 'pattern' from invalid patterns if passed. + if (pattern_filter_count > 0) { + bool passed = false; + for (int i = 0; i < pattern_filter_count; i++) { + assert(i < AUCMD_MAX_PATTERNS); + assert(pattern_filters[i]); + + char_u *pat = pattern_filters[i]; + int patlen = (int)STRLEN(pat); + + if (aupat_is_buflocal(pat, patlen)) { + aupat_normalize_buflocal_pat(pattern_buflocal, + pat, + patlen, + aupat_get_buflocal_nr(pat, patlen)); + + pat = pattern_buflocal; + } + + if (strequal((char *)ap->pat, (char *)pat)) { + passed = true; + break; + } + } + + if (!passed) { + continue; + } + } + + for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { + if (aucmd_exec_is_deleted(ac->exec)) { + continue; + } + + Dictionary autocmd_info = ARRAY_DICT_INIT; + + if (ap->group != AUGROUP_DEFAULT) { + PUT(autocmd_info, "group", INTEGER_OBJ(ap->group)); + } + + if (ac->id > 0) { + PUT(autocmd_info, "id", INTEGER_OBJ(ac->id)); + } + + if (ac->desc != NULL) { + PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc)); + } + + PUT(autocmd_info, + "command", + STRING_OBJ(cstr_to_string(aucmd_exec_to_string(ac, ac->exec)))); + + PUT(autocmd_info, + "pattern", + STRING_OBJ(cstr_to_string((char *)ap->pat))); + + PUT(autocmd_info, + "event", + STRING_OBJ(cstr_to_string((char *)event_nr2name(event)))); + + PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once)); + + if (ap->buflocal_nr) { + PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true)); + PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr)); + } else { + PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false)); + } + + // TODO(sctx): It would be good to unify script_ctx to actually work with lua + // right now it's just super weird, and never really gives you the info that + // you would expect from this. + // + // I think we should be able to get the line number, filename, etc. from lua + // when we're executing something, and it should be easy to then save that + // info here. + // + // I think it's a big loss not getting line numbers of where options, autocmds, + // etc. are set (just getting "Sourced (lua)" or something is not that helpful. + // + // Once we do that, we can put these into the autocmd_info, but I don't think it's + // useful to do that at this time. + // + // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid)); + // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum)); + + ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info)); + } + } + } + +cleanup: + api_free_array(buffers); + return autocmd_list; +} + +/// Create an |autocommand| +/// +/// The API allows for two (mutually exclusive) types of actions to be executed when the autocommand +/// triggers: a callback function (Lua or Vimscript), or a command (like regular autocommands). +/// +/// Example using callback: +/// <pre> +/// -- Lua function +/// local myluafun = function() print("This buffer enters") end +/// +/// -- Vimscript function name (as a string) +/// local myvimfun = "g:MyVimFunction" +/// +/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +/// pattern = {"*.c", "*.h"}, +/// callback = myluafun, -- Or myvimfun +/// }) +/// </pre> +/// +/// Example using command: +/// <pre> +/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +/// pattern = {"*.c", "*.h"}, +/// command = "echo 'Entering a C or C++ file'", +/// }) +/// </pre> +/// +/// Example values for pattern: +/// <pre> +/// pattern = "*.py" +/// pattern = { "*.py", "*.pyi" } +/// </pre> +/// +/// Examples values for event: +/// <pre> +/// "BufPreWrite" +/// {"CursorHold", "BufPreWrite", "BufPostWrite"} +/// </pre> +/// +/// @param event (string|array) The event or events to register this autocommand +/// @param opts Dictionary of autocommand options: +/// - group (string|integer) optional: the autocommand group name or +/// id to match against. +/// - pattern (string|array) optional: pattern or patterns to match +/// against |autocmd-pattern|. +/// - buffer (integer) optional: buffer number for buffer local autocommands +/// |autocmd-buflocal|. Cannot be used with {pattern}. +/// - desc (string) optional: description of the autocommand. +/// - callback (function|string) optional: if a string, the name of a Vimscript function +/// to call when this autocommand is triggered. Otherwise, a Lua function which is +/// called when this autocommand is triggered. Cannot be used with {command}. Lua +/// callbacks can return true to delete the autocommand; in addition, they accept a +/// single table argument with the following keys: +/// - id: (number) the autocommand id +/// - event: (string) the name of the event that triggered the autocommand +/// |autocmd-events| +/// - group: (number|nil) the autocommand group id, if it exists +/// - match: (string) the expanded value of |<amatch>| +/// - buf: (number) the expanded value of |<abuf>| +/// - file: (string) the expanded value of |<afile>| +/// - command (string) optional: Vim command to execute on event. Cannot be used with +/// {callback} +/// - once (boolean) optional: defaults to false. Run the autocommand +/// only once |autocmd-once|. +/// - nested (boolean) optional: defaults to false. Run nested +/// autocommands |autocmd-nested|. +/// +/// @return Integer id of the created autocommand. +/// @see |autocommand| +/// @see |nvim_del_autocmd()| +Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts, + Error *err) + FUNC_API_SINCE(9) +{ + int64_t autocmd_id = -1; + char *desc = NULL; + + Array patterns = ARRAY_DICT_INIT; + Array event_array = ARRAY_DICT_INIT; + + AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT; + Callback cb = CALLBACK_NONE; + + + if (!unpack_string_or_array(&event_array, &event, "event", true, err)) { + goto cleanup; + } + + if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "cannot pass both: 'callback' and 'command' for the same autocmd"); + goto cleanup; + } else if (opts->callback.type != kObjectTypeNil) { + // TODO(tjdevries): It's possible we could accept callable tables, + // but we don't do that many other places, so for the moment let's + // not do that. + + Object *callback = &opts->callback; + if (callback->type == kObjectTypeLuaRef) { + if (callback->data.luaref == LUA_NOREF) { + api_set_error(err, + kErrorTypeValidation, + "must pass an actual value"); + goto cleanup; + } + + if (!nlua_ref_is_function(callback->data.luaref)) { + api_set_error(err, + kErrorTypeValidation, + "must pass a function for callback"); + goto cleanup; + } + + cb.type = kCallbackLua; + cb.data.luaref = api_new_luaref(callback->data.luaref); + } else if (callback->type == kObjectTypeString) { + cb.type = kCallbackFuncref; + cb.data.funcref = vim_strsave((char_u *)callback->data.string.data); + } else { + api_set_error(err, + kErrorTypeException, + "'callback' must be a lua function or name of vim function"); + goto cleanup; + } + + aucmd.type = CALLABLE_CB; + aucmd.callable.cb = cb; + } else if (opts->command.type != kObjectTypeNil) { + Object *command = &opts->command; + if (command->type == kObjectTypeString) { + aucmd.type = CALLABLE_EX; + aucmd.callable.cmd = vim_strsave((char_u *)command->data.string.data); + } else { + api_set_error(err, + kErrorTypeValidation, + "'command' must be a string"); + goto cleanup; + } + } else { + api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'"); + goto cleanup; + } + + bool is_once = api_object_to_bool(opts->once, "once", false, err); + bool is_nested = api_object_to_bool(opts->nested, "nested", false, err); + + int au_group = get_augroup_from_object(opts->group, err); + if (au_group == AUGROUP_ERROR) { + goto cleanup; + } + + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { + goto cleanup; + } + + if (opts->desc.type != kObjectTypeNil) { + if (opts->desc.type == kObjectTypeString) { + desc = opts->desc.data.string.data; + } else { + api_set_error(err, + kErrorTypeValidation, + "'desc' must be a string"); + goto cleanup; + } + } + + if (patterns.size == 0) { + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*"))); + } + + if (event_array.size == 0) { + api_set_error(err, kErrorTypeValidation, "'event' is a required key"); + goto cleanup; + } + + autocmd_id = next_autocmd_id++; + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup); + + int retval; + + for (size_t i = 0; i < patterns.size; i++) { + Object pat = patterns.items[i]; + + // See: TODO(sctx) + WITH_SCRIPT_CONTEXT(channel_id, { + retval = autocmd_register(autocmd_id, + event_nr, + (char_u *)pat.data.string.data, + (int)pat.data.string.size, + au_group, + is_once, + is_nested, + desc, + aucmd); + }); + + if (retval == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to set autocmd"); + goto cleanup; + } + } + }); + + +cleanup: + aucmd_exec_free(&aucmd); + api_free_array(event_array); + api_free_array(patterns); + + return autocmd_id; +} + +/// Delete an autocommand by id. +/// +/// NOTE: Only autocommands created via the API have an id. +/// @param id Integer The id returned by nvim_create_autocmd +/// @see |nvim_create_autocmd()| +void nvim_del_autocmd(Integer id, Error *err) + FUNC_API_SINCE(9) +{ + if (id <= 0) { + api_set_error(err, kErrorTypeException, "Invalid autocmd id"); + return; + } + if (!autocmd_delete_id(id)) { + api_set_error(err, kErrorTypeException, "Failed to delete autocmd"); + } +} + +/// Clear all autocommands that match the corresponding {opts}. To delete +/// a particular autocmd, see |nvim_del_autocmd|. +/// @param opts Parameters +/// - event: (string|table) +/// Examples: +/// - event: "pat1" +/// - event: { "pat1" } +/// - event: { "pat1", "pat2", "pat3" } +/// - pattern: (string|table) +/// - pattern or patterns to match exactly. +/// - For example, if you have `*.py` as that pattern for the autocmd, +/// you must pass `*.py` exactly to clear it. `test.py` will not +/// match the pattern. +/// - defaults to clearing all patterns. +/// - NOTE: Cannot be used with {buffer} +/// - buffer: (bufnr) +/// - clear only |autocmd-buflocal| autocommands. +/// - NOTE: Cannot be used with {pattern} +/// - group: (string|int) The augroup name or id. +/// - NOTE: If not passed, will only delete autocmds *not* in any group. +/// +void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err) + FUNC_API_SINCE(9) +{ + // TODO(tjdevries): Future improvements: + // - once: (boolean) - Only clear autocmds with once. See |autocmd-once| + // - nested: (boolean) - Only clear autocmds with nested. See |autocmd-nested| + // - group: Allow passing "*" or true or something like that to force doing all + // autocmds, regardless of their group. + + Array patterns = ARRAY_DICT_INIT; + Array event_array = ARRAY_DICT_INIT; + + if (!unpack_string_or_array(&event_array, &opts->event, "event", false, err)) { + goto cleanup; + } + + if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Cannot use both 'pattern' and 'buffer'"); + goto cleanup; + } + + int au_group = get_augroup_from_object(opts->group, err); + if (au_group == AUGROUP_ERROR) { + goto cleanup; + } + + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { + goto cleanup; + } + + // When we create the autocmds, we want to say that they are all matched, so that's * + // but when we clear them, we want to say that we didn't pass a pattern, so that's NUL + if (patterns.size == 0) { + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING(""))); + } + + // If we didn't pass any events, that means clear all events. + if (event_array.size == 0) { + FOR_ALL_AUEVENTS(event) { + FOREACH_ITEM(patterns, pat_object, { + char_u *pat = (char_u *)pat_object.data.string.data; + if (!clear_autocmd(event, pat, au_group, err)) { + goto cleanup; + } + }); + } + } else { + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup); + + FOREACH_ITEM(patterns, pat_object, { + char_u *pat = (char_u *)pat_object.data.string.data; + if (!clear_autocmd(event_nr, pat, au_group, err)) { + goto cleanup; + } + }); + }); + } + +cleanup: + api_free_array(event_array); + api_free_array(patterns); + + return; +} + +/// Create or get an autocommand group |autocmd-groups|. +/// +/// To get an existing group id, do: +/// <pre> +/// local id = vim.api.nvim_create_augroup("MyGroup", { +/// clear = false +/// }) +/// </pre> +/// +/// @param name String: The name of the group +/// @param opts Dictionary Parameters +/// - clear (bool) optional: defaults to true. Clear existing +/// commands if the group already exists |autocmd-groups|. +/// @return Integer id of the created group. +/// @see |autocmd-groups| +Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augroup) *opts, + Error *err) + FUNC_API_SINCE(9) +{ + char *augroup_name = name.data; + bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err); + + int augroup = -1; + WITH_SCRIPT_CONTEXT(channel_id, { + augroup = augroup_add(augroup_name); + if (augroup == AUGROUP_ERROR) { + api_set_error(err, kErrorTypeException, "Failed to set augroup"); + return -1; + } + + if (clear_autocmds) { + FOR_ALL_AUEVENTS(event) { + aupat_del_for_event_and_group(event, augroup); + } + } + }); + + return augroup; +} + +/// Delete an autocommand group by id. +/// +/// To get a group id one can use |nvim_get_autocmds()|. +/// +/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// this group will also be deleted and cleared. This group will no longer exist. +/// @param id Integer The id of the group. +/// @see |nvim_del_augroup_by_name()| +/// @see |nvim_create_augroup()| +void nvim_del_augroup_by_id(Integer id, Error *err) + FUNC_API_SINCE(9) +{ + TRY_WRAP({ + try_start(); + char *name = augroup_name((int)id); + augroup_del(name, false); + try_end(err); + }); +} + +/// Delete an autocommand group by name. +/// +/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// this group will also be deleted and cleared. This group will no longer exist. +/// @param name String The name of the group. +/// @see |autocommand-groups| +void nvim_del_augroup_by_name(String name, Error *err) + FUNC_API_SINCE(9) +{ + TRY_WRAP({ + try_start(); + augroup_del(name.data, false); + try_end(err); + }); +} + +/// Execute all autocommands for {event} that match the corresponding +/// {opts} |autocmd-execute|. +/// @param event (String|Array) The event or events to execute +/// @param opts Dictionary of autocommand options: +/// - group (string|integer) optional: the autocommand group name or +/// id to match against. |autocmd-groups|. +/// - pattern (string|array) optional: defaults to "*" |autocmd-pattern|. Cannot be used +/// with {buffer}. +/// - buffer (integer) optional: buffer number |autocmd-buflocal|. Cannot be used with +/// {pattern}. +/// - modeline (bool) optional: defaults to true. Process the +/// modeline after the autocommands |<nomodeline>|. +/// @see |:doautocmd| +void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) + FUNC_API_SINCE(9) +{ + int au_group = AUGROUP_ALL; + bool modeline = true; + + buf_T *buf = curbuf; + bool set_buf = false; + + char_u *pattern = NULL; + bool set_pattern = false; + + Array event_array = ARRAY_DICT_INIT; + + if (!unpack_string_or_array(&event_array, &event, "event", true, err)) { + goto cleanup; + } + + switch (opts->group.type) { + case kObjectTypeNil: + break; + case kObjectTypeString: + au_group = augroup_find(opts->group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", opts->group.data.string.data); + goto cleanup; + } + break; + case kObjectTypeInteger: + au_group = (int)opts->group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + goto cleanup; + } + + if (opts->buffer.type != kObjectTypeNil) { + Object buf_obj = opts->buffer; + if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) { + api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type); + goto cleanup; + } + + buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err); + set_buf = true; + + if (ERROR_SET(err)) { + goto cleanup; + } + } + + if (opts->pattern.type != kObjectTypeNil) { + if (opts->pattern.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "'pattern' must be a string"); + goto cleanup; + } + + pattern = vim_strsave((char_u *)opts->pattern.data.string.data); + set_pattern = true; + } + + modeline = api_object_to_bool(opts->modeline, "modeline", true, err); + + if (set_pattern && set_buf) { + api_set_error(err, kErrorTypeValidation, "must not set 'buffer' and 'pattern'"); + goto cleanup; + } + + bool did_aucmd = false; + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup) + + did_aucmd |= apply_autocmds_group(event_nr, pattern, NULL, true, au_group, buf, NULL); + }) + + if (did_aucmd && modeline) { + do_modelines(0); + } + +cleanup: + api_free_array(event_array); + XFREE_CLEAR(pattern); +} + +static bool check_autocmd_string_array(Array arr, char *k, Error *err) +{ + for (size_t i = 0; i < arr.size; i++) { + if (arr.items[i].type != kObjectTypeString) { + api_set_error(err, + kErrorTypeValidation, + "All entries in '%s' must be strings", + k); + return false; + } + + // Disallow newlines in the middle of the line. + const String l = arr.items[i].data.string; + if (memchr(l.data, NL, l.size)) { + api_set_error(err, kErrorTypeValidation, + "String cannot contain newlines"); + return false; + } + } + return true; +} + +static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err) +{ + if (v->type == kObjectTypeString) { + ADD(*array, copy_object(*v)); + } else if (v->type == kObjectTypeArray) { + if (!check_autocmd_string_array(v->data.array, k, err)) { + return false; + } + *array = copy_array(v->data.array); + } else { + if (required) { + api_set_error(err, + kErrorTypeValidation, + "'%s' must be an array or a string.", + k); + return false; + } + } + + return true; +} + +// Returns AUGROUP_ERROR if there was a problem with {group} +static int get_augroup_from_object(Object group, Error *err) +{ + int au_group = AUGROUP_ERROR; + + switch (group.type) { + case kObjectTypeNil: + return AUGROUP_DEFAULT; + case kObjectTypeString: + au_group = augroup_find(group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", group.data.string.data); + + return AUGROUP_ERROR; + } + + return au_group; + case kObjectTypeInteger: + au_group = (int)group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + return AUGROUP_ERROR; + } + + return au_group; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + return AUGROUP_ERROR; + } +} + +static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer, + Error *err) +{ + const char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + + if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "cannot pass both: 'pattern' and 'buffer' for the same autocmd"); + return false; + } else if (pattern.type != kObjectTypeNil) { + Object *v = &pattern; + + if (v->type == kObjectTypeString) { + char_u *pat = (char_u *)v->data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } else if (v->type == kObjectTypeArray) { + if (!check_autocmd_string_array(*patterns, "pattern", err)) { + return false; + } + + Array array = v->data.array; + for (size_t i = 0; i < array.size; i++) { + char_u *pat = (char_u *)array.items[i].data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } + } else { + api_set_error(err, + kErrorTypeValidation, + "'pattern' must be a string"); + return false; + } + } else if (buffer.type != kObjectTypeNil) { + if (buffer.type != kObjectTypeInteger) { + api_set_error(err, + kErrorTypeValidation, + "'buffer' must be an integer"); + return false; + } + + buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err); + if (ERROR_SET(err)) { + return false; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle); + ADD(*patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal))); + } + + return true; +} + +static bool clear_autocmd(event_T event, char_u *pat, int au_group, Error *err) +{ + if (do_autocmd_event(event, pat, false, false, (char_u *)"", true, au_group) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to clear autocmd"); + return false; + } + + return true; +} + +#undef GET_ONE_EVENT diff --git a/src/nvim/api/autocmd.h b/src/nvim/api/autocmd.h new file mode 100644 index 0000000000..f9432830d9 --- /dev/null +++ b/src/nvim/api/autocmd.h @@ -0,0 +1,11 @@ +#ifndef NVIM_API_AUTOCMD_H +#define NVIM_API_AUTOCMD_H + +#include <stdint.h> + +#include "nvim/api/private/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/autocmd.h.generated.h" +#endif +#endif // NVIM_API_AUTOCMD_H diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 4076a0d220..6d5803a5fe 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -12,24 +12,19 @@ #include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" -#include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" -#include "nvim/getchar.h" #include "nvim/lua/executor.h" -#include "nvim/map.h" -#include "nvim/map_defs.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/undo.h" @@ -57,7 +52,7 @@ /// whether a buffer is loaded. -/// Gets the buffer line count +/// Returns the number of lines in the given buffer. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any @@ -138,7 +133,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - buffer handle /// - on_reload: Lua callback invoked on reload. The entire buffer /// content should be considered changed. Args: -/// - the string "detach" +/// - the string "reload" /// - buffer handle /// - utf_sizes: include UTF-32 and UTF-16 size of the replaced /// region, as args to `on_lines`. @@ -292,8 +287,8 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, } bool oob = false; - start = normalize_index(buf, start, &oob); - end = normalize_index(buf, end, &oob); + start = normalize_index(buf, start, true, &oob); + end = normalize_index(buf, end, true, &oob); if (strict_indexing && oob) { api_set_error(err, kErrorTypeValidation, "Index out of bounds"); @@ -379,15 +374,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ } bool oob = false; - start = normalize_index(buf, start, &oob); - end = normalize_index(buf, end, &oob); + start = normalize_index(buf, start, true, &oob); + end = normalize_index(buf, end, true, &oob); if (strict_indexing && oob) { api_set_error(err, kErrorTypeValidation, "Index out of bounds"); return; } - if (start > end) { api_set_error(err, kErrorTypeValidation, @@ -535,7 +529,7 @@ end: /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param start_row First line index -/// @param start_column Last column +/// @param start_column First column /// @param end_row Last line index /// @param end_column Last column /// @param replacement Array of lines to use as replacement @@ -559,13 +553,13 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // check range is ordered and everything! // start_row, end_row within buffer len (except add text past the end?) - start_row = normalize_index(buf, start_row, &oob); + start_row = normalize_index(buf, start_row, false, &oob); if (oob || start_row == buf->b_ml.ml_line_count + 1) { api_set_error(err, kErrorTypeValidation, "start_row out of bounds"); return; } - end_row = normalize_index(buf, end_row, &oob); + end_row = normalize_index(buf, end_row, false, &oob); if (oob || end_row == buf->b_ml.ml_line_count + 1) { api_set_error(err, kErrorTypeValidation, "end_row out of bounds"); return; @@ -603,12 +597,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In if (start_row == end_row) { old_byte = (bcount_t)end_col - start_col; } else { - const char *bufline; old_byte += (bcount_t)strlen(str_at_start) - start_col; for (int64_t i = 1; i < end_row - start_row; i++) { int64_t lnum = start_row + i; - bufline = (char *)ml_get_buf(buf, lnum, false); + const char *bufline = (char *)ml_get_buf(buf, lnum, false); old_byte += (bcount_t)(strlen(bufline))+1; } old_byte += (bcount_t)end_col+1; @@ -762,6 +755,108 @@ end: try_end(err); } +/// Gets a range from the buffer. +/// +/// This differs from |nvim_buf_get_lines()| in that it allows retrieving only +/// portions of a line. +/// +/// Indexing is zero-based. Column indices are end-exclusive. +/// +/// Prefer |nvim_buf_get_lines()| when retrieving entire lines. +/// +/// @param channel_id +/// @param buffer Buffer handle, or 0 for current buffer +/// @param start_row First line index +/// @param start_col Starting byte offset of first line +/// @param end_row Last line index +/// @param end_col Ending byte offset of last line (exclusive) +/// @param opts Optional parameters. Currently unused. +/// @param[out] err Error details, if any +/// @return Array of lines, or empty array for unloaded buffer. +ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, + Integer start_row, Integer start_col, + Integer end_row, Integer end_col, + Dictionary opts, Error *err) + FUNC_API_SINCE(9) +{ + Array rv = ARRAY_DICT_INIT; + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return rv; + } + + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return rv; + } + + // return sentinel value if the buffer isn't loaded + if (buf->b_ml.ml_mfp == NULL) { + return rv; + } + + bool oob = false; + start_row = normalize_index(buf, start_row, false, &oob); + end_row = normalize_index(buf, end_row, false, &oob); + + if (oob) { + api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + return rv; + } + + // nvim_buf_get_lines doesn't care if the start row is greater than the end + // row (it will just return an empty array), but nvim_buf_get_text does in + // order to maintain symmetry with nvim_buf_set_text. + if (start_row > end_row) { + api_set_error(err, kErrorTypeValidation, "start is higher than end"); + return rv; + } + + bool replace_nl = (channel_id != VIML_INTERNAL_CALL); + + if (start_row == end_row) { + String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err); + if (ERROR_SET(err)) { + return rv; + } + + ADD(rv, STRING_OBJ(line)); + return rv; + } + + rv.size = (size_t)(end_row - start_row) + 1; + rv.items = xcalloc(rv.size, sizeof(Object)); + + rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL-1, replace_nl, err)); + if (ERROR_SET(err)) { + goto end; + } + + if (rv.size > 2) { + Array tmp = ARRAY_DICT_INIT; + tmp.items = &rv.items[1]; + if (!buf_collect_lines(buf, rv.size - 2, start_row + 1, replace_nl, &tmp, err)) { + goto end; + } + } + + rv.items[rv.size-1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err)); + if (ERROR_SET(err)) { + goto end; + } + +end: + if (ERROR_SET(err)) { + api_free_array(rv); + rv.size = 0; + rv.items = NULL; + } + + return rv; +} + /// Returns the byte offset of a line (0-indexed). |api-indexing| /// /// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte. @@ -840,7 +935,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) /// @param[out] err Error details, if any /// @returns Array of maparg()-like dictionaries describing mappings. /// The "buffer" key holds the associated buffer handle. -ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) +ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, String mode, Error *err) FUNC_API_SINCE(3) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -849,7 +944,7 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) return (Array)ARRAY_DICT_INIT; } - return keymap_array(mode, buf); + return keymap_array(mode, buf, channel_id == LUA_INTERNAL_CALL); } /// Sets a buffer-local |mapping| for the given mode. @@ -857,11 +952,11 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) /// @see |nvim_set_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dict(keymap) *opts, - Error *err) +void nvim_buf_set_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, String rhs, + Dict(keymap) *opts, Error *err) FUNC_API_SINCE(6) { - modify_keymap(buffer, false, mode, lhs, rhs, opts, err); + modify_keymap(channel_id, buffer, false, mode, lhs, rhs, opts, err); } /// Unmaps a buffer-local |mapping| for the given mode. @@ -869,11 +964,11 @@ void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dic /// @see |nvim_del_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err) +void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, Error *err) FUNC_API_SINCE(6) { String rhs = { .data = "", .size = 0 }; - modify_keymap(buffer, true, mode, lhs, rhs, NULL, err); + modify_keymap(channel_id, buffer, true, mode, lhs, rhs, NULL, err); } /// Gets a map of buffer-local |user-commands|. @@ -1246,7 +1341,7 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// 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 +/// Otherwise a temporary scratch window (called the "autocmd window" for /// historical reasons) will be used. /// /// This is useful e.g. to call vimL functions that only work with the current @@ -1278,6 +1373,63 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) return res; } +/// Create a new user command |user-commands| in the given buffer. +/// +/// @param buffer Buffer handle, or 0 for current buffer. +/// @param[out] err Error details, if any. +/// @see nvim_create_user_command +void nvim_buf_create_user_command(Buffer buffer, String name, Object command, + Dict(user_command) *opts, Error *err) + FUNC_API_SINCE(9) +{ + buf_T *target_buf = find_buffer_by_handle(buffer, err); + if (ERROR_SET(err)) { + return; + } + + buf_T *save_curbuf = curbuf; + curbuf = target_buf; + create_user_command(name, command, opts, UC_BUFFER, err); + curbuf = save_curbuf; +} + +/// Delete a buffer-local user-defined command. +/// +/// Only commands created with |:command-buffer| or +/// |nvim_buf_create_user_command()| can be deleted with this function. +/// +/// @param buffer Buffer handle, or 0 for current buffer. +/// @param name Name of the command to delete. +/// @param[out] err Error details, if any. +void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) + FUNC_API_SINCE(9) +{ + garray_T *gap; + if (buffer == -1) { + gap = &ucmds; + } else { + buf_T *buf = find_buffer_by_handle(buffer, err); + gap = &buf->b_ucmds; + } + + for (int i = 0; i < gap->ga_len; i++) { + ucmd_T *cmd = USER_CMD_GA(gap, i); + if (!STRCMP(name.data, cmd->uc_name)) { + free_ucmd(cmd); + + gap->ga_len -= 1; + + if (i < gap->ga_len) { + memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T)); + } + + return; + } + } + + api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data); +} + Dictionary nvim__buf_stats(Buffer buffer, Error *err) { Dictionary rv = ARRAY_DICT_INIT; @@ -1334,11 +1486,11 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) } // Normalizes 0-based indexes to buffer line numbers -static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob) +static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob) { int64_t line_count = buf->b_ml.ml_line_count; // Fix if < 0 - index = index < 0 ? line_count + index +1 : index; + index = index < 0 ? line_count + index + (int)end_exclusive : index; // Check for oob if (index > line_count) { diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 76b699800e..6a41df0aa9 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -22,11 +22,11 @@ /// @deprecated /// @see nvim_exec -String nvim_command_output(String command, Error *err) +String nvim_command_output(uint64_t channel_id, String command, Error *err) FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(7) { - return nvim_exec(command, true, err); + return nvim_exec(channel_id, command, true, err); } /// @deprecated Use nvim_exec_lua() instead. @@ -51,11 +51,10 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err) FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(2) { - Integer rv = 0; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { - return rv; + return 0; } return buf->b_fnum; @@ -130,7 +129,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A return 0; } - uint64_t ns_id = src2ns(&src_id); + uint32_t ns_id = src2ns(&src_id); int width; VirtText virt_text = parse_virt_text(chunks, err, &width); @@ -148,11 +147,12 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A return src_id; } - Decoration *decor = xcalloc(1, sizeof(*decor)); - decor->virt_text = virt_text; - decor->virt_text_width = width; + Decoration decor = DECORATION_INIT; + decor.virt_text = virt_text; + decor.virt_text_width = width; + decor.priority = 0; - extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, true, + extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true, false, kExtmarkNoUndo); return src_id; } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 6f1fb15dac..8dca37a321 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -8,11 +8,12 @@ #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/decoration_provider.h" #include "nvim/extmark.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/memline.h" #include "nvim/screen.h" -#include "nvim/syntax.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/extmark.c.generated.h" @@ -85,12 +86,12 @@ const char *describe_ns(NS ns_id) } // Is the Namespace in use? -static bool ns_initialized(uint64_t ns) +static bool ns_initialized(uint32_t ns) { if (ns < 1) { return false; } - return ns < (uint64_t)next_namespace_id; + return ns < (uint32_t)next_namespace_id; } @@ -106,22 +107,55 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) if (add_dict) { Dictionary dict = ARRAY_DICT_INIT; + PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark.right_gravity)); + if (extmark.end_row >= 0) { PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row)); PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col)); + PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity)); + } + + 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)); + PUT(dict, "hl_eol", BOOLEAN_OBJ(decor->hl_eol)); + } + if (decor->hl_mode) { + PUT(dict, "hl_mode", STRING_OBJ(cstr_to_string(hl_mode_str[decor->hl_mode]))); } - 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)); + PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide)); + if (decor->virt_text_pos == kVTWinCol) { + PUT(dict, "virt_text_win_col", INTEGER_OBJ(decor->col)); } - if (kv_size(decor->virt_text)) { + PUT(dict, "virt_text_pos", + STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos]))); + } + + if (kv_size(decor->virt_lines)) { + Array all_chunks = ARRAY_DICT_INIT; + bool virt_lines_leftcol = false; + for (size_t i = 0; i < decor->virt_lines.size; i++) { Array chunks = ARRAY_DICT_INIT; - for (size_t i = 0; i < decor->virt_text.size; i++) { + VirtText *vt = &decor->virt_lines.items[i].line; + virt_lines_leftcol = decor->virt_lines.items[i].left_col; + for (size_t j = 0; j < vt->size; j++) { Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &decor->virt_text.items[i]; + VirtTextChunk *vtc = &vt->items[j]; ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text))); if (vtc->hl_id > 0) { ADD(chunk, @@ -129,9 +163,14 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) } ADD(chunks, ARRAY_OBJ(chunk)); } - PUT(dict, "virt_text", ARRAY_OBJ(chunks)); + ADD(all_chunks, ARRAY_OBJ(chunks)); } + PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks)); + PUT(dict, "virt_lines_above", BOOLEAN_OBJ(decor->virt_lines_above)); + PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol)); + } + if (decor->hl_id || kv_size(decor->virt_text)) { PUT(dict, "priority", INTEGER_OBJ(decor->priority)); } @@ -166,7 +205,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return rv; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } @@ -191,7 +230,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, } - ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); + ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); if (extmark.row < 0) { return rv; } @@ -220,9 +259,9 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// local pos = a.nvim_win_get_cursor(0) /// local ns = a.nvim_create_namespace('my-plugin') /// -- Create new extmark at line 1, column 1. -/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {}) +/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {}) /// -- Create new extmark at line 3, column 1. -/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {}) +/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {}) /// -- Get extmarks only from line 3. /// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) /// -- Get all marks in this buffer + namespace. @@ -252,7 +291,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e return rv; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } @@ -310,7 +349,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e } - ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, + ExtmarkInfoArray marks = extmark_get(buf, (uint32_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++) { @@ -339,7 +378,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// @param col Column where to place the mark, 0-based. |api-indexing| /// @param opts Optional parameters. /// - id : id of the extmark to edit. -/// - end_line : ending line of the mark, 0-based inclusive. +/// - end_row : ending line of the mark, 0-based inclusive. /// - end_col : ending col of the mark, 0-based exclusive. /// - hl_group : name of the highlight group used to highlight /// this mark. @@ -404,6 +443,36 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// for left). Defaults to false. /// - priority: a priority value for the highlight group. For /// example treesitter highlighting uses a value of 100. +/// - strict: boolean that indicates extmark should not be placed +/// if the line or column value is past the end of the +/// buffer or end of the line respectively. Defaults to true. +/// - sign_text: string of length 1-2 used to display in the +/// sign column. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - sign_hl_group: name of the highlight group used to +/// highlight the sign column text. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - number_hl_group: name of the highlight group used to +/// highlight the number column. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - line_hl_group: name of the highlight group used to +/// highlight the whole line. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - cursorline_hl_group: name of the highlight group used to +/// highlight the line when the cursor is on the same line +/// as the mark and 'cursorline' is enabled. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - conceal: string which should be either empty or a single +/// character. Enable concealing similar to |:syn-conceal|. +/// When a character is supplied it is used as |:syn-cchar|. +/// "hl_group" is used as highlight for the cchar if provided, +/// otherwise it defaults to |hl-Conceal|. +/// /// @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 line, Integer col, @@ -417,30 +486,49 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); goto error; } - uint64_t id = 0; + uint32_t id = 0; if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) { - id = (uint64_t)opts->id.data.integer; + id = (uint32_t)opts->id.data.integer; } else if (HAS_KEY(opts->id)) { api_set_error(err, kErrorTypeValidation, "id is not a positive integer"); goto error; } int line2 = -1; - if (opts->end_line.type == kObjectTypeInteger) { - Integer val = opts->end_line.data.integer; - if (val < 0 || val > buf->b_ml.ml_line_count) { - api_set_error(err, kErrorTypeValidation, "end_line value outside range"); + + // For backward compatibility we support "end_line" as an alias for "end_row" + if (HAS_KEY(opts->end_line)) { + if (HAS_KEY(opts->end_row)) { + api_set_error(err, kErrorTypeValidation, "cannot use both end_row and end_line"); + goto error; + } + opts->end_row = opts->end_line; + } + +#define OPTION_TO_BOOL(target, name, val) \ + target = api_object_to_bool(opts->name, #name, val, err); \ + if (ERROR_SET(err)) { \ + goto error; \ + } + + bool strict = true; + OPTION_TO_BOOL(strict, strict, true); + + if (opts->end_row.type == kObjectTypeInteger) { + Integer val = opts->end_row.data.integer; + if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) { + api_set_error(err, kErrorTypeValidation, "end_row value outside range"); goto error; } else { line2 = (int)val; } - } else if (HAS_KEY(opts->end_line)) { - api_set_error(err, kErrorTypeValidation, "end_line is not an integer"); + } else if (HAS_KEY(opts->end_row)) { + api_set_error(err, kErrorTypeValidation, "end_row is not an integer"); goto error; } @@ -458,13 +546,39 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - if (HAS_KEY(opts->hl_group)) { - decor.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err); - if (ERROR_SET(err)) { - goto error; + struct { + const char *name; + Object *opt; + int *dest; + } hls[] = { + { "hl_group" , &opts->hl_group , &decor.hl_id }, + { "sign_hl_group" , &opts->sign_hl_group , &decor.sign_hl_id }, + { "number_hl_group" , &opts->number_hl_group , &decor.number_hl_id }, + { "line_hl_group" , &opts->line_hl_group , &decor.line_hl_id }, + { "cursorline_hl_group", &opts->cursorline_hl_group, &decor.cursorline_hl_id }, + { NULL, NULL, NULL }, + }; + + for (int j = 0; hls[j].name && hls[j].dest; j++) { + if (HAS_KEY(*hls[j].opt)) { + *hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err); + if (ERROR_SET(err)) { + goto error; + } } } + if (opts->conceal.type == kObjectTypeString) { + String c = opts->conceal.data.string; + decor.conceal = true; + if (c.size) { + decor.conceal_char = utf_ptr2char((const char_u *)c.data); + } + } else if (HAS_KEY(opts->conceal)) { + api_set_error(err, kErrorTypeValidation, "conceal is not a String"); + goto error; + } + if (opts->virt_text.type == kObjectTypeArray) { decor.virt_text = parse_virt_text(opts->virt_text.data.array, err, &decor.virt_text_width); @@ -502,12 +616,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } -#define OPTION_TO_BOOL(target, name, val) \ - target = api_object_to_bool(opts->name, #name, val, err); \ - if (ERROR_SET(err)) { \ - goto error; \ - } - OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false); OPTION_TO_BOOL(decor.hl_eol, hl_eol, false); @@ -567,14 +675,25 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } + if (opts->sign_text.type == kObjectTypeString) { + if (!init_sign_text(&decor.sign_text, + (char_u *)opts->sign_text.data.string.data)) { + api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value"); + goto error; + } + } else if (HAS_KEY(opts->sign_text)) { + api_set_error(err, kErrorTypeValidation, "sign_text is not a String"); + goto error; + } + bool right_gravity = true; OPTION_TO_BOOL(right_gravity, right_gravity, true); // Only error out if they try to set end_right_gravity without - // setting end_col or end_line + // setting end_col or end_row if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) { api_set_error(err, kErrorTypeValidation, - "cannot set end_right_gravity without setting end_line or end_col"); + "cannot set end_right_gravity without setting end_row or end_col"); goto error; } @@ -586,16 +705,30 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool ephemeral = false; OPTION_TO_BOOL(ephemeral, ephemeral, false); - if (line < 0 || line > buf->b_ml.ml_line_count) { + if (line < 0) { api_set_error(err, kErrorTypeValidation, "line value outside range"); goto error; + } else if (line > buf->b_ml.ml_line_count) { + if (strict) { + api_set_error(err, kErrorTypeValidation, "line value outside range"); + goto error; + } else { + line = buf->b_ml.ml_line_count; + } } else if (line < buf->b_ml.ml_line_count) { len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line+1, false)); } if (col == -1) { col = (Integer)len; - } else if (col < -1 || col > (Integer)len) { + } else if (col > (Integer)len) { + if (strict) { + api_set_error(err, kErrorTypeValidation, "col value outside range"); + goto error; + } else { + col = (Integer)len; + } + } else if (col < -1) { api_set_error(err, kErrorTypeValidation, "col value outside range"); goto error; } @@ -611,27 +744,17 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer line2 = (int)line; } if (col2 > (Integer)len) { - api_set_error(err, kErrorTypeValidation, "end_col value outside range"); - goto error; + if (strict) { + api_set_error(err, kErrorTypeValidation, "end_col value outside range"); + goto error; + } else { + col2 = (int)len; + } } } else if (line2 >= 0) { col2 = 0; } - Decoration *d = NULL; - - if (ephemeral) { - d = &decor; - } else if (kv_size(decor.virt_text) || kv_size(decor.virt_lines) - || decor.priority != DECOR_PRIORITY_BASE - || decor.hl_eol) { - // TODO(bfredl): this is a bit sketchy. eventually we should - // have predefined decorations for both marks/ephemerals - d = xcalloc(1, sizeof(*d)); - *d = decor; - } else if (decor.hl_id) { - d = decor_hl(decor.hl_id); - } // TODO(bfredl): synergize these two branches even more if (ephemeral && decor_state.buf == buf) { @@ -642,18 +765,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2, - d, right_gravity, end_right_gravity, kExtmarkNoUndo); - - if (kv_size(decor.virt_lines)) { - redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(decor.virt_lines_above?0:1))); - } + extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2, + &decor, right_gravity, end_right_gravity, kExtmarkNoUndo); } return (Integer)id; error: clear_virttext(&decor.virt_text); + xfree(decor.sign_text); return 0; } @@ -672,23 +792,23 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er if (!buf) { return false; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return false; } - return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id); + return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id); } -uint64_t src2ns(Integer *src_id) +uint32_t src2ns(Integer *src_id) { if (*src_id == 0) { *src_id = nvim_create_namespace((String)STRING_INIT); } if (*src_id < 0) { - return UINT64_MAX; + return (((uint32_t)1) << 31) - 1; } else { - return (uint64_t)(*src_id); + return (uint32_t)(*src_id); } } @@ -743,7 +863,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In col_end = MAXCOL; } - uint64_t ns = src2ns(&ns_id); + uint32_t ns = src2ns(&ns_id); if (!(line < buf->b_ml.ml_line_count)) { // safety check, we can't add marks outside the range @@ -752,7 +872,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In int hl_id = 0; if (hl_group.size > 0) { - hl_id = syn_check_group(hl_group.data, (int)hl_group.size); + hl_id = syn_check_group(hl_group.data, hl_group.size); } else { return ns_id; } @@ -763,10 +883,13 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In end_line++; } + Decoration decor = DECORATION_INIT; + decor.hl_id = hl_id; + extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, - decor_hl(hl_id), true, false, kExtmarkNoUndo); + &decor, true, false, kExtmarkNoUndo); return ns_id; } @@ -798,7 +921,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, if (line_end < 0 || line_end > MAXLNUM) { line_end = MAXLNUM; } - extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id), + extmark_clear(buf, (ns_id < 0 ? 0 : (uint32_t)ns_id), (int)line_start, 0, (int)line_end-1, MAXCOL); } @@ -839,7 +962,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// - 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) +/// interaction with fold lines is subject to change) /// ["win", winid, bufnr, row] /// - on_end: called at the end of a redraw cycle /// ["end", tick] diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 144c252687..8ad4dae928 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -5,6 +5,7 @@ return { set_extmark = { "id"; "end_line"; + "end_row"; "end_col"; "hl_group"; "virt_text"; @@ -20,6 +21,13 @@ return { "virt_lines"; "virt_lines_above"; "virt_lines_leftcol"; + "strict"; + "sign_text"; + "sign_hl_group"; + "number_hl_group"; + "line_hl_group"; + "cursorline_hl_group"; + "conceal"; }; keymap = { "noremap"; @@ -28,10 +36,25 @@ return { "script"; "expr"; "unique"; + "callback"; + "desc"; }; get_commands = { "builtin"; }; + user_command = { + "addr"; + "bang"; + "bar"; + "complete"; + "count"; + "desc"; + "force"; + "keepscript"; + "nargs"; + "range"; + "register"; + }; float_config = { "row"; "col"; @@ -58,5 +81,78 @@ return { "highlights"; "use_tabline"; }; + option = { + "scope"; + }; + highlight = { + "bold"; + "standout"; + "strikethrough"; + "underline"; + "underlineline"; + "undercurl"; + "underdot"; + "underdash"; + "italic"; + "reverse"; + "nocombine"; + "default"; + "global"; + "cterm"; + "foreground"; "fg"; + "background"; "bg"; + "ctermfg"; + "ctermbg"; + "special"; "sp"; + "link"; + "fallback"; + "blend"; + "temp"; + }; + highlight_cterm = { + "bold"; + "standout"; + "strikethrough"; + "underline"; + "underlineline"; + "undercurl"; + "underdot"; + "underdash"; + "italic"; + "reverse"; + "nocombine"; + }; + -- Autocmds + clear_autocmds = { + "buffer"; + "event"; + "group"; + "pattern"; + }; + create_autocmd = { + "buffer"; + "callback"; + "command"; + "desc"; + "group"; + "nested"; + "once"; + "pattern"; + }; + exec_autocmds = { + "buffer"; + "group"; + "modeline"; + "pattern"; + }; + get_autocmds = { + "event"; + "group"; + "pattern"; + "buffer"; + }; + create_augroup = { + "clear"; + }; } diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 36da6c13a9..a26383ec7d 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -10,6 +10,9 @@ #include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/lua/converter.h" +#include "nvim/lua/executor.h" /// Helper structure for vim_to_object typedef struct { @@ -54,14 +57,20 @@ typedef struct { const size_t len_ = (size_t)(len); \ const blob_T *const blob_ = (blob); \ kvi_push(edata->stack, STRING_OBJ(((String) { \ - .data = len_ != 0 ? xmemdup(blob_->bv_ga.ga_data, len_) : NULL, \ + .data = len_ != 0 ? xmemdupz(blob_->bv_ga.ga_data, len_) : xstrdup(""), \ .size = len_ \ }))); \ } while (0) #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - TYPVAL_ENCODE_CONV_NIL(tv); \ + ufunc_T *fp = find_func(fun); \ + if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \ + LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \ + kvi_push(edata->stack, LUAREF_OBJ(ref)); \ + } else { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + } \ goto typval_encode_stop_converting_one_item; \ } while (0) @@ -340,6 +349,16 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) tv->vval.v_dict = dict; break; } + + case kObjectTypeLuaRef: { + LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); + state->lua_callable.func_ref = api_new_luaref(obj.data.luaref); + char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state); + tv->v_type = VAR_FUNC; + tv->vval.v_string = vim_strsave(name); + break; + } + default: abort(); } diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index 8ab7743e01..ba2e560d63 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -6,22 +6,31 @@ #include <msgpack.h> #include <stdbool.h> -#include "nvim/api/buffer.h" #include "nvim/api/deprecated.h" -#include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/log.h" +#include "nvim/map.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/vim.h" + +// =========================================================================== +// NEW API FILES MUST GO HERE. +// +// When creating a new API file, you must include it here, +// so that the dispatcher can find the C functions that you are creating! +// =========================================================================== +#include "nvim/api/autocmd.h" +#include "nvim/api/buffer.h" +#include "nvim/api/extmark.h" #include "nvim/api/tabpage.h" #include "nvim/api/ui.h" #include "nvim/api/vim.h" #include "nvim/api/vimscript.h" #include "nvim/api/win_config.h" #include "nvim/api/window.h" -#include "nvim/log.h" -#include "nvim/map.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/vim.h" +#include "nvim/ui_client.h" static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT; @@ -30,6 +39,13 @@ static void msgpack_rpc_add_method_handler(String method, MsgpackRpcRequestHandl map_put(String, MsgpackRpcRequestHandler)(&methods, method, handler); } +void msgpack_rpc_add_redraw(void) +{ + msgpack_rpc_add_method_handler(STATIC_CSTR_AS_STRING("redraw"), + (MsgpackRpcRequestHandler) { .fn = ui_client_handle_redraw, + .fast = true }); +} + /// @param name API method name /// @param name_len name size (includes terminating NUL) MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, size_t name_len, diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index d470def277..5ba4700f62 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -22,6 +22,7 @@ #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" #include "nvim/lua/executor.h" #include "nvim/map.h" @@ -32,7 +33,6 @@ #include "nvim/msgpack_rpc/helpers.h" #include "nvim/option.h" #include "nvim/option_defs.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/version.h" #include "nvim/vim.h" @@ -111,7 +111,7 @@ bool try_leave(const TryState *const tstate, Error *const err) /// try_enter()/try_leave() pair should be used instead. void try_start(void) { - ++trylevel; + trylevel++; } /// End try block, set the error message if any and return true if an error @@ -139,10 +139,10 @@ bool try_end(Error *err) got_int = false; } else if (msg_list != NULL && *msg_list != NULL) { int should_free; - char *msg = (char *)get_exception_string(*msg_list, - ET_ERROR, - NULL, - &should_free); + char *msg = get_exception_string(*msg_list, + ET_ERROR, + NULL, + &should_free); api_set_error(err, kErrorTypeException, "%s", msg); free_global_msglist(); @@ -396,19 +396,14 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object stringval = value.data.string.data; } - const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_sid = - channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; - current_sctx.sc_lnum = 0; - current_channel_id = channel_id; + WITH_SCRIPT_CONTEXT(channel_id, { + const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) + ? 0 : (type == SREQ_GLOBAL) + ? OPT_GLOBAL : OPT_LOCAL; - const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) - ? 0 : (type == SREQ_GLOBAL) - ? OPT_GLOBAL : OPT_LOCAL; - set_option_value_for(name.data, numval, stringval, - opt_flags, type, to, err); - - current_sctx = save_current_sctx; + set_option_value_for(name.data, numval, stringval, + opt_flags, type, to, err); + }); } @@ -513,7 +508,7 @@ String cbuf_to_string(const char *buf, size_t size) String cstrn_to_string(const char *str, size_t maxsize) FUNC_ATTR_NONNULL_ALL { - return cbuf_to_string(str, strnlen(str, maxsize)); + return cbuf_to_string(str, STRNLEN(str, maxsize)); } /// Creates a String using the given C string. Unlike @@ -591,9 +586,10 @@ Array string_to_array(const String input, bool crlf) /// @param buffer Buffer handle for a specific buffer, or 0 for the current /// buffer, or -1 to signify global behavior ("all buffers") /// @param is_unmap When true, removes the mapping that matches {lhs}. -void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String rhs, - Dict(keymap) *opts, Error *err) +void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mode, String lhs, + String rhs, Dict(keymap) *opts, Error *err) { + LuaRef lua_funcref = LUA_NOREF; bool global = (buffer == -1); if (global) { buffer = 0; @@ -604,6 +600,12 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String return; } + const sctx_T save_current_sctx = api_set_sctx(channel_id); + + if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) { + lua_funcref = opts->callback.data.luaref; + opts->callback.data.luaref = LUA_NOREF; + } MapArguments parsed_args = MAP_ARGUMENTS_INIT; if (opts) { #define KEY_TO_BOOL(name) \ @@ -623,9 +625,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String parsed_args.buffer = !global; set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size, - (char_u *)rhs.data, rhs.size, + (char_u *)rhs.data, rhs.size, lua_funcref, CPO_TO_CPO_FLAGS, &parsed_args); - + if (opts != NULL && opts->desc.type == kObjectTypeString) { + parsed_args.desc = xstrdup(opts->desc.data.string.data); + } else { + parsed_args.desc = NULL; + } if (parsed_args.lhs_len > MAXMAPLEN) { api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data); goto fail_and_free; @@ -658,7 +664,8 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String bool is_noremap = parsed_args.noremap; assert(!(is_unmap && is_noremap)); - if (!is_unmap && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) { + if (!is_unmap && lua_funcref == LUA_NOREF + && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) { if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop> parsed_args.rhs_is_noop = true; } else { @@ -668,9 +675,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data); goto fail_and_free; } - } else if (is_unmap && parsed_args.rhs_len) { - api_set_error(err, kErrorTypeValidation, - "Gave nonempty RHS in unmap command: %s", parsed_args.rhs); + } else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) { + if (parsed_args.rhs_len) { + api_set_error(err, kErrorTypeValidation, + "Gave nonempty RHS in unmap command: %s", parsed_args.rhs); + } else { + api_set_error(err, kErrorTypeValidation, "Gave nonempty RHS for unmap"); + } goto fail_and_free; } @@ -700,10 +711,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String goto fail_and_free; } // switch + parsed_args.rhs_lua = LUA_NOREF; // don't clear ref on success fail_and_free: + current_sctx = save_current_sctx; + NLUA_CLEAR_REF(parsed_args.rhs_lua); xfree(parsed_args.rhs); xfree(parsed_args.orig_rhs); - return; + XFREE_CLEAR(parsed_args.desc); } /// Collects `n` buffer lines into array `l`, optionally replacing newlines @@ -742,6 +756,52 @@ bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Arr return true; } +/// Returns a substring of a buffer line +/// +/// @param buf Buffer handle +/// @param lnum Line number (1-based) +/// @param start_col Starting byte offset into line (0-based) +/// @param end_col Ending byte offset into line (0-based, exclusive) +/// @param replace_nl Replace newlines ('\n') with null ('\0') +/// @param err Error object +/// @return The text between start_col and end_col on line lnum of buffer buf +String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, bool replace_nl, + Error *err) +{ + String rv = STRING_INIT; + + if (lnum >= MAXLNUM) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); + return rv; + } + + const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + size_t line_length = strlen(bufstr); + + start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col; + end_col = end_col < 0 ? (int64_t)line_length + end_col + 1 : end_col; + + if (start_col >= MAXCOL || end_col >= MAXCOL) { + api_set_error(err, kErrorTypeValidation, "Column index is too high"); + return rv; + } + + if (start_col > end_col) { + api_set_error(err, kErrorTypeValidation, "start_col must be less than end_col"); + return rv; + } + + if ((size_t)start_col >= line_length) { + return rv; + } + + rv = cstrn_to_string(&bufstr[start_col], (size_t)(end_col - start_col)); + if (replace_nl) { + strchrsub(rv.data, '\n', '\0'); + } + + return rv; +} void api_free_string(String value) { @@ -961,6 +1021,10 @@ Object copy_object(Object obj) case kObjectTypeDictionary: return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary)); + + case kObjectTypeLuaRef: + return LUAREF_OBJ(api_new_luaref(obj.data.luaref)); + default: abort(); } @@ -969,18 +1033,15 @@ Object copy_object(Object obj) static void set_option_value_for(char *key, int numval, char *stringval, int opt_flags, int opt_type, void *from, Error *err) { - win_T *save_curwin = NULL; - tabpage_T *save_curtab = NULL; + switchwin_T switchwin; aco_save_T aco; try_start(); - switch (opt_type) - { + switch (opt_type) { case SREQ_WIN: - if (switch_win_noblock(&save_curwin, &save_curtab, (win_T *)from, - win_find_tabpage((win_T *)from), true) + if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) == FAIL) { - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); if (try_end(err)) { return; } @@ -990,7 +1051,7 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt return; } set_option_value_err(key, numval, stringval, opt_flags, err); - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); break; case SREQ_BUF: aucmd_prepbuf(&aco, (buf_T *)from); @@ -1048,8 +1109,9 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...) /// /// @param mode The abbreviation for the mode /// @param buf The buffer to get the mapping array. NULL for global +/// @param from_lua Whether it is called from internal lua api. /// @returns Array of maparg()-like dictionaries describing mappings -ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) +ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua) { Array mappings = ARRAY_DICT_INIT; dict_T *const dict = tv_dict_alloc(); @@ -1069,8 +1131,19 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) // Check for correct mode if (int_mode & current_maphash->m_mode) { mapblock_fill_dict(dict, current_maphash, buffer_value, false); - ADD(mappings, vim_to_object((typval_T[]) { { .v_type = VAR_DICT, .vval.v_dict = dict } })); - + Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT, + .vval.v_dict = dict } }); + if (from_lua) { + Dictionary d = api_dict.data.dictionary; + for (size_t j = 0; j < d.size; j++) { + if (strequal("callback", d.items[j].key.data)) { + d.items[j].value.type = kObjectTypeLuaRef; + d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer); + break; + } + } + } + ADD(mappings, api_dict); tv_dict_clear(dict); } } @@ -1108,7 +1181,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int return false; } - ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); + ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); if (extmark.row >= 0) { *row = extmark.row; *col = extmark.col; @@ -1220,7 +1293,7 @@ int object_to_hl_id(Object obj, const char *what, Error *err) { if (obj.type == kObjectTypeString) { String str = obj.data.string; - return str.size ? syn_check_group(str.data, (int)str.size) : 0; + return str.size ? syn_check_group(str.data, str.size) : 0; } else if (obj.type == kObjectTypeInteger) { return MAX((int)obj.data.integer, 0); } else { @@ -1254,7 +1327,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) String hl = chunk.items[1].data.string; if (hl.size > 0) { // TODO(bfredl): use object_to_hl_id and allow integer - int hl_id = syn_check_group(hl.data, (int)hl.size); + int hl_id = syn_check_group(hl.data, hl.size); attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; } } @@ -1342,3 +1415,267 @@ const char *get_default_stl_hl(win_T *wp) return "StatusLineNC"; } } + +void create_user_command(String name, Object command, Dict(user_command) *opts, int flags, + Error *err) +{ + uint32_t argt = 0; + long def = -1; + cmd_addr_T addr_type_arg = ADDR_NONE; + int compl = EXPAND_NOTHING; + char *compl_arg = NULL; + char *rep = NULL; + LuaRef luaref = LUA_NOREF; + LuaRef compl_luaref = LUA_NOREF; + + if (!uc_validate_name(name.data)) { + api_set_error(err, kErrorTypeValidation, "Invalid command name"); + goto err; + } + + if (mb_islower(name.data[0])) { + api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter"); + goto err; + } + + if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) { + api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive"); + goto err; + } + + if (opts->nargs.type == kObjectTypeInteger) { + switch (opts->nargs.data.integer) { + case 0: + // Default value, nothing to do + break; + case 1: + argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG; + break; + default: + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + } else if (opts->nargs.type == kObjectTypeString) { + if (opts->nargs.data.string.size > 1) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + + switch (opts->nargs.data.string.data[0]) { + case '*': + argt |= EX_EXTRA; + break; + case '?': + argt |= EX_EXTRA | EX_NOSPC; + break; + case '+': + argt |= EX_EXTRA | EX_NEEDARG; + break; + default: + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + } else if (HAS_KEY(opts->nargs)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + + if (HAS_KEY(opts->complete) && !argt) { + api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'"); + goto err; + } + + if (opts->range.type == kObjectTypeBoolean) { + if (opts->range.data.boolean) { + argt |= EX_RANGE; + addr_type_arg = ADDR_LINES; + } + } else if (opts->range.type == kObjectTypeString) { + if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) { + argt |= EX_RANGE | EX_DFLALL; + addr_type_arg = ADDR_LINES; + } else { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'"); + goto err; + } + } else if (opts->range.type == kObjectTypeInteger) { + argt |= EX_RANGE | EX_ZEROR; + def = opts->range.data.integer; + addr_type_arg = ADDR_LINES; + } else if (HAS_KEY(opts->range)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'"); + goto err; + } + + if (opts->count.type == kObjectTypeBoolean) { + if (opts->count.data.boolean) { + argt |= EX_COUNT | EX_ZEROR | EX_RANGE; + addr_type_arg = ADDR_OTHER; + def = 0; + } + } else if (opts->count.type == kObjectTypeInteger) { + argt |= EX_COUNT | EX_ZEROR | EX_RANGE; + addr_type_arg = ADDR_OTHER; + def = opts->count.data.integer; + } else if (HAS_KEY(opts->count)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'"); + goto err; + } + + if (opts->addr.type == kObjectTypeString) { + if (parse_addr_type_arg((char_u *)opts->addr.data.string.data, (int)opts->addr.data.string.size, + &addr_type_arg) != OK) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); + goto err; + } + + if (addr_type_arg != ADDR_LINES) { + argt |= EX_ZEROR; + } + } else if (HAS_KEY(opts->addr)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); + goto err; + } + + if (api_object_to_bool(opts->bang, "bang", false, err)) { + argt |= EX_BANG; + } else if (ERROR_SET(err)) { + goto err; + } + + if (api_object_to_bool(opts->bar, "bar", false, err)) { + argt |= EX_TRLBAR; + } else if (ERROR_SET(err)) { + goto err; + } + + + if (api_object_to_bool(opts->register_, "register", false, err)) { + argt |= EX_REGSTR; + } else if (ERROR_SET(err)) { + goto err; + } + + if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) { + argt |= EX_KEEPSCRIPT; + } else if (ERROR_SET(err)) { + goto err; + } + + bool force = api_object_to_bool(opts->force, "force", true, err); + if (ERROR_SET(err)) { + goto err; + } + + if (opts->complete.type == kObjectTypeLuaRef) { + compl = EXPAND_USER_LUA; + compl_luaref = api_new_luaref(opts->complete.data.luaref); + } else if (opts->complete.type == kObjectTypeString) { + if (parse_compl_arg((char_u *)opts->complete.data.string.data, + (int)opts->complete.data.string.size, &compl, &argt, + (char_u **)&compl_arg) != OK) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); + goto err; + } + } else if (HAS_KEY(opts->complete)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); + goto err; + } + + switch (command.type) { + case kObjectTypeLuaRef: + luaref = api_new_luaref(command.data.luaref); + if (opts->desc.type == kObjectTypeString) { + rep = opts->desc.data.string.data; + } else { + snprintf((char *)IObuff, IOSIZE, "<Lua function %d>", luaref); + rep = (char *)IObuff; + } + break; + case kObjectTypeString: + rep = command.data.string.data; + break; + default: + api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function"); + goto err; + } + + if (uc_add_command((char_u *)name.data, name.size, (char_u *)rep, argt, def, flags, + compl, (char_u *)compl_arg, compl_luaref, addr_type_arg, luaref, + force) != OK) { + api_set_error(err, kErrorTypeException, "Failed to create user command"); + goto err; + } + + return; + +err: + NLUA_CLEAR_REF(luaref); + NLUA_CLEAR_REF(compl_luaref); +} + +int find_sid(uint64_t channel_id) +{ + switch (channel_id) { + case VIML_INTERNAL_CALL: + // TODO(autocmd): Figure out what this should be + // return SID_API_CLIENT; + case LUA_INTERNAL_CALL: + return SID_LUA; + default: + return SID_API_CLIENT; + } +} + +/// Sets sctx for API calls. +/// +/// @param channel_id api clients id. Used to determine if it's a internal +/// call or a rpc call. +/// @return returns previous value of current_sctx. To be used +/// to be used for restoring sctx to previous state. +sctx_T api_set_sctx(uint64_t channel_id) +{ + sctx_T old_current_sctx = current_sctx; + if (channel_id != VIML_INTERNAL_CALL) { + current_sctx.sc_sid = + channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; + current_sctx.sc_lnum = 0; + } + return old_current_sctx; +} + +// adapted from sign.c:sign_define_init_text. +// TODO(lewis6991): Consider merging +int init_sign_text(char_u **sign_text, char_u *text) +{ + char_u *s; + + char_u *endp = text + (int)STRLEN(text); + + // Count cells and check for non-printable chars + int cells = 0; + for (s = text; s < endp; s += utfc_ptr2len(s)) { + if (!vim_isprintc(utf_ptr2char(s))) { + break; + } + cells += utf_ptr2cells(s); + } + // Currently must be empty, one or two display cells + if (s != endp || cells > 2) { + return FAIL; + } + if (cells < 1) { + return OK; + } + + // Allocate one byte more if we need to pad up + // with a space. + size_t len = (size_t)(endp - text + ((cells == 1) ? 1 : 0)); + *sign_text = vim_strnsave(text, len); + + if (cells == 1) { + STRCPY(*sign_text + len - 1, " "); + } + + return OK; +} diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 6d0aec9c90..650349cde7 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -138,10 +138,30 @@ typedef struct { msg_list = saved_msg_list; /* Restore the exception context. */ \ } while (0) +// Useful macro for executing some `code` for each item in an array. +#define FOREACH_ITEM(a, __foreach_item, code) \ + for (size_t (__foreach_item ## _index) = 0; (__foreach_item ## _index) < (a).size; \ + (__foreach_item ## _index)++) { \ + Object __foreach_item = (a).items[__foreach_item ## _index]; \ + code; \ + } + + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" # include "keysets.h.generated.h" #endif +#define WITH_SCRIPT_CONTEXT(channel_id, code) \ + do { \ + const sctx_T save_current_sctx = current_sctx; \ + current_sctx.sc_sid = \ + (channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \ + current_sctx.sc_lnum = 0; \ + current_channel_id = channel_id; \ + code; \ + current_sctx = save_current_sctx; \ + } while (0); + #endif // NVIM_API_PRIVATE_HELPERS_H diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 14b6be8eeb..b994d18c43 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -102,11 +102,10 @@ void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err) Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) FUNC_API_SINCE(1) { - Window rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); if (!tab || !valid_tabpage(tab)) { - return rv; + return 0; } if (tab == curtab) { @@ -130,11 +129,10 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) Integer nvim_tabpage_get_number(Tabpage tabpage, Error *err) FUNC_API_SINCE(1) { - Integer rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); if (!tab) { - return rv; + return 0; } return tabpage_index(tab); diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 03fe5c5058..db348359eb 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -78,13 +78,13 @@ void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, void hl_group_set(String name, Integer id) FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL; void grid_resize(Integer grid, Integer width, Integer height) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL; void grid_clear(Integer grid) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; void grid_cursor_goto(Integer grid, Integer row, Integer col) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_line(Integer grid, Integer row, Integer col_start, Array data) - FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL; void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows, Integer cols) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 3103905819..626f7dc3eb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -19,8 +19,10 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" +#include "nvim/charset.h" #include "nvim/context.h" #include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" @@ -30,7 +32,10 @@ #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/highlight.h" +#include "nvim/highlight_defs.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" @@ -46,7 +51,6 @@ #include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/state.h" -#include "nvim/syntax.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -108,7 +112,7 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) Integer nvim_get_hl_id_by_name(String name) FUNC_API_SINCE(7) { - return syn_check_group(name.data, (int)name.size); + return syn_check_group(name.data, name.size); } Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) @@ -119,33 +123,36 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) abort(); } -/// Set a highlight group. -/// -/// @param ns_id number of namespace for this highlight -/// @param name highlight group name, like ErrorMsg -/// @param val highlight definition map, like |nvim_get_hl_by_name|. -/// in addition the following keys are also recognized: -/// `default`: don't override existing definition, -/// like `hi default` -/// `ctermfg`: sets foreground of cterm color -/// `ctermbg`: sets background of cterm color -/// `cterm` : cterm attribute map. sets attributed for -/// cterm colors. similer to `hi cterm` -/// Note: by default cterm attributes are -/// same as attributes of gui color +/// Sets a highlight group. +/// +/// Note: Unlike the `:highlight` command which can update a highlight group, +/// this function completely replaces the definition. For example: +/// ``nvim_set_hl(0, 'Visual', {})`` will clear the highlight group 'Visual'. +/// +/// @param ns_id Namespace id for this highlight |nvim_create_namespace()|. +/// Use 0 to set a highlight group globally |:highlight|. +/// @param name Highlight group name, e.g. "ErrorMsg" +/// @param val Highlight definition map, like |synIDattr()|. In +/// addition, the following keys are recognized: +/// - default: Don't override existing definition |:hi-default| +/// - ctermfg: Sets foreground of cterm color |highlight-ctermfg| +/// - ctermbg: Sets background of cterm color |highlight-ctermbg| +/// - cterm: cterm attribute map, like +/// |highlight-args|. +/// Note: Attributes default to those set for `gui` +/// if not set. /// @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) +// TODO(bfredl): val should take update vs reset flag +void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) FUNC_API_SINCE(7) { - int hl_id = syn_check_group(name.data, (int)name.size); + int hl_id = syn_check_group(name.data, 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); + ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val); } } @@ -169,7 +176,7 @@ void nvim__set_hl_ns(Integer ns_id, Error *err) // 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 (!provider_active && !ns_hl_changed) { + if (!provider_active && !ns_hl_changed && must_redraw < NOT_VALID) { multiqueue_put(main_loop.events, on_redraw_event, 0); } ns_hl_changed = true; @@ -187,21 +194,23 @@ static void on_redraw_event(void **argv) /// On execution error: does not fail, but updates v:errmsg. /// /// To input sequences like <C-o> use |nvim_replace_termcodes()| (typically -/// with escape_csi=true) to replace |keycodes|, then pass the result to +/// with escape_ks=false) to replace |keycodes|, then pass the result to /// nvim_feedkeys(). /// /// Example: /// <pre> /// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) -/// :call nvim_feedkeys(key, 'n', v:true) +/// :call nvim_feedkeys(key, 'n', v:false) /// </pre> /// /// @param keys to be typed /// @param mode behavior flags, see |feedkeys()| -/// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys` +/// @param escape_ks If true, escape K_SPECIAL bytes in `keys` +/// This should be false if you already used +/// |nvim_replace_termcodes()|, and true otherwise. /// @see feedkeys() -/// @see vim_strsave_escape_csi -void nvim_feedkeys(String keys, String mode, Boolean escape_csi) +/// @see vim_strsave_escape_ks +void nvim_feedkeys(String keys, String mode, Boolean escape_ks) FUNC_API_SINCE(1) { bool remap = true; @@ -210,7 +219,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) bool execute = false; bool dangerous = false; - for (size_t i = 0; i < mode.size; ++i) { + for (size_t i = 0; i < mode.size; i++) { switch (mode.data[i]) { case 'n': remap = false; break; @@ -232,10 +241,10 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) } char *keys_esc; - if (escape_csi) { - // Need to escape K_SPECIAL and CSI before putting the string in the + if (escape_ks) { + // Need to escape K_SPECIAL before putting the string in the // typeahead buffer. - keys_esc = (char *)vim_strsave_escape_csi((char_u *)keys.data); + keys_esc = (char *)vim_strsave_escape_ks((char_u *)keys.data); } else { keys_esc = keys.data; } @@ -245,7 +254,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) typebuf_was_filled = true; } - if (escape_csi) { + if (escape_ks) { xfree(keys_esc); } @@ -383,7 +392,7 @@ error: /// @param str String to be converted. /// @param from_part Legacy Vim parameter. Usually true. /// @param do_lt Also translate <lt>. Ignored if `special` is false. -/// @param special Replace |keycodes|, e.g. <CR> becomes a "\n" char. +/// @param special Replace |keycodes|, e.g. <CR> becomes a "\r" char. /// @see replace_termcodes /// @see cpoptions String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Boolean special) @@ -491,8 +500,12 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err) int flags = DIP_DIRFILE | (all ? DIP_ALL : 0); - do_in_runtimepath((char_u *)(name.size ? name.data : ""), - flags, find_runtime_cb, &rv); + TRY_WRAP({ + try_start(); + do_in_runtimepath((char_u *)(name.size ? name.data : ""), + flags, find_runtime_cb, &rv); + try_end(err); + }); return rv; } @@ -540,20 +553,19 @@ void nvim_set_current_dir(String dir, Error *err) return; } - char string[MAXPATHL]; + char_u string[MAXPATHL]; memcpy(string, dir.data, dir.size); string[dir.size] = NUL; try_start(); - if (vim_chdir((char_u *)string)) { + if (!changedir_func(string, kCdScopeGlobal)) { if (!try_end(err)) { api_set_error(err, kErrorTypeException, "Failed to change directory"); } return; } - post_chdir(kCdScopeGlobal, true); try_end(err); } @@ -596,7 +608,19 @@ void nvim_del_current_line(Error *err) Object nvim_get_var(String name, Error *err) FUNC_API_SINCE(1) { - return dict_get_value(&globvardict, name, err); + dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size); + if (di == NULL) { // try to autoload script + if (!script_autoload(name.data, name.size, false) || aborting()) { + api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data); + return (Object)OBJECT_INIT; + } + di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size); + } + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data); + return (Object)OBJECT_INIT; + } + return vim_to_object(&di->di_tv); } /// Sets a global (g:) variable. @@ -642,7 +666,7 @@ void nvim_set_vvar(String name, Object value, Error *err) dict_set_var(&vimvardict, name, value, false, false, err); } -/// Gets an option value string. +/// Gets the global value of an option. /// /// @param name Option name /// @param[out] err Error details, if any @@ -653,6 +677,125 @@ Object nvim_get_option(String name, Error *err) return get_option_from(NULL, SREQ_GLOBAL, name, err); } +/// Gets the value of an option. The behavior of this function matches that of +/// |:set|: the local value of an option is returned if it exists; otherwise, +/// the global value is returned. Local values always correspond to the current +/// buffer or window. To get a buffer-local or window-local option for a +/// specific buffer or window, use |nvim_buf_get_option()| or +/// |nvim_win_get_option()|. +/// +/// @param name Option name +/// @param opts Optional parameters +/// - scope: One of 'global' or 'local'. Analogous to +/// |:setglobal| and |:setlocal|, respectively. +/// @param[out] err Error details, if any +/// @return Option value +Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) + FUNC_API_SINCE(9) +{ + Object rv = OBJECT_INIT; + + int scope = 0; + if (opts->scope.type == kObjectTypeString) { + if (!strcmp(opts->scope.data.string.data, "local")) { + scope = OPT_LOCAL; + } else if (!strcmp(opts->scope.data.string.data, "global")) { + scope = OPT_GLOBAL; + } else { + api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'"); + goto end; + } + } else if (HAS_KEY(opts->scope)) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: scope"); + goto end; + } + + long numval = 0; + char *stringval = NULL; + switch (get_option_value(name.data, &numval, (char_u **)&stringval, scope)) { + case 0: + rv = STRING_OBJ(cstr_as_string(stringval)); + break; + case 1: + rv = INTEGER_OBJ(numval); + break; + case 2: + switch (numval) { + case 0: + case 1: + rv = BOOLEAN_OBJ(numval); + break; + default: + // Boolean options that return something other than 0 or 1 should return nil. Currently this + // only applies to 'autoread' which uses -1 as a local value to indicate "unset" + rv = NIL; + break; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data); + goto end; + } + +end: + return rv; +} + +/// Sets the value of an option. The behavior of this function matches that of +/// |:set|: for global-local options, both the global and local value are set +/// unless otherwise specified with {scope}. +/// +/// @param name Option name +/// @param value New option value +/// @param opts Optional parameters +/// - scope: One of 'global' or 'local'. Analogous to +/// |:setglobal| and |:setlocal|, respectively. +/// @param[out] err Error details, if any +void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err) + FUNC_API_SINCE(9) +{ + int scope = 0; + if (opts->scope.type == kObjectTypeString) { + if (!strcmp(opts->scope.data.string.data, "local")) { + scope = OPT_LOCAL; + } else if (!strcmp(opts->scope.data.string.data, "global")) { + scope = OPT_GLOBAL; + } else { + api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'"); + return; + } + } else if (HAS_KEY(opts->scope)) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: scope"); + return; + } + + long numval = 0; + char *stringval = NULL; + + switch (value.type) { + case kObjectTypeInteger: + numval = value.data.integer; + break; + case kObjectTypeBoolean: + numval = value.data.boolean ? 1 : 0; + break; + case kObjectTypeString: + stringval = value.data.string.data; + break; + case kObjectTypeNil: + scope |= OPT_CLEAR; + break; + default: + api_set_error(err, kErrorTypeValidation, "invalid value for option"); + return; + } + + char *e = set_option_value(name.data, numval, stringval, scope); + if (e) { + api_set_error(err, kErrorTypeException, "%s", e); + } +} + /// Gets the option information for all options. /// /// The dictionary has the full option names as keys and option metadata @@ -694,7 +837,7 @@ Dictionary nvim_get_option_info(String name, Error *err) return get_vimoption(name, err); } -/// Sets an option value. +/// Sets the global value of an option. /// /// @param channel_id /// @param name Option name @@ -733,7 +876,7 @@ void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) for (uint32_t i = 0; i < kv_size(hl_msg); i++) { HlMessageChunk chunk = kv_A(hl_msg, i); msg_multiline_attr((const char *)chunk.text.data, chunk.attr, - false, &need_clear); + true, &need_clear); } if (history) { msg_ext_set_kind("echomsg"); @@ -995,6 +1138,7 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err) TerminalOptions topts; Channel *chan = channel_alloc(kChannelStreamInternal); chan->stream.internal.cb = cb; + chan->stream.internal.closed = false; topts.data = chan; // NB: overridden in terminal_check_size if a window is already // displaying the buffer @@ -1044,7 +1188,7 @@ static void term_close(void *data) /// Send data to channel `id`. For a job, it writes it to the /// stdin of the process. For the stdio channel |channel-stdio|, /// it writes to Nvim's stdout. For an internal terminal instance -/// (|nvim_open_term()|) it writes directly to terimal output. +/// (|nvim_open_term()|) it writes directly to terminal output. /// See |channel-bytes| for more information. /// /// This function writes raw data, not RPC messages. If the channel @@ -1184,7 +1328,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) if (!cancel && !(State & CMDLINE)) { // Dot-repeat. for (size_t i = 0; i < lines.size; i++) { String s = lines.items[i].data.string; - assert(data.size <= INT_MAX); + assert(s.size <= INT_MAX); AppendToRedobuffLit((char_u *)s.data, (int)s.size); // readfile()-style: "\n" is indicated by presence of N+1 item. if (i + 1 < lines.size) { @@ -1405,10 +1549,11 @@ Dictionary nvim_get_mode(void) FUNC_API_SINCE(2) FUNC_API_FAST { Dictionary rv = ARRAY_DICT_INIT; - char *modestr = get_mode(); + char modestr[MODE_MAX_LENGTH]; + get_mode(modestr); bool blocked = input_blocking(); - PUT(rv, "mode", STRING_OBJ(cstr_as_string(modestr))); + PUT(rv, "mode", STRING_OBJ(cstr_to_string(modestr))); PUT(rv, "blocking", BOOLEAN_OBJ(blocked)); return rv; @@ -1419,10 +1564,10 @@ Dictionary nvim_get_mode(void) /// @param mode Mode short-name ("n", "i", "v", ...) /// @returns Array of maparg()-like dictionaries describing mappings. /// The "buffer" key is always zero. -ArrayOf(Dictionary) nvim_get_keymap(String mode) +ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) FUNC_API_SINCE(3) { - return keymap_array(mode, NULL); + return keymap_array(mode, NULL, channel_id == LUA_INTERNAL_CALL); } /// Sets a global |mapping| for the given mode. @@ -1442,18 +1587,23 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// nmap <nowait> <Space><NL> <Nop> /// </pre> /// +/// @param channel_id /// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …) /// or "!" for |:map!|, or empty string for |:map|. /// @param lhs Left-hand-side |{lhs}| of the mapping. /// @param rhs Right-hand-side |{rhs}| of the mapping. /// @param opts Optional parameters map. Accepts all |:map-arguments| -/// as keys excluding |<buffer>| but including |noremap|. +/// as keys excluding |<buffer>| but including |noremap| and "desc". +/// "desc" can be used to give a description to keymap. +/// When called from Lua, also accepts a "callback" key that takes +/// a Lua function to call when the mapping is executed. /// Values are Booleans. Unknown key is an error. /// @param[out] err Error details, if any. -void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err) +void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts, + Error *err) FUNC_API_SINCE(6) { - modify_keymap(-1, false, mode, lhs, rhs, opts, err); + modify_keymap(channel_id, -1, false, mode, lhs, rhs, opts, err); } /// Unmaps a global |mapping| for the given mode. @@ -1461,10 +1611,10 @@ void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Er /// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|. /// /// @see |nvim_set_keymap()| -void nvim_del_keymap(String mode, String lhs, Error *err) +void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err) FUNC_API_SINCE(6) { - nvim_buf_del_keymap(-1, mode, lhs, err); + nvim_buf_del_keymap(channel_id, -1, mode, lhs, err); } /// Gets a map of global (non-buffer-local) Ex commands. @@ -1622,7 +1772,7 @@ Array nvim_list_chans(void) /// 1. To perform several requests from an async context atomically, i.e. /// without interleaving redraws, RPC requests from other clients, or user /// interactions (however API methods may trigger autocommands or event -/// processing which have such side-effects, e.g. |:sleep| may wake timers). +/// processing which have such side effects, e.g. |:sleep| may wake timers). /// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests. /// /// @param channel_id @@ -1731,15 +1881,18 @@ static void write_msg(String message, bool to_err) } \ line_buf[pos++] = message.data[i]; - ++no_wait_return; + no_wait_return++; for (uint32_t i = 0; i < message.size; i++) { + if (got_int) { + break; + } if (to_err) { PUSH_CHAR(i, err_pos, err_line_buf, emsg); } else { PUSH_CHAR(i, out_pos, out_line_buf, msg); } } - --no_wait_return; + no_wait_return--; msg_end(); } @@ -1805,7 +1958,7 @@ Dictionary nvim__stats(void) Dictionary rv = ARRAY_DICT_INIT; PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); - PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount)); + PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count())); return rv; } @@ -1839,14 +1992,13 @@ Array nvim_get_proc_children(Integer pid, Error *err) size_t proc_count; int rv = os_proc_children((int)pid, &proc_list, &proc_count); - if (rv != 0) { + if (rv == 2) { // syscall failed (possibly because of kernel options), try shelling out. DLOG("fallback to vim._os_proc_children()"); Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); - String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); + String s = STATIC_CSTR_AS_STRING("return vim._os_proc_children(...)"); Object o = nlua_exec(s, a, err); - api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray) { rvobj = o.data.array; @@ -1979,7 +2131,7 @@ void nvim__screenshot(String path) } -/// Deletes a uppercase/file named mark. See |mark-motions|. +/// Deletes an uppercase/file named mark. See |mark-motions|. /// /// @note fails with error if a lowercase or buffer local named mark is used. /// @param name Mark name @@ -2089,7 +2241,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// - winid: (number) |window-ID| of the window to use as context for statusline. /// - maxwidth: (number) Maximum width of statusline. /// - fillchar: (string) Character to fill blank spaces in the statusline (see -/// 'fillchars'). +/// 'fillchars'). Treated as single-width even if it isn't. /// - highlights: (boolean) Return highlight information. /// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid} /// is ignored. @@ -2109,7 +2261,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * Dictionary result = ARRAY_DICT_INIT; int maxwidth; - char fillchar = 0; + int fillchar = 0; Window window = 0; bool use_tabline = false; bool highlights = false; @@ -2124,12 +2276,13 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * } if (HAS_KEY(opts->fillchar)) { - if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) { - api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character"); + if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0 + || ((size_t)utf_ptr2len((char_u *)opts->fillchar.data.string.data) + != opts->fillchar.data.string.size)) { + api_set_error(err, kErrorTypeValidation, "fillchar must be a single character"); return result; } - - fillchar = opts->fillchar.data.string.data[0]; + fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data); } if (HAS_KEY(opts->highlights)) { @@ -2156,11 +2309,16 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * fillchar = ' '; } else { wp = find_window_by_handle(window, err); + + if (wp == NULL) { + api_set_error(err, kErrorTypeException, "unknown winid %d", window); + return result; + } ewp = wp; if (fillchar == 0) { int attr; - fillchar = (char)fillchar_status(&attr, wp); + fillchar = fillchar_status(&attr, wp); } } @@ -2172,7 +2330,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * maxwidth = (int)opts->maxwidth.data.integer; } else { - maxwidth = use_tabline ? Columns : wp->w_width; + maxwidth = (use_tabline || global_stl_height() > 0) ? Columns : wp->w_width; } char buf[MAXPATHL]; @@ -2188,7 +2346,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * sizeof(buf), (char_u *)str.data, false, - (char_u)fillchar, + fillchar, maxwidth, hltab_ptr, NULL); @@ -2241,3 +2399,55 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * return result; } + +/// Create a new user command |user-commands| +/// +/// {name} is the name of the new command. The name must begin with an uppercase letter. +/// +/// {command} is the replacement text or Lua function to execute. +/// +/// Example: +/// <pre> +/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {}) +/// :SayHello +/// Hello world! +/// </pre> +/// +/// @param name Name of the new user command. Must begin with an uppercase letter. +/// @param command Replacement command to execute when this user command is executed. When called +/// from Lua, the command can also be a Lua function. The function is called with a +/// single table argument that contains the following keys: +/// - args: (string) The args passed to the command, if any |<args>| +/// - fargs: (table) The args split by unescaped whitespace (when more than one +/// argument is allowed), if any |<f-args>| +/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>| +/// - line1: (number) The starting line of the command range |<line1>| +/// - line2: (number) The final line of the command range |<line2>| +/// - range: (number) The number of items in the command range: 0, 1, or 2 |<range>| +/// - count: (number) Any count supplied |<count>| +/// - reg: (string) The optional register, if specified |<reg>| +/// - mods: (string) Command modifiers, if any |<mods>| +/// @param opts Optional command attributes. See |command-attributes| for more details. To use +/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to +/// "true". In addition to the string options listed in |:command-complete|, the +/// "complete" key also accepts a Lua function which works like the "customlist" +/// completion mode |:command-completion-customlist|. Additional parameters: +/// - desc: (string) Used for listing the command when a Lua function is used for +/// {command}. +/// - force: (boolean, default true) Override any previous definition. +/// @param[out] err Error details, if any. +void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err) + FUNC_API_SINCE(9) +{ + create_user_command(name, command, opts, 0, err); +} + +/// Delete a user-defined command. +/// +/// @param name Name of the command to delete. +/// @param[out] err Error details, if any. +void nvim_del_user_command(String name, Error *err) + FUNC_API_SINCE(9) +{ + nvim_buf_del_user_command(-1, name, err); +} diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 640144b234..4123674fe7 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -37,7 +37,7 @@ /// @param[out] err Error details (Vim error), if any /// @return Output (non-error, non-shell |:!|) if `output` is true, /// else empty string. -String nvim_exec(String src, Boolean output, Error *err) +String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) FUNC_API_SINCE(7) { const int save_msg_silent = msg_silent; @@ -52,11 +52,16 @@ String nvim_exec(String src, Boolean output, Error *err) if (output) { msg_silent++; } + + const sctx_T save_current_sctx = api_set_sctx(channel_id); + do_source_str(src.data, "nvim_exec()"); if (output) { capture_ga = save_capture_ga; msg_silent = save_msg_silent; } + + current_sctx = save_current_sctx; try_end(err); if (ERROR_SET(err)) { diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index ceb7f71423..5e37596884 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -10,6 +10,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/win_config.h" #include "nvim/ascii.h" +#include "nvim/highlight_group.h" #include "nvim/option.h" #include "nvim/screen.h" #include "nvim/strings.h" @@ -125,13 +126,13 @@ /// [ "â•”", "â•" ,"â•—", "â•‘", "â•", "â•", "╚", "â•‘" ]. /// If the number of chars are less than eight, they will be repeated. Thus /// an ASCII border could be specified as -/// [ "/", "-", "\\", "|" ], +/// [ "/", "-", \"\\\\\", "|" ], /// or all chars the same as /// [ "x" ]. /// An empty string can be used to turn off a specific border, for instance, /// [ "", "", "", ">", "", "", "", "<" ] /// will only make vertical borders but not horizontal ones. -/// By default, `FloatBorder` highlight is used, which links to `VertSplit` +/// By default, `FloatBorder` highlight is used, which links to `WinSeparator` /// when not defined. It could also be specified by character: /// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. /// - noautocmd: If true then no buffer-related autocommand events such as @@ -149,7 +150,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E if (!parse_float_config(config, &fconfig, false, true, err)) { return 0; } - win_T *wp = win_new_float(NULL, fconfig, err); + win_T *wp = win_new_float(NULL, false, fconfig, err); if (!wp) { return 0; } @@ -199,7 +200,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) return; } if (new_float) { - if (!win_new_float(win, fconfig, err)) { + if (!win_new_float(win, false, fconfig, err)) { return; } redraw_later(win, NOT_VALID); diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 6e68c057dc..fd33a82be3 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -39,7 +39,7 @@ Buffer nvim_win_get_buf(Window window, Error *err) return win->w_buffer->handle; } -/// Sets the current buffer in a window, without side-effects +/// Sets the current buffer in a window, without side effects /// /// @param window Window handle, or 0 for current window /// @param buffer Buffer handle @@ -71,6 +71,7 @@ ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) } /// Sets the (1,0)-indexed cursor position in the window. |api-indexing| +/// This scrolls the window even if it is not the current one. /// /// @param window Window handle, or 0 for current window /// @param pos (row, col) tuple representing the new position @@ -118,6 +119,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) update_topline_win(win); redraw_later(win, VALID); + win->w_redr_status = true; } /// Gets the window height @@ -395,7 +397,7 @@ void nvim_win_hide(Window window, Error *err) TryState tstate; try_enter(&tstate); if (tabpage == curtab) { - win_close(win, false); + win_close(win, false, false); } else { win_close_othertab(win, false, tabpage); } @@ -455,17 +457,12 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - win_T *save_curwin; - tabpage_T *save_curtab; - try_start(); Object res = OBJECT_INIT; - if (switch_win_noblock(&save_curwin, &save_curtab, win, tabpage, true) == - OK) { + WIN_EXECUTE(win, tabpage, { Array args = ARRAY_DICT_INIT; res = nlua_call_ref(fun, NULL, args, true, err); - } - restore_win_noblock(save_curwin, save_curtab, true); + }); try_end(err); return res; } diff --git a/src/nvim/assert.h b/src/nvim/assert.h index ad92d9a2af..65519a8004 100644 --- a/src/nvim/assert.h +++ b/src/nvim/assert.h @@ -1,3 +1,5 @@ +// uncrustify:off + #ifndef NVIM_ASSERT_H #define NVIM_ASSERT_H diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c deleted file mode 100644 index af519dcba9..0000000000 --- a/src/nvim/aucmd.c +++ /dev/null @@ -1,122 +0,0 @@ -// 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/aucmd.h" -#include "nvim/buffer.h" -#include "nvim/eval.h" -#include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" -#include "nvim/fileio.h" -#include "nvim/main.h" -#include "nvim/os/os.h" -#include "nvim/ui.h" -#include "nvim/vim.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "aucmd.c.generated.h" -#endif - -void do_autocmd_uienter(uint64_t chanid, bool attached) -{ - static bool recursive = false; - - if (recursive) { - return; // disallow recursion - } - recursive = true; - - dict_T *dict = get_vim_var_dict(VV_EVENT); - assert(chanid < VARNUMBER_MAX); - tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid); - tv_dict_set_keys_readonly(dict); - apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE, - NULL, NULL, false, curbuf); - tv_dict_clear(dict); - - recursive = false; -} - -void init_default_autocmds(void) -{ - // open terminals when opening files that start with term:// -#define PROTO "term://" - do_cmdline_cmd("augroup nvim_terminal"); - do_cmdline_cmd("autocmd BufReadCmd " PROTO "* ++nested " - "if !exists('b:term_title')|call termopen(" - // Capture the command string - "matchstr(expand(\"<amatch>\"), " - "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), " - // capture the working directory - "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), " - "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})" - "|endif"); - do_cmdline_cmd("augroup END"); -#undef PROTO - - // limit syntax synchronization in the command window - do_cmdline_cmd("augroup nvim_cmdwin"); - do_cmdline_cmd("autocmd! CmdwinEnter [:>] syntax sync minlines=1 maxlines=1"); - do_cmdline_cmd("augroup END"); -} - -static void focusgained_event(void **argv) -{ - bool *gainedp = argv[0]; - do_autocmd_focusgained(*gainedp); - xfree(gainedp); -} -void aucmd_schedule_focusgained(bool gained) -{ - bool *gainedp = xmalloc(sizeof(*gainedp)); - *gainedp = gained; - loop_schedule_deferred(&main_loop, - event_create(focusgained_event, 1, gainedp)); -} - -static void do_autocmd_focusgained(bool gained) -{ - static bool recursive = false; - static Timestamp last_time = (time_t)0; - bool need_redraw = false; - - if (recursive) { - return; // disallow recursion - } - recursive = true; - need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), - NULL, NULL, false, curbuf); - - // When activated: Check if any file was modified outside of Vim. - // Only do this when not done within the last two seconds as: - // 1. Some filesystems have modification time granularity in seconds. Fat32 - // has a granularity of 2 seconds. - // 2. We could get multiple notifications in a row. - if (gained && last_time + (Timestamp)2000 < os_now()) { - need_redraw = check_timestamps(true); - last_time = os_now(); - } - - if (need_redraw) { - // Something was executed, make sure the cursor is put back where it - // belongs. - need_wait_return = false; - - if (State & CMDLINE) { - redrawcmdline(); - } else if ((State & NORMAL) || (State & INSERT)) { - if (must_redraw != 0) { - update_screen(0); - } - - setcursor(); - } - - ui_flush(); - } - - if (need_maketitle) { - maketitle(); - } - - recursive = false; -} diff --git a/src/nvim/aucmd.h b/src/nvim/aucmd.h deleted file mode 100644 index 9a4dd79a78..0000000000 --- a/src/nvim/aucmd.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef NVIM_AUCMD_H -#define NVIM_AUCMD_H - -#include <stdint.h> - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "aucmd.h.generated.h" -#endif - -#endif // NVIM_AUCMD_H - diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 6be51c504c..9e3eb06752 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -37,8 +37,10 @@ return { 'CursorHoldI', -- idem, in Insert mode 'CursorMoved', -- cursor was moved 'CursorMovedI', -- cursor was moved in Insert mode + 'DiagnosticChanged', -- diagnostics in a buffer were modified 'DiffUpdated', -- diffs have been updated 'DirChanged', -- directory changed + 'DirChangedPre', -- directory is going to change 'EncodingChanged', -- after changing the 'encoding' option 'ExitPre', -- before exiting 'FileAppendCmd', -- append to a file using command @@ -69,11 +71,15 @@ return { 'InsertLeave', -- just after leaving Insert mode 'InsertLeavePre', -- just before leaving Insert mode 'MenuPopup', -- just before popup menu is displayed + 'ModeChanged', -- after changing the mode 'OptionSet', -- after setting any option 'QuickFixCmdPost', -- after :make, :grep etc. 'QuickFixCmdPre', -- before :make, :grep etc. 'QuitPre', -- before :quit + 'RecordingEnter', -- when starting to record a macro + 'RecordingLeave', -- just before a macro stops recording 'RemoteReply', -- upon string reception from a remote vim + 'SearchWrapped', -- after the search wrapped around 'SessionLoadPost', -- after loading a session file 'ShellCmdPost', -- after ":!cmd" 'ShellFilterPost', -- after ":1,2!cmd", ":w !cmd", ":r !cmd". @@ -126,16 +132,14 @@ return { -- syntax file nvim_specific = { BufModifiedSet=true, - DirChanged=true, + DiagnosticChanged=true, + RecordingEnter=true, + RecordingLeave=true, Signal=true, - TabClosed=true, - TabNew=true, TabNewEntered=true, TermClose=true, TermOpen=true, UIEnter=true, UILeave=true, - WinClosed=true, - WinScrolled=true, }, } diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 2d0c0f3fd5..1b146f82c6 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2,7 +2,9 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // autocmd.c: Autocommand related functions +#include <signal.h> +#include "lauxlib.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" @@ -13,10 +15,13 @@ #include "nvim/eval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" -#include "nvim/misc1.h" +#include "nvim/lua/executor.h" +#include "nvim/map.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/regexp.h" #include "nvim/search.h" #include "nvim/state.h" @@ -28,6 +33,14 @@ # include "autocmd.c.generated.h" #endif +// Naming Conventions: +// - general autocmd behavior start with au_ +// - AutoCmd start with aucmd_ +// - Autocmd.exec stat with aucmd_exec +// - AutoPat start with aupat_ +// - Groups start with augroup_ +// - Events start with event_ + // // The autocommands are stored in a list for each event. // Autocommands for the same pattern, that are consecutive, are joined @@ -67,21 +80,20 @@ // Code for automatic commands. static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands -/// List of autocmd group names -static garray_T augroups = { 0, 0, sizeof(char_u *), 10, NULL }; -#define AUGROUP_NAME(i) (((char **)augroups.ga_data)[i]) -#define BUFLOCAL_PAT_LEN 25 +// ID for associating autocmds created via nvim_create_autocmd +// Used to delete autocmds from nvim_del_autocmd +static int next_augroup_id = 1; // use get_deleted_augroup() to get this static const char *deleted_augroup = NULL; -// The ID of the current group. Group 0 is the default one. +// The ID of the current group. static int current_augroup = AUGROUP_DEFAULT; -static int au_need_clean = false; // need to delete marked patterns +// Whether we need to delete marked patterns. +// While deleting autocmds, they aren't actually remover, just marked. +static int au_need_clean = false; -static event_T last_event; -static int last_group; static int autocmd_blocked = 0; // block all autocmds static bool autocmd_nested = false; @@ -89,6 +101,31 @@ static bool autocmd_include_groups = false; static char_u *old_termresponse = NULL; +/// Iterates over all the AutoPats for a particular event +#define FOR_ALL_AUPATS_IN_EVENT(event, ap) \ + for (AutoPat *ap = first_autopat[event]; ap != NULL; ap = ap->next) // NOLINT + +// Map of autocmd group names and ids. +// name -> ID +// ID -> name +static Map(String, int) map_augroup_name_to_id = MAP_INIT; +static Map(int, String) map_augroup_id_to_name = MAP_INIT; + +static void augroup_map_del(int id, char *name) +{ + if (name != NULL) { + String key = map_key(String, int)(&map_augroup_name_to_id, cstr_as_string(name)); + map_del(String, int)(&map_augroup_name_to_id, key); + api_free_string(key); + } + if (id > 0) { + String mapped = map_get(int, String)(&map_augroup_id_to_name, id); + api_free_string(mapped); + map_del(int, String)(&map_augroup_id_to_name, id); + } +} + + static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE { if (deleted_augroup == NULL) { @@ -98,48 +135,53 @@ static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE } // Show the autocommands for one AutoPat. -static void show_autocmd(AutoPat *ap, event_T event) +static void aupat_show(AutoPat *ap, event_T event, int previous_group) { - AutoCmd *ac; - // Check for "got_int" (here and at various places below), which is set // when "q" has been hit for the "--more--" prompt if (got_int) { return; } + // pattern has been removed if (ap->pat == NULL) { return; } + char *name = augroup_name(ap->group); + msg_putchar('\n'); if (got_int) { return; } - if (event != last_event || ap->group != last_group) { + // When switching groups, we need to show the new group information. + if (ap->group != previous_group) { + // show the group name, if it's not the default group if (ap->group != AUGROUP_DEFAULT) { - if (AUGROUP_NAME(ap->group) == NULL) { + if (name == NULL) { msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E)); } else { - msg_puts_attr(AUGROUP_NAME(ap->group), HL_ATTR(HLF_T)); + msg_puts_attr(name, HL_ATTR(HLF_T)); } msg_puts(" "); } + // show the event name msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T)); - last_event = event; - last_group = ap->group; msg_putchar('\n'); if (got_int) { return; } } + msg_col = 4; msg_outtrans(ap->pat); - for (ac = ap->cmds; ac != NULL; ac = ac->next) { - if (ac->cmd == NULL) { // skip removed commands + for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { + // skip removed commands + if (aucmd_exec_is_deleted(ac->exec)) { continue; } + if (msg_col >= 14) { msg_putchar('\n'); } @@ -147,7 +189,7 @@ static void show_autocmd(AutoPat *ap, event_T event) if (got_int) { return; } - msg_outtrans(ac->cmd); + msg_outtrans((char_u *)aucmd_exec_to_string(ac, ac->exec)); if (p_verbose > 0) { last_set_msg(ac->script_ctx); } @@ -163,27 +205,111 @@ static void show_autocmd(AutoPat *ap, event_T event) } } +static void au_show_for_all_events(int group, char_u *pat) +{ + FOR_ALL_AUEVENTS(event) { + au_show_for_event(group, event, pat); + } +} + +static void au_show_for_event(int group, event_T event, char_u *pat) +{ + // Return early if there are no autocmds for this event + if (au_event_is_empty(event)) { + return; + } + + // always need to show group information before the first pattern for the event + int previous_group = AUGROUP_ERROR; + + if (*pat == NUL) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (group == AUGROUP_ALL || ap->group == group) { + aupat_show(ap, event, previous_group); + previous_group = ap->group; + } + } + return; + } + + char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" + // Loop through all the specified patterns. + int patlen = (int)aucmd_pattern_length(pat); + while (patlen) { + // detect special <buffer[=X]> buffer-local patterns + if (aupat_is_buflocal(pat, patlen)) { + // normalize pat into standard "<buffer>#N" form + aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen)); + pat = buflocal_pat; + patlen = (int)STRLEN(buflocal_pat); + } + + assert(*pat != NUL); + + // Find AutoPat entries with this pattern. + // always goes at or after the last one, so start at the end. + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->pat != NULL) { + // Accept a pattern when: + // - a group was specified and it's that group + // - the length of the pattern matches + // - the pattern matches. + // For <buffer[=X]>, this condition works because we normalize + // all buffer-local patterns. + if ((group == AUGROUP_ALL || ap->group == group) + && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) { + // Show autocmd's for this autopat, or buflocals <buffer=X> + aupat_show(ap, event, previous_group); + previous_group = ap->group; + } + } + } + + pat = aucmd_next_pattern(pat, (size_t)patlen); + patlen = (int)aucmd_pattern_length(pat); + } +} + // Mark an autocommand handler for deletion. -static void au_remove_pat(AutoPat *ap) +static void aupat_del(AutoPat *ap) { XFREE_CLEAR(ap->pat); ap->buflocal_nr = -1; au_need_clean = true; } +void aupat_del_for_event_and_group(event_T event, int group) +{ + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->group == group) { + aupat_del(ap); + } + } + + au_cleanup(); +} + // Mark all commands for a pattern for deletion. -static void au_remove_cmds(AutoPat *ap) +static void aupat_remove_cmds(AutoPat *ap) { for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { - XFREE_CLEAR(ac->cmd); + aucmd_exec_free(&ac->exec); + + if (ac->desc != NULL) { + XFREE_CLEAR(ac->desc); + } } au_need_clean = true; } // Delete one command from an autocmd pattern. -static void au_del_cmd(AutoCmd *ac) +static void aucmd_del(AutoCmd *ac) { - XFREE_CLEAR(ac->cmd); + aucmd_exec_free(&ac->exec); + if (ac->desc != NULL) { + XFREE_CLEAR(ac->desc); + } au_need_clean = true; } @@ -191,19 +317,15 @@ static void au_del_cmd(AutoCmd *ac) /// This is only done when not executing autocommands. static void au_cleanup(void) { - AutoPat *ap, **prev_ap; - event_T event; - if (autocmd_busy || !au_need_clean) { return; } // Loop over all events. - for (event = (event_T)0; (int)event < NUM_EVENTS; - event = (event_T)((int)event + 1)) { + FOR_ALL_AUEVENTS(event) { // Loop over all autocommand patterns. - prev_ap = &(first_autopat[(int)event]); - for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { + AutoPat **prev_ap = &(first_autopat[(int)event]); + for (AutoPat *ap = *prev_ap; ap != NULL; ap = *prev_ap) { bool has_cmd = false; // Loop over all commands for this pattern. @@ -211,9 +333,13 @@ static void au_cleanup(void) 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) { + if (ap->pat == NULL || aucmd_exec_is_deleted(ac->exec)) { *prev_ac = ac->next; - xfree(ac->cmd); + aucmd_exec_free(&ac->exec); + if (ac->desc != NULL) { + XFREE_CLEAR(ac->desc); + } + xfree(ac); } else { has_cmd = true; @@ -224,7 +350,7 @@ static void au_cleanup(void) if (ap->pat != NULL && !has_cmd) { // Pattern was not marked for deletion, but all of its commands were. // So mark the pattern for deletion. - au_remove_pat(ap); + aupat_del(ap); } // Remove the pattern if it has been marked for deletion. @@ -250,28 +376,30 @@ static void au_cleanup(void) au_need_clean = false; } + +// Get the first AutoPat for a particular event. +AutoPat *au_get_autopat_for_event(event_T event) +{ + return first_autopat[(int)event]; +} + // Called when buffer is freed, to remove/invalidate related buffer-local // autocmds. void aubuflocal_remove(buf_T *buf) { - AutoPat *ap; - event_T event; - AutoPatCmd *apc; - // invalidate currently executing autocommands - for (apc = active_apc_list; apc; apc = apc->next) { + for (AutoPatCmd *apc = active_apc_list; apc; apc = apc->next) { if (buf->b_fnum == apc->arg_bufnr) { apc->arg_bufnr = 0; } } // invalidate buflocals looping through events - for (event = (event_T)0; (int)event < NUM_EVENTS; - event = (event_T)((int)event + 1)) { - // loop over all autocommand patterns - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { if (ap->buflocal_nr == buf->b_fnum) { - au_remove_pat(ap); + aupat_del(ap); + if (p_verbose >= 6) { verbose_enter(); smsg(_("auto-removing autocommand: %s <buffer=%d>"), @@ -284,60 +412,74 @@ void aubuflocal_remove(buf_T *buf) au_cleanup(); } -// Add an autocmd group name. -// Return its ID. Returns AUGROUP_ERROR (< 0) for error. -static int au_new_group(char_u *name) +// Add an autocmd group name or return existing group matching name. +// Return its ID. +int augroup_add(char *name) { - int i = au_find_group(name); - if (i == AUGROUP_ERROR) { // the group doesn't exist yet, add it. - // First try using a free entry. - for (i = 0; i < augroups.ga_len; i++) { - if (AUGROUP_NAME(i) == NULL) { - break; - } - } - if (i == augroups.ga_len) { - ga_grow(&augroups, 1); - } + assert(STRICMP(name, "end") != 0); - AUGROUP_NAME(i) = xstrdup((char *)name); - if (i == augroups.ga_len) { - augroups.ga_len++; - } + int existing_id = augroup_find(name); + if (existing_id > 0) { + assert(existing_id != AUGROUP_DELETED); + return existing_id; + } + + if (existing_id == AUGROUP_DELETED) { + augroup_map_del(existing_id, name); } - return i; + int next_id = next_augroup_id++; + String name_key = cstr_to_string(name); + String name_val = cstr_to_string(name); + map_put(String, int)(&map_augroup_name_to_id, name_key, next_id); + map_put(int, String)(&map_augroup_id_to_name, next_id, name_val); + + return next_id; } -static void au_del_group(char_u *name) +/// Delete the augroup that matches name. +/// @param stupid_legacy_mode bool: This parameter determines whether to run the augroup +/// deletion in the same fashion as `:augroup! {name}` where if there are any remaining +/// autocmds left in the augroup, it will change the name of the augroup to `--- DELETED ---` +/// but leave the autocmds existing. These are _separate_ augroups, so if you do this for +/// multiple augroups, you will have a bunch of `--- DELETED ---` augroups at the same time. +/// There is no way, as far as I could tell, how to actually delete them at this point as a user +/// +/// I did not consider this good behavior, so now when NOT in stupid_legacy_mode, we actually +/// delete these groups and their commands, like you would expect (and don't leave hanging +/// `--- DELETED ---` groups around) +void augroup_del(char *name, bool stupid_legacy_mode) { - int i = au_find_group(name); + int i = augroup_find(name); if (i == AUGROUP_ERROR) { // the group doesn't exist semsg(_("E367: No such group: \"%s\""), name); } else if (i == current_augroup) { emsg(_("E936: Cannot delete the current group")); } else { - event_T event; - AutoPat *ap; - int in_use = false; - - for (event = (event_T)0; (int)event < NUM_EVENTS; - event = (event_T)((int)event + 1)) { - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { - if (ap->group == i && ap->pat != NULL) { - give_warning((char_u *)_("W19: Deleting augroup that is still in use"), true); - in_use = true; - event = NUM_EVENTS; - break; + if (stupid_legacy_mode) { + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->group == i && ap->pat != NULL) { + give_warning((char_u *)_("W19: Deleting augroup that is still in use"), true); + map_put(String, int)(&map_augroup_name_to_id, cstr_as_string(name), AUGROUP_DELETED); + augroup_map_del(ap->group, NULL); + return; + } } } - } - xfree(AUGROUP_NAME(i)); - if (in_use) { - AUGROUP_NAME(i) = (char *)get_deleted_augroup(); } else { - AUGROUP_NAME(i) = NULL; + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->group == i) { + aupat_del(ap); + } + } + } } + + // Remove the group because it's not currently in use. + augroup_map_del(i, name); + au_cleanup(); } } @@ -346,25 +488,67 @@ static void au_del_group(char_u *name) /// @param name augroup name /// /// @return the ID or AUGROUP_ERROR (< 0) for error. -static int au_find_group(const char_u *name) +int augroup_find(const char *name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (int i = 0; i < augroups.ga_len; i++) { - if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup() - && STRCMP(AUGROUP_NAME(i), name) == 0) { - return i; - } + int existing_id = map_get(String, int)(&map_augroup_name_to_id, cstr_as_string((char *)name)); + if (existing_id == AUGROUP_DELETED) { + return existing_id; } + + if (existing_id > 0) { + return existing_id; + } + return AUGROUP_ERROR; } +/// Gets the name for a particular group. +char *augroup_name(int group) +{ + assert(group != 0); + + if (group == AUGROUP_DELETED) { + return (char *)get_deleted_augroup(); + } + + if (group == AUGROUP_ALL) { + group = current_augroup; + } + + // next_augroup_id is the "source of truth" about what autocmds have existed + // + // The map_size is not the source of truth because groups can be removed from + // the map. When this happens, the map size is reduced. That's why this function + // relies on next_augroup_id instead. + + // "END" is always considered the last augroup ID. + // Used for expand_get_event_name and expand_get_augroup_name + if (group == next_augroup_id) { + return "END"; + } + + // If it's larger than the largest group, then it doesn't have a name + if (group > next_augroup_id) { + return NULL; + } + + String key = map_get(int, String)(&map_augroup_id_to_name, group); + if (key.data != NULL) { + return key.data; + } + + // If it's not in the map anymore, then it must have been deleted. + return (char *)get_deleted_augroup(); +} + /// Return true if augroup "name" exists. /// /// @param name augroup name -bool au_has_group(const char_u *name) +bool augroup_exists(const char *name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return au_find_group(name) != AUGROUP_ERROR; + return augroup_find(name) > 0; } /// ":augroup {name}". @@ -374,23 +558,27 @@ void do_augroup(char_u *arg, int del_group) if (*arg == NUL) { emsg(_(e_argreq)); } else { - au_del_group(arg); + augroup_del((char *)arg, true); } } else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0 current_augroup = AUGROUP_DEFAULT; } else if (*arg) { // ":aug xxx": switch to group xxx - int i = au_new_group(arg); - if (i != AUGROUP_ERROR) { - current_augroup = i; - } + current_augroup = augroup_add((char *)arg); } else { // ":aug": list the group names msg_start(); - for (int i = 0; i < augroups.ga_len; i++) { - if (AUGROUP_NAME(i) != NULL) { - msg_puts(AUGROUP_NAME(i)); - msg_puts(" "); + + String name; + int value; + map_foreach(&map_augroup_name_to_id, name, value, { + if (value > 0) { + msg_puts(name.data); + } else { + msg_puts(augroup_name(value)); } - } + + msg_puts(" "); + }); + msg_clr_eos(); msg_end(); } @@ -399,35 +587,45 @@ void do_augroup(char_u *arg, int del_group) #if defined(EXITFREE) void free_all_autocmds(void) { - for (current_augroup = -1; current_augroup < augroups.ga_len; - current_augroup++) { - do_autocmd((char_u *)"", true); - } - - for (int i = 0; i < augroups.ga_len; i++) { - char *const s = ((char **)(augroups.ga_data))[i]; - if ((const char *)s != get_deleted_augroup()) { - xfree(s); + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + aupat_del(ap); } } - ga_clear(&augroups); + + au_need_clean = true; + au_cleanup(); + + // Delete the augroup_map, including free the data + String name; + int id; + map_foreach(&map_augroup_name_to_id, name, id, { + (void)id; + api_free_string(name); + }) + map_destroy(String, int)(&map_augroup_name_to_id); + + map_foreach(&map_augroup_id_to_name, id, name, { + (void)id; + api_free_string(name); + }) + map_destroy(int, String)(&map_augroup_id_to_name); } #endif // Return the event number for event name "start". // Return NUM_EVENTS if the event name was not found. // Return a pointer to the next event name in "end". -static event_T event_name2nr(const char_u *start, char_u **end) +event_T event_name2nr(const char_u *start, char_u **end) { const char_u *p; int i; - int len; // the event name ends with end of line, '|', a blank or a comma for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) { } for (i = 0; event_names[i].name != NULL; i++) { - len = (int)event_names[i].len; + int len = (int)event_names[i].len; if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) { break; } @@ -447,12 +645,10 @@ static event_T event_name2nr(const char_u *start, char_u **end) /// @param[in] event Event to return name for. /// /// @return Event name, static string. Returns "Unknown" for unknown events. -static const char *event_nr2name(event_T event) +const char *event_nr2name(event_T event) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST { - int i; - - for (i = 0; event_names[i].name != NULL; i++) { + for (int i = 0; event_names[i].name != NULL; i++) { if (event_names[i].event == event) { return event_names[i].name; } @@ -460,33 +656,6 @@ static const char *event_nr2name(event_T event) return "Unknown"; } -/// Scan over the events. "*" stands for all events. -/// true when group name was found -static char_u *find_end_event(char_u *arg, int have_group) -{ - char_u *pat; - char_u *p; - - if (*arg == '*') { - if (arg[1] && !ascii_iswhite(arg[1])) { - semsg(_("E215: Illegal character after *: %s"), arg); - return NULL; - } - pat = arg + 1; - } else { - for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) { - if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) { - if (have_group) { - semsg(_("E216: No such event: %s"), pat); - } else { - semsg(_("E216: No such group or event: %s"), pat); - } - return NULL; - } - } - } - return pat; -} /// Return true if "event" is included in 'eventignore'. /// @@ -532,11 +701,8 @@ int check_ei(void) // Returns the old value of 'eventignore' in allocated memory. char_u *au_event_disable(char *what) { - char_u *new_ei; - char_u *save_ei; - - save_ei = vim_strsave(p_ei); - new_ei = vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); + char_u *save_ei = vim_strsave(p_ei); + char_u *new_ei = vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); if (*what == ',' && *p_ei == NUL) { STRCPY(new_ei, what + 1); } else { @@ -591,11 +757,10 @@ void au_event_restore(char_u *old_ei) void do_autocmd(char_u *arg_in, int forceit) { char_u *arg = arg_in; - char_u *pat; char_u *envpat = NULL; char_u *cmd; int need_free = false; - int nested = false; + bool nested = false; bool once = false; int group; @@ -604,12 +769,12 @@ void do_autocmd(char_u *arg_in, int forceit) group = AUGROUP_ALL; // no argument, use all groups } else { // Check for a legal group name. If not, use AUGROUP_ALL. - group = au_get_grouparg(&arg); + group = arg_augroup_get(&arg); } // Scan over the events. // If we find an illegal name, return here, don't do anything. - pat = find_end_event(arg, group != AUGROUP_ALL); + char_u *pat = arg_event_skip(arg, group != AUGROUP_ALL); if (pat == NULL) { return; } @@ -646,37 +811,22 @@ void do_autocmd(char_u *arg_in, int forceit) } cmd = skipwhite(cmd); + + bool invalid_flags = false; for (size_t i = 0; i < 2; i++) { if (*cmd != NUL) { - // Check for "++once" flag. - if (STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) { - if (once) { - semsg(_(e_duparg2), "++once"); - } - once = true; - cmd = skipwhite(cmd + 6); - } + invalid_flags |= arg_autocmd_flag_get(&once, &cmd, "++once", 6); + invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "++nested", 8); - // Check for "++nested" flag. - if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) { - if (nested) { - semsg(_(e_duparg2), "++nested"); - } - nested = true; - cmd = skipwhite(cmd + 8); - } - - // Check for the old (deprecated) "nested" flag. - if (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) { - if (nested) { - semsg(_(e_duparg2), "nested"); - } - nested = true; - cmd = skipwhite(cmd + 6); - } + // Check the deprecated "nested" flag. + invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "nested", 6); } } + if (invalid_flags) { + return; + } + // Find the start of the commands. // Expand <sfile> in it. if (*cmd != NUL) { @@ -688,36 +838,37 @@ void do_autocmd(char_u *arg_in, int forceit) } } + bool is_showing = !forceit && *cmd == NUL; + // Print header when showing autocommands. - if (!forceit && *cmd == NUL) { + if (is_showing) { // Highlight title msg_puts_title(_("\n--- Autocommands ---")); - } - // Loop over the events. - last_event = (event_T)-1; // for listing the event name - last_group = AUGROUP_ERROR; // for listing the group name - if (*arg == '*' || *arg == NUL || *arg == '|') { - if (!forceit && *cmd != NUL) { - emsg(_(e_cannot_define_autocommands_for_all_events)); + if (*arg == '*' || *arg == '|' || *arg == NUL) { + au_show_for_all_events(group, pat); } else { - for (event_T event = (event_T)0; event < NUM_EVENTS; - event = (event_T)(event + 1)) { + event_T event = event_name2nr(arg, &arg); + assert(event < NUM_EVENTS); + au_show_for_event(group, event, pat); + } + } else { + if (*arg == '*' || *arg == NUL || *arg == '|') { + if (!forceit && *cmd != NUL) { + emsg(_(e_cannot_define_autocommands_for_all_events)); + } else { + do_all_autocmd_events(pat, once, nested, cmd, forceit, group); + } + } else { + while (*arg && *arg != '|' && !ascii_iswhite(*arg)) { + event_T event = event_name2nr(arg, &arg); + assert(event < NUM_EVENTS); if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) == FAIL) { break; } } } - } else { - while (*arg && *arg != '|' && !ascii_iswhite(*arg)) { - event_T event = event_name2nr(arg, &arg); - assert(event < NUM_EVENTS); - if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) - == FAIL) { - break; - } - } } if (need_free) { @@ -726,30 +877,14 @@ void do_autocmd(char_u *arg_in, int forceit) xfree(envpat); } -// Find the group ID in a ":autocmd" or ":doautocmd" argument. -// The "argp" argument is advanced to the following argument. -// -// Returns the group ID or AUGROUP_ALL. -static int au_get_grouparg(char_u **argp) +void do_all_autocmd_events(char_u *pat, bool once, int nested, char_u *cmd, bool delete, int group) { - char_u *group_name; - char_u *p; - char_u *arg = *argp; - int group = AUGROUP_ALL; - - for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) { - } - if (p > arg) { - group_name = vim_strnsave(arg, (size_t)(p - arg)); - group = au_find_group(group_name); - if (group == AUGROUP_ERROR) { - group = AUGROUP_ALL; // no match, use all groups - } else { - *argp = skipwhite(p); // match, skip over group name + FOR_ALL_AUEVENTS(event) { + if (do_autocmd_event(event, pat, once, nested, cmd, delete, group) + == FAIL) { + return; } - xfree(group_name); } - return group; } // do_autocmd() for one event. @@ -759,219 +894,310 @@ static int au_get_grouparg(char_u **argp) // If *cmd == NUL: show entries. // If forceit == true: delete entries. // If group is not AUGROUP_ALL: only use this group. -static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, char_u *cmd, - int forceit, int group) +int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, char_u *cmd, bool delete, + int group) + FUNC_ATTR_NONNULL_ALL { + // Cannot be used to show all patterns. See au_show_for_event or au_show_for_all_events + assert(*pat != NUL || delete); + AutoPat *ap; AutoPat **prev_ap; - AutoCmd *ac; - AutoCmd **prev_ac; - int brace_level; - char_u *endpat; int findgroup; - int allgroups; - int patlen; - int is_buflocal; int buflocal_nr; char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" + bool is_adding_cmd = *cmd != NUL; + if (group == AUGROUP_ALL) { findgroup = current_augroup; } else { findgroup = group; } - allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL); - // Show or delete all patterns for an event. - if (*pat == NUL) { - for (ap = first_autopat[event]; ap != NULL; ap = ap->next) { - if (forceit) { // delete the AutoPat, if it's in the current group - if (ap->group == findgroup) { - au_remove_pat(ap); - } - } else if (group == AUGROUP_ALL || ap->group == group) { - show_autocmd(ap, event); - } - } + // Delete all aupat for an event. + if (*pat == NUL && delete) { + aupat_del_for_event_and_group(event, findgroup); + return OK; } // Loop through all the specified patterns. - for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) { - // Find end of the pattern. - // Watch out for a comma in braces, like "*.\{obj,o\}". - endpat = pat; - // ignore single comma - if (*endpat == ',') { - continue; - } - brace_level = 0; - for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); - endpat++) { - if (*endpat == '{') { - brace_level++; - } else if (*endpat == '}') { - brace_level--; - } - } - patlen = (int)(endpat - pat); - - // detect special <buflocal[=X]> buffer-local patterns - is_buflocal = false; - buflocal_nr = 0; - - if (patlen >= 8 && STRNCMP(pat, "<buffer", 7) == 0 - && pat[patlen - 1] == '>') { - // "<buffer...>": Error will be printed only for addition. - // printing and removing will proceed silently. - is_buflocal = true; - if (patlen == 8) { - // "<buffer>" - buflocal_nr = curbuf->b_fnum; - } else if (patlen > 9 && pat[7] == '=') { - if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) { - // "<buffer=abuf>" - buflocal_nr = autocmd_bufnr; - } else if (skipdigits(pat + 8) == pat + patlen - 1) { - // "<buffer=123>" - buflocal_nr = atoi((char *)pat + 8); - } - } - } + int patlen = (int)aucmd_pattern_length(pat); + while (patlen) { + // detect special <buffer[=X]> buffer-local patterns + int is_buflocal = aupat_is_buflocal(pat, patlen); if (is_buflocal) { + buflocal_nr = aupat_get_buflocal_nr(pat, patlen); + // normalize pat into standard "<buffer>#N" form - snprintf((char *)buflocal_pat, - BUFLOCAL_PAT_LEN, - "<buffer=%d>", - buflocal_nr); + aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr); - pat = buflocal_pat; // can modify pat and patlen - patlen = (int)STRLEN(buflocal_pat); // but not endpat + pat = buflocal_pat; + patlen = (int)STRLEN(buflocal_pat); } - // Find AutoPat entries with this pattern. When adding a command it - // always goes at or after the last one, so start at the end. - if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) { - prev_ap = &last_autopat[(int)event]; - } else { + if (delete) { + assert(*pat != NUL); + + // Find AutoPat entries with this pattern. prev_ap = &first_autopat[(int)event]; - } - while ((ap = *prev_ap) != NULL) { - if (ap->pat != NULL) { - // Accept a pattern when: - // - a group was specified and it's that group, or a group was - // not specified and it's the current group, or a group was - // not specified and we are listing - // - the length of the pattern matches - // - the pattern matches. - // For <buffer[=X]>, this condition works because we normalize - // all buffer-local patterns. - if ((allgroups || ap->group == findgroup) && ap->patlen == patlen - && STRNCMP(pat, ap->pat, patlen) == 0) { - // Remove existing autocommands. - // If adding any new autocmd's for this AutoPat, don't - // delete the pattern from the autopat list, append to - // this list. - if (forceit) { - if (*cmd != NUL && ap->next == NULL) { - au_remove_cmds(ap); + while ((ap = *prev_ap) != NULL) { + if (ap->pat != NULL) { + // Accept a pattern when: + // - a group was specified and it's that group + // - the length of the pattern matches + // - the pattern matches. + // For <buffer[=X]>, this condition works because we normalize + // all buffer-local patterns. + if (ap->group == findgroup + && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) { + // Remove existing autocommands. + // If adding any new autocmd's for this AutoPat, don't + // delete the pattern from the autopat list, append to + // this list. + if (is_adding_cmd && ap->next == NULL) { + aupat_remove_cmds(ap); break; } - au_remove_pat(ap); - } else if (*cmd == NUL) { - // Show autocmd's for this autopat, or buflocals <buffer=X> - show_autocmd(ap, event); - } else if (ap->next == NULL) { - // Add autocmd to this autopat, if it's the last one. - break; + aupat_del(ap); } } + prev_ap = &ap->next; } - prev_ap = &ap->next; } - // Add a new command. - if (*cmd != NUL) { - // If the pattern we want to add a command to does appear at the - // end of the list (or not is not in the list at all), add the - // pattern at the end of the list. - if (ap == NULL) { - // refuse to add buffer-local ap if buffer number is invalid - if (is_buflocal - && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) { - semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr); - return FAIL; - } + if (is_adding_cmd) { + AucmdExecutable exec = AUCMD_EXECUTABLE_INIT; + exec.type = CALLABLE_EX; + exec.callable.cmd = cmd; + autocmd_register(0, event, pat, patlen, group, once, nested, NULL, exec); + } - ap = xmalloc(sizeof(AutoPat)); - ap->pat = vim_strnsave(pat, (size_t)patlen); - ap->patlen = patlen; + pat = aucmd_next_pattern(pat, (size_t)patlen); + patlen = (int)aucmd_pattern_length(pat); + } - if (is_buflocal) { - ap->buflocal_nr = buflocal_nr; - ap->reg_prog = NULL; - } else { - char_u *reg_pat; + au_cleanup(); // may really delete removed patterns/commands now + return OK; +} - ap->buflocal_nr = 0; - reg_pat = file_pat_to_reg_pat(pat, endpat, &ap->allow_dirs, true); - if (reg_pat != NULL) { - ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC); - } - xfree(reg_pat); - if (reg_pat == NULL || ap->reg_prog == NULL) { - xfree(ap->pat); - xfree(ap); - return FAIL; - } - } - ap->cmds = NULL; - *prev_ap = ap; - last_autopat[(int)event] = ap; - ap->next = NULL; - if (group == AUGROUP_ALL) { - ap->group = current_augroup; - } else { - ap->group = group; +int autocmd_register(int64_t id, event_T event, char_u *pat, int patlen, int group, bool once, + bool nested, char *desc, AucmdExecutable aucmd) +{ + // 0 is not a valid group. + assert(group != 0); + + AutoPat *ap; + AutoPat **prev_ap; + AutoCmd *ac; + int findgroup; + char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" + + if (patlen > (int)STRLEN(pat)) { + return FAIL; + } + + if (group == AUGROUP_ALL) { + findgroup = current_augroup; + } else { + findgroup = group; + } + + + // detect special <buffer[=X]> buffer-local patterns + int is_buflocal = aupat_is_buflocal(pat, patlen); + int buflocal_nr = 0; + + if (is_buflocal) { + buflocal_nr = aupat_get_buflocal_nr(pat, patlen); + + // normalize pat into standard "<buffer>#N" form + aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr); + + pat = buflocal_pat; + patlen = (int)STRLEN(buflocal_pat); + } + + // always goes at or after the last one, so start at the end. + if (last_autopat[(int)event] != NULL) { + prev_ap = &last_autopat[(int)event]; + } else { + prev_ap = &first_autopat[(int)event]; + } + + while ((ap = *prev_ap) != NULL) { + if (ap->pat != NULL) { + // Accept a pattern when: + // - a group was specified and it's that group + // - the length of the pattern matches + // - the pattern matches. + // For <buffer[=X]>, this condition works because we normalize + // all buffer-local patterns. + if (ap->group == findgroup + && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) { + if (ap->next == NULL) { + // Add autocmd to this autopat, if it's the last one. + break; } } + } + prev_ap = &ap->next; + } + + // If the pattern we want to add a command to does appear at the + // end of the list (or not is not in the list at all), add the + // pattern at the end of the list. + if (ap == NULL) { + // refuse to add buffer-local ap if buffer number is invalid + if (is_buflocal + && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) { + semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr); + return FAIL; + } + + ap = xmalloc(sizeof(AutoPat)); + ap->pat = vim_strnsave(pat, (size_t)patlen); + ap->patlen = patlen; + + if (is_buflocal) { + ap->buflocal_nr = buflocal_nr; + ap->reg_prog = NULL; + } else { + char_u *reg_pat; - // Add the autocmd at the end of the AutoCmd list. - prev_ac = &(ap->cmds); - while ((ac = *prev_ac) != NULL) { - prev_ac = &ac->next; + ap->buflocal_nr = 0; + reg_pat = file_pat_to_reg_pat(pat, pat + patlen, &ap->allow_dirs, true); + if (reg_pat != NULL) { + ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC); + } + xfree(reg_pat); + if (reg_pat == NULL || ap->reg_prog == NULL) { + xfree(ap->pat); + xfree(ap); + return FAIL; } - ac = xmalloc(sizeof(AutoCmd)); - ac->cmd = vim_strsave(cmd); - ac->script_ctx = current_sctx; - ac->script_ctx.sc_lnum += sourcing_lnum; - ac->next = NULL; - *prev_ac = ac; - ac->once = once; - ac->nested = nested; + } + + // need to initialize last_mode for the first ModeChanged autocmd + if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) { + get_mode(last_mode); + } + + // If the event is CursorMoved, update the last cursor position + // position to avoid immediately triggering the autocommand + if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) { + curwin->w_last_cursormoved = curwin->w_cursor; + } + + // Initialize the fields checked by the WinScrolled trigger to + // stop it from firing right after the first autocmd is defined. + if (event == EVENT_WINSCROLLED && !has_event(EVENT_WINSCROLLED)) { + 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; + } + + ap->cmds = NULL; + *prev_ap = ap; + last_autopat[(int)event] = ap; + ap->next = NULL; + if (group == AUGROUP_ALL) { + ap->group = current_augroup; + } else { + ap->group = group; } } - au_cleanup(); // may really delete removed patterns/commands now + // Add the autocmd at the end of the AutoCmd list. + AutoCmd **prev_ac = &(ap->cmds); + while ((ac = *prev_ac) != NULL) { + prev_ac = &ac->next; + } + + ac = xmalloc(sizeof(AutoCmd)); + *prev_ac = ac; + + ac->id = id; + ac->exec = aucmd_exec_copy(aucmd); + ac->script_ctx = current_sctx; + ac->script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&ac->script_ctx); + ac->next = NULL; + ac->once = once; + ac->nested = nested; + ac->desc = NULL; + + // TODO(tjdevries): What to do about :autocmd and where/how to show lua stuffs there. + // perhaps: <lua>DESCRIPTION or similar + if (desc != NULL) { + ac->desc = xstrdup(desc); + } else { + ac->desc = aucmd_exec_default_desc(aucmd); + } + return OK; } +size_t aucmd_pattern_length(char_u *pat) +{ + if (*pat == NUL) { + return 0; + } + + char_u *endpat; + + for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) { + // Find end of the pattern. + // Watch out for a comma in braces, like "*.\{obj,o\}". + endpat = pat; + // ignore single comma + if (*endpat == ',') { + continue; + } + int brace_level = 0; + for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); + endpat++) { + if (*endpat == '{') { + brace_level++; + } else if (*endpat == '}') { + brace_level--; + } + } + + return (size_t)(endpat - pat); + } + + return STRLEN(pat); +} + +char_u *aucmd_next_pattern(char_u *pat, size_t patlen) +{ + pat = pat + patlen; + if (*pat == ',') { + pat = pat + 1; + } + + return pat; +} + /// Implementation of ":doautocmd [group] event [fname]". /// Return OK for success, FAIL for failure; /// /// @param do_msg give message for no matching autocmds? int do_doautocmd(char_u *arg, bool do_msg, bool *did_something) { - char_u *fname; int nothing_done = true; - int group; if (did_something != NULL) { *did_something = false; } // Check for a legal group name. If not, use AUGROUP_ALL. - group = au_get_grouparg(&arg); + int group = arg_augroup_get(&arg); if (*arg == '*') { emsg(_("E217: Can't execute autocommands for ALL events")); @@ -980,7 +1206,7 @@ int do_doautocmd(char_u *arg, bool do_msg, bool *did_something) // Scan over the events. // If we find an illegal name, return here, don't do anything. - fname = find_end_event(arg, group != AUGROUP_ALL); + char_u *fname = arg_event_skip(arg, group != AUGROUP_ALL); if (fname == NULL) { return FAIL; } @@ -1056,8 +1282,6 @@ void ex_doautoall(exarg_T *eap) do_modelines(0); } } - - check_cursor(); // just in case lines got deleted } /// Check *argp for <nomodeline>. When it is present return false, otherwise @@ -1147,7 +1371,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) // Prevent chdir() call in win_enter_ext(), through do_autochdir() int save_acd = p_acd; p_acd = false; + // no redrawing and don't set the window title + RedrawingDisabled++; win_enter(aucmd_win, false); + RedrawingDisabled--; p_acd = save_acd; unblock_autocmds(); curwin = aucmd_win; @@ -1155,6 +1382,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) curbuf = buf; aco->new_curwin_handle = curwin->handle; set_bufref(&aco->new_curbuf, curbuf); + + // disable the Visual area, the position may be invalid in another buffer + aco->save_VIsual_active = VIsual_active; + VIsual_active = false; } /// Cleanup after executing autocommands for a (hidden) buffer. @@ -1206,10 +1437,13 @@ win_found: // Hmm, original window disappeared. Just use the first one. curwin = firstwin; } + curbuf = curwin->w_buffer; + // May need to restore insert mode for a prompt buffer. + entering_window(curwin); + prevwin = win_find_by_handle(aco->save_prevwin_handle); vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab - curbuf = curwin->w_buffer; xfree(globaldir); globaldir = aco->globaldir; @@ -1248,6 +1482,12 @@ win_found: check_cursor(); } } + + check_cursor(); // just in case lines got deleted + VIsual_active = aco->save_VIsual_active; + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } } /// Execute autocommands for "event" and file name "fname". @@ -1330,11 +1570,9 @@ bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// Return true if the CursorHold/CursorHoldI event can be triggered. bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - int state; - if (!did_cursorhold && has_cursorhold() && reg_recording == 0 && typebuf.tb_len == 0 && !ins_compl_active()) { - state = get_real_state(); + int state = get_real_state(); if (state == NORMAL_BUSY || (state & INSERT) != 0) { return true; } @@ -1354,23 +1592,12 @@ bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// @param eap Ex command arguments /// /// @return true if some commands were executed. -static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, bool force, - int group, buf_T *buf, exarg_T *eap) +bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, bool force, int group, + buf_T *buf, exarg_T *eap) { char_u *sfname = NULL; // short file name - char_u *tail; - bool save_changed; - buf_T *old_curbuf; bool retval = false; - char_u *save_sourcing_name; - linenr_T save_sourcing_lnum; - char_u *save_autocmd_fname; - int save_autocmd_bufnr; - char_u *save_autocmd_match; - int save_autocmd_busy; - int save_autocmd_nested; static int nesting = 0; - AutoPatCmd patcmd; AutoPat *ap; char_u *save_cmdarg; long save_cmdbang; @@ -1378,7 +1605,7 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, proftime_T wait_time; bool did_save_redobuff = false; save_redo_T save_redo; - const bool save_KeyTyped = KeyTyped; + const bool save_KeyTyped = KeyTyped; // NOLINT // Quickly return if there are no autocommands for this event or // autocommands are blocked. @@ -1427,20 +1654,20 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, } // Save the autocmd_* variables and info about the current buffer. - save_autocmd_fname = autocmd_fname; - save_autocmd_bufnr = autocmd_bufnr; - save_autocmd_match = autocmd_match; - save_autocmd_busy = autocmd_busy; - save_autocmd_nested = autocmd_nested; - save_changed = curbuf->b_changed; - old_curbuf = curbuf; + char_u *save_autocmd_fname = autocmd_fname; + int save_autocmd_bufnr = autocmd_bufnr; + char_u *save_autocmd_match = autocmd_match; + int save_autocmd_busy = autocmd_busy; + int save_autocmd_nested = autocmd_nested; + bool save_changed = curbuf->b_changed; + buf_T *old_curbuf = curbuf; // Set the file name to be used for <afile>. // Make a copy to avoid that changing a buffer name or directory makes it // invalid. if (fname_io == NULL) { if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE - || event == EVENT_OPTIONSET) { + || event == EVENT_OPTIONSET || event == EVENT_MODECHANGED) { autocmd_fname = NULL; } else if (fname != NULL && !ends_excmd(*fname)) { autocmd_fname = fname; @@ -1493,12 +1720,14 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, || event == EVENT_CMDLINELEAVE || event == EVENT_CMDWINENTER || event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE - || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE - || event == EVENT_FUNCUNDEFINED || event == EVENT_OPTIONSET + || event == EVENT_DIRCHANGED || event == EVENT_DIRCHANGEDPRE + || event == EVENT_FILETYPE || event == EVENT_FUNCUNDEFINED + || event == EVENT_MODECHANGED || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX || event == EVENT_SIGNAL - || event == EVENT_TABCLOSED || event == EVENT_WINCLOSED) { + || event == EVENT_TABCLOSED || event == EVENT_USER + || event == EVENT_WINCLOSED || event == EVENT_WINSCROLLED) { fname = vim_strsave(fname); } else { fname = (char_u *)FullName_save((char *)fname, false); @@ -1524,9 +1753,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, // Don't redraw while doing autocommands. RedrawingDisabled++; - save_sourcing_name = sourcing_name; + char_u *save_sourcing_name = sourcing_name; sourcing_name = NULL; // don't free this one - save_sourcing_lnum = sourcing_lnum; + linenr_T save_sourcing_lnum = sourcing_lnum; sourcing_lnum = 0; // no line number here const sctx_T save_current_sctx = current_sctx; @@ -1559,9 +1788,10 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, did_filetype = true; } - tail = path_tail(fname); + char_u *tail = path_tail(fname); // Find first autocommand that matches + AutoPatCmd patcmd; patcmd.curpat = first_autopat[(int)event]; patcmd.nextcmd = NULL; patcmd.group = group; @@ -1783,14 +2013,62 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) } } +static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) +{ + bool ret = false; + Callback callback = ac->exec.callable.cb; + if (callback.type == kCallbackLua) { + Dictionary data = ARRAY_DICT_INIT; + PUT(data, "id", INTEGER_OBJ(ac->id)); + PUT(data, "event", CSTR_TO_OBJ(event_nr2name(apc->event))); + PUT(data, "match", CSTR_TO_OBJ((char *)autocmd_match)); + PUT(data, "file", CSTR_TO_OBJ((char *)autocmd_fname)); + PUT(data, "buf", INTEGER_OBJ(autocmd_bufnr)); + + int group = apc->curpat->group; + switch (group) { + case AUGROUP_ERROR: + abort(); // unreachable + case AUGROUP_DEFAULT: + case AUGROUP_ALL: + case AUGROUP_DELETED: + // omit group in these cases + break; + default: + PUT(data, "group", INTEGER_OBJ(group)); + break; + } + + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = DICTIONARY_OBJ(data); + + Object result = nlua_call_ref(callback.data.luaref, NULL, args, true, NULL); + if (result.type == kObjectTypeBoolean) { + ret = result.data.boolean; + } + api_free_dictionary(data); + api_free_object(result); + } else { + typval_T argsin = TV_INITIAL_VALUE; + typval_T rettv = TV_INITIAL_VALUE; + callback_call(&callback, 0, &argsin, &rettv); + } + + return ret; +} + /// Get next autocommand command. /// Called by do_cmdline() to get the next line for ":if". /// @return allocated string, or NULL for end of autocommands. char_u *getnextac(int c, void *cookie, int indent, bool do_concat) { + // These arguments are required for do_cmdline. + (void)c; + (void)indent; + (void)do_concat; + AutoPatCmd *acp = (AutoPatCmd *)cookie; char_u *retval; - AutoCmd *ac; // Can be called again after returning the last line. if (acp->curpat == NULL) { @@ -1800,7 +2078,8 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) // repeat until we find an autocommand to execute for (;;) { // skip removed commands - while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) { + while (acp->nextcmd != NULL + && aucmd_exec_is_deleted(acp->nextcmd->exec)) { if (acp->nextcmd->last) { acp->nextcmd = NULL; } else { @@ -1826,21 +2105,46 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) } } - ac = acp->nextcmd; + AutoCmd *ac = acp->nextcmd; + bool oneshot = ac->once; if (p_verbose >= 9) { verbose_enter_scroll(); - smsg(_("autocommand %s"), ac->cmd); + smsg(_("autocommand %s"), aucmd_exec_to_string(ac, ac->exec)); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } - retval = vim_strsave(ac->cmd); - // Remove one-shot ("once") autocmd in anticipation of its execution. - if (ac->once) { - au_del_cmd(ac); - } + + // Make sure to set autocmd_nested before executing + // lua code, so that it works properly autocmd_nested = ac->nested; current_sctx = ac->script_ctx; + + if (ac->exec.type == CALLABLE_CB) { + if (call_autocmd_callback(ac, acp)) { + // If an autocommand callback returns true, delete the autocommand + oneshot = true; + } + + // TODO(tjdevries): + // + // Major Hack Alert: + // We just return "not-null" and continue going. + // This would be a good candidate for a refactor. You would need to refactor: + // 1. do_cmdline to accept something besides a string + // OR + // 2. make where we call do_cmdline for autocmds not have to return anything, + // and instead we loop over all the matches and just execute one-by-one. + // However, my expectation would be that could be expensive. + retval = vim_strsave((char_u *)""); + } else { + retval = vim_strsave(ac->exec.callable.cmd); + } + + // Remove one-shot ("once") autocmd in anticipation of its execution. + if (oneshot) { + aucmd_del(ac); + } if (ac->last) { acp->nextcmd = NULL; } else { @@ -1859,12 +2163,10 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) /// @param buf buffer the file is open in bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSED_RESULT { - AutoPat *ap; - char_u *fname; char_u *tail = path_tail(sfname); bool retval = false; - fname = (char_u *)FullName_save((char *)sfname, false); + char_u *fname = (char_u *)FullName_save((char *)sfname, false); if (fname == NULL) { return false; } @@ -1877,7 +2179,7 @@ bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSE forward_slash(fname); #endif - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { + for (AutoPat *ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { if (ap->pat != NULL && ap->cmds != NULL && (ap->buflocal_nr == 0 ? match_file_pat(NULL, @@ -1902,31 +2204,21 @@ bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSE // Function given to ExpandGeneric() to obtain the list of autocommand group // names. -char_u *get_augroup_name(expand_T *xp, int idx) +char_u *expand_get_augroup_name(expand_T *xp, int idx) { - if (idx == augroups.ga_len) { // add "END" add the end - return (char_u *)"END"; - } - if (idx >= augroups.ga_len) { // end of list - return NULL; - } - if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) { - // skip deleted entries - return (char_u *)""; - } - return (char_u *)AUGROUP_NAME(idx); + // Required for ExpandGeneric + (void)xp; + + return (char_u *)augroup_name(idx + 1); } /// @param doautocmd true for :doauto*, false for :autocmd char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd) { - char_u *p; - int group; - // check for a group name, skip it if present autocmd_include_groups = false; - p = arg; - group = au_get_grouparg(&arg); + char_u *p = arg; + int group = arg_augroup_get(&arg); // If there only is a group name that's what we expand. if (*arg == NUL && group != AUGROUP_ALL && !ascii_iswhite(arg[-1])) { @@ -1967,16 +2259,24 @@ char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd) } // Function given to ExpandGeneric() to obtain the list of event names. -char_u *get_event_name(expand_T *xp, int idx) +char_u *expand_get_event_name(expand_T *xp, int idx) { - if (idx < augroups.ga_len) { // First list group names, if wanted - if (!autocmd_include_groups || AUGROUP_NAME(idx) == NULL - || AUGROUP_NAME(idx) == get_deleted_augroup()) { - return (char_u *)""; // skip deleted entries + // xp is a required parameter to be used with ExpandGeneric + (void)xp; + + // List group names + char *name = augroup_name(idx + 1); + if (name != NULL) { + // skip when not including groups or skip deleted entries + if (!autocmd_include_groups || name == get_deleted_augroup()) { + return (char_u *)""; } - return (char_u *)AUGROUP_NAME(idx); + + return (char_u *)name; } - return (char_u *)event_names[idx - augroups.ga_len].name; + + // List event names + return (char_u *)event_names[idx - next_augroup_id].name; } /// Check whether given autocommand is supported @@ -2005,10 +2305,7 @@ bool autocmd_supported(const char *const event) /// @param arg autocommand string bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT { - event_T event; - AutoPat *ap; buf_T *buflocal_buf = NULL; - int group; bool retval = false; // Make a copy so that we can change the '#' chars to a NUL. @@ -2019,7 +2316,7 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT } // First, look for an autocmd group name. - group = au_find_group((char_u *)arg_save); + int group = augroup_find(arg_save); char *event_name; if (group == AUGROUP_ERROR) { // Didn't match a group name, assume the first argument is an event. @@ -2043,7 +2340,7 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT char *pattern = p; // "pattern" is NULL when there is no pattern. // Find the index (enum) for the event name. - event = event_name2nr((char_u *)event_name, (char_u **)&p); + event_T event = event_name2nr((char_u *)event_name, (char_u **)&p); // return false if the event name is not recognized if (event == NUM_EVENTS) { @@ -2053,7 +2350,7 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT // Find the first autocommand for this event. // If there isn't any, return false; // If there is one and no pattern given, return true; - ap = first_autopat[(int)event]; + AutoPat *ap = first_autopat[(int)event]; if (ap == NULL) { goto theend; } @@ -2083,3 +2380,366 @@ theend: xfree(arg_save); return retval; } + +// Checks if a pattern is buflocal +bool aupat_is_buflocal(char_u *pat, int patlen) +{ + return patlen >= 8 + && STRNCMP(pat, "<buffer", 7) == 0 + && (pat)[patlen - 1] == '>'; +} + +int aupat_get_buflocal_nr(char_u *pat, int patlen) +{ + assert(aupat_is_buflocal(pat, patlen)); + + // "<buffer>" + if (patlen == 8) { + return curbuf->b_fnum; + } + + if (patlen > 9 && (pat)[7] == '=') { + // "<buffer=abuf>" + if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) { + return autocmd_bufnr; + } + + // "<buffer=123>" + if (skipdigits(pat + 8) == pat + patlen - 1) { + return atoi((char *)pat + 8); + } + } + + return 0; +} + +// normalize buffer pattern +void aupat_normalize_buflocal_pat(char_u *dest, char_u *pat, int patlen, int buflocal_nr) +{ + assert(aupat_is_buflocal(pat, patlen)); + + if (buflocal_nr == 0) { + buflocal_nr = curbuf->handle; + } + + // normalize pat into standard "<buffer>#N" form + snprintf((char *)dest, + BUFLOCAL_PAT_LEN, + "<buffer=%d>", + buflocal_nr); +} + +int autocmd_delete_event(int group, event_T event, char_u *pat) + FUNC_ATTR_NONNULL_ALL +{ + return do_autocmd_event(event, pat, false, false, (char_u *)"", true, group); +} + +/// Deletes an autocmd by ID. +/// Only autocmds created via the API have IDs associated with them. There +/// is no way to delete a specific autocmd created via :autocmd +bool autocmd_delete_id(int64_t id) +{ + assert(id > 0); + bool success = false; + + // Note that since multiple AutoCmd objects can have the same ID, we need to do a full scan. + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { + if (ac->id == id) { + aucmd_del(ac); + success = true; + } + } + } + } + return success; +} + +// =========================================================================== +// AucmdExecutable Functions +// =========================================================================== +char *aucmd_exec_default_desc(AucmdExecutable acc) +{ + size_t msglen = 100; + + switch (acc.type) { + case CALLABLE_CB: + switch (acc.callable.cb.type) { + case kCallbackLua: { + char *msg = (char *)xmallocz(msglen); + snprintf(msg, msglen, "<Lua function %d>", acc.callable.cb.data.luaref); + return msg; + } + case kCallbackFuncref: { + // TODO(tjdevries): Is this enough space for this? + char *msg = (char *)xmallocz(msglen); + snprintf(msg, msglen, "<vim function: %s>", acc.callable.cb.data.funcref); + return msg; + } + case kCallbackPartial: { + char *msg = (char *)xmallocz(msglen); + snprintf(msg, msglen, "<vim partial: %s>", acc.callable.cb.data.partial->pt_name); + return msg; + } + default: + return NULL; + } + default: + return NULL; + } +} + +char *aucmd_exec_to_string(AutoCmd *ac, AucmdExecutable acc) +{ + switch (acc.type) { + case CALLABLE_EX: + return (char *)acc.callable.cmd; + case CALLABLE_CB: + return ac->desc; + case CALLABLE_NONE: + return "This is not possible"; + } + + abort(); +} + +void aucmd_exec_free(AucmdExecutable *acc) +{ + switch (acc->type) { + case CALLABLE_EX: + XFREE_CLEAR(acc->callable.cmd); + break; + case CALLABLE_CB: + callback_free(&acc->callable.cb); + break; + case CALLABLE_NONE: + return; + } + + acc->type = CALLABLE_NONE; +} + +AucmdExecutable aucmd_exec_copy(AucmdExecutable src) +{ + AucmdExecutable dest = AUCMD_EXECUTABLE_INIT; + + switch (src.type) { + case CALLABLE_EX: + dest.type = CALLABLE_EX; + dest.callable.cmd = vim_strsave(src.callable.cmd); + return dest; + case CALLABLE_CB: + dest.type = CALLABLE_CB; + callback_copy(&dest.callable.cb, &src.callable.cb); + return dest; + case CALLABLE_NONE: + return dest; + } + + abort(); +} + +bool aucmd_exec_is_deleted(AucmdExecutable acc) +{ + switch (acc.type) { + case CALLABLE_EX: + return acc.callable.cmd == NULL; + case CALLABLE_CB: + return acc.callable.cb.type == kCallbackNone; + case CALLABLE_NONE: + return true; + } + + abort(); +} + +bool au_event_is_empty(event_T event) +{ + return first_autopat[event] == NULL; +} + +// Arg Parsing Functions + +/// Scan over the events. "*" stands for all events. +/// true when group name was found +static char_u *arg_event_skip(char_u *arg, int have_group) +{ + char_u *pat; + char_u *p; + + if (*arg == '*') { + if (arg[1] && !ascii_iswhite(arg[1])) { + semsg(_("E215: Illegal character after *: %s"), arg); + return NULL; + } + pat = arg + 1; + } else { + for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) { + if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) { + if (have_group) { + semsg(_("E216: No such event: %s"), pat); + } else { + semsg(_("E216: No such group or event: %s"), pat); + } + return NULL; + } + } + } + return pat; +} + +// Find the group ID in a ":autocmd" or ":doautocmd" argument. +// The "argp" argument is advanced to the following argument. +// +// Returns the group ID or AUGROUP_ALL. +static int arg_augroup_get(char_u **argp) +{ + char_u *p; + char_u *arg = *argp; + int group = AUGROUP_ALL; + + for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) { + } + if (p > arg) { + char_u *group_name = vim_strnsave(arg, (size_t)(p - arg)); + group = augroup_find((char *)group_name); + if (group == AUGROUP_ERROR) { + group = AUGROUP_ALL; // no match, use all groups + } else { + *argp = skipwhite(p); // match, skip over group name + } + xfree(group_name); + } + return group; +} + +/// Handles grabbing arguments from `:autocmd` such as ++once and ++nested +static bool arg_autocmd_flag_get(bool *flag, char_u **cmd_ptr, char *pattern, int len) +{ + if (STRNCMP(*cmd_ptr, pattern, len) == 0 && ascii_iswhite((*cmd_ptr)[len])) { + if (*flag) { + semsg(_(e_duparg2), pattern); + return true; + } + + *flag = true; + *cmd_ptr = skipwhite((*cmd_ptr) + len); + } + + return false; +} + + +// UI Enter +void do_autocmd_uienter(uint64_t chanid, bool attached) +{ + static bool recursive = false; + + if (recursive) { + return; // disallow recursion + } + recursive = true; + + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); + assert(chanid < VARNUMBER_MAX); + tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid); + tv_dict_set_keys_readonly(dict); + apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE, + NULL, NULL, false, curbuf); + restore_v_event(dict, &save_v_event); + + recursive = false; +} + +// FocusGained + +static void focusgained_event(void **argv) +{ + bool *gainedp = argv[0]; + do_autocmd_focusgained(*gainedp); + xfree(gainedp); +} + +void autocmd_schedule_focusgained(bool gained) +{ + bool *gainedp = xmalloc(sizeof(*gainedp)); + *gainedp = gained; + loop_schedule_deferred(&main_loop, + event_create(focusgained_event, 1, gainedp)); +} + +static void do_autocmd_focusgained(bool gained) +{ + static bool recursive = false; + static Timestamp last_time = (time_t)0; + bool need_redraw = false; + + if (recursive) { + return; // disallow recursion + } + recursive = true; + need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), + NULL, NULL, false, curbuf); + + // When activated: Check if any file was modified outside of Vim. + // Only do this when not done within the last two seconds as: + // 1. Some filesystems have modification time granularity in seconds. Fat32 + // has a granularity of 2 seconds. + // 2. We could get multiple notifications in a row. + if (gained && last_time + (Timestamp)2000 < os_now()) { + need_redraw = check_timestamps(true); + last_time = os_now(); + } + + if (need_redraw) { + // Something was executed, make sure the cursor is put back where it + // belongs. + need_wait_return = false; + + if (State & CMDLINE) { + redrawcmdline(); + } else if ((State & NORMAL) || (State & INSERT)) { + if (must_redraw != 0) { + update_screen(0); + } + + setcursor(); + } + + ui_flush(); + } + + if (need_maketitle) { + maketitle(); + } + + recursive = false; +} + +// initialization + +void init_default_autocmds(void) +{ + // open terminals when opening files that start with term:// +#define PROTO "term://" + do_cmdline_cmd("augroup nvim_terminal"); + do_cmdline_cmd("autocmd BufReadCmd " PROTO "* ++nested " + "if !exists('b:term_title')|call termopen(" + // Capture the command string + "matchstr(expand(\"<amatch>\"), " + "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), " + // capture the working directory + "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), " + "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})" + "|endif"); + do_cmdline_cmd("augroup END"); +#undef PROTO + + // limit syntax synchronization in the command window + do_cmdline_cmd("augroup nvim_cmdwin"); + do_cmdline_cmd("autocmd! CmdwinEnter [:>] syntax sync minlines=1 maxlines=1"); + do_cmdline_cmd("augroup END"); +} diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index ac12e2acf3..53ec7f2bb7 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -4,6 +4,11 @@ #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" +// event_T definition +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "auevents_enum.generated.h" +#endif + // Struct to save values in before executing autocommands for a buffer that is // not the current buffer. typedef struct { @@ -14,25 +19,27 @@ typedef struct { handle_T save_prevwin_handle; ///< ID of saved prevwin bufref_T new_curbuf; ///< new curbuf char_u *globaldir; ///< saved value of globaldir + bool save_VIsual_active; ///< saved VIsual_active } aco_save_T; typedef struct AutoCmd { - char_u *cmd; // Command to be executed (NULL when - // command has been removed) + AucmdExecutable exec; bool once; // "One shot": removed after execution bool nested; // If autocommands nest here bool last; // last command in list + int64_t id; // ID used for uniquely tracking an autocmd. sctx_T script_ctx; // script context where defined - struct AutoCmd *next; // Next AutoCmd in list + char *desc; // Description for the autocmd. + struct AutoCmd *next; // Next AutoCmd in list } AutoCmd; typedef struct AutoPat { - struct AutoPat *next; // next AutoPat in AutoPat list; MUST - // be the first entry - char_u *pat; // pattern as typed (NULL when pattern - // has been removed) - regprog_T *reg_prog; // compiled regprog for pattern - AutoCmd *cmds; // list of commands to do + struct AutoPat *next; // next AutoPat in AutoPat list; MUST + // be the first entry + char_u *pat; // pattern as typed (NULL when pattern + // has been removed) + regprog_T *reg_prog; // compiled regprog for pattern + AutoCmd *cmds; // list of commands to do int group; // group ID int patlen; // strlen() of pat int buflocal_nr; // !=0 for buffer-local AutoPat @@ -40,13 +47,7 @@ typedef struct AutoPat { char last; // last pattern for apply_autocmds() } AutoPat; -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "auevents_enum.generated.h" -#endif - -/// /// Struct used to keep status while executing autocommands for an event. -/// typedef struct AutoPatCmd { AutoPat *curpat; // next AutoPat to examine AutoCmd *nextcmd; // next AutoCmd to execute @@ -74,8 +75,16 @@ EXTERN bool au_did_filetype INIT(= false); # include "autocmd.h.generated.h" #endif -#define AUGROUP_DEFAULT -1 // default autocmd group -#define AUGROUP_ERROR -2 // erroneous autocmd group -#define AUGROUP_ALL -3 // all autocmd groups +#define AUGROUP_DEFAULT (-1) // default autocmd group +#define AUGROUP_ERROR (-2) // erroneous autocmd group +#define AUGROUP_ALL (-3) // all autocmd groups +#define AUGROUP_DELETED (-4) // all autocmd groups +// #define AUGROUP_NS -5 // TODO(tjdevries): Support namespaced based augroups + +#define BUFLOCAL_PAT_LEN 25 + +/// Iterates over all the events for auto commands +#define FOR_ALL_AUEVENTS(event) \ + for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1)) // NOLINT #endif // NVIM_AUTOCMD_H diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 1512d6bcd5..4948e2bb69 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -34,6 +34,7 @@ #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/digraph.h" #include "nvim/eval.h" @@ -50,6 +51,7 @@ #include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/main.h" @@ -57,7 +59,6 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -173,7 +174,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) if (ml_open(curbuf) == FAIL) { // There MUST be a memfile, otherwise we can't do anything // If we can't create one for the current buffer, take another buffer - close_buffer(NULL, curbuf, 0, false); + close_buffer(NULL, curbuf, 0, false, false); curbuf = NULL; FOR_ALL_BUFFERS(buf) { @@ -223,7 +224,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) || (S_ISCHR(perm) && is_dev_fd_file(curbuf->b_ffname)) # endif - )) { + )) { // NOLINT(whitespace/parens) read_fifo = true; } if (read_fifo) { @@ -401,8 +402,10 @@ bool buf_valid(buf_T *buf) /// there to be only one window with this buffer. e.g. when /// ":quit" is supposed to close the window but autocommands /// close all other windows. -/// @returns true when we got to the end and b_nwindows was decremented. -bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) +/// @param ignore_abort +/// If true, don't abort even when aborting() returns true. +/// @return true when we got to the end and b_nwindows was decremented. +bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool ignore_abort) { bool unload_buf = (action != 0); bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); @@ -493,7 +496,8 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) return false; } } - if (aborting()) { // autocmds may abort script processing + // autocmds may abort script processing + if (!ignore_abort && aborting()) { return false; } } @@ -551,14 +555,16 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf->b_nwindows = nwindows; - buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0)); + buf_freeall(buf, ((del_buf ? BFA_DEL : 0) + + (wipe_buf ? BFA_WIPE : 0) + + (ignore_abort ? BFA_IGNORE_ABORT : 0))); if (!bufref_valid(&bufref)) { // Autocommands may have deleted the buffer. return false; } - if (aborting()) { - // Autocmds may abort script processing. + // autocmds may abort script processing. + if (!ignore_abort && aborting()) { return false; } @@ -587,10 +593,12 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) // No need to check `unload_buf`: in that case the function returned above. buf_updates_unload(buf, false); - /* - * Remove the buffer from the list. - */ + // Remove the buffer from the list. if (wipe_buf) { + // Do not wipe out the buffer if it is used in a window. + if (buf->b_nwindows > 0) { + return false; + } if (buf->b_sfname != buf->b_ffname) { XFREE_CLEAR(buf->b_sfname); } else { @@ -657,9 +665,10 @@ void buf_clear(void) /// buf_freeall() - free all things allocated for a buffer that are related to /// the file. Careful: get here with "curwin" NULL when exiting. /// -/// @param flags BFA_DEL buffer is going to be deleted -/// BFA_WIPE buffer is going to be wiped out -/// BFA_KEEP_UNDO do not free undo information +/// @param flags BFA_DEL buffer is going to be deleted +/// BFA_WIPE buffer is going to be wiped out +/// BFA_KEEP_UNDO do not free undo information +/// BFA_IGNORE_ABORT don't abort even when aborting() returns true void buf_freeall(buf_T *buf, int flags) { bool is_curbuf = (buf == curbuf); @@ -703,7 +712,8 @@ void buf_freeall(buf_T *buf, int flags) goto_tabpage_win(the_curtab, the_curwin); unblock_autocmds(); } - if (aborting()) { // autocmds may abort script processing + // autocmds may abort script processing + if ((flags & BFA_IGNORE_ABORT) == 0 && aborting()) { return; } @@ -737,10 +747,8 @@ void buf_freeall(buf_T *buf, int flags) buf->b_flags &= ~BF_READERR; // a read error is no longer relevant } -/* - * Free a buffer structure and the things it contains related to the buffer - * itself (not the file, that must have been done already). - */ +/// Free a buffer structure and the things it contains related to the buffer +/// itself (not the file, that must have been done already). static void free_buffer(buf_T *buf) { pmap_del(handle_T)(&buffer_handles, buf->b_fnum); @@ -813,9 +821,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) buf_updates_unload(buf, false); } -/* - * Free the b_wininfo list for buffer "buf". - */ +/// Free the b_wininfo list for buffer "buf". static void clear_wininfo(buf_T *buf) { wininfo_T *wip; @@ -827,9 +833,7 @@ static void clear_wininfo(buf_T *buf) } } -/* - * Go to another buffer. Handles the result of the ATTENTION dialog. - */ +/// Go to another buffer. Handles the result of the ATTENTION dialog. void goto_buffer(exarg_T *eap, int start, int dir, int count) { bufref_T old_curbuf; @@ -847,7 +851,7 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count) enter_cleanup(&cs); // Quitting means closing the split window, nothing else. - win_close(curwin, true); + win_close(curwin, true, false); swap_exists_action = SEA_NONE; swap_exists_did_quit = true; @@ -880,7 +884,7 @@ void handle_swap_exists(bufref_T *old_curbuf) // open a new, empty buffer. swap_exists_action = SEA_NONE; // don't want it again swap_exists_did_quit = true; - close_buffer(curwin, curbuf, DOBUF_UNLOAD, false); + close_buffer(curwin, curbuf, DOBUF_UNLOAD, false, false); if (old_curbuf == NULL || !bufref_valid(old_curbuf) || old_curbuf->br_buf == curbuf) { @@ -1033,10 +1037,8 @@ char *do_bufdel(int command, char_u *arg, int addr_count, int start_bnr, int end } -/* - * Make the current buffer empty. - * Used when it is wiped out and it's the last buffer. - */ +/// Make the current buffer empty. +/// Used when it is wiped out and it's the last buffer. static int empty_curbuf(int close_others, int forceit, int action) { int retval; @@ -1051,8 +1053,24 @@ static int empty_curbuf(int close_others, int forceit, int action) set_bufref(&bufref, buf); if (close_others) { - // Close any other windows on this buffer, then make it empty. - close_windows(buf, true); + bool can_close_all_others = true; + if (curwin->w_floating) { + // Closing all other windows with this buffer may leave only floating windows. + can_close_all_others = false; + for (win_T *wp = firstwin; !wp->w_floating; wp = wp->w_next) { + if (wp->w_buffer != curbuf) { + // Found another non-floating window with a different (probably unlisted) buffer. + // Closing all other windows with this buffer is fine in this case. + can_close_all_others = true; + break; + } + } + } + // If it is fine to close all other windows with this buffer, keep the current window and + // close any other windows with this buffer, then make it empty. + // Otherwise close_windows() will refuse to close the last non-floating window, so allow it + // to close the current window instead. + close_windows(buf, can_close_all_others); } setpcmark(); @@ -1063,7 +1081,7 @@ static int empty_curbuf(int close_others, int forceit, int action) // the old one. But do_ecmd() may have done that already, check // if the buffer still exists. if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows == 0) { - close_buffer(NULL, buf, action, false); + close_buffer(NULL, buf, action, false, false); } if (!close_others) { @@ -1233,12 +1251,13 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } // If the deleted buffer is the current one, close the current window - // (unless it's the only window). Repeat this so long as we end up in - // a window with this buffer. + // (unless it's the only non-floating window). + // When the autocommand window is involved win_close() may need to print an error message. + // Repeat this so long as we end up in a window with this buffer. while (buf == curbuf && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) - && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) { - if (win_close(curwin, false) == FAIL) { + && (lastwin == aucmd_win || !last_window(curwin))) { + if (win_close(curwin, false, false) == FAIL) { break; } } @@ -1247,7 +1266,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) if (buf != curbuf) { close_windows(buf, false); if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows <= 0) { - close_buffer(NULL, buf, action, false); + close_buffer(NULL, buf, action, false, false); } return OK; } @@ -1276,8 +1295,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit) while (jumpidx != curwin->w_jumplistidx) { buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); if (buf != NULL) { - if (buf == curbuf || !buf->b_p_bl) { - buf = NULL; // skip current and unlisted bufs + // Skip current and unlisted bufs. Also skip a quickfix + // buffer, it might be deleted soon. + if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) { + buf = NULL; } else if (buf->b_ml.ml_mfp == NULL) { // skip unloaded buf, but may keep it for later if (bp == NULL) { @@ -1315,7 +1336,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) continue; } // in non-help buffer, try to skip help buffers, and vv - if (buf->b_help == curbuf->b_help && buf->b_p_bl) { + if (buf->b_help == curbuf->b_help && buf->b_p_bl && !bt_quickfix(buf)) { if (buf->b_ml.ml_mfp != NULL) { // found loaded buffer break; } @@ -1335,7 +1356,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } if (buf == NULL) { // No loaded buffer, find listed one FOR_ALL_BUFFERS(buf2) { - if (buf2->b_p_bl && buf2 != curbuf) { + if (buf2->b_p_bl && buf2 != curbuf && !bt_quickfix(buf2)) { buf = buf2; break; } @@ -1347,6 +1368,9 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } else { buf = curbuf->b_prev; } + if (bt_quickfix(buf)) { + buf = NULL; + } } } @@ -1410,15 +1434,15 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } -/* - * Set current buffer to "buf". Executes autocommands and closes current - * buffer. "action" tells how to close the current buffer: - * DOBUF_GOTO free or hide it - * DOBUF_SPLIT nothing - * DOBUF_UNLOAD unload it - * DOBUF_DEL delete it - * DOBUF_WIPE wipe it out - */ +/// Set current buffer to "buf". Executes autocommands and closes current +/// buffer. +/// +/// @param action tells how to close the current buffer: +/// DOBUF_GOTO free or hide it +/// DOBUF_SPLIT nothing +/// DOBUF_UNLOAD unload it +/// DOBUF_DEL delete it +/// DOBUF_WIPE wipe it out void set_curbuf(buf_T *buf, int action) { buf_T *prevbuf; @@ -1442,7 +1466,7 @@ void set_curbuf(buf_T *buf, int action) set_bufref(&prevbufref, prevbuf); set_bufref(&newbufref, buf); - // Autocommands may delete the curren buffer and/or the buffer we want to go + // Autocommands may delete the current buffer and/or the buffer we want to go // to. In those cases don't close the buffer. if (!apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf) || (bufref_valid(&prevbufref) && bufref_valid(&newbufref) @@ -1455,7 +1479,11 @@ void set_curbuf(buf_T *buf, int action) } if (bufref_valid(&prevbufref) && !aborting()) { win_T *previouswin = curwin; - if (prevbuf == curbuf) { + + // Do not sync when in Insert mode and the buffer is open in + // another window, might be a timer doing something in another + // window. + if (prevbuf == curbuf && ((State & INSERT) == 0 || curbuf->b_nwindows <= 1)) { u_sync(false); } close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, @@ -1464,7 +1492,7 @@ void set_curbuf(buf_T *buf, int action) ? action : (action == DOBUF_GOTO && !buf_hide(prevbuf) && !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0, - false); + false, false); if (curwin != previouswin && win_valid(previouswin)) { // autocommands changed curwin, Grr! curwin = previouswin; @@ -1474,10 +1502,15 @@ void set_curbuf(buf_T *buf, int action) // An autocommand may have deleted "buf", already entered it (e.g., when // it did ":bunload") or aborted the script processing! // If curwin->w_buffer is null, enter_buffer() will make it valid again - if ((buf_valid(buf) && buf != curbuf - && !aborting() - ) || curwin->w_buffer == NULL) { - enter_buffer(buf); + bool valid = buf_valid(buf); + if ((valid && buf != curbuf && !aborting()) || curwin->w_buffer == NULL) { + // If the buffer is not valid but curwin->w_buffer is NULL we must + // enter some buffer. Using the last one is hopefully OK. + if (!valid) { + enter_buffer(lastbuf); + } else { + enter_buffer(buf); + } if (old_tw != curbuf->b_p_tw) { check_colorcolumn(curwin); } @@ -1488,13 +1521,16 @@ void set_curbuf(buf_T *buf, int action) } } -/* - * Enter a new current buffer. - * Old curbuf must have been abandoned already! This also means "curbuf" may - * be pointing to freed memory. - */ +/// Enter a new current buffer. +/// Old curbuf must have been abandoned already! This also means "curbuf" may +/// be pointing to freed memory. void enter_buffer(buf_T *buf) { + // Get the buffer in the current window. + curwin->w_buffer = buf; + curbuf = buf; + curbuf->b_nwindows++; + // Copy buffer and window local option values. Not for a help buffer. buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); if (!buf->b_help) { @@ -1505,11 +1541,6 @@ void enter_buffer(buf_T *buf) } foldUpdateAll(curwin); // update folds (later). - // Get the buffer in the current window. - curwin->w_buffer = buf; - curbuf = buf; - curbuf->b_nwindows++; - if (curwin->w_p_diff) { diff_buf_add(curbuf); } @@ -1579,15 +1610,15 @@ void enter_buffer(buf_T *buf) redraw_later(curwin, NOT_VALID); } -// Change to the directory of the current buffer. -// Don't do this while still starting up. +/// Change to the directory of the current buffer. +/// Don't do this while still starting up. void do_autochdir(void) { if (p_acd) { if (starting == 0 && curbuf->b_ffname != NULL && vim_chdirfile(curbuf->b_ffname, kCdCauseAuto) == OK) { - post_chdir(kCdScopeGlobal, false); + last_chdir_reason = "autochdir"; shorten_fnames(true); } } @@ -1657,7 +1688,7 @@ static inline void buf_init_changedtick(buf_T *const buf) /// @param flags BLN_ defines /// @param bufnr /// -/// @return pointer to the buffer +/// @return pointer to the buffer buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int flags) { char_u *ffname = ffname_arg; @@ -1699,14 +1730,12 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int fl return buf; } - /* - * If the current buffer has no name and no contents, use the current - * buffer. Otherwise: Need to allocate a new buffer structure. - * - * This is the ONLY place where a new buffer structure is allocated! - * (A spell file buffer is allocated in spell.c, but that's not a normal - * buffer.) - */ + // If the current buffer has no name and no contents, use the current + // buffer. Otherwise: Need to allocate a new buffer structure. + // + // This is the ONLY place where a new buffer structure is allocated! + // (A spell file buffer is allocated in spell.c, but that's not a normal + // buffer.) buf = NULL; if ((flags & BLN_CURBUF) && curbuf_reusable()) { assert(curbuf != NULL); @@ -1733,7 +1762,7 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int fl buf = xcalloc(1, sizeof(buf_T)); // init b: variables buf->b_vars = tv_dict_alloc(); - buf->b_signcols_valid = false; + buf->b_signcols.valid = false; init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); buf_init_changedtick(buf); } @@ -1777,9 +1806,7 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int fl // need to reload lmaps and set b:keymap_name curbuf->b_kmap_state |= KEYMAP_INIT; } else { - /* - * put new buffer at the end of the buffer list - */ + // put new buffer at the end of the buffer list buf->b_next = NULL; if (firstbuf == NULL) { // buffer list is empty buf->b_prev = NULL; @@ -1871,11 +1898,9 @@ bool curbuf_reusable(void) && !curbufIsChanged()); } -/* - * Free the memory for the options of a buffer. - * If "free_p_ff" is true also free 'fileformat', 'buftype' and - * 'fileencoding'. - */ +/// Free the memory for the options of a buffer. +/// If "free_p_ff" is true also free 'fileformat', 'buftype' and +/// 'fileencoding'. void free_buf_options(buf_T *buf, int free_p_ff) { if (free_p_ff) { @@ -1897,10 +1922,8 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_flp); clear_string_option(&buf->b_p_isk); clear_string_option(&buf->b_p_vsts); - xfree(buf->b_p_vsts_nopaste); - buf->b_p_vsts_nopaste = NULL; - xfree(buf->b_p_vsts_array); - buf->b_p_vsts_array = NULL; + XFREE_CLEAR(buf->b_p_vsts_nopaste); + XFREE_CLEAR(buf->b_p_vsts_array); clear_string_option(&buf->b_p_vts); XFREE_CLEAR(buf->b_p_vts_array); clear_string_option(&buf->b_p_keymap); @@ -1922,6 +1945,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_cink); clear_string_option(&buf->b_p_cino); clear_string_option(&buf->b_p_cinw); + clear_string_option(&buf->b_p_cinsd); clear_string_option(&buf->b_p_cpt); clear_string_option(&buf->b_p_cfu); clear_string_option(&buf->b_p_ofu); @@ -2037,7 +2061,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) return FAIL; } -// Go to the last known line number for the current buffer. +/// Go to the last known line number for the current buffer. void buflist_getfpos(void) { pos_T *fpos; @@ -2057,10 +2081,9 @@ void buflist_getfpos(void) } } -/* - * Find file in buffer list by name (it has to be for the current window). - * Returns NULL if not found. - */ +/// Find file in buffer list by name (it has to be for the current window). +/// +/// @return buffer or NULL if not found buf_T *buflist_findname_exp(char_u *fname) { char_u *ffname; @@ -2074,7 +2097,7 @@ buf_T *buflist_findname_exp(char_u *fname) #else false #endif - ); + ); // NOLINT(whitespace/parens) if (ffname != NULL) { buf = buflist_findname(ffname); xfree(ffname); @@ -2082,12 +2105,11 @@ buf_T *buflist_findname_exp(char_u *fname) return buf; } -/* - * Find file in buffer list by name (it has to be for the current window). - * "ffname" must have a full path. - * Skips dummy buffers. - * Returns NULL if not found. - */ +/// Find file in buffer list by name (it has to be for the current window). +/// "ffname" must have a full path. +/// Skips dummy buffers. +/// +/// @return buffer or NULL if not found buf_T *buflist_findname(char_u *ffname) { FileID file_id; @@ -2095,11 +2117,10 @@ buf_T *buflist_findname(char_u *ffname) return buflist_findname_file_id(ffname, &file_id, file_id_valid); } -/* - * Same as buflist_findname(), but pass the FileID structure to avoid - * getting it twice for the same file. - * Returns NULL if not found. - */ +/// Same as buflist_findname(), but pass the FileID structure to avoid +/// getting it twice for the same file. +/// +/// @return buffer or NULL if not found static buf_T *buflist_findname_file_id(char_u *ffname, FileID *file_id, bool file_id_valid) { // Start at the last buffer, expect to find a match sooner. @@ -2113,13 +2134,13 @@ static buf_T *buflist_findname_file_id(char_u *ffname, FileID *file_id, bool fil } /// Find file in buffer list by a regexp pattern. -/// Return fnum of the found buffer. -/// Return < 0 for error. /// /// @param pattern_end pointer to first char after pattern /// @param unlisted find unlisted buffers /// @param diffmode find diff-mode buffers only /// @param curtab_only find buffers in current tab only +/// +/// @return fnum of the found buffer or < 0 for error. int buflist_findpat(const char_u *pattern, const char_u *pattern_end, bool unlisted, bool diffmode, bool curtab_only) FUNC_ATTR_NONNULL_ARG(1) @@ -2143,7 +2164,6 @@ int buflist_findpat(const char_u *pattern, const char_u *pattern_end, bool unlis match = -1; } } else { - // // Try four ways of matching a listed buffer: // attempt == 0: without '^' or '$' (at any position) // attempt == 1: with '^' at start (only at position 0) @@ -2151,7 +2171,6 @@ int buflist_findpat(const char_u *pattern, const char_u *pattern_end, bool unlis // attempt == 3: with '^' at start and '$' at end (only full match) // Repeat this for finding an unlisted buffer if there was no matching // listed buffer. - // pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, false); if (pat == NULL) { @@ -2249,11 +2268,10 @@ static int buf_time_compare(const void *s1, const void *s2) return buf1->b_last_used > buf2->b_last_used ? -1 : 1; } -/* - * Find all buffer names that match. - * For command line expansion of ":buf" and ":sbuf". - * Return OK if matches found, FAIL otherwise. - */ +/// Find all buffer names that match. +/// For command line expansion of ":buf" and ":sbuf". +/// +/// @return OK if matches found, FAIL otherwise. int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) { int count = 0; @@ -2377,7 +2395,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) /// Check for a match on the file name for buffer "buf" with regprog "prog". /// -/// @param ignore_case When true, ignore case. Use 'fic' otherwise. +/// @param ignore_case When true, ignore case. Use 'fic' otherwise. static char_u *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) { // First try the short file name, then the long file name. @@ -2390,8 +2408,9 @@ static char_u *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) /// Try matching the regexp in "prog" with file name "name". /// -/// @param ignore_case When true, ignore case. Use 'fileignorecase' otherwise. -/// @return "name" when there is a match, NULL when not. +/// @param ignore_case When true, ignore case. Use 'fileignorecase' otherwise. +/// +/// @return "name" when there is a match, NULL when not. static char_u *fname_match(regmatch_T *rmp, char_u *name, bool ignore_case) { char_u *match = NULL; @@ -2526,12 +2545,13 @@ static bool wininfo_other_tab_diff(wininfo_T *wip) return false; } -// Find info for the current window in buffer "buf". -// If not found, return the info for the most recently used window. -// When "need_options" is true skip entries where wi_optset is false. -// When "skip_diff_buffer" is true avoid windows with 'diff' set that is in -// another tab page. -// Returns NULL when there isn't any info. +/// Find info for the current window in buffer "buf". +/// If not found, return the info for the most recently used window. +/// +/// @param need_options when true, skip entries where wi_optset is false. +/// @param skip_diff_buffer when true, avoid windows with 'diff' set that is in another tab page. +/// +/// @return NULL when there isn't any info. static wininfo_T *find_wininfo(buf_T *buf, bool need_options, bool skip_diff_buffer) FUNC_ATTR_NONNULL_ALL { @@ -2568,12 +2588,10 @@ static wininfo_T *find_wininfo(buf_T *buf, bool need_options, bool skip_diff_buf return wip; } -/* - * Reset the local window options to the values last used in this window. - * If the buffer wasn't used in this window before, use the values from - * the most recently used window. If the values were never set, use the - * global values for the window. - */ +/// Reset the local window options to the values last used in this window. +/// If the buffer wasn't used in this window before, use the values from +/// the most recently used window. If the values were never set, use the +/// global values for the window. void get_winopts(buf_T *buf) { clear_winopt(&curwin->w_onebuf_opt); @@ -2608,11 +2626,10 @@ void get_winopts(buf_T *buf) didset_window_options(curwin); } -/* - * Find the position (lnum and col) for the buffer 'buf' for the current - * window. - * Returns a pointer to no_position if no position is found. - */ +/// Find the position (lnum and col) for the buffer 'buf' for the current +/// window. +/// +/// @return a pointer to no_position if no position is found. pos_T *buflist_findfpos(buf_T *buf) { static pos_T no_position = { 1, 0, 0 }; @@ -2621,15 +2638,13 @@ pos_T *buflist_findfpos(buf_T *buf) return (wip == NULL) ? &no_position : &(wip->wi_fpos); } -/* - * Find the lnum for the buffer 'buf' for the current window. - */ +/// Find the lnum for the buffer 'buf' for the current window. linenr_T buflist_findlnum(buf_T *buf) { return buflist_findfpos(buf)->lnum; } -// List all known file names (for :files and :buffers command). +/// List all known file names (for :files and :buffers command). void buflist_list(exarg_T *eap) { buf_T *buf = firstbuf; @@ -2657,8 +2672,7 @@ void buflist_list(exarg_T *eap) for (; buf != NULL && !got_int; buf = buflist_data != NULL - ? (++p < buflist_data + buflist.ga_len ? *p : NULL) - : buf->b_next) { + ? (++p < buflist_data + buflist.ga_len ? *p : NULL) : buf->b_next) { const bool is_terminal = buf->terminal; const bool job_running = buf->terminal && terminal_running(buf->terminal); @@ -2719,12 +2733,10 @@ void buflist_list(exarg_T *eap) IObuff[len++] = ' '; } while (--i > 0 && len < IOSIZE - 18); if (vim_strchr(eap->arg, 't') && buf->b_last_used) { - add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); + undo_fmt_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); } else { - vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), - _("line %" PRId64), - buf == curbuf ? (int64_t)curwin->w_cursor.lnum - : (int64_t)buflist_findlnum(buf)); + vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), _("line %" PRId64), + buf == curbuf ? (int64_t)curwin->w_cursor.lnum : (int64_t)buflist_findlnum(buf)); } msg_outtrans(IObuff); @@ -2736,12 +2748,11 @@ void buflist_list(exarg_T *eap) } } -/* - * Get file name and line number for file 'fnum'. - * Used by DoOneCmd() for translating '%' and '#'. - * Used by insert_reg() and cmdline_paste() for '#' register. - * Return FAIL if not found, OK for success. - */ +/// Get file name and line number for file 'fnum'. +/// Used by DoOneCmd() for translating '%' and '#'. +/// Used by insert_reg() and cmdline_paste() for '#' register. +/// +/// @return FAIL if not found, OK for success. int buflist_name_nr(int fnum, char_u **fname, linenr_T *lnum) { buf_T *buf; @@ -2802,7 +2813,7 @@ int setfname(buf_T *buf, char_u *ffname_arg, char_u *sfname_arg, bool message) return FAIL; } // delete from the list - close_buffer(NULL, obuf, DOBUF_WIPE, false); + close_buffer(NULL, obuf, DOBUF_WIPE, false, false); } sfname = vim_strsave(sfname); #ifdef USE_FNAME_CASE @@ -2827,10 +2838,8 @@ int setfname(buf_T *buf, char_u *ffname_arg, char_u *sfname_arg, bool message) return OK; } -/* - * Crude way of changing the name of a buffer. Use with care! - * The name should be relative to the current directory. - */ +/// Crude way of changing the name of a buffer. Use with care! +/// The name should be relative to the current directory. void buf_set_name(int fnum, char_u *name) { buf_T *buf; @@ -2850,15 +2859,10 @@ void buf_set_name(int fnum, char_u *name) } } -/* - * Take care of what needs to be done when the name of buffer "buf" has - * changed. - */ +/// Take care of what needs to be done when the name of buffer "buf" has changed. void buf_name_changed(buf_T *buf) { - /* - * If the file name changed, also change the name of the swapfile - */ + // If the file name changed, also change the name of the swapfile if (buf->b_ml.ml_mfp != NULL) { ml_setname(buf); } @@ -2872,12 +2876,11 @@ void buf_name_changed(buf_T *buf) ml_timestamp(buf); // reset timestamp } -/* - * set alternate file name for current window - * - * Used by do_one_cmd(), do_write() and do_ecmd(). - * Return the buffer. - */ +/// Set alternate file name for current window +/// +/// Used by do_one_cmd(), do_write() and do_ecmd(). +/// +/// @return the buffer. buf_T *setaltfname(char_u *ffname, char_u *sfname, linenr_T lnum) { buf_T *buf; @@ -2908,12 +2911,10 @@ char_u *getaltfname(bool errmsg) return fname; } -/* - * Add a file name to the buflist and return its number. - * Uses same flags as buflist_new(), except BLN_DUMMY. - * - * used by qf_init(), main() and doarglist() - */ +/// Add a file name to the buflist and return its number. +/// Uses same flags as buflist_new(), except BLN_DUMMY. +/// +/// Used by qf_init(), main() and doarglist() int buflist_add(char_u *fname, int flags) { buf_T *buf; @@ -2926,9 +2927,7 @@ int buflist_add(char_u *fname, int flags) } #if defined(BACKSLASH_IN_FILENAME) -/* - * Adjust slashes in file names. Called after 'shellslash' was set. - */ +/// Adjust slashes in file names. Called after 'shellslash' was set. void buflist_slash_adjust(void) { FOR_ALL_BUFFERS(bp) { @@ -2943,10 +2942,8 @@ void buflist_slash_adjust(void) #endif -/* - * Set alternate cursor position for the current buffer and window "win". - * Also save the local window option values. - */ +/// Set alternate cursor position for the current buffer and window "win". +/// Also save the local window option values. void buflist_altfpos(win_T *win) { buflist_setfpos(curbuf, win, win->w_cursor.lnum, win->w_cursor.col, true); @@ -3009,8 +3006,8 @@ static bool otherfile_buf(buf_T *buf, char_u *ffname, FileID *file_id_p, bool fi return true; } -// Set file_id for a buffer. -// Must always be called when b_fname is changed! +/// Set file_id for a buffer. +/// Must always be called when b_fname is changed! void buf_set_file_id(buf_T *buf) { FileID file_id; @@ -3150,7 +3147,7 @@ static char_u *lasttitle = NULL; static char_u *lasticon = NULL; -// Put the title name in the title bar and icon of the window. +/// Put the title name in the title bar and icon of the window. void maketitle(void) { char_u *title_str = NULL; @@ -3327,7 +3324,7 @@ void maketitle(void) len = (int)STRLEN(buf_p); if (len > 100) { len -= 100; - len += (*mb_tail_off)(buf_p, buf_p + len) + 1; + len += mb_tail_off(buf_p, buf_p + len) + 1; buf_p += len; } STRCPY(icon_str, buf_p); @@ -3348,8 +3345,8 @@ void maketitle(void) /// /// @param str desired title string /// @param[in,out] last current title string -// -/// @return true if resettitle() is to be called. +/// +/// @return true if resettitle() is to be called. static bool value_change(char_u *str, char_u **last) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -3406,20 +3403,20 @@ typedef enum { /// If maxwidth is not zero, the string will be filled at any middle marker /// or truncated if too long, fillchar is used for all whitespace. /// -/// @param wp The window to build a statusline for -/// @param out The output buffer to write the statusline to -/// Note: This should not be NameBuff -/// @param outlen The length of the output buffer -/// @param fmt The statusline format string -/// @param use_sandbox Use a sandboxed environment when evaluating fmt -/// @param fillchar Character to use when filling empty space in the statusline -/// @param maxwidth The maximum width to make the statusline -/// @param hltab HL attributes (can be NULL) -/// @param tabtab Tab clicks definition (can be NULL). +/// @param wp The window to build a statusline for +/// @param out The output buffer to write the statusline to +/// Note: This should not be NameBuff +/// @param outlen The length of the output buffer +/// @param fmt The statusline format string +/// @param use_sandbox Use a sandboxed environment when evaluating fmt +/// @param fillchar Character to use when filling empty space in the statusline +/// @param maxwidth The maximum width to make the statusline +/// @param hltab HL attributes (can be NULL) +/// @param tabtab Tab clicks definition (can be NULL). /// -/// @return The final width of the statusline +/// @return The final width of the statusline int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use_sandbox, - char_u fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) + int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) { static size_t stl_items_len = 20; // Initial value, grows as needed. static stl_item_t *stl_items = NULL; @@ -3438,8 +3435,12 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use 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(StlClickRecord) * stl_items_len); + + // Allocate one more, because the last element is used to indicate the + // end of the list. + stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1)); + stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1)); + stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); } @@ -3462,9 +3463,6 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use if (fillchar == 0) { fillchar = ' '; - } else if (utf_char2len(fillchar) > 1) { - // Can't handle a multi-byte fill character yet. - fillchar = '-'; } // The cursor in windows other than the current one isn't always @@ -3517,8 +3515,8 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use 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_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1)); + stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1)); stl_separator_locations = xrealloc(stl_separator_locations, sizeof(int) * new_len); @@ -3662,7 +3660,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use out_p = out_p - n + 1; // Fill up space left over by half a double-wide char. while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } // } @@ -3678,21 +3676,20 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use } } // If the group is shorter than the minimum width, add padding characters. - } else if ( - abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { + } 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; while (group_len++ < min_group_width && out_p < out_end_p) { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } // If the group is right-aligned, shift everything to the right and // prepend with filler characters. } else { // { Move the group to the right - memmove(t + min_group_width - group_len, t, (size_t)(out_p - t)); - group_len = min_group_width - group_len; + group_len = (min_group_width - group_len) * utf_char2len(fillchar); + memmove(t + group_len, t, (size_t)(out_p - t)); if (out_p + group_len >= (out_end_p + 1)) { group_len = (long)(out_end_p - out_p); } @@ -3706,7 +3703,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use // Prepend the fill characters for (; group_len > 0; group_len--) { - *t++ = fillchar; + MB_CHAR2BYTES(fillchar, t); } } } @@ -4002,14 +3999,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use case STL_VIRTCOL: case STL_VIRTCOL_ALT: { - // In list mode virtcol needs to be recomputed - colnr_T virtcol = wp->w_virtcol; - if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { - wp->w_p_list = false; - getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); - wp->w_p_list = true; - } - virtcol++; + colnr_T virtcol = wp->w_virtcol + 1; // Don't display %V if it's the same as %c. if (opt == STL_VIRTCOL_ALT && (virtcol == (colnr_T)(!(State & INSERT) && empty_line @@ -4238,7 +4228,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { *out_p++ = ' '; } else { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } } minwid = 0; @@ -4249,20 +4239,21 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use } // { Copy the string text into the output buffer - while (*t && out_p < out_end_p) { - *out_p++ = *t++; + for (; *t && out_p < out_end_p; t++) { // Change a space by fillchar, unless fillchar is '-' and a // digit follows. - if (fillable && out_p[-1] == ' ' - && (!ascii_isdigit(*t) || fillchar != '-')) { - out_p[-1] = fillchar; + if (fillable && *t == ' ' + && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) { + MB_CHAR2BYTES(fillchar, out_p); + } else { + *out_p++ = *t; } } // } // For left-aligned items, fill any remaining space with the fillchar for (; l < minwid && out_p < out_end_p; l++) { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } // Otherwise if the item is a number, copy that to the output buffer. @@ -4352,7 +4343,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use // Only free the string buffer if we allocated it. // Note: This is not needed if `str` is pointing at `tmp` if (opt == STL_VIM_EXPR) { - xfree(str); + XFREE_CLEAR(str); } if (num >= 0 || (!itemisflag && str && *str)) { @@ -4455,7 +4446,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use // Fill up for half a double-wide character. while (++width < maxwidth) { - *trunc_p++ = fillchar; + MB_CHAR2BYTES(fillchar, trunc_p); *trunc_p = NUL; } // } @@ -4506,13 +4497,13 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use standard_spaces * (num_separators - 1); for (int i = 0; i < num_separators; i++) { - int dislocation = (i == (num_separators - 1)) - ? final_spaces : standard_spaces; + int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; + dislocation *= utf_char2len(fillchar); 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 (char_u *s = start; s < seploc;) { + MB_CHAR2BYTES(fillchar, s); } for (int item_idx = stl_separator_locations[i] + 1; @@ -4577,7 +4568,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use cur_tab_rec->def.func = NULL; } - // When inside update_screen we do not want redrawing a stausline, ruler, + // When inside update_screen we do not want redrawing a statusline, ruler, // title, etc. to trigger another redraw, it may cause an endless loop. if (updating_screen) { must_redraw = save_must_redraw; @@ -4585,12 +4576,10 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use } return width; -} +} // NOLINT(readability/fn_size) -/* - * Get relative cursor position in window into "buf[buflen]", in the form 99%, - * using "Top", "Bot" or "All" when appropriate. - */ +/// Get relative cursor position in window into "buf[buflen]", in the form 99%, +/// using "Top", "Bot" or "All" when appropriate. void get_rel_pos(win_T *wp, char_u *buf, int buflen) { // Need at least 3 chars for writing. @@ -4627,7 +4616,7 @@ void get_rel_pos(win_T *wp, char_u *buf, int buflen) /// @param buflen length of the string buffer /// @param add_file if true, add "file" before the arg number /// -/// @return true if it was appended. +/// @return true if it was appended. static bool append_arg_number(win_T *wp, char_u *buf, int buflen, bool add_file) FUNC_ATTR_NONNULL_ALL { @@ -4656,12 +4645,12 @@ static bool append_arg_number(win_T *wp, char_u *buf, int buflen, bool add_file) return true; } -// Make "*ffname" a full file name, set "*sfname" to "*ffname" if not NULL. -// "*ffname" becomes a pointer to allocated memory (or NULL). -// When resolving a link both "*sfname" and "*ffname" will point to the same -// allocated memory. -// The "*ffname" and "*sfname" pointer values on call will not be freed. -// Note that the resulting "*ffname" pointer should be considered not allocated. +/// Make "*ffname" a full file name, set "*sfname" to "*ffname" if not NULL. +/// "*ffname" becomes a pointer to allocated memory (or NULL). +/// When resolving a link both "*sfname" and "*ffname" will point to the same +/// allocated memory. +/// The "*ffname" and "*sfname" pointer values on call will not be freed. +/// Note that the resulting "*ffname" pointer should be considered not allocated. void fname_expand(buf_T *buf, char_u **ffname, char_u **sfname) { if (*ffname == NULL) { // no file name given, nothing to do @@ -4685,9 +4674,7 @@ void fname_expand(buf_T *buf, char_u **ffname, char_u **sfname) #endif } -/* - * Get the file name for an argument list entry. - */ +/// Get the file name for an argument list entry. char_u *alist_name(aentry_T *aep) { buf_T *bp; @@ -4729,8 +4716,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) assert(firstwin != NULL); // satisfy coverity if (ARGCOUNT <= 0) { - /* Don't give an error message. We don't want it when the ":all" - * command is in the .vimrc. */ + // Don't give an error message. We don't want it when the ":all" command is in the .vimrc. return; } setpcmark(); @@ -4738,9 +4724,9 @@ void do_arg_all(int count, int forceit, int keep_tabs) opened_len = ARGCOUNT; opened = xcalloc((size_t)opened_len, 1); - /* Autocommands may do anything to the argument list. Make sure it's not - * freed while we are working here by "locking" it. We still have to - * watch out for its size to be changed. */ + // Autocommands may do anything to the argument list. Make sure it's not + // freed while we are working here by "locking" it. We still have to + // watch out for its size to be changed. alist = curwin->w_alist; alist->al_refcount++; @@ -4748,13 +4734,11 @@ void do_arg_all(int count, int forceit, int keep_tabs) old_curtab = curtab; - /* - * Try closing all windows that are not in the argument list. - * Also close windows that are not full width; - * When 'hidden' or "forceit" set the buffer becomes hidden. - * Windows that have a changed buffer and can't be hidden won't be closed. - * When the ":tab" modifier was used do this for all tab pages. - */ + // Try closing all windows that are not in the argument list. + // Also close windows that are not full width; + // When 'hidden' or "forceit" set the buffer becomes hidden. + // Windows that have a changed buffer and can't be hidden won't be closed. + // When the ":tab" modifier was used do this for all tab pages. if (had_tab > 0) { goto_tabpage_tp(first_tabpage, true, true); } @@ -4799,8 +4783,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) } if (wp->w_alist != alist) { - /* Use the current argument list for all windows - * containing a file from it. */ + // Use the current argument list for all windows containing a file from it. alist_unlink(wp->w_alist); wp->w_alist = alist; wp->w_alist->al_refcount++; @@ -4814,8 +4797,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) if (i == opened_len && !keep_tabs) { // close this window if (buf_hide(buf) || forceit || buf->b_nwindows > 1 || !bufIsChanged(buf)) { - /* If the buffer was changed, and we would like to hide it, - * try autowriting. */ + // If the buffer was changed, and we would like to hide it, try autowriting. if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { bufref_T bufref; set_bufref(&bufref, buf); @@ -4831,7 +4813,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) && (first_tabpage->tp_next == NULL || !had_tab)) { use_firstwin = true; } else { - win_close(wp, !buf_hide(buf) && !bufIsChanged(buf)); + win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false); // check if autocommands removed the next window if (!win_valid(wpnext)) { // start all over... @@ -4854,10 +4836,8 @@ void do_arg_all(int count, int forceit, int keep_tabs) goto_tabpage_tp(tpnext, true, true); } - /* - * Open a window for files in the argument list that don't have one. - * ARGCOUNT may change while doing this, because of autocommands. - */ + // Open a window for files in the argument list that don't have one. + // ARGCOUNT may change while doing this, because of autocommands. if (count > opened_len || count <= 0) { count = opened_len; } @@ -4912,9 +4892,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) autocmd_no_leave--; } - /* - * edit file "i" - */ + // edit file "i" curwin->w_arg_idx = i; if (i == 0) { new_curwin = curwin; @@ -4966,15 +4944,13 @@ void do_arg_all(int count, int forceit, int keep_tabs) xfree(opened); } -/// @return true if "buf" is a prompt buffer. +/// @return true if "buf" is a prompt buffer. bool bt_prompt(buf_T *buf) { return buf != NULL && buf->b_p_bt[0] == 'p'; } -/* - * Open a window for a number of buffers. - */ +/// Open a window for a number of buffers. void ex_buffer_all(exarg_T *eap) { buf_T *buf; @@ -5002,10 +4978,8 @@ void ex_buffer_all(exarg_T *eap) setpcmark(); - /* - * Close superfluous windows (two windows for the same buffer). - * Also close windows that are not full-width. - */ + // Close superfluous windows (two windows for the same buffer). + // Also close windows that are not full-width. if (had_tab > 0) { goto_tabpage_tp(first_tabpage, true, true); } @@ -5015,14 +4989,14 @@ void ex_buffer_all(exarg_T *eap) wpnext = wp->w_next; if ((wp->w_buffer->b_nwindows > 1 || ((cmdmod.split & WSP_VERT) - ? wp->w_height + wp->w_status_height < Rows - p_ch - - tabline_height() + ? wp->w_height + wp->w_hsep_height + wp->w_status_height < Rows - p_ch + - tabline_height() - global_stl_height() : wp->w_width != Columns) || (had_tab > 0 && wp != firstwin)) && !ONE_WINDOW && !(wp->w_closing || wp->w_buffer->b_locked > 0)) { - win_close(wp, false); + win_close(wp, false, false); wpnext = firstwin; // just in case an autocommand does // something strange with windows tpnext = first_tabpage; // start all over... @@ -5039,7 +5013,6 @@ void ex_buffer_all(exarg_T *eap) goto_tabpage_tp(tpnext, true, true); } - // // Go through the buffer list. When a buffer doesn't have a window yet, // open one. Otherwise move the window to the right position. // Watch out for autocommands that delete buffers or windows! @@ -5103,7 +5076,7 @@ void ex_buffer_all(exarg_T *eap) enter_cleanup(&cs); // User selected Quit at ATTENTION prompt; close this window. - win_close(curwin, true); + win_close(curwin, true, false); open_wins--; swap_exists_action = SEA_NONE; swap_exists_did_quit = true; @@ -5135,9 +5108,7 @@ void ex_buffer_all(exarg_T *eap) win_enter(firstwin, false); // back to first window autocmd_no_leave--; - /* - * Close superfluous windows. - */ + // Close superfluous windows. for (wp = lastwin; open_wins > count;) { r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) || autowrite(wp->w_buffer, false) == OK); @@ -5145,7 +5116,7 @@ void ex_buffer_all(exarg_T *eap) // BufWrite Autocommands made the window invalid, start over wp = lastwin; } else if (r) { - win_close(wp, !buf_hide(wp->w_buffer)); + win_close(wp, !buf_hide(wp->w_buffer), false); open_wins--; wp = lastwin; } else { @@ -5158,15 +5129,13 @@ void ex_buffer_all(exarg_T *eap) } -/* - * do_modelines() - process mode lines for the current file - * - * "flags" can be: - * OPT_WINONLY only set options local to window - * OPT_NOWIN don't set options local to window - * - * Returns immediately if the "ml" option isn't set. - */ +/// do_modelines() - process mode lines for the current file +/// +/// @param flags +/// OPT_WINONLY only set options local to window +/// OPT_NOWIN don't set options local to window +/// +/// Returns immediately if the "ml" option isn't set. void do_modelines(int flags) { linenr_T lnum; @@ -5272,10 +5241,8 @@ static int chk_modeline(linenr_T lnum, int flags) break; } - /* - * Find end of set command: ':' or end of line. - * Skip over "\:", replacing it with ":". - */ + // Find end of set command: ':' or end of line. + // Skip over "\:", replacing it with ":". for (e = s; *e != ':' && *e != NUL; e++) { if (e[0] == '\\' && e[1] == ':') { STRMOVE(e, e + 1); @@ -5285,13 +5252,11 @@ static int chk_modeline(linenr_T lnum, int flags) end = true; } - /* - * If there is a "set" command, require a terminating ':' and - * ignore the stuff after the ':'. - * "vi:set opt opt opt: foo" -- foo not interpreted - * "vi:opt opt opt: foo" -- foo interpreted - * Accept "se" for compatibility with Elvis. - */ + // If there is a "set" command, require a terminating ':' and + // ignore the stuff after the ':'. + // "vi:set opt opt opt: foo" -- foo not interpreted + // "vi:opt opt opt: foo" -- foo interpreted + // Accept "se" for compatibility with Elvis. if (STRNCMP(s, "set ", (size_t)4) == 0 || STRNCMP(s, "se ", (size_t)3) == 0) { if (*e != ':') { // no terminating ':'? @@ -5330,36 +5295,36 @@ static int chk_modeline(linenr_T lnum, int flags) return retval; } -// Return true if "buf" is a help buffer. +/// @return true if "buf" is a help buffer. bool bt_help(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_help; } -// Return true if "buf" is a normal buffer, 'buftype' is empty. +/// @return true if "buf" is a normal buffer, 'buftype' is empty. bool bt_normal(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_p_bt[0] == NUL; } -// Return true if "buf" is the quickfix buffer. +/// @return true if "buf" is the quickfix buffer. bool bt_quickfix(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_p_bt[0] == 'q'; } -// Return true if "buf" is a terminal buffer. +/// @return true if "buf" is a terminal buffer. bool bt_terminal(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_p_bt[0] == 't'; } -// Return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" -// buffer. This means the buffer name is not a file name. +/// @return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" / +/// buffer. This means the buffer name is not a file name. bool bt_nofile(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5369,8 +5334,8 @@ bool bt_nofile(const buf_T *const buf) || buf->b_p_bt[0] == 'p'); } -// Return true if "buf" is a "nowrite", "nofile", "terminal" or "prompt" -// buffer. +/// @return true if "buf" is a "nowrite", "nofile", "terminal" or "prompt" +/// buffer. bool bt_dontwrite(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5389,8 +5354,8 @@ bool bt_dontwrite_msg(const buf_T *const buf) return false; } -// Return true if the buffer should be hidden, according to 'hidden', ":hide" -// and 'bufhidden'. +/// @return true if the buffer should be hidden, according to 'hidden', ":hide" +/// and 'bufhidden'. bool buf_hide(const buf_T *const buf) { // 'bufhidden' overrules 'hidden' and ":hide", check it first @@ -5405,23 +5370,17 @@ bool buf_hide(const buf_T *const buf) return p_hid || cmdmod.hide; } -/* - * Return special buffer name. - * Returns NULL when the buffer has a normal file name. - */ +/// @return special buffer name or +/// NULL when the buffer has a normal file name. char_u *buf_spname(buf_T *buf) { if (bt_quickfix(buf)) { - 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. - if (find_win_for_buf(buf, &win, &tp) && win->w_llist_ref != NULL) { - return (char_u *)_(msg_loclist); - } else { + // Differentiate between the quickfix and location list buffers using + // the buffer number stored in the global quickfix stack. + if (buf->b_fnum == qf_stack_get_bufnr()) { return (char_u *)_(msg_qflist); } + return (char_u *)_(msg_loclist); } // There is no _file_ when 'buftype' is "nofile", b_sfname // contains the name as specified by the user. @@ -5449,7 +5408,7 @@ char_u *buf_spname(buf_T *buf) /// @param[out] wp stores the found window /// @param[out] tp stores the found tabpage /// -/// @return true if a window was found for the buffer. +/// @return true if a window was found for the buffer. bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) { *wp = NULL; @@ -5464,43 +5423,156 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) return false; } -int buf_signcols(buf_T *buf) +static int buf_signcols_inner(buf_T *buf, int maximum) { - if (!buf->b_signcols_valid) { - sign_entry_T *sign; // a sign in the sign list - int signcols = 0; - int linesum = 0; - linenr_T curline = 0; - - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->se_lnum > curline) { - if (linesum > signcols) { - signcols = linesum; + sign_entry_T *sign; // a sign in the sign list + int signcols = 0; + int linesum = 0; + linenr_T curline = 0; + + buf->b_signcols.sentinel = 0; + + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (sign->se_lnum > curline) { + // Counted all signs, now add extmark signs + if (curline > 0) { + linesum += decor_signcols(buf, &decor_state, (int)curline-1, (int)curline-1, + maximum-linesum); + } + curline = sign->se_lnum; + if (linesum > signcols) { + signcols = linesum; + buf->b_signcols.sentinel = curline; + if (signcols >= maximum) { + return maximum; } - curline = sign->se_lnum; - linesum = 0; - } - if (sign->se_has_text_or_icon) { - linesum++; } + linesum = 0; } - if (linesum > signcols) { - signcols = linesum; + if (sign->se_has_text_or_icon) { + linesum++; } + } + if (curline > 0) { + linesum += decor_signcols(buf, &decor_state, (int)curline-1, (int)curline-1, maximum-linesum); + } + if (linesum > signcols) { + signcols = linesum; + if (signcols >= maximum) { + return maximum; + } + } + + // Check extmarks between signs + linesum = decor_signcols(buf, &decor_state, 0, (int)buf->b_ml.ml_line_count-1, maximum); + + if (linesum > signcols) { + signcols = linesum; + buf->b_signcols.sentinel = curline; + if (signcols >= maximum) { + return maximum; + } + } + + return signcols; +} + +/// Invalidate the signcolumn if needed after deleting +/// signs between line1 and line2 (inclusive). +/// +/// @param buf buffer to check +/// @param line1 start of region being deleted +/// @param line2 end of region being deleted +void buf_signcols_del_check(buf_T *buf, linenr_T line1, linenr_T line2) +{ + if (!buf->b_signcols.valid) { + return; + } + + if (!buf->b_signcols.sentinel) { + buf->b_signcols.valid = false; + return; + } + + linenr_T sent = buf->b_signcols.sentinel; + + if (sent >= line1 && sent <= line2) { + // Only invalidate when removing signs at the sentinel line. + buf->b_signcols.valid = false; + } +} + +/// Re-calculate the signcolumn after adding a sign. +/// +/// @param buf buffer to check +/// @param added sign being added +void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) +{ + if (!buf->b_signcols.valid) { + return; + } + + if (!added || !buf->b_signcols.sentinel) { + buf->b_signcols.valid = false; + return; + } + + if (added->se_lnum == buf->b_signcols.sentinel) { + if (buf->b_signcols.size == buf->b_signcols.max) { + buf->b_signcols.max++; + } + buf->b_signcols.size++; + redraw_buf_later(buf, NOT_VALID); + return; + } + + sign_entry_T *s; + + // Get first sign for added lnum + for (s = added; s->se_prev && s->se_lnum == s->se_prev->se_lnum; s = s->se_prev) {} + + // Count signs for lnum + int linesum = 1; + for (; s->se_next && s->se_lnum == s->se_next->se_lnum; s = s->se_next) { + linesum++; + } + linesum += decor_signcols(buf, &decor_state, (int)s->se_lnum-1, (int)s->se_lnum-1, + SIGN_SHOW_MAX-linesum); + + if (linesum > buf->b_signcols.size) { + buf->b_signcols.size = linesum; + buf->b_signcols.max = linesum; + buf->b_signcols.sentinel = added->se_lnum; + redraw_buf_later(buf, NOT_VALID); + } +} + +int buf_signcols(buf_T *buf, int maximum) +{ + // The maximum can be determined from 'signcolumn' which is window scoped so + // need to invalidate signcols if the maximum is greater than the previous + // maximum. + if (maximum > buf->b_signcols.max) { + buf->b_signcols.valid = false; + } + + if (!buf->b_signcols.valid) { + int signcols = buf_signcols_inner(buf, maximum); // Check if we need to redraw - if (signcols != buf->b_signcols) { - buf->b_signcols = signcols; + if (signcols != buf->b_signcols.size) { + buf->b_signcols.size = signcols; + buf->b_signcols.max = maximum; redraw_buf_later(buf, NOT_VALID); } - buf->b_signcols_valid = true; + buf->b_signcols.valid = true; } - return buf->b_signcols; + return buf->b_signcols.size; } -// Get "buf->b_fname", use "[No Name]" if it is NULL. +/// 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 { @@ -5510,9 +5582,7 @@ char_u *buf_get_fname(const buf_T *buf) return buf->b_fname; } -/* - * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. - */ +/// Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. void set_buflisted(int on) { if (on != curbuf->b_p_bl) { @@ -5530,7 +5600,7 @@ void set_buflisted(int on) /// /// @param buf buffer to check /// -/// @return true if the buffer's contents have changed +/// @return true if the buffer's contents have changed bool buf_contents_changed(buf_T *buf) FUNC_ATTR_NONNULL_ALL { @@ -5588,7 +5658,7 @@ void wipe_buffer(buf_T *buf, bool aucmd) // Don't trigger BufDelete autocommands here. block_autocmds(); } - close_buffer(NULL, buf, DOBUF_WIPE, false); + close_buffer(NULL, buf, DOBUF_WIPE, false, true); if (!aucmd) { unblock_autocmds(); } @@ -5611,4 +5681,3 @@ void buf_open_scratch(handle_T bufnr, char *bufname) set_option_value("swf", 0L, NULL, OPT_LOCAL); RESET_BINDING(curwin); } - diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index 9e2ca999e4..7f4bbcc9e5 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -23,7 +23,7 @@ enum getf_retvalues { GETFILE_ERROR = 1, // normal error GETFILE_NOT_WRITTEN = 2, // "not written" error GETFILE_SAME_FILE = 0, // success, same file - GETFILE_OPEN_OTHER = -1, // success, opened another file + GETFILE_OPEN_OTHER = (-1), // success, opened another file GETFILE_UNUSED = 8, }; @@ -58,9 +58,10 @@ enum dobuf_start_values { // flags for buf_freeall() enum bfa_values { - BFA_DEL = 1, // buffer is going to be deleted - BFA_WIPE = 2, // buffer is going to be wiped out - BFA_KEEP_UNDO = 4, // do not free undo information + BFA_DEL = 1, // buffer is going to be deleted + BFA_WIPE = 2, // buffer is going to be wiped out + BFA_KEEP_UNDO = 4, // do not free undo information + BFA_IGNORE_ABORT = 8, // do not abort for aborting() }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 49e527e98b..375bebc5ac 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -90,7 +90,6 @@ typedef struct { #define BF_NEW_W 0x20 // Warned for BF_NEW and file created #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 @@ -204,6 +203,10 @@ typedef struct { #define w_p_nu w_onebuf_opt.wo_nu // 'number' int wo_rnu; #define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber' + char_u *wo_ve; +#define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit' + unsigned wo_ve_flags; +#define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit' long wo_nuw; #define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth' int wo_wfh; @@ -352,6 +355,7 @@ struct mapblock { char_u *m_keys; // mapped from, lhs char_u *m_str; // mapped to, rhs char_u *m_orig_str; // rhs as entered by the user + LuaRef m_luaref; // lua function reference as rhs int m_keylen; // strlen(m_keys) int m_mode; // valid mode int m_noremap; // if non-zero no re-mapping for m_str @@ -359,6 +363,7 @@ struct mapblock { char m_nowait; // <nowait> used char m_expr; // <expr> used, m_str is an expression sctx_T m_script_ctx; // SCTX where map was defined + char *m_desc; // description of keymap }; /// Used for highlighting in the status line. @@ -581,7 +586,9 @@ struct file_buffer { // where invoked long b_mtime; // last change time of original file + long b_mtime_ns; // nanoseconds of last change time long b_mtime_read; // last change time when reading + long b_mtime_read_ns; // nanoseconds of last read time uint64_t b_orig_size; // size of original file in bytes int b_orig_mode; // mode of original file time_t b_last_used; // time when the buffer was last used; used @@ -654,7 +661,7 @@ struct file_buffer { // flags for use of ":lmap" and IM control long b_p_iminsert; // input mode for insert long b_p_imsearch; // input mode for search -#define B_IMODE_USE_INSERT -1 // Use b_p_iminsert value for search +#define B_IMODE_USE_INSERT (-1) // Use b_p_iminsert value for search #define B_IMODE_NONE 0 // Input via none #define B_IMODE_LMAP 1 // Input via langmap #define B_IMODE_LAST 1 @@ -689,6 +696,7 @@ struct file_buffer { char_u *b_p_cino; ///< 'cinoptions' char_u *b_p_cink; ///< 'cinkeys' char_u *b_p_cinw; ///< 'cinwords' + char_u *b_p_cinsd; ///< 'cinscopedecls' char_u *b_p_com; ///< 'comments' char_u *b_p_cms; ///< 'commentstring' char_u *b_p_cpt; ///< 'complete' @@ -854,8 +862,12 @@ struct file_buffer { // may use a different synblock_T. sign_entry_T *b_signlist; // list of placed signs - int b_signcols; // last calculated number of sign columns - bool b_signcols_valid; // calculated sign columns is valid + struct { + int size; // last calculated number of sign columns + bool valid; // calculated sign columns is valid + linenr_T sentinel; // a line number which is holding up the signcolumn + int max; // Maximum value size is valid for. + } b_signcols; Terminal *terminal; // Terminal instance associated with the buffer @@ -864,9 +876,9 @@ struct file_buffer { int b_mapped_ctrl_c; // modes where CTRL-C is mapped MarkTree b_marktree[1]; - Map(uint64_t, ExtmarkItem) b_extmark_index[1]; - Map(uint64_t, ExtmarkNs) b_extmark_ns[1]; // extmark namespaces + Map(uint32_t, uint32_t) b_extmark_ns[1]; // extmark namespaces size_t b_virt_line_blocks; // number of virt_line blocks + size_t b_signs; // number of sign extmarks // array of channel_id:s which have asked to receive updates for this // buffer. @@ -1200,6 +1212,8 @@ struct window_S { colnr_T w_old_visual_col; ///< last known start of visual part colnr_T w_old_curswant; ///< last known value of Curswant + linenr_T w_last_cursor_lnum_rnu; ///< cursor lnum when 'rnu' was last redrawn + // 'listchars' characters. Defaults set in set_chars_option(). struct { int eol; @@ -1220,7 +1234,13 @@ struct window_S { struct { int stl; int stlnc; + int horiz; + int horizup; + int horizdown; int vert; + int vertleft; + int vertright; + int verthoriz; int fold; int foldopen; ///< when fold is open int foldclosed; ///< when fold is closed @@ -1249,12 +1269,11 @@ 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 + // four fields that are only used when there is a WinScrolled autocommand + linenr_T w_last_topline; ///< last known value for w_topline + colnr_T w_last_leftcol; ///< last known value for w_leftcol + int w_last_width; ///< last known value for w_width + int w_last_height; ///< last known value for w_height // // Layout of the window in the screen. @@ -1266,7 +1285,8 @@ struct window_S { int w_status_height; // number of status lines (0 or 1) int w_wincol; // Leftmost column of window in screen. int w_width; // Width of window, excluding separation. - int w_vsep_width; // Number of separator columns (0 or 1). + int w_hsep_height; // Number of horizontal separator rows (0 or 1) + int w_vsep_width; // Number of vertical separator columns (0 or 1). pos_save_T w_save_cursor; // backup of cursor pos and topline // inner size of window, which can be overridden by external UI @@ -1346,6 +1366,7 @@ struct window_S { // recomputed int w_nrwidth; // width of 'number' and 'relativenumber' // column being used + int w_scwidth; // width of 'signcolumn' /* * === end of cached values === @@ -1399,7 +1420,7 @@ struct window_S { int w_briopt_list; // additional indent for lists // transform a pointer to a "onebuf" option into a "allbuf" option -#define GLOBAL_WO(p) ((char *)p + sizeof(winopt_T)) +#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) long w_scbind_pos; diff --git a/src/nvim/change.c b/src/nvim/change.c index c52d992fbe..024969415d 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -19,7 +19,6 @@ #include "nvim/indent_c.h" #include "nvim/mark.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" @@ -43,7 +42,7 @@ /// Careful: may trigger autocommands that reload the buffer. void change_warning(buf_T *buf, int col) { - static char *w_readonly = N_("W10: Warning: Changing a readonly file"); + static const char *w_readonly = N_("W10: Warning: Changing a readonly file"); if (buf->b_did_warn == false && curbufIsChanged() == 0 @@ -131,7 +130,7 @@ void changed_internal(void) curbuf->b_changed = true; curbuf->b_changed_invalid = true; ml_setflags(curbuf); - check_status(curbuf); + redraw_buf_status_later(curbuf); redraw_tabline = true; need_maketitle = true; // set window title later } @@ -141,10 +140,6 @@ void changed_internal(void) /// Careful: may trigger autocommands that reload the buffer. static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra) { - int i; - pos_T *p; - int add; - // mark the buffer as modified changed(); @@ -159,13 +154,14 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra // Create a new entry if a new undo-able change was started or we // don't have an entry yet. if (curbuf->b_new_change || curbuf->b_changelistlen == 0) { + int add; if (curbuf->b_changelistlen == 0) { add = true; } else { // Don't create a new entry when the line number is the same // as the last one and the column is not too far away. Avoids // creating many entries for typing "xxxxx". - p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark; + pos_T *p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark; if (p->lnum != lnum) { add = true; } else { @@ -224,26 +220,27 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra // values for the cursor. // Update the folds for this window. Can't postpone this, because // a following operator might work on the whole fold: ">>dd". - foldUpdate(wp, lnum, lnume + xtra - 1); + linenr_T last = lnume + xtra - 1; // last line after the change + foldUpdate(wp, lnum, last); // The change may cause lines above or below the change to become // included in a fold. Set lnum/lnume to the first/last line that // might be displayed differently. // Set w_cline_folded here as an efficient way to update it when - // inserting lines just above a closed fold. */ + // inserting lines just above a closed fold. bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL); if (wp->w_cursor.lnum == lnum) { wp->w_cline_folded = folded; } - folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL); - if (wp->w_cursor.lnum == lnume) { + folded = hasFoldingWin(wp, last, NULL, &last, false, NULL); + if (wp->w_cursor.lnum == last) { wp->w_cline_folded = folded; } // If the changed line is in a range of previously folded lines, // compare with the first line in that range. if (wp->w_cursor.lnum <= lnum) { - i = find_wl_entry(wp, lnum); + int i = find_wl_entry(wp, lnum); if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) { changed_line_abv_curs_win(wp); } @@ -264,7 +261,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra // For entries below the change: Correct the lnums for // inserted/deleted lines. Makes it possible to stop displaying // after the change. - for (i = 0; i < wp->w_lines_valid; i++) { + for (int i = 0; i < wp->w_lines_valid; i++) { if (wp->w_lines[i].wl_valid) { if (wp->w_lines[i].wl_lnum >= lnum) { if (wp->w_lines[i].wl_lnum < lnume) { @@ -289,9 +286,11 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra set_topline(wp, wp->w_topline); } - // Relative numbering may require updating more. - if (wp->w_p_rnu) { - redraw_later(wp, SOME_VALID); + // If lines have been added or removed, relative numbering always + // requires a redraw. + if (wp->w_p_rnu && xtra != 0) { + wp->w_last_cursor_lnum_rnu = 0; + redraw_later(wp, VALID); } // Cursor line highlighting probably need to be updated with @@ -352,12 +351,10 @@ void changed_bytes(linenr_T lnum, colnr_T col) // Diff highlighting in other diff windows may need to be updated too. if (curwin->w_p_diff) { - linenr_T wlnum; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { redraw_later(wp, VALID); - wlnum = diff_lnum_win(lnum, wp); + linenr_T wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changedOneline(wp->w_buffer, wlnum); } @@ -413,7 +410,7 @@ void deleted_lines(linenr_T lnum, long count) /// be triggered to display the cursor. void deleted_lines_mark(linenr_T lnum, long count) { - // if we deleted the entire buffer, we need to implicity add a new empty line + // if we deleted the entire buffer, we need to implicitly add a new empty line bool made_empty = (count > 0) && curbuf->b_ml.ml_flags & ML_EMPTY; mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, @@ -517,7 +514,7 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) if (ff) { save_file_ff(buf); } - check_status(buf); + redraw_buf_status_later(buf); redraw_tabline = true; need_maketitle = true; // set window title later buf_inc_changedtick(buf); @@ -625,7 +622,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } } - char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen)); + char_u *newp = xmalloc(linelen + newlen - oldlen); // Copy bytes before the cursor. if (col > 0) { @@ -673,21 +670,18 @@ void ins_char_bytes(char_u *buf, size_t charlen) /// Caller must have prepared for undo. void ins_str(char_u *s) { - char_u *oldp, *newp; int newlen = (int)STRLEN(s); - int oldlen; - colnr_T col; linenr_T lnum = curwin->w_cursor.lnum; if (virtual_active() && curwin->w_cursor.coladd > 0) { coladvance_force(getviscol()); } - col = curwin->w_cursor.col; - oldp = ml_get(lnum); - oldlen = (int)STRLEN(oldp); + colnr_T col = curwin->w_cursor.col; + char_u *oldp = ml_get(lnum); + int oldlen = (int)STRLEN(oldp); - newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1); + char_u *newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1); if (col > 0) { memmove(newp, oldp, (size_t)col); } @@ -719,13 +713,9 @@ int del_char(bool fixpos) int del_chars(long count, int fixpos) { int bytes = 0; - long i; - char_u *p; - int l; - - p = get_cursor_pos_ptr(); - for (i = 0; i < count && *p != NUL; i++) { - l = utfc_ptr2len(p); + char_u *p = get_cursor_pos_ptr(); + for (long i = 0; i < count && *p != NUL; i++) { + int l = utfc_ptr2len(p); bytes += l; p += l; } @@ -768,12 +758,11 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) if (p_deco && use_delcombine && utfc_ptr2len(oldp + col) >= count) { int cc[MAX_MCO]; - int n; (void)utfc_ptr2char(oldp + col, cc); if (cc[0] != NUL) { // Find the last composing char, there can be several. - n = col; + int n = col; do { col = n; count = utf_ptr2len(oldp + n); @@ -790,7 +779,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // fixpos is true, we don't want to end up positioned at the NUL, // unless "restart_edit" is set or 'virtualedit' contains "onemore". if (col > 0 && fixpos && restart_edit == 0 - && (ve_flags & VE_ONEMORE) == 0) { + && (get_ve_flags() & VE_ONEMORE) == 0) { curwin->w_cursor.col--; curwin->w_cursor.coladd = 0; curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); @@ -828,23 +817,18 @@ int copy_indent(int size, char_u *src) { char_u *p = NULL; char_u *line = NULL; - char_u *s; - int todo; int ind_len; int line_len = 0; int tab_pad; - int ind_done; - int round; - int ind_col; // Round 1: compute the number of characters needed for the indent // Round 2: copy the characters. - for (round = 1; round <= 2; round++) { - todo = size; + for (int round = 1; round <= 2; round++) { + int todo = size; ind_len = 0; - ind_done = 0; - ind_col = 0; - s = src; + int ind_done = 0; + int ind_col = 0; + char_u *s = src; // Count/copy the usable portion of the source line. while (todo > 0 && ascii_iswhite(*s)) { @@ -953,11 +937,13 @@ int copy_indent(int size, char_u *src) /// /// "second_line_indent": indent for after ^^D in Insert mode or if flag /// OPENLINE_COM_LIST +/// "did_do_comment" is set to true when intentionally putting the comment +/// leader in fromt of the new line. /// /// @param dir FORWARD or BACKWARD /// /// @return true on success, false on failure -int open_line(int dir, int flags, int second_line_indent) +int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) { char_u *next_line = NULL; // copy of the next line char_u *p_extra = NULL; // what goes to next line @@ -970,6 +956,7 @@ int open_line(int dir, int flags, int second_line_indent) bool retval = false; // return value int extra_len = 0; // length of p_extra string int lead_len; // length of comment leader + int comment_start = 0; // start index of the comment leader char_u *lead_flags; // position in 'comments' for comment leader char_u *leader = NULL; // copy of comment leader char_u *allocated = NULL; // allocated memory @@ -978,6 +965,7 @@ int open_line(int dir, int flags, int second_line_indent) pos_T *pos; bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin && *curbuf->b_p_inde == NUL); + bool do_cindent; bool no_si = false; // reset did_si afterwards int first_char = NUL; // init for GCC int vreplace_mode; @@ -1061,7 +1049,6 @@ int open_line(int dir, int flags, int second_line_indent) if (!trunc_line && do_si && *saved_line != NUL && (p_extra == NULL || first_char != '{')) { char_u *ptr; - char_u last_char; old_cursor = curwin->w_cursor; ptr = saved_line; @@ -1071,8 +1058,7 @@ int open_line(int dir, int flags, int second_line_indent) lead_len = 0; } if (dir == FORWARD) { - // Skip preprocessor directives, unless they are - // recognised as comments. + // Skip preprocessor directives, unless they are recognised as comments. if (lead_len == 0 && ptr[0] == '#') { while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) { ptr = ml_get(--curwin->w_cursor.lnum); @@ -1115,7 +1101,7 @@ int open_line(int dir, int flags, int second_line_indent) while (p > ptr && ascii_iswhite(*p)) { p--; } - last_char = *p; + char_u last_char = *p; // find the character just before the '{' or ';' if (last_char == '{' || last_char == ';') { @@ -1190,11 +1176,30 @@ int open_line(int dir, int flags, int second_line_indent) did_ai = true; } + // May do indenting after opening a new line. + do_cindent = !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL) + && in_cinkeys(dir == FORWARD ? KEY_OPEN_FORW : KEY_OPEN_BACK, + ' ', linewhite(curwin->w_cursor.lnum)); + // Find out if the current line starts with a comment leader. // This may then be inserted in front of the new line. end_comment_pending = NUL; if (flags & OPENLINE_DO_COM) { lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true); + if (lead_len == 0 && curbuf->b_p_cin && do_cindent && dir == FORWARD) { + // Check for a line comment after code. + comment_start = check_linecomment(saved_line); + if (comment_start != MAXCOL) { + lead_len = get_leader_len(saved_line + comment_start, + &lead_flags, false, true); + if (lead_len != 0) { + lead_len += comment_start; + if (did_do_comment != NULL) { + *did_do_comment = true; + } + } + } + } } else { lead_len = 0; } @@ -1350,6 +1355,13 @@ int open_line(int dir, int flags, int second_line_indent) STRLCPY(leader, saved_line, lead_len + 1); + // TODO(vim): handle multi-byte and double width chars + for (int li = 0; li < comment_start; li++) { + if (!ascii_iswhite(leader[li])) { + leader[li] = ' '; + } + } + // Replace leader with lead_repl, right or left adjusted if (lead_repl != NULL) { int c = 0; @@ -1759,13 +1771,7 @@ int open_line(int dir, int flags, int second_line_indent) ai_col = (colnr_T)getwhitecols_curline(); } // May do indenting after opening a new line. - if (!p_paste - && (curbuf->b_p_cin - || *curbuf->b_p_inde != NUL - ) - && in_cinkeys(dir == FORWARD - ? KEY_OPEN_FORW - : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) { + if (do_cindent) { do_c_expr_indent(); ai_col = (colnr_T)getwhitecols_curline(); } @@ -1863,3 +1869,287 @@ void del_lines(long nlines, bool undo) // adjust marks, mark the buffer as changed and prepare for displaying deleted_lines_mark(first, n); } + +/// Returns the length in bytes of the prefix of the given string which introduces a comment. +/// +/// If this string is not a comment then 0 is returned. +/// When "flags" is not NULL, it is set to point to the flags of the recognized comment leader. +/// "backward" must be true for the "O" command. +/// If "include_space" is set, include trailing whitespace while calculating the length. +int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_space) +{ + int j; + int got_com = false; + char_u part_buf[COM_MAX_LEN]; // buffer for one option part + char_u *string; // pointer to comment string + char_u *list; + int middle_match_len = 0; + char_u *prev_list; + char_u *saved_flags = NULL; + + int result = 0; + int i = 0; + while (ascii_iswhite(line[i])) { // leading white space is ignored + i++; + } + + // Repeat to match several nested comment strings. + while (line[i] != NUL) { + // scan through the 'comments' option for a match + int found_one = false; + for (list = curbuf->b_p_com; *list;) { + // Get one option part into part_buf[]. Advance "list" to next + // one. Put "string" at start of string. + if (!got_com && flags != NULL) { + *flags = list; // remember where flags started + } + prev_list = list; + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); + if (string == NULL) { // missing ':', ignore this part + continue; + } + *string++ = NUL; // isolate flags from string + + // If we found a middle match previously, use that match when this + // is not a middle or end. + if (middle_match_len != 0 + && vim_strchr(part_buf, COM_MIDDLE) == NULL + && vim_strchr(part_buf, COM_END) == NULL) { + break; + } + + // When we already found a nested comment, only accept further + // nested comments. + if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) { + continue; + } + + // When 'O' flag present and using "O" command skip this one. + if (backward && vim_strchr(part_buf, COM_NOBACK) != NULL) { + continue; + } + + // Line contents and string must match. + // When string starts with white space, must have some white space + // (but the amount does not need to match, there might be a mix of + // TABs and spaces). + if (ascii_iswhite(string[0])) { + if (i == 0 || !ascii_iswhite(line[i - 1])) { + continue; // missing white space + } + while (ascii_iswhite(string[0])) { + string++; + } + } + for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) { + } + if (string[j] != NUL) { + continue; // string doesn't match + } + // When 'b' flag used, there must be white space or an + // end-of-line after the string in the line. + if (vim_strchr(part_buf, COM_BLANK) != NULL + && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { + continue; + } + + // We have found a match, stop searching unless this is a middle + // comment. The middle comment can be a substring of the end + // comment in which case it's better to return the length of the + // end comment and its flags. Thus we keep searching with middle + // and end matches and use an end match if it matches better. + if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { + if (middle_match_len == 0) { + middle_match_len = j; + saved_flags = prev_list; + } + continue; + } + if (middle_match_len != 0 && j > middle_match_len) { + // Use this match instead of the middle match, since it's a + // longer thus better match. + middle_match_len = 0; + } + + if (middle_match_len == 0) { + i += j; + } + found_one = true; + break; + } + + if (middle_match_len != 0) { + // Use the previously found middle match after failing to find a + // match with an end. + if (!got_com && flags != NULL) { + *flags = saved_flags; + } + i += middle_match_len; + found_one = true; + } + + // No match found, stop scanning. + if (!found_one) { + break; + } + + result = i; + + // Include any trailing white space. + while (ascii_iswhite(line[i])) { + i++; + } + + if (include_space) { + result = i; + } + + // If this comment doesn't nest, stop here. + got_com = true; + if (vim_strchr(part_buf, COM_NEST) == NULL) { + break; + } + } + return result; +} + +/// Return the offset at which the last comment in line starts. If there is no +/// comment in the whole line, -1 is returned. +/// +/// When "flags" is not null, it is set to point to the flags describing the +/// recognized comment leader. +int get_last_leader_offset(char_u *line, char_u **flags) +{ + int result = -1; + int j; + int lower_check_bound = 0; + char_u *string; + char_u *com_leader; + char_u *com_flags; + char_u *list; + char_u part_buf[COM_MAX_LEN]; // buffer for one option part + + // Repeat to match several nested comment strings. + int i = (int)STRLEN(line); + while (--i >= lower_check_bound) { + // scan through the 'comments' option for a match + int found_one = false; + for (list = curbuf->b_p_com; *list;) { + char_u *flags_save = list; + + // Get one option part into part_buf[]. Advance list to next one. + // put string at start of string. + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); + if (string == NULL) { // If everything is fine, this cannot actually + // happen. + continue; + } + *string++ = NUL; // Isolate flags from string. + com_leader = string; + + // Line contents and string must match. + // When string starts with white space, must have some white space + // (but the amount does not need to match, there might be a mix of + // TABs and spaces). + if (ascii_iswhite(string[0])) { + if (i == 0 || !ascii_iswhite(line[i - 1])) { + continue; + } + while (ascii_iswhite(*string)) { + string++; + } + } + for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) { + // do nothing + } + if (string[j] != NUL) { + continue; + } + + // When 'b' flag used, there must be white space or an + // end-of-line after the string in the line. + if (vim_strchr(part_buf, COM_BLANK) != NULL + && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { + continue; + } + + if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { + // For a middlepart comment, only consider it to match if + // everything before the current position in the line is + // whitespace. Otherwise we would think we are inside a + // comment if the middle part appears somewhere in the middle + // of the line. E.g. for C the "*" appears often. + for (j = 0; j <= i && ascii_iswhite(line[j]); j++) { + } + if (j < i) { + continue; + } + } + + // We have found a match, stop searching. + found_one = true; + + if (flags) { + *flags = flags_save; + } + com_flags = flags_save; + + break; + } + + if (found_one) { + char_u part_buf2[COM_MAX_LEN]; // buffer for one option part + int len1, len2, off; + + result = i; + // If this comment nests, continue searching. + if (vim_strchr(part_buf, COM_NEST) != NULL) { + continue; + } + + lower_check_bound = i; + + // Let's verify whether the comment leader found is a substring + // of other comment leaders. If it is, let's adjust the + // lower_check_bound so that we make sure that we have determined + // the comment leader correctly. + + while (ascii_iswhite(*com_leader)) { + com_leader++; + } + len1 = (int)STRLEN(com_leader); + + for (list = curbuf->b_p_com; *list;) { + char_u *flags_save = list; + + (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); + if (flags_save == com_flags) { + continue; + } + string = vim_strchr(part_buf2, ':'); + string++; + while (ascii_iswhite(*string)) { + string++; + } + len2 = (int)STRLEN(string); + if (len2 == 0) { + continue; + } + + // Now we have to verify whether string ends with a substring + // beginning the com_leader. + for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) { + off--; + if (!STRNCMP(string + off, com_leader, len2 - off)) { + if (i - off < lower_check_bound) { + lower_check_bound = i - off; + } + } + } + } + } + } + return result; +} diff --git a/src/nvim/change.h b/src/nvim/change.h index e1a1bfba17..e7c8a2b031 100644 --- a/src/nvim/change.h +++ b/src/nvim/change.h @@ -4,6 +4,13 @@ #include "nvim/buffer_defs.h" // for buf_T #include "nvim/pos.h" // for linenr_T +// flags for open_line() +#define OPENLINE_DELSPACES 1 // delete spaces after cursor +#define OPENLINE_DO_COM 2 // format comments +#define OPENLINE_KEEPTRAIL 4 // keep trailing spaces +#define OPENLINE_MARKFIX 8 // fix mark positions +#define OPENLINE_COM_LIST 16 // format comments with list/2nd line indent + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "change.h.generated.h" #endif diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 9662f6205f..ac6c9cd110 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -138,8 +138,14 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error) *error = (const char *)e_invstream; return false; } - api_free_luaref(chan->stream.internal.cb); - chan->stream.internal.cb = LUA_NOREF; + if (chan->term) { + api_free_luaref(chan->stream.internal.cb); + chan->stream.internal.cb = LUA_NOREF; + chan->stream.internal.closed = true; + terminal_close(chan->term, 0); + } else { + channel_decref(chan); + } break; default: @@ -536,7 +542,11 @@ size_t channel_send(uint64_t id, char *data, size_t len, bool data_owned, const } if (chan->streamtype == kChannelStreamInternal) { - if (!chan->term) { + if (chan->is_rpc) { + *error = _("Can't send raw data to rpc channel"); + goto retfree; + } + if (!chan->term || chan->stream.internal.closed) { *error = _("Can't send data to closed stream"); goto retfree; } @@ -613,7 +623,6 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, size_ } else { if (chan->term) { terminal_receive(chan->term, ptr, count); - terminal_flush_output(chan->term); } rbuffer_consumed(buf, count); @@ -821,15 +830,17 @@ static void set_info_event(void **argv) Channel *chan = argv[0]; event_T event = (event_T)(ptrdiff_t)argv[1]; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); Dictionary info = channel_info(chan->id); typval_T retval; (void)object_to_vim(DICTIONARY_OBJ(info), &retval, NULL); tv_dict_add_dict(dict, S_LEN("info"), retval.vval.v_dict); + tv_dict_set_keys_readonly(dict); apply_autocmds(event, NULL, NULL, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); api_free_dictionary(info); channel_decref(chan); } diff --git a/src/nvim/channel.h b/src/nvim/channel.h index 81b75e2d31..5cec5731eb 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -44,6 +44,7 @@ typedef struct { typedef struct { LuaRef cb; + bool closed; } InternalState; typedef struct { @@ -96,6 +97,8 @@ struct Channel { EXTERN PMap(uint64_t) channels INIT(= MAP_INIT); +EXTERN Callback on_print INIT(= CALLBACK_INIT); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "channel.h.generated.h" #endif diff --git a/src/nvim/charset.c b/src/nvim/charset.c index eb0903b594..ec1866e9cc 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -21,7 +21,6 @@ #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os_unix.h" @@ -160,7 +159,7 @@ int buf_init_chartab(buf_T *buf, int global) if ((*p == '^') && (p[1] != NUL)) { tilde = true; - ++p; + p++; } if (ascii_isdigit(*p)) { @@ -171,7 +170,7 @@ int buf_init_chartab(buf_T *buf, int global) c2 = -1; if ((*p == '-') && (p[1] != NUL)) { - ++p; + p++; if (ascii_isdigit(*p)) { c2 = getdigits_int((char_u **)&p, true, 0); @@ -218,9 +217,7 @@ int buf_init_chartab(buf_T *buf, int global) } } else if (i == 1) { // (re)set printable - // For double-byte we keep the cell width, so - // that we can detect it from the first byte. - if (((c < ' ') || (c > '~'))) { + if (c < ' ' || c > '~') { if (tilde) { g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) + ((dy_flags & DY_UHEX) ? 4 : 2)); @@ -246,7 +243,7 @@ int buf_init_chartab(buf_T *buf, int global) } } } - ++c; + c++; } c = *p; @@ -271,15 +268,12 @@ int buf_init_chartab(buf_T *buf, int global) /// @param bufsize void trans_characters(char_u *buf, int bufsize) { - int len; // length of string needing translation - int room; // room in buffer after string - char_u *trs; // translated character - int trs_len; // length of trs[] - - len = (int)STRLEN(buf); - room = bufsize - len; + char_u *trs; // translated character + int len = (int)STRLEN(buf); // length of string needing translation + int room = bufsize - len; // room in buffer after string while (*buf != 0) { + int trs_len; // length of trs[] // Assume a multi-byte character doesn't need translation. if ((trs_len = utfc_ptr2len(buf)) > 1) { len -= trs_len; @@ -295,7 +289,7 @@ void trans_characters(char_u *buf, int bufsize) memmove(buf + trs_len, buf + 1, (size_t)len); } memmove(buf, trs, (size_t)trs_len); - --len; + len--; } buf += trs_len; } @@ -431,9 +425,9 @@ char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) int len = orglen; #define GA_CHAR(i) ((char_u *)ga.ga_data)[i] -#define GA_PTR(i) ((char_u *)ga.ga_data + i) +#define GA_PTR(i) ((char_u *)ga.ga_data + (i)) #define STR_CHAR(i) (buf == NULL ? GA_CHAR(i) : buf[i]) -#define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + i) +#define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + (i)) // Copy "str" into "buf" or allocated memory, unmodified. if (buf == NULL) { @@ -540,7 +534,7 @@ char_u *transchar_buf(const buf_T *buf, int c) c = K_SECOND(c); } - if ((!chartab_initialized && (((c >= ' ') && (c <= '~')))) + if ((!chartab_initialized && (c >= ' ' && c <= '~')) || ((c <= 0xFF) && vim_isprintc_strict(c))) { // printable character transchar_charbuf[i] = (char_u)c; @@ -876,14 +870,11 @@ bool vim_isprintc_strict(int c) bool in_win_border(win_T *wp, colnr_T vcol) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { - int width1; // width of first line (after line number) - int width2; // width of further lines - if (wp->w_width_inner == 0) { // there is no border return false; } - width1 = wp->w_width_inner - win_col_off(wp); + int width1 = wp->w_width_inner - win_col_off(wp); // width of first line (after line number) if ((int)vcol < width1 - 1) { return false; @@ -892,7 +883,7 @@ bool in_win_border(win_T *wp, colnr_T vcol) if ((int)vcol == width1 - 1) { return true; } - width2 = width1 + win_col_off2(wp); + int width2 = width1 + win_col_off2(wp); // width of further lines if (width2 <= 0) { return false; @@ -914,27 +905,26 @@ bool in_win_border(win_T *wp, colnr_T vcol) /// @param end void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { - colnr_T vcol; char_u *ptr; // points to current char char_u *posptr; // points to char at pos->col - char_u *line; // start of the line int incr; int head; long *vts = wp->w_buffer->b_p_vts_array; int ts = (int)wp->w_buffer->b_p_ts; - int c; - vcol = 0; - line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); + colnr_T vcol = 0; + char_u *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); // start of the line if (pos->col == MAXCOL) { // continue until the NUL posptr = NULL; } else { - // Special check for an empty line, which can happen on exit, when - // ml_get_buf() always returns an empty string. - if (*ptr == NUL) { - pos->col = 0; + // In a few cases the position can be beyond the end of the line. + for (colnr_T i = 0; i < pos->col; i++) { + if (ptr[i] == NUL) { + pos->col = i; + break; + } } posptr = ptr + pos->col; posptr -= utf_head_off(line, posptr); @@ -950,7 +940,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en && !wp->w_p_bri) { for (;;) { head = 0; - c = *ptr; + int c = *ptr; // make sure we don't go past the end of the line if (c == NUL) { @@ -1067,19 +1057,16 @@ colnr_T getvcol_nolist(pos_T *posp) void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { colnr_T col; - colnr_T coladd; - colnr_T endadd; - char_u *ptr; if (virtual_active()) { // For virtual mode, only want one value getvcol(wp, pos, &col, NULL, NULL); - coladd = pos->coladd; - endadd = 0; + colnr_T coladd = pos->coladd; + colnr_T endadd = 0; // Cannot put the cursor on part of a wide character. - ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); + char_u *ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); if (pos->col < (colnr_T)STRLEN(ptr)) { int c = utf_ptr2char(ptr + pos->col); @@ -1315,9 +1302,9 @@ char_u *skiptowhite_esc(char_u *p) { while (*p != ' ' && *p != '\t' && *p != NUL) { if (((*p == '\\') || (*p == Ctrl_V)) && (*(p + 1) != NUL)) { - ++p; + p++; } - ++p; + p++; } return p; } @@ -1442,7 +1429,7 @@ bool vim_isblankline(char_u *lbuf) /// @param unptr Returns the unsigned result. /// @param maxlen Max length of string to check. /// @param strict If true, fail if the number has unexpected trailing -/// alpha-numeric chars: *len is set to 0 and nothing else is +/// alphanumeric chars: *len is set to 0 and nothing else is /// returned. void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen, @@ -1503,7 +1490,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, cons } else if ((what & (STR2NR_HEX | STR2NR_OCT | STR2NR_OOCT | STR2NR_BIN)) && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { - pre = ptr[1]; + pre = (char_u)ptr[1]; // Detect hexadecimal: 0x or 0X followed by hex digit. if ((what & STR2NR_HEX) && !STRING_ENDED(ptr + 2) @@ -1588,7 +1575,7 @@ vim_str2nr_hex: #undef PARSE_NUMBER vim_str2nr_proceed: - // Check for an alpha-numeric character immediately following, that is + // Check for an alphanumeric character immediately following, that is // most likely a typo. if (strict && ptr - (const char *)start != maxlen && ASCII_ISALNUM(*ptr)) { return; @@ -1687,7 +1674,7 @@ bool rem_backslash(const char_u *str) /// @param p void backslash_halve(char_u *p) { - for (; *p; ++p) { + for (; *p; p++) { if (rem_backslash(p)) { STRMOVE(p, p + 1); } diff --git a/src/nvim/charset.h b/src/nvim/charset.h index 47d89717e8..c4e5d9522b 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -14,8 +14,8 @@ /// @return Folded variant. #define CH_FOLD(c) \ utf_fold((sizeof(c) == sizeof(char)) \ - ?((int)(uint8_t)(c)) \ - :((int)(c))) + ? ((int)(uint8_t)(c)) \ + : ((int)(c))) /// Flags for vim_str2nr() typedef enum { diff --git a/src/nvim/context.c b/src/nvim/context.c index 614a3ce30e..9434b4e0ea 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -256,7 +256,8 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly) size_t cmd_len = sizeof("func! ") + STRLEN(name); char *cmd = xmalloc(cmd_len); snprintf(cmd, cmd_len, "func! %s", name); - String func_body = nvim_exec(cstr_as_string(cmd), true, &err); + String func_body = nvim_exec(VIML_INTERNAL_CALL, cstr_as_string(cmd), + true, &err); xfree(cmd); if (!ERROR_SET(&err)) { ADD(ctx->funcs, STRING_OBJ(func_body)); diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index e334fd166e..d924119fdf 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -14,8 +14,8 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" +#include "nvim/option.h" #include "nvim/plines.h" #include "nvim/screen.h" #include "nvim/state.h" @@ -25,9 +25,7 @@ # include "cursor.c.generated.h" #endif -/* - * Get the screen position of the cursor. - */ +/// @return the screen position of the cursor. int getviscol(void) { colnr_T x; @@ -36,9 +34,7 @@ int getviscol(void) return (int)x; } -/* - * Get the screen position of character col with a coladd in the cursor line. - */ +/// @return the screen position of character col with a coladd in the cursor line. int getviscol2(colnr_T col, colnr_T coladd) { colnr_T x; @@ -51,11 +47,9 @@ int getviscol2(colnr_T col, colnr_T coladd) return (int)x; } -/* - * Go to column "wcol", and add/insert white space as necessary to get the - * cursor in that column. - * The caller must have saved the cursor line for undo! - */ +/// Go to column "wcol", and add/insert white space as necessary to get the +/// cursor in that column. +/// The caller must have saved the cursor line for undo! int coladvance_force(colnr_T wcol) { int rc = coladvance2(&curwin->w_cursor, true, false, wcol); @@ -70,15 +64,13 @@ int coladvance_force(colnr_T wcol) return rc; } -/* - * Try to advance the Cursor to the specified screen column. - * If virtual editing: fine tune the cursor position. - * Note that all virtual positions off the end of a line should share - * a curwin->w_cursor.col value (n.b. this is equal to STRLEN(line)), - * beginning at coladd 0. - * - * return OK if desired column is reached, FAIL if not - */ +/// Try to advance the Cursor to the specified screen column. +/// If virtual editing: fine tune the cursor position. +/// Note that all virtual positions off the end of a line should share +/// a curwin->w_cursor.col value (n.b. this is equal to STRLEN(line)), +/// beginning at coladd 0. +/// +/// @return OK if desired column is reached, FAIL if not int coladvance(colnr_T wcol) { int rc = getvpos(&curwin->w_cursor, wcol); @@ -100,18 +92,16 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a { colnr_T wcol = wcol_arg; int idx; - char_u *ptr; - char_u *line; colnr_T col = 0; - int csize = 0; - int one_more; int head = 0; - one_more = (State & INSERT) - || restart_edit != NUL - || (VIsual_active && *p_sel != 'o') - || ((ve_flags & VE_ONEMORE) && wcol < MAXCOL); - line = ml_get_buf(curbuf, pos->lnum, false); + int one_more = (State & INSERT) + || (State & TERM_FOCUS) + || restart_edit != NUL + || (VIsual_active && *p_sel != 'o') + || ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL); + + char_u *line = ml_get_buf(curbuf, pos->lnum, false); if (wcol >= MAXCOL) { idx = (int)STRLEN(line) - 1 + one_more; @@ -120,11 +110,12 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a if ((addspaces || finetune) && !VIsual_active) { curwin->w_curswant = linetabsize(line) + one_more; if (curwin->w_curswant > 0) { - --curwin->w_curswant; + curwin->w_curswant--; } } } else { int width = curwin->w_width_inner - win_col_off(curwin); + int csize = 0; if (finetune && curwin->w_p_wrap @@ -138,15 +129,15 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a if (wcol / width > (colnr_T)csize / width && ((State & INSERT) == 0 || (int)wcol > csize + 1)) { - /* In case of line wrapping don't move the cursor beyond the - * right screen edge. In Insert mode allow going just beyond - * the last character (like what happens when typing and - * reaching the right window edge). */ + // In case of line wrapping don't move the cursor beyond the + // right screen edge. In Insert mode allow going just beyond + // the last character (like what happens when typing and + // reaching the right window edge). wcol = (csize / width + 1) * width - 1; } } - ptr = line; + char_u *ptr = line; while (col <= wcol && *ptr != NUL) { // Count a tab for what it's worth (if list mode not on) csize = win_lbr_chartabsize(curwin, line, ptr, col, &head); @@ -154,12 +145,10 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a col += csize; } idx = (int)(ptr - line); - /* - * Handle all the special cases. The virtual_active() check - * is needed to ensure that a virtual position off the end of - * a line has the correct indexing. The one_more comparison - * replaces an explicit add of one_more later on. - */ + // Handle all the special cases. The virtual_active() check + // is needed to ensure that a virtual position off the end of + // a line has the correct indexing. The one_more comparison + // replaces an explicit add of one_more later on. if (col > wcol || (!virtual_active() && one_more == 0)) { idx -= 1; // Don't count the chars from 'showbreak'. @@ -171,8 +160,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a && addspaces && wcol >= 0 && ((col != wcol && col != wcol + 1) || csize > 1)) { - /* 'virtualedit' is set: The difference between wcol and col is - * filled with spaces. */ + // 'virtualedit' is set: The difference between wcol and col is filled with spaces. if (line[idx] == NUL) { // Append spaces @@ -255,29 +243,23 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a return OK; } -/* - * Return in "pos" the position of the cursor advanced to screen column "wcol". - * return OK if desired column is reached, FAIL if not - */ +/// Return in "pos" the position of the cursor advanced to screen column "wcol". +/// +/// @return OK if desired column is reached, FAIL if not int getvpos(pos_T *pos, colnr_T wcol) { return coladvance2(pos, false, virtual_active(), wcol); } -/* - * Increment the cursor position. See inc() for return values. - */ +/// Increment the cursor position. See inc() for return values. int inc_cursor(void) { return inc(&curwin->w_cursor); } -/* - * dec(p) - * - * Decrement the line pointer 'p' crossing line boundaries as necessary. - * Return 1 when crossing a line, -1 when at start of file, 0 otherwise. - */ +/// Decrement the line pointer 'p' crossing line boundaries as necessary. +/// +/// @return 1 when crossing a line, -1 when at start of file, 0 otherwise. int dec_cursor(void) { return dec(&curwin->w_cursor); @@ -313,34 +295,29 @@ linenr_T get_cursor_rel_lnum(win_T *wp, linenr_T lnum) return (lnum < cursor) ? -retval : retval; } -// Make sure "pos.lnum" and "pos.col" are valid in "buf". -// This allows for the col to be on the NUL byte. +/// Make sure "pos.lnum" and "pos.col" are valid in "buf". +/// This allows for the col to be on the NUL byte. void check_pos(buf_T *buf, pos_T *pos) { - char_u *line; - colnr_T len; - if (pos->lnum > buf->b_ml.ml_line_count) { pos->lnum = buf->b_ml.ml_line_count; } if (pos->col > 0) { - line = ml_get_buf(buf, pos->lnum, false); - len = (colnr_T)STRLEN(line); + char_u *line = ml_get_buf(buf, pos->lnum, false); + colnr_T len = (colnr_T)STRLEN(line); if (pos->col > len) { pos->col = len; } } } -/* - * Make sure curwin->w_cursor.lnum is valid. - */ +/// Make sure curwin->w_cursor.lnum is valid. void check_cursor_lnum(void) { if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - /* If there is a closed fold at the end of the file, put the cursor in - * its first line. Otherwise in the last line. */ + // If there is a closed fold at the end of the file, put the cursor in + // its first line. Otherwise in the last line. if (!hasFolding(curbuf->b_ml.ml_line_count, &curwin->w_cursor.lnum, NULL)) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; @@ -351,9 +328,7 @@ void check_cursor_lnum(void) } } -/* - * Make sure curwin->w_cursor.col is valid. - */ +/// Make sure curwin->w_cursor.col is valid. void check_cursor_col(void) { check_cursor_col_win(curwin); @@ -363,21 +338,21 @@ void check_cursor_col(void) /// @see mb_check_adjust_col void check_cursor_col_win(win_T *win) { - colnr_T len; colnr_T oldcol = win->w_cursor.col; colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd; + unsigned int cur_ve_flags = get_ve_flags(); - len = (colnr_T)STRLEN(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false)); + colnr_T len = (colnr_T)STRLEN(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false)); if (len == 0) { win->w_cursor.col = 0; } else if (win->w_cursor.col >= len) { - /* Allow cursor past end-of-line when: - * - in Insert mode or restarting Insert mode - * - in Visual mode and 'selection' isn't "old" - * - 'virtualedit' is set */ + // Allow cursor past end-of-line when: + // - in Insert mode or restarting Insert mode + // - in Visual mode and 'selection' isn't "old" + // - 'virtualedit' is set */ if ((State & INSERT) || restart_edit || (VIsual_active && *p_sel != 'o') - || (ve_flags & VE_ONEMORE) + || (cur_ve_flags & VE_ONEMORE) || virtual_active()) { win->w_cursor.col = len; } else { @@ -394,7 +369,7 @@ void check_cursor_col_win(win_T *win) // line. if (oldcol == MAXCOL) { win->w_cursor.coladd = 0; - } else if (ve_flags == VE_ALL) { + } else if (cur_ve_flags == VE_ALL) { if (oldcoladd > win->w_cursor.col) { win->w_cursor.coladd = oldcoladd - win->w_cursor.col; @@ -417,32 +392,27 @@ void check_cursor_col_win(win_T *win) } } -/* - * make sure curwin->w_cursor in on a valid character - */ +/// Make sure curwin->w_cursor in on a valid character void check_cursor(void) { check_cursor_lnum(); check_cursor_col(); } -/* - * Make sure curwin->w_cursor is not on the NUL at the end of the line. - * Allow it when in Visual mode and 'selection' is not "old". - */ +/// Make sure curwin->w_cursor is not on the NUL at the end of the line. +/// Allow it when in Visual mode and 'selection' is not "old". void adjust_cursor_col(void) { if (curwin->w_cursor.col > 0 && (!VIsual_active || *p_sel == 'o') && gchar_cursor() == NUL) { - --curwin->w_cursor.col; + curwin->w_cursor.col--; } } -/* - * When curwin->w_leftcol has changed, adjust the cursor position. - * Return true if the cursor was moved. - */ +/// When curwin->w_leftcol has changed, adjust the cursor position. +/// +/// @return true if the cursor was moved. bool leftcol_changed(void) { // TODO(hinidu): I think it should be colnr_T or int, but p_siso is long. @@ -455,10 +425,8 @@ bool leftcol_changed(void) lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1; validate_virtcol(); - /* - * If the cursor is right or left of the screen, move it to last or first - * character. - */ + // If the cursor is right or left of the screen, move it to last or first + // character. if (curwin->w_virtcol > (colnr_T)(lastcol - p_siso)) { retval = true; coladvance((colnr_T)(lastcol - p_siso)); @@ -467,11 +435,9 @@ bool leftcol_changed(void) coladvance((colnr_T)(curwin->w_leftcol + p_siso)); } - /* - * If the start of the character under the cursor is not on the screen, - * advance the cursor one more char. If this fails (last char of the - * line) adjust the scrolling. - */ + // If the start of the character under the cursor is not on the screen, + // advance the cursor one more char. If this fails (last char of the + // line) adjust the scrolling. getvvcol(curwin, &curwin->w_cursor, &s, NULL, &e); if (e > (colnr_T)lastcol) { retval = true; @@ -496,27 +462,21 @@ int gchar_cursor(void) return utf_ptr2char(get_cursor_pos_ptr()); } -/* - * Write a character at the current cursor position. - * It is directly written into the block. - */ +/// Write a character at the current cursor position. +/// It is directly written into the block. void pchar_cursor(char_u c) { *(ml_get_buf(curbuf, curwin->w_cursor.lnum, true) + curwin->w_cursor.col) = c; } -/* - * Return pointer to cursor line. - */ +/// @return pointer to cursor line. char_u *get_cursor_line_ptr(void) { return ml_get_buf(curbuf, curwin->w_cursor.lnum, false); } -/* - * Return pointer to cursor position. - */ +/// @return pointer to cursor position. char_u *get_cursor_pos_ptr(void) { return ml_get_buf(curbuf, curwin->w_cursor.lnum, false) + diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 6b0a5dfe12..0e4a4bcfb0 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -9,8 +9,8 @@ #include "nvim/charset.h" #include "nvim/cursor_shape.h" #include "nvim/ex_getln.h" +#include "nvim/highlight_group.h" #include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -74,8 +74,7 @@ Array mode_style_array(void) PUT(dic, "hl_id", INTEGER_OBJ(cur->id)); PUT(dic, "id_lm", INTEGER_OBJ(cur->id_lm)); PUT(dic, "attr_id", INTEGER_OBJ(cur->id ? syn_id2attr(cur->id) : 0)); - PUT(dic, "attr_id_lm", INTEGER_OBJ(cur->id_lm ? syn_id2attr(cur->id_lm) - : 0)); + PUT(dic, "attr_id_lm", INTEGER_OBJ(cur->id_lm ? syn_id2attr(cur->id_lm) : 0)); } PUT(dic, "name", STRING_OBJ(cstr_to_string(cur->full_name))); PUT(dic, "short_name", STRING_OBJ(cstr_to_string(cur->name))); @@ -95,7 +94,6 @@ Array mode_style_array(void) /// @returns error message for an illegal option, NULL otherwise. char *parse_shape_opt(int what) { - char_u *modep; char_u *colonp; char_u *commap; char_u *slashp; @@ -120,7 +118,7 @@ char *parse_shape_opt(int what) } } // Repeat for all comma separated parts. - modep = p_guicursor; + char_u *modep = p_guicursor; while (modep != NULL && *modep != NUL) { colonp = vim_strchr(modep, ':'); commap = vim_strchr(modep, ','); @@ -147,7 +145,7 @@ char *parse_shape_opt(int what) if (len == 1 && TOLOWER_ASC(modep[0]) == 'a') { all_idx = SHAPE_IDX_COUNT - 1; } else { - for (idx = 0; idx < SHAPE_IDX_COUNT; ++idx) { + for (idx = 0; idx < SHAPE_IDX_COUNT; idx++) { if (STRNICMP(modep, shape_table[idx].name, len) == 0) { break; } @@ -170,9 +168,7 @@ char *parse_shape_opt(int what) // Parse the part after the colon for (p = colonp + 1; *p && *p != ',';) { { - /* - * First handle the ones with a number argument. - */ + // First handle the ones with a number argument. i = *p; len = 0; if (STRNICMP(p, "ver", 3) == 0) { @@ -230,11 +226,11 @@ char *parse_shape_opt(int what) slashp = vim_strchr(p, '/'); if (slashp != NULL && slashp < endp) { // "group/langmap_group" - i = syn_check_group((char *)p, (int)(slashp - p)); + i = syn_check_group((char *)p, (size_t)(slashp - p)); p = slashp + 1; } if (round == 2) { - shape_table[idx].id = syn_check_group((char *)p, (int)(endp - p)); + shape_table[idx].id = syn_check_group((char *)p, (size_t)(endp - p)); shape_table[idx].id_lm = shape_table[idx].id; if (slashp != NULL && slashp < endp) { shape_table[idx].id = i; @@ -245,7 +241,7 @@ char *parse_shape_opt(int what) } // if (what != SHAPE_MOUSE) if (*p == '-') { - ++p; + p++; } } } diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index b6e35f3047..cd823546b4 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -58,7 +58,6 @@ void do_debug(char_u *cmd) tasave_T typeaheadbuf; bool typeahead_saved = false; int save_ignore_script = 0; - int save_ex_normal_busy; int n; char_u *cmdline = NULL; char_u *p; @@ -117,7 +116,7 @@ void do_debug(char_u *cmd) // with the commands being executed. Reset "ex_normal_busy" to avoid // the side effects of using ":normal". Save the stuff buffer and make // it empty. Set ignore_script to avoid reading from script input. - save_ex_normal_busy = ex_normal_busy; + int save_ex_normal_busy = ex_normal_busy; ex_normal_busy = 0; if (!debug_greedy) { save_typeahead(&typeaheadbuf); @@ -201,7 +200,7 @@ void do_debug(char_u *cmd) if (last_cmd != 0) { // Check that the tail matches. p++; - while (*p != NUL && *p == *tail) { + while (*p != NUL && *p == (char_u)(*tail)) { p++; tail++; } @@ -268,7 +267,7 @@ void do_debug(char_u *cmd) DOCMD_VERBOSE|DOCMD_EXCRESET); debug_break_level = n; } - lines_left = (int)(Rows - 1); + lines_left = Rows - 1; } xfree(cmdline); @@ -277,7 +276,7 @@ void do_debug(char_u *cmd) redraw_all_later(NOT_VALID); need_wait_return = false; msg_scroll = save_msg_scroll; - lines_left = (int)(Rows - 1); + lines_left = Rows - 1; State = save_State; debug_mode = false; did_emsg = save_did_emsg; @@ -390,11 +389,10 @@ static char_u *debug_skipped_name; /// Called from do_one_cmd() before executing a command. void dbg_check_breakpoint(exarg_T *eap) { - char *p; - debug_skipped = false; if (debug_breakpoint_name != NULL) { if (!eap->skip) { + char *p; // replace K_SNR with "<SNR>" if (debug_breakpoint_name[0] == K_SPECIAL && debug_breakpoint_name[1] == KS_EXTRA @@ -430,12 +428,10 @@ void dbg_check_breakpoint(exarg_T *eap) /// @return true when the debug mode is entered this time. bool dbg_check_skipped(exarg_T *eap) { - int prev_got_int; - if (debug_skipped) { // Save the value of got_int and reset it. We don't want a previous // interruption cause flushing the input buffer. - prev_got_int = got_int; + int prev_got_int = got_int; got_int = false; debug_breakpoint_name = debug_skipped_name; // eap->skip is true @@ -482,12 +478,11 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) { char_u *p = arg; char_u *q; - struct debuggy *bp; bool here = false; ga_grow(gap, 1); - bp = &DEBUGGY(gap, gap->ga_len); + struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); // Find "func" or "file". if (STRNCMP(p, "func", 4) == 0) { @@ -564,16 +559,13 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) /// ":breakadd". Also used for ":profile". void ex_breakadd(exarg_T *eap) { - struct debuggy *bp; - garray_T *gap; - - gap = &dbg_breakp; + garray_T *gap = &dbg_breakp; if (eap->cmdidx == CMD_profile) { gap = &prof_ga; } if (dbg_parsearg(eap->arg, gap) == OK) { - bp = &DEBUGGY(gap, gap->ga_len); + struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); bp->dbg_forceit = eap->forceit; if (bp->dbg_type != DBG_EXPR) { @@ -616,20 +608,18 @@ void ex_debuggreedy(exarg_T *eap) void ex_breakdel(exarg_T *eap) { struct debuggy *bp, *bpi; - int nr; int todel = -1; bool del_all = false; linenr_T best_lnum = 0; - garray_T *gap; + garray_T *gap = &dbg_breakp; - gap = &dbg_breakp; if (eap->cmdidx == CMD_profdel) { gap = &prof_ga; } if (ascii_isdigit(*eap->arg)) { // ":breakdel {nr}" - nr = atoi((char *)eap->arg); + int nr = atoi((char *)eap->arg); for (int i = 0; i < gap->ga_len; i++) { if (DEBUGGY(gap, i).dbg_nr == nr) { todel = i; @@ -693,13 +683,11 @@ void ex_breakdel(exarg_T *eap) /// ":breaklist". void ex_breaklist(exarg_T *eap) { - struct debuggy *bp; - if (GA_EMPTY(&dbg_breakp)) { msg(_("No breakpoints defined")); } else { for (int i = 0; i < dbg_breakp.ga_len; i++) { - bp = &BREAKP(i); + struct debuggy *bp = &BREAKP(i); if (bp->dbg_type == DBG_FILE) { home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, true); } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index c0f3c32f93..a0f189ca38 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,20 +1,20 @@ // 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/buffer.h" #include "nvim/decoration.h" #include "nvim/extmark.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" +#include "nvim/move.h" #include "nvim/screen.h" -#include "nvim/syntax.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "decoration.c.generated.h" #endif -static PMap(uint64_t) hl_decors; - /// Add highlighting to a buffer, bounded by two cursor positions, /// with an offset. /// @@ -33,9 +33,9 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start { colnr_T hl_start = 0; colnr_T hl_end = 0; - Decoration *decor = decor_hl(hl_id); + Decoration decor = DECORATION_INIT; + decor.hl_id = hl_id; - decor->priority = DECOR_PRIORITY_BASE; // 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; @@ -59,40 +59,25 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start hl_start = pos_start.col + offset; hl_end = pos_end.col + offset; } - (void)extmark_set(buf, (uint64_t)src_id, NULL, - (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end, - decor, true, false, kExtmarkNoUndo); + extmark_set(buf, (uint32_t)src_id, NULL, + (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end, + &decor, true, false, 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; - decor->priority = DECOR_PRIORITY_BASE; - *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 (row2 >= row1) { + if (!decor || decor->hl_id || decor_has_sign(decor)) { + redraw_buf_range_later(buf, row1+1, row2+1); + } } - if (kv_size(decor->virt_text)) { + if (decor && kv_size(decor->virt_text)) { redraw_buf_line_later(buf, row1+1); } - if (kv_size(decor->virt_lines)) { + if (decor && kv_size(decor->virt_lines)) { redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, row1+1+(decor->virt_lines_above?0:1))); } @@ -100,22 +85,32 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) { - if (kv_size(decor->virt_lines)) { - assert(buf->b_virt_line_blocks > 0); - buf->b_virt_line_blocks--; - } decor_redraw(buf, row, row2, decor); + if (decor) { + if (kv_size(decor->virt_lines)) { + assert(buf->b_virt_line_blocks > 0); + buf->b_virt_line_blocks--; + } + if (decor_has_sign(decor)) { + assert(buf->b_signs > 0); + buf->b_signs--; + } + if (row2 >= row && decor->sign_text) { + buf_signcols_del_check(buf, row+1, row2+1); + } + } decor_free(decor); } void decor_free(Decoration *decor) { - if (decor && !decor->shared) { + if (decor) { clear_virttext(&decor->virt_text); for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { clear_virttext(&kv_A(decor->virt_lines, i).line); } kv_destroy(decor->virt_lines); + xfree(decor->sign_text); xfree(decor); } } @@ -134,17 +129,16 @@ Decoration *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) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 || mark.pos.row > row) { break; - } else if (marktree_decor_level(mark.id) < kDecorLevelVisible) { + } else if (marktree_decor_level(mark) < kDecorLevelVisible) { goto next_mark; } - 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; + Decoration *decor = mark.decor_full; + if ((ns_id == 0 || ns_id == mark.ns) + && decor && kv_size(decor->virt_text)) { + return decor; } next_mark: marktree_itr_next(buf->b_marktree, itr); @@ -163,7 +157,20 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state) } } kv_size(state->active) = 0; - return map_size(buf->b_extmark_index); + return buf->b_marktree->n_keys; +} + +Decoration get_decor(mtkey_t mark) +{ + if (mark.decor_full) { + return *mark.decor_full; + } else { + Decoration fake = DECORATION_INIT; + fake.hl_id = mark.hl_id; + fake.priority = mark.priority; + fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL); + return fake; + } } @@ -176,42 +183,44 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) } 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 + mtkey_t mark = marktree_itr_current(state->itr); + if (mark.pos.row < 0) { // || mark.row > end_row break; } - if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG) - || marktree_decor_level(mark.id) < kDecorLevelVisible) { + if ((mark.pos.row < top_row && mt_end(mark)) + || marktree_decor_level(mark) < kDecorLevelVisible) { goto next_mark; } - 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) { + Decoration decor = get_decor(mark); + + // Exclude non-paired marks unless they contain virt_text or a sign + if (!mt_paired(mark) + && !kv_size(decor.virt_text) + && !decor_has_sign(&decor)) { goto next_mark; } - Decoration *decor = item->decor; - mtpos_t altpos = marktree_lookup(buf->b_marktree, - mark.id^MARKTREE_END_FLAG, NULL); + mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row - && !kv_size(decor->virt_text)) - || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { + // Exclude start marks if the end mark position is above the top row + // Exclude end marks if we have already added the start mark + if ((mt_start(mark) && altpos.row < top_row + && !kv_size(decor.virt_text)) + || (mt_end(mark) && altpos.row >= top_row)) { goto next_mark; } - if (mark.id&MARKTREE_END_FLAG) { - decor_add(state, altpos.row, altpos.col, mark.row, mark.col, - decor, false); + if (mt_end(mark)) { + decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col, + &decor, false); } else { if (altpos.row == -1) { - altpos.row = mark.row; - altpos.col = mark.col; + altpos.row = mark.pos.row; + altpos.col = mark.pos.col; } - decor_add(state, mark.row, mark.col, altpos.row, altpos.col, - decor, false); + decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col, + &decor, false); } next_mark: @@ -266,43 +275,36 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState * while (true) { // TODO(bfredl): check duplicate entry in "intersection" // branch - mtmark_t mark = marktree_itr_current(state->itr); - if (mark.row < 0 || mark.row > state->row) { + mtkey_t mark = marktree_itr_current(state->itr); + if (mark.pos.row < 0 || mark.pos.row > state->row) { break; - } else if (mark.row == state->row && mark.col > col) { - state->col_until = mark.col-1; + } else if (mark.pos.row == state->row && mark.pos.col > col) { + state->col_until = mark.pos.col-1; break; } - if ((mark.id&MARKTREE_END_FLAG) - || marktree_decor_level(mark.id) < kDecorLevelVisible) { + if (mt_end(mark) + || marktree_decor_level(mark) < kDecorLevelVisible) { goto next_mark; } - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - if (!item || !item->decor) { - goto next_mark; - } - Decoration *decor = item->decor; + Decoration decor = get_decor(mark); - mtpos_t endpos = marktree_lookup(buf->b_marktree, - mark.id|MARKTREE_END_FLAG, NULL); + mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); if (endpos.row == -1) { - endpos.row = mark.row; - endpos.col = mark.col; + endpos = mark.pos; } - if (endpos.row < mark.row - || (endpos.row == mark.row && endpos.col <= mark.col)) { - if (!kv_size(decor->virt_text)) { + if (endpos.row < mark.pos.row + || (endpos.row == mark.pos.row && endpos.col <= mark.pos.col)) { + if (!kv_size(decor.virt_text)) { goto next_mark; } } - decor_add(state, mark.row, mark.col, endpos.row, endpos.col, - decor, false); + decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, + &decor, false); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -310,6 +312,10 @@ next_mark: int attr = 0; size_t j = 0; + bool conceal = 0; + int conceal_char = 0; + int conceal_attr = 0; + for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); bool active = false, keep = true; @@ -334,6 +340,14 @@ next_mark: if (active && item.attr_id > 0) { attr = hl_combine_attr(attr, item.attr_id); } + if (active && item.decor.conceal) { + conceal = true; + if (item.start_row == state->row && item.start_col == col && item.decor.conceal_char) { + conceal_char = item.decor.conceal_char; + state->col_until = MIN(state->col_until, item.start_col); + conceal_attr = item.attr_id; + } + } if ((item.start_row == state->row && item.start_col <= col) && kv_size(item.decor.virt_text) && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) { @@ -347,9 +361,150 @@ next_mark: } kv_size(state->active) = j; state->current = attr; + state->conceal = conceal; + state->conceal_char = conceal_char; + state->conceal_attr = conceal_attr; return attr; } +void decor_redraw_signs(buf_T *buf, int row, int *num_signs, sign_attrs_T sattrs[]) +{ + if (!buf->b_signs) { + return; + } + + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, row, 0, itr); + + while (true) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 || mark.pos.row > row) { + break; + } + + if (mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { + goto next_mark; + } + + Decoration *decor = mark.decor_full; + + if (!decor || !decor_has_sign(decor)) { + goto next_mark; + } + + int j; + for (j = (*num_signs); j > 0; j--) { + if (sattrs[j].sat_prio <= decor->priority) { + break; + } + sattrs[j] = sattrs[j-1]; + } + if (j < SIGN_SHOW_MAX) { + memset(&sattrs[j], 0, sizeof(sign_attrs_T)); + sattrs[j].sat_text = decor->sign_text; + if (decor->sign_hl_id != 0) { + sattrs[j].sat_texthl = syn_id2attr(decor->sign_hl_id); + } + if (decor->number_hl_id != 0) { + sattrs[j].sat_numhl = syn_id2attr(decor->number_hl_id); + } + if (decor->line_hl_id != 0) { + sattrs[j].sat_linehl = syn_id2attr(decor->line_hl_id); + } + if (decor->cursorline_hl_id != 0) { + sattrs[j].sat_culhl = syn_id2attr(decor->cursorline_hl_id); + } + sattrs[j].sat_prio = decor->priority; + (*num_signs)++; + } + +next_mark: + marktree_itr_next(buf->b_marktree, itr); + } +} + +// Get the maximum required amount of sign columns needed between row and +// end_row. +int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max) +{ + int count = 0; // count for the number of signs on a given row + int count_remove = 0; // how much to decrement count by when iterating marks for a new row + int signcols = 0; // highest value of count + int currow = -1; // current row + + if (max <= 1 && buf->b_signs >= (size_t)max) { + return max; + } + + if (buf->b_signs == 0) { + return 0; + } + + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, 0, -1, itr); + while (true) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 || mark.pos.row > end_row) { + break; + } + + if ((mark.pos.row < row && mt_end(mark)) + || marktree_decor_level(mark) < kDecorLevelVisible + || !mark.decor_full) { + goto next_mark; + } + + Decoration decor = get_decor(mark); + + if (!decor.sign_text) { + goto next_mark; + } + + if (mark.pos.row > currow) { + count -= count_remove; + count_remove = 0; + currow = mark.pos.row; + } + + if (!mt_paired(mark)) { + if (mark.pos.row >= row) { + count++; + if (count > signcols) { + signcols = count; + if (signcols >= max) { + return max; + } + } + count_remove++; + } + goto next_mark; + } + + mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); + + if (mt_end(mark)) { + if (mark.pos.row >= row && altpos.row <= end_row) { + count_remove++; + } + } else { + if (altpos.row >= row) { + count++; + if (count > signcols) { + signcols = count; + if (signcols >= max) { + return max; + } + } + } + } + +next_mark: + marktree_itr_next(buf->b_marktree, itr); + } + + return signcols; +} + void decor_redraw_end(DecorState *state) { state->buf = NULL; @@ -383,59 +538,6 @@ void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, } -DecorProvider *get_decor_provider(NS ns_id, bool force) -{ - size_t i; - size_t len = kv_size(decor_providers); - for (i = 0; i < len; 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; - } - - // Adding a new provider, so allocate room in the vector - (void)kv_a(decor_providers, len); - if (i < len) { - // New ns_id needs to be inserted between existing providers to maintain - // ordering, so shift other providers with larger ns_id - memmove(&kv_A(decor_providers, i + 1), - &kv_A(decor_providers, i), - (len - i) * sizeof(kv_a(decor_providers, i))); - } - DecorProvider *item = &kv_a(decor_providers, i); - *item = DECORATION_PROVIDER_INIT(ns_id); - - return item; -} - -void decor_provider_clear(DecorProvider *p) -{ - if (p == NULL) { - 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; -} - -void decor_free_all_mem(void) -{ - for (size_t i = 0; i < kv_size(decor_providers); i++) { - decor_provider_clear(&kv_A(decor_providers, i)); - } - kv_destroy(decor_providers); -} - int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) { @@ -452,18 +554,18 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) 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 >= end_row) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 || mark.pos.row >= end_row) { break; - } else if (marktree_decor_level(mark.id) < kDecorLevelVirtLine) { + } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) { goto next_mark; } - bool above = mark.row > (int)(lnum - 2); - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id, false); - if (item && item->decor && item->decor->virt_lines_above == above) { - virt_lines += (int)kv_size(item->decor->virt_lines); + bool above = mark.pos.row > (int)(lnum - 2); + Decoration *decor = mark.decor_full; + if (decor && decor->virt_lines_above == above) { + virt_lines += (int)kv_size(decor->virt_lines); if (lines) { - kv_splice(*lines, item->decor->virt_lines); + kv_splice(*lines, decor->virt_lines); } } next_mark: diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 611b4223da..97ab218f86 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -17,6 +17,8 @@ typedef enum { kVTRightAlign, } VirtTextPos; +EXTERN const char *const virt_text_pos_str[] INIT(= { "eol", "overlay", "win_col", "right_align" }); + typedef enum { kHlModeUnknown, kHlModeReplace, @@ -24,6 +26,8 @@ typedef enum { kHlModeBlend, } HlMode; +EXTERN const char *const hl_mode_str[] INIT(= { "", "replace", "combine", "blend" }); + typedef kvec_t(VirtTextChunk) VirtText; #define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) @@ -42,15 +46,24 @@ struct Decoration { // TODO(bfredl): at some point turn this into FLAGS bool virt_text_hide; bool hl_eol; - bool shared; // shared decoration, don't free bool virt_lines_above; - // TODO(bfredl): style, signs, etc + bool conceal; + // TODO(bfredl): style, etc DecorPriority priority; int col; // fixed col value, like win_col int virt_text_width; // width of virt_text + char_u *sign_text; + int sign_hl_id; + int number_hl_id; + int line_hl_id; + int cursorline_hl_id; + // TODO(bfredl): in principle this should be a schar_T, but we + // probably want some kind of glyph cache for that.. + int conceal_char; }; -#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, kHlModeUnknown, \ - false, false, false, false, DECOR_PRIORITY_BASE, 0, 0 } +#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \ + kHlModeUnknown, false, false, false, false, DECOR_PRIORITY_BASE, \ + 0, 0, NULL, 0, 0, 0, 0, 0 } typedef struct { int start_row; @@ -72,28 +85,22 @@ typedef struct { int col_until; int current; int eol_col; + + bool conceal; + int conceal_char; + int conceal_attr; } 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 }); -EXTERN bool provider_active INIT(= false); -#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ - { ns_id, false, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, -1 } +static inline bool decor_has_sign(Decoration *decor) +{ + return decor->sign_text + || decor->sign_hl_id + || decor->number_hl_id + || decor->line_hl_id + || decor->cursorline_hl_id; +} #ifdef INCLUDE_GENERATED_DECLARATIONS # include "decoration.h.generated.h" diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c new file mode 100644 index 0000000000..6efcb08b9d --- /dev/null +++ b/src/nvim/decoration_provider.c @@ -0,0 +1,231 @@ +// 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/api/extmark.h" +#include "nvim/api/private/helpers.h" +#include "nvim/buffer.h" +#include "nvim/decoration.h" +#include "nvim/decoration_provider.h" +#include "nvim/highlight.h" +#include "nvim/lua/executor.h" + +static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE; + +#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ + { ns_id, false, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, -1 } + +static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, + Array args, bool default_true, char **perr) +{ + Error err = ERROR_INIT; + + textlock++; + provider_active = true; + Object ret = nlua_call_ref(ref, name, args, true, &err); + provider_active = false; + 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 && perr && *perr == NULL) { + static char errbuf[IOSIZE]; + snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg); + *perr = xstrdup(errbuf); + } + } + + api_free_object(ret); + return false; +} + +/// For each provider invoke the 'start' callback +/// +/// @param[out] providers Decoration providers +/// @param[out] err Provider err +void decor_providers_start(DecorProviders *providers, int type, char **err) +{ + 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((int)display_tick); + args.items[1] = INTEGER_OBJ(type); + active = decor_provider_invoke(p->ns_id, "start", p->redraw_start, args, true, err); + } else { + active = true; + } + + if (active) { + kvi_push(*providers, p); + } + } +} + +/// For each provider run 'win'. If result is not false, then collect the +/// 'on_line' callback to call inside win_line +/// +/// @param wp Window +/// @param providers Decoration providers +/// @param[out] line_providers Enabled line providers to invoke in win_line +/// @param[out] err Provider error +void decor_providers_invoke_win(win_T *wp, DecorProviders *providers, + DecorProviders *line_providers, char **err) +{ + 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(wp->w_buffer->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 (decor_provider_invoke(p->ns_id, "win", p->redraw_win, args, true, err)) { + kvi_push(*line_providers, p); + } + } + } + + win_check_ns_hl(wp); +} + +/// For each provider invoke the 'line' callback for a given window row. +/// +/// @param wp Window +/// @param providers Decoration providers +/// @param row Row to invoke line callback for +/// @param[out] has_decor Set when at least one provider invokes a line callback +/// @param[out] err Provider error +void providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor, + char **err) +{ + 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(wp->w_buffer->handle); + args.items[2] = INTEGER_OBJ(row); + if (decor_provider_invoke(p->ns_id, "line", p->redraw_line, args, true, err)) { + *has_decor = true; + } else { + // return 'false' or error: skip rest of this window + kv_A(*providers, k) = NULL; + } + + win_check_ns_hl(wp); + } + } +} + + +/// For each provider invoke the 'buf' callback for a given buffer. +/// +/// @param buf Buffer +/// @param providers Decoration providers +/// @param[out] err Provider error +void decor_providers_invoke_buf(buf_T *buf, DecorProviders *providers, char **err) +{ + 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); + decor_provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true, err); + } + } +} + + +/// For each provider invoke the 'end' callback +/// +/// @param providers Decoration providers +/// @param displaytick Display tick +/// @param[out] err Provider error +void decor_providers_invoke_end(DecorProviders *providers, char **err) +{ + for (size_t i = 0; i < kv_size(*providers); i++) { + DecorProvider *p = kv_A(*providers, i); + if (p && p->active && p->redraw_end != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = INTEGER_OBJ((int)display_tick); + decor_provider_invoke(p->ns_id, "end", p->redraw_end, args, true, err); + } + } +} + +DecorProvider *get_decor_provider(NS ns_id, bool force) +{ + size_t i; + size_t len = kv_size(decor_providers); + for (i = 0; i < len; 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; + } + + // Adding a new provider, so allocate room in the vector + (void)kv_a(decor_providers, len); + if (i < len) { + // New ns_id needs to be inserted between existing providers to maintain + // ordering, so shift other providers with larger ns_id + memmove(&kv_A(decor_providers, i + 1), + &kv_A(decor_providers, i), + (len - i) * sizeof(kv_a(decor_providers, i))); + } + DecorProvider *item = &kv_a(decor_providers, i); + *item = DECORATION_PROVIDER_INIT(ns_id); + + return item; +} + +void decor_provider_clear(DecorProvider *p) +{ + if (p == NULL) { + 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; +} + +void decor_free_all_mem(void) +{ + for (size_t i = 0; i < kv_size(decor_providers); i++) { + decor_provider_clear(&kv_A(decor_providers, i)); + } + kv_destroy(decor_providers); +} + diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h new file mode 100644 index 0000000000..3ec7c80357 --- /dev/null +++ b/src/nvim/decoration_provider.h @@ -0,0 +1,26 @@ +#ifndef NVIM_DECORATION_PROVIDER_H +#define NVIM_DECORATION_PROVIDER_H + +#include "nvim/buffer_defs.h" + +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; + +typedef kvec_withinit_t(DecorProvider *, 4) DecorProviders; + +EXTERN bool provider_active INIT(= false); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "decoration_provider.h.generated.h" +#endif + +#endif // NVIM_DECORATION_PROVIDER_H diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 1f8acd8c79..0b55fb877c 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -29,7 +29,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -38,6 +37,7 @@ #include "nvim/path.h" #include "nvim/screen.h" #include "nvim/strings.h" +#include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -82,6 +82,14 @@ typedef struct { garray_T dout_ga; // used for internal diff } diffout_T; +// used for recording hunks from xdiff +typedef struct { + linenr_T lnum_orig; + long count_orig; + linenr_T lnum_new; + long count_new; +} diffhunk_T; + // two diff inputs and one result typedef struct { diffin_T dio_orig; // original file input @@ -158,8 +166,7 @@ void diff_buf_add(buf_T *buf) return; } - int i; - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if (curtab->tp_diffbuf[i] == NULL) { curtab->tp_diffbuf[i] = buf; curtab->tp_diff_invalid = true; @@ -193,7 +200,7 @@ static void diff_buf_clear(void) static int diff_buf_idx(buf_T *buf) { int idx; - for (idx = 0; idx < DB_COUNT; ++idx) { + for (idx = 0; idx < DB_COUNT; idx++) { if (curtab->tp_diffbuf[idx] == buf) { break; } @@ -210,7 +217,7 @@ static int diff_buf_idx(buf_T *buf) static int diff_buf_idx_tp(buf_T *buf, tabpage_T *tp) { int idx; - for (idx = 0; idx < DB_COUNT; ++idx) { + for (idx = 0; idx < DB_COUNT; idx++) { if (tp->tp_diffbuf[idx] == buf) { break; } @@ -296,7 +303,6 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T diff_T *dprev = NULL; diff_T *dp = tp->tp_first_diff; - linenr_T last; linenr_T lnum_deleted = line1; // lnum of remaining deletion int n; int off; @@ -315,7 +321,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T dnext->df_lnum[idx] = line1; dnext->df_count[idx] = inserted; int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((tp->tp_diffbuf[i] != NULL) && (i != idx)) { if (dprev == NULL) { dnext->df_lnum[i] = line1; @@ -346,7 +352,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T // 3 5 6 // compute last line of this change - last = dp->df_lnum[idx] + dp->df_count[idx] - 1; + linenr_T last = dp->df_lnum[idx] + dp->df_count[idx] - 1; // 1. change completely above line1: nothing to do if (last >= line1 - 1) { @@ -413,7 +419,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T } int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((tp->tp_diffbuf[i] != NULL) && (i != idx)) { dp->df_lnum[i] -= off; dp->df_count[i] += n; @@ -434,7 +440,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T // Check if inserted lines are equal, may reduce the size of the // diff. // - // TODO: also check for equal lines in the middle and perhaps split + // TODO(unknown): also check for equal lines in the middle and perhaps split // the block. diff_check_unchanged(tp, dp); } @@ -445,7 +451,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T if ((dprev != NULL) && (dprev->df_lnum[idx] + dprev->df_count[idx] == dp->df_lnum[idx])) { int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if (tp->tp_diffbuf[i] != NULL) { dprev->df_count[i] += dp->df_count[i]; } @@ -466,7 +472,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T while (dp != NULL) { // All counts are zero, remove this entry. int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((tp->tp_diffbuf[i] != NULL) && (dp->df_count[i] != 0)) { break; } @@ -534,7 +540,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) // Find the first buffers, use it as the original, compare the other // buffer lines against this one. int i_org; - for (i_org = 0; i_org < DB_COUNT; ++i_org) { + for (i_org = 0; i_org < DB_COUNT; i_org++) { if (tp->tp_diffbuf[i_org] != NULL) { break; } @@ -566,7 +572,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) false)); int i_new; - for (i_new = i_org + 1; i_new < DB_COUNT; ++i_new) { + for (i_new = i_org + 1; i_new < DB_COUNT; i_new++) { if (tp->tp_diffbuf[i_new] == NULL) { continue; } @@ -594,7 +600,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) } // Line matched in all buffers, remove it from the diff. - for (i_new = i_org; i_new < DB_COUNT; ++i_new) { + for (i_new = i_org; i_new < DB_COUNT; i_new++) { if (tp->tp_diffbuf[i_new] != NULL) { if (dir == FORWARD) { dp->df_lnum[i_new]++; @@ -620,8 +626,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) /// @return OK if the diff block doesn't contain invalid line numbers. static int diff_check_sanity(tabpage_T *tp, diff_T *dp) { - int i; - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if (tp->tp_diffbuf[i] != NULL) { if (dp->df_lnum[i] + dp->df_count[i] - 1 > tp->tp_diffbuf[i]->b_ml.ml_line_count) { @@ -674,11 +679,11 @@ void diff_redraw(bool dofold) } if (wp_other != NULL && curwin->w_p_scb) { if (used_max_fill_curwin) { - // The current window was set to used the maximum number of filler + // The current window was set to use the maximum number of filler // lines, may need to reduce them. diff_set_topline(wp_other, curwin); } else if (used_max_fill_other) { - // The other window was set to used the maximum number of filler + // The other window was set to use the maximum number of filler // lines, may need to reduce them. diff_set_topline(curwin, wp_other); } @@ -711,16 +716,13 @@ static void clear_diffout(diffout_T *dout) /// @return FAIL for failure. static int diff_write_buffer(buf_T *buf, diffin_T *din) { - linenr_T lnum; - char_u *s; long len = 0; - char_u *ptr; // xdiff requires one big block of memory with all the text. - for (lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { len += (long)STRLEN(ml_get_buf(buf, lnum, false)) + 1; } - ptr = try_malloc(len); + char_u *ptr = try_malloc(len); if (ptr == NULL) { // Allocating memory failed. This can happen, because we try to read // the whole buffer text into memory. Set the failed flag, the diff @@ -738,14 +740,19 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) din->din_mmfile.size = len; len = 0; - for (lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - for (s = ml_get_buf(buf, lnum, false); *s != NUL;) { + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (char_u *s = ml_get_buf(buf, lnum, false); *s != NUL;) { if (diff_flags & DIFF_ICASE) { + int c; char_u cbuf[MB_MAXBYTES + 1]; - // xdiff doesn't support ignoring case, fold-case the text. - int c = utf_ptr2char(s); - c = utf_fold(c); + if (*s == NL) { + c = NUL; + } else { + // xdiff doesn't support ignoring case, fold-case the text. + c = utf_ptr2char(s); + c = utf_fold(c); + } const int orig_len = utfc_ptr2len(s); if (utf_char2bytes(c, cbuf) != orig_len) { // TODO(Bram): handle byte length difference @@ -757,7 +764,8 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) s += orig_len; len += orig_len; } else { - ptr[len++] = *s++; + ptr[len++] = *s == NL ? NUL : *s; + s++; } } ptr[len++] = NL; @@ -782,9 +790,14 @@ static int diff_write(buf_T *buf, diffin_T *din) // Always use 'fileformat' set to "unix". char_u *save_ff = buf->b_p_ff; buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + const bool save_lockmarks = cmdmod.lockmarks; + // Writing the buffer is an implementation detail of performing the diff, + // so it shouldn't update the '[ and '] marks. + cmdmod.lockmarks = true; int r = buf_write(buf, din->din_fname, NULL, (linenr_T)1, buf->b_ml.ml_line_count, NULL, false, false, false, true); + cmdmod.lockmarks = save_lockmarks; free_string_option(buf->b_p_ff); buf->b_p_ff = save_ff; return r; @@ -798,9 +811,6 @@ static int diff_write(buf_T *buf, diffin_T *din) /// @param eap can be NULL static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) { - buf_T *buf; - int idx_new; - if (dio->dio_internal) { ga_init(&dio->dio_diff.dout_ga, sizeof(char *), 1000); } else { @@ -820,9 +830,11 @@ static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) goto theend; } + buf_T *buf; + // :diffupdate! if (eap != NULL && eap->forceit) { - for (idx_new = idx_orig; idx_new < DB_COUNT; idx_new++) { + for (int idx_new = idx_orig; idx_new < DB_COUNT; idx_new++) { buf = curtab->tp_diffbuf[idx_new]; if (buf_valid(buf)) { buf_check_timestamp(buf); @@ -837,7 +849,7 @@ static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) } // Make a difference between the first buffer and every other. - for (idx_new = idx_orig + 1; idx_new < DB_COUNT; idx_new++) { + for (int idx_new = idx_orig + 1; idx_new < DB_COUNT; idx_new++) { buf = curtab->tp_diffbuf[idx_new]; if (buf == NULL || buf->b_ml.ml_mfp == NULL) { continue; // skip buffer that isn't loaded @@ -852,7 +864,7 @@ static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) } // Read the diff output and add each entry to the diff list. - diff_read(idx_orig, idx_new, &dio->dio_diff); + diff_read(idx_orig, idx_new, dio); clear_diffin(&dio->dio_new); clear_diffout(&dio->dio_diff); @@ -880,10 +892,8 @@ int diff_internal(void) /// static int diff_internal_failed(void) { - int idx; - // Only need to do something when there is another buffer. - for (idx = 0; idx < DB_COUNT; idx++) { + for (int idx = 0; idx < DB_COUNT; idx++) { if (curtab->tp_diffbuf[idx] != NULL && curtab->tp_diffbuf[idx]->b_diff_failed) { return true; @@ -914,7 +924,7 @@ void ex_diffupdate(exarg_T *eap) // Use the first buffer as the original text. int idx_orig; - for (idx_orig = 0; idx_orig < DB_COUNT; ++idx_orig) { + for (idx_orig = 0; idx_orig < DB_COUNT; idx_orig++) { if (curtab->tp_diffbuf[idx_orig] != NULL) { break; } @@ -926,7 +936,7 @@ void ex_diffupdate(exarg_T *eap) // Only need to do something when there is another buffer. int idx_new; - for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new) { + for (idx_new = idx_orig + 1; idx_new < DB_COUNT; idx_new++) { if (curtab->tp_diffbuf[idx_new] != NULL) { break; } @@ -1078,7 +1088,7 @@ static int diff_file_internal(diffio_T *diffio) emit_cfg.ctxlen = 0; // don't need any diff_context here emit_cb.priv = &diffio->dio_diff; - emit_cb.out_line = xdiff_out; + emit_cfg.hunk_func = xdiff_out; if (xdl_diff(&diffio->dio_orig.din_mmfile, &diffio->dio_new.din_mmfile, ¶m, &emit_cfg, &emit_cb) < 0) { @@ -1272,7 +1282,7 @@ void ex_diffpatch(exarg_T *eap) ex_file(eap); // Do filetype detection with the new name. - if (au_has_group((char_u *)"filetypedetect")) { + if (augroup_exists("filetypedetect")) { do_cmdline_cmd(":doau filetypedetect BufRead"); } } @@ -1508,7 +1518,7 @@ void ex_diffoff(exarg_T *eap) diff_clear(curtab); } - // Remove "hor" from from 'scrollopt' if there are no diff windows left. + // Remove "hor" from 'scrollopt' if there are no diff windows left. if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) { do_cmdline_cmd("set sbo-=hor"); } @@ -1519,20 +1529,20 @@ void ex_diffoff(exarg_T *eap) /// @param idx_orig idx of original file /// @param idx_new idx of new file /// @dout diff output -static void diff_read(int idx_orig, int idx_new, diffout_T *dout) +static void diff_read(int idx_orig, int idx_new, diffio_T *dio) { FILE *fd = NULL; int line_idx = 0; diff_T *dprev = NULL; diff_T *dp = curtab->tp_first_diff; diff_T *dn, *dpl; + diffout_T *dout = &dio->dio_diff; char_u linebuf[LBUFLEN]; // only need to hold the diff line char_u *line; long off; int i; - linenr_T lnum_orig, lnum_new; - long count_orig, count_new; int notset = true; // block "*dp" not set yet + diffhunk_T *hunk = NULL; // init to avoid gcc warning enum { DIFF_ED, DIFF_UNIFIED, @@ -1549,70 +1559,79 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) } } + if (!dio->dio_internal) { + hunk = xmalloc(sizeof(*hunk)); + } + for (;;) { - if (fd == NULL) { + if (dio->dio_internal) { if (line_idx >= dout->dout_ga.ga_len) { break; // did last line } - line = ((char_u **)dout->dout_ga.ga_data)[line_idx++]; + hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_idx++]; } else { - if (vim_fgets(linebuf, LBUFLEN, fd)) { - break; // end of file - } - line = linebuf; - } - - if (diffstyle == DIFF_NONE) { - // Determine diff style. - // ed like diff looks like this: - // {first}[,{last}]c{first}[,{last}] - // {first}a{first}[,{last}] - // {first}[,{last}]d{first} - // - // unified diff looks like this: - // --- file1 2018-03-20 13:23:35.783153140 +0100 - // +++ file2 2018-03-20 13:23:41.183156066 +0100 - // @@ -1,3 +1,5 @@ - if (isdigit(*line)) { - diffstyle = DIFF_ED; - } else if ((STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; - } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "+++ ", 4) == 0) - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; + if (fd == NULL) { + if (line_idx >= dout->dout_ga.ga_len) { + break; // did last line + } + line = ((char_u **)dout->dout_ga.ga_data)[line_idx++]; } else { - // Format not recognized yet, skip over this line. Cygwin diff - // may put a warning at the start of the file. - continue; + if (vim_fgets(linebuf, LBUFLEN, fd)) { + break; // end of file + } + line = linebuf; } - } - if (diffstyle == DIFF_ED) { - if (!isdigit(*line)) { - continue; // not the start of a diff block - } - if (parse_diff_ed(line, &lnum_orig, &count_orig, - &lnum_new, &count_new) == FAIL) { - continue; - } - } else { - assert(diffstyle == DIFF_UNIFIED); - if (STRNCMP(line, "@@ ", 3) != 0) { - continue; // not the start of a diff block + if (diffstyle == DIFF_NONE) { + // Determine diff style. + // ed like diff looks like this: + // {first}[,{last}]c{first}[,{last}] + // {first}a{first}[,{last}] + // {first}[,{last}]d{first} + // + // unified diff looks like this: + // --- file1 2018-03-20 13:23:35.783153140 +0100 + // +++ file2 2018-03-20 13:23:41.183156066 +0100 + // @@ -1,3 +1,5 @@ + if (isdigit(*line)) { + diffstyle = DIFF_ED; + } else if ((STRNCMP(line, "@@ ", 3) == 0)) { + diffstyle = DIFF_UNIFIED; + } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 + && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 + && (STRNCMP(line, "+++ ", 4) == 0) + && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 + && (STRNCMP(line, "@@ ", 3) == 0)) { + diffstyle = DIFF_UNIFIED; + } else { + // Format not recognized yet, skip over this line. Cygwin diff + // may put a warning at the start of the file. + continue; + } } - if (parse_diff_unified(line, &lnum_orig, &count_orig, - &lnum_new, &count_new) == FAIL) { - continue; + + if (diffstyle == DIFF_ED) { + if (!isdigit(*line)) { + continue; // not the start of a diff block + } + if (parse_diff_ed(line, hunk) == FAIL) { + continue; + } + } else { + assert(diffstyle == DIFF_UNIFIED); + if (STRNCMP(line, "@@ ", 3) != 0) { + continue; // not the start of a diff block + } + if (parse_diff_unified(line, hunk) == FAIL) { + continue; + } } } // Go over blocks before the change, for which orig and new are equal. // Copy blocks from orig to new. while (dp != NULL - && lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { + && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { if (notset) { diff_copy_entry(dprev, dp, idx_orig, idx_new); } @@ -1622,35 +1641,35 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) } if ((dp != NULL) - && (lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) - && (lnum_orig + count_orig >= dp->df_lnum[idx_orig])) { + && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) + && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) { // New block overlaps with existing block(s). // First find last block that overlaps. for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) { - if (lnum_orig + count_orig < dpl->df_next->df_lnum[idx_orig]) { + if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) { break; } } // If the newly found block starts before the old one, set the // start back a number of lines. - off = dp->df_lnum[idx_orig] - lnum_orig; + off = dp->df_lnum[idx_orig] - hunk->lnum_orig; if (off > 0) { - for (i = idx_orig; i < idx_new; ++i) { + for (i = idx_orig; i < idx_new; i++) { if (curtab->tp_diffbuf[i] != NULL) { dp->df_lnum[i] -= off; } } - dp->df_lnum[idx_new] = lnum_new; - dp->df_count[idx_new] = count_new; + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = hunk->count_new; } else if (notset) { // new block inside existing one, adjust new block - dp->df_lnum[idx_new] = lnum_new + off; - dp->df_count[idx_new] = count_new - off; + dp->df_lnum[idx_new] = hunk->lnum_new + off; + dp->df_count[idx_new] = hunk->count_new - off; } else { // second overlap of new block with existing block - dp->df_count[idx_new] += count_new - count_orig + dp->df_count[idx_new] += hunk->count_new - hunk->count_orig + dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig] - (dp->df_lnum[idx_orig] + @@ -1659,7 +1678,7 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) // Adjust the size of the block to include all the lines to the // end of the existing block or the new diff, whatever ends last. - off = (lnum_orig + count_orig) + off = (hunk->lnum_orig + hunk->count_orig) - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); if (off < 0) { @@ -1671,7 +1690,7 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) off = 0; } - for (i = idx_orig; i < idx_new; ++i) { + for (i = idx_orig; i < idx_new; i++) { if (curtab->tp_diffbuf[i] != NULL) { dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] - dp->df_lnum[i] + off; @@ -1691,15 +1710,15 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) // Allocate a new diffblock. dp = diff_alloc_new(curtab, dprev, dp); - dp->df_lnum[idx_orig] = lnum_orig; - dp->df_count[idx_orig] = count_orig; - dp->df_lnum[idx_new] = lnum_new; - dp->df_count[idx_new] = count_new; + dp->df_lnum[idx_orig] = hunk->lnum_orig; + dp->df_count[idx_orig] = hunk->count_orig; + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = hunk->count_new; // Set values for other buffers, these must be equal to the // original buffer, otherwise there would have been a change // already. - for (i = idx_orig + 1; i < idx_new; ++i) { + for (i = idx_orig + 1; i < idx_new; i++) { if (curtab->tp_diffbuf[i] != NULL) { diff_copy_entry(dprev, dp, idx_orig, i); } @@ -1718,6 +1737,10 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) notset = true; } + if (!dio->dio_internal) { + xfree(hunk); + } + if (fd != NULL) { fclose(fd); } @@ -1749,9 +1772,8 @@ static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig, int idx_new void diff_clear(tabpage_T *tp) FUNC_ATTR_NONNULL_ALL { - diff_T *p; diff_T *next_p; - for (p = tp->tp_first_diff; p != NULL; p = next_p) { + for (diff_T *p = tp->tp_first_diff; p != NULL; p = next_p) { next_p = p->df_next; xfree(p); } @@ -1773,12 +1795,8 @@ void diff_clear(tabpage_T *tp) /// @return diff status. int diff_check(win_T *wp, linenr_T lnum) { - int idx; // index in tp_diffbuf[] for this buffer diff_T *dp; - int maxcount; - int i; buf_T *buf = wp->w_buffer; - int cmp; if (curtab->tp_diff_invalid) { // update after a big change @@ -1795,7 +1813,7 @@ int diff_check(win_T *wp, linenr_T lnum) return 0; } - idx = diff_buf_idx(buf); + int idx = diff_buf_idx(buf); // index in tp_diffbuf[] for this buffer if (idx == DB_COUNT) { // no diffs for buffer "buf" @@ -1824,9 +1842,9 @@ int diff_check(win_T *wp, linenr_T lnum) // Changed or inserted line. If the other buffers have a count of // zero, the lines were inserted. If the other buffers have the same // count, check if the lines are identical. - cmp = false; + int cmp = false; - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if ((i != idx) && (curtab->tp_diffbuf[i] != NULL)) { if (dp->df_count[i] == 0) { zero = true; @@ -1843,7 +1861,7 @@ int diff_check(win_T *wp, linenr_T lnum) if (cmp) { // Compare all lines. If they are equal the lines were inserted // in some buffers, deleted in others, but not changed. - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if ((i != idx) && (curtab->tp_diffbuf[i] != NULL) && (dp->df_count[i] != 0)) { @@ -1872,8 +1890,8 @@ int diff_check(win_T *wp, linenr_T lnum) // Insert filler lines above the line just below the change. Will return // 0 when this buf had the max count. - maxcount = 0; - for (i = 0; i < DB_COUNT; ++i) { + int maxcount = 0; + for (int i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (dp->df_count[i] > maxcount)) { maxcount = dp->df_count[i]; } @@ -2004,8 +2022,6 @@ void diff_set_topline(win_T *fromwin, win_T *towin) buf_T *frombuf = fromwin->w_buffer; linenr_T lnum = fromwin->w_topline; diff_T *dp; - int max_count; - int i; int fromidx = diff_buf_idx(frombuf); if (fromidx == DB_COUNT) { @@ -2045,9 +2061,9 @@ void diff_set_topline(win_T *fromwin, win_T *towin) if (lnum >= dp->df_lnum[fromidx]) { // Inside a change: compute filler lines. With three or more // buffers we need to know the largest count. - max_count = 0; + int max_count = 0; - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (max_count < dp->df_count[i])) { max_count = dp->df_count[i]; } @@ -2193,7 +2209,7 @@ int diffopt_changed(void) } if (*p == ',') { - ++p; + p++; } } @@ -2296,7 +2312,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) int off = lnum - dp->df_lnum[idx]; int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (i != idx)) { // Skip lines that are not in the other change (filler lines). if (off >= dp->df_count[i]) { @@ -2395,7 +2411,6 @@ bool diff_infold(win_T *wp, linenr_T lnum) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { bool other = false; - diff_T *dp; // Return if 'diff' isn't set. if (!wp->w_p_diff) { @@ -2404,7 +2419,7 @@ bool diff_infold(win_T *wp, linenr_T lnum) int idx = -1; int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if (curtab->tp_diffbuf[i] == wp->w_buffer) { idx = i; } else if (curtab->tp_diffbuf[i] != NULL) { @@ -2427,7 +2442,7 @@ bool diff_infold(win_T *wp, linenr_T lnum) return true; } - for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { + for (diff_T *dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { // If this change is below the line there can't be any further match. if (dp->df_lnum[idx] - diff_context > lnum) { break; @@ -2454,7 +2469,7 @@ void nv_diffgetput(bool put, size_t count) if (count == 0) { ea.arg = (char_u *)""; } else { - vim_snprintf(buf, 30, "%zu", count); + vim_snprintf(buf, sizeof(buf), "%zu", count); ea.arg = (char_u *)buf; } @@ -2479,7 +2494,6 @@ void ex_diffgetput(exarg_T *eap) int count; linenr_T off = 0; diff_T *dp; - diff_T *dprev; diff_T *dfree; int i; int added; @@ -2503,7 +2517,7 @@ void ex_diffgetput(exarg_T *eap) if (*eap->arg == NUL) { // No argument: Find the other buffer in the list of diff buffers. - for (idx_other = 0; idx_other < DB_COUNT; ++idx_other) { + for (idx_other = 0; idx_other < DB_COUNT; idx_other++) { if ((curtab->tp_diffbuf[idx_other] != curbuf) && (curtab->tp_diffbuf[idx_other] != NULL)) { if ((eap->cmdidx != CMD_diffput) @@ -2524,7 +2538,7 @@ void ex_diffgetput(exarg_T *eap) } // Check that there isn't a third buffer in the list - for (i = idx_other + 1; i < DB_COUNT; ++i) { + for (i = idx_other + 1; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != curbuf) && (curtab->tp_diffbuf[i] != NULL) && ((eap->cmdidx != CMD_diffput) @@ -2541,7 +2555,7 @@ void ex_diffgetput(exarg_T *eap) p--; } - for (i = 0; ascii_isdigit(eap->arg[i]) && eap->arg + i < p; ++i) { + for (i = 0; ascii_isdigit(eap->arg[i]) && eap->arg + i < p; i++) { } if (eap->arg + i == p) { @@ -2584,9 +2598,9 @@ void ex_diffgetput(exarg_T *eap) && (eap->line1 == curbuf->b_ml.ml_line_count) && (diff_check(curwin, eap->line1) == 0) && ((eap->line1 == 1) || (diff_check(curwin, eap->line1 - 1) == 0))) { - ++eap->line2; + eap->line2++; } else if (eap->line1 > 0) { - --eap->line1; + eap->line1--; } } @@ -2615,7 +2629,7 @@ void ex_diffgetput(exarg_T *eap) } } - dprev = NULL; + diff_T *dprev = NULL; for (dp = curtab->tp_first_diff; dp != NULL;) { if (dp->df_lnum[idx_cur] > eap->line2 + off) { @@ -2677,14 +2691,14 @@ void ex_diffgetput(exarg_T *eap) buf_empty = buf_is_empty(curbuf); added = 0; - for (i = 0; i < count; ++i) { + for (i = 0; i < count; i++) { // remember deleting the last line of the buffer buf_empty = curbuf->b_ml.ml_line_count == 1; ml_delete(lnum, false); added--; } - for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; ++i) { + for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; i++) { linenr_T nr = dp->df_lnum[idx_from] + start_skip + i; if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) { break; @@ -2706,7 +2720,7 @@ void ex_diffgetput(exarg_T *eap) if ((start_skip == 0) && (end_skip == 0)) { // Check if there are any other buffers and if the diff is // equal in them. - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (i != idx_from) && (i != idx_to) @@ -2809,7 +2823,7 @@ theend: static void diff_fold_update(diff_T *dp, int skip_idx) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - for (int i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] == wp->w_buffer) && (i != skip_idx)) { foldUpdate(wp, dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i]); } @@ -2898,13 +2912,10 @@ int diff_move_to(int dir, long count) /// "buf1" in diff mode. static linenr_T diff_get_corresponding_line_int(buf_T *buf1, linenr_T lnum1) { - int idx1; - int idx2; - diff_T *dp; int baseline = 0; - idx1 = diff_buf_idx(buf1); - idx2 = diff_buf_idx(curbuf); + int idx1 = diff_buf_idx(buf1); + int idx2 = diff_buf_idx(curbuf); if ((idx1 == DB_COUNT) || (idx2 == DB_COUNT) @@ -2922,7 +2933,7 @@ static linenr_T diff_get_corresponding_line_int(buf_T *buf1, linenr_T lnum1) return lnum1; } - for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { + for (diff_T *dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { if (dp->df_lnum[idx1] > lnum1) { return lnum1 - baseline; } @@ -2978,11 +2989,8 @@ linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1) linenr_T diff_lnum_win(linenr_T lnum, win_T *wp) { diff_T *dp; - int idx; - int i; - linenr_T n; - idx = diff_buf_idx(curbuf); + int idx = diff_buf_idx(curbuf); if (idx == DB_COUNT) { // safety check @@ -3008,14 +3016,14 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp) } // Find index for "wp". - i = diff_buf_idx(wp->w_buffer); + int i = diff_buf_idx(wp->w_buffer); if (i == DB_COUNT) { // safety check return (linenr_T)0; } - n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]); + linenr_T n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]); if (n > dp->df_lnum[i] + dp->df_count[i]) { n = dp->df_lnum[i] + dp->df_count[i]; } @@ -3026,19 +3034,16 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp) /// Handle an ED style diff line. /// Return FAIL if the line does not contain diff info. /// -static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, linenr_T *lnum_new, - long *count_new) +static int parse_diff_ed(char_u *line, diffhunk_T *hunk) { - char_u *p; - long f1, l1, f2, l2; - int difftype; + long l1, l2; // The line must be one of three formats: // change: {first}[,{last}]c{first}[,{last}] // append: {first}a{first}[,{last}] // delete: {first}[,{last}]d{first} - p = line; - f1 = getdigits(&p, true, 0); + char_u *p = line; + long f1 = getdigits(&p, true, 0); if (*p == ',') { p++; l1 = getdigits(&p, true, 0); @@ -3048,8 +3053,8 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li if (*p != 'a' && *p != 'c' && *p != 'd') { return FAIL; // invalid diff format } - difftype = *p++; - f2 = getdigits(&p, true, 0); + int difftype = *p++; + long f2 = getdigits(&p, true, 0); if (*p == ',') { p++; l2 = getdigits(&p, true, 0); @@ -3061,18 +3066,18 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li } if (difftype == 'a') { - *lnum_orig = f1 + 1; - *count_orig = 0; + hunk->lnum_orig = f1 + 1; + hunk->count_orig = 0; } else { - *lnum_orig = f1; - *count_orig = l1 - f1 + 1; + hunk->lnum_orig = f1; + hunk->count_orig = l1 - f1 + 1; } if (difftype == 'd') { - *lnum_new = f2 + 1; - *count_new = 0; + hunk->lnum_new = f2 + 1; + hunk->count_new = 0; } else { - *lnum_new = f2; - *count_new = l2 - f2 + 1; + hunk->lnum_new = f2; + hunk->count_new = l2 - f2 + 1; } return OK; } @@ -3081,17 +3086,16 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li /// Parses unified diff with zero(!) context lines. /// Return FAIL if there is no diff information in "line". /// -static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_orig, - linenr_T *lnum_new, long *count_new) +static int parse_diff_unified(char_u *line, diffhunk_T *hunk) { - char_u *p; - long oldline, oldcount, newline, newcount; - // Parse unified diff hunk header: // @@ -oldline,oldcount +newline,newcount @@ - p = line; + char_u *p = line; if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-') { - oldline = getdigits(&p, true, 0); + long oldcount; + long newline; + long newcount; + long oldline = getdigits(&p, true, 0); if (*p == ',') { p++; oldcount = getdigits(&p, true, 0); @@ -3120,10 +3124,10 @@ static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_ori newline = 1; } - *lnum_orig = oldline; - *count_orig = oldcount; - *lnum_new = newline; - *count_new = newcount; + hunk->lnum_orig = oldline; + hunk->count_orig = oldcount; + hunk->lnum_new = newline; + hunk->count_new = newcount; return OK; } @@ -3135,25 +3139,16 @@ static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_ori /// Callback function for the xdl_diff() function. /// Stores the diff output in a grow array. /// -static int xdiff_out(void *priv, mmbuffer_t *mb, int nbuf) +static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv) { diffout_T *dout = (diffout_T *)priv; - char_u *p; - - // The header line always comes by itself, text lines in at least two - // parts. We drop the text part. - if (nbuf > 1) { - return 0; - } - - // sanity check - if (STRNCMP(mb[0].ptr, "@@ ", 3) != 0) { - return 0; - } + diffhunk_T *p = xmalloc(sizeof(*p)); ga_grow(&dout->dout_ga, 1); - - p = vim_strnsave((char_u *)mb[0].ptr, mb[0].size); - ((char_u **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p; + p->lnum_orig = start_a + 1; + p->count_orig = count_a; + p->lnum_new = start_b + 1; + p->count_new = count_b; + ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p; return 0; } diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index d1dd9b8309..083c868607 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -12,6 +12,7 @@ #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/digraph.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" @@ -20,7 +21,6 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/normal.h" #include "nvim/os/input.h" #include "nvim/screen.h" @@ -35,6 +35,12 @@ typedef struct digraph { result_T result; } digr_T; +static char e_digraph_must_be_just_two_characters_str[] + = N_("E1214: Digraph must be just two characters: %s"); +static char e_digraph_argument_must_be_one_character_str[] + = N_("E1215: Digraph must be one character: %s"); +static char e_digraph_setlist_argument_must_be_list_of_lists_with_two_items[] + = N_("E1216: digraph_setlist() argument must be a list of lists with two items"); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "digraph.c.generated.h" @@ -1459,7 +1465,7 @@ int do_digraph(int c) backspaced = -1; } else if (p_dg) { if (backspaced >= 0) { - c = getdigraph(backspaced, c, false); + c = digraph_get(backspaced, c, false); } backspaced = -1; @@ -1476,17 +1482,16 @@ int do_digraph(int c) char_u *get_digraph_for_char(int val_arg) { const int val = val_arg; - digr_T *dp; + const digr_T *dp; static char_u r[3]; for (int use_defaults = 0; use_defaults <= 1; use_defaults++) { if (use_defaults == 0) { - dp = (digr_T *)user_digraphs.ga_data; + dp = (const digr_T *)user_digraphs.ga_data; } else { dp = digraphdefault; } - for (int i = 0; - use_defaults ? dp->char1 != NUL : i < user_digraphs.ga_len; i++) { + for (int i = 0; use_defaults ? dp->char1 != NUL : i < user_digraphs.ga_len; i++) { if (dp->result == val) { r[0] = dp->char1; r[1] = dp->char2; @@ -1507,7 +1512,6 @@ char_u *get_digraph_for_char(int val_arg) /// @returns composed character, or NUL when ESC was used. int get_digraph(bool cmdline) { - int cc; no_mapping++; int c = plain_vgetc(); no_mapping--; @@ -1527,12 +1531,12 @@ int get_digraph(bool cmdline) add_to_showcmd(c); } no_mapping++; - cc = plain_vgetc(); + int cc = plain_vgetc(); no_mapping--; if (cc != ESC) { // ESC cancels CTRL-K - return getdigraph(c, cc, true); + return digraph_get(c, cc, true); } } return NUL; @@ -1555,25 +1559,25 @@ static int getexactdigraph(int char1, int char2, bool meta_char) } // Search user digraphs first. - digr_T *dp = (digr_T *)user_digraphs.ga_data; - for (int i = 0; i < user_digraphs.ga_len; ++i) { + const digr_T *dp = (const digr_T *)user_digraphs.ga_data; + for (int i = 0; i < user_digraphs.ga_len; i++) { if (((int)dp->char1 == char1) && ((int)dp->char2 == char2)) { retval = dp->result; break; } - ++dp; + dp++; } // Search default digraphs. if (retval == 0) { dp = digraphdefault; - for (int i = 0; dp->char1 != 0; ++i) { + for (int i = 0; dp->char1 != 0; i++) { if (((int)dp->char1 == char1) && ((int)dp->char2 == char2)) { retval = dp->result; break; } - ++dp; + dp++; } } @@ -1596,7 +1600,7 @@ static int getexactdigraph(int char1, int char2, bool meta_char) /// @param meta_char /// /// @return The digraph. -int getdigraph(int char1, int char2, bool meta_char) +int digraph_get(int char1, int char2, bool meta_char) { int retval; @@ -1609,33 +1613,63 @@ int getdigraph(int char1, int char2, bool meta_char) return retval; } +/// Add a digraph to the digraph table. +static void registerdigraph(int char1, int char2, int n) +{ + // If the digraph already exists, replace "result". + digr_T *dp = (digr_T *)user_digraphs.ga_data; + for (int i = 0; i < user_digraphs.ga_len; i++) { + if ((int)dp->char1 == char1 && (int)dp->char2 == char2) { + dp->result = n; + return; + } + dp++; + } + + // Add a new digraph to the table. + dp = GA_APPEND_VIA_PTR(digr_T, &user_digraphs); + dp->char1 = (char_u)char1; + dp->char2 = (char_u)char2; + dp->result = n; +} + +/// Check the characters are valid for a digraph. +/// If they are valid, returns true; otherwise, give an error message and +/// returns false. +bool check_digraph_chars_valid(int char1, int char2) +{ + if (char2 == 0) { + char_u msg[MB_MAXBYTES + 1]; + msg[utf_char2bytes(char1, msg)] = NUL; + semsg(_(e_digraph_must_be_just_two_characters_str), msg); + return false; + } + if (char1 == ESC || char2 == ESC) { + emsg(_("E104: Escape not allowed in digraph")); + return false; + } + return true; +} + /// Add the digraphs in the argument to the digraph table. /// format: {c1}{c2} char {c1}{c2} char ... /// /// @param str void putdigraph(char_u *str) { - char_u char1, char2; - digr_T *dp; - while (*str != NUL) { str = skipwhite(str); if (*str == NUL) { return; } - char1 = *str++; - char2 = *str++; + char_u char1 = *str++; + char_u char2 = *str++; - if (char2 == 0) { - emsg(_(e_invarg)); + if (!check_digraph_chars_valid(char1, char2)) { return; } - if ((char1 == ESC) || (char2 == ESC)) { - emsg(_("E104: Escape not allowed in digraph")); - return; - } str = skipwhite(str); if (!ascii_isdigit(*str)) { @@ -1644,25 +1678,7 @@ void putdigraph(char_u *str) } int n = getdigits_int(&str, true, 0); - // If the digraph already exists, replace the result. - dp = (digr_T *)user_digraphs.ga_data; - - int i; - for (i = 0; i < user_digraphs.ga_len; ++i) { - if (((int)dp->char1 == char1) && ((int)dp->char2 == char2)) { - dp->result = n; - break; - } - ++dp; - } - - // Add a new digraph to the table. - if (i == user_digraphs.ga_len) { - dp = GA_APPEND_VIA_PTR(digr_T, &user_digraphs); - dp->char1 = char1; - dp->char2 = char2; - dp->result = n; - } + registerdigraph(char1, char2, n); } } @@ -1678,14 +1694,13 @@ static void digraph_header(const char *msg) void listdigraphs(bool use_headers) { - digr_T *dp; result_T previous = 0; msg_putchar('\n'); - dp = digraphdefault; + const digr_T *dp = digraphdefault; - for (int i = 0; dp->char1 != NUL && !got_int; ++i) { + for (int i = 0; dp->char1 != NUL && !got_int; i++) { digr_T tmp; // May need to convert the result to 'encoding'. @@ -1693,15 +1708,14 @@ void listdigraphs(bool use_headers) tmp.char2 = dp->char2; tmp.result = getexactdigraph(tmp.char1, tmp.char2, false); - if ((tmp.result != 0) - && (tmp.result != tmp.char2)) { + if (tmp.result != 0 && tmp.result != tmp.char2) { printdigraph(&tmp, use_headers ? &previous : NULL); } dp++; fast_breakcheck(); } - dp = (digr_T *)user_digraphs.ga_data; + dp = (const digr_T *)user_digraphs.ga_data; for (int i = 0; i < user_digraphs.ga_len && !got_int; i++) { if (previous >= 0 && use_headers) { digraph_header(_("Custom")); @@ -1713,6 +1727,50 @@ void listdigraphs(bool use_headers) } } +static void digraph_getlist_appendpair(const digr_T *dp, list_T *l) +{ + list_T *l2 = tv_list_alloc(2); + tv_list_append_list(l, l2); + + char_u buf[30]; + buf[0] = dp->char1; + buf[1] = dp->char2; + buf[2] = NUL; + tv_list_append_string(l2, (char *)buf, -1); + + char_u *p = buf; + p += utf_char2bytes(dp->result, p); + *p = NUL; + tv_list_append_string(l2, (char *)buf, -1); +} + +void digraph_getlist_common(bool list_all, typval_T *rettv) +{ + tv_list_alloc_ret(rettv, (int)sizeof(digraphdefault) + user_digraphs.ga_len); + + const digr_T *dp; + + if (list_all) { + dp = digraphdefault; + for (int i = 0; dp->char1 != NUL && !got_int; i++) { + digr_T tmp; + tmp.char1 = dp->char1; + tmp.char2 = dp->char2; + tmp.result = getexactdigraph(tmp.char1, tmp.char2, false); + if (tmp.result != 0 && tmp.result != tmp.char2) { + digraph_getlist_appendpair(&tmp, rettv->vval.v_list); + } + dp++; + } + } + + dp = (const digr_T *)user_digraphs.ga_data; + for (int i = 0; i < user_digraphs.ga_len && !got_int; i++) { + digraph_getlist_appendpair(dp, rettv->vval.v_list); + dp++; + } +} + struct dg_header_entry { int dg_start; const char *dg_header; @@ -1750,11 +1808,7 @@ static void printdigraph(const digr_T *dp, result_T *previous) FUNC_ATTR_NONNULL_ARG(1) { char_u buf[30]; - char_u *p; - - int list_width; - - list_width = 13; + int list_width = 13; if (dp->result != 0) { if (previous != NULL) { @@ -1781,7 +1835,7 @@ static void printdigraph(const digr_T *dp, result_T *previous) } } - p = &buf[0]; + char_u *p = &buf[0]; *p++ = dp->char1; *p++ = dp->char2; *p++ = ' '; @@ -1807,6 +1861,147 @@ static void printdigraph(const digr_T *dp, result_T *previous) } } +/// Get the two digraph characters from a typval. +/// @return OK or FAIL. +static int get_digraph_chars(const typval_T *arg, int *char1, int *char2) +{ + char buf_chars[NUMBUFLEN]; + const char *chars = tv_get_string_buf_chk(arg, buf_chars); + const char_u *p = (const char_u *)chars; + + if (p != NULL) { + if (*p != NUL) { + *char1 = mb_cptr2char_adv(&p); + if (*p != NUL) { + *char2 = mb_cptr2char_adv(&p); + if (*p == NUL) { + if (check_digraph_chars_valid(*char1, *char2)) { + return OK; + } + return FAIL; + } + } + } + } + semsg(_(e_digraph_must_be_just_two_characters_str), chars); + return FAIL; +} + +static bool digraph_set_common(const typval_T *argchars, const typval_T *argdigraph) +{ + int char1, char2; + if (get_digraph_chars(argchars, &char1, &char2) == FAIL) { + return false; + } + + char buf_digraph[NUMBUFLEN]; + const char *digraph = tv_get_string_buf_chk(argdigraph, buf_digraph); + if (digraph == NULL) { + return false; + } + const char_u *p = (const char_u *)digraph; + int n = mb_cptr2char_adv(&p); + if (*p != NUL) { + semsg(_(e_digraph_argument_must_be_one_character_str), digraph); + return false; + } + + registerdigraph(char1, char2, n); + return true; +} + +/// "digraph_get()" function +void f_digraph_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; // Return empty string for failure + const char *digraphs = tv_get_string_chk(&argvars[0]); + + if (digraphs == NULL) { + return; + } + if (STRLEN(digraphs) != 2) { + semsg(_(e_digraph_must_be_just_two_characters_str), digraphs); + return; + } + int code = digraph_get(digraphs[0], digraphs[1], false); + + char_u buf[NUMBUFLEN]; + buf[utf_char2bytes(code, buf)] = NUL; + rettv->vval.v_string = vim_strsave(buf); +} + +/// "digraph_getlist()" function +void f_digraph_getlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool flag_list_all; + + if (argvars[0].v_type == VAR_UNKNOWN) { + flag_list_all = false; + } else { + bool error = false; + varnumber_T flag = tv_get_number_chk(&argvars[0], &error); + if (error) { + return; + } + flag_list_all = flag != 0; + } + + digraph_getlist_common(flag_list_all, rettv); +} + +/// "digraph_set()" function +void f_digraph_set(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_BOOL; + rettv->vval.v_bool = kBoolVarFalse; + + if (!digraph_set_common(&argvars[0], &argvars[1])) { + return; + } + + rettv->vval.v_bool = kBoolVarTrue; +} + +/// "digraph_setlist()" function +void f_digraph_setlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_BOOL; + rettv->vval.v_bool = kBoolVarFalse; + + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_digraph_setlist_argument_must_be_list_of_lists_with_two_items)); + return; + } + + list_T *pl = argvars[0].vval.v_list; + if (pl == NULL) { + // Empty list always results in success. + rettv->vval.v_bool = kBoolVarTrue; + return; + } + + TV_LIST_ITER_CONST(pl, pli, { + if (TV_LIST_ITEM_TV(pli)->v_type != VAR_LIST) { + emsg(_(e_digraph_setlist_argument_must_be_list_of_lists_with_two_items)); + return; + } + + list_T *l = TV_LIST_ITEM_TV(pli)->vval.v_list; + if (l == NULL || tv_list_len(l) != 2) { + emsg(_(e_digraph_setlist_argument_must_be_list_of_lists_with_two_items)); + return; + } + + if (!digraph_set_common(TV_LIST_ITEM_TV(tv_list_first(l)), + TV_LIST_ITEM_TV(TV_LIST_ITEM_NEXT(l, tv_list_first(l))))) { + return; + } + }); + + rettv->vval.v_bool = kBoolVarTrue; +} + /// structure used for b_kmap_ga.ga_data typedef struct { char_u *from; @@ -1864,8 +2059,6 @@ char *keymap_init(void) /// @param eap void ex_loadkeymap(exarg_T *eap) { - char_u *line; - char_u *p; char_u *s; #define KMAP_LLEN 200 // max length of "to" and "from" together @@ -1888,13 +2081,13 @@ void ex_loadkeymap(exarg_T *eap) // Get each line of the sourced file, break at the end. for (;;) { - line = eap->getline(0, eap->cookie, 0, true); + char_u *line = eap->getline(0, eap->cookie, 0, true); if (line == NULL) { break; } - p = skipwhite(line); + char_u *p = skipwhite(line); if ((*p != '"') && (*p != NUL)) { kmap_T *kp = GA_APPEND_VIA_PTR(kmap_T, &curbuf->b_kmap_ga); @@ -1912,7 +2105,7 @@ void ex_loadkeymap(exarg_T *eap) } xfree(kp->from); xfree(kp->to); - --curbuf->b_kmap_ga.ga_len; + curbuf->b_kmap_ga.ga_len--; } } xfree(line); @@ -1947,7 +2140,6 @@ static void keymap_unload(void) { char_u buf[KMAP_MAXLEN + 10]; char_u *save_cpo = p_cpo; - kmap_T *kp; if (!(curbuf->b_kmap_state & KEYMAP_LOADED)) { return; @@ -1957,7 +2149,7 @@ static void keymap_unload(void) p_cpo = (char_u *)"C"; // clear the ":lmap"s - kp = (kmap_T *)curbuf->b_kmap_ga.ga_data; + kmap_T *kp = (kmap_T *)curbuf->b_kmap_ga.ga_data; for (int i = 0; i < curbuf->b_kmap_ga.ga_len; i++) { vim_snprintf((char *)buf, sizeof(buf), "<buffer> %s", kp[i].from); diff --git a/src/nvim/digraph.h b/src/nvim/digraph.h index 71330ae9b1..80b4148213 100644 --- a/src/nvim/digraph.h +++ b/src/nvim/digraph.h @@ -2,6 +2,7 @@ #define NVIM_DIGRAPH_H #include "nvim/ex_cmds_defs.h" +#include "nvim/eval/funcs.h" #include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/edit.c b/src/nvim/edit.c index d505b40935..47d491033b 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -26,6 +26,7 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/keymap.h" @@ -35,7 +36,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -227,6 +227,7 @@ typedef struct insert_state { cmdarg_T *ca; int mincol; int cmdchar; + int cmdchar_todo; // cmdchar to handle once in init_prompt int startln; long count; int c; @@ -260,7 +261,7 @@ static colnr_T Insstart_blank_vcol; // vcol for first inserted blank static bool update_Insstart_orig = true; // set Insstart_orig to Insstart static char_u *last_insert = NULL; // the text of the previous insert, - // K_SPECIAL and CSI are escaped + // K_SPECIAL is escaped static int last_insert_skip; // nr of chars in front of previous insert static int new_insert_skip; // nr of chars in front of current insert static int did_restart_edit; // "restart_edit" when calling edit() @@ -290,6 +291,7 @@ static void insert_enter(InsertState *s) s->did_backspace = true; s->old_topfill = -1; s->replaceState = REPLACE; + s->cmdchar_todo = s->cmdchar; // Remember whether editing was restarted after CTRL-O did_restart_edit = restart_edit; // sleep before redrawing, needed for "CTRL-O :" that results in an @@ -385,11 +387,13 @@ static void insert_enter(InsertState *s) State = INSERT; } + may_trigger_modechanged(); stop_insert_mode = false; - // Need to recompute the cursor position, it might move when the cursor is - // on a TAB or special character. - curs_columns(curwin, true); + // need to position cursor again when on a TAB + if (gchar_cursor() == TAB) { + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); + } // Enable langmap or IME, indicated by 'iminsert'. // Note that IME may enabled/disabled without us noticing here, thus the @@ -584,7 +588,8 @@ static int insert_check(VimState *state) } if (bt_prompt(curbuf)) { - init_prompt(s->cmdchar); + init_prompt(s->cmdchar_todo); + s->cmdchar_todo = NUL; } // If we inserted a character at the last position of the last line in the @@ -640,7 +645,10 @@ static int insert_check(VimState *state) update_curswant(); s->old_topline = curwin->w_topline; s->old_topfill = curwin->w_topfill; - s->lastc = s->c; // remember previous char for CTRL-D + + if (s->c != K_EVENT) { + s->lastc = s->c; // remember previous char for CTRL-D + } // After using CTRL-G U the next cursor key will not break undo. if (dont_sync_undo == kNone) { @@ -654,10 +662,21 @@ static int insert_check(VimState *state) static int insert_execute(VimState *state, int key) { + InsertState *const s = (InsertState *)state; + if (stop_insert_mode) { + // Insert mode ended, possibly from a callback. + if (key != K_IGNORE && key != K_NOP) { + vungetc(key); + } + s->count = 0; + s->nomove = true; + ins_compl_prep(ESC); + return 0; + } + if (key == K_IGNORE || key == K_NOP) { return -1; // get another key } - InsertState *s = (InsertState *)state; s->c = key; // Don't want K_EVENT with cursorhold for the second key, e.g., after CTRL-V. @@ -821,6 +840,16 @@ static int insert_execute(VimState *state, int key) return insert_handle_key(s); } + +/// Return true when need to go to Insert mode because of 'insertmode'. +/// +/// Don't do this when still processing a command or a mapping. +/// Don't do this when inside a ":normal" command. +bool goto_im(void) +{ + return p_im && stuff_empty() && typebuf_typed(); +} + static int insert_handle_key(InsertState *s) { // The big switch to handle a character in insert mode. @@ -886,7 +915,7 @@ static int insert_handle_key(InsertState *s) ins_ctrl_o(); // don't move the cursor left when 'virtualedit' has "onemore". - if (ve_flags & VE_ONEMORE) { + if (get_ve_flags() & VE_ONEMORE) { ins_at_eol = false; s->nomove = true; } @@ -983,6 +1012,15 @@ static int insert_handle_key(InsertState *s) break; case Ctrl_W: // delete word before the cursor + if (bt_prompt(curbuf) && (mod_mask & MOD_MASK_SHIFT) == 0) { + // In a prompt window CTRL-W is used for window commands. + // Use Shift-CTRL-W to delete a word. + stuffcharReadbuff(Ctrl_W); + restart_edit = 'A'; + s->nomove = true; + s->count = 0; + return 0; + } s->did_backspace = ins_bs(s->c, BACKSPACE_WORD, &s->inserted_space); auto_format(false, true); break; @@ -1044,13 +1082,21 @@ static int insert_handle_key(InsertState *s) case K_COMMAND: // some command do_cmdline(NULL, getcmdkeycmd, NULL, 0); + goto check_pum; + + case K_LUA: + map_execute_lua(); check_pum: + // nvim_select_popupmenu_item() can be called from the handling of + // K_EVENT, K_COMMAND, or K_LUA. // TODO(bfredl): Not entirely sure this indirection is necessary // but doing like this ensures using nvim_select_popupmenu_item is // equivalent to selecting the item with a typed key. if (pum_want.active) { if (pum_visible()) { + // Set this to NULL so that ins_complete() will update the message. + edit_submode_extra = NULL; insert_do_complete(s); if (pum_want.finish) { // accept the item and stop completion @@ -1347,6 +1393,7 @@ static void insert_do_complete(InsertState *s) compl_cont_status = 0; } compl_busy = false; + can_si = true; // allow smartindenting } static void insert_do_cindent(InsertState *s) @@ -1435,15 +1482,13 @@ bool edit(int cmdchar, bool startln, long count) /// @param ready not busy with something static void ins_redraw(bool ready) { - bool conceal_cursor_moved = false; - if (char_avail()) { return; } // Trigger CursorMoved if the cursor moved. Not when the popup menu is // visible, the command might delete it. - if (ready && (has_event(EVENT_CURSORMOVEDI) || curwin->w_p_cole > 0) + if (ready && has_event(EVENT_CURSORMOVEDI) && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor) && !pum_visible()) { // Need to update the screen first, to make sure syntax @@ -1453,13 +1498,10 @@ static void ins_redraw(bool ready) if (syntax_present(curwin) && must_redraw) { update_screen(0); } - if (has_event(EVENT_CURSORMOVEDI)) { - // Make sure curswant is correct, an autocommand may call - // getcurpos() - update_curswant(); - ins_apply_autocmds(EVENT_CURSORMOVEDI); - } - conceal_cursor_moved = true; + // Make sure curswant is correct, an autocommand may call + // getcurpos() + update_curswant(); + ins_apply_autocmds(EVENT_CURSORMOVEDI); curwin->w_last_cursormoved = curwin->w_cursor; } @@ -1501,10 +1543,9 @@ static void ins_redraw(bool ready) } } - // Trigger Scroll if viewport changed. - if (ready && has_event(EVENT_WINSCROLLED) - && win_did_scroll(curwin)) { - do_autocmd_winscrolled(curwin); + if (ready) { + // Trigger Scroll if viewport changed. + may_trigger_winscrolled(); } // Trigger BufModified if b_changed_invalid is set. @@ -1515,11 +1556,6 @@ static void ins_redraw(bool ready) curbuf->b_changed_invalid = false; } - if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin) - && conceal_cursor_moved) { - redrawWinline(curwin, curwin->w_cursor.lnum); - } - pum_check_clear(); if (must_redraw) { update_screen(0); @@ -1568,8 +1604,8 @@ static void ins_ctrl_v(void) */ static int pc_status; #define PC_STATUS_UNSET 0 // pc_bytes was not set -#define PC_STATUS_RIGHT 1 // right halve of double-wide char -#define PC_STATUS_LEFT 2 // left halve of double-wide char +#define PC_STATUS_RIGHT 1 // right half of double-wide char +#define PC_STATUS_LEFT 2 // left half of double-wide char #define PC_STATUS_SET 3 // pc_bytes was filled static char_u pc_bytes[MB_MAXBYTES + 1]; // saved bytes static int pc_attr; @@ -1652,11 +1688,22 @@ static void init_prompt(int cmdchar_todo) coladvance(MAXCOL); changed_bytes(curbuf->b_ml.ml_line_count, 0); } + + // Insert always starts after the prompt, allow editing text after it. + if (Insstart_orig.lnum != curwin->w_cursor.lnum || Insstart_orig.col != (colnr_T)STRLEN(prompt)) { + Insstart.lnum = curwin->w_cursor.lnum; + Insstart.col = (colnr_T)STRLEN(prompt); + Insstart_orig = Insstart; + Insstart_textlen = Insstart.col; + Insstart_blank_vcol = MAXCOL; + arrow_used = false; + } + if (cmdchar_todo == 'A') { coladvance(MAXCOL); } - if (cmdchar_todo == 'I' || curwin->w_cursor.col <= (int)STRLEN(prompt)) { - curwin->w_cursor.col = STRLEN(prompt); + if (curwin->w_cursor.col < (colnr_T)STRLEN(prompt)) { + curwin->w_cursor.col = (colnr_T)STRLEN(prompt); } // Make sure the cursor is in a valid position. check_cursor(); @@ -2048,6 +2095,8 @@ static void ins_ctrl_x(void) // CTRL-V look like CTRL-N ctrl_x_mode = CTRL_X_CMDLINE_CTRL_X; } + + may_trigger_modechanged(); } // Whether other than default completion has been selected. @@ -2660,6 +2709,7 @@ void set_completion(colnr_T startcol, list_T *list) show_pum(save_w_wrow, save_w_leftcol); } + may_trigger_modechanged(); ui_flush(); } @@ -2715,12 +2765,13 @@ static bool pum_enough_matches(void) static void trigger_complete_changed_event(int cur) { static bool recursive = false; + save_v_event_T save_v_event; if (recursive) { return; } - dict_T *v_event = get_vim_var_dict(VV_EVENT); + dict_T *v_event = get_v_event(&save_v_event); if (cur < 0) { tv_dict_add_dict(v_event, S_LEN("completed_item"), tv_dict_alloc()); } else { @@ -2736,7 +2787,7 @@ static void trigger_complete_changed_event(int cur) textlock--; recursive = false; - tv_dict_clear(v_event); + restore_v_event(v_event, &save_v_event); } /// Show the popup menu for the list of matches. @@ -3569,7 +3620,7 @@ static bool ins_compl_prep(int c) // Ignore end of Select mode mapping and mouse scroll buttons. if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT - || c == K_COMMAND) { + || c == K_COMMAND || c == K_LUA) { return retval; } @@ -3838,6 +3889,8 @@ static bool ins_compl_prep(int c) ins_apply_autocmds(EVENT_COMPLETEDONE); } + may_trigger_modechanged(); + /* reset continue_* if we left expansion-mode, if we stay they'll be * (re)set properly in ins_complete() */ if (!vim_is_ctrl_x_key(c)) { @@ -3919,7 +3972,8 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag) /// Get the user-defined completion function name for completion 'type' -static char_u *get_complete_funcname(int type) { +static char_u *get_complete_funcname(int type) +{ switch (type) { case CTRL_X_FUNCTION: return curbuf->b_p_cfu; @@ -4586,6 +4640,8 @@ static int ins_compl_get_exp(pos_T *ini) compl_curr_match = compl_old_match; } } + may_trigger_modechanged(); + return i; } @@ -4930,7 +4986,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func) */ static int ins_compl_key2dir(int c) { - if (c == K_EVENT || c == K_COMMAND) { + if (c == K_EVENT || c == K_COMMAND || c == K_LUA) { return pum_want.item < pum_selected_item ? BACKWARD : FORWARD; } if (c == Ctrl_P || c == Ctrl_L @@ -4960,7 +5016,7 @@ static int ins_compl_key2count(int c) { int h; - if (c == K_EVENT || c == K_COMMAND) { + if (c == K_EVENT || c == K_COMMAND || c == K_LUA) { int offset = pum_want.item - pum_selected_item; return abs(offset); } @@ -4994,6 +5050,7 @@ static bool ins_compl_use_match(int c) return false; case K_EVENT: case K_COMMAND: + case K_LUA: return pum_want.active && pum_want.insert; } return true; @@ -5051,10 +5108,10 @@ static int ins_complete(int c, bool enable_pum) || ctrl_x_mode == CTRL_X_PATH_PATTERNS || ctrl_x_mode == CTRL_X_PATH_DEFINES) { if (compl_startpos.lnum != curwin->w_cursor.lnum) { - /* line (probably) wrapped, set compl_startpos to the - * first non_blank in the line, if it is not a wordchar - * include it to get a better pattern, but then we don't - * want the "\\<" prefix, check it bellow */ + // line (probably) wrapped, set compl_startpos to the + // first non_blank in the line, if it is not a wordchar + // include it to get a better pattern, but then we don't + // want the "\\<" prefix, check it below. compl_col = (colnr_T)getwhitecols(line); compl_startpos.col = compl_col; compl_startpos.lnum = curwin->w_cursor.lnum; @@ -5231,7 +5288,7 @@ static int ins_complete(int c, bool enable_pum) funcname = get_complete_funcname(ctrl_x_mode); if (*funcname == NUL) { semsg(_(e_notset), ctrl_x_mode == CTRL_X_FUNCTION - ? "completefunc" : "omnifunc"); + ? "completefunc" : "omnifunc"); // restore did_ai, so that adding comment leader works did_ai = save_did_ai; return FAIL; @@ -5572,8 +5629,12 @@ int get_literal(void) i = 0; for (;;) { nc = plain_vgetc(); - if (!(State & CMDLINE) - && MB_BYTE2LEN_CHECK(nc) == 1) { + if ((mod_mask & ~MOD_MASK_SHIFT) != 0) { + // A character with non-Shift modifiers should not be a valid + // character for i_CTRL-V_digit. + break; + } + if (!(State & CMDLINE) && MB_BYTE2LEN_CHECK(nc) == 1) { add_to_showcmd(nc); } if (nc == 'x' || nc == 'X') { @@ -5639,6 +5700,8 @@ int get_literal(void) --no_mapping; if (nc) { vungetc(nc); + // A character typed with i_CTRL-V_digit cannot have modifiers. + mod_mask = 0; } got_int = false; // CTRL-C typed after CTRL-V is not an interrupt return cc; @@ -5950,6 +6013,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for char_u *saved_text = NULL; colnr_T col; colnr_T end_col; + bool did_do_comment = false; virtcol = get_nolist_virtcol() + char2cells(c != NUL ? c : gchar_cursor()); @@ -6065,8 +6129,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for if (curwin->w_cursor.col <= (colnr_T)wantcol) { break; } - } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) - && fo_multibyte) { + } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { int ncc; bool allow_break; @@ -6223,11 +6286,18 @@ static void internal_format(int textwidth, int second_indent, int flags, int for + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) + (do_comments ? OPENLINE_DO_COM : 0) + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0), - ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent)); + ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), + &did_do_comment); if (!(flags & INSCHAR_COM_LIST)) { old_indent = 0; } + // If a comment leader was inserted, may also do this on a following + // line. + if (did_do_comment) { + no_leader = false; + } + replace_offset = 0; if (first_line) { if (!(flags & INSCHAR_COM_LIST)) { @@ -6564,7 +6634,7 @@ static void spell_back_to_badword(void) int stop_arrow(void) { if (arrow_used) { - Insstart = curwin->w_cursor; //new insertion starts here + Insstart = curwin->w_cursor; // new insertion starts here if (Insstart.col > Insstart_orig.col && !ins_need_undo) { // Don't update the original insert position when moved to the // right, except when nothing was inserted yet. @@ -6758,7 +6828,7 @@ void free_last_insert(void) /// Add character "c" to buffer "s" /// -/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte +/// Escapes the special meaning of K_SPECIAL, handles multi-byte /// characters. /// /// @param[in] c Character to add. @@ -6772,7 +6842,7 @@ char_u *add_char2buf(int c, char_u *s) const int len = utf_char2bytes(c, temp); for (int i = 0; i < len; i++) { c = temp[i]; - // Need to escape K_SPECIAL and CSI like in the typeahead buffer. + // Need to escape K_SPECIAL like in the typeahead buffer. if (c == K_SPECIAL) { *s++ = K_SPECIAL; *s++ = KS_SPECIAL; @@ -6846,8 +6916,7 @@ int oneright(void) // move "l" bytes right, but don't end up on the NUL, unless 'virtualedit' // contains "onemore". - if (ptr[l] == NUL - && (ve_flags & VE_ONEMORE) == 0) { + if (ptr[l] == NUL && (get_ve_flags() & VE_ONEMORE) == 0) { return FAIL; } curwin->w_cursor.col += l; @@ -7054,9 +7123,7 @@ int stuff_inserted(int c, long count, int no_esc) stuffReadbuff((const char *)ptr); // A trailing "0" is inserted as "<C-V>048", "^" as "<C-V>^". if (last) { - stuffReadbuff((last == '0' - ? "\026\060\064\070" - : "\026^")); + stuffReadbuff(last == '0' ? "\026\060\064\070" : "\026^"); } } while (--count > 0); @@ -7260,21 +7327,21 @@ static void mb_replace_pop_ins(int cc) // Not a multi-byte char, put it back. replace_push(c); break; + } + + buf[0] = c; + assert(n > 1); + for (i = 1; i < n; i++) { + buf[i] = replace_pop(); + } + if (utf_iscomposing(utf_ptr2char(buf))) { + ins_bytes_len(buf, n); } else { - buf[0] = c; - assert(n > 1); - for (i = 1; i < n; i++) { - buf[i] = replace_pop(); - } - if (utf_iscomposing(utf_ptr2char(buf))) { - ins_bytes_len(buf, n); - } else { - // Not a composing char, put it back. - for (i = n - 1; i >= 0; i--) { - replace_push(buf[i]); - } - break; + // Not a composing char, put it back. + for (i = n - 1; i >= 0; i--) { + replace_push(buf[i]); } + break; } } } @@ -7630,16 +7697,34 @@ int hkmap(int c) KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN, PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV, }; - static char_u map[26] = - { (char_u)hALEF /*a*/, (char_u)BET /*b*/, (char_u)hKAF /*c*/, - (char_u)DALET /*d*/, (char_u)-1 /*e*/, (char_u)PEIsofit /*f*/, - (char_u)GIMEL /*g*/, (char_u)HEI /*h*/, (char_u)IUD /*i*/, - (char_u)HET /*j*/, (char_u)KOF /*k*/, (char_u)LAMED /*l*/, - (char_u)MEM /*m*/, (char_u)NUN /*n*/, (char_u)SAMEH /*o*/, - (char_u)PEI /*p*/, (char_u)-1 /*q*/, (char_u)RESH /*r*/, - (char_u)ZAIN /*s*/, (char_u)TAV /*t*/, (char_u)TET /*u*/, - (char_u)VAV /*v*/, (char_u)hSHIN /*w*/, (char_u)-1 /*x*/, - (char_u)AIN /*y*/, (char_u)ZADI /*z*/ }; + static char_u map[26] = { + (char_u)hALEF, // a + (char_u)BET, // b + (char_u)hKAF, // c + (char_u)DALET, // d + (char_u)-1, // e + (char_u)PEIsofit, // f + (char_u)GIMEL, // g + (char_u)HEI, // h + (char_u)IUD, // i + (char_u)HET, // j + (char_u)KOF, // k + (char_u)LAMED, // l + (char_u)MEM, // m + (char_u)NUN, // n + (char_u)SAMEH, // o + (char_u)PEI, // p + (char_u)-1, // q + (char_u)RESH, // r + (char_u)ZAIN, // s + (char_u)TAV, // t + (char_u)TET, // u + (char_u)VAV, // v + (char_u)hSHIN, // w + (char_u)-1, // x + (char_u)AIN, // y + (char_u)ZADI, // z + }; if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') { return (int)(map[CharOrd(c)] - 1 + p_aleph); @@ -7683,7 +7768,7 @@ int hkmap(int c) case ';': c = 't'; break; default: { - static char str[] = "zqbcxlsjphmkwonu ydafe rig"; + static char_u str[] = "zqbcxlsjphmkwonu ydafe rig"; if (c < 'a' || c > 'z') { return c; @@ -7951,7 +8036,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) && !VIsual_active )) && !revins_on) { - if (curwin->w_cursor.coladd > 0 || ve_flags == VE_ALL) { + if (curwin->w_cursor.coladd > 0 || get_ve_flags() == VE_ALL) { oneleft(); if (restart_edit != NUL) { curwin->w_cursor.coladd++; @@ -7965,8 +8050,11 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) State = NORMAL; - // need to position cursor again (e.g. when on a TAB ) - changed_cline_bef_curs(); + may_trigger_modechanged(); + // need to position cursor again when on a TAB + if (gchar_cursor() == TAB) { + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); + } setmouse(); ui_cursor_shape(); // may show different cursor shape @@ -8066,6 +8154,7 @@ static void ins_insert(int replaceState) } else { State = replaceState | (State & LANGMAP); } + may_trigger_modechanged(); AppendCharToRedobuff(K_INS); showmode(); ui_cursor_shape(); // may show different cursor shape @@ -8202,6 +8291,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) int in_indent; int oldState; int cpc[MAX_MCO]; // composing characters + bool call_fix_indent = false; // can't delete anything in an empty file // can't backup past first character in buffer @@ -8211,7 +8301,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) || (!revins_on && ((curwin->w_cursor.lnum == 1 && curwin->w_cursor.col == 0) || (!can_bs(BS_START) - && (arrow_used + && ((arrow_used && !bt_prompt(curbuf)) || (curwin->w_cursor.lnum == Insstart_orig.lnum && curwin->w_cursor.col <= Insstart_orig.col))) || (!can_bs(BS_INDENT) && !arrow_used && ai_col > 0 @@ -8345,6 +8435,8 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) beginline(BL_WHITE); if (curwin->w_cursor.col < save_col) { mincol = curwin->w_cursor.col; + // should now fix the indent to match with the previous line + call_fix_indent = true; } curwin->w_cursor.col = save_col; } @@ -8479,6 +8571,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) if (curwin->w_cursor.col <= 1) { did_ai = false; } + + if (call_fix_indent) { + fix_indent(); + } + // It's a little strange to put backspaces into the redo // buffer, but it makes auto-indent a lot easier to deal // with. @@ -9093,7 +9190,7 @@ static bool ins_eol(int c) AppendToRedobuff(NL_STR); bool i = open_line(FORWARD, has_format_option(FO_RET_COMS) ? OPENLINE_DO_COM : 0, - old_indent); + old_indent, NULL); old_indent = 0; can_cindent = true; // When inserting a line the cursor line must never be in a closed fold. @@ -9163,7 +9260,7 @@ static int ins_digraph(void) } if (cc != ESC) { AppendToRedobuff(CTRL_V_STR); - c = getdigraph(c, cc, true); + c = digraph_get(c, cc, true); clear_showcmd(); return c; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b97282dd1a..3e855ece15 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6,6 +6,7 @@ */ #include <math.h> +#include <stdlib.h> #include "auto/config.h" @@ -27,20 +28,18 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" -#include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/input.h" -#include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/path.h" #include "nvim/quickfix.h" @@ -67,9 +66,9 @@ static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); static char *e_nowhitespace = N_("E274: No white space allowed before parenthesis"); -static char *e_invalwindow = N_("E957: Invalid window number"); static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); static char *e_write2 = N_("E80: Error while writing: %s"); +static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required"); // TODO(ZyX-I): move to eval/executor static char *e_letwrong = N_("E734: Wrong variable type for %s="); @@ -113,9 +112,11 @@ typedef struct { int fi_semicolon; // TRUE if ending in '; var]' int fi_varcount; // nr of variables in the list listwatch_T fi_lw; // keep an eye on the item used. - list_T *fi_list; // list being used + list_T *fi_list; // list being used int fi_bi; // index of blob blob_T *fi_blob; // blob being used + char_u *fi_string; // copy of string being used + int fi_byte_idx; // byte index in fi_string } forinfo_T; // values for vv_flags: @@ -134,13 +135,15 @@ typedef struct { .vv_flags = flags, \ } +#define VIMVAR_KEY_LEN 16 // Maximum length of the key of v:variables + // Array to hold the value of v: variables. // The value is in a dictitem, so that it can also be used in the v: scope. // The reason to use this table anyway is for very quick access to the // variables with the VV_ defines. static struct vimvar { char *vv_name; ///< Name of the variable, without v:. - TV_DICTITEM_STRUCT(17) vv_di; ///< Value and name for key (max 16 chars). + TV_DICTITEM_STRUCT(VIMVAR_KEY_LEN + 1) vv_di; ///< Value and name for key (max 16 chars). char vv_flags; ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX. } vimvars[] = { @@ -150,100 +153,103 @@ static struct vimvar { // VV_SEND_SERVER "servername" // VV_REG "register" // VV_OP "operator" - VV(VV_COUNT, "count", VAR_NUMBER, VV_RO), - VV(VV_COUNT1, "count1", VAR_NUMBER, VV_RO), - VV(VV_PREVCOUNT, "prevcount", VAR_NUMBER, VV_RO), - VV(VV_ERRMSG, "errmsg", VAR_STRING, 0), - VV(VV_WARNINGMSG, "warningmsg", VAR_STRING, 0), - VV(VV_STATUSMSG, "statusmsg", VAR_STRING, 0), - VV(VV_SHELL_ERROR, "shell_error", VAR_NUMBER, VV_RO), - VV(VV_THIS_SESSION, "this_session", VAR_STRING, 0), - VV(VV_VERSION, "version", VAR_NUMBER, VV_COMPAT+VV_RO), - VV(VV_LNUM, "lnum", VAR_NUMBER, VV_RO_SBX), - VV(VV_TERMRESPONSE, "termresponse", VAR_STRING, VV_RO), - VV(VV_FNAME, "fname", VAR_STRING, VV_RO), - VV(VV_LANG, "lang", VAR_STRING, VV_RO), - VV(VV_LC_TIME, "lc_time", VAR_STRING, VV_RO), - VV(VV_CTYPE, "ctype", VAR_STRING, VV_RO), - VV(VV_CC_FROM, "charconvert_from", VAR_STRING, VV_RO), - VV(VV_CC_TO, "charconvert_to", VAR_STRING, VV_RO), - VV(VV_FNAME_IN, "fname_in", VAR_STRING, VV_RO), - VV(VV_FNAME_OUT, "fname_out", VAR_STRING, VV_RO), - VV(VV_FNAME_NEW, "fname_new", VAR_STRING, VV_RO), - VV(VV_FNAME_DIFF, "fname_diff", VAR_STRING, VV_RO), - VV(VV_CMDARG, "cmdarg", VAR_STRING, VV_RO), - VV(VV_FOLDSTART, "foldstart", VAR_NUMBER, VV_RO_SBX), - VV(VV_FOLDEND, "foldend", VAR_NUMBER, VV_RO_SBX), - VV(VV_FOLDDASHES, "folddashes", VAR_STRING, VV_RO_SBX), - VV(VV_FOLDLEVEL, "foldlevel", VAR_NUMBER, VV_RO_SBX), - VV(VV_PROGNAME, "progname", VAR_STRING, VV_RO), - VV(VV_SEND_SERVER, "servername", VAR_STRING, VV_RO), - VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), - VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), - VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), - VV(VV_REG, "register", VAR_STRING, VV_RO), - VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), - VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), - VV(VV_VAL, "val", VAR_UNKNOWN, VV_RO), - VV(VV_KEY, "key", VAR_UNKNOWN, VV_RO), - VV(VV_PROFILING, "profiling", VAR_NUMBER, VV_RO), - VV(VV_FCS_REASON, "fcs_reason", VAR_STRING, VV_RO), - VV(VV_FCS_CHOICE, "fcs_choice", VAR_STRING, 0), - VV(VV_BEVAL_BUFNR, "beval_bufnr", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_WINNR, "beval_winnr", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_WINID, "beval_winid", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_LNUM, "beval_lnum", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_COL, "beval_col", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_TEXT, "beval_text", VAR_STRING, VV_RO), - VV(VV_SCROLLSTART, "scrollstart", VAR_STRING, 0), - VV(VV_SWAPNAME, "swapname", VAR_STRING, VV_RO), - VV(VV_SWAPCHOICE, "swapchoice", VAR_STRING, 0), - VV(VV_SWAPCOMMAND, "swapcommand", VAR_STRING, VV_RO), - VV(VV_CHAR, "char", VAR_STRING, 0), - VV(VV_MOUSE_WIN, "mouse_win", VAR_NUMBER, 0), - VV(VV_MOUSE_WINID, "mouse_winid", VAR_NUMBER, 0), - VV(VV_MOUSE_LNUM, "mouse_lnum", VAR_NUMBER, 0), - VV(VV_MOUSE_COL, "mouse_col", VAR_NUMBER, 0), - VV(VV_OP, "operator", VAR_STRING, VV_RO), - VV(VV_SEARCHFORWARD, "searchforward", VAR_NUMBER, 0), - VV(VV_HLSEARCH, "hlsearch", VAR_NUMBER, 0), - VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), - VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), - VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), - VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), - VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), - VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), - VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO), - VV(VV_ERRORS, "errors", VAR_LIST, 0), - VV(VV_FALSE, "false", VAR_BOOL, VV_RO), - VV(VV_TRUE, "true", VAR_BOOL, VV_RO), - VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), - VV(VV_NUMBERMAX, "numbermax", VAR_NUMBER, VV_RO), - VV(VV_NUMBERMIN, "numbermin", VAR_NUMBER, VV_RO), - VV(VV_NUMBERSIZE, "numbersize", VAR_NUMBER, VV_RO), - VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), - VV(VV_TESTING, "testing", VAR_NUMBER, 0), - VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), - VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO), - VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO), - VV(VV_TYPE_LIST, "t_list", VAR_NUMBER, VV_RO), - VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO), - VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO), - VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), - VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO), - VV(VV_EVENT, "event", VAR_DICT, VV_RO), - VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), - VV(VV_ARGV, "argv", VAR_LIST, VV_RO), - VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), - VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), + VV(VV_COUNT, "count", VAR_NUMBER, VV_RO), + VV(VV_COUNT1, "count1", VAR_NUMBER, VV_RO), + VV(VV_PREVCOUNT, "prevcount", VAR_NUMBER, VV_RO), + VV(VV_ERRMSG, "errmsg", VAR_STRING, 0), + VV(VV_WARNINGMSG, "warningmsg", VAR_STRING, 0), + VV(VV_STATUSMSG, "statusmsg", VAR_STRING, 0), + VV(VV_SHELL_ERROR, "shell_error", VAR_NUMBER, VV_RO), + VV(VV_THIS_SESSION, "this_session", VAR_STRING, 0), + VV(VV_VERSION, "version", VAR_NUMBER, VV_COMPAT+VV_RO), + VV(VV_LNUM, "lnum", VAR_NUMBER, VV_RO_SBX), + VV(VV_TERMRESPONSE, "termresponse", VAR_STRING, VV_RO), + VV(VV_FNAME, "fname", VAR_STRING, VV_RO), + VV(VV_LANG, "lang", VAR_STRING, VV_RO), + VV(VV_LC_TIME, "lc_time", VAR_STRING, VV_RO), + VV(VV_CTYPE, "ctype", VAR_STRING, VV_RO), + VV(VV_CC_FROM, "charconvert_from", VAR_STRING, VV_RO), + VV(VV_CC_TO, "charconvert_to", VAR_STRING, VV_RO), + VV(VV_FNAME_IN, "fname_in", VAR_STRING, VV_RO), + VV(VV_FNAME_OUT, "fname_out", VAR_STRING, VV_RO), + VV(VV_FNAME_NEW, "fname_new", VAR_STRING, VV_RO), + VV(VV_FNAME_DIFF, "fname_diff", VAR_STRING, VV_RO), + VV(VV_CMDARG, "cmdarg", VAR_STRING, VV_RO), + VV(VV_FOLDSTART, "foldstart", VAR_NUMBER, VV_RO_SBX), + VV(VV_FOLDEND, "foldend", VAR_NUMBER, VV_RO_SBX), + VV(VV_FOLDDASHES, "folddashes", VAR_STRING, VV_RO_SBX), + VV(VV_FOLDLEVEL, "foldlevel", VAR_NUMBER, VV_RO_SBX), + VV(VV_PROGNAME, "progname", VAR_STRING, VV_RO), + VV(VV_SEND_SERVER, "servername", VAR_STRING, VV_RO), + VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), + VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), + VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), + VV(VV_REG, "register", VAR_STRING, VV_RO), + VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), + VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), + VV(VV_VAL, "val", VAR_UNKNOWN, VV_RO), + VV(VV_KEY, "key", VAR_UNKNOWN, VV_RO), + VV(VV_PROFILING, "profiling", VAR_NUMBER, VV_RO), + VV(VV_FCS_REASON, "fcs_reason", VAR_STRING, VV_RO), + VV(VV_FCS_CHOICE, "fcs_choice", VAR_STRING, 0), + VV(VV_BEVAL_BUFNR, "beval_bufnr", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_WINNR, "beval_winnr", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_WINID, "beval_winid", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_LNUM, "beval_lnum", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_COL, "beval_col", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_TEXT, "beval_text", VAR_STRING, VV_RO), + VV(VV_SCROLLSTART, "scrollstart", VAR_STRING, 0), + VV(VV_SWAPNAME, "swapname", VAR_STRING, VV_RO), + VV(VV_SWAPCHOICE, "swapchoice", VAR_STRING, 0), + VV(VV_SWAPCOMMAND, "swapcommand", VAR_STRING, VV_RO), + VV(VV_CHAR, "char", VAR_STRING, 0), + VV(VV_MOUSE_WIN, "mouse_win", VAR_NUMBER, 0), + VV(VV_MOUSE_WINID, "mouse_winid", VAR_NUMBER, 0), + VV(VV_MOUSE_LNUM, "mouse_lnum", VAR_NUMBER, 0), + VV(VV_MOUSE_COL, "mouse_col", VAR_NUMBER, 0), + VV(VV_OP, "operator", VAR_STRING, VV_RO), + VV(VV_SEARCHFORWARD, "searchforward", VAR_NUMBER, 0), + VV(VV_HLSEARCH, "hlsearch", VAR_NUMBER, 0), + VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), + VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), + VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), + VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), + VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), + VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), + VV(VV_OPTION_OLDLOCAL, "option_oldlocal", VAR_STRING, VV_RO), + VV(VV_OPTION_OLDGLOBAL, "option_oldglobal", VAR_STRING, VV_RO), + VV(VV_OPTION_COMMAND, "option_command", VAR_STRING, VV_RO), + VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO), + VV(VV_ERRORS, "errors", VAR_LIST, 0), + VV(VV_FALSE, "false", VAR_BOOL, VV_RO), + VV(VV_TRUE, "true", VAR_BOOL, VV_RO), + VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), + VV(VV_NUMBERMAX, "numbermax", VAR_NUMBER, VV_RO), + VV(VV_NUMBERMIN, "numbermin", VAR_NUMBER, VV_RO), + VV(VV_NUMBERSIZE, "numbersize", VAR_NUMBER, VV_RO), + VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), + VV(VV_TESTING, "testing", VAR_NUMBER, 0), + VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), + VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO), + VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO), + VV(VV_TYPE_LIST, "t_list", VAR_NUMBER, VV_RO), + VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO), + VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO), + VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), + VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO), + VV(VV_EVENT, "event", VAR_DICT, VV_RO), + VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), + VV(VV_ARGV, "argv", VAR_LIST, VV_RO), + VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), + VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), // Neovim - VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), - VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), - VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO), - VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), - VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), - VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO), - VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), + VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), + VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), + VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO), + VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), + VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), + VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO), + VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), }; #undef VV @@ -298,7 +304,32 @@ const list_T *eval_msgpack_type_lists[] = { [kMPExt] = NULL, }; -// Return "n1" divided by "n2", taking care of dividing by zero. +dict_T *get_v_event(save_v_event_T *sve) +{ + dict_T *v_event = get_vim_var_dict(VV_EVENT); + + if (v_event->dv_hashtab.ht_used > 0) { + // recursive use of v:event, save, make empty and restore later + sve->sve_did_save = true; + sve->sve_hashtab = v_event->dv_hashtab; + hash_init(&v_event->dv_hashtab); + } else { + sve->sve_did_save = false; + } + return v_event; +} + +void restore_v_event(dict_T *v_event, save_v_event_T *sve) +{ + tv_dict_free_contents(v_event); + if (sve->sve_did_save) { + v_event->dv_hashtab = sve->sve_hashtab; + } else { + hash_init(&v_event->dv_hashtab); + } +} + +/// @return "n1" divided by "n2", taking care of dividing by zero. varnumber_T num_divide(varnumber_T n1, varnumber_T n2) FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { @@ -319,7 +350,7 @@ varnumber_T num_divide(varnumber_T n1, varnumber_T n2) return result; } -// Return "n1" modulus "n2", taking care of dividing by zero. +/// @return "n1" modulus "n2", taking care of dividing by zero. varnumber_T num_modulus(varnumber_T n1, varnumber_T n2) FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { @@ -327,9 +358,7 @@ varnumber_T num_modulus(varnumber_T n1, varnumber_T n2) return (n2 == 0) ? 0 : (n1 % n2); } -/* - * Initialize the global and v: variables. - */ +/// Initialize the global and v: variables. void eval_init(void) { vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; @@ -344,7 +373,7 @@ void eval_init(void) for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { p = &vimvars[i]; - assert(STRLEN(p->vv_name) <= 16); + assert(STRLEN(p->vv_name) <= VIMVAR_KEY_LEN); STRCPY(p->vv_di.di_key, p->vv_name); if (p->vv_flags & VV_RO) { p->vv_di.di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; @@ -469,10 +498,8 @@ void eval_clear(void) #endif -/* - * Set an internal variable to a string value. Creates the variable if it does - * not already exist. - */ +/// Set an internal variable to a string value. Creates the variable if it does +/// not already exist. void set_internal_string_var(const char *name, char_u *value) FUNC_ATTR_NONNULL_ARG(1) { @@ -490,9 +517,10 @@ static char_u *redir_endp = NULL; static char_u *redir_varname = NULL; /// Start recording command output to a variable -/// Returns OK if successfully completed the setup. FAIL otherwise. /// /// @param append append to an existing variable +/// +/// @return OK if successfully completed the setup. FAIL otherwise. int var_redir_start(char_u *name, int append) { int save_emsg; @@ -553,15 +581,13 @@ int var_redir_start(char_u *name, int append) return OK; } -/* - * Append "value[value_len]" to the variable set by var_redir_start(). - * The actual appending is postponed until redirection ends, because the value - * appended may in fact be the string we write to, changing it may cause freed - * memory to be used: - * :redir => foo - * :let foo - * :redir END - */ +/// Append "value[value_len]" to the variable set by var_redir_start(). +/// The actual appending is postponed until redirection ends, because the value +/// appended may in fact be the string we write to, changing it may cause freed +/// memory to be used: +/// :redir => foo +/// :let foo +/// :redir END void var_redir_str(char_u *value, int value_len) { int len; @@ -581,10 +607,8 @@ void var_redir_str(char_u *value, int value_len) redir_ga.ga_len += len; } -/* - * Stop redirecting command output to a variable. - * Frees the allocated memory. - */ +/// Stop redirecting command output to a variable. +/// Frees the allocated memory. void var_redir_stop(void) { typval_T tv; @@ -711,7 +735,7 @@ int eval_to_bool(char_u *arg, bool *error, char_u **nextcmd, int skip) return retval; } -// Call eval1() and give an error message if not done at a lower level. +/// Call eval1() and give an error message if not done at a lower level. static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) FUNC_ATTR_NONNULL_ARG(1, 2) { @@ -734,6 +758,15 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) return ret; } +/// @return whether a typval is a valid expression to pass to eval_expr_typval() +/// or eval_expr_to_bool(). An empty string returns false; +bool eval_expr_valid_arg(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_CONST +{ + return tv->v_type != VAR_UNKNOWN + && (tv->v_type != VAR_STRING || (tv->vval.v_string != NULL && *tv->vval.v_string != NUL)); +} + int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv) FUNC_ATTR_NONNULL_ARG(1, 2, 4) { @@ -825,10 +858,9 @@ char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip return retval; } -/* - * Skip over an expression at "*pp". - * Return FAIL for an error, OK otherwise. - */ +/// Skip over an expression at "*pp". +/// +/// @return FAIL for an error, OK otherwise. int skip_expr(char_u **pp) { typval_T rettv; @@ -875,10 +907,10 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, bool convert) return (char_u *)retval; } -/* - * Call eval_to_string() without using current local variables and using - * textlock. When "use_sandbox" is TRUE use the sandbox. - */ +/// Call eval_to_string() without using current local variables and using +/// textlock. +/// +/// @param use_sandbox when TRUE, use the sandbox. char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox) { char_u *retval; @@ -898,11 +930,10 @@ char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox) return retval; } -/* - * Top level evaluation function, returning a number. - * Evaluates "expr" silently. - * Returns -1 for an error. - */ +/// Top level evaluation function, returning a number. +/// Evaluates "expr" silently. +/// +/// @return -1 for an error. varnumber_T eval_to_number(char_u *expr) { typval_T rettv; @@ -922,9 +953,10 @@ varnumber_T eval_to_number(char_u *expr) return retval; } -// Top level evaluation function. -// Returns an allocated typval_T with the result. -// Returns NULL when there is an error. +/// Top level evaluation function. +/// +/// @return an allocated typval_T with the result or +/// NULL when there is an error. typval_T *eval_expr(char_u *arg) { typval_T *tv = xmalloc(sizeof(*tv)); @@ -934,11 +966,9 @@ typval_T *eval_expr(char_u *arg) return tv; } -/* - * Prepare v: variable "idx" to be used. - * Save the current typeval in "save_tv". - * When not used yet add the variable to the v: hashtable. - */ +/// Prepare v: variable "idx" to be used. +/// Save the current typeval in "save_tv". +/// When not used yet add the variable to the v: hashtable. void prepare_vimvar(int idx, typval_T *save_tv) { *save_tv = vimvars[idx].vv_tv; @@ -947,10 +977,8 @@ void prepare_vimvar(int idx, typval_T *save_tv) } } -/* - * Restore v: variable "idx" to typeval "save_tv". - * When no longer defined, remove the variable from the v: hashtable. - */ +/// Restore v: variable "idx" to typeval "save_tv". +/// When no longer defined, remove the variable from the v: hashtable. void restore_vimvar(int idx, typval_T *save_tv) { hashitem_T *hi; @@ -977,11 +1005,10 @@ void find_win_for_curbuf(void) } } -/* - * Evaluate an expression to a list with suggestions. - * For the "expr:" part of 'spellsuggest'. - * Returns NULL when there is an error. - */ +/// Evaluate an expression to a list with suggestions. +/// For the "expr:" part of 'spellsuggest'. +/// +/// @return NULL when there is an error. list_T *eval_spell_expr(char_u *badword, char_u *expr) { typval_T save_val; @@ -1045,7 +1072,7 @@ int get_spellword(list_T *const list, const char **ret_word) // Uses argv[0] to argv[argc-1] for the function arguments. argv[argc] // should have type VAR_UNKNOWN. // -// Return OK or FAIL. +// @return OK or FAIL. int call_vim_function(const char_u *func, int argc, typval_T *argv, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { @@ -1183,10 +1210,8 @@ void prof_child_exit(proftime_T *tm) } -/* - * Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding - * it in "*cp". Doesn't give error messages. - */ +/// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding +/// it in "*cp". Doesn't give error messages. int eval_foldexpr(char_u *arg, int *cp) { typval_T tv; @@ -1227,26 +1252,27 @@ int eval_foldexpr(char_u *arg, int *cp) return (int)retval; } -// ":cons[t] var = expr1" define constant -// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list -// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list +/// ":cons[t] var = expr1" define constant +/// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list +/// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list void ex_const(exarg_T *eap) { ex_let_const(eap, true); } -// Get a list of lines from a HERE document. The here document is a list of -// lines surrounded by a marker. -// cmd << {marker} -// {line1} -// {line2} -// .... -// {marker} -// -// The {marker} is a string. If the optional 'trim' word is supplied before the -// marker, then the leading indentation before the lines (matching the -// indentation in the 'cmd' line) is stripped. -// Returns a List with {lines} or NULL. +/// Get a list of lines from a HERE document. The here document is a list of +/// lines surrounded by a marker. +/// cmd << {marker} +/// {line1} +/// {line2} +/// .... +/// {marker} +/// +/// The {marker} is a string. If the optional 'trim' word is supplied before the +/// marker, then the leading indentation before the lines (matching the +/// indentation in the 'cmd' line) is stripped. +/// +/// @return a List with {lines} or NULL. static list_T *heredoc_get(exarg_T *eap, char_u *cmd) { char_u *marker; @@ -1344,18 +1370,18 @@ static list_T *heredoc_get(exarg_T *eap, char_u *cmd) return l; } -// ":let" list all variable values -// ":let var1 var2" list variable values -// ":let var = expr" assignment command. -// ":let var += expr" assignment command. -// ":let var -= expr" assignment command. -// ":let var *= expr" assignment command. -// ":let var /= expr" assignment command. -// ":let var %= expr" assignment command. -// ":let var .= expr" assignment command. -// ":let var ..= expr" assignment command. -// ":let [var1, var2] = expr" unpack list. -// ":let [name, ..., ; lastname] = expr" unpack list. +/// ":let" list all variable values +/// ":let var1 var2" list variable values +/// ":let var = expr" assignment command. +/// ":let var += expr" assignment command. +/// ":let var -= expr" assignment command. +/// ":let var *= expr" assignment command. +/// ":let var /= expr" assignment command. +/// ":let var %= expr" assignment command. +/// ":let var .= expr" assignment command. +/// ":let var ..= expr" assignment command. +/// ":let [var1, var2] = expr" unpack list. +/// ":let [name, ..., ; lastname] = expr" unpack list. void ex_let(exarg_T *eap) { ex_let_const(eap, false); @@ -1536,13 +1562,12 @@ static int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, return OK; } -/* - * Skip over assignable variable "var" or list of variables "[var, var]". - * Used for ":let varvar = expr" and ":for varvar in expr". - * For "[var, var]" increment "*var_count" for each variable. - * for "[var, var; var]" set "semicolon". - * Return NULL for an error. - */ +/// Skip over assignable variable "var" or list of variables "[var, var]". +/// Used for ":let varvar = expr" and ":for varvar in expr". +/// For "[var, var]" increment "*var_count" for each variable. +/// for "[var, var; var]" set "semicolon". +/// +/// @return NULL for an error. static const char_u *skip_var_list(const char_u *arg, int *var_count, int *semicolon) { const char_u *p; @@ -1580,10 +1605,8 @@ static const char_u *skip_var_list(const char_u *arg, int *var_count, int *semic } } -/* - * Skip one (assignable) variable name, including @r, $VAR, &option, d.key, - * l[idx]. - */ +/// Skip one (assignable) variable name, including @r, $VAR, &option, d.key, +/// l[idx]. static const char_u *skip_var_one(const char_u *arg) { if (*arg == '@' && arg[1] != NUL) { @@ -1593,10 +1616,9 @@ static const char_u *skip_var_one(const char_u *arg) NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); } -/* - * List variables for hashtab "ht" with prefix "prefix". - * If "empty" is TRUE also list NULL strings as empty strings. - */ +/// List variables for hashtab "ht" with prefix "prefix". +/// +/// @param empty if TRUE also list NULL strings as empty strings. void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *first) { hashitem_T *hi; @@ -1625,47 +1647,37 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *firs } } -/* - * List global variables. - */ +/// List global variables. static void list_glob_vars(int *first) { list_hashtable_vars(&globvarht, "", true, first); } -/* - * List buffer variables. - */ +/// List buffer variables. static void list_buf_vars(int *first) { list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first); } -/* - * List window variables. - */ +/// List window variables. static void list_win_vars(int *first) { list_hashtable_vars(&curwin->w_vars->dv_hashtab, "w:", true, first); } -/* - * List tab page variables. - */ +/// List tab page variables. static void list_tab_vars(int *first) { list_hashtable_vars(&curtab->tp_vars->dv_hashtab, "t:", true, first); } -/* - * List Vim variables. - */ +/// List Vim variables. static void list_vim_vars(int *first) { list_hashtable_vars(&vimvarht, "v:", false, first); } -// List script-local variables, if there is a script. +/// List script-local variables, if there is a script. static void list_script_vars(int *first) { if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= ga_scripts.ga_len) { @@ -1673,9 +1685,7 @@ static void list_script_vars(int *first) } } -/* - * List variables in "arg". - */ +/// List variables in "arg". static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) { int error = FALSE; @@ -2338,9 +2348,7 @@ char_u *get_lval(char_u *const name, typval_T *const rettv, lval_T *const lp, co // TODO(ZyX-I): move to eval/executor -/* - * Clear lval "lp" that was filled by get_lval(). - */ +/// Clear lval "lp" that was filled by get_lval(). void clear_lval(lval_T *lp) { xfree(lp->ll_exp_name); @@ -2349,12 +2357,11 @@ void clear_lval(lval_T *lp) // TODO(ZyX-I): move to eval/executor -/* - * Set a variable that was parsed by get_lval() to "rettv". - * "endp" points to just after the parsed name. - * "op" is NULL, "+" for "+=", "-" for "-=", "*" for "*=", "/" for "/=", - * "%" for "%=", "." for ".=" or "=" for "=". - */ +/// Set a variable that was parsed by get_lval() to "rettv". +/// +/// @param endp points to just after the parsed name. +/// @param op NULL, "+" for "+=", "-" for "-=", "*" for "*=", "/" for "/=", +/// "%" for "%=", "." for ".=" or "=" for "=". static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, const bool is_const, const char *op) { @@ -2557,12 +2564,12 @@ notify: // TODO(ZyX-I): move to eval/ex_cmds -/* - * Evaluate the expression used in a ":for var in expr" command. - * "arg" points to "var". - * Set "*errp" to TRUE for an error, FALSE otherwise; - * Return a pointer that holds the info. Null when there is an error. - */ +/// Evaluate the expression used in a ":for var in expr" command. +/// "arg" points to "var". +/// +/// @param[out] *errp set to TRUE for an error, FALSE otherwise; +/// +/// @return a pointer that holds the info. Null when there is an error. void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) { forinfo_T *fi = xcalloc(1, sizeof(forinfo_T)); @@ -2612,8 +2619,15 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) fi->fi_blob = btv.vval.v_blob; } tv_clear(&tv); + } else if (tv.v_type == VAR_STRING) { + fi->fi_byte_idx = 0; + fi->fi_string = tv.vval.v_string; + tv.vval.v_string = NULL; + if (fi->fi_string == NULL) { + fi->fi_string = vim_strsave((char_u *)""); + } } else { - emsg(_(e_listblobreq)); + emsg(_(e_string_list_or_blob_required)); tv_clear(&tv); } } @@ -2627,12 +2641,11 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) // TODO(ZyX-I): move to eval/ex_cmds -/* - * Use the first item in a ":for" list. Advance to the next. - * Assign the values to the variable (list). "arg" points to the first one. - * Return TRUE when a valid item was found, FALSE when at end of list or - * something wrong. - */ +/// Use the first item in a ":for" list. Advance to the next. +/// Assign the values to the variable (list). "arg" points to the first one. +/// +/// @return true when a valid item was found, false when at end of list or +/// something wrong. bool next_for_item(void *fi_void, char_u *arg) { forinfo_T *fi = (forinfo_T *)fi_void; @@ -2650,6 +2663,22 @@ bool next_for_item(void *fi_void, char_u *arg) fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK; } + if (fi->fi_string != NULL) { + const int len = utfc_ptr2len(fi->fi_string + fi->fi_byte_idx); + if (len == 0) { + return false; + } + typval_T tv; + tv.v_type = VAR_STRING; + tv.v_lock = VAR_FIXED; + tv.vval.v_string = vim_strnsave(fi->fi_string + fi->fi_byte_idx, len); + fi->fi_byte_idx += len; + const int result + = ex_let_vars(arg, &tv, true, fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK; + xfree(tv.vval.v_string); + return result; + } + listitem_T *item = fi->fi_lw.lw_item; if (item == NULL) { return false; @@ -2662,19 +2691,21 @@ bool next_for_item(void *fi_void, char_u *arg) // TODO(ZyX-I): move to eval/ex_cmds -/* - * Free the structure used to store info used by ":for". - */ +/// Free the structure used to store info used by ":for". void free_for_info(void *fi_void) { forinfo_T *fi = (forinfo_T *)fi_void; - if (fi != NULL && fi->fi_list != NULL) { + if (fi == NULL) { + return; + } + if (fi->fi_list != NULL) { tv_list_watch_remove(fi->fi_list, &fi->fi_lw); tv_list_unref(fi->fi_list); - } - if (fi != NULL && fi->fi_blob != NULL) { + } else if (fi->fi_blob != NULL) { tv_blob_unref(fi->fi_blob); + } else { + xfree(fi->fi_string); } xfree(fi); } @@ -3109,9 +3140,7 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED, exarg_T *e return ret; } -/* - * Delete all "menutrans_" variables. - */ +/// Delete all "menutrans_" variables. void del_menutrans_vars(void) { hash_lock(&globvarht); @@ -3133,9 +3162,7 @@ void del_menutrans_vars(void) static char_u *varnamebuf = NULL; static size_t varnamebuflen = 0; -/* - * Function to concatenate a prefix and a variable name. - */ +/// Function to concatenate a prefix and a variable name. char_u *cat_prefix_varname(int prefix, const char_u *name) FUNC_ATTR_NONNULL_ALL { @@ -3153,10 +3180,8 @@ char_u *cat_prefix_varname(int prefix, const char_u *name) return varnamebuf; } -/* - * Function given to ExpandGeneric() to obtain the list of user defined - * (global/buffer/window/built-in) variable names. - */ +/// Function given to ExpandGeneric() to obtain the list of user defined +/// (global/buffer/window/built-in) variable names. char_u *get_user_var_name(expand_T *xp, int idx) { static size_t gdone; @@ -3188,10 +3213,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) } // b: variables - // 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; + const hashtab_T *ht = &prevwin_curwin()->w_buffer->b_vars->dv_hashtab; if (bdone < ht->ht_used) { if (bdone++ == 0) { hi = ht->ht_array; @@ -3205,10 +3227,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) } // w: variables - // 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; + ht = &prevwin_curwin()->w_vars->dv_hashtab; if (wdone < ht->ht_used) { if (wdone++ == 0) { hi = ht->ht_array; @@ -3247,9 +3266,10 @@ char_u *get_user_var_name(expand_T *xp, int idx) // TODO(ZyX-I): move to eval/expressions -/// Return TRUE if "pat" matches "text". /// Does not use 'cpo' and always uses 'magic'. -static int pattern_match(char_u *pat, char_u *text, bool ic) +/// +/// @return TRUE if "pat" matches "text". +int pattern_match(char_u *pat, char_u *text, bool ic) { int matches = 0; regmatch_T regmatch; @@ -3269,10 +3289,10 @@ static int pattern_match(char_u *pat, char_u *text, bool ic) /// Handle a name followed by "(". Both for just "name(arg)" and for /// "expr->name(arg)". -// +/// /// @param arg Points to "(", will be advanced /// @param basetv "expr" for "expr->name(arg)" -// +/// /// @return OK or FAIL. static int eval_func(char_u **const arg, char_u *const name, const int name_len, typval_T *const rettv, const bool evaluate, typval_T *const basetv) @@ -3333,13 +3353,12 @@ static int eval_func(char_u **const arg, char_u *const name, const int name_len, * VAR_UNKNOWN. The function still returns FAIL for a syntax error. */ -/* - * Handle zero level expression. - * This calls eval1() and handles error message and nextcmd. - * Put the result in "rettv" when returning OK and "evaluate" is TRUE. - * Note: "rettv.v_lock" is not set. - * Return OK or FAIL. - */ +/// Handle zero level expression. +/// This calls eval1() and handles error message and nextcmd. +/// Put the result in "rettv" when returning OK and "evaluate" is TRUE. +/// Note: "rettv.v_lock" is not set. +/// +/// @return OK or FAIL. int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) { int ret; @@ -3372,17 +3391,15 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle top level expression: - * expr2 ? expr1 : expr1 - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Note: "rettv.v_lock" is not set. - * - * Return OK or FAIL. - */ +/// Handle top level expression: +/// expr2 ? expr1 : expr1 +/// +/// "arg" must point to the first non-white of the expression. +/// "arg" is advanced to the next non-white after the recognized expression. +/// +/// Note: "rettv.v_lock" is not set. +/// +/// @return OK or FAIL. int eval1(char_u **arg, typval_T *rettv, int evaluate) { int result; @@ -3448,15 +3465,13 @@ int eval1(char_u **arg, typval_T *rettv, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle first level expression: - * expr2 || expr2 || expr2 logical OR - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Return OK or FAIL. - */ +/// Handle first level expression: +/// expr2 || expr2 || expr2 logical OR +/// +/// "arg" must point to the first non-white of the expression. +/// "arg" is advanced to the next non-white after the recognized expression. +/// +/// @return OK or FAIL. static int eval2(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; @@ -3519,15 +3534,13 @@ static int eval2(char_u **arg, typval_T *rettv, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle second level expression: - * expr3 && expr3 && expr3 logical AND - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Return OK or FAIL. - */ +/// Handle second level expression: +/// expr3 && expr3 && expr3 logical AND +/// +/// @param arg must point to the first non-white of the expression. +/// `arg` is advanced to the next non-white after the recognized expression. +/// +/// @return OK or FAIL. static int eval3(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; @@ -3590,24 +3603,22 @@ static int eval3(char_u **arg, typval_T *rettv, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle third level expression: - * var1 == var2 - * var1 =~ var2 - * var1 != var2 - * var1 !~ var2 - * var1 > var2 - * var1 >= var2 - * var1 < var2 - * var1 <= var2 - * var1 is var2 - * var1 isnot var2 - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Return OK or FAIL. - */ +/// Handle third level expression: +/// var1 == var2 +/// var1 =~ var2 +/// var1 != var2 +/// var1 !~ var2 +/// var1 > var2 +/// var1 >= var2 +/// var1 < var2 +/// var1 <= var2 +/// var1 is var2 +/// var1 isnot var2 +/// +/// "arg" must point to the first non-white of the expression. +/// "arg" is advanced to the next non-white after the recognized expression. +/// +/// @return OK or FAIL. static int eval4(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; @@ -3701,18 +3712,16 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle fourth level expression: - * + number addition - * - number subtraction - * . string concatenation - * .. string concatenation - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Return OK or FAIL. - */ +/// Handle fourth level expression: +/// + number addition +/// - number subtraction +/// . string concatenation +/// .. string concatenation +/// +/// @param arg must point to the first non-white of the expression. +/// `arg` is advanced to the next non-white after the recognized expression. +/// +/// @return OK or FAIL. static int eval5(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; @@ -3965,18 +3974,11 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string) f1 = f1 * f2; } else if (op == '/') { // Division by zero triggers error from AddressSanitizer - f1 = (f2 == 0 - ? ( + f1 = (f2 == 0 ? ( #ifdef NAN - f1 == 0 - ? NAN - : + f1 == 0 ? (float_T)NAN : #endif - (f1 > 0 - ? INFINITY - : -INFINITY) - ) - : f1 / f2); + (f1 > 0 ? (float_T)INFINITY : (float_T)-INFINITY)) : f1 / f2); } else { emsg(_("E804: Cannot use '%' with Float")); return FAIL; @@ -4258,7 +4260,8 @@ static int eval7(char_u **arg, typval_T *rettv, int evaluate, int want_string) /// Apply the leading "!" and "-" before an eval7 expression to "rettv". /// Adjusts "end_leaderp" until it is at "start_leader". -/// @return OK on success, FAIL on failure. +/// +/// @return OK on success, FAIL on failure. static int eval7_leader(typval_T *const rettv, const char_u *const start_leader, const char_u **const end_leaderp) FUNC_ATTR_NONNULL_ALL @@ -4312,7 +4315,7 @@ static int eval7_leader(typval_T *const rettv, const char_u *const start_leader, /// @param lua_funcname If `rettv` refers to a v:lua function, this must point /// to the name of the Lua function to call (after the /// "v:lua." prefix). -/// @return OK on success, FAIL on failure. +/// @return OK on success, FAIL on failure. static int call_func_rettv(char_u **const arg, typval_T *const rettv, const bool evaluate, dict_T *const selfdict, typval_T *const basetv, const char_u *const lua_funcname) @@ -4360,9 +4363,13 @@ static int call_func_rettv(char_u **const arg, typval_T *const rettv, const bool } /// Evaluate "->method()". +/// /// @param verbose if true, give error messages. -/// @note "*arg" points to the '-'. -/// @return FAIL or OK. @note "*arg" is advanced to after the ')'. +/// @param *arg points to the '-'. +/// +/// @return FAIL or OK. +/// +/// @note "*arg" is advanced to after the ')'. static int eval_lambda(char_u **const arg, typval_T *const rettv, const bool evaluate, const bool verbose) FUNC_ATTR_NONNULL_ALL @@ -4373,7 +4380,7 @@ static int eval_lambda(char_u **const arg, typval_T *const rettv, const bool eva rettv->v_type = VAR_UNKNOWN; int ret = get_lambda_tv(arg, rettv, evaluate); - if (ret == NOTDONE) { + if (ret != OK) { return FAIL; } else if (**arg != '(') { if (verbose) { @@ -4399,8 +4406,10 @@ static int eval_lambda(char_u **const arg, typval_T *const rettv, const bool eva } /// Evaluate "->method()" or "->v:lua.method()". -/// @note "*arg" points to the '-'. -/// @return FAIL or OK. "*arg" is advanced to after the ')'. +/// +/// @param *arg points to the '-'. +/// +/// @return FAIL or OK. "*arg" is advanced to after the ')'. static int eval_method(char_u **const arg, typval_T *const rettv, const bool evaluate, const bool verbose) FUNC_ATTR_NONNULL_ALL @@ -4473,9 +4482,10 @@ static int eval_method(char_u **const arg, typval_T *const rettv, const bool eva /// Evaluate an "[expr]" or "[expr:expr]" index. Also "dict.key". /// "*arg" points to the '[' or '.'. -/// Returns FAIL or OK. "*arg" is advanced to after the ']'. /// /// @param verbose give error messages +/// +/// @returns FAIL or OK. "*arg" is advanced to after the ']'. static int eval_index(char_u **arg, typval_T *rettv, int evaluate, int verbose) { bool empty1 = false; @@ -4523,7 +4533,7 @@ static int eval_index(char_u **arg, typval_T *rettv, int evaluate, int verbose) * dict.name */ key = *arg + 1; - for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; ++len) { + for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; len++) { } if (len == 0) { return FAIL; @@ -4780,19 +4790,18 @@ static int eval_index(char_u **arg, typval_T *rettv, int evaluate, int verbose) /// Get an option value /// -/// @param[in,out] arg Points to the '&' or '+' before the option name. Is +/// @param[in,out] arg Points to the '&' or '+' before the option name. Is /// advanced to the character after the option name. -/// @param[out] rettv Location where result is saved. -/// @param[in] evaluate If not true, rettv is not populated. +/// @param[out] rettv Location where result is saved. +/// @param[in] evaluate If not true, rettv is not populated. /// -/// @return OK or FAIL. +/// @return OK or FAIL. int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate) FUNC_ATTR_NONNULL_ARG(1) { long numval; char_u *stringval; int opt_type; - int c; bool working = (**arg == '+'); // has("+option") int ret = OK; int opt_flags; @@ -4811,7 +4820,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval return OK; } - c = *option_end; + char c = *option_end; *option_end = NUL; opt_type = get_option_value(*arg, &numval, rettv == NULL ? NULL : &stringval, opt_flags); @@ -4828,7 +4837,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval } else if (opt_type == -1) { // hidden number option rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - } else if (opt_type == 1) { // number option + } else if (opt_type == 1 || opt_type == 2) { // number or boolean option rettv->v_type = VAR_NUMBER; rettv->vval.v_number = numval; } else { // string option @@ -4845,10 +4854,9 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval return ret; } -/* - * Allocate a variable for a string constant. - * Return OK or FAIL. - */ +/// Allocate a variable for a string constant. +/// +/// @return OK or FAIL. static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; @@ -4985,10 +4993,9 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -/* - * Allocate a variable for a 'str''ing' constant. - * Return OK or FAIL. - */ +/// Allocate a variable for a 'str''ing' constant. +/// +/// @return OK or FAIL. static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; @@ -5041,7 +5048,7 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -/// @return the function name of the partial. +/// @return the function name of the partial. char_u *partial_name(partial_T *pt) { if (pt->pt_name != NULL) { @@ -5080,7 +5087,8 @@ void partial_unref(partial_T *pt) } /// Allocate a variable for a List and fill it from "*arg". -/// Return OK or FAIL. +/// +/// @return OK or FAIL. static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) { list_T *l = NULL; @@ -5218,7 +5226,8 @@ int get_copyID(void) /// Do garbage collection for lists and dicts. /// /// @param testing true if called from test_garbagecollect_now(). -/// @returns true if some memory was freed. +/// +/// @return true if some memory was freed. bool garbage_collect(bool testing) { bool abort = false; @@ -5396,10 +5405,11 @@ bool garbage_collect(bool testing) /// Free lists and dictionaries that are no longer referenced. /// -/// @note This function may only be called from garbage_collect(). +/// @note This function may only be called from garbage_collect(). /// -/// @param copyID Free lists/dictionaries that don't have this ID. -/// @return true, if something was freed. +/// @param copyID Free lists/dictionaries that don't have this ID. +/// +/// @return true, if something was freed. static int free_unref_items(int copyID) { bool did_free = false; @@ -5634,7 +5644,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack /// Mark all lists and dicts referenced in given mark /// -/// @returns true if setting references failed somehow. +/// @return true if setting references failed somehow. static inline bool set_ref_in_fmark(fmark_T fm, int copyID) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5648,7 +5658,7 @@ static inline bool set_ref_in_fmark(fmark_T fm, int copyID) /// Mark all lists and dicts referenced in given list and the list itself /// -/// @returns true if setting references failed somehow. +/// @return true if setting references failed somehow. static inline bool set_ref_list(list_T *list, int copyID) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5664,7 +5674,7 @@ static inline bool set_ref_list(list_T *list, int copyID) /// Mark all lists and dicts referenced in given dict and the dict itself /// -/// @returns true if setting references failed somehow. +/// @return true if setting references failed somehow. static inline bool set_ref_dict(dict_T *dict, int copyID) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5679,8 +5689,9 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) } -// Get the key for *{key: val} into "tv" and advance "arg". -// Return FAIL when there is no valid key. +/// 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 { @@ -5698,9 +5709,10 @@ static int get_literal_key(char_u **arg, typval_T *tv) 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}. +/// 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; @@ -5810,10 +5822,10 @@ failret: /// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to /// make sure this always uses a decimal point. /// -/// @param[in] text String to convert. -/// @param[out] ret_value Location where conversion result is saved. +/// @param[in] text String to convert. +/// @param[out] ret_value Location where conversion result is saved. /// -/// @return Length of the text that was consumed. +/// @return Length of the text that was consumed. size_t string2float(const char *const text, float_T *const ret_value) FUNC_ATTR_NONNULL_ALL { @@ -5821,15 +5833,15 @@ size_t string2float(const char *const text, float_T *const ret_value) // MS-Windows does not deal with "inf" and "nan" properly if (STRNICMP(text, "inf", 3) == 0) { - *ret_value = INFINITY; + *ret_value = (float_T)INFINITY; return 3; } if (STRNICMP(text, "-inf", 3) == 0) { - *ret_value = -INFINITY; + *ret_value = (float_T)-INFINITY; return 4; } if (STRNICMP(text, "nan", 3) == 0) { - *ret_value = NAN; + *ret_value = (float_T)NAN; return 3; } *ret_value = strtod(text, &s); @@ -5840,9 +5852,9 @@ size_t string2float(const char *const text, float_T *const ret_value) /// /// If the environment variable was not set, silently assume it is empty. /// -/// @param arg Points to the '$'. It is advanced to after the name. -/// @return FAIL if the name is invalid. +/// @param arg Points to the '$'. It is advanced to after the name. /// +/// @return FAIL if the name is invalid. static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *name; @@ -5891,145 +5903,7 @@ void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) } } -// Prepare "gap" for an assert error and add the sourcing position. -void prepare_assert_error(garray_T *gap) -{ - char buf[NUMBUFLEN]; - - ga_init(gap, 1, 100); - if (sourcing_name != NULL) { - ga_concat(gap, (char *)sourcing_name); - if (sourcing_lnum > 0) { - ga_concat(gap, " "); - } - } - if (sourcing_lnum > 0) { - vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)sourcing_lnum); - ga_concat(gap, buf); - } - if (sourcing_name != NULL || sourcing_lnum > 0) { - ga_concat(gap, ": "); - } -} - -// Append "p[clen]" to "gap", escaping unprintable characters. -// Changes NL to \n, CR to \r, etc. -static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) - FUNC_ATTR_NONNULL_ALL -{ - char_u buf[NUMBUFLEN]; - - if (clen > 1) { - memmove(buf, p, clen); - buf[clen] = NUL; - ga_concat(gap, (char *)buf); - } else { - switch (*p) { - case BS: - ga_concat(gap, "\\b"); break; - case ESC: - ga_concat(gap, "\\e"); break; - case FF: - ga_concat(gap, "\\f"); break; - case NL: - ga_concat(gap, "\\n"); break; - case TAB: - ga_concat(gap, "\\t"); break; - case CAR: - ga_concat(gap, "\\r"); break; - case '\\': - ga_concat(gap, "\\\\"); break; - default: - if (*p < ' ') { - vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); - ga_concat(gap, (char *)buf); - } else { - ga_append(gap, *p); - } - break; - } - } -} - -// Append "str" to "gap", escaping unprintable characters. -// Changes NL to \n, CR to \r, etc. -static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) - FUNC_ATTR_NONNULL_ARG(1) -{ - char_u buf[NUMBUFLEN]; - - if (str == NULL) { - ga_concat(gap, "NULL"); - return; - } - - for (const char_u *p = str; *p != NUL; p++) { - int same_len = 1; - const char_u *s = p; - const int c = mb_ptr2char_adv(&s); - const int clen = s - p; - while (*s != NUL && c == utf_ptr2char(s)) { - same_len++; - s += clen; - } - if (same_len > 20) { - ga_concat(gap, "\\["); - ga_concat_esc(gap, p, clen); - ga_concat(gap, " occurs "); - vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); - ga_concat(gap, (char *)buf); - ga_concat(gap, " times]"); - p = s - 1; - } else { - ga_concat_esc(gap, p, clen); - } - } -} - -// Fill "gap" with information about an assert error. -void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, - typval_T *got_tv, assert_type_T atype) -{ - char_u *tofree; - - if (opt_msg_tv->v_type != VAR_UNKNOWN) { - tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); - ga_concat(gap, (char *)tofree); - xfree(tofree); - ga_concat(gap, ": "); - } - - if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { - ga_concat(gap, "Pattern "); - } else if (atype == ASSERT_NOTEQUAL) { - ga_concat(gap, "Expected not equal to "); - } else { - ga_concat(gap, "Expected "); - } - - if (exp_str == NULL) { - tofree = (char_u *)encode_tv2string(exp_tv, NULL); - ga_concat_shorten_esc(gap, tofree); - xfree(tofree); - } else { - ga_concat_shorten_esc(gap, exp_str); - } - - if (atype != ASSERT_NOTEQUAL) { - if (atype == ASSERT_MATCH) { - ga_concat(gap, " does not match "); - } else if (atype == ASSERT_NOTMATCH) { - ga_concat(gap, " does match "); - } else { - ga_concat(gap, " but got "); - } - tofree = (char_u *)encode_tv2string(got_tv, NULL); - ga_concat_shorten_esc(gap, tofree); - xfree(tofree); - } -} - -// Add an assert error to v:errors. +/// Add an assert error to v:errors. void assert_error(garray_T *gap) { struct vimvar *vp = &vimvars[VV_ERRORS]; @@ -6042,328 +5916,6 @@ void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -int assert_equal_common(typval_T *argvars, assert_type_T atype) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - - if (tv_equal(&argvars[0], &argvars[1], false, false) - != (atype == ASSERT_EQUAL)) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, - &argvars[0], &argvars[1], atype); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -int assert_equalfile(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); - const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); - garray_T ga; - - if (fname1 == NULL || fname2 == NULL) { - return 0; - } - - IObuff[0] = NUL; - FILE *const fd1 = os_fopen(fname1, READBIN); - char line1[200]; - char line2[200]; - ptrdiff_t lineidx = 0; - if (fd1 == NULL) { - snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); - } else { - FILE *const fd2 = os_fopen(fname2, READBIN); - if (fd2 == NULL) { - fclose(fd1); - snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); - } else { - int64_t linecount = 1; - for (int64_t count = 0;; count++) { - const int c1 = fgetc(fd1); - const int c2 = fgetc(fd2); - if (c1 == EOF) { - if (c2 != EOF) { - STRCPY(IObuff, "first file is shorter"); - } - break; - } else if (c2 == EOF) { - STRCPY(IObuff, "second file is shorter"); - break; - } else { - line1[lineidx] = c1; - line2[lineidx] = c2; - lineidx++; - if (c1 != c2) { - snprintf((char *)IObuff, IOSIZE, - "difference at byte %" PRId64 ", line %" PRId64, - count, linecount); - break; - } - } - if (c1 == NL) { - linecount++; - lineidx = 0; - } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) { - memmove(line1, line1 + 100, lineidx - 100); - memmove(line2, line2 + 100, lineidx - 100); - lineidx -= 100; - } - } - fclose(fd1); - fclose(fd2); - } - } - if (IObuff[0] != NUL) { - prepare_assert_error(&ga); - if (argvars[2].v_type != VAR_UNKNOWN) { - char *const tofree = encode_tv2echo(&argvars[2], NULL); - ga_concat(&ga, tofree); - xfree(tofree); - ga_concat(&ga, ": "); - } - ga_concat(&ga, (char *)IObuff); - if (lineidx > 0) { - line1[lineidx] = NUL; - line2[lineidx] = NUL; - ga_concat(&ga, " after \""); - ga_concat(&ga, line1); - if (STRCMP(line1, line2) != 0) { - ga_concat(&ga, "\" vs \""); - ga_concat(&ga, line2); - } - ga_concat(&ga, "\""); - } - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -int assert_inrange(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - - if (argvars[0].v_type == VAR_FLOAT - || argvars[1].v_type == VAR_FLOAT - || argvars[2].v_type == VAR_FLOAT) { - const float_T flower = tv_get_float(&argvars[0]); - const float_T fupper = tv_get_float(&argvars[1]); - const float_T factual = tv_get_float(&argvars[2]); - - if (factual < flower || factual > fupper) { - garray_T ga; - prepare_assert_error(&ga); - if (argvars[3].v_type != VAR_UNKNOWN) { - char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL); - ga_concat(&ga, (char *)tofree); - xfree(tofree); - } else { - char msg[80]; - vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g", - flower, fupper, factual); - ga_concat(&ga, msg); - } - assert_error(&ga); - ga_clear(&ga); - return 1; - } - } else { - const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); - const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); - const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); - - if (error) { - return 0; - } - if (actual < lower || actual > upper) { - garray_T ga; - prepare_assert_error(&ga); - - char msg[55]; - vim_snprintf(msg, sizeof(msg), - "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", - lower, upper); // -V576 - fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], - ASSERT_INRANGE); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - } - return 0; -} - -// Common for assert_true() and assert_false(). -int assert_bool(typval_T *argvars, bool is_true) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - garray_T ga; - - if ((argvars[0].v_type != VAR_NUMBER - || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true - || error) - && (argvars[0].v_type != VAR_BOOL - || (argvars[0].vval.v_bool - != (BoolVarValue)(is_true - ? kBoolVarTrue - : kBoolVarFalse)))) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], - (char_u *)(is_true ? "True" : "False"), - NULL, &argvars[0], ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -int assert_exception(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - - const char *const error = tv_get_string_chk(&argvars[0]); - if (vimvars[VV_EXCEPTION].vv_str == NULL) { - prepare_assert_error(&ga); - ga_concat(&ga, "v:exception is not set"); - assert_error(&ga); - ga_clear(&ga); - return 1; - } else if (error != NULL - && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], - &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, const char *cmd) - FUNC_ATTR_NONNULL_ALL -{ - if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { - char *const tofree = encode_tv2echo(&argvars[2], NULL); - ga_concat(gap, tofree); - xfree(tofree); - } else { - ga_concat(gap, cmd); - } -} - -int assert_beeps(typval_T *argvars, bool no_beep) - FUNC_ATTR_NONNULL_ALL -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - int ret = 0; - - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (no_beep ? called_vim_beep : !called_vim_beep) { - garray_T ga; - prepare_assert_error(&ga); - if (no_beep) { - ga_concat(&ga, "command did beep: "); - } else { - ga_concat(&ga, "command did not beep: "); - } - ga_concat(&ga, cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } - - suppress_errthrow = false; - emsg_on_display = false; - return ret; -} - -int assert_fails(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - int save_trylevel = trylevel; - - // trylevel must be zero for a ":throw" command to be considered failed - trylevel = 0; - called_emsg = false; - suppress_errthrow = true; - emsg_silent = true; - - do_cmdline_cmd(cmd); - if (!called_emsg) { - prepare_assert_error(&ga); - ga_concat(&ga, "command did not fail: "); - assert_append_cmd_or_arg(&ga, argvars, cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } else if (argvars[1].v_type != VAR_UNKNOWN) { - char buf[NUMBUFLEN]; - const char *const error = tv_get_string_buf_chk(&argvars[1], buf); - - if (error == NULL - || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], - &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); - ga_concat(&ga, ": "); - assert_append_cmd_or_arg(&ga, argvars, cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } - } - - trylevel = save_trylevel; - called_emsg = false; - suppress_errthrow = false; - emsg_silent = false; - emsg_on_display = false; - set_vim_var_string(VV_ERRMSG, NULL, 0); - return ret; -} - -int assert_match_common(typval_T *argvars, assert_type_T atype) - FUNC_ATTR_NONNULL_ALL -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1); - const char *const text = tv_get_string_buf_chk(&argvars[1], buf2); - - if (pat == NULL || text == NULL) { - emsg(_(e_invarg)); - } else if (pattern_match((char_u *)pat, (char_u *)text, false) - != (atype == ASSERT_MATCH)) { - garray_T ga; - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - /// Find a window: When using a Window ID in any tab page, when using a number /// in the current tab page. win_T *find_win_by_nr_or_id(typval_T *vp) @@ -6371,15 +5923,13 @@ win_T *find_win_by_nr_or_id(typval_T *vp) int nr = (int)tv_get_number_chk(vp, NULL); if (nr >= LOWEST_WIN_ID) { - return win_id2wp(vp); + return win_id2wp(tv_get_number(vp)); } return find_win_by_nr(vp, NULL); } -/* - * Implementation of map() and filter(). - */ +/// Implementation of map() and filter(). void filter_map(typval_T *argvars, typval_T *rettv, int map) { typval_T *expr; @@ -6439,6 +5989,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) if (argvars[0].v_type == VAR_DICT) { vimvars[VV_KEY].vv_type = VAR_STRING; + const VarLockStatus prev_lock = d->dv_lock; + if (map && d->dv_lock == VAR_UNLOCKED) { + d->dv_lock = VAR_LOCKED; + } ht = &d->dv_hashtab; hash_lock(ht); todo = (int)ht->ht_used; @@ -6469,6 +6023,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } } hash_unlock(ht); + d->dv_lock = prev_lock; } else if (argvars[0].v_type == VAR_BLOB) { vimvars[VV_KEY].vv_type = VAR_NUMBER; @@ -6501,6 +6056,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) assert(argvars[0].v_type == VAR_LIST); vimvars[VV_KEY].vv_type = VAR_NUMBER; + const VarLockStatus prev_lock = tv_list_locked(l); + if (map && tv_list_locked(l) == VAR_UNLOCKED) { + tv_list_set_lock(l, VAR_LOCKED); + } for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map && var_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, @@ -6519,6 +6078,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } idx++; } + tv_list_set_lock(l, prev_lock); } restore_vimvar(VV_KEY, &save_key); @@ -6603,8 +6163,9 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr : (const char *)s)); // Don't check an autoload name for existence here. } else if (trans_name != NULL - && (is_funcref ? find_func(trans_name) == NULL - : !translated_function_exists((const char *)trans_name))) { + && (is_funcref + ? find_func(trans_name) == NULL + : !translated_function_exists((const char *)trans_name))) { semsg(_("E700: Unknown function: %s"), s); } else { int dict_idx = 0; @@ -6730,7 +6291,7 @@ theend: xfree(trans_name); } -/// Returns buffer options, variables and other attributes in a dictionary. +/// @return buffer options, variables and other attributes in a dictionary. dict_T *get_buffer_info(buf_T *buf) { dict_T *const dict = tv_dict_alloc(); @@ -6774,12 +6335,12 @@ dict_T *get_buffer_info(buf_T *buf) /// /// @note Unlike tv_get_lnum(), this one supports only "$" special string. /// -/// @param[in] tv Object to get value from. Is expected to be a number or +/// @param[in] tv Object to get value from. Is expected to be a number or /// a special string "$". -/// @param[in] buf Buffer to take last line number from in case tv is "$". May -/// be NULL, in this case "$" results in zero return. +/// @param[in] buf Buffer to take last line number from in case tv is "$". May +/// be NULL, in this case "$" results in zero return. /// -/// @return Line number or 0 in case of error. +/// @return Line number or 0 in case of error. linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf) FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -6815,8 +6376,8 @@ void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) } } -/// Returns information (variables, options, etc.) about a tab page -/// as a dictionary. +/// @return information (variables, options, etc.) about a tab page +/// as a dictionary. dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) { dict_T *const dict = tv_dict_alloc(); @@ -6835,7 +6396,7 @@ dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) return dict; } -/// Returns information about a window as a dictionary. +/// @return information about a window as a dictionary. dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) { dict_T *const dict = tv_dict_alloc(); @@ -6851,7 +6412,7 @@ dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); - + tv_dict_add_nr(dict, S_LEN("textoff"), win_col_off(wp)); tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer)); tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); tv_dict_add_nr(dict, S_LEN("loclist"), @@ -6926,10 +6487,9 @@ win_T *find_tabwin(typval_T *wvp, typval_T *tvp) /// @param off 1 for gettabwinvar() void getwinvar(typval_T *argvars, typval_T *rettv, int off) { - win_T *win, *oldcurwin; + win_T *win; dictitem_T *v; tabpage_T *tp = NULL; - tabpage_T *oldtabpage = NULL; bool done = false; if (off == 1) { @@ -6949,8 +6509,8 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off) // otherwise the window is not valid. Only do this when needed, // autocommands get blocked. bool need_switch_win = tp != curtab || win != curwin; - if (!need_switch_win - || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) { + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { if (*varname == '&') { if (varname[1] == NUL) { // get all window-local options in a dict @@ -6978,7 +6538,7 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off) if (need_switch_win) { // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); + restore_win(&switchwin, true); } } emsg_off--; @@ -6989,12 +6549,10 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off) } } -/* - * This function is used by f_input() and f_inputdialog() functions. The third - * argument to f_input() specifies the type of completion to use at the - * prompt. The third argument to f_inputdialog() specifies the value to return - * when the user cancels the prompt. - */ +/// This function is used by f_input() and f_inputdialog() functions. The third +/// argument to f_input() specifies the type of completion to use at the +/// prompt. The third argument to f_inputdialog() specifies the value to return +/// when the user cancels the prompt. void get_user_input(const typval_T *const argvars, typval_T *const rettv, const bool inputdialog, const bool secret) FUNC_ATTR_NONNULL_ALL @@ -7126,10 +6684,10 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const /// Turn a dictionary into a list /// -/// @param[in] tv Dictionary to convert. Is checked for actually being -/// a dictionary, will give an error if not. -/// @param[out] rettv Location where result will be saved. -/// @param[in] what What to save in rettv. +/// @param[in] tv Dictionary to convert. Is checked for actually being +/// a dictionary, will give an error if not. +/// @param[out] rettv Location where result will be saved. +/// @param[in] what What to save in rettv. void dict_list(typval_T *const tv, typval_T *const rettv, const DictListType what) { if (tv->v_type != VAR_DICT) { @@ -7146,30 +6704,30 @@ void dict_list(typval_T *const tv, typval_T *const rettv, const DictListType wha typval_T tv_item = { .v_lock = VAR_UNLOCKED }; switch (what) { - case kDictListKeys: - tv_item.v_type = VAR_STRING; - tv_item.vval.v_string = vim_strsave(di->di_key); - break; - case kDictListValues: - tv_copy(&di->di_tv, &tv_item); - break; - case kDictListItems: { - // items() - list_T *const sub_l = tv_list_alloc(2); - tv_item.v_type = VAR_LIST; - tv_item.vval.v_list = sub_l; - tv_list_ref(sub_l); - - tv_list_append_owned_tv(sub_l, (typval_T) { + case kDictListKeys: + tv_item.v_type = VAR_STRING; + tv_item.vval.v_string = vim_strsave(di->di_key); + break; + case kDictListValues: + tv_copy(&di->di_tv, &tv_item); + break; + case kDictListItems: { + // items() + list_T *const sub_l = tv_list_alloc(2); + tv_item.v_type = VAR_LIST; + tv_item.vval.v_list = sub_l; + tv_list_ref(sub_l); + + tv_list_append_owned_tv(sub_l, (typval_T) { .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), }); - tv_list_append_tv(sub_l, &di->di_tv); + tv_list_append_tv(sub_l, &di->di_tv); - break; - } + break; + } } tv_list_append_owned_tv(rettv->vval.v_list, tv_item); @@ -7182,7 +6740,7 @@ void dict_list(typval_T *const tv, typval_T *const rettv, const DictListType wha /// @param[out] cmd Returns the command or executable name. /// @param[out] executable Returns `false` if argv[0] is not executable. /// -/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String. +/// @return Result of `shell_build_argv()` if `cmd_tv` is a String. /// Else, string values of `cmd_tv` copied to a (char **) list with /// argv[0] resolved to full path ($PATHEXT-resolved on Windows). char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) @@ -7246,10 +6804,10 @@ char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) /// Fill a dictionary with all applicable maparg() like dictionaries /// -/// @param dict The dictionary to be filled -/// @param mp The maphash that contains the mapping information -/// @param buffer_value The "buffer" value -/// @param compatible True for compatible with old maparg() dict +/// @param dict The dictionary to be filled +/// @param mp The maphash that contains the mapping information +/// @param buffer_value The "buffer" value +/// @param compatible True for compatible with old maparg() dict void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buffer_value, bool compatible) FUNC_ATTR_NONNULL_ALL @@ -7269,12 +6827,19 @@ void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buf noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap; } - if (compatible) { - tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); + if (mp->m_luaref != LUA_NOREF) { + tv_dict_add_nr(dict, S_LEN("callback"), mp->m_luaref); } else { - tv_dict_add_allocated_str(dict, S_LEN("rhs"), - str2special_save((const char *)mp->m_str, false, - true)); + if (compatible) { + tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); + } else { + tv_dict_add_allocated_str(dict, S_LEN("rhs"), + str2special_save((const char *)mp->m_str, false, + true)); + } + } + if (mp->m_desc != NULL) { + tv_dict_add_allocated_str(dict, S_LEN("desc"), xstrdup(mp->m_desc)); } tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs); tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value); @@ -7288,30 +6853,6 @@ void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buf tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode); } -int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **win) -{ - dictitem_T *di; - - if (tv->v_type != VAR_DICT) { - emsg(_(e_dictreq)); - return FAIL; - } - - if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) { - *conceal_char = tv_get_string(&di->di_tv); - } - - if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) { - *win = find_win_by_nr_or_id(&di->di_tv); - if (*win == NULL) { - emsg(_(e_invalwindow)); - return FAIL; - } - } - - return OK; -} - void return_register(int regname, typval_T *rettv) { char_u buf[2] = { regname, 0 }; @@ -7473,11 +7014,9 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off) typval_T *varp = &argvars[off + 2]; if (win != NULL && varname != NULL && varp != NULL) { - win_T *save_curwin; - tabpage_T *save_curtab; bool need_switch_win = tp != curtab || win != curwin; - if (!need_switch_win - || switch_win(&save_curwin, &save_curtab, win, tp, true) == OK) { + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { if (*varname == '&') { long numval; bool error = false; @@ -7499,7 +7038,7 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off) } } if (need_switch_win) { - restore_win(save_curwin, save_curtab, true); + restore_win(&switchwin, true); } } } @@ -7541,7 +7080,7 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) return list; } -// os_system wrapper. Handles 'verbose', :profile, and v:shell_error. +/// os_system wrapper. Handles 'verbose', :profile, and v:shell_error. void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist) { proftime_T wait_time; @@ -7666,6 +7205,7 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) callback->type = kCallbackFuncref; } } else if (nlua_is_table_from_lua(arg)) { + // TODO(tjdvries): UnifiedCallback char_u *name = nlua_register_table_as_callable(arg); if (name != NULL) { @@ -7695,6 +7235,8 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co { partial_T *partial; char_u *name; + Array args = ARRAY_DICT_INIT; + Object rv; switch (callback->type) { case kCallbackFuncref: name = callback->data.funcref; @@ -7706,6 +7248,15 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co name = partial_name(partial); break; + case kCallbackLua: + rv = nlua_call_ref(callback->data.luaref, NULL, args, true, NULL); + switch (rv.type) { + case kObjectTypeBoolean: + return rv.data.boolean; + default: + return false; + } + case kCallbackNone: return false; break; @@ -7798,7 +7349,7 @@ void add_timer_info_all(typval_T *rettv) }) } -// invoked on the main loop +/// invoked on the main loop void timer_due_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; @@ -7884,8 +7435,8 @@ void timer_stop(timer_T *timer) time_watcher_close(&timer->tw, timer_close_cb); } -// This will be run on the main loop after the last timer_due_cb, so at this -// point it is safe to free the callback. +/// This will be run on the main loop after the last timer_due_cb, so at this +/// point it is safe to free the callback. static void timer_close_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; @@ -8110,6 +7661,68 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) return ret; } +/// Convert the specified byte index of line 'lnum' in buffer 'buf' to a +/// character index. Works only for loaded buffers. Returns -1 on failure. +/// The index of the first byte and the first character is zero. +int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx) +{ + if (buf == NULL || buf->b_ml.ml_mfp == NULL) { + return -1; + } + + if (lnum > buf->b_ml.ml_line_count) { + lnum = buf->b_ml.ml_line_count; + } + + char_u *str = ml_get_buf(buf, lnum, false); + + if (*str == NUL) { + return 0; + } + + // count the number of characters + char_u *t = str; + int count; + for (count = 0; *t != NUL && t <= str + byteidx; count++) { + t += utfc_ptr2len(t); + } + + // In insert mode, when the cursor is at the end of a non-empty line, + // byteidx points to the NUL character immediately past the end of the + // string. In this case, add one to the character count. + if (*t == NUL && byteidx != 0 && t == str + byteidx) { + count++; + } + + return count - 1; +} + +/// Convert the specified character index of line 'lnum' in buffer 'buf' to a +/// byte index. Works only for loaded buffers. +/// The index of the first byte and the first character is zero. +/// +/// @return -1 on failure. +int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx) +{ + if (buf == NULL || buf->b_ml.ml_mfp == NULL) { + return -1; + } + + if (lnum > buf->b_ml.ml_line_count) { + lnum = buf->b_ml.ml_line_count; + } + + char_u *str = ml_get_buf(buf, lnum, false); + + // Convert the character offset to a byte offset + char_u *t = str; + while (*t != NUL && --charidx > 0) { + t += utfc_ptr2len(t); + } + + return t - str; +} + /// Translate a VimL object into a position /// /// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid @@ -8118,13 +7731,14 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) /// @param[in] tv Object to translate. /// @param[in] dollar_lnum True when "$" is last line. /// @param[out] ret_fnum Set to fnum for marks. +/// @param[in] charcol True to return character column. /// /// @return Pointer to position or NULL in case of error (e.g. invalid type). -pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum) +pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum, + const bool charcol) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { static pos_T pos; - pos_T *pp; // Argument can be [lnum, col, coladd]. if (tv->v_type == VAR_LIST) { @@ -8150,7 +7764,11 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (error) { return NULL; } - len = (long)STRLEN(ml_get(pos.lnum)); + if (charcol) { + len = mb_charlen(ml_get(pos.lnum)); + } else { + len = STRLEN(ml_get(pos.lnum)); + } // We accept "$" for the column number: last column. li = tv_list_find(l, 1L); @@ -8180,21 +7798,31 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (name == NULL) { return NULL; } - if (name[0] == '.') { // Cursor. - return &curwin->w_cursor; - } - if (name[0] == 'v' && name[1] == NUL) { // Visual start. + + pos.lnum = 0; + if (name[0] == '.') { + // cursor + pos = curwin->w_cursor; + } else if (name[0] == 'v' && name[1] == NUL) { + // Visual start if (VIsual_active) { - return &VIsual; + pos = VIsual; + } else { + pos = curwin->w_cursor; } - return &curwin->w_cursor; - } - if (name[0] == '\'') { // Mark. - pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum); + } else if (name[0] == '\'') { + // mark + const pos_T *const pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum); if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) { return NULL; } - return pp; + pos = *pp; + } + if (pos.lnum != 0) { + if (charcol) { + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col); + } + return &pos; } pos.coladd = 0; @@ -8219,22 +7847,25 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret pos.col = 0; } else { pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + if (charcol) { + pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr()); + } else { + pos.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + } } return &pos; } return NULL; } -/* - * Convert list in "arg" into a position and optional file number. - * When "fnump" is NULL there is no file number, only 3 items. - * Note that the column is passed on as-is, the caller may want to decrement - * it to use 1 for the first column. - * Return FAIL when conversion is not possible, doesn't check the position for - * validity. - */ -int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) +/// Convert list in "arg" into a position and optional file number. +/// When "fnump" is NULL there is no file number, only 3 items. +/// Note that the column is passed on as-is, the caller may want to decrement +/// it to use 1 for the first column. +/// +/// @return FAIL when conversion is not possible, doesn't check the position for +/// validity. +int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol) { list_T *l; long i = 0; @@ -8270,6 +7901,15 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) if (n < 0) { return FAIL; } + // If character position is specified, then convert to byte position + if (charcol) { + // Get the text for the specified line in a loaded buffer + buf_T *buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump); + if (buf == NULL || buf->b_ml.ml_mfp == NULL) { + return FAIL; + } + n = buf_charidx_to_byteidx(buf, posp->lnum, n) + 1; + } posp->col = n; n = tv_list_find_nr(l, i, NULL); // off @@ -8286,11 +7926,10 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) return OK; } -/* - * Get the length of an environment variable name. - * Advance "arg" to the first character after the name. - * Return 0 for error. - */ +/// Get the length of an environment variable name. +/// Advance "arg" to the first character after the name. +/// +/// @return 0 for error. static int get_env_len(const char_u **arg) { int len; @@ -8307,9 +7946,11 @@ static int get_env_len(const char_u **arg) return len; } -// Get the length of the name of a function or internal variable. -// "arg" is advanced to the first non-white character after the name. -// Return 0 if something is wrong. +/// Get the length of the name of a function or internal variable. +/// +/// @param arg is advanced to the first non-white character after the name. +/// +/// @return 0 if something is wrong. int get_id_len(const char **const arg) { int len; @@ -8337,15 +7978,15 @@ int get_id_len(const char **const arg) return len; } -/* - * Get the length of the name of a variable or function. - * Only the name is recognized, does not handle ".key" or "[idx]". - * "arg" is advanced to the first non-white character after the name. - * Return -1 if curly braces expansion failed. - * Return 0 if something else is wrong. - * If the name contains 'magic' {}'s, expand them and return the - * expanded name in an allocated string via 'alias' - caller must free. - */ +/// Get the length of the name of a variable or function. +/// Only the name is recognized, does not handle ".key" or "[idx]". +/// +/// @param arg is advanced to the first non-white character after the name. +/// If the name contains 'magic' {}'s, expand them and return the +/// expanded name in an allocated string via 'alias' - caller must free. +/// +/// @return -1 if curly braces expansion failed or +/// 0 if something else is wrong. int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbose) { int len; @@ -8402,12 +8043,15 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo return len; } -// Find the end of a variable or function name, taking care of magic braces. -// If "expr_start" is not NULL then "expr_start" and "expr_end" are set to the -// start and end of the first magic braces item. -// "flags" can have FNE_INCL_BR and FNE_CHECK_START. -// Return a pointer to just after the name. Equal to "arg" if there is no -// valid name. +/// Find the end of a variable or function name, taking care of magic braces. +/// +/// @param expr_start if not NULL, then `expr_start` and `expr_end` are set to the +/// start and end of the first magic braces item. +/// +/// @param flags can have FNE_INCL_BR and FNE_CHECK_START. +/// +/// @return a pointer to just after the name. Equal to "arg" if there is no +/// valid name. const char_u *find_name_end(const char_u *arg, const char_u **expr_start, const char_u **expr_end, int flags) { @@ -8485,19 +8129,17 @@ const char_u *find_name_end(const char_u *arg, const char_u **expr_start, const return p; } -/* - * Expands out the 'magic' {}'s in a variable/function name. - * Note that this can call itself recursively, to deal with - * constructs like foo{bar}{baz}{bam} - * The four pointer arguments point to "foo{expre}ss{ion}bar" - * "in_start" ^ - * "expr_start" ^ - * "expr_end" ^ - * "in_end" ^ - * - * Returns a new allocated string, which the caller must free. - * Returns NULL for failure. - */ +/// Expands out the 'magic' {}'s in a variable/function name. +/// Note that this can call itself recursively, to deal with +/// constructs like foo{bar}{baz}{bam} +/// The four pointer arguments point to "foo{expre}ss{ion}bar" +/// "in_start" ^ +/// "expr_start" ^ +/// "expr_end" ^ +/// "in_end" ^ +/// +/// @return a new allocated string, which the caller must free or +/// NULL for failure. static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end) { @@ -8544,44 +8186,42 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start, ch return retval; } -/* - * Return TRUE if character "c" can be used in a variable or function name. - * Does not include '{' or '}' for magic braces. - */ +/// @return TRUE if character "c" can be used in a variable or function name. +/// Does not include '{' or '}' for magic braces. int eval_isnamec(int c) { return ASCII_ISALNUM(c) || c == '_' || c == ':' || c == AUTOLOAD_CHAR; } -/* - * Return TRUE if character "c" can be used as the first character in a - * variable or function name (excluding '{' and '}'). - */ +/// @return TRUE if character "c" can be used as the first character in a +/// variable or function name (excluding '{' and '}'). int eval_isnamec1(int c) { return ASCII_ISALPHA(c) || c == '_'; } -/* - * Get number v: variable value. - */ +/// Get typval_T v: variable value. +typval_T *get_vim_var_tv(int idx) +{ + return &vimvars[idx].vv_tv; +} + +/// Get number v: variable value. varnumber_T get_vim_var_nr(int idx) FUNC_ATTR_PURE { return vimvars[idx].vv_nr; } -// Get string v: variable value. Uses a static buffer, can only be used once. -// If the String variable has never been set, return an empty string. -// Never returns NULL; +/// Get string v: variable value. Uses a static buffer, can only be used once. +/// If the String variable has never been set, return an empty string. +/// Never returns NULL; char_u *get_vim_var_str(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET { return (char_u *)tv_get_string(&vimvars[idx].vv_tv); } -/* - * Get List v: variable value. Caller must take care of reference count when - * needed. - */ +/// Get List v: variable value. Caller must take care of reference count when +/// needed. list_T *get_vim_var_list(int idx) FUNC_ATTR_PURE { return vimvars[idx].vv_list; @@ -8594,9 +8234,7 @@ dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE return vimvars[idx].vv_dict; } -/* - * Set v:char to character "c". - */ +/// Set v:char to character "c". void set_vim_var_char(int c) { char buf[MB_MAXBYTES + 1]; @@ -8605,10 +8243,9 @@ void set_vim_var_char(int c) set_vim_var_string(VV_CHAR, buf, -1); } -/* - * Set v:count to "count" and v:count1 to "count1". - * When "set_prevcount" is TRUE first set v:prevcount from v:count. - */ +/// Set v:count to "count" and v:count1 to "count1". +/// +/// @param set_prevcount if TRUE, first set v:prevcount from v:count. void set_vcount(long count, long count1, int set_prevcount) { if (set_prevcount) { @@ -8716,9 +8353,7 @@ void set_argv_var(char **argv, int argc) set_vim_var_list(VV_ARGV, l); } -/* - * Set v:register if needed. - */ +/// Set v:register if needed. void set_reg_var(int c) { char regname; @@ -8734,12 +8369,10 @@ void set_reg_var(int c) } } -/* - * Get or set v:exception. If "oldval" == NULL, return the current value. - * Otherwise, restore the value to "oldval" and return NULL. - * Must always be called in pairs to save and restore v:exception! Does not - * take care of memory allocations. - */ +/// Get or set v:exception. If "oldval" == NULL, return the current value. +/// Otherwise, restore the value to "oldval" and return NULL. +/// Must always be called in pairs to save and restore v:exception! Does not +/// take care of memory allocations. char_u *v_exception(char_u *oldval) { if (oldval == NULL) { @@ -8750,12 +8383,10 @@ char_u *v_exception(char_u *oldval) return NULL; } -/* - * Get or set v:throwpoint. If "oldval" == NULL, return the current value. - * Otherwise, restore the value to "oldval" and return NULL. - * Must always be called in pairs to save and restore v:throwpoint! Does not - * take care of memory allocations. - */ +/// Get or set v:throwpoint. If "oldval" == NULL, return the current value. +/// Otherwise, restore the value to "oldval" and return NULL. +/// Must always be called in pairs to save and restore v:throwpoint! Does not +/// take care of memory allocations. char_u *v_throwpoint(char_u *oldval) { if (oldval == NULL) { @@ -8766,12 +8397,10 @@ char_u *v_throwpoint(char_u *oldval) return NULL; } -/* - * Set v:cmdarg. - * If "eap" != NULL, use "eap" to generate the value and return the old value. - * If "oldarg" != NULL, restore the value to "oldarg" and return NULL. - * Must always be called in pairs! - */ +/// Set v:cmdarg. +/// If "eap" != NULL, use "eap" to generate the value and return the old value. +/// If "oldarg" != NULL, restore the value to "oldarg" and return NULL. +/// Must always be called in pairs! char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) { char_u *oldval = vimvars[VV_CMDARG].vv_str; @@ -8909,7 +8538,7 @@ static bool tv_is_luafunc(typval_T *tv) const char *skip_luafunc_name(const char *p) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') { + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '-' || *p == '.' || *p == '\'') { p++; } return p; @@ -9036,11 +8665,12 @@ void set_selfdict(typval_T *const rettv, dict_T *const selfdict) make_partial(selfdict, rettv); } -// Find variable "name" in the list of variables. -// Return a pointer to it if found, NULL if not found. -// Careful: "a:0" variables don't have a name. -// When "htp" is not NULL we are writing to the variable, set "htp" to the -// hashtab_T used. +/// Find variable "name" in the list of variables. +/// Careful: "a:0" variables don't have a name. +/// When "htp" is not NULL we are writing to the variable, set "htp" to the +/// hashtab_T used. +/// +/// @return a pointer to it if found, NULL if not found. dictitem_T *find_var(const char *const name, const size_t name_len, hashtab_T **htp, int no_autoload) { @@ -9128,6 +8758,8 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va /// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. /// +/// Assigns SID if s: scope is accessed from Lua or anonymous Vimscript. #15994 +/// /// @param[in] name Variable name, possibly with scope prefix. /// @param[in] name_len Variable name length. /// @param[out] varname Will be set to the start of the name without scope @@ -9190,10 +8822,32 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, cons } else if (*name == 'l' && funccal != NULL) { // local variable *d = &funccal->l_vars; } else if (*name == 's' // script variable - && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR) + && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR + || current_sctx.sc_sid == SID_LUA) && current_sctx.sc_sid <= ga_scripts.ga_len) { // For anonymous scripts without a script item, create one now so script vars can be used - if (current_sctx.sc_sid == SID_STR) { + if (current_sctx.sc_sid == SID_LUA) { + // try to resolve lua filename & line no so it can be shown in lastset messages. + nlua_set_sctx(¤t_sctx); + if (current_sctx.sc_sid != SID_LUA) { + // Great we have valid location. Now here this out we'll create a new + // script context with the name and lineno of this one. why ? + // for behavioral consistency. With this different anonymous exec from + // same file can't access each others script local stuff. We need to do + // this all other cases except this will act like that otherwise. + const LastSet last_set = (LastSet){ + .script_ctx = current_sctx, + .channel_id = LUA_INTERNAL_CALL, + }; + bool should_free; + // should_free is ignored as script_sctx will be resolved to a fnmae + // & new_script_item will consume it. + char_u *sc_name = get_scriptname(last_set, &should_free); + new_script_item(sc_name, ¤t_sctx.sc_sid); + } + } + if (current_sctx.sc_sid == SID_STR || current_sctx.sc_sid == SID_LUA) { + // Create SID if s: scope is accessed from Lua or anon Vimscript. #15994 new_script_item(NULL, ¤t_sctx.sc_sid); } *d = &SCRIPT_SV(current_sctx.sc_sid)->sv_dict; @@ -9217,11 +8871,10 @@ hashtab_T *find_var_ht(const char *name, const size_t name_len, const char **var return find_var_ht_dict(name, name_len, varname, &d); } -/* - * Get the string value of a (global/local) variable. - * Note: see tv_get_string() for how long the pointer remains valid. - * Returns NULL when it doesn't exist. - */ +/// @return the string value of a (global/local) variable or +/// NULL when it doesn't exist. +/// +/// @see tv_get_string() for how long the pointer remains valid. char_u *get_var_value(const char *const name) { dictitem_T *v; @@ -9233,16 +8886,14 @@ char_u *get_var_value(const char *const name) return (char_u *)tv_get_string(&v->di_tv); } -/* - * Allocate a new hashtab for a sourced script. It will be used while - * sourcing this script and when executing functions defined in the script. - */ +/// Allocate a new hashtab for a sourced script. It will be used while +/// sourcing this script and when executing functions defined in the script. void new_script_vars(scid_T id) { hashtab_T *ht; scriptvar_T *sv; - ga_grow(&ga_scripts, (int)(id - ga_scripts.ga_len)); + ga_grow(&ga_scripts, id - ga_scripts.ga_len); { /* Re-allocating ga_data means that an ht_array pointing to * ht_smallarray becomes invalid. We can recognize this: ht_mask is @@ -9264,10 +8915,8 @@ void new_script_vars(scid_T id) } } -/* - * Initialize dictionary "dict" as a scope and set variable "dict_var" to - * point to it. - */ +/// Initialize dictionary "dict" as a scope and set variable "dict_var" to +/// point to it. void init_var_dict(dict_T *dict, ScopeDictDictItem *dict_var, int scope) { hash_init(&dict->dv_hashtab); @@ -9283,9 +8932,7 @@ void init_var_dict(dict_T *dict, ScopeDictDictItem *dict_var, int scope) QUEUE_INIT(&dict->watchers); } -/* - * Unreference a dictionary initialized by init_var_dict(). - */ +/// Unreference a dictionary initialized by init_var_dict(). void unref_var_dict(dict_T *dict) { /* Now the dict needs to be freed if no one else is using it, go back to @@ -9294,19 +8941,15 @@ void unref_var_dict(dict_T *dict) tv_dict_unref(dict); } -/* - * Clean up a list of internal variables. - * Frees all allocated variables and the value they contain. - * Clears hashtab "ht", does not free it. - */ +/// Clean up a list of internal variables. +/// Frees all allocated variables and the value they contain. +/// Clears hashtab "ht", does not free it. void vars_clear(hashtab_T *ht) { vars_clear_ext(ht, TRUE); } -/* - * Like vars_clear(), but only free the value if "free_val" is TRUE. - */ +/// Like vars_clear(), but only free the value if "free_val" is TRUE. void vars_clear_ext(hashtab_T *ht, int free_val) { int todo; @@ -9335,10 +8978,8 @@ void vars_clear_ext(hashtab_T *ht, int free_val) ht->ht_used = 0; } -/* - * Delete a variable from hashtab "ht" at item "hi". - * Clear the variable value and free the dictitem. - */ +/// Delete a variable from hashtab "ht" at item "hi". +/// Clear the variable value and free the dictitem. static void delete_var(hashtab_T *ht, hashitem_T *hi) { dictitem_T *di = TV_DICT_HI2DI(hi); @@ -9348,9 +8989,7 @@ static void delete_var(hashtab_T *ht, hashitem_T *hi) xfree(di); } -/* - * List the value of one internal variable. - */ +/// List the value of one internal variable. static void list_one_var(dictitem_T *v, const char *prefix, int *first) { char *const s = encode_tv2echo(&v->di_tv, NULL); @@ -9649,8 +9288,8 @@ bool var_check_func_name(const char *const name, const bool new_var) { // Allow for w: b: s: and t:. if (!(vim_strchr((char_u *)"wbst", name[0]) != NULL && name[1] == ':') - && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') ? name[2] - : name[0])) { + && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') + ? name[2] : name[0])) { semsg(_("E704: Funcref variable name must start with a capital: %s"), name); return false; } @@ -9783,11 +9422,9 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c return ret; } -/* - * ":echo expr1 ..." print each argument separated with a space, add a - * newline at the end. - * ":echon expr1 ..." print each argument plain. - */ +/// ":echo expr1 ..." print each argument separated with a space, add a +/// newline at the end. +/// ":echon expr1 ..." print each argument plain. void ex_echo(exarg_T *eap) { char_u *arg = eap->arg; @@ -9861,21 +9498,17 @@ void ex_echo(exarg_T *eap) } } -/* - * ":echohl {name}". - */ +/// ":echohl {name}". void ex_echohl(exarg_T *eap) { echo_attr = syn_name2attr(eap->arg); } -/* - * ":execute expr1 ..." execute the result of an expression. - * ":echomsg expr1 ..." Print a message - * ":echoerr expr1 ..." Print an error - * Each gets spaces around each argument and a newline at the end for - * echo commands - */ +/// ":execute expr1 ..." execute the result of an expression. +/// ":echomsg expr1 ..." Print a message +/// ":echoerr expr1 ..." Print an error +/// Each gets spaces around each argument and a newline at the end for +/// echo commands void ex_execute(exarg_T *eap) { char_u *arg = eap->arg; @@ -9952,12 +9585,12 @@ void ex_execute(exarg_T *eap) eap->nextcmd = check_nextcmd(arg); } -/* - * Skip over the name of an option: "&option", "&g:option" or "&l:option". - * "arg" points to the "&" or '+' when called, to "option" when returning. - * Returns NULL when no option name found. Otherwise pointer to the char - * after the option name. - */ +/// Skip over the name of an option: "&option", "&g:option" or "&l:option". +/// +/// @param arg points to the "&" or '+' when called, to "option" when returning. +/// +/// @return NULL when no option name found. Otherwise pointer to the char +/// after the option name. static const char *find_option_end(const char **const arg, int *const opt_flags) { const char *p = *arg; @@ -10020,9 +9653,7 @@ void func_do_profile(ufunc_T *fp) fp->uf_profiling = TRUE; } -/* - * Dump the profiling results for all functions in file "fd". - */ +/// Dump the profiling results for all functions in file "fd". void func_dump_profile(FILE *fd) { hashitem_T *hi; @@ -10142,9 +9773,7 @@ static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *s } } -/* - * Compare function for total time sorting. - */ +/// Compare function for total time sorting. static int prof_total_cmp(const void *s1, const void *s2) { ufunc_T *p1 = *(ufunc_T **)s1; @@ -10152,9 +9781,7 @@ static int prof_total_cmp(const void *s1, const void *s2) return profile_cmp(p1->uf_tm_total, p2->uf_tm_total); } -/* - * Compare function for self time sorting. - */ +/// Compare function for self time sorting. static int prof_self_cmp(const void *s1, const void *s2) { ufunc_T *p1 = *(ufunc_T **)s1; @@ -10236,12 +9863,10 @@ bool script_autoload(const char *const name, const size_t name_len, const bool r return ret; } -/* - * Called when starting to read a function line. - * "sourcing_lnum" must be correct! - * When skipping lines it may not actually be executed, but we won't find out - * until later and we need to store the time now. - */ +/// Called when starting to read a function line. +/// "sourcing_lnum" must be correct! +/// When skipping lines it may not actually be executed, but we won't find out +/// until later and we need to store the time now. void func_line_start(void *cookie) { funccall_T *fcp = (funccall_T *)cookie; @@ -10261,9 +9886,7 @@ void func_line_start(void *cookie) } } -/* - * Called when actually executing a function line. - */ +/// Called when actually executing a function line. void func_line_exec(void *cookie) { funccall_T *fcp = (funccall_T *)cookie; @@ -10274,9 +9897,7 @@ void func_line_exec(void *cookie) } } -/* - * Called when done with a function line. - */ +/// Called when done with a function line. void func_line_end(void *cookie) { funccall_T *fcp = (funccall_T *)cookie; @@ -10411,10 +10032,8 @@ int store_session_globals(FILE *fd) return OK; } -/* - * Display script name where an item was last set. - * Should only be invoked when 'verbose' is non-zero. - */ +/// Display script name where an item was last set. +/// Should only be invoked when 'verbose' is non-zero. void last_set_msg(sctx_T script_ctx) { const LastSet last_set = (LastSet){ @@ -10446,11 +10065,15 @@ void option_last_set_msg(LastSet last_set) } } -// reset v:option_new, v:option_old and v:option_type +// reset v:option_new, v:option_old, v:option_oldlocal, v:option_oldglobal, +// v:option_type, and v:option_command. void reset_v_option_vars(void) { - set_vim_var_string(VV_OPTION_NEW, NULL, -1); - set_vim_var_string(VV_OPTION_OLD, NULL, -1); + set_vim_var_string(VV_OPTION_NEW, NULL, -1); + set_vim_var_string(VV_OPTION_OLD, NULL, -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, NULL, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, NULL, -1); + set_vim_var_string(VV_OPTION_COMMAND, NULL, -1); set_vim_var_string(VV_OPTION_TYPE, NULL, -1); } @@ -10474,12 +10097,13 @@ int modify_fname(char_u *src, bool tilde_file, size_t *usedlen, char_u **fnamep, char_u *s, *p, *pbuf; char_u dirname[MAXPATHL]; int c; - int has_fullname = 0; + bool has_fullname = false; + bool has_homerelative = false; repeat: // ":p" - full path/file_name if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') { - has_fullname = 1; + has_fullname = true; valid |= VALID_PATH; *usedlen += 2; @@ -10548,8 +10172,8 @@ repeat: } pbuf = NULL; // Need full path first (use expand_env() to remove a "~/") - if (!has_fullname) { - if (c == '.' && **fnamep == '~') { + if (!has_fullname && !has_homerelative) { + if (**fnamep == '~') { p = pbuf = expand_env_save(*fnamep); } else { p = pbuf = (char_u *)FullName_save((char *)*fnamep, FALSE); @@ -10558,18 +10182,33 @@ repeat: p = *fnamep; } - has_fullname = 0; + has_fullname = false; if (p != NULL) { if (c == '.') { os_dirname(dirname, MAXPATHL); - s = path_shorten_fname(p, dirname); - if (s != NULL) { - *fnamep = s; - if (pbuf != NULL) { - xfree(*bufp); // free any allocated file name - *bufp = pbuf; - pbuf = NULL; + if (has_homerelative) { + s = vim_strsave(dirname); + home_replace(NULL, s, dirname, MAXPATHL, true); + xfree(s); + } + size_t namelen = STRLEN(dirname); + + // Do not call shorten_fname() here since it removes the prefix + // even though the path does not have a prefix. + if (fnamencmp(p, dirname, namelen) == 0) { + p += namelen; + if (vim_ispathsep(*p)) { + while (*p && vim_ispathsep(*p)) { + p++; + } + *fnamep = p; + if (pbuf != NULL) { + // free any allocated file name + xfree(*bufp); + *bufp = pbuf; + pbuf = NULL; + } } } } else { @@ -10580,6 +10219,7 @@ repeat: *fnamep = s; xfree(*bufp); *bufp = s; + has_homerelative = true; } } xfree(pbuf); @@ -10746,7 +10386,8 @@ repeat: /// Perform a substitution on "str" with pattern "pat" and substitute "sub". /// When "sub" is NULL "expr" is used, must be a VAR_FUNC or VAR_PARTIAL. /// "flags" can be "g" to do a global substitute. -/// Returns an allocated string, NULL for error. +/// +/// @return an allocated string, NULL for error. char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, typval_T *expr, char_u *flags) { int sublen; @@ -10956,10 +10597,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo bool eval_has_provider(const char *feat) { if (!strequal(feat, "clipboard") - && !strequal(feat, "python") && !strequal(feat, "python3") - && !strequal(feat, "python_compiled") - && !strequal(feat, "python_dynamic") && !strequal(feat, "python3_compiled") && !strequal(feat, "python3_dynamic") && !strequal(feat, "perl") @@ -11085,7 +10723,7 @@ void invoke_prompt_callback(void) tv_clear(&rettv); } -// Return true When the interrupt callback was invoked. +/// @return true when the interrupt callback was invoked. bool invoke_prompt_interrupt(void) { typval_T rettv; diff --git a/src/nvim/eval.h b/src/nvim/eval.h index d34348a274..a9ec5d47a6 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -138,6 +138,9 @@ typedef enum { VV_COMPLETED_ITEM, VV_OPTION_NEW, VV_OPTION_OLD, + VV_OPTION_OLDLOCAL, + VV_OPTION_OLDGLOBAL, + VV_OPTION_COMMAND, VV_OPTION_TYPE, VV_ERRORS, VV_FALSE, @@ -190,6 +193,13 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1]; #undef LAST_MSGPACK_TYPE +// Struct passed to get_v_event() and restore_v_event(). +typedef struct { + bool sve_did_save; + hashtab_T sve_hashtab; +} save_v_event_T; + + /// trans_function_name() flags typedef enum { TFN_INT = 1, ///< May use internal function name @@ -209,8 +219,8 @@ typedef enum { /// flags for find_name_end() #define FNE_INCL_BR 1 // find_name_end(): include [] in name -#define FNE_CHECK_START 2 /* find_name_end(): check name starts with - valid character */ +#define FNE_CHECK_START 2 // find_name_end(): check name starts with + // valid character typedef struct { TimeWatcher tw; diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index dfc51d80af..698cffe2fa 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -17,7 +17,7 @@ end -- Usable with the base key: use the last function argument as the method base. -- Value is from funcs.h file. "BASE_" prefix is omitted. -local LAST = "BASE_LAST" +-- local LAST = "BASE_LAST" (currently unused after port of v8.2.1168) return { funcs={ @@ -26,14 +26,14 @@ return { add={args=2, base=1}, ['and']={args=2, base=1}, api_info={}, - append={args=2, base=LAST}, - appendbufline={args=3, base=LAST}, + append={args=2, base=2}, + appendbufline={args=3, base=3}, argc={args={0, 1}}, argidx={}, arglistid={args={0, 2}}, argv={args={0, 2}}, asin={args=1, base=1, func="float_op_wrapper", data="&asin"}, -- WJMc - assert_beeps={args={1}, base=1}, + assert_beeps={args=1, base=1}, assert_equal={args={2, 3}, base=2}, assert_equalfile={args={2, 3}, base=1}, assert_exception={args={1, 2}}, @@ -41,7 +41,7 @@ return { assert_false={args={1, 2}, base=1}, assert_inrange={args={3, 4}, base=3}, assert_match={args={2, 3}, base=2}, - assert_nobeep={args={1}}, + assert_nobeep={args=1, base=1}, assert_notequal={args={2, 3}, base=2}, assert_notmatch={args={2, 3}, base=2}, assert_report={args=1, base=1}, @@ -53,8 +53,8 @@ return { bufadd={args=1, base=1}, bufexists={args=1, base=1}, buffer_exists={args=1, base=1, func='f_bufexists'}, -- obsolete - buffer_name={args={0, 1}, func='f_bufname'}, -- obsolete - buffer_number={args={0, 1}, func='f_bufnr'}, -- obsolete + buffer_name={args={0, 1}, base=1, func='f_bufname'}, -- obsolete + buffer_number={args={0, 1}, base=1, func='f_bufnr'}, -- obsolete buflisted={args=1, base=1}, bufload={args=1, base=1}, bufloaded={args=1, base=1}, @@ -71,7 +71,8 @@ return { chanclose={args={1, 2}}, chansend={args=2}, char2nr={args={1, 2}, base=1}, - charidx={args={2, 3}}, + charcol={args=1, base=1}, + charidx={args={2, 3}, base=1}, chdir={args=1, base=1}, cindent={args=1, base=1}, clearmatches={args={0, 1}, base=1}, @@ -101,6 +102,10 @@ return { did_filetype={}, diff_filler={args=1, base=1}, diff_hlID={args=2, base=1}, + digraph_get={args=1, base=1}, + digraph_getlist={args={0, 1}, base=1}, + digraph_set={args=2, base=1}, + digraph_setlist={args=1, base=1}, empty={args=1, base=1}, environ={}, escape={args=2, base=1}, @@ -121,7 +126,7 @@ return { filter={args=2, base=1}, finddir={args={1, 3}, base=1}, findfile={args={1, 3}, base=1}, - flatten={args={1, 2}}, + flatten={args={1, 2}, base=1}, float2nr={args=1, base=1}, floor={args=1, base=1, func="float_op_wrapper", data="&floor"}, fmod={args=2, base=1}, @@ -133,16 +138,18 @@ return { foldtext={}, foldtextresult={args=1, base=1}, foreground={}, + fullcommand={args=1, base=1}, funcref={args={1, 3}, base=1}, ['function']={args={1, 3}, base=1}, garbagecollect={args={0, 1}}, get={args={2, 3}, base=1}, - getbufinfo={args={0, 1}}, + getbufinfo={args={0, 1}, base=1}, getbufline={args={2, 3}, base=1}, getbufvar={args={2, 3}, base=1}, getchangelist={args={0, 1}, base=1}, getchar={args={0, 1}}, getcharmod={}, + getcharpos={args=1, base=1}, getcharsearch={}, getcharstr={args={0, 1}}, getcmdline={}, @@ -150,9 +157,10 @@ return { getcmdtype={}, getcmdwintype={}, getcompletion={args={2, 3}, base=1}, - getcurpos={}, + getcurpos={args={0, 1}, base=1}, + getcursorcharpos={args={0, 1}, base=1}, getcwd={args={0, 2}, base=1}, - getenv={args={1}, base=1}, + getenv={args=1, base=1}, getfontname={args={0, 1}}, getfperm={args=1, base=1}, getfsize={args=1, base=1}, @@ -161,7 +169,7 @@ return { getjumplist={args={0, 2}, base=1}, getline={args={1, 2}, base=1}, getloclist={args={1, 2}}, - getmarklist={args={0, 1}}, + getmarklist={args={0, 1}, base=1}, getmatches={args={0, 1}}, getmousepos={}, getpid={}, @@ -245,6 +253,8 @@ return { matcharg={args=1, base=1}, matchdelete={args={1, 2}, base=1}, matchend={args={2, 4}, base=1}, + matchfuzzy={args={2, 3}, base=1}, + matchfuzzypos={args={2, 3}, base=1}, matchlist={args={2, 4}, base=1}, matchstr={args={2, 4}, base=1}, matchstrpos={args={2,4}, base=1}, @@ -258,25 +268,28 @@ return { nextnonblank={args=1, base=1}, nr2char={args={1, 2}, base=1}, ['or']={args=2, base=1}, - pathshorten={args=1, base=1}, + pathshorten={args={1, 2}, base=1}, pow={args=2, base=1}, prevnonblank={args=1, base=1}, printf={args=varargs(1), base=2}, - prompt_getprompt={args=1}, + prompt_getprompt={args=1, base=1}, prompt_setcallback={args={2, 2}, base=1}, prompt_setinterrupt={args={2, 2}, base=1}, prompt_setprompt={args={2, 2}, base=1}, pum_getpos={}, pumvisible={}, py3eval={args=1, base=1}, - pyeval={args=1, base=1}, - pyxeval={args=1, base=1}, + pyeval={args=1, base=1, func="f_py3eval"}, + pyxeval={args=1, base=1, func="f_py3eval"}, perleval={args=1, base=1}, + rand={args={0, 1}, base=1}, range={args={1, 3}, base=1}, readdir={args={1, 2}, base=1}, readfile={args={1, 3}, base=1}, + reduce={args={2, 3}, base=1}, reg_executing={}, reg_recording={}, + reg_recorded={}, reltime={args={0, 2}, base=1}, reltimefloat={args=1, base=1}, reltimestr={args=1, base=1}, @@ -291,82 +304,85 @@ return { rpcstart={args={1, 2}}, rpcstop={args=1}, rubyeval={args=1, base=1}, - screenattr={args=2}, - screenchar={args=2}, - screenchars={args=2}, + screenattr={args=2, base=1}, + screenchar={args=2, base=1}, + screenchars={args=2, base=1}, screencol={}, - screenpos={args=3}, + screenpos={args=3, base=1}, screenrow={}, - screenstring={args=2}, - search={args={1, 4}}, - searchcount={args={0,1}}, - searchdecl={args={1, 3}}, + screenstring={args=2, base=1}, + search={args={1, 5}, base=1}, + searchcount={args={0, 1}, base=1}, + searchdecl={args={1, 3}, base=1}, searchpair={args={3, 7}}, searchpairpos={args={3, 7}}, - searchpos={args={1, 4}}, + searchpos={args={1, 5}, base=1}, serverlist={}, serverstart={args={0, 1}}, serverstop={args=1}, - setbufline={args=3}, - setbufvar={args=3}, - setcharsearch={args=1}, - setcmdpos={args=1}, - setenv={args=2}, + setbufline={args=3, base=3}, + setbufvar={args=3, base=3}, + setcharpos={args=2, base=2}, + setcharsearch={args=1, base=1}, + setcmdpos={args=1, base=1}, + setcursorcharpos={args={1, 3}, base=1}, + setenv={args=2, base=2}, setfperm={args=2, base=1}, - setline={args=2}, - setloclist={args={2, 4}}, - setmatches={args={1, 2}}, - setpos={args=2}, - setqflist={args={1, 3}}, - setreg={args={2, 3}}, - settabvar={args=3}, - settabwinvar={args=4}, - settagstack={args={2, 3}}, - setwinvar={args=3}, - sha256={args=1}, - shellescape={args={1, 2}}, - shiftwidth={args={0, 1}}, - sign_define={args={1, 2}}, - sign_getdefined={args={0, 1}}, - sign_getplaced={args={0, 2}}, - sign_jump={args={3, 3}}, - sign_place={args={4, 5}}, - sign_placelist={args={1}}, - sign_undefine={args={0, 1}}, - sign_unplace={args={1, 2}}, - sign_unplacelist={args={1}}, - simplify={args=1}, + setline={args=2, base=2}, + setloclist={args={2, 4}, base=2}, + setmatches={args={1, 2}, base=1}, + setpos={args=2, base=2}, + setqflist={args={1, 3}, base=1}, + setreg={args={2, 3}, base=2}, + settabvar={args=3, base=3}, + settabwinvar={args=4, base=4}, + settagstack={args={2, 3}, base=2}, + setwinvar={args=3, base=3}, + sha256={args=1, base=1}, + shellescape={args={1, 2}, base=1}, + shiftwidth={args={0, 1}, base=1}, + sign_define={args={1, 2}, base=1}, + sign_getdefined={args={0, 1}, base=1}, + sign_getplaced={args={0, 2}, base=1}, + sign_jump={args=3, base=1}, + sign_place={args={4, 5}, base=1}, + sign_placelist={args=1, base=1}, + sign_undefine={args={0, 1}, base=1}, + sign_unplace={args={1, 2}, base=1}, + sign_unplacelist={args=1, base=1}, + simplify={args=1, base=1}, sin={args=1, base=1, func="float_op_wrapper", data="&sin"}, sinh={args=1, base=1, func="float_op_wrapper", data="&sinh"}, sockconnect={args={2,3}}, sort={args={1, 3}, base=1}, - soundfold={args=1}, + soundfold={args=1, base=1}, stdioopen={args=1}, - spellbadword={args={0, 1}}, - spellsuggest={args={1, 3}}, + spellbadword={args={0, 1}, base=1}, + spellsuggest={args={1, 3}, base=1}, split={args={1, 3}, base=1}, sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"}, + srand={args={0, 1}, base=1}, stdpath={args=1}, str2float={args=1, base=1}, str2list={args={1, 2}, base=1}, - str2nr={args={1, 3}}, - strcharpart={args={2, 3}}, - strchars={args={1,2}}, - strdisplaywidth={args={1, 2}}, - strftime={args={1, 2}}, - strgetchar={args={2, 2}}, - stridx={args={2, 3}}, + str2nr={args={1, 3}, base=1}, + strcharpart={args={2, 3}, base=1}, + strchars={args={1, 2}, base=1}, + strdisplaywidth={args={1, 2}, base=1}, + strftime={args={1, 2}, base=1}, + strgetchar={args=2, base=1}, + stridx={args={2, 3}, base=1}, string={args=1, base=1}, strlen={args=1, base=1}, - strpart={args={2, 4}}, - strptime={args=2}, - strridx={args={2, 3}}, + strpart={args={2, 4}, base=1}, + strptime={args=2, base=1}, + strridx={args={2, 3}, base=1}, strtrans={args=1, base=1}, strwidth={args=1, base=1}, - submatch={args={1, 2}}, + submatch={args={1, 2}, base=1}, substitute={args=4, base=1}, - swapinfo={args={1}}, - swapname={args={1}}, + swapinfo={args=1, base=1}, + swapname={args=1, base=1}, synID={args=3}, synIDattr={args={2, 3}, base=1}, synIDtrans={args=1, base=1}, @@ -374,58 +390,60 @@ return { synstack={args=2}, system={args={1, 2}, base=1}, systemlist={args={1, 3}, base=1}, - tabpagebuflist={args={0, 1}}, + tabpagebuflist={args={0, 1}, base=1}, tabpagenr={args={0, 1}}, - tabpagewinnr={args={1, 2}}, + tabpagewinnr={args={1, 2}, base=1}, tagfiles={}, - taglist={args={1, 2}}, + taglist={args={1, 2}, base=1}, tan={args=1, base=1, func="float_op_wrapper", data="&tan"}, tanh={args=1, base=1, func="float_op_wrapper", data="&tanh"}, tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, test_write_list_log={args=1}, - timer_info={args={0,1}}, - timer_pause={args=2}, - timer_start={args={2,3}}, - timer_stop={args=1}, + timer_info={args={0, 1}, base=1}, + timer_pause={args=2, base=1}, + timer_start={args={2, 3}, base=1}, + timer_stop={args=1, base=1}, timer_stopall={args=0}, - tolower={args=1}, - toupper={args=1}, - tr={args=3}, - trim={args={1,3}}, + tolower={args=1, base=1}, + toupper={args=1, base=1}, + tr={args=3, base=1}, + trim={args={1, 3}, base=1}, trunc={args=1, base=1, func="float_op_wrapper", data="&trunc"}, type={args=1, base=1}, - undofile={args=1}, + undofile={args=1, base=1}, undotree={}, uniq={args={1, 3}, base=1}, values={args=1, base=1}, - virtcol={args=1}, + virtcol={args=1, base=1}, visualmode={args={0, 1}}, wait={args={2,3}}, wildmenumode={}, - win_execute={args={2, 3}}, - win_findbuf={args=1}, - win_getid={args={0,2}}, - win_gettype={args={0,1}}, - win_gotoid={args=1}, - win_id2tabwin={args=1}, - win_id2win={args=1}, - win_screenpos={args=1}, - win_splitmove={args={2, 3}}, + win_execute={args={2, 3}, base=2}, + win_findbuf={args=1, base=1}, + win_getid={args={0, 2}, base=1}, + win_gettype={args={0, 1}, base=1}, + win_gotoid={args=1, base=1}, + win_id2tabwin={args=1, base=1}, + win_id2win={args=1, base=1}, + win_move_separator={args=2, base=1}, + win_move_statusline={args=2, base=1}, + win_screenpos={args=1, base=1}, + win_splitmove={args={2, 3}, base=1}, winbufnr={args=1, base=1}, wincol={}, windowsversion={}, - winheight={args=1}, - winlayout={args={0, 1}}, + winheight={args=1, base=1}, + winlayout={args={0, 1}, base=1}, winline={}, - winnr={args={0, 1}}, + winnr={args={0, 1}, base=1}, winrestcmd={}, - winrestview={args=1}, + winrestview={args=1, base=1}, winsaveview={}, - winwidth={args=1}, + winwidth={args=1, base=1}, wordcount={}, - writefile={args={2, 3}}, + writefile={args={2, 3}, base=1}, xor={args=2, base=1}, }, } diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 5008945f09..797420c150 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -726,11 +726,11 @@ json_decode_string_cycle_start: semsg(_("E474: Using comma in place of colon: %.*s"), LENP(p, e)); goto json_decode_string_fail; } else if (last_container.special_val == NULL - ? (last_container.container.v_type == VAR_DICT - ? (DICT_LEN(last_container.container.vval.v_dict) == 0) - : (tv_list_len(last_container.container.vval.v_list) - == 0)) - : (tv_list_len(last_container.special_val) == 0)) { + ? (last_container.container.v_type == VAR_DICT + ? (DICT_LEN(last_container.container.vval.v_dict) == 0) + : (tv_list_len(last_container.container.vval.v_list) + == 0)) + : (tv_list_len(last_container.special_val) == 0)) { semsg(_("E474: Leading comma: %.*s"), LENP(p, e)); goto json_decode_string_fail; } diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index d694b8a374..6f4357421b 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -352,20 +352,20 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s const float_T flt_ = (flt); \ switch (xfpclassify(flt_)) { \ case FP_NAN: { \ - ga_concat(gap, "str2float('nan')"); \ - break; \ + ga_concat(gap, "str2float('nan')"); \ + break; \ } \ case FP_INFINITE: { \ - if (flt_ < 0) { \ - ga_append(gap, '-'); \ - } \ - ga_concat(gap, "str2float('inf')"); \ - break; \ + if (flt_ < 0) { \ + ga_append(gap, '-'); \ + } \ + ga_concat(gap, "str2float('inf')"); \ + break; \ } \ default: { \ - char numbuf[NUMBUFLEN]; \ - vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \ - ga_concat(gap, numbuf); \ + char numbuf[NUMBUFLEN]; \ + vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \ + ga_concat(gap, numbuf); \ } \ } \ } while (0) @@ -556,18 +556,18 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s const float_T flt_ = (flt); \ switch (xfpclassify(flt_)) { \ case FP_NAN: { \ - emsg(_("E474: Unable to represent NaN value in JSON")); \ - return FAIL; \ + emsg(_("E474: Unable to represent NaN value in JSON")); \ + return FAIL; \ } \ case FP_INFINITE: { \ - emsg(_("E474: Unable to represent infinity in JSON")); \ - return FAIL; \ + emsg(_("E474: Unable to represent infinity in JSON")); \ + return FAIL; \ } \ default: { \ - char numbuf[NUMBUFLEN]; \ - vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \ - ga_concat(gap, numbuf); \ - break; \ + char numbuf[NUMBUFLEN]; \ + vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \ + ga_concat(gap, numbuf); \ + break; \ } \ } \ } while (0) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 714c60c27e..609db3990b 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -15,6 +15,7 @@ #include "nvim/charset.h" #include "nvim/context.h" #include "nvim/cursor.h" +#include "nvim/digraph.h" #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/eval.h" @@ -22,6 +23,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" @@ -30,15 +32,17 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/globals.h" +#include "nvim/highlight_group.h" #include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/input.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/match.h" #include "nvim/math.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" @@ -61,6 +65,7 @@ #include "nvim/state.h" #include "nvim/syntax.h" #include "nvim/tag.h" +#include "nvim/testing.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/version.h" @@ -97,9 +102,9 @@ PRAGMA_DIAG_POP #endif -static char *e_listarg = N_("E686: Argument of %s must be a List"); static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); static char *e_invalwindow = N_("E957: Invalid window number"); +static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); /// Dummy va_list for passing to vim_snprintf /// @@ -174,7 +179,7 @@ char_u *get_expr_name(expand_T *xp, int idx) /// /// @param[in] name Name of the function. /// -/// Returns pointer to the function definition or NULL if not found. +/// @return pointer to the function definition or NULL if not found. const VimLFuncDef *find_internal_func(const char *const name) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL { @@ -228,9 +233,7 @@ int call_internal_method(const char_u *const fname, const int argcount, typval_T return ERROR_NONE; } -/* - * Return TRUE for a non-zero Number and a non-empty String. - */ +/// @return TRUE for a non-zero Number and a non-empty String. static int non_zero_arg(typval_T *argvars) { return ((argvars[0].v_type == VAR_NUMBER @@ -242,11 +245,11 @@ static int non_zero_arg(typval_T *argvars) && *argvars[0].vval.v_string != NUL)); } -// Apply a floating point C function on a typval with one float_T. -// -// Some versions of glibc on i386 have an optimization that makes it harder to -// call math functions indirectly from inside an inlined function, causing -// compile-time errors. Avoid `inline` in that case. #3072 +/// Apply a floating point C function on a typval with one float_T. +/// +/// Some versions of glibc on i386 have an optimization that makes it harder to +/// call math functions indirectly from inside an inlined function, causing +/// compile-time errors. Avoid `inline` in that case. #3072 static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T f; @@ -292,9 +295,7 @@ end: api_clear_error(&err); } -/* - * "abs(expr)" function - */ +/// "abs(expr)" function static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type == VAR_FLOAT) { @@ -314,9 +315,7 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "add(list, item)" function - */ +/// "add(list, item)" function static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = 1; // Default: failed. @@ -344,9 +343,7 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "and(expr, expr)" function - */ +/// "and(expr, expr)" function static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) @@ -362,7 +359,7 @@ static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) api_free_dictionary(metadata); } -// "append(lnum, string/list)" function +/// "append(lnum, string/list)" function static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const linenr_T lnum = tv_get_lnum(&argvars[0]); @@ -370,7 +367,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); } -// "appendbufline(buf, lnum, string/list)" function +/// "appendbufline(buf, lnum, string/list)" function static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *const buf = tv_get_buf(&argvars[0], false); @@ -402,9 +399,7 @@ static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "argidx()" function - */ +/// "argidx()" function static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = curwin->w_arg_idx; @@ -420,9 +415,7 @@ static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "argv(nr)" function - */ +/// "argv(nr)" function static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) { aentry_T *arglist = NULL; @@ -457,93 +450,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "assert_beeps(cmd [, error])" function -static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_beeps(argvars, false); -} - -// "assert_nobeep(cmd [, error])" function -static void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_beeps(argvars, true); -} - -// "assert_equal(expected, actual[, msg])" function -static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); -} - -// "assert_equalfile(fname-one, fname-two[, msg])" function -static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equalfile(argvars); -} - -// "assert_notequal(expected, actual[, msg])" function -static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); -} - -/// "assert_report(msg) -static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - garray_T ga; - - prepare_assert_error(&ga); - ga_concat(&ga, tv_get_string(&argvars[0])); - assert_error(&ga); - ga_clear(&ga); - rettv->vval.v_number = 1; -} - -/// "assert_exception(string[, msg])" function -static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_exception(argvars); -} - -/// "assert_fails(cmd [, error [, msg]])" function -static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_fails(argvars); -} - -// "assert_false(actual[, msg])" function -static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, false); -} - -/// "assert_inrange(lower, upper[, msg])" function -static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_inrange(argvars); -} - -/// "assert_match(pattern, actual[, msg])" function -static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); -} - -/// "assert_notmatch(pattern, actual[, msg])" function -static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); -} - -// "assert_true(actual[, msg])" function -static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, true); -} - -/* - * "atan2()" function - */ +/// "atan2()" function static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T fx; @@ -557,27 +464,21 @@ static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "browse(save, title, initdir, default)" function - */ +/// "browse(save, title, initdir, default)" function static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_string = NULL; rettv->v_type = VAR_STRING; } -/* - * "browsedir(title, initdir)" function - */ +/// "browsedir(title, initdir)" function static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { f_browse(argvars, rettv, NULL); } -/* - * Find a buffer by number or exact name. - */ +/// Find a buffer by number or exact name. static buf_T *find_buffer(typval_T *avar) { buf_T *buf = NULL; @@ -604,7 +505,7 @@ static buf_T *find_buffer(typval_T *avar) return buf; } -// "bufadd(expr)" function +/// "bufadd(expr)" function static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *name = (char_u *)tv_get_string(&argvars[0]); @@ -612,17 +513,13 @@ static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); } -/* - * "bufexists(expr)" function - */ +/// "bufexists(expr)" function static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); } -/* - * "buflisted(expr)" function - */ +/// "buflisted(expr)" function static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -631,7 +528,7 @@ static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = (buf != NULL && buf->b_p_bl); } -// "bufload(expr)" function +/// "bufload(expr)" function static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) { buf_T *buf = get_buf_arg(&argvars[0]); @@ -646,9 +543,7 @@ static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) } } -/* - * "bufloaded(expr)" function - */ +/// "bufloaded(expr)" function static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -657,9 +552,7 @@ static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); } -/* - * "bufname(expr)" function - */ +/// "bufname(expr)" function static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const buf_T *buf; @@ -675,9 +568,7 @@ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "bufnr(expr)" function - */ +/// "bufnr(expr)" function static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const buf_T *buf; @@ -749,9 +640,7 @@ static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) buf_win_common(argvars, rettv, true); } -/* - * Get buffer by number or pattern. - */ +/// Get buffer by number or pattern. buf_T *tv_get_buf(typval_T *tv, int curtab_only) { char_u *name = tv->vval.v_string; @@ -819,9 +708,7 @@ buf_T *get_buf_arg(typval_T *arg) return buf; } -/* - * "byte2line(byte)" function - */ +/// "byte2line(byte)" function static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) { long boff = tv_get_number(&argvars[0]) - 1; @@ -856,17 +743,13 @@ static void byteidx(typval_T *argvars, typval_T *rettv, int comp) rettv->vval.v_number = (varnumber_T)(t - str); } -/* - * "byteidx()" function - */ +/// "byteidx()" function static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { byteidx(argvars, rettv, FALSE); } -/* - * "byteidxcomp()" function - */ +/// "byteidxcomp()" function static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) { byteidx(argvars, rettv, TRUE); @@ -893,6 +776,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) partial = argvars[0].vval.v_partial; func = partial_name(partial); } else if (nlua_is_table_from_lua(&argvars[0])) { + // TODO(tjdevries): UnifiedCallback func = nlua_register_table_as_callable(&argvars[0]); owned = true; } else { @@ -917,15 +801,13 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "changenr()" function - */ +/// "changenr()" function static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = curbuf->b_u_seq_cur; } -// "chanclose(id[, stream])" function +/// "chanclose(id[, stream])" function static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -964,7 +846,7 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "chansend(id, data)" function +/// "chansend(id, data)" function static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -1005,9 +887,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "char2nr(string)" function - */ +/// "char2nr(string)" function static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { @@ -1019,7 +899,51 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = utf_ptr2char((const char_u *)tv_get_string(&argvars[0])); } -// "charidx()" function +/// Get the current cursor column and store it in 'rettv'. +/// +/// @return the character index of the column if 'charcol' is true, +/// otherwise the byte index of the column. +static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) +{ + colnr_T col = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], false, &fnum, charcol); + if (fp != NULL && fnum == curbuf->b_fnum) { + if (fp->col == MAXCOL) { + // '> can be MAXCOL, get the length of the line then + if (fp->lnum <= curbuf->b_ml.ml_line_count) { + col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; + } else { + col = MAXCOL; + } + } else { + col = fp->col + 1; + // col(".") when the cursor is on the NUL at the end of the line + // because of "coladd" can be seen as an extra column. + if (virtual_active() && fp == &curwin->w_cursor) { + char_u *p = get_cursor_pos_ptr(); + if (curwin->w_cursor.coladd >= + (colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) { + int l; + if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) { + col += l; + } + } + } + } + } + rettv->vval.v_number = col; +} + +/// "charcol()" function +static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_col(argvars, rettv, true); +} + +/// "charidx()" function static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; @@ -1065,7 +989,7 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = len > 0 ? len - 1 : 0; } -// "chdir(dir)" function +/// "chdir(dir)" function static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *cwd; @@ -1082,15 +1006,13 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Return the current directory cwd = xmalloc(MAXPATHL); - if (cwd != NULL) { - if (os_dirname(cwd, MAXPATHL) != FAIL) { + if (os_dirname(cwd, MAXPATHL) != FAIL) { #ifdef BACKSLASH_IN_FILENAME - slash_adjust(cwd); + slash_adjust(cwd); #endif - rettv->vval.v_string = vim_strsave(cwd); - } - xfree(cwd); + rettv->vval.v_string = vim_strsave(cwd); } + xfree(cwd); if (curwin->w_localdir != NULL) { scope = kCdScopeWindow; @@ -1104,9 +1026,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "cindent(lnum)" function - */ +/// "cindent(lnum)" function static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T pos; @@ -1123,7 +1043,7 @@ static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -static win_T *get_optional_window(typval_T *argvars, int idx) +win_T *get_optional_window(typval_T *argvars, int idx) { win_T *win = curwin; @@ -1137,62 +1057,13 @@ static win_T *get_optional_window(typval_T *argvars, int idx) return win; } -/* - * "clearmatches()" function - */ -static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *win = get_optional_window(argvars, 0); - - if (win != NULL) { - clear_matches(win); - } -} - -/* - * "col(string)" function - */ +/// "col(string)" function static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - colnr_T col = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fnum == curbuf->b_fnum) { - if (fp->col == MAXCOL) { - // '> can be MAXCOL, get the length of the line then - if (fp->lnum <= curbuf->b_ml.ml_line_count) { - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; - } else { - col = MAXCOL; - } - } else { - col = fp->col + 1; - // col(".") when the cursor is on the NUL at the end of the line - // because of "coladd" can be seen as an extra column. - if (virtual_active() && fp == &curwin->w_cursor) { - char_u *p = get_cursor_pos_ptr(); - - if (curwin->w_cursor.coladd - >= (colnr_T)win_chartabsize(curwin, p, - (curwin->w_virtcol - - curwin->w_cursor.coladd))) { - int l; - - if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) { - col += l; - } - } - } - } - } - rettv->vval.v_number = col; + get_col(argvars, rettv, false); } -/* - * "complete()" function - */ +/// "complete()" function static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if ((State & INSERT) == 0) { @@ -1219,17 +1090,13 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) set_completion(startcol - 1, argvars[1].vval.v_list); } -/* - * "complete_add()" function - */ +/// "complete_add()" function static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, false); } -/* - * "complete_check()" function - */ +/// "complete_check()" function static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int saved = RedrawingDisabled; @@ -1240,7 +1107,7 @@ static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) RedrawingDisabled = saved; } -// "complete_info()" function +/// "complete_info()" function static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); @@ -1257,9 +1124,7 @@ static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_complete_info(what_list, rettv->vval.v_dict); } -/* - * "confirm(message, buttons[, default [, type]])" function - */ +/// "confirm(message, buttons[, default [, type]])" function static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf[NUMBUFLEN]; @@ -1314,17 +1179,13 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "copy()" function - */ +/// "copy()" function static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) { var_item_copy(NULL, &argvars[0], rettv, false, 0); } -/* - * "count()" function - */ +/// "count()" function static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) { long n = 0; @@ -1415,11 +1276,9 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function - * - * Checks the existence of a cscope connection. - */ +/// "cscope_connection([{num} , {dbpath} [, {prepend}]])" function +/// +/// Checks the existence of a cscope connection. static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int num = 0; @@ -1550,24 +1409,21 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = ctx_size(); } -/// "cursor(lnum, col)" function, or -/// "cursor(list)" -/// -/// Moves the cursor to the specified line and column. -/// -/// @returns 0 when the position could be set, -1 otherwise. -static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Set the cursor position. +/// If 'charcol' is true, then use the column number as a character offset. +/// Otherwise use the column number as a byte offset. +static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) { long line, col; long coladd = 0; bool set_curswant = true; rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) { + if (argvars[0].v_type == VAR_LIST) { pos_T pos; colnr_T curswant = -1; - if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) { + if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) { emsg(_(e_invarg)); return; } @@ -1579,16 +1435,22 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) curwin->w_curswant = curswant - 1; set_curswant = false; } - } else { + } else if ((argvars[0].v_type == VAR_NUMBER || argvars[0].v_type == VAR_STRING) + && (argvars[1].v_type == VAR_NUMBER || argvars[1].v_type == VAR_STRING)) { line = tv_get_lnum(argvars); col = (long)tv_get_number_chk(&argvars[1], NULL); + if (charcol) { + col = buf_charidx_to_byteidx(curbuf, line, col) + 1; + } if (argvars[2].v_type != VAR_UNKNOWN) { coladd = (long)tv_get_number_chk(&argvars[2], NULL); } + } else { + emsg(_(e_invarg)); + return; } - if (line < 0 || col < 0 - || coladd < 0) { - return; // type error; errmsg already given + if (line < 0 || col < 0 || coladd < 0) { + return; // type error; errmsg already given } if (line > 0) { curwin->w_cursor.lnum = line; @@ -1607,7 +1469,18 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; } -// "debugbreak()" function +/// "cursor(lnum, col)" function, or +/// "cursor(list)" +/// +/// Moves the cursor to the specified line and column. +/// +/// @return 0 when the position could be set, -1 otherwise. +static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_cursorpos(argvars, rettv, false); +} + +/// "debugbreak()" function static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int pid; @@ -1631,7 +1504,7 @@ static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "deepcopy()" function +/// "deepcopy()" function static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int noref = 0; @@ -1648,7 +1521,7 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "delete()" function +/// "delete()" function static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; @@ -1684,7 +1557,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// dictwatcheradd(dict, key, funcref) function +/// dictwatcheradd(dict, key, funcref) function static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure()) { @@ -1722,7 +1595,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) callback); } -// dictwatcherdel(dict, key, funcref) function +/// dictwatcherdel(dict, key, funcref) function static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure()) { @@ -1831,25 +1704,19 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "did_filetype()" function - */ +/// "did_filetype()" function static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = did_filetype; } -/* - * "diff_filler()" function - */ +/// "diff_filler()" function static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars))); } -/* - * "diff_hlID()" function - */ +/// "diff_hlID()" function static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = tv_get_lnum(argvars); @@ -1901,9 +1768,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1); } -/* - * "empty({expr})" function - */ +/// "empty({expr})" function static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool n = true; @@ -2000,9 +1865,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) os_free_fullenv(env); } -/* - * "escape({string}, {chars})" function - */ +/// "escape({string}, {chars})" function static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf[NUMBUFLEN]; @@ -2026,9 +1889,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * "eval()" function - */ +/// "eval()" function static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *s = tv_get_string_chk(&argvars[0]); @@ -2049,17 +1910,13 @@ static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "eventhandler()" function - */ +/// "eventhandler()" function static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = vgetc_busy; } -/* - * "executable()" function - */ +/// "executable()" function static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (tv_check_for_string(&argvars[0]) == FAIL) { @@ -2167,36 +2024,24 @@ static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr, int capture_ga = save_capture_ga; } -// "execute(command)" function +/// "execute(command)" function static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { execute_common(argvars, rettv, fptr, 0); } -// "win_execute(win_id, command)" function +/// "win_execute(win_id, command)" function static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tabpage_T *tp; - win_T *wp = win_id2wp_tp(argvars, &tp); - win_T *save_curwin; - tabpage_T *save_curtab; // Return an empty string if something fails. rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; + int id = tv_get_number(argvars); + tabpage_T *tp; + win_T *wp = win_id2wp_tp(id, &tp); if (wp != NULL && tp != NULL) { - pos_T curpos = wp->w_cursor; - if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) == - OK) { - check_cursor(); - execute_common(argvars, rettv, fptr, 1); - } - restore_win_noblock(save_curwin, save_curtab, true); - - // Update the status line if the cursor moved. - if (win_valid(wp) && !equalpos(curpos, wp->w_cursor)) { - wp->w_redr_status = true; - } + WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, fptr, 1)); } } @@ -2215,9 +2060,7 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)path; } -/* - * "exists()" function - */ +/// "exists()" function static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n = false; @@ -2257,9 +2100,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "expand()" function - */ +/// "expand()" function static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) { size_t len; @@ -2344,8 +2185,8 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); } -// "expandcmd()" function -// Expand all the special characters in a command string. +/// "expandcmd()" function +/// Expand all the special characters in a command string. static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char *errormsg = NULL; @@ -2405,10 +2246,8 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "extend(list, list [, idx])" function - * "extend(dict, dict [, action])" function - */ +/// "extend(list, list [, idx])" function +/// "extend(dict, dict [, action])" function static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const arg_errmsg = N_("extend() argument"); @@ -2485,9 +2324,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "feedkeys()" function - */ +/// "feedkeys()" function static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) { // This is not allowed in the sandbox. If the commands would still be @@ -2516,10 +2353,9 @@ static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr) (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p)); } -/* - * Return 0 for not writable, 1 for writable file, 2 for a dir which we have - * rights to write into. - */ +/// @return 0 for not writable +/// 1 for writable file +/// 2 for a dir which we have rights to write into. static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *filename = tv_get_string(&argvars[0]); @@ -2586,33 +2422,25 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } -/* - * "filter()" function - */ +/// "filter()" function static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr) { filter_map(argvars, rettv, FALSE); } -/* - * "finddir({fname}[, {path}[, {count}]])" function - */ +/// "finddir({fname}[, {path}[, {count}]])" function static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { findfilendir(argvars, rettv, FINDFILE_DIR); } -/* - * "findfile({fname}[, {path}[, {count}]])" function - */ +/// "findfile({fname}[, {path}[, {count}]])" function static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { findfilendir(argvars, rettv, FINDFILE_FILE); } -/* - * "float2nr({float})" function - */ +/// "float2nr({float})" function static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T f; @@ -2628,9 +2456,7 @@ static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "fmod()" function - */ +/// "fmod()" function static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T fx; @@ -2644,18 +2470,14 @@ static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "fnameescape({string})" function - */ +/// "fnameescape({string})" function static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_string = (char_u *)vim_strsave_fnameescape(tv_get_string(&argvars[0]), false); rettv->v_type = VAR_STRING; } -/* - * "fnamemodify({fname}, {mods})" function - */ +/// "fnamemodify({fname}, {mods})" function static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *fbuf = NULL; @@ -2684,9 +2506,7 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -/* - * "foldclosed()" function - */ +/// "foldclosed()" function static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) { const linenr_T lnum = tv_get_lnum(argvars); @@ -2705,25 +2525,19 @@ static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) rettv->vval.v_number = -1; } -/* - * "foldclosed()" function - */ +/// "foldclosed()" function static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) { foldclosed_both(argvars, rettv, FALSE); } -/* - * "foldclosedend()" function - */ +/// "foldclosedend()" function static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { foldclosed_both(argvars, rettv, TRUE); } -/* - * "foldlevel()" function - */ +/// "foldlevel()" function static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const linenr_T lnum = tv_get_lnum(argvars); @@ -2732,9 +2546,7 @@ static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "foldtext()" function - */ +/// "foldtext()" function static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T foldstart; @@ -2787,9 +2599,7 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "foldtextresult(lnum)" function - */ +/// "foldtextresult(lnum)" function static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *text; @@ -2820,9 +2630,7 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) entered = false; } -/* - * "foreground()" function - */ +/// "foreground()" function static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) { } @@ -2849,9 +2657,7 @@ static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "get()" function - */ +/// "get()" function static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) { listitem_T *li; @@ -3011,12 +2817,12 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * Get line or list of lines from buffer "buf" into "rettv". - * Return a range (from start to end) of lines in rettv from the specified - * buffer. - * If 'retlist' is TRUE, then the lines are returned as a Vim List. - */ +/// Get line or list of lines from buffer "buf" into "rettv". +/// +/// @param retlist if TRUE, then the lines are returned as a Vim List. +/// +/// @return range (from start to end) of lines in rettv from the specified +/// buffer. static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) { rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); @@ -3049,9 +2855,7 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli } } -/* - * "getbufline()" function - */ +/// "getbufline()" function static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); @@ -3064,9 +2868,7 @@ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_buffer_lines(buf, lnum, end, true, rettv); } -/* - * "getbufvar()" function - */ +/// "getbufvar()" function static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool done = false; @@ -3126,7 +2928,7 @@ f_getbufvar_end: } } -// "getchangelist()" function +/// "getchangelist()" function static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, 2); @@ -3166,7 +2968,7 @@ static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "getchar()" and "getcharstr()" functions +/// "getchar()" and "getcharstr()" functions static void getchar_common(typval_T *argvars, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { @@ -3182,7 +2984,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) if (argvars[0].v_type == VAR_UNKNOWN) { // getchar(): blocking wait. // TODO(bfredl): deduplicate shared logic with state_enter ? - if (!(char_avail() || using_script() || input_available())) { + if (!char_avail()) { (void)os_inchar(NULL, 0, -1, 0, main_loop.events); if (!multiqueue_empty(main_loop.events)) { state_handle_k_event(); @@ -3218,7 +3020,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) set_vim_var_nr(VV_MOUSE_COL, 0); rettv->vval.v_number = n; - if (IS_SPECIAL(n) || mod_mask != 0) { + if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) { char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 int i = 0; @@ -3268,13 +3070,13 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) } } -// "getchar()" function +/// "getchar()" function static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { getchar_common(argvars, rettv); } -// "getcharstr()" function +/// "getcharstr()" function static void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { getchar_common(argvars, rettv); @@ -3294,17 +3096,74 @@ static void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getcharmod()" function - */ +/// "getcharmod()" function static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = mod_mask; } -/* - * "getcharsearch()" function - */ +static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol) +{ + pos_T *fp = NULL; + pos_T pos; + win_T *wp = curwin; + int fnum = -1; + + if (getcurpos) { + if (argvars[0].v_type != VAR_UNKNOWN) { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) { + fp = &wp->w_cursor; + } + } else { + fp = &curwin->w_cursor; + } + if (fp != NULL && charcol) { + pos = *fp; + pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col); + fp = &pos; + } + } else { + fp = var2fpos(&argvars[0], true, &fnum, charcol); + } + + list_T *const l = tv_list_alloc_ret(rettv, 4 + getcurpos); + tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); + tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum : (varnumber_T)0)); + tv_list_append_number(l, ((fp != NULL) + ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) + : (varnumber_T)0)); + tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); + if (getcurpos) { + const int save_set_curswant = curwin->w_set_curswant; + const colnr_T save_curswant = curwin->w_curswant; + const colnr_T save_virtcol = curwin->w_virtcol; + + if (wp == curwin) { + update_curswant(); + } + tv_list_append_number(l, (wp == NULL) ? 0 : ((wp->w_curswant == MAXCOL) + ? (varnumber_T)MAXCOL + : (varnumber_T)wp->w_curswant + 1)); + + // Do not change "curswant", as it is unexpected that a get + // function has a side effect. + if (wp == curwin && save_set_curswant) { + curwin->w_set_curswant = save_set_curswant; + curwin->w_curswant = save_curswant; + curwin->w_virtcol = save_virtcol; + curwin->w_valid &= ~VALID_VIRTCOL; + } + } +} + +/// "getcharpos()" function +static void f_getcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getpos_both(argvars, rettv, false, true); +} + +/// "getcharsearch()" function static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); @@ -3316,26 +3175,20 @@ static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); } -/* - * "getcmdline()" function - */ +/// "getcmdline()" function static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_cmdline_str(); } -/* - * "getcmdpos()" function - */ +/// "getcmdpos()" function static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = get_cmdline_pos() + 1; } -/* - * "getcmdtype()" function - */ +/// "getcmdtype()" function static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -3343,9 +3196,7 @@ static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string[0] = get_cmdline_type(); } -/* - * "getcmdwintype()" function - */ +/// "getcmdwintype()" function static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -3354,7 +3205,7 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string[0] = cmdwin_type; } -// "getcompletion()" function +/// "getcompletion()" function static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *pat; @@ -3386,15 +3237,17 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_(e_invarg)); return; } + const char *pattern = tv_get_string(&argvars[0]); if (strcmp(type, "cmdline") == 0) { - set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); + set_one_cmd_context(&xpc, pattern); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_col = STRLEN(pattern); goto theend; } ExpandInit(&xpc); - xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); + xpc.xp_pattern = (char_u *)pattern; xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); xpc.xp_context = cmdcomplete_str_to_type(type); if (xpc.xp_context == EXPAND_NOTHING) { @@ -3485,11 +3338,6 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - // Find the tabpage by number if (scope_number[kCdScopeTabpage] > 0) { tp = find_tabpage(scope_number[kCdScopeTabpage]); @@ -3535,12 +3383,13 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) case kCdScopeGlobal: if (globaldir) { // `globaldir` is not always set. from = globaldir; - } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD. + break; + } + FALLTHROUGH; // In global directory, just need to get OS CWD. + case kCdScopeInvalid: // If called without any arguments, get OS CWD. + if (os_dirname(cwd, MAXPATHL) == FAIL) { from = (char_u *)""; // Return empty string on failure. } - break; - case kCdScopeInvalid: // We should never get here - abort(); } if (from) { @@ -3555,18 +3404,14 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(cwd); } -/* - * "getfontname()" function - */ +/// "getfontname()" function static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; } -/* - * "getfperm({fname})" function - */ +/// "getfperm({fname})" function static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char *perm = NULL; @@ -3586,9 +3431,7 @@ static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)perm; } -/* - * "getfsize({fname})" function - */ +/// "getfsize({fname})" function static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -3613,9 +3456,7 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getftime({fname})" function - */ +/// "getftime({fname})" function static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -3628,9 +3469,7 @@ static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getftype({fname})" function - */ +/// "getftype({fname})" function static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *type = NULL; @@ -3664,7 +3503,7 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = type; } -// "getjumplist()" function +/// "getjumplist()" function static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); @@ -3695,9 +3534,7 @@ static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getline(lnum, [end])" function - */ +/// "getline(lnum, [end])" function static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T end; @@ -3741,65 +3578,8 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_buf_local_marks(buf, rettv->vval.v_list); } -/* - * "getmatches()" function - */ -static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - matchitem_T *cur; - int i; - win_T *win = get_optional_window(argvars, 0); - - if (win == NULL) { - return; - } - - tv_list_alloc_ret(rettv, kListLenMayKnow); - cur = win->w_match_head; - while (cur != NULL) { - dict_T *dict = tv_dict_alloc(); - if (cur->match.regprog == NULL) { - // match added with matchaddpos() - for (i = 0; i < MAXPOSMATCH; i++) { - llpos_T *llpos; - char buf[30]; // use 30 to avoid compiler warning - - llpos = &cur->pos.pos[i]; - if (llpos->lnum == 0) { - break; - } - list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); - tv_list_append_number(l, (varnumber_T)llpos->lnum); - if (llpos->col > 0) { - tv_list_append_number(l, (varnumber_T)llpos->col); - tv_list_append_number(l, (varnumber_T)llpos->len); - } - int len = snprintf(buf, sizeof(buf), "pos%d", i + 1); - assert((size_t)len < sizeof(buf)); - tv_dict_add_list(dict, buf, (size_t)len, l); - } - } else { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern); - } - tv_dict_add_str(dict, S_LEN("group"), - (const char *)syn_id2name(cur->hlg_id)); - tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority); - tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id); - - if (cur->conceal_char) { - char buf[MB_MAXBYTES + 1]; - - buf[utf_char2bytes(cur->conceal_char, (char_u *)buf)] = NUL; - tv_dict_add_str(dict, S_LEN("conceal"), buf); - } - - tv_list_append_dict(rettv->vval.v_list, dict); - cur = cur->next; - } -} - -// "getmousepos()" function -void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// "getmousepos()" function +static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *d; win_T *wp; @@ -3809,7 +3589,7 @@ void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) varnumber_T winid = 0; varnumber_T winrow = 0; varnumber_T wincol = 0; - linenr_T line = 0; + linenr_T lnum = 0; varnumber_T column = 0; tv_dict_alloc_ret(rettv); @@ -3820,7 +3600,7 @@ void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) wp = mouse_find_win(&grid, &row, &col); if (wp != NULL) { - int height = wp->w_height + wp->w_status_height; + int height = wp->w_height + wp->w_hsep_height + wp->w_status_height; // The height is adjusted by 1 when there is a bottom border. This is not // necessary for a top border since `row` starts at -1 in that case. if (row < height + wp->w_border_adj[2]) { @@ -3828,18 +3608,8 @@ void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) winrow = row + 1 + wp->w_border_adj[0]; // Adjust by 1 for top border wincol = col + 1 + wp->w_border_adj[3]; // Adjust by 1 for left border if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) { - char_u *p; - int count; - - mouse_comp_pos(wp, &row, &col, &line); - - // limit to text length plus one - p = ml_get_buf(wp->w_buffer, line, false); - count = (int)STRLEN(p); - if (col > count) { - col = count; - } - + (void)mouse_comp_pos(wp, &row, &col, &lnum); + col = vcol2col(wp, lnum, col); column = col + 1; } } @@ -3847,73 +3617,31 @@ void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(d, S_LEN("winid"), winid); tv_dict_add_nr(d, S_LEN("winrow"), winrow); tv_dict_add_nr(d, S_LEN("wincol"), wincol); - tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)line); + tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum); tv_dict_add_nr(d, S_LEN("column"), column); } -/* - * "getpid()" function - */ +/// "getpid()" function static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = os_get_pid(); } -static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) +/// "getcurpos(string)" function +static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - pos_T *fp; - int fnum = -1; - - if (getcurpos) { - fp = &curwin->w_cursor; - } else { - fp = var2fpos(&argvars[0], true, &fnum); - } - - list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); - tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); - tv_list_append_number(l, ((fp != NULL) - ? (varnumber_T)fp->lnum - : (varnumber_T)0)); - tv_list_append_number(l, ((fp != NULL) - ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) - : (varnumber_T)0)); - tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); - if (getcurpos) { - const int save_set_curswant = curwin->w_set_curswant; - const colnr_T save_curswant = curwin->w_curswant; - const colnr_T save_virtcol = curwin->w_virtcol; - - update_curswant(); - tv_list_append_number(l, (curwin->w_curswant == MAXCOL - ? (varnumber_T)MAXCOL - : (varnumber_T)curwin->w_curswant + 1)); - - // Do not change "curswant", as it is unexpected that a get - // function has a side effect. - if (save_set_curswant) { - curwin->w_set_curswant = save_set_curswant; - curwin->w_curswant = save_curswant; - curwin->w_virtcol = save_virtcol; - curwin->w_valid &= ~VALID_VIRTCOL; - } - } + getpos_both(argvars, rettv, true, false); } -/* - * "getcurpos(string)" function - */ -static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - getpos_both(argvars, rettv, true); + getpos_both(argvars, rettv, true, true); } -/* - * "getpos(string)" function - */ +/// "getpos(string)" function static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - getpos_both(argvars, rettv, false); + getpos_both(argvars, rettv, false, false); } /// "getqflist()" functions @@ -3922,34 +3650,46 @@ static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_qf_loc_list(true, NULL, &argvars[0], rettv); } -/// "getreg()" function -static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Common between getreg(), getreginfo() and getregtype(): get the register +/// name from the first argument. +/// Returns zero on error. +static int getreg_get_regname(typval_T *argvars) { - const char *strregname; - int arg2 = false; - bool return_list = false; - bool error = false; + const char_u *strregname; if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - error = strregname == NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - arg2 = tv_get_number_chk(&argvars[1], &error); - if (!error && argvars[2].v_type != VAR_UNKNOWN) { - return_list = tv_get_number_chk(&argvars[2], &error); - } + strregname = (const char_u *)tv_get_string_chk(&argvars[0]); + if (strregname == NULL) { // type error; errmsg already given + return 0; } } else { - strregname = _(get_vim_var_str(VV_REG)); + // Default to v:register + strregname = get_vim_var_str(VV_REG); } - if (error) { + return *strregname == 0 ? '"' : *strregname; +} + +/// "getreg()" function +static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int arg2 = false; + bool return_list = false; + + int regname = getreg_get_regname(argvars); + if (regname == 0) { return; } - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); - if (regname == 0) { - regname = '"'; + if (argvars[0].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_UNKNOWN) { + bool error = false; + arg2 = (int)tv_get_number_chk(&argvars[1], &error); + if (!error && argvars[2].v_type != VAR_UNKNOWN) { + return_list = (bool)tv_get_number_chk(&argvars[2], &error); + } + if (error) { + return; + } } if (return_list) { @@ -3966,28 +3706,16 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getregtype()" function - */ +/// "getregtype()" function static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *strregname; - - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - if (strregname == NULL) { // Type error; errmsg already given. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - return; - } - } else { - // Default to v:register. - strregname = _(get_vim_var_str(VV_REG)); - } + // on error return an empty string + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); + int regname = getreg_get_regname(argvars); if (regname == 0) { - regname = '"'; + return; } colnr_T reglen = 0; @@ -3995,7 +3723,6 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) MotionType reg_type = get_reg_type(regname, ®len); format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf)); - rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)xstrdup(buf); } @@ -4031,13 +3758,9 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "gettabvar()" function - */ +/// "gettabvar()" function static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *oldcurwin; - tabpage_T *oldtabpage; bool done = false; rettv->v_type = VAR_STRING; @@ -4051,7 +3774,8 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) win_T *const window = tp == curtab || tp->tp_firstwin == NULL ? firstwin : tp->tp_firstwin; - if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) { + switchwin_T switchwin; + if (switch_win(&switchwin, window, tp, true) == OK) { // look up the variable // Let gettabvar({nr}, "") return the "t:" dictionary. const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', @@ -4064,7 +3788,7 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); + restore_win(&switchwin, true); } if (!done && argvars[2].v_type != VAR_UNKNOWN) { @@ -4072,15 +3796,13 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "gettabwinvar()" function - */ +/// "gettabwinvar()" function static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { getwinvar(argvars, rettv, 1); } -// "gettagstack()" function +/// "gettagstack()" function static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wp = curwin; // default is current window @@ -4105,7 +3827,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_UNKNOWN) { - wparg = win_id2wp(argvars); + wparg = win_id2wp(tv_get_number(&argvars[0])); if (wparg == NULL) { return; } @@ -4132,12 +3854,12 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// Dummy timer callback. Used by f_wait(). +/// Dummy timer callback. Used by f_wait(). static void dummy_timer_due_cb(TimeWatcher *tw, void *data) { } -// Dummy timer close callback. Used by f_wait(). +/// Dummy timer close callback. Used by f_wait(). static void dummy_timer_close_cb(TimeWatcher *tw, void *data) { xfree(tw); @@ -4200,7 +3922,7 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) time_watcher_close(tw, dummy_timer_close_cb); } -// "win_screenpos()" function +/// "win_screenpos()" function static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, 2); @@ -4209,16 +3931,14 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); } -// -// Move the window wp into a new split of targetwin in a given direction -// +/// Move the window wp into a new split of targetwin in a given direction static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags) { int dir; int height = wp->w_height; win_T *oldwin = curwin; - if (wp == targetwin) { + if (wp == targetwin || wp == aucmd_win) { return; } @@ -4249,7 +3969,7 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags } } -// "win_splitmove()" function +/// "win_splitmove()" function static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wp; @@ -4289,7 +4009,7 @@ static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) win_move_into_split(wp, targetwin, size, flags); } -// "getwinpos({timeout})" function +/// "getwinpos({timeout})" function static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, 2); @@ -4297,17 +4017,13 @@ static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, -1); } -/* - * "getwinposx()" function - */ +/// "getwinposx()" function static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; } -/* - * "getwinposy()" function - */ +/// "getwinposy()" function static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; @@ -4319,9 +4035,7 @@ static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) getwinvar(argvars, rettv, 0); } -/* - * "glob()" function - */ +/// "glob()" function static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int options = WILD_SILENT|WILD_USE_NL; @@ -4419,7 +4133,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "glob2regpat()" function +/// "glob2regpat()" function static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error @@ -4438,6 +4152,12 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #if defined(BSD) && !defined(__APPLE__) "bsd", #endif +#ifdef __linux__ + "linux", +#endif +#ifdef SUN_SYSTEM + "sun", +#endif #ifdef UNIX "unix", #endif @@ -4509,6 +4229,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "mouse", "multi_byte", "multi_lang", + "nanotime", "num64", "packages", "path_extra", @@ -4748,9 +4469,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "hasmapto()" function - */ +/// "hasmapto()" function static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *mode; @@ -4773,9 +4492,7 @@ static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "histadd()" function - */ +/// "histadd()" function static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { HistoryType histype; @@ -4798,9 +4515,7 @@ static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "histdel()" function - */ +/// "histdel()" function static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n; @@ -4823,9 +4538,7 @@ static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "histget()" function - */ +/// "histget()" function static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) { HistoryType type; @@ -4847,9 +4560,7 @@ static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * "histnr()" function - */ +/// "histnr()" function static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const history = tv_get_string_chk(&argvars[0]); @@ -4862,25 +4573,19 @@ static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = i; } -/* - * "highlightID(name)" function - */ +/// "highlightID(name)" function static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = syn_name2id(tv_get_string(&argvars[0])); } -/* - * "highlight_exists()" function - */ +/// "highlight_exists()" function static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = highlight_exists(tv_get_string(&argvars[0])); } -/* - * "hostname()" function - */ +/// "hostname()" function static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char hostname[256]; @@ -4890,9 +4595,7 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = vim_strsave((char_u *)hostname); } -/* - * iconv() function - */ +/// iconv() function static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) { vimconv_T vimconv; @@ -4920,9 +4623,7 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(to); } -/* - * "indent()" function - */ +/// "indent()" function static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const linenr_T lnum = tv_get_lnum(argvars); @@ -4933,9 +4634,7 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "index()" function - */ +/// "index()" function static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) { long idx = 0; @@ -5009,26 +4708,20 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) static bool inputsecret_flag = false; -/* - * "input()" function - * Also handles inputsecret() when inputsecret is set. - */ +/// "input()" function +/// Also handles inputsecret() when inputsecret is set. static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) { get_user_input(argvars, rettv, FALSE, inputsecret_flag); } -/* - * "inputdialog()" function - */ +/// "inputdialog()" function static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) { get_user_input(argvars, rettv, TRUE, inputsecret_flag); } -/* - * "inputlist()" function - */ +/// "inputlist()" function static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int selected; @@ -5094,9 +4787,7 @@ static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr) inputsecret_flag = false; } -/* - * "insert()" function - */ +/// "insert()" function static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) { list_T *l; @@ -5168,32 +4859,26 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "interrupt()" function +/// "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 - */ +/// "invert(expr)" function static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); } -/* - * "isdirectory()" function - */ +/// "isdirectory()" function static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0])); } -/* - * "islocked()" function - */ +/// "islocked()" function static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) { lval_T lv; @@ -5236,7 +4921,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) clear_lval(&lv); } -// "isinf()" function +/// "isinf()" function static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type == VAR_FLOAT @@ -5245,7 +4930,7 @@ static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "isnan()" function +/// "isnan()" function static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT @@ -5263,15 +4948,13 @@ static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) dummy_ap, argvars); } -/* - * "items(dict)" function - */ +/// "items(dict)" function static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_list(argvars, rettv, 2); } -// "jobpid(id)" function +/// "jobpid(id)" function static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5295,7 +4978,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = proc->pid; } -// "jobresize(job, width, height)" function +/// "jobresize(job, width, height)" function static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5442,7 +5125,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en return env; } -// "jobstart()" function +/// "jobstart()" function static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5563,7 +5246,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "jobstop()" function +/// "jobstop()" function static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5596,7 +5279,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "jobwait(ids[, timeout])" function +/// "jobwait(ids[, timeout])" function static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5695,9 +5378,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_list = rv; } -/* - * "join()" function - */ +/// "join()" function static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type != VAR_LIST) { @@ -5762,17 +5443,13 @@ static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)encode_tv2json(&argvars[0], NULL); } -/* - * "keys()" function - */ +/// "keys()" function static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_list(argvars, rettv, 0); } -/* - * "last_buffer_nr()" function. - */ +/// "last_buffer_nr()" function. static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n = 0; @@ -5786,9 +5463,7 @@ static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "len()" function - */ +/// "len()" function static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) { switch (argvars[0].v_type) { @@ -5861,23 +5536,19 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) } } -/* - * "libcall()" function - */ +/// "libcall()" function static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr) { libcall_common(argvars, rettv, VAR_STRING); } -/* - * "libcallnr()" function - */ +/// "libcallnr()" function static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { libcall_common(argvars, rettv, VAR_NUMBER); } -// "line(string, [winid])" function +/// "line(string, [winid])" function static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = 0; @@ -5885,23 +5556,21 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) int fnum; if (argvars[1].v_type != VAR_UNKNOWN) { - tabpage_T *tp; - win_T *save_curwin; - tabpage_T *save_curtab; - // use window specified in the second argument - win_T *wp = win_id2wp_tp(&argvars[1], &tp); + int id = (int)tv_get_number(&argvars[1]); + tabpage_T *tp; + win_T *wp = win_id2wp_tp(id, &tp); if (wp != NULL && tp != NULL) { - if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) - == OK) { + switchwin_T switchwin; + if (switch_win_noblock(&switchwin, wp, tp, true) == OK) { check_cursor(); - fp = var2fpos(&argvars[0], true, &fnum); + fp = var2fpos(&argvars[0], true, &fnum, false); } - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); } } else { // use current window - fp = var2fpos(&argvars[0], true, &fnum); + fp = var2fpos(&argvars[0], true, &fnum, false); } if (fp != NULL) { @@ -5910,9 +5579,7 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = lnum; } -/* - * "line2byte(lnum)" function - */ +/// "line2byte(lnum)" function static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const linenr_T lnum = tv_get_lnum(argvars); @@ -5926,9 +5593,7 @@ static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "lispindent(lnum)" function - */ +/// "lispindent(lnum)" function static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const pos_T pos = curwin->w_cursor; @@ -5942,7 +5607,7 @@ static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "list2str()" function +/// "list2str()" function static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) { garray_T ga; @@ -5971,9 +5636,7 @@ static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ga.ga_data; } -/* - * "localtime()" function - */ +/// "localtime()" function static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = (varnumber_T)time(NULL); @@ -5984,6 +5647,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) { char_u *keys_buf = NULL; char_u *rhs; + LuaRef rhs_lua; int mode; int abbr = FALSE; int get_dict = FALSE; @@ -6020,7 +5684,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true, CPO_TO_CPO_FLAGS); - rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local); + rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); xfree(keys_buf); if (!get_dict) { @@ -6031,10 +5695,15 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) } else { rettv->vval.v_string = (char_u *)str2special_save((char *)rhs, false, false); } + } else if (rhs_lua != LUA_NOREF) { + size_t msglen = 100; + char *msg = (char *)xmalloc(msglen); + snprintf(msg, msglen, "<Lua function %d>", mp->m_luaref); + rettv->vval.v_string = (char_u *)msg; } } else { tv_dict_alloc_ret(rettv); - if (rhs != NULL) { + if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) { // Return a dictionary. mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true); } @@ -6053,25 +5722,19 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv); } -/* - * "map()" function - */ +/// "map()" function static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) { filter_map(argvars, rettv, TRUE); } -/* - * "maparg()" function - */ +/// "maparg()" function static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { get_maparg(argvars, rettv, TRUE); } -/* - * "mapcheck()" function - */ +/// "mapcheck()" function static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) { get_maparg(argvars, rettv, FALSE); @@ -6293,166 +5956,25 @@ theend: p_cpo = save_cpo; } -/* - * "match()" function - */ +/// "match()" function static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) { find_some_match(argvars, rettv, kSomeMatch); } -/* - * "matchadd()" function - */ -static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char grpbuf[NUMBUFLEN]; - char patbuf[NUMBUFLEN]; - // group - const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf); - // pattern - const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); - // default priority - int prio = 10; - int id = -1; - bool error = false; - const char *conceal_char = NULL; - win_T *win = curwin; - - rettv->vval.v_number = -1; - - if (grp == NULL || pat == NULL) { - return; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN - && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { - return; - } - } - } - if (error) { - return; - } - if (id >= 1 && id <= 3) { - semsg(_("E798: ID is reserved for \":match\": %" PRId64), (int64_t)id); - return; - } - - rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); -} - -static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - char buf[NUMBUFLEN]; - const char *const group = tv_get_string_buf_chk(&argvars[0], buf); - if (group == NULL) { - return; - } - - if (argvars[1].v_type != VAR_LIST) { - semsg(_(e_listarg), "matchaddpos()"); - return; - } - - list_T *l; - l = argvars[1].vval.v_list; - if (l == NULL) { - return; - } - - bool error = false; - int prio = 10; - int id = -1; - const char *conceal_char = NULL; - win_T *win = curwin; - - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN - && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { - return; - } - } - } - if (error == true) { - return; - } - - // id == 3 is ok because matchaddpos() is supposed to substitute :3match - if (id == 1 || id == 2) { - semsg(_("E798: ID is reserved for \"match\": %" PRId64), (int64_t)id); - return; - } - - rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); -} - -/* - * "matcharg()" function - */ -static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int id = tv_get_number(&argvars[0]); - - tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 - ? 2 - : 0)); - - if (id >= 1 && id <= 3) { - matchitem_T *const m = get_match(curwin, id); - - if (m != NULL) { - tv_list_append_string(rettv->vval.v_list, - (const char *)syn_id2name(m->hlg_id), -1); - tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); - } else { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } - } -} - -/* - * "matchdelete()" function - */ -static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *win = get_optional_window(argvars, 1); - if (win == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = match_delete(win, - (int)tv_get_number(&argvars[0]), true); - } -} - -/* - * "matchend()" function - */ +/// "matchend()" function static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { find_some_match(argvars, rettv, kSomeMatchEnd); } -/* - * "matchlist()" function - */ +/// "matchlist()" function static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { find_some_match(argvars, rettv, kSomeMatchList); } -/* - * "matchstr()" function - */ +/// "matchstr()" function static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { find_some_match(argvars, rettv, kSomeMatchStr); @@ -6513,25 +6035,19 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool rettv->vval.v_number = n; } -/* - * "max()" function - */ +/// "max()" function static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr) { max_min(argvars, rettv, TRUE); } -/* - * "min()" function - */ +/// "min()" function static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) { max_min(argvars, rettv, FALSE); } -/* - * "mkdir()" function - */ +/// "mkdir()" function static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int prot = 0755; // -V536 @@ -6579,15 +6095,17 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "mode()" function static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - char *mode = get_mode(); + char buf[MODE_MAX_LENGTH]; + + get_mode(buf); // Clear out the minor mode when the argument is not a non-zero number or // non-empty string. if (!non_zero_arg(&argvars[0])) { - mode[1] = NUL; + buf[1] = NUL; } - rettv->vval.v_string = (char_u *)mode; + rettv->vval.v_string = vim_strsave((char_u *)buf); rettv->v_type = VAR_STRING; } @@ -6746,9 +6264,7 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "nextnonblank()" function - */ +/// "nextnonblank()" function static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum; @@ -6765,9 +6281,7 @@ static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = lnum; } -/* - * "nr2char()" function - */ +/// "nr2char()" function static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { @@ -6798,31 +6312,36 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = xmemdupz(buf, (size_t)len); } -/* - * "or(expr, expr)" function - */ +/// "or(expr, expr)" function static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) | tv_get_number_chk(&argvars[1], NULL); } -/* - * "pathshorten()" function - */ +/// "pathshorten()" function static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) { + int trim_len = 1; + + if (argvars[1].v_type != VAR_UNKNOWN) { + trim_len = (int)tv_get_number(&argvars[1]); + if (trim_len < 1) { + trim_len = 1; + } + } + rettv->v_type = VAR_STRING; - const char *const s = tv_get_string_chk(&argvars[0]); - if (!s) { - return; + const char_u *p = (char_u *)tv_get_string_chk(&argvars[0]); + if (p == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = vim_strsave(p); + shorten_dir_len(rettv->vval.v_string, trim_len); } - rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s)); } -/* - * "pow()" function - */ +/// "pow()" function static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T fx; @@ -6836,9 +6355,7 @@ static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "prevnonblank()" function - */ +/// "prevnonblank()" function static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = tv_get_lnum(argvars); @@ -6852,9 +6369,7 @@ static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = lnum; } -/* - * "printf()" function - */ +/// "printf()" function static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -6877,7 +6392,7 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "prompt_setcallback({buffer}, {callback})" function +/// "prompt_setcallback({buffer}, {callback})" function static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -6901,7 +6416,7 @@ static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr buf->b_prompt_callback = prompt_callback; } -// "prompt_setinterrupt({buffer}, {callback})" function +/// "prompt_setinterrupt({buffer}, {callback})" function static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -6926,7 +6441,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fpt } /// "prompt_getprompt({buffer})" function -void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL { // return an empty string by default, e.g. it's not a prompt buffer @@ -6945,7 +6460,7 @@ void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = vim_strsave(buf_prompt_text(buf)); } -// "prompt_setprompt({buffer}, {text})" function +/// "prompt_setprompt({buffer}, {text})" function static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -6964,16 +6479,14 @@ static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) buf->b_prompt_text = vim_strsave(text); } -// "pum_getpos()" function +/// "pum_getpos()" function static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); pum_set_event_info(rettv->vval.v_dict); } -/* - * "pumvisible()" function - */ +/// "pumvisible()" function static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (pum_visible()) { @@ -6981,50 +6494,183 @@ static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "pyeval()" function - */ -static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// "py3eval()" and "pyxeval()" functions (always python3) +static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + script_host_eval("python3", argvars, rettv); +} + +static void init_srand(uint32_t *const x) + FUNC_ATTR_NONNULL_ALL +{ +#ifndef MSWIN + static int dev_urandom_state = NOTDONE; // FAIL or OK once tried + + if (dev_urandom_state != FAIL) { + const int fd = os_open("/dev/urandom", O_RDONLY, 0); + struct { + union { + uint32_t number; + char bytes[sizeof(uint32_t)]; + } contents; + } buf; + + // Attempt reading /dev/urandom. + if (fd == -1) { + dev_urandom_state = FAIL; + } else { + buf.contents.number = 0; + if (read(fd, buf.contents.bytes, sizeof(uint32_t)) != sizeof(uint32_t)) { + dev_urandom_state = FAIL; + } else { + dev_urandom_state = OK; + *x = buf.contents.number; + } + os_close(fd); + } + } + if (dev_urandom_state != OK) { + // Reading /dev/urandom doesn't work, fall back to time(). +#endif + // uncrustify:off + *x = time(NULL); +#ifndef MSWIN + } +#endif + // uncrustify:on +} + +static inline uint32_t splitmix32(uint32_t *const x) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { - script_host_eval("python", argvars, rettv); + uint32_t z = (*x += 0x9e3779b9); + z = (z ^ (z >> 16)) * 0x85ebca6b; + z = (z ^ (z >> 13)) * 0xc2b2ae35; + return z ^ (z >> 16); } -/* - * "py3eval()" function - */ -static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *const y, + uint32_t *const z, uint32_t *const w) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { - script_host_eval("python3", argvars, rettv); +#define ROTL(x, k) (((x) << (k)) | ((x) >> (32 - (k)))) + const uint32_t result = ROTL(*y * 5, 7) * 9; + const uint32_t t = *y << 9; + *z ^= *x; + *w ^= *y; + *y ^= *z; + *x ^= *w; + *z ^= t; + *w = ROTL(*w, 11); +#undef ROTL + return result; } -// "pyxeval()" function -static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// "rand()" function +static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - init_pyxversion(); - if (p_pyx == 2) { - f_pyeval(argvars, rettv, NULL); + uint32_t result; + + if (argvars[0].v_type == VAR_UNKNOWN) { + static uint32_t gx, gy, gz, gw; + static bool initialized = false; + + // When no argument is given use the global seed list. + if (!initialized) { + // Initialize the global seed list. + uint32_t x; + init_srand(&x); + + gx = splitmix32(&x); + gy = splitmix32(&x); + gz = splitmix32(&x); + gw = splitmix32(&x); + initialized = true; + } + + result = shuffle_xoshiro128starstar(&gx, &gy, &gz, &gw); + } else if (argvars[0].v_type == VAR_LIST) { + list_T *const l = argvars[0].vval.v_list; + if (tv_list_len(l) != 4) { + goto theend; + } + + typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0L)); + typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1L)); + typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2L)); + typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3L)); + if (tvx->v_type != VAR_NUMBER) { + goto theend; + } + if (tvy->v_type != VAR_NUMBER) { + goto theend; + } + if (tvz->v_type != VAR_NUMBER) { + goto theend; + } + if (tvw->v_type != VAR_NUMBER) { + goto theend; + } + uint32_t x = tvx->vval.v_number; + uint32_t y = tvy->vval.v_number; + uint32_t z = tvz->vval.v_number; + uint32_t w = tvw->vval.v_number; + + result = shuffle_xoshiro128starstar(&x, &y, &z, &w); + + tvx->vval.v_number = (varnumber_T)x; + tvy->vval.v_number = (varnumber_T)y; + tvz->vval.v_number = (varnumber_T)z; + tvw->vval.v_number = (varnumber_T)w; } else { - f_py3eval(argvars, rettv, NULL); + goto theend; } + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = (varnumber_T)result; + return; + +theend: + semsg(_(e_invarg2), tv_get_string(&argvars[0])); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; +} + +/// "srand()" function +static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + uint32_t x = 0; + + tv_list_alloc_ret(rettv, 4); + if (argvars[0].v_type == VAR_UNKNOWN) { + init_srand(&x); + } else { + bool error = false; + x = tv_get_number_chk(&argvars[0], &error); + if (error) { + return; + } + } + + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); } -/// /// "perleval()" function -/// static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr) { script_host_eval("perl", argvars, rettv); } -// "rubyeval()" function +/// "rubyeval()" function static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) { script_host_eval("ruby", argvars, rettv); } -/* - * "range()" function - */ +/// "range()" function static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) { varnumber_T start; @@ -7059,15 +6705,21 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// Evaluate "expr" for readdir(). -static varnumber_T readdir_checkitem(typval_T *expr, const char *name) +/// Evaluate "expr" (= "context") for readdir(). +static varnumber_T readdir_checkitem(void *context, const char *name) + FUNC_ATTR_NONNULL_ALL { + typval_T *expr = (typval_T *)context; typval_T save_val; typval_T rettv; typval_T argv[2]; varnumber_T retval = 0; bool error = false; + if (expr->v_type == VAR_UNKNOWN) { + return 1; + } + prepare_vimvar(VV_VAL, &save_val); set_vim_var_string(VV_VAL, name, -1); argv[0].v_type = VAR_STRING; @@ -7090,65 +6742,25 @@ theend: return retval; } -// "readdir()" function +/// "readdir()" function static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - typval_T *expr; - const char *path; - garray_T ga; - Directory dir; - tv_list_alloc_ret(rettv, kListLenUnknown); - path = tv_get_string(&argvars[0]); - expr = &argvars[1]; - ga_init(&ga, (int)sizeof(char *), 20); - - if (!os_scandir(&dir, path)) { - smsg(_(e_notopen), path); - } else { - for (;;) { - bool ignore; - - path = os_scandir_next(&dir); - if (path == NULL) { - break; - } - - ignore = (path[0] == '.' - && (path[1] == NUL || (path[1] == '.' && path[2] == NUL))); - if (!ignore && expr->v_type != VAR_UNKNOWN) { - varnumber_T r = readdir_checkitem(expr, path); - - if (r < 0) { - break; - } - if (r == 0) { - ignore = true; - } - } - - if (!ignore) { - ga_grow(&ga, 1); - ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path); - } - } - - os_closedir(&dir); - } - if (rettv->vval.v_list != NULL && ga.ga_len > 0) { - sort_strings((char_u **)ga.ga_data, ga.ga_len); + const char *path = tv_get_string(&argvars[0]); + typval_T *expr = &argvars[1]; + garray_T ga; + int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem); + if (ret == OK && ga.ga_len > 0) { for (int i = 0; i < ga.ga_len; i++) { - path = ((const char **)ga.ga_data)[i]; - tv_list_append_string(rettv->vval.v_list, path, -1); + const char *p = ((const char **)ga.ga_data)[i]; + tv_list_append_string(rettv->vval.v_list, p, -1); } } ga_clear_strings(&ga); } -/* - * "readfile()" function - */ +/// "readfile()" function static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool binary = false; @@ -7335,18 +6947,12 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "getreginfo()" function static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *strregname; - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - if (strregname == NULL) { - return; - } - } else { - strregname = (const char *)get_vim_var_str(VV_REG); + int regname = getreg_get_regname(argvars); + if (regname == 0) { + return; } - int regname = (strregname == NULL ? '"' : *strregname); - if (regname == 0 || regname == '@') { + if (regname == '@') { regname = '"'; } @@ -7388,18 +6994,23 @@ static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "reg_executing()" function +/// "reg_executing()" function static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr) { return_register(reg_executing, rettv); } -// "reg_recording()" function +/// "reg_recording()" function static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) { return_register(reg_recording, rettv); } +static void f_reg_recorded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + return_register(reg_recorded, rettv); +} + /// list2proftime - convert a List to proftime_T /// /// @param arg The input list, must be of type VAR_LIST and have @@ -7491,9 +7102,7 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "remove()" function - */ +/// "remove()" function static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) { list_T *l; @@ -7627,9 +7236,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "rename({from}, {to})" function - */ +/// "rename({from}, {to})" function static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure()) { @@ -7641,9 +7248,7 @@ static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "repeat()" function - */ +/// "repeat()" function static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) { varnumber_T n = tv_get_number(&argvars[1]); @@ -7680,9 +7285,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "resolve()" function - */ +/// "resolve()" function static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -7851,9 +7454,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) simplify_filename(rettv->vval.v_string); } -/* - * "reverse({list})" function - */ +/// "reverse({list})" function static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type == VAR_BLOB) { @@ -7878,6 +7479,102 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// "reduce(list, { accumulator, element -> value } [, initial])" function +static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { + emsg(_(e_listblobreq)); + return; + } + + const char_u *func_name; + partial_T *partial = NULL; + if (argvars[1].v_type == VAR_FUNC) { + func_name = argvars[1].vval.v_string; + } else if (argvars[1].v_type == VAR_PARTIAL) { + partial = argvars[1].vval.v_partial; + func_name = partial_name(partial); + } else { + func_name = (const char_u *)tv_get_string(&argvars[1]); + } + if (*func_name == NUL) { + return; // type error or empty name + } + + funcexe_T funcexe = FUNCEXE_INIT; + funcexe.evaluate = true; + funcexe.partial = partial; + + typval_T initial; + typval_T argv[3]; + if (argvars[0].v_type == VAR_LIST) { + list_T *const l = argvars[0].vval.v_list; + const listitem_T *li; + + if (argvars[2].v_type == VAR_UNKNOWN) { + if (tv_list_len(l) == 0) { + semsg(_(e_reduceempty), "List"); + return; + } + const listitem_T *const first = tv_list_first(l); + initial = *TV_LIST_ITEM_TV(first); + li = TV_LIST_ITEM_NEXT(l, first); + } else { + initial = argvars[2]; + li = tv_list_first(l); + } + + tv_copy(&initial, rettv); + + if (l != NULL) { + const VarLockStatus prev_locked = tv_list_locked(l); + const int called_emsg_start = called_emsg; + + tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here + for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + argv[0] = *rettv; + argv[1] = *TV_LIST_ITEM_TV(li); + rettv->v_type = VAR_UNKNOWN; + const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe); + tv_clear(&argv[0]); + if (r == FAIL || called_emsg != called_emsg_start) { + break; + } + } + tv_list_set_lock(l, prev_locked); + } + } else { + const blob_T *const b = argvars[0].vval.v_blob; + int i; + + if (argvars[2].v_type == VAR_UNKNOWN) { + if (tv_blob_len(b) == 0) { + semsg(_(e_reduceempty), "Blob"); + return; + } + initial.v_type = VAR_NUMBER; + initial.vval.v_number = tv_blob_get(b, 0); + i = 1; + } else if (argvars[2].v_type != VAR_NUMBER) { + emsg(_(e_number_exp)); + return; + } else { + initial = argvars[2]; + i = 0; + } + + tv_copy(&initial, rettv); + for (; i < tv_blob_len(b); i++) { + argv[0] = *rettv; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = tv_blob_get(b, i); + if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) { + return; + } + } + } +} + #define SP_NOMOVE 0x01 ///< don't move cursor #define SP_REPEAT 0x02 ///< repeat to find outer pair #define SP_RETCOUNT 0x04 ///< return matchcount @@ -7887,11 +7584,10 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) #define SP_END 0x40 ///< leave cursor at end of match #define SP_COLUMN 0x80 ///< start at cursor column -/* - * Get flags for a search function. - * Possibly sets "p_ws". - * Returns BACKWARD, FORWARD or zero (for an error). - */ +/// Get flags for a search function. +/// Possibly sets "p_ws". +/// +/// @return BACKWARD, FORWARD or zero (for an error). static int get_search_arg(typval_T *varp, int *flagsp) { int dir = FORWARD; @@ -7949,7 +7645,7 @@ static int get_search_arg(typval_T *varp, int *flagsp) return dir; } -// Shared by search() and searchpos() functions. +/// Shared by search() and searchpos() functions. static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) { int flags; @@ -7964,6 +7660,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) int options = SEARCH_KEEP; int subpatnum; searchit_arg_T sia; + bool use_skip = false; const char *const pat = tv_get_string(&argvars[0]); dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. @@ -7981,7 +7678,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) options |= SEARCH_COL; } - // Optional arguments: line number to stop searching and timeout. + // Optional arguments: line number to stop searching, timeout and skip. if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { lnum_stop = tv_get_number_chk(&argvars[2], NULL); if (lnum_stop < 0) { @@ -7992,6 +7689,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) if (time_limit < 0) { goto theend; } + use_skip = eval_expr_valid_arg(&argvars[4]); } } @@ -8011,11 +7709,46 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) } pos = save_cursor = curwin->w_cursor; + pos_T firstpos = { 0 }; memset(&sia, 0, sizeof(sia)); sia.sa_stop_lnum = (linenr_T)lnum_stop; sia.sa_tm = &tm; - subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, - options, RE_SEARCH, &sia); + + // Repeat until {skip} returns false. + for (;;) { + subpatnum + = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, options, RE_SEARCH, &sia); + // finding the first match again means there is no match where {skip} + // evaluates to zero. + if (firstpos.lnum != 0 && equalpos(pos, firstpos)) { + subpatnum = FAIL; + } + + if (subpatnum == FAIL || !use_skip) { + // didn't find it or no skip argument + break; + } + firstpos = pos; + + // If the skip expression matches, ignore this match. + { + const pos_T save_pos = curwin->w_cursor; + + curwin->w_cursor = pos; + bool err = false; + const bool do_skip = eval_expr_to_bool(&argvars[4], &err); + curwin->w_cursor = save_pos; + if (err) { + // Evaluating {skip} caused an error, break here. + subpatnum = FAIL; + break; + } + if (!do_skip) { + break; + } + } + } + if (subpatnum != FAIL) { if (flags & SP_SUBPAT) { retval = subpatnum; @@ -8048,7 +7781,7 @@ theend: return retval; } -// "rpcnotify()" function +/// "rpcnotify()" function static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -8083,7 +7816,7 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; } -// "rpcrequest()" function +/// "rpcrequest()" function static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -8181,7 +7914,7 @@ end: api_clear_error(&err); } -// "rpcstart()" function (DEPRECATED) +/// "rpcstart()" function (DEPRECATED) static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -8248,7 +7981,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "rpcstop()" function +/// "rpcstop()" function static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -8278,7 +8011,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "screenattr()" function +/// "screenattr()" function static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int c; @@ -8296,7 +8029,7 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = c; } -// "screenchar()" function +/// "screenchar()" function static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int c; @@ -8314,7 +8047,7 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = c; } -// "screenchars()" function +/// "screenchars()" function static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int row = tv_get_number_chk(&argvars[0], NULL) - 1; @@ -8339,9 +8072,9 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "screencol()" function -// -// First column is 1 to be consistent with virtcol(). +/// "screencol()" function +/// +/// First column is 1 to be consistent with virtcol(). static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = ui_current_col() + 1; @@ -8373,13 +8106,13 @@ static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("endcol"), ecol); } -// "screenrow()" function +/// "screenrow()" function static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = ui_current_row() + 1; } -// "screenstring()" function +/// "screenstring()" function static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_string = NULL; @@ -8395,7 +8128,7 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = vim_strsave(grid->chars[grid->line_offset[row] + col]); } -// "search()" function +/// "search()" function static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int flags = 0; @@ -8403,9 +8136,7 @@ static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = search_cmn(argvars, NULL, &flags); } -/* - * "searchdecl()" function - */ +/// "searchdecl()" function static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int locally = 1; @@ -8427,9 +8158,7 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * Used by searchpair() and searchpairpos() - */ +/// Used by searchpair() and searchpairpos() static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) { bool save_p_ws = p_ws; @@ -8475,13 +8204,9 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) || argvars[4].v_type == VAR_UNKNOWN) { skip = NULL; } else { + // Type is checked later. skip = &argvars[4]; - if (skip->v_type != VAR_FUNC - && skip->v_type != VAR_PARTIAL - && skip->v_type != VAR_STRING) { - semsg(_(e_invarg2), tv_get_string(&argvars[4])); - goto theend; // Type error. - } + if (argvars[5].v_type != VAR_UNKNOWN) { lnum_stop = tv_get_number_chk(&argvars[5], NULL); if (lnum_stop < 0) { @@ -8507,17 +8232,13 @@ theend: return retval; } -/* - * "searchpair()" function - */ +/// "searchpair()" function static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = searchpair_cmn(argvars, NULL); } -/* - * "searchpairpos()" function - */ +/// "searchpairpos()" function static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T match_pos; @@ -8592,10 +8313,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir } if (skip != NULL) { - // Empty string means to not use the skip expression. - if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { - use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; - } + use_skip = eval_expr_valid_arg(skip); } save_cursor = curwin->w_cursor; @@ -8706,9 +8424,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir return retval; } -/* - * "searchpos()" function - */ +/// "searchpos()" function static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T match_pos; @@ -8822,9 +8538,7 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setbufvar()" function - */ +/// "setbufvar()" function static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure() @@ -8868,6 +8582,49 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// Set the cursor or mark position. +/// If 'charpos' is TRUE, then use the column number as a character offset. +/// Otherwise use the column number as a byte offset. +static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) +{ + pos_T pos; + int fnum; + colnr_T curswant = -1; + + rettv->vval.v_number = -1; + const char *const name = tv_get_string_chk(argvars); + if (name != NULL) { + if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) { + if (pos.col != MAXCOL && --pos.col < 0) { + pos.col = 0; + } + if (name[0] == '.' && name[1] == NUL) { + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = false; + } + check_cursor(); + rettv->vval.v_number = 0; + } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { + // set mark + if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { + rettv->vval.v_number = 0; + } + } else { + emsg(_(e_invarg)); + } + } + } +} + +/// "setcharpos()" function +static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_position(argvars, rettv, true); +} + static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *d; @@ -8898,9 +8655,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setcmdpos()" function - */ +/// "setcmdpos()" function static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const int pos = (int)tv_get_number(&argvars[0]) - 1; @@ -8910,6 +8665,12 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// "setcursorcharpos" function +static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_cursorpos(argvars, rettv, true); +} + /// "setenv()" function static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -8956,9 +8717,7 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = os_setperm(fname, mode) == OK; } -/* - * "setline()" function - */ +/// "setline()" function static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = tv_get_lnum(&argvars[0]); @@ -8985,7 +8744,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) { static char *e_invact = N_("E927: Invalid action: '%s'"); const char *title = NULL; - int action = ' '; + char action = ' '; static int recursive = 0; rettv->vval.v_number = -1; dict_T *what = NULL; @@ -9046,9 +8805,7 @@ skip_args: recursive--; } -/* - * "setloclist()" function - */ +/// "setloclist()" function static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *win; @@ -9061,151 +8818,13 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setmatches()" function - */ -static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *d; - list_T *s = NULL; - win_T *win = get_optional_window(argvars, 1); - - rettv->vval.v_number = -1; - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } - if (win == NULL) { - return; - } - - list_T *const l = argvars[0].vval.v_list; - // To some extent make sure that we are dealing with a list from - // "getmatches()". - int li_idx = 0; - TV_LIST_ITER_CONST(l, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT - || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { - semsg(_("E474: List item %d is either not a dictionary " - "or an empty one"), li_idx); - return; - } - if (!(tv_dict_find(d, S_LEN("group")) != NULL - && (tv_dict_find(d, S_LEN("pattern")) != NULL - || tv_dict_find(d, S_LEN("pos1")) != NULL) - && tv_dict_find(d, S_LEN("priority")) != NULL - && tv_dict_find(d, S_LEN("id")) != NULL)) { - semsg(_("E474: List item %d is missing one of the required keys"), - li_idx); - return; - } - li_idx++; - }); - - clear_matches(win); - bool match_add_failed = false; - TV_LIST_ITER_CONST(l, li, { - int i = 0; - - d = TV_LIST_ITEM_TV(li)->vval.v_dict; - dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); - if (di == NULL) { - if (s == NULL) { - s = tv_list_alloc(9); - } - - // match from matchaddpos() - for (i = 1; i < 9; i++) { - char buf[30]; // use 30 to avoid compiler warning - snprintf(buf, sizeof(buf), "pos%d", i); - dictitem_T *const pos_di = tv_dict_find(d, buf, -1); - if (pos_di != NULL) { - if (pos_di->di_tv.v_type != VAR_LIST) { - return; - } - - tv_list_append_tv(s, &pos_di->di_tv); - tv_list_ref(s); - } else { - break; - } - } - } - - // Note: there are three number buffers involved: - // - group_buf below. - // - numbuf in tv_dict_get_string(). - // - mybuf in tv_get_string(). - // - // If you change this code make sure that buffers will not get - // accidentally reused. - char group_buf[NUMBUFLEN]; - const char *const group = tv_dict_get_string_buf(d, "group", group_buf); - const int priority = (int)tv_dict_get_number(d, "priority"); - const int id = (int)tv_dict_get_number(d, "id"); - dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); - const char *const conceal = (conceal_di != NULL - ? tv_get_string(&conceal_di->di_tv) - : NULL); - if (i == 0) { - if (match_add(win, group, - tv_dict_get_string(d, "pattern", false), - priority, id, NULL, conceal) != id) { - match_add_failed = true; - } - } else { - if (match_add(win, group, NULL, priority, id, s, conceal) != id) { - match_add_failed = true; - } - tv_list_unref(s); - s = NULL; - } - }); - if (!match_add_failed) { - rettv->vval.v_number = 0; - } -} - -/* - * "setpos()" function - */ +/// "setpos()" function static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - pos_T pos; - int fnum; - colnr_T curswant = -1; - - rettv->vval.v_number = -1; - const char *const name = tv_get_string_chk(argvars); - if (name != NULL) { - if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) { - if (pos.col != MAXCOL && --pos.col < 0) { - pos.col = 0; - } - if (name[0] == '.' && name[1] == NUL) { - // set cursor; "fnum" is ignored - curwin->w_cursor = pos; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = false; - } - check_cursor(); - rettv->vval.v_number = 0; - } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { - // set mark - if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { - rettv->vval.v_number = 0; - } - } else { - emsg(_(e_invarg)); - } - } - } + set_position(argvars, rettv, false); } -/* - * "setqflist()" function - */ +/// "setqflist()" function static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { set_qf_ll_list(NULL, argvars, rettv); @@ -9241,12 +8860,9 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c return OK; } -/* - * "setreg()" function - */ +/// "setreg()" function static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int regname; bool append = false; MotionType yank_type; long block_len; @@ -9260,13 +8876,13 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (strregname == NULL) { return; // Type error; errmsg already given. } - regname = (uint8_t)(*strregname); + char regname = (uint8_t)(*strregname); if (regname == 0 || regname == '@') { regname = '"'; } const typval_T *regcontents = NULL; - int pointreg = 0; + char pointreg = 0; if (argvars[1].v_type == VAR_DICT) { dict_T *const d = argvars[1].vval.v_dict; @@ -9384,14 +9000,11 @@ free_lstval: if (set_unnamed) { // Discard the result. We already handle the error case. - if (op_reg_set_previous(regname)) { - } + op_reg_set_previous(regname); } } -/* - * "settabvar()" function - */ +/// "settabvar()" function static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = 0; @@ -9422,21 +9035,19 @@ static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "settabwinvar()" function - */ +/// "settabwinvar()" function static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { setwinvar(argvars, rettv, 1); } -// "settagstack()" function +/// "settagstack()" function static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { static char *e_invact2 = N_("E962: Invalid action: '%s'"); win_T *wp; dict_T *d; - int action = 'r'; + char action = 'r'; rettv->vval.v_number = -1; @@ -9483,9 +9094,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setwinvar()" function - */ +/// "setwinvar()" function static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { setwinvar(argvars, rettv, 0); @@ -9502,9 +9111,7 @@ static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * "shellescape({string})" function - */ +/// "shellescape({string})" function static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const bool do_special = non_zero_arg(&argvars[1]); @@ -9515,9 +9122,7 @@ static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * shiftwidth() function - */ +/// shiftwidth() function static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = 0; @@ -9535,275 +9140,7 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = get_sw_value(curbuf); } -/// "sign_define()" function -static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name; - - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { - // Define multiple signs - tv_list_alloc_ret(rettv, kListLenMayKnow); - - sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list); - return; - } - - // Define a single sign - rettv->vval.v_number = -1; - - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) { - emsg(_(e_dictreq)); - return; - } - - rettv->vval.v_number = sign_define_from_dict(name, - argvars[1].v_type == - VAR_DICT ? argvars[1].vval.v_dict : NULL); -} - -/// "sign_getdefined()" function -static void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name = NULL; - - tv_list_alloc_ret(rettv, 0); - - if (argvars[0].v_type != VAR_UNKNOWN) { - name = tv_get_string(&argvars[0]); - } - - sign_getlist((const char_u *)name, rettv->vval.v_list); -} - -/// "sign_getplaced()" function -static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf = NULL; - dict_T *dict; - dictitem_T *di; - linenr_T lnum = 0; - int sign_id = 0; - const char *group = NULL; - bool notanum = false; - - tv_list_alloc_ret(rettv, 0); - - if (argvars[0].v_type != VAR_UNKNOWN) { - // get signs placed in the specified buffer - buf = get_buf_arg(&argvars[0]); - if (buf == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT - || ((dict = argvars[1].vval.v_dict) == NULL)) { - emsg(_(e_dictreq)); - return; - } - if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { - // get signs placed at this line - lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - return; - } - (void)lnum; - lnum = tv_get_lnum(&di->di_tv); - } - if ((di = tv_dict_find(dict, "id", -1)) != NULL) { - // get sign placed with this identifier - sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - return; - } - } - if ((di = tv_dict_find(dict, "group", -1)) != NULL) { - group = tv_get_string_chk(&di->di_tv); - if (group == NULL) { - return; - } - if (*group == '\0') { // empty string means global group - group = NULL; - } - } - } - } - - sign_get_placed(buf, lnum, sign_id, (const char_u *)group, - rettv->vval.v_list); -} - -/// "sign_jump()" function -static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int sign_id; - char *sign_group = NULL; - buf_T *buf; - bool notanum = false; - - rettv->vval.v_number = -1; - - // Sign identifier - sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); - if (notanum) { - return; - } - if (sign_id <= 0) { - emsg(_(e_invarg)); - return; - } - - // Sign group - const char *sign_group_chk = tv_get_string_chk(&argvars[1]); - if (sign_group_chk == NULL) { - return; - } - if (sign_group_chk[0] == '\0') { - sign_group = NULL; // global sign group - } else { - sign_group = xstrdup(sign_group_chk); - } - - // Buffer to place the sign - buf = get_buf_arg(&argvars[2]); - if (buf == NULL) { - goto cleanup; - } - - rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); - -cleanup: - xfree(sign_group); -} - -/// "sign_place()" function -static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict = NULL; - - rettv->vval.v_number = -1; - - if (argvars[4].v_type != VAR_UNKNOWN - && (argvars[4].v_type != VAR_DICT - || ((dict = argvars[4].vval.v_dict) == NULL))) { - emsg(_(e_dictreq)); - return; - } - - rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3], - dict); -} - -/// "sign_placelist()" function. Place multiple signs. -static void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int sign_id; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } - - // Process the List of sign attributes - TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - sign_id = -1; - if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { - sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); - } else { - emsg(_(e_dictreq)); - } - tv_list_append_number(rettv->vval.v_list, sign_id); - }); -} - -/// "sign_undefine()" function -static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name; - - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { - // Undefine multiple signs - tv_list_alloc_ret(rettv, kListLenMayKnow); - - sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list); - return; - } - - rettv->vval.v_number = -1; - - if (argvars[0].v_type == VAR_UNKNOWN) { - // Free all the signs - free_signs(); - rettv->vval.v_number = 0; - } else { - // Free only the specified sign - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { - return; - } - - if (sign_undefine_by_name((const char_u *)name) == OK) { - rettv->vval.v_number = 0; - } - } -} - -/// "sign_unplace()" function -static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict = NULL; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type != VAR_STRING) { - emsg(_(e_invarg)); - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT) { - emsg(_(e_dictreq)); - return; - } - dict = argvars[1].vval.v_dict; - } - - rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict); -} - -/// "sign_unplacelist()" function -static void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int retval; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } - - TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - retval = -1; - if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { - retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); - } else { - emsg(_(e_dictreq)); - } - tv_list_append_number(rettv->vval.v_list, retval); - }); -} - -/* - * "simplify()" function - */ +/// "simplify()" function static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const p = tv_get_string(&argvars[0]); @@ -9880,9 +9217,7 @@ static sortinfo_T *sortinfo = NULL; #define ITEM_COMPARE_FAIL 999 -/* - * Compare functions for f_sort() and f_uniq() below. - */ +/// Compare functions for f_sort() and f_uniq() below. static int item_compare(const void *s1, const void *s2, bool keep_zero) { ListSortItem *const si1 = (ListSortItem *)s1; @@ -10019,6 +9354,11 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) res = ITEM_COMPARE_FAIL; } else { res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); + if (res > 0) { + res = 1; + } else if (res < 0) { + res = -1; + } } if (sortinfo->item_compare_func_err) { res = ITEM_COMPARE_FAIL; // return value has wrong type @@ -10046,9 +9386,7 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2) return item_compare2(s1, s2, false); } -/* - * "sort({list})" function - */ +/// "sort({list})" function static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) { ListSortItem *ptrs; @@ -10215,6 +9553,10 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { return; } + if (!tv_dict_get_callback(opts, S_LEN("on_print"), &on_print)) { + return; + } + on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered"); if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) { on_stdin.self = opts; @@ -10237,7 +9579,7 @@ static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_sort_uniq(argvars, rettv, false); } -// "reltimefloat()" function +/// "reltimefloat()" function static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL { @@ -10250,9 +9592,7 @@ static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "soundfold({word})" function - */ +/// "soundfold({word})" function static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -10260,9 +9600,7 @@ static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)eval_soundfold(s); } -/* - * "spellbadword()" function - */ +/// "spellbadword()" function static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *word = ""; @@ -10321,9 +9659,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) NULL), -1); } -/* - * "spellsuggest()" function - */ +/// "spellsuggest()" function static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool typeerr = false; @@ -10478,9 +9814,7 @@ static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "str2float()" function - */ +/// "str2float()" function static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); @@ -10496,7 +9830,7 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_FLOAT; } -// "str2list()" function +/// "str2list()" function static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, kListLenUnknown); @@ -10507,7 +9841,7 @@ static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "str2nr()" function +/// "str2nr()" function static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int base = 10; @@ -10550,9 +9884,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "strftime({format}[, {time}])" function - */ +/// "strftime({format}[, {time}])" function static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { time_t seconds; @@ -10604,7 +9936,7 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "strgetchar()" function +/// "strgetchar()" function static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; @@ -10632,9 +9964,7 @@ static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "stridx()" function - */ +/// "stridx()" function static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; @@ -10666,26 +9996,20 @@ static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "string()" function - */ -void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// "string()" function +static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)encode_tv2string(&argvars[0], NULL); } -/* - * "strlen()" function - */ +/// "strlen()" function static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); } -/* - * "strchars()" function - */ +/// "strchars()" function static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *s = tv_get_string(&argvars[0]); @@ -10708,9 +10032,7 @@ static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "strdisplaywidth()" function - */ +/// "strdisplaywidth()" function static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const s = tv_get_string(&argvars[0]); @@ -10723,9 +10045,7 @@ static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); } -/* - * "strwidth()" function - */ +/// "strwidth()" function static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const s = tv_get_string(&argvars[0]); @@ -10733,7 +10053,7 @@ static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s); } -// "strcharpart()" function +/// "strcharpart()" function static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const p = tv_get_string(&argvars[0]); @@ -10787,9 +10107,7 @@ static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len); } -/* - * "strpart()" function - */ +/// "strpart()" function static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool error = false; @@ -10835,7 +10153,7 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); } -// "strptime({format}, {timestring})" function +/// "strptime({format}, {timestring})" function static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char fmt_buf[NUMBUFLEN]; @@ -10867,9 +10185,7 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(enc); } -/* - * "strridx()" function - */ +/// "strridx()" function static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf[NUMBUFLEN]; @@ -10912,18 +10228,14 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "strtrans()" function - */ +/// "strtrans()" function static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0]), true); } -/* - * "submatch()" function - */ +/// "submatch()" function static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool error = false; @@ -10954,9 +10266,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "substitute()" function - */ +/// "substitute()" function static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char patbuf[NUMBUFLEN]; @@ -11025,9 +10335,7 @@ static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = id; } -/* - * "synIDattr(id, what [, mode])" function - */ +/// "synIDattr(id, what [, mode])" function static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const int id = (int)tv_get_number(&argvars[0]); @@ -11082,22 +10390,28 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) p = highlight_has_attr(id, HL_STANDOUT, modec); } break; - case 'u': - if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline + case 'u': { + const size_t len = STRLEN(what); + if (len <= 5 || (TOLOWER_ASC(what[5]) == 'l' && len <= 9)) { // underline p = highlight_has_attr(id, HL_UNDERLINE, modec); - } else { // undercurl + } else if (TOLOWER_ASC(what[5]) == 'c') { // undercurl p = highlight_has_attr(id, HL_UNDERCURL, modec); + } else if (len > 9 && TOLOWER_ASC(what[9]) == 'l') { // underlineline + p = highlight_has_attr(id, HL_UNDERLINELINE, modec); + } else if (len > 6 && TOLOWER_ASC(what[6]) == 'o') { // underdot + p = highlight_has_attr(id, HL_UNDERDOT, modec); + } else { // underdash + p = highlight_has_attr(id, HL_UNDERDASH, modec); } break; } + } rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p)); } -/* - * "synIDtrans(id)" function - */ +/// "synIDtrans(id)" function static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int id = tv_get_number(&argvars[0]); @@ -11111,9 +10425,7 @@ static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = id; } -/* - * "synconcealed(lnum, col)" function - */ +/// "synconcealed(lnum, col)" function static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int syntax_flags = 0; @@ -11155,9 +10467,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, matchid); } -/* - * "synstack(lnum, col)" function - */ +/// "synstack(lnum, col)" function static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_set_ret(rettv, NULL); @@ -11193,9 +10503,7 @@ static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -/* - * "tabpagebuflist()" function - */ +/// "tabpagebuflist()" function static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wp = NULL; @@ -11217,9 +10525,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "tabpagenr()" function - */ +/// "tabpagenr()" function static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int nr = 1; @@ -11231,9 +10537,7 @@ static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (strcmp(arg, "$") == 0) { nr = tabpage_index(NULL) - 1; } else if (strcmp(arg, "#") == 0) { - nr = valid_tabpage(lastused_tabpage) - ? tabpage_index(lastused_tabpage) - : nr; + nr = valid_tabpage(lastused_tabpage) ? tabpage_index(lastused_tabpage) : 0; } else { semsg(_(e_invexpr2), arg); } @@ -11245,9 +10549,7 @@ static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -/* - * Common code for tabpagewinnr() and winnr(). - */ +/// Common code for tabpagewinnr() and winnr(). static int get_winnr(tabpage_T *tp, typval_T *argvar) { win_T *twin; @@ -11312,9 +10614,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) return nr; } -/* - * "tabpagewinnr()" function - */ +/// "tabpagewinnr()" function static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int nr = 1; @@ -11327,9 +10627,7 @@ static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = nr; } -/* - * "tagfiles()" function - */ +/// "tagfiles()" function static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char *fname; @@ -11348,9 +10646,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(fname); } -/* - * "taglist()" function - */ +/// "taglist()" function static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const tag_pattern = tv_get_string(&argvars[0]); @@ -11368,16 +10664,14 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) (char_u *)tag_pattern, (char_u *)fname); } -/* - * "tempname()" function - */ +/// "tempname()" function static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = vim_tempname(); } -// "termopen(cmd[, cwd])" function +/// "termopen(cmd[, cwd])" function static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure()) { @@ -11490,24 +10784,6 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) channel_create_event(chan, NULL); } -// "test_garbagecollect_now()" function -static void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // This is dangerous, any Lists and Dicts used internally may be freed - // while still in use. - garbage_collect(true); -} - -// "test_write_list_log()" function -static void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, FunPtr fptr) -{ - const char *const fname = tv_get_string_chk(&argvars[0]); - if (fname == NULL) { - return; - } - list_write_log(fname); -} - /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -11553,6 +10829,9 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) dict_T *dict; rettv->vval.v_number = -1; + if (check_secure()) { + return; + } if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT @@ -11578,7 +10857,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -// "timer_stop(timerid)" function +/// "timer_stop(timerid)" function static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type != VAR_NUMBER) { @@ -11599,9 +10878,7 @@ static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) timer_stop_all(); } -/* - * "tolower(string)" function - */ +/// "tolower(string)" function static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -11609,9 +10886,7 @@ static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) false); } -/* - * "toupper(string)" function - */ +/// "toupper(string)" function static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -11619,9 +10894,7 @@ static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) true); } -/* - * "tr(string, fromstr, tostr)" function - */ +/// "tr(string, fromstr, tostr)" function static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf[NUMBUFLEN]; @@ -11701,7 +10974,7 @@ error: return; } -// "trim({expr})" function +/// "trim({expr})" function static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf1[NUMBUFLEN]; @@ -11784,9 +11057,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = vim_strnsave(head, tail - head); } -/* - * "type(expr)" function - */ +/// "type(expr)" function static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n = -1; @@ -11818,9 +11089,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "undofile(name)" function - */ +/// "undofile(name)" function static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -11839,9 +11108,7 @@ static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "undotree()" function - */ +/// "undotree()" function static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); @@ -11859,24 +11126,20 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); } -/* - * "values(dict)" function - */ +/// "values(dict)" function static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_list(argvars, rettv, 1); } -/* - * "virtcol(string)" function - */ +/// "virtcol(string)" function static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { colnr_T vcol = 0; pos_T *fp; int fnum = curbuf->b_fnum; - fp = var2fpos(&argvars[0], FALSE, &fnum); + fp = var2fpos(&argvars[0], false, &fnum, false); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { // Limit the column to a valid value, getvvcol() doesn't check. @@ -11895,9 +11158,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = vcol; } -/* - * "visualmode()" function - */ +/// "visualmode()" function static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u str[2]; @@ -11913,9 +11174,7 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "wildmenumode()" function - */ +/// "wildmenumode()" function static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) { @@ -11982,6 +11241,42 @@ static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = win_id2win(argvars); } +/// "win_move_separator()" function +static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp; + int offset; + + rettv->vval.v_number = false; + + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL || wp->w_floating) { + return; + } + + offset = (int)tv_get_number(&argvars[1]); + win_drag_vsep_line(wp, offset); + rettv->vval.v_number = true; +} + +/// "win_move_statusline()" function +static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp; + int offset; + + rettv->vval.v_number = false; + + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL || wp->w_floating) { + return; + } + + offset = (int)tv_get_number(&argvars[1]); + win_drag_status_line(wp, offset); + rettv->vval.v_number = true; +} + /// "winbufnr(nr)" function static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -11993,9 +11288,7 @@ static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "wincol()" function - */ +/// "wincol()" function static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { validate_cursor(); @@ -12013,7 +11306,7 @@ static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "winlayout()" function +/// "winlayout()" function static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tabpage_T *tp; @@ -12032,18 +11325,14 @@ static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); } -/* - * "winline()" function - */ +/// "winline()" function static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { validate_cursor(); rettv->vval.v_number = curwin->w_wrow + 1; } -/* - * "winnr()" function - */ +/// "winnr()" function static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int nr = 1; @@ -12052,9 +11341,7 @@ static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = nr; } -/* - * "winrestcmd()" function - */ +/// "winrestcmd()" function static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { garray_T ga; @@ -12081,9 +11368,7 @@ static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * "winrestview()" function - */ +/// "winrestview()" function static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *dict; @@ -12134,9 +11419,7 @@ static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "winsaveview()" function - */ +/// "winsaveview()" function static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *dict; @@ -12167,7 +11450,7 @@ static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "windowsversion()" function +/// "windowsversion()" function static void f_windowsversion(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -12258,9 +11541,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } -/* - * "xor(expr, expr)" function - */ + +/// "xor(expr, expr)" function static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 3e37e8cbb6..2a432ecb47 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -8,6 +8,7 @@ #include <stdlib.h> #include <string.h> +#include "lauxlib.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/charset.h" @@ -28,12 +29,11 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/os/fileio.h" +#include "nvim/os/input.h" #include "nvim/pos.h" #include "nvim/types.h" #include "nvim/vim.h" -// TODO(ZyX-I): Move line_breakcheck out of misc1 -#include "nvim/misc1.h" // For line_breakcheck -#include "nvim/os/fileio.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" @@ -1124,6 +1124,8 @@ bool tv_callback_equal(const Callback *cb1, const Callback *cb2) // FIXME: this is inconsistent with tv_equal but is needed for precision // maybe change dictwatcheradd to return a watcher id instead? return cb1->data.partial == cb2->data.partial; + case kCallbackLua: + return cb1->data.luaref == cb2->data.luaref; case kCallbackNone: return true; } @@ -1143,6 +1145,9 @@ void callback_free(Callback *callback) case kCallbackPartial: partial_unref(callback->data.partial); break; + case kCallbackLua: + NLUA_CLEAR_REF(callback->data.luaref); + break; case kCallbackNone: break; } @@ -1165,6 +1170,11 @@ void callback_put(Callback *cb, typval_T *tv) tv->vval.v_string = vim_strsave(cb->data.funcref); func_ref(cb->data.funcref); break; + case kCallbackLua: + // TODO(tjdevries): Unified Callback. + // At this point this isn't possible, but it'd be nice to put + // these handled more neatly in one place. + // So instead, we just do the default and put nil default: tv->v_type = VAR_SPECIAL; tv->vval.v_special = kSpecialVarNull; @@ -1186,6 +1196,9 @@ void callback_copy(Callback *dest, Callback *src) dest->data.funcref = vim_strsave(src->data.funcref); func_ref(src->data.funcref); break; + case kCallbackLua: + dest->data.luaref = api_new_luaref(src->data.luaref); + break; default: dest->data.funcref = NULL; break; @@ -2456,13 +2469,11 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, dict_T **const dic #define TYPVAL_ENCODE_NAME nothing #define TYPVAL_ENCODE_FIRST_ARG_TYPE const void *const #define TYPVAL_ENCODE_FIRST_ARG_NAME ignored -#define TYPVAL_ENCODE_TRANSLATE_OBJECT_NAME #include "nvim/eval/typval_encode.c.h" #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME #undef TYPVAL_ENCODE_FIRST_ARG_TYPE #undef TYPVAL_ENCODE_FIRST_ARG_NAME -#undef TYPVAL_ENCODE_TRANSLATE_OBJECT_NAME #undef TYPVAL_ENCODE_ALLOW_SPECIALS #undef TYPVAL_ENCODE_CONV_NIL @@ -3094,7 +3105,7 @@ linenr_T tv_get_lnum(const typval_T *const tv) linenr_T lnum = (linenr_T)tv_get_number_chk(tv, NULL); if (lnum == 0) { // No valid number, try using same function as line() does. int fnum; - pos_T *const fp = var2fpos(tv, true, &fnum); + pos_T *const fp = var2fpos(tv, true, &fnum, false); if (fp != NULL) { lnum = fp->lnum; } @@ -3208,8 +3219,9 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) case VAR_BLOB: case VAR_UNKNOWN: emsg(_(str_errors[tv->v_type])); - return false; + return NULL; } + abort(); return NULL; } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index d1275d6512..40dc819754 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -72,16 +72,20 @@ typedef enum { kCallbackNone = 0, kCallbackFuncref, kCallbackPartial, + kCallbackLua, } CallbackType; typedef struct { union { char_u *funcref; partial_T *partial; + LuaRef luaref; } data; CallbackType type; } Callback; -#define CALLBACK_NONE ((Callback){ .type = kCallbackNone }) + +#define CALLBACK_INIT { .type = kCallbackNone } +#define CALLBACK_NONE ((Callback)CALLBACK_INIT) /// Structure holding dictionary watcher typedef struct dict_watcher { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 9478a8441b..eb5c6e503a 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -17,7 +17,6 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" -#include "nvim/misc1.h" #include "nvim/os/input.h" #include "nvim/regexp.h" #include "nvim/search.h" @@ -202,7 +201,7 @@ static void register_closure(ufunc_T *fp) } -/// Get a name for a lambda. Returned in static memory. +/// @return a name for a lambda. Returned in static memory. char_u *get_lambda_name(void) { static char_u name[30]; @@ -517,7 +516,7 @@ static char_u *fname_trans_sid(const char_u *const name, char_u *const fname_buf if (llen > 0) { fname_buf[0] = K_SPECIAL; fname_buf[1] = KS_EXTRA; - fname_buf[2] = (int)KE_SNR; + fname_buf[2] = KE_SNR; int i = 3; if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:" if (current_sctx.sc_sid <= 0) { @@ -545,7 +544,8 @@ static char_u *fname_trans_sid(const char_u *const name, char_u *const fname_buf } /// Find a function by name, return pointer to it in ufuncs. -/// @return NULL for unknown function. +/// +/// @return NULL for unknown function. ufunc_T *find_func(const char_u *name) { hashitem_T *hi; @@ -557,11 +557,9 @@ ufunc_T *find_func(const char_u *name) return NULL; } -/* - * Copy the function name of "fp" to buffer "buf". - * "buf" must be able to hold the function name plus three bytes. - * Takes care of script-local function names. - */ +/// Copy the function name of "fp" to buffer "buf". +/// "buf" must be able to hold the function name plus three bytes. +/// Takes care of script-local function names. static void cat_func_name(char_u *buf, ufunc_T *fp) { if (fp->uf_name[0] == K_SPECIAL) { @@ -572,9 +570,7 @@ static void cat_func_name(char_u *buf, ufunc_T *fp) } } -/* - * Add a number variable "name" to dict "dp" with value "nr". - */ +/// Add a number variable "name" to dict "dp" with value "nr". static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) { #ifndef __clang_analyzer__ @@ -587,7 +583,7 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) v->di_tv.vval.v_number = nr; } -// Free "fc" +/// Free "fc" static void free_funccal(funccall_T *fc) { for (int i = 0; i < fc->fc_funcs.ga_len; i++) { @@ -607,9 +603,9 @@ static void free_funccal(funccall_T *fc) xfree(fc); } -// Free "fc" and what it contains. -// Can be called only when "fc" is kept beyond the period of it called, -// i.e. after cleanup_function_call(fc). +/// Free "fc" and what it contains. +/// Can be called only when "fc" is kept beyond the period of it called, +/// i.e. after cleanup_function_call(fc). static void free_funccal_contents(funccall_T *fc) { // Free all l: variables. @@ -758,7 +754,7 @@ static void func_clear_items(ufunc_T *fp) /// Free all things that a function contains. Does not free the function /// itself, use func_free() for that. /// -/// param[in] force When true, we are exiting. +/// @param[in] force When true, we are exiting. static void func_clear(ufunc_T *fp, bool force) { if (fp->uf_cleared) { @@ -774,7 +770,7 @@ static void func_clear(ufunc_T *fp, bool force) /// Free a function and remove it from the list of functions. Does not free /// what a function contains, call func_clear() first. /// -/// param[in] fp The function to free. +/// @param[in] fp The function to free. static void func_free(ufunc_T *fp) { // only remove it when not done already, otherwise we would remove a newer @@ -787,7 +783,7 @@ static void func_free(ufunc_T *fp) /// Free all things that a function contains and free the function itself. /// -/// param[in] force When true, we are exiting. +/// @param[in] force When true, we are exiting. static void func_clear_free(ufunc_T *fp, bool force) { func_clear(fp, force); @@ -796,13 +792,13 @@ static void func_clear_free(ufunc_T *fp, bool force) /// Call a user function /// -/// @param fp Function to call. -/// @param[in] argcount Number of arguments. -/// @param argvars Arguments. -/// @param[out] rettv Return value. -/// @param[in] firstline First line of range. -/// @param[in] lastline Last line of range. -/// @param selfdict Dictionary for "self" for dictionary functions. +/// @param fp Function to call. +/// @param[in] argcount Number of arguments. +/// @param argvars Arguments. +/// @param[out] rettv Return value. +/// @param[in] firstline First line of range. +/// @param[in] lastline Last line of range. +/// @param selfdict Dictionary for "self" for dictionary functions. void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, linenr_T firstline, linenr_T lastline, dict_T *selfdict) FUNC_ATTR_NONNULL_ARG(1, 3, 4) @@ -1231,8 +1227,8 @@ static bool func_name_refcount(char_u *name) static funccal_entry_T *funccal_stack = NULL; -// Save the current function call pointer, and set it to NULL. -// Used when executing autocommands and for ":source". +/// Save the current function call pointer, and set it to NULL. +/// Used when executing autocommands and for ":source". void save_funccal(funccal_entry_T *entry) { entry->top_funccal = current_funccal; @@ -1385,8 +1381,8 @@ func_call_skip_call: return r; } -// Give an error message for the result of a function. -// Nothing if "error" is FCERR_NONE. +/// 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 { @@ -1714,7 +1710,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, // Check for hard coded <SNR>: already translated function ID (from a user // command). if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA - && (*pp)[2] == (int)KE_SNR) { + && (*pp)[2] == KE_SNR) { *pp += 3; len = get_id_len((const char **)pp) + 3; return (char_u *)xmemdupz(start, len); @@ -1822,7 +1818,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, // Change "<SNR>" to the byte sequence. name[0] = K_SPECIAL; name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; + name[2] = KE_SNR; memmove(name + 3, name + 5, strlen((char *)name + 5) + 1); } goto theend; @@ -1889,7 +1885,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, if (!skip && lead > 0) { name[0] = K_SPECIAL; name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; + name[2] = KE_SNR; if (sid_buf_len > 0) { // If it's "<SID>" memcpy(name + 3, sid_buf, sid_buf_len); } @@ -1903,9 +1899,7 @@ theend: return name; } -/* - * ":function" - */ +/// ":function" void ex_function(exarg_T *eap) { char_u *theline; @@ -2574,6 +2568,7 @@ void ex_function(exarg_T *eap) fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; + nlua_set_sctx(&fp->uf_script_ctx); goto ret_free; @@ -2595,11 +2590,9 @@ ret_free: } } // NOLINT(readability/fn_size) -/* - * Return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case). - * Return 2 if "p" starts with "s:". - * Return 0 otherwise. - */ +/// @return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case). +/// 2 if "p" starts with "s:". +/// 0 otherwise. int eval_fname_script(const char *const p) { // Use mb_strnicmp() because in Turkish comparing the "I" may not work with @@ -2625,10 +2618,10 @@ bool translated_function_exists(const char *name) /// Check whether function with the given name exists /// -/// @param[in] name Function name. -/// @param[in] no_deref Whether to dereference a Funcref. +/// @param[in] name Function name. +/// @param[in] no_deref Whether to dereference a Funcref. /// -/// @return True if it exists, false otherwise. +/// @return true if it exists, false otherwise. bool function_exists(const char *const name, bool no_deref) { const char_u *nm = (const char_u *)name; @@ -2651,10 +2644,8 @@ bool function_exists(const char *const name, bool no_deref) return n; } -/* - * Function given to ExpandGeneric() to obtain the list of user defined - * function names. - */ +/// Function given to ExpandGeneric() to obtain the list of user defined +/// function names. char_u *get_user_func_name(expand_T *xp, int idx) { static size_t done; @@ -2771,10 +2762,8 @@ void ex_delfunction(exarg_T *eap) } } -/* - * Unreference a Function: decrement the reference count and free it when it - * becomes zero. - */ +/// Unreference a Function: decrement the reference count and free it when it +/// becomes zero. void func_unref(char_u *name) { ufunc_T *fp = NULL; @@ -2868,9 +2857,7 @@ static int can_free_funccal(funccall_T *fc, int copyID) && fc->fc_copyID != copyID; } -/* - * ":return [expr]" - */ +/// ":return [expr]" void ex_return(exarg_T *eap) { char_u *arg = eap->arg; @@ -2921,9 +2908,7 @@ void ex_return(exarg_T *eap) // TODO(ZyX-I): move to eval/ex_cmds -/* - * ":1,25call func(arg1, arg2)" function call. - */ +/// ":1,25call func(arg1, arg2)" function call. void ex_call(exarg_T *eap) { char_u *arg = eap->arg; @@ -3050,14 +3035,16 @@ end: xfree(tofree); } -/* - * Return from a function. Possibly makes the return pending. Also called - * for a pending return at the ":endtry" or after returning from an extra - * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set - * when called due to a ":return" command. "rettv" may point to a typval_T - * with the return rettv. Returns TRUE when the return can be carried out, - * FALSE when the return gets pending. - */ +/// Return from a function. Possibly makes the return pending. Also called +/// for a pending return at the ":endtry" or after returning from an extra +/// do_cmdline(). "reanimate" is used in the latter case. +/// +/// @param reanimate used after returning from an extra do_cmdline(). +/// @param is_cmd set when called due to a ":return" command. +/// @param rettv may point to a typval_T with the return rettv. +/// +/// @return TRUE when the return can be carried out, +/// FALSE when the return gets pending. int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) { int idx; @@ -3068,12 +3055,10 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) current_funccal->returned = false; } - // // Cleanup (and deactivate) conditionals, but stop when a try conditional // not in its finally clause (which then is to be executed next) is found. // In this case, make the ":return" pending for execution at the ":endtry". // Otherwise, return normally. - // idx = cleanup_conditionals(eap->cstack, 0, true); if (idx >= 0) { cstack->cs_pending[idx] = CSTP_RETURN; @@ -3126,10 +3111,8 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) return idx < 0; } -/* - * Generate a return command for producing the value of "rettv". The result - * is an allocated string. Used by report_pending() for verbose messages. - */ +/// Generate a return command for producing the value of "rettv". The result +/// is an allocated string. Used by report_pending() for verbose messages. char_u *get_return_cmd(void *rettv) { char_u *s = NULL; @@ -3151,11 +3134,10 @@ char_u *get_return_cmd(void *rettv) return vim_strsave(IObuff); } -/* - * Get next function line. - * Called by do_cmdline() to get the next line. - * Returns allocated string, or NULL for end of function. - */ +/// Get next function line. +/// Called by do_cmdline() to get the next line. +/// +/// @return allocated string, or NULL for end of function. char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) { funccall_T *fcp = (funccall_T *)cookie; @@ -3206,10 +3188,8 @@ char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) return retval; } -/* - * Return TRUE if the currently active function should be ended, because a - * return was encountered or an error occurred. Used inside a ":while". - */ +/// @return TRUE if the currently active function should be ended, because a +/// return was encountered or an error occurred. Used inside a ":while". int func_has_ended(void *cookie) { funccall_T *fcp = (funccall_T *)cookie; @@ -3220,9 +3200,7 @@ int func_has_ended(void *cookie) || fcp->returned; } -/* - * return TRUE if cookie indicates a function which "abort"s on errors. - */ +/// @return TRUE if cookie indicates a function which "abort"s on errors. int func_has_abort(void *cookie) { return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; @@ -3289,41 +3267,31 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) } } -/* - * Return the name of the executed function. - */ +/// @return the name of the executed function. char_u *func_name(void *cookie) { return ((funccall_T *)cookie)->func->uf_name; } -/* - * Return the address holding the next breakpoint line for a funccall cookie. - */ +/// @return the address holding the next breakpoint line for a funccall cookie. linenr_T *func_breakpoint(void *cookie) { return &((funccall_T *)cookie)->breakpoint; } -/* - * Return the address holding the debug tick for a funccall cookie. - */ +/// @return the address holding the debug tick for a funccall cookie. int *func_dbg_tick(void *cookie) { return &((funccall_T *)cookie)->dbg_tick; } -/* - * Return the nesting level for a funccall cookie. - */ +/// @return the nesting level for a funccall cookie. int func_level(void *cookie) { return ((funccall_T *)cookie)->level; } -/* - * Return TRUE when a function was ended by a ":return" command. - */ +/// @return TRUE when a function was ended by a ":return" command. int current_func_returned(void) { return current_funccal->returned; @@ -3372,8 +3340,8 @@ funccall_T *get_funccal(void) return funccal; } -/// Return the hashtable used for local variables in the current funccal. -/// Return NULL if there is no current funccal. +/// @return hashtable used for local variables in the current funccal or +/// NULL if there is no current funccal. hashtab_T *get_funccal_local_ht(void) { if (current_funccal == NULL) { @@ -3382,8 +3350,8 @@ hashtab_T *get_funccal_local_ht(void) return &get_funccal()->l_vars.dv_hashtab; } -/// Return the l: scope variable. -/// Return NULL if there is no current funccal. +/// @return the l: scope variable or +/// NULL if there is no current funccal. dictitem_T *get_funccal_local_var(void) { if (current_funccal == NULL) { @@ -3392,8 +3360,8 @@ dictitem_T *get_funccal_local_var(void) return (dictitem_T *)&get_funccal()->l_vars_var; } -/// Return the hashtable used for argument in the current funccal. -/// Return NULL if there is no current funccal. +/// @return the hashtable used for argument in the current funccal or +/// NULL if there is no current funccal. hashtab_T *get_funccal_args_ht(void) { if (current_funccal == NULL) { @@ -3402,8 +3370,8 @@ hashtab_T *get_funccal_args_ht(void) return &get_funccal()->l_avars.dv_hashtab; } -/// Return the a: scope variable. -/// Return NULL if there is no current funccal. +/// @return the a: scope variable or +/// NULL if there is no current funccal. dictitem_T *get_funccal_args_var(void) { if (current_funccal == NULL) { @@ -3412,9 +3380,7 @@ dictitem_T *get_funccal_args_var(void) return (dictitem_T *)¤t_funccal->l_avars_var; } -/* - * List function variables, if there is a function. - */ +/// List function variables, if there is a function. void list_func_vars(int *first) { if (current_funccal != NULL) { @@ -3423,9 +3389,8 @@ void list_func_vars(int *first) } } -/// If "ht" is the hashtable for local variables in the current funccal, return -/// the dict that contains it. -/// Otherwise return NULL. +/// @return if "ht" is the hashtable for local variables in the current +/// funccal, return the dict that contains it. Otherwise return NULL. dict_T *get_current_funccal_dict(hashtab_T *ht) { if (current_funccal != NULL && ht == ¤t_funccal->l_vars.dv_hashtab) { @@ -3589,7 +3554,7 @@ bool set_ref_in_func_args(int copyID) /// "list_stack" is used to add lists to be marked. Can be NULL. /// "ht_stack" is used to add hashtabs to be marked. Can be NULL. /// -/// @return true if setting references failed somehow. +/// @return true if setting references failed somehow. bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) { ufunc_T *fp = fp_in; diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 892c46dd04..89fced59c5 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -75,7 +75,7 @@ bool loop_poll_events(Loop *loop, int ms) /// @note Event is queued into `fast_events`, which is processed outside of the /// primary `events` queue by loop_poll_events(). For `main_loop`, that /// means `fast_events` is NOT processed in an "editor mode" -/// (VimState.execute), so redraw and other side-effects are likely to be +/// (VimState.execute), so redraw and other side effects are likely to be /// skipped. /// @see loop_schedule_deferred void loop_schedule_fast(Loop *loop, Event event) diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index dae4dad16d..653fffae1c 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -237,7 +237,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL KILL_TIMEOUT_MS, 0); } -// Frees process-owned resources. +/// Frees process-owned resources. void process_free(Process *proc) FUNC_ATTR_NONNULL_ALL { if (proc->argv != NULL) { diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index f070c8179f..26b5ce3b75 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -11,8 +11,8 @@ #include "nvim/event/loop.h" #include "nvim/event/rstream.h" #include "nvim/log.h" +#include "nvim/main.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -85,7 +85,7 @@ static void on_rbuffer_nonfull(RBuffer *buf, void *data) // Callbacks used by libuv -// Called by libuv to allocate memory for reading. +/// Called by libuv to allocate memory for reading. static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) { Stream *stream = handle->data; @@ -95,9 +95,9 @@ static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) buf->len = UV_BUF_LEN(write_count); } -// Callback invoked by libuv after it copies the data into the buffer provided -// by `alloc_cb`. This is also called on EOF or when `alloc_cb` returns a -// 0-length buffer. +/// Callback invoked by libuv after it copies the data into the buffer provided +/// by `alloc_cb`. This is also called on EOF or when `alloc_cb` returns a +/// 0-length buffer. static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) { Stream *stream = uvstream->data; @@ -134,11 +134,11 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) invoke_read_cb(stream, nread, false); } -// Called by the by the 'idle' handle to emulate a reading event -// -// Idle callbacks are invoked once per event loop: -// - to perform some very low priority activity. -// - to keep the loop "alive" (so there is always an event to process) +/// Called by the by the 'idle' handle to emulate a reading event +/// +/// Idle callbacks are invoked once per event loop: +/// - to perform some very low priority activity. +/// - to keep the loop "alive" (so there is always an event to process) static void fread_idle_cb(uv_idle_t *handle) { uv_fs_t req; diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c index 7948a7be83..c20716496f 100644 --- a/src/nvim/event/socket.c +++ b/src/nvim/event/socket.c @@ -53,10 +53,8 @@ int socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoint uv_getaddrinfo_t request; int retval = uv_getaddrinfo(&loop->uv, &request, NULL, addr, port, - &(struct addrinfo){ - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - }); + &(struct addrinfo){ .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, }); if (retval != 0) { ELOG("Host lookup failed: %s", endpoint); return retval; @@ -103,10 +101,9 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb) // contain 0 in this case, unless uv_tcp_getsockname() is used first. uv_tcp_getsockname(&watcher->uv.tcp.handle, (struct sockaddr *)&sas, &(int){ sizeof(sas) }); - uint16_t port = (uint16_t)( - (sas.ss_family == AF_INET) - ? (STRUCT_CAST(struct sockaddr_in, &sas))->sin_port - : (STRUCT_CAST(struct sockaddr_in6, &sas))->sin6_port); + uint16_t port = (uint16_t)((sas.ss_family == AF_INET) + ? (STRUCT_CAST(struct sockaddr_in, &sas))->sin_port + : (STRUCT_CAST(struct sockaddr_in6, &sas))->sin6_port); // v:servername uses the string from watcher->addr size_t len = strlen(watcher->addr); snprintf(watcher->addr+len, sizeof(watcher->addr)-len, ":%" PRIu16, diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 77944851d2..98aabe89b3 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -38,7 +38,9 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/indent.h" +#include "nvim/input.h" #include "nvim/log.h" #include "nvim/main.h" #include "nvim/mark.h" @@ -46,7 +48,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -225,9 +226,7 @@ void do_ascii(const exarg_T *const eap) msg((char *)IObuff); } -/* - * ":left", ":center" and ":right": align text. - */ +/// ":left", ":center" and ":right": align text. void ex_align(exarg_T *eap) { pos_T save_curpos; @@ -305,9 +304,8 @@ void ex_align(exarg_T *eap) */ do { (void)set_indent(++new_indent, 0); - } - while (linelen(NULL) <= width); - --new_indent; + } while (linelen(NULL) <= width); + new_indent--; break; } --new_indent; @@ -325,9 +323,7 @@ void ex_align(exarg_T *eap) beginline(BL_WHITE | BL_FIX); } -/* - * Get the length of the current line, excluding trailing white space. - */ +/// @return the length of the current line, excluding trailing white space. static int linelen(int *has_tab) { char_u *line; @@ -453,7 +449,7 @@ static int sort_compare(const void *s1, const void *s2) return result; } -// ":sort". +/// ":sort". void ex_sort(exarg_T *eap) { regmatch_T regmatch; @@ -724,9 +720,7 @@ sortend: } } -/* - * ":retab". - */ +/// ":retab". void ex_retab(exarg_T *eap) { linenr_T lnum; @@ -815,7 +809,11 @@ void ex_retab(exarg_T *eap) // len is actual number of white characters used len = num_spaces + num_tabs; old_len = (long)STRLEN(ptr); - long new_len = old_len - col + start_col + len + 1; + const long new_len = old_len - col + start_col + len + 1; + if (new_len <= 0 || new_len >= MAXCOL) { + emsg(_(e_resulting_text_too_long)); + break; + } new_line = xmalloc(new_len); if (start_col > 0) { @@ -848,6 +846,10 @@ void ex_retab(exarg_T *eap) break; } vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol); + if (vcol >= MAXCOL) { + emsg(_(e_resulting_text_too_long)); + break; + } col += utfc_ptr2len(ptr + col); } if (new_line == NULL) { // out of memory @@ -900,11 +902,9 @@ void ex_retab(exarg_T *eap) u_clearline(); } -/* - * :move command - move lines line1-line2 to line dest - * - * return FAIL for failure, OK otherwise - */ +/// :move command - move lines line1-line2 to line dest +/// +/// @return FAIL for failure, OK otherwise int do_move(linenr_T line1, linenr_T line2, linenr_T dest) { char_u *str; @@ -981,8 +981,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) foldMoveRange(win, &win->w_folds, line1, line2, dest); } } - curbuf->b_op_start.lnum = dest - num_lines + 1; - curbuf->b_op_end.lnum = dest; + if (!cmdmod.lockmarks) { + curbuf->b_op_start.lnum = dest - num_lines + 1; + curbuf->b_op_end.lnum = dest; + } line_off = -num_lines; byte_off = -extent_byte; } else { @@ -992,10 +994,14 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2); } } - curbuf->b_op_start.lnum = dest + 1; - curbuf->b_op_end.lnum = dest + num_lines; + if (!cmdmod.lockmarks) { + curbuf->b_op_start.lnum = dest + 1; + curbuf->b_op_end.lnum = dest + num_lines; + } + } + if (!cmdmod.lockmarks) { + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; } - curbuf->b_op_start.col = curbuf->b_op_end.col = 0; mark_adjust_nofold(last_line - num_lines + 1, last_line, -(last_line - dest - extra), 0L, kExtmarkNOOP); @@ -1049,18 +1055,18 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) return OK; } -/* - * ":copy" - */ +/// ":copy" void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) { linenr_T count; char_u *p; count = line2 - line1 + 1; - curbuf->b_op_start.lnum = n + 1; - curbuf->b_op_end.lnum = n + count; - curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + if (!cmdmod.lockmarks) { + curbuf->b_op_start.lnum = n + 1; + curbuf->b_op_end.lnum = n + count; + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + } /* * there are three situations: @@ -1100,6 +1106,9 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) } appended_lines_mark(n, count); + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } msgmore((long)count); } @@ -1114,11 +1123,9 @@ void free_prev_shellcmd(void) #endif -/* - * Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd" - * Bangs in the argument are replaced with the previously entered command. - * Remember the argument. - */ +/// Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd" +/// Bangs in the argument are replaced with the previously entered command. +/// Remember the argument. void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out) FUNC_ATTR_NONNULL_ALL { @@ -1270,12 +1277,18 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd, char_u *cmd_buf; buf_T *old_curbuf = curbuf; int shell_flags = 0; + const pos_T orig_start = curbuf->b_op_start; + const pos_T orig_end = curbuf->b_op_end; const int stmp = p_stmp; if (*cmd == NUL) { // no filter command return; } + const bool save_lockmarks = cmdmod.lockmarks; + // Temporarily disable lockmarks since that's needed to propagate changed + // regions of the buffer for foldUpdate(), linecount, etc. + cmdmod.lockmarks = false; cursor_save = curwin->w_cursor; linecount = line2 - line1 + 1; @@ -1350,7 +1363,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd, ui_cursor_goto(Rows - 1, 0); if (do_out) { - if (u_save((line2), (linenr_T)(line2 + 1)) == FAIL) { + if (u_save(line2, (linenr_T)(line2 + 1)) == FAIL) { xfree(cmd_buf); goto error; } @@ -1456,10 +1469,15 @@ error: filterend: + cmdmod.lockmarks = save_lockmarks; if (curbuf != old_curbuf) { - --no_wait_return; + no_wait_return--; emsg(_("E135: *Filter* Autocommands must not change current buffer")); + } else if (cmdmod.lockmarks) { + curbuf->b_op_start = orig_start; + curbuf->b_op_end = orig_end; } + if (itmp != NULL) { os_remove((char *)itmp); } @@ -1655,9 +1673,7 @@ void print_line_no_prefix(linenr_T lnum, int use_number, int list) msg_prt_line(ml_get(lnum), list); } -/* - * Print a text line. Also in silent mode ("ex -s"). - */ +/// Print a text line. Also in silent mode ("ex -s"). void print_line(linenr_T lnum, int use_number, int list) { int save_silent = silent_mode; @@ -1725,9 +1741,7 @@ int rename_buffer(char_u *new_fname) return OK; } -/* - * ":file[!] [fname]". - */ +/// ":file[!] [fname]". void ex_file(exarg_T *eap) { // ":0file" removes the file name. Check for illegal uses ":3file", @@ -1753,9 +1767,7 @@ void ex_file(exarg_T *eap) } } -/* - * ":update". - */ +/// ":update". void ex_update(exarg_T *eap) { if (curbufIsChanged()) { @@ -1763,9 +1775,7 @@ void ex_update(exarg_T *eap) } } -/* - * ":write" and ":saveas". - */ +/// ":write" and ":saveas". void ex_write(exarg_T *eap) { if (eap->cmdidx == CMD_saveas) { @@ -1781,14 +1791,12 @@ void ex_write(exarg_T *eap) } } -/* - * write current buffer to file 'eap->arg' - * if 'eap->append' is TRUE, append to the file - * - * if *eap->arg == NUL write to current file - * - * return FAIL for failure, OK otherwise - */ +/// write current buffer to file 'eap->arg' +/// if 'eap->append' is TRUE, append to the file +/// +/// if *eap->arg == NUL write to current file +/// +/// @return FAIL for failure, OK otherwise int do_write(exarg_T *eap) { int other; @@ -1914,7 +1922,7 @@ int do_write(exarg_T *eap) // If 'filetype' was empty try detecting it now. if (*curbuf->b_p_ft == NUL) { - if (au_has_group((char_u *)"filetypedetect")) { + if (augroup_exists("filetypedetect")) { (void)do_doautocmd((char_u *)"filetypedetect BufRead", true, NULL); } do_modelines(0); @@ -2041,9 +2049,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char_u *fname, char_u *ffname, int return OK; } -/* - * Handle ":wnext", ":wNext" and ":wprevious" commands. - */ +/// Handle ":wnext", ":wNext" and ":wprevious" commands. void ex_wnext(exarg_T *eap) { int i; @@ -2060,9 +2066,7 @@ void ex_wnext(exarg_T *eap) } } -/* - * ":wall", ":wqall" and ":xall": Write all changed files (and exit). - */ +/// ":wall", ":wqall" and ":xall": Write all changed files (and exit). void do_wqall(exarg_T *eap) { int error = 0; @@ -2094,7 +2098,7 @@ void do_wqall(exarg_T *eap) } if (buf->b_ffname == NULL) { semsg(_("E141: No file name for buffer %" PRId64), (int64_t)buf->b_fnum); - ++error; + error++; } else if (check_readonly(&eap->forceit, buf) || check_overwrite(eap, buf, buf->b_fname, buf->b_ffname, FALSE) == FAIL) { @@ -2120,24 +2124,21 @@ void do_wqall(exarg_T *eap) } } -/* - * Check the 'write' option. - * Return TRUE and give a message when it's not st. - */ -int not_writing(void) +/// Check the 'write' option. +/// +/// @return true and give a message when it's not st. +bool not_writing(void) { if (p_write) { - return FALSE; + return false; } emsg(_("E142: File not written: Writing is disabled by 'write' option")); - return TRUE; + return true; } -/* - * Check if a buffer is read-only (either 'readonly' option is set or file is - * read-only). Ask for overruling in a dialog. Return TRUE and give an error - * message when the buffer is readonly. - */ +/// Check if a buffer is read-only (either 'readonly' option is set or file is +/// read-only). Ask for overruling in a dialog. Return TRUE and give an error +/// message when the buffer is readonly. static int check_readonly(int *forceit, buf_T *buf) { // Handle a file being readonly when the 'readonly' option is set or when @@ -2178,15 +2179,16 @@ static int check_readonly(int *forceit, buf_T *buf) return FALSE; } -// Try to abandon the current file and edit a new or existing file. -// "fnum" is the number of the file, if zero use "ffname_arg"/"sfname_arg". -// "lnum" is the line number for the cursor in the new file (if non-zero). -// -// Return: -// GETFILE_ERROR for "normal" error, -// GETFILE_NOT_WRITTEN for "not written" error, -// GETFILE_SAME_FILE for success -// GETFILE_OPEN_OTHER for successfully opening another file. +/// Try to abandon the current file and edit a new or existing file. +/// +/// @param fnum the number of the file, if zero use "ffname_arg"/"sfname_arg". +/// @param lnum the line number for the cursor in the new file (if non-zero). +/// +/// @return: +/// GETFILE_ERROR for "normal" error, +/// GETFILE_NOT_WRITTEN for "not written" error, +/// GETFILE_SAME_FILE for success +/// GETFILE_OPEN_OTHER for successfully opening another file. int getfile(int fnum, char_u *ffname_arg, char_u *sfname_arg, int setpm, linenr_T lnum, int forceit) { char_u *ffname = ffname_arg; @@ -2525,9 +2527,9 @@ int do_ecmd(int fnum, char_u *ffname, char_u *sfname, exarg_T *eap, linenr_T new // Close the link to the current buffer. This will set // oldwin->w_buffer to NULL. u_sync(false); - const bool did_decrement = close_buffer(oldwin, curbuf, - (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, - false); + const bool did_decrement + = close_buffer(oldwin, curbuf, (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, + false, false); // Autocommands may have closed the window. if (win_valid(the_curwin)) { @@ -2873,7 +2875,7 @@ int do_ecmd(int fnum, char_u *ffname, char_u *sfname, exarg_T *eap, linenr_T new redraw_curbuf_later(NOT_VALID); // redraw this buffer later } - if (p_im) { + if (p_im && (State & INSERT) == 0) { need_start_insertmode = true; } @@ -2907,9 +2909,7 @@ static void delbuf_msg(char_u *name) static int append_indent = 0; // autoindent for first line -/* - * ":insert" and ":append", also used by ":change" - */ +/// ":insert" and ":append", also used by ":change" void ex_append(exarg_T *eap) { char_u *theline; @@ -2945,7 +2945,7 @@ void ex_append(exarg_T *eap) } for (;;) { - msg_scroll = TRUE; + msg_scroll = true; need_wait_return = false; if (curbuf->b_p_ai) { if (append_indent >= 0) { @@ -3009,7 +3009,12 @@ void ex_append(exarg_T *eap) did_undo = true; ml_append(lnum, theline, (colnr_T)0, false); - appended_lines_mark(lnum + (empty ? 1 : 0), 1L); + if (empty) { + // there are no marks below the inserted lines + appended_lines(lnum, 1L); + } else { + appended_lines_mark(lnum, 1L); + } xfree(theline); ++lnum; @@ -3028,15 +3033,16 @@ void ex_append(exarg_T *eap) // "start" is set to eap->line2+1 unless that position is invalid (when // eap->line2 pointed to the end of the buffer and nothing was appended) // "end" is set to lnum when something has been appended, otherwise - // it is the same than "start" -- Acevedo - curbuf->b_op_start.lnum = (eap->line2 < curbuf->b_ml.ml_line_count) ? - eap->line2 + 1 : curbuf->b_ml.ml_line_count; - if (eap->cmdidx != CMD_append) { - --curbuf->b_op_start.lnum; + // it is the same as "start" -- Acevedo + if (!cmdmod.lockmarks) { + curbuf->b_op_start.lnum + = (eap->line2 < curbuf->b_ml.ml_line_count) ? eap->line2 + 1 : curbuf->b_ml.ml_line_count; + if (eap->cmdidx != CMD_append) { + curbuf->b_op_start.lnum--; + } + curbuf->b_op_end.lnum = (eap->line2 < lnum) ? lnum : curbuf->b_op_start.lnum; + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; } - curbuf->b_op_end.lnum = (eap->line2 < lnum) - ? lnum : curbuf->b_op_start.lnum; - curbuf->b_op_start.col = curbuf->b_op_end.col = 0; curwin->w_cursor.lnum = lnum; check_cursor_lnum(); beginline(BL_SOL | BL_FIX); @@ -3045,9 +3051,7 @@ void ex_append(exarg_T *eap) ex_no_reprint = true; } -/* - * ":change" - */ +/// ":change" void ex_change(exarg_T *eap) { linenr_T lnum; @@ -3131,7 +3135,7 @@ void ex_z(exarg_T *eap) // the number of '-' and '+' multiplies the distance if (*kind == '-' || *kind == '+') { - for (x = kind + 1; *x == *kind; ++x) { + for (x = kind + 1; *x == *kind; x++) { } } @@ -3214,26 +3218,24 @@ void ex_z(exarg_T *eap) ex_no_reprint = true; } -/* - * Check if the secure flag is set (.exrc or .vimrc in current directory). - * If so, give an error message and return TRUE. - * Otherwise, return FALSE. - */ -int check_secure(void) +/// @return true if the secure flag is set (.exrc or .vimrc in current directory) +/// and also give an error message. +/// Otherwise, return false. +bool check_secure(void) { if (secure) { secure = 2; emsg(_(e_curdir)); - return TRUE; + return true; } // In the sandbox more things are not allowed, including the things // disallowed in secure mode. if (sandbox != 0) { emsg(_(e_sandbox)); - return TRUE; + return true; } - return FALSE; + return false; } /// Previous substitute replacement string @@ -3625,12 +3627,25 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle 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. assert(sub != NULL); - if (!(sub[0] == '\\' && sub[1] == '=')) { - sub = regtilde(sub, p_magic); + + bool sub_needs_free = false; + char_u *sub_copy = NULL; + + // If the substitute pattern starts with "\=" then it's an expression. + // Make a copy, a recursive function may free it. + // Otherwise, '~' 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. + if (sub[0] == '\\' && sub[1] == '=') { + sub = vim_strsave(sub); + sub_copy = sub; + } else { + char_u *source = sub; + sub = regtilde(sub, p_magic, preview); + // When previewing, the new pattern allocated by regtilde() needs to be freed + // in this function because it will not be used or freed by regtilde() later. + sub_needs_free = preview && sub != source; } // Check for a match on each line. @@ -3852,13 +3867,22 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle prompt = xmallocz(ec + 1); memset(prompt, ' ', sc); memset(prompt + sc, '^', ec - sc + 1); - resp = (char_u *)getcmdline_prompt(NUL, prompt, 0, EXPAND_NOTHING, + resp = (char_u *)getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE); msg_putchar('\n'); xfree(prompt); if (resp != NULL) { typed = *resp; xfree(resp); + } else { + // getcmdline_prompt() returns NULL if there is no command line to return. + typed = NUL; + } + // When ":normal" runs out of characters we get + // an empty line. Use "q" to get out of the + // loop. + if (ex_normal_busy && typed == NUL) { + typed = 'q'; } } else { char_u *orig_line = NULL; @@ -4289,18 +4313,22 @@ skip: #define PUSH_PREVIEW_LINES() \ do { \ - linenr_T match_lines = current_match.end.lnum \ - - current_match.start.lnum +1; \ - if (preview_lines.subresults.size > 0) { \ - linenr_T last = kv_last(preview_lines.subresults).end.lnum; \ - if (last == current_match.start.lnum) { \ - preview_lines.lines_needed += match_lines - 1; \ + if (preview) { \ + linenr_T match_lines = current_match.end.lnum \ + - current_match.start.lnum +1; \ + if (preview_lines.subresults.size > 0) { \ + linenr_T last = kv_last(preview_lines.subresults).end.lnum; \ + if (last == current_match.start.lnum) { \ + preview_lines.lines_needed += match_lines - 1; \ + } else { \ + preview_lines.lines_needed += match_lines; \ + } \ + } else { \ + preview_lines.lines_needed += match_lines; \ } \ - } else { \ - preview_lines.lines_needed += match_lines; \ + kv_push(preview_lines.subresults, current_match); \ } \ - kv_push(preview_lines.subresults, current_match); \ - } while (0) + } while (0) // Push the match to preview_lines. PUSH_PREVIEW_LINES(); @@ -4351,10 +4379,12 @@ skip: } if (sub_nsubs > start_nsubs) { - // Set the '[ and '] marks. - curbuf->b_op_start.lnum = eap->line1; - curbuf->b_op_end.lnum = line2; - curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + if (!cmdmod.lockmarks) { + // Set the '[ and '] marks. + curbuf->b_op_start.lnum = eap->line1; + curbuf->b_op_end.lnum = line2; + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + } if (!global_busy) { // when interactive leave cursor on the match @@ -4393,6 +4423,10 @@ skip: } vim_regfree(regmatch.regprog); + xfree(sub_copy); + if (sub_needs_free) { + xfree(sub); + } // Restore the flag values, they can be used for ":&&". subflags.do_all = save_do_all; @@ -4490,22 +4524,20 @@ static void global_exe_one(char_u *const cmd, const linenr_T lnum) } } -/* - * Execute a global command of the form: - * - * g/pattern/X : execute X on all lines where pattern matches - * v/pattern/X : execute X on all lines where pattern does not match - * - * where 'X' is an EX command - * - * The command character (as well as the trailing slash) is optional, and - * is assumed to be 'p' if missing. - * - * This is implemented in two passes: first we scan the file for the pattern and - * set a mark for each line that (not) matches. Secondly we execute the command - * for each line that has a mark. This is required because after deleting - * lines we do not know where to search for the next match. - */ +/// Execute a global command of the form: +/// +/// g/pattern/X : execute X on all lines where pattern matches +/// v/pattern/X : execute X on all lines where pattern does not match +/// +/// where 'X' is an EX command +/// +/// The command character (as well as the trailing slash) is optional, and +/// is assumed to be 'p' if missing. +/// +/// This is implemented in two passes: first we scan the file for the pattern and +/// set a mark for each line that (not) matches. Secondly we execute the command +/// for each line that has a mark. This is required because after deleting +/// lines we do not know where to search for the next match. void ex_global(exarg_T *eap) { linenr_T lnum; // line number according to old situation @@ -4589,6 +4621,9 @@ void ex_global(exarg_T *eap) // a match on this line? match = vim_regexec_multi(®match, curwin, curbuf, lnum, (colnr_T)0, NULL, NULL); + if (regmatch.regprog == NULL) { + break; // re-compiling regprog failed + } if ((type == 'g' && match) || (type == 'v' && !match)) { ml_setmarked(lnum); ndone++; @@ -4718,9 +4753,7 @@ bool prepare_tagpreview(bool undo_sync) } -/* - * ":help": open a read-only window on a help file - */ +/// ":help": open a read-only window on a help file void ex_help(exarg_T *eap) { char_u *arg; @@ -4903,11 +4936,10 @@ erret: } -/* - * In an argument search for a language specifiers in the form "@xx". - * Changes the "@" to NUL if found, and returns a pointer to "xx". - * Returns NULL if not found. - */ +/// In an argument search for a language specifiers in the form "@xx". +/// Changes the "@" to NUL if found, and returns a pointer to "xx". +/// +/// @return NULL if not found. char_u *check_help_lang(char_u *arg) { int len = (int)STRLEN(arg); @@ -4973,10 +5005,8 @@ int help_heuristic(char_u *matched_string, int offset, int wrong_case) return (int)(100 * num_letters + STRLEN(matched_string) + offset); } -/* - * Compare functions for qsort() below, that checks the help heuristics number - * that has been put after the tagname by find_tags(). - */ +/// Compare functions for qsort() below, that checks the help heuristics number +/// that has been put after the tagname by find_tags(). static int help_compare(const void *s1, const void *s2) { char *p1; @@ -4987,10 +5017,10 @@ static int help_compare(const void *s1, const void *s2) return strcmp(p1, p2); } -// Find all help tags matching "arg", sort them and return in matches[], with -// the number of matches in num_matches. -// The matches will be sorted with a "best" match algorithm. -// When "keep_lang" is true try keeping the language of the current buffer. +/// Find all help tags matching "arg", sort them and return in matches[], with +/// the number of matches in num_matches. +/// The matches will be sorted with a "best" match algorithm. +/// When "keep_lang" is true try keeping the language of the current buffer. int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, bool keep_lang) { int i; @@ -5065,8 +5095,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, bool && ((arg[1] != NUL && arg[2] == NUL) || (vim_strchr((char_u *)"%_z@", arg[1]) != NULL && arg[2] != NUL))) { - STRCPY(d, "/\\\\"); - STRCPY(d + 3, arg + 1); + vim_snprintf((char *)d, IOSIZE, "/\\\\%s", arg + 1); // Check for "/\\_$", should be "/\\_\$" if (d[3] == '_' && d[4] == '$') { STRCPY(d + 4, "\\$"); @@ -5263,10 +5292,8 @@ static void prepare_help_buffer(void) set_buflisted(FALSE); } -/* - * After reading a help file: May cleanup a help buffer when syntax - * highlighting is not used. - */ +/// After reading a help file: May cleanup a help buffer when syntax +/// highlighting is not used. void fix_help_buffer(void) { linenr_T lnum; @@ -5462,17 +5489,13 @@ void fix_help_buffer(void) } } -/* - * ":exusage" - */ +/// ":exusage" void ex_exusage(exarg_T *eap) { do_cmdline_cmd("help ex-cmd-index"); } -/* - * ":viusage" - */ +/// ":viusage" void ex_viusage(exarg_T *eap) { do_cmdline_cmd("help normal-index"); @@ -5781,9 +5804,7 @@ static void helptags_cb(char_u *fname, void *cookie) do_helptags(fname, *(bool *)cookie, true); } -/* - * ":helptags" - */ +/// ":helptags" void ex_helptags(exarg_T *eap) { expand_T xpc; @@ -5812,14 +5833,12 @@ void ex_helptags(exarg_T *eap) } } -/* - * ":helpclose": Close one help window - */ +/// ":helpclose": Close one help window void ex_helpclose(exarg_T *eap) { FOR_ALL_WINDOWS_IN_TAB(win, curtab) { if (bt_help(win->w_buffer)) { - win_close(win, false); + win_close(win, false, eap->forceit); return; } } @@ -5828,7 +5847,7 @@ void ex_helpclose(exarg_T *eap) /// Tries to enter to an existing window of given buffer. If no existing buffer /// is found, creates a new split. /// -/// Returns OK/FAIL. +/// @return OK/FAIL. int sub_preview_win(buf_T *preview_buf) { if (preview_buf != NULL) { @@ -6070,9 +6089,11 @@ void ex_substitute(exarg_T *eap) /// Skip over the pattern argument of ":vimgrep /pat/[g][j]". /// Put the start of the pattern in "*s", unless "s" is NULL. -/// If "flags" is not NULL put the flags in it: VGR_GLOBAL, VGR_NOJUMP. -/// If "s" is not NULL terminate the pattern with a NUL. -/// Return a pointer to the char just past the pattern plus flags. +/// +/// @param flags if not NULL, put the flags in it: VGR_GLOBAL, VGR_NOJUMP. +/// @param s if not NULL, terminate the pattern with a NUL. +/// +/// @return a pointer to the char just past the pattern plus flags. char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags) { int c; @@ -6104,12 +6125,14 @@ char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags) p++; // Find the flags - while (*p == 'g' || *p == 'j') { + while (*p == 'g' || *p == 'j' || *p == 'f') { if (flags != NULL) { if (*p == 'g') { *flags |= VGR_GLOBAL; - } else { + } else if (*p == 'j') { *flags |= VGR_NOJUMP; + } else { + *flags |= VGR_FUZZY; } } p++; diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index 241674c24c..1c95b75001 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -21,7 +21,7 @@ // for lnum argument in do_ecmd() #define ECMD_LASTL (linenr_T)0 // use last position in loaded file -#define ECMD_LAST (linenr_T)-1 // use last position in all files +#define ECMD_LAST ((linenr_T)-1) // use last position in all files #define ECMD_ONE (linenr_T)1 // use first line /// Previous :substitute replacement string definition diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index c388373ac1..c2d40c8bb7 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2097,19 +2097,19 @@ module.cmds = { command='python', flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_python', + func='ex_python3', }, { command='pydo', flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pydo', + func='ex_pydo3', }, { command='pyfile', flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyfile', + func='ex_py3file', }, { command='py3', @@ -2139,25 +2139,25 @@ module.cmds = { command='pyx', flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyx', + func='ex_python3', }, { command='pyxdo', flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyxdo', + func='ex_pydo3', }, { command='pythonx', flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyx', + func='ex_python3', }, { command='pyxfile', flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyxfile', + func='ex_py3file', }, { command='quit', @@ -2413,7 +2413,7 @@ module.cmds = { }, { command='set', - flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, @@ -2425,13 +2425,13 @@ module.cmds = { }, { command='setglobal', - flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, { command='setlocal', - flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 449e6f7bf5..ac1d760bce 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -12,6 +12,7 @@ #include <string.h> #include "nvim/ascii.h" +#include "nvim/globals.h" #include "nvim/vim.h" #ifdef HAVE_LOCALE_H # include <locale.h> @@ -35,12 +36,12 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" +#include "nvim/os/input.h" #include "nvim/os/shell.h" #include "nvim/os_unix.h" #include "nvim/path.h" @@ -135,21 +136,6 @@ void ex_profile(exarg_T *eap) } } -void ex_python(exarg_T *eap) -{ - script_host_execute("python", eap); -} - -void ex_pyfile(exarg_T *eap) -{ - script_host_execute_file("python", eap); -} - -void ex_pydo(exarg_T *eap) -{ - script_host_do_range("python", eap); -} - void ex_ruby(exarg_T *eap) { script_host_execute("ruby", eap); @@ -450,8 +436,8 @@ static void script_dump_profile(FILE *fd) } } -/// Return true when a function defined in the current script should be -/// profiled. +/// @return true when a function defined in the current script should be +/// profiled. bool prof_def_func(void) { if (current_sctx.sc_sid > 0) { @@ -506,7 +492,7 @@ void autowrite_all(void) } } -/// Return true if buffer was changed and cannot be abandoned. +/// @return true if buffer was changed and cannot be abandoned. /// For flags use the CCGD_ values. bool check_changed(buf_T *buf, int flags) { @@ -630,7 +616,7 @@ bool dialog_close_terminal(buf_T *buf) return ret == VIM_YES; } -/// Return true if the buffer "buf" can be abandoned, either by making it +/// @return true if the buffer "buf" can be abandoned, either by making it /// hidden, autowriting it or unloading it. bool can_abandon(buf_T *buf, int forceit) { @@ -785,8 +771,8 @@ theend: return ret; } -/// Return FAIL if there is no file name, OK if there is one. -/// Give error message for FAIL. +/// @return FAIL if there is no file name, OK if there is one. +/// Give error message for FAIL. int check_fname(void) { if (curbuf->b_ffname == NULL) { @@ -798,7 +784,7 @@ int check_fname(void) /// Flush the contents of a buffer, unless it has no file name. /// -/// @return FAIL for failure, OK otherwise +/// @return FAIL for failure, OK otherwise int buf_write_all(buf_T *buf, int forceit) { int retval; @@ -822,7 +808,8 @@ int buf_write_all(buf_T *buf, int forceit) /// Isolate one argument, taking backticks. /// Changes the argument in-place, puts a NUL after it. Backticks remain. -/// Return a pointer to the start of the next argument. +/// +/// @return a pointer to the start of the next argument. static char_u *do_one_arg(char_u *str) { char_u *p; @@ -873,7 +860,7 @@ static void get_arglist(garray_T *gap, char_u *str, int escaped) /// Parse a list of arguments (file names), expand them and return in /// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'. /// -/// @return FAIL or OK. +/// @return FAIL or OK. int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig) { garray_T ga; @@ -903,7 +890,7 @@ int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig) /// 0 means before first one /// @param will_edit will edit added argument /// -/// @return FAIL for failure, OK otherwise. +/// @return FAIL for failure, OK otherwise. static int do_arglist(char_u *str, int what, int after, bool will_edit) FUNC_ATTR_NONNULL_ALL { @@ -1002,8 +989,8 @@ static void alist_check_arg_idx(void) } } -/// Return true if window "win" is editing the file at the current argument -/// index. +/// @return true if window "win" is editing the file at the current argument +/// index. static bool editing_arg_idx(win_T *win) { return !(win->w_arg_idx >= WARGCOUNT(win) @@ -1483,7 +1470,13 @@ void ex_listdo(exarg_T *eap) size_t qf_idx = qf_get_cur_idx(eap); + // Clear 'shm' to avoid that the file message overwrites + // any output from the command. + p_shm_save = vim_strsave(p_shm); + set_option_value("shm", 0L, "", 0); ex_cnext(eap); + set_option_value("shm", 0L, (char *)p_shm_save, 0); + xfree(p_shm_save); // If jumping to the next quickfix entry fails, quit here. if (qf_get_cur_idx(eap) == qf_idx) { @@ -1613,7 +1606,7 @@ void ex_compiler(exarg_T *eap) if (old_cur_comp != NULL) { old_cur_comp = vim_strsave(old_cur_comp); } - do_cmdline_cmd("command -nargs=* CompilerSet setlocal <args>"); + do_cmdline_cmd("command -nargs=* -keepscript CompilerSet setlocal <args>"); } do_unlet(S_LEN("g:current_compiler"), true); do_unlet(S_LEN("b:current_compiler"), true); @@ -1660,126 +1653,6 @@ void ex_options(exarg_T *eap) cmd_source((char_u *)SYS_OPTWIN_FILE, NULL); } -// Detect Python 3 or 2, and initialize 'pyxversion'. -void init_pyxversion(void) -{ - if (p_pyx == 0) { - if (eval_has_provider("python3")) { - p_pyx = 3; - } else if (eval_has_provider("python")) { - p_pyx = 2; - } - } -} - -// Does a file contain one of the following strings at the beginning of any -// line? -// "#!(any string)python2" => returns 2 -// "#!(any string)python3" => returns 3 -// "# requires python 2.x" => returns 2 -// "# requires python 3.x" => returns 3 -// otherwise return 0. -static int requires_py_version(char_u *filename) -{ - FILE *file; - int requires_py_version = 0; - int i, lines; - - lines = (int)p_mls; - if (lines < 0) { - lines = 5; - } - - file = os_fopen((char *)filename, "r"); - if (file != NULL) { - for (i = 0; i < lines; i++) { - if (vim_fgets(IObuff, IOSIZE, file)) { - break; - } - if (i == 0 && IObuff[0] == '#' && IObuff[1] == '!') { - // Check shebang. - if (strstr((char *)IObuff + 2, "python2") != NULL) { - requires_py_version = 2; - break; - } - if (strstr((char *)IObuff + 2, "python3") != NULL) { - requires_py_version = 3; - break; - } - } - IObuff[21] = '\0'; - if (STRCMP("# requires python 2.x", IObuff) == 0) { - requires_py_version = 2; - break; - } - if (STRCMP("# requires python 3.x", IObuff) == 0) { - requires_py_version = 3; - break; - } - } - fclose(file); - } - return requires_py_version; -} - - -// Source a python file using the requested python version. -static void source_pyx_file(exarg_T *eap, char_u *fname) -{ - exarg_T ex; - long int v = requires_py_version(fname); - - init_pyxversion(); - if (v == 0) { - // user didn't choose a preference, 'pyx' is used - v = p_pyx; - } - - // now source, if required python version is not supported show - // unobtrusive message. - if (eap == NULL) { - memset(&ex, 0, sizeof(ex)); - } else { - ex = *eap; - } - ex.arg = fname; - ex.cmd = (char_u *)(v == 2 ? "pyfile" : "pyfile3"); - - if (v == 2) { - ex_pyfile(&ex); - } else { - ex_py3file(&ex); - } -} - -// ":pyxfile {fname}" -void ex_pyxfile(exarg_T *eap) -{ - source_pyx_file(eap, eap->arg); -} - -// ":pyx" -void ex_pyx(exarg_T *eap) -{ - init_pyxversion(); - if (p_pyx == 2) { - ex_python(eap); - } else { - ex_python3(eap); - } -} - -// ":pyxdo" -void ex_pyxdo(exarg_T *eap) -{ - init_pyxversion(); - if (p_pyx == 2) { - ex_pydo(eap); - } else { - ex_pydo3(eap); - } -} - /// ":source [{fname}]" void ex_source(exarg_T *eap) { @@ -1832,7 +1705,7 @@ static bool concat_continued_line(garray_T *const ga, const int init_growsize, return false; } if (ga->ga_len > init_growsize) { - ga_set_growsize(ga, MAX(ga->ga_len, 8000)); + ga_set_growsize(ga, MIN(ga->ga_len, 8000)); } ga_concat_len(ga, (const char *)line + 1, len - 1); return true; @@ -1851,13 +1724,13 @@ linenr_T *source_breakpoint(void *cookie) return &((struct source_cookie *)cookie)->breakpoint; } -/// Return the address holding the debug tick for a source cookie. +/// @return the address holding the debug tick for a source cookie. int *source_dbg_tick(void *cookie) { return &((struct source_cookie *)cookie)->dbg_tick; } -/// Return the nesting level for a source cookie. +/// @return the nesting level for a source cookie. int source_level(void *cookie) { return ((struct source_cookie *)cookie)->level; @@ -1922,7 +1795,8 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) /// /// @param name File name of the script. NULL for anonymous :source. /// @param[out] sid_out SID of the new item. -/// @return pointer to the created script item. +/// +/// @return pointer to the created script item. scriptitem_T *new_script_item(char_u *const name, scid_T *const sid_out) { static scid_T last_current_SID = 0; @@ -1930,7 +1804,7 @@ scriptitem_T *new_script_item(char_u *const name, scid_T *const sid_out) if (sid_out != NULL) { *sid_out = sid; } - ga_grow(&script_items, (int)(sid - script_items.ga_len)); + ga_grow(&script_items, sid - script_items.ga_len); while (script_items.ga_len < sid) { script_items.ga_len++; SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; @@ -1957,7 +1831,9 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char sourcing_lnum = 0; const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_sid = SID_STR; + if (current_sctx.sc_sid != SID_LUA) { + current_sctx.sc_sid = SID_STR; + } current_sctx.sc_seq = 0; current_sctx.sc_lnum = save_sourcing_lnum; funccal_entry_T entry; @@ -1984,7 +1860,7 @@ static void cmd_source_buffer(const exarg_T *const eap) for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) { // Adjust growsize to current length to speed up concatenating many lines. if (ga.ga_len > 400) { - ga_set_growsize(&ga, MAX(ga.ga_len, 8000)); + ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); } ga_concat(&ga, (char *)ml_get(curr_lnum)); ga_append(&ga, NL); @@ -2028,7 +1904,7 @@ int do_source_str(const char *cmd, const char *traceback_name) /// @param check_other check for .vimrc and _vimrc /// @param is_vimrc DOSO_ value /// -/// @return FAIL if file could not be opened, OK otherwise +/// @return FAIL if file could not be opened, OK otherwise int do_source(char *fname, int check_other, int is_vimrc) { struct source_cookie cookie; @@ -2038,7 +1914,6 @@ int do_source(char *fname, int check_other, int is_vimrc) char_u *fname_exp; char_u *firstline = NULL; int retval = FAIL; - static int last_current_SID_seq = 0; int save_debug_break_level = debug_break_level; scriptitem_T *si = NULL; proftime_T wait_start; @@ -2086,7 +1961,7 @@ int do_source(char *fname, int check_other, int is_vimrc) } if (cookie.fp == NULL) { - if (p_verbose > 0) { + if (p_verbose > 1) { verbose_enter(); if (sourcing_name == NULL) { smsg(_("could not source \"%s\""), fname); @@ -2162,37 +2037,8 @@ int do_source(char *fname, int check_other, int is_vimrc) funccal_entry_T funccalp_entry; save_funccal(&funccalp_entry); - // Check if this script was sourced before to finds its SID. - // If it's new, generate a new SID. - // Always use a new sequence number. const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_seq = ++last_current_SID_seq; - current_sctx.sc_lnum = 0; - FileID file_id; - bool file_id_ok = os_fileid((char *)fname_exp, &file_id); - assert(script_items.ga_len >= 0); - for (current_sctx.sc_sid = script_items.ga_len; current_sctx.sc_sid > 0; - current_sctx.sc_sid--) { - si = &SCRIPT_ITEM(current_sctx.sc_sid); - // Compare dev/ino when possible, it catches symbolic links. - // Also compare file names, the inode may change when the file was edited. - bool file_id_equal = file_id_ok && si->file_id_valid - && os_fileid_equal(&(si->file_id), &file_id); - if (si->sn_name != NULL - && (file_id_equal || fnamecmp(si->sn_name, fname_exp) == 0)) { - break; - } - } - if (current_sctx.sc_sid == 0) { - si = new_script_item(fname_exp, ¤t_sctx.sc_sid); - fname_exp = vim_strsave(si->sn_name); // used for autocmd - if (file_id_ok) { - si->file_id_valid = true; - si->file_id = file_id; - } else { - si->file_id_valid = false; - } - } + si = get_current_script_id(fname_exp, ¤t_sctx); if (l_do_profiling == PROF_YES) { bool forceit = false; @@ -2225,16 +2071,14 @@ int do_source(char *fname, int check_other, int is_vimrc) firstline = p; } - if (path_with_extension((const char *)fname, "lua")) { - // TODO(shadmansaleh): Properly handle :verbose for lua - // For now change currennt_sctx before sourcing lua files - // So verbose doesn't say everything was done in line 1 since we don't know + if (path_with_extension((const char *)fname_exp, "lua")) { const sctx_T current_sctx_backup = current_sctx; const linenr_T sourcing_lnum_backup = sourcing_lnum; + current_sctx.sc_sid = SID_LUA; current_sctx.sc_lnum = 0; sourcing_lnum = 0; // Source the file as lua - nlua_exec_file((const char *)fname); + nlua_exec_file((const char *)fname_exp); current_sctx = current_sctx_backup; sourcing_lnum = sourcing_lnum_backup; } else { @@ -2307,6 +2151,52 @@ theend: } +/// Check if fname was sourced before to finds its SID. +/// If it's new, generate a new SID. +/// +/// @param[in] fname file path of script +/// @param[out] ret_sctx sctx of this script +scriptitem_T *get_current_script_id(char_u *fname, sctx_T *ret_sctx) +{ + static int last_current_SID_seq = 0; + + sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq, + .sc_lnum = 0, + .sc_sid = 0 }; + FileID file_id; + scriptitem_T *si = NULL; + + bool file_id_ok = os_fileid((char *)fname, &file_id); + assert(script_items.ga_len >= 0); + for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; + script_sctx.sc_sid--) { + si = &SCRIPT_ITEM(script_sctx.sc_sid); + // Compare dev/ino when possible, it catches symbolic links. + // Also compare file names, the inode may change when the file was edited. + bool file_id_equal = file_id_ok && si->file_id_valid + && os_fileid_equal(&(si->file_id), &file_id); + if (si->sn_name != NULL + && (file_id_equal || fnamecmp(si->sn_name, fname) == 0)) { + break; + } + } + if (script_sctx.sc_sid == 0) { + si = new_script_item(vim_strsave(fname), &script_sctx.sc_sid); + if (file_id_ok) { + si->file_id_valid = true; + si->file_id = file_id; + } else { + si->file_id_valid = false; + } + } + if (ret_sctx != NULL) { + *ret_sctx = script_sctx; + } + + return si; +} + + /// ":scriptnames" void ex_scriptnames(exarg_T *eap) { @@ -2323,9 +2213,13 @@ void ex_scriptnames(exarg_T *eap) for (int i = 1; i <= script_items.ga_len && !got_int; i++) { if (SCRIPT_ITEM(i).sn_name != NULL) { - home_replace(NULL, SCRIPT_ITEM(i).sn_name, - NameBuff, MAXPATHL, true); - smsg("%3d: %s", i, NameBuff); + home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true); + vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff); + if (!message_filtered(IObuff)) { + msg_putchar('\n'); + msg_outtrans(IObuff); + line_breakcheck(); + } } } } @@ -2729,9 +2623,9 @@ void do_finish(exarg_T *eap, int reanimate) } -/// Return true when a sourced file had the ":finish" command: Don't give error -/// message for missing ":endif". -/// Return false when not sourcing a file. +/// @return true when a sourced file had the ":finish" command: Don't give error +/// message for missing ":endif". +/// false when not sourcing a file. bool source_finished(LineGetter fgetline, void *cookie) { return getline_equal(fgetline, cookie, getsourceline) @@ -2768,8 +2662,8 @@ static char *get_locale_val(int what) } #endif -// Return true when "lang" starts with a valid language name. -// Rejects NULL, empty string, "C", "C.UTF-8" and others. +/// @return true when "lang" starts with a valid language name. +/// Rejects NULL, empty string, "C", "C.UTF-8" and others. static bool is_valid_mess_lang(char *lang) { return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); @@ -2872,11 +2766,10 @@ void set_lang_var(void) } #ifdef HAVE_WORKING_LIBINTL -/// + /// ":language": Set the language (locale). /// /// @param eap -/// void ex_language(exarg_T *eap) { char *loc; @@ -2985,8 +2878,9 @@ static char_u **locales = NULL; // Array of all available locales # ifndef WIN32 static bool did_init_locales = false; -/// Return an array of strings for all available locales + NULL for the -/// last element. Return NULL in case of error. +/// @return an array of strings for all available locales + NULL for the +/// last element or, +/// NULL in case of error. static char_u **find_locales(void) { garray_T locales_ga; diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index ea899b660b..39dfc1b94c 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include <stdint.h> +#include "nvim/eval/typval.h" #include "nvim/normal.h" #include "nvim/pos.h" // for linenr_T #include "nvim/regexp_defs.h" @@ -61,6 +62,7 @@ // current buffer is locked #define EX_MODIFY 0x100000 // forbidden in non-'modifiable' buffer #define EX_FLAGS 0x200000 // allow flags after count in argument +#define EX_KEEPSCRIPT 0x4000000 // keep sctx of where command was invoked #define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed #define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file #define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed @@ -85,11 +87,40 @@ typedef struct exarg exarg_T; // behavior for bad character, "++bad=" argument #define BAD_REPLACE '?' // replace it with '?' (default) -#define BAD_KEEP -1 // leave it -#define BAD_DROP -2 // erase it +#define BAD_KEEP (-1) // leave it +#define BAD_DROP (-2) // erase it typedef void (*ex_func_T)(exarg_T *eap); +// NOTE: These possible could be removed and changed so that +// Callback could take a "command" style string, and simply +// execute that (instead of it being a function). +// +// But it's still a bit weird to do that. +// +// Another option would be that we just make a callback reference to +// "execute($INPUT)" or something like that, so whatever the user +// sends in via autocmds is just executed via this. +// +// However, that would probably have some performance cost (probably +// very marginal, but still some cost either way). +typedef enum { + CALLABLE_NONE, + CALLABLE_EX, + CALLABLE_CB, +} AucmdExecutableType; + +typedef struct aucmd_executable_t AucmdExecutable; +struct aucmd_executable_t { + AucmdExecutableType type; + union { + char_u *cmd; + Callback cb; + } callable; +}; + +#define AUCMD_EXECUTABLE_INIT { .type = CALLABLE_NONE } + typedef char_u *(*LineGetter)(int, void *, int, bool); /// Structure for command definition. @@ -192,6 +223,7 @@ struct expand { int xp_context; // type of expansion size_t xp_pattern_len; // bytes in xp_pattern before cursor char_u *xp_arg; // completion function + LuaRef xp_luaref; // Ref to Lua completion function sctx_T xp_script_ctx; // SCTX for completion function int xp_backslash; // one of the XP_BS_ values #ifndef BACKSLASH_IN_FILENAME diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 34c3b3889e..cbfe6e3789 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -38,17 +38,19 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/hardcopy.h" +#include "nvim/highlight_group.h" #include "nvim/if_cscope.h" +#include "nvim/input.h" #include "nvim/keymap.h" #include "nvim/lua/executor.h" #include "nvim/main.h" +#include "nvim/match.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -67,6 +69,7 @@ #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/spellfile.h" +#include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" @@ -77,26 +80,14 @@ #include "nvim/vim.h" #include "nvim/window.h" +static char *e_no_such_user_defined_command_str = N_("E184: No such user-defined command: %s"); +static char *e_no_such_user_defined_command_in_current_buffer_str + = N_("E1237: No such user-defined command in current buffer: %s"); + static int quitmore = 0; static bool ex_pressedreturn = false; -typedef struct ucmd { - char_u *uc_name; // The command name - uint32_t uc_argt; // The argument type - char_u *uc_rep; // The command's replacement string - long uc_def; // The default value for a range/count - int uc_compl; // completion type - cmd_addr_T uc_addr_type; // The command's address type - sctx_T uc_script_ctx; // SCTX where the command was defined - char_u *uc_compl_arg; // completion argument if any -} ucmd_T; - -#define UC_BUFFER 1 // -buffer: local to current buffer - -static garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL }; - -#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i]) -#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i]) +garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL }; // Whether a command index indicates a user command. #define IS_USER_CMDIDX(idx) ((int)(idx) < 0) @@ -196,6 +187,7 @@ void do_exmode(void) exmode_active = true; State = NORMAL; + may_trigger_modechanged(); // When using ":global /pat/ visual" and then "Q" we return to continue // the :global command. @@ -259,8 +251,9 @@ void do_exmode(void) msg_scroll = save_msg_scroll; } -// Print the executed command for when 'verbose' is set. -// When "lnum" is 0 only print the command. +/// Print the executed command for when 'verbose' is set. +/// +/// @param lnum if 0, only print the command. static void msg_verbose_cmd(linenr_T lnum, char_u *cmd) FUNC_ATTR_NONNULL_ALL { @@ -280,9 +273,7 @@ static void msg_verbose_cmd(linenr_T lnum, char_u *cmd) no_wait_return--; } -/* - * Execute a simple command line. Used for translated commands like "*". - */ +/// Execute a simple command line. Used for translated commands like "*". int do_cmdline_cmd(const char *cmd) { return do_cmdline((char_u *)cmd, NULL, NULL, @@ -434,7 +425,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) && cstack.cs_idx < 0 && !(getline_is_func && func_has_abort(real_cookie))) { - did_emsg = FALSE; + did_emsg = false; } /* @@ -818,7 +809,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) // of interrupts or errors to exceptions, and ensure that no more // commands are executed. if (current_exception) { - void *p = NULL; + char *p = NULL; char_u *saved_sourcing_name; int saved_sourcing_lnum; struct msglist *messages = NULL; @@ -835,7 +826,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) vim_snprintf((char *)IObuff, IOSIZE, _("E605: Exception not caught: %s"), current_exception->value); - p = vim_strsave(IObuff); + p = (char *)vim_strsave(IObuff); break; case ET_ERROR: messages = current_exception->messages; @@ -961,9 +952,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) return retval; } -/* - * Obtain a line when inside a ":while" or ":for" loop. - */ +/// Obtain a line when inside a ":while" or ":for" loop. static char_u *get_loop_line(int c, void *cookie, int indent, bool do_concat) { struct loop_cookie *cp = (struct loop_cookie *)cookie; @@ -995,9 +984,7 @@ static char_u *get_loop_line(int c, void *cookie, int indent, bool do_concat) return vim_strsave(wp->line); } -/* - * Store a line in "gap" so that a ":while" loop can execute it again. - */ +/// Store a line in "gap" so that a ":while" loop can execute it again. static void store_loop_line(garray_T *gap, char_u *line) { wcmd_T *p = GA_APPEND_VIA_PTR(wcmd_T, gap); @@ -1047,11 +1034,10 @@ void *getline_cookie(LineGetter fgetline, void *cookie) return cp; } -/* - * Helper function to apply an offset for buffer commands, i.e. ":bdelete", - * ":bwipeout", etc. - * Returns the buffer number. - */ +/// Helper function to apply an offset for buffer commands, i.e. ":bdelete", +/// ":bwipeout", etc. +/// +/// @return the buffer number. static int compute_buffer_local_count(int addr_type, int lnum, int offset) { buf_T *buf; @@ -1093,8 +1079,8 @@ static int compute_buffer_local_count(int addr_type, int lnum, int offset) return buf->b_fnum; } -// Return the window number of "win". -// When "win" is NULL return the number of windows. +/// @return the window number of "win" or, +/// the number of windows if "win" is NULL static int current_win_nr(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -1262,6 +1248,7 @@ static char_u *do_one_cmd(char_u **cmdlinep, int flags, cstack_T *cstack, LineGe const int save_msg_scroll = msg_scroll; cmdmod_T save_cmdmod; const int save_reg_executing = reg_executing; + const bool save_pending_end_reg_executing = pending_end_reg_executing; char_u *cmd; memset(&ea, 0, sizeof(ea)); @@ -1772,7 +1759,9 @@ static char_u *do_one_cmd(char_u **cmdlinep, int flags, cstack_T *cstack, LineGe ea.regname = *ea.arg++; // for '=' register: accept the rest of the line as an expression if (ea.arg[-1] == '=' && ea.arg[0] != NUL) { - set_expr_line(vim_strsave(ea.arg)); + if (!ea.skip) { + set_expr_line(vim_strsave(ea.arg)); + } ea.arg += STRLEN(ea.arg); } ea.arg = skipwhite(ea.arg); @@ -2030,6 +2019,7 @@ doend: undo_cmdmod(&ea, save_msg_scroll); cmdmod = save_cmdmod; reg_executing = save_reg_executing; + pending_end_reg_executing = save_pending_end_reg_executing; if (ea.did_sandbox) { sandbox--; @@ -2044,18 +2034,32 @@ doend: return ea.nextcmd; } -// Parse and skip over command modifiers: -// - update eap->cmd -// - store flags in "cmdmod". -// - Set ex_pressedreturn for an empty command line. -// - set msg_silent for ":silent" -// - set 'eventignore' to "all" for ":noautocmd" -// - set p_verbose for ":verbose" -// - Increment "sandbox" for ":sandbox" -// When "skip_only" is true the global variables are not changed, except for -// "cmdmod". -// Return FAIL when the command is not to be executed. -// May set "errormsg" to an error message. +static char ex_error_buf[MSG_BUF_LEN]; + +/// @return an error message with argument included. +/// Uses a static buffer, only the last error will be kept. +/// "msg" will be translated, caller should use N_(). +char *ex_errmsg(const char *const msg, const char_u *const arg) + FUNC_ATTR_NONNULL_ALL +{ + vim_snprintf(ex_error_buf, MSG_BUF_LEN, _(msg), arg); + return ex_error_buf; +} + +/// 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" +/// +/// @param skip_only if true, the global variables are not changed, except for +/// "cmdmod". +/// @param[out] errormsg potential error message. +/// +/// @return FAIL when the command is not to be executed. int parse_command_modifiers(exarg_T *eap, char **errormsg, bool skip_only) { char_u *p; @@ -2314,7 +2318,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, bool skip_only) return OK; } -// Undo and free contents of "cmdmod". +/// Undo and free contents of "cmdmod". static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll) FUNC_ATTR_NONNULL_ALL { @@ -2353,9 +2357,10 @@ static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll) } -// 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. +/// 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 **errormsg, bool silent) FUNC_ATTR_NONNULL_ALL { @@ -2547,11 +2552,9 @@ int checkforcmd(char_u **pp, char *cmd, int len) return FALSE; } -/* - * Append "cmd" to the error message in IObuff. - * Takes care of limiting the length and handling 0xa0, which would be - * invisible otherwise. - */ +/// Append "cmd" to the error message in IObuff. +/// Takes care of limiting the length and handling 0xa0, which would be +/// invisible otherwise. static void append_command(char_u *cmd) { char_u *s = cmd; @@ -2571,11 +2574,12 @@ static void append_command(char_u *cmd) *d = NUL; } -// Find an Ex command by its name, either built-in or user. -// Start of the name can be found at eap->cmd. -// Sets eap->cmdidx and returns a pointer to char after the command name. -// "full" is set to TRUE if the whole command name matched. -// Returns NULL for an ambiguous user command. +/// Find an Ex command by its name, either built-in or user. +/// Start of the name can be found at eap->cmd. +/// Sets eap->cmdidx and returns a pointer to char after the command name. +/// "full" is set to TRUE if the whole command name matched. +/// +/// @return NULL for an ambiguous user command. static char_u *find_command(exarg_T *eap, int *full) FUNC_ATTR_NONNULL_ARG(1) { @@ -2711,12 +2715,10 @@ static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int * bool amb_local = false; // Found ambiguous buffer-local command, // only full match global is accepted. - /* - * Look for buffer-local user commands first, then global ones. - */ - gap = &curbuf->b_ucmds; + // Look for buffer-local user commands first, then global ones. + gap = &prevwin_curwin()->w_buffer->b_ucmds; for (;;) { - for (j = 0; j < gap->ga_len; ++j) { + for (j = 0; j < gap->ga_len; j++) { uc = USER_CMD_GA(gap, j); cp = eap->cmd; np = uc->uc_name; @@ -2759,6 +2761,7 @@ static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int * *complp = uc->uc_compl; } if (xp != NULL) { + xp->xp_luaref = uc->uc_compl_luaref; xp->xp_arg = uc->uc_compl_arg; xp->xp_script_ctx = uc->uc_script_ctx; xp->xp_script_ctx.sc_lnum += sourcing_lnum; @@ -2830,10 +2833,8 @@ static struct cmdmod { { "vertical", 4, false }, }; -/* - * Return length of a command modifier (including optional count). - * Return zero when it's not a modifier. - */ +/// @return length of a command modifier (including optional count) or, +/// zero when it's not a modifier. int modifier_len(char_u *cmd) { char_u *p = cmd; @@ -2844,7 +2845,7 @@ int modifier_len(char_u *cmd) for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) { int j; for (j = 0; p[j] != NUL; j++) { - if (p[j] != cmdmods[i].name[j]) { + if (p[j] != (char_u)cmdmods[i].name[j]) { break; } } @@ -2857,11 +2858,9 @@ int modifier_len(char_u *cmd) return 0; } -/* - * Return > 0 if an Ex command "name" exists. - * Return 2 if there is an exact match. - * Return 3 if there is an ambiguous match. - */ +/// @return > 0 if an Ex command "name" exists or, +/// 2 if there is an exact match or, +/// 3 if there is an ambiguous match. int cmd_exists(const char *const name) { exarg_T ea; @@ -2898,6 +2897,35 @@ int cmd_exists(const char *const name) return ea.cmdidx == CMD_SIZE ? 0 : (full ? 2 : 1); } +/// "fullcommand" function +void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + exarg_T ea; + char_u *name = argvars[0].vval.v_string; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (name == NULL) { + return; + } + + while (*name == ':') { + name++; + } + name = skip_range(name, NULL); + + ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; + ea.cmdidx = (cmdidx_T)0; + char_u *p = find_command(&ea, NULL); + if (p == NULL || ea.cmdidx == CMD_SIZE) { + return; + } + + rettv->vval.v_string = vim_strsave(IS_USER_CMDIDX(ea.cmdidx) + ? get_user_command_name(ea.useridx, ea.cmdidx) + : cmdnames[ea.cmdidx].cmd_name); +} + /// This is all pretty much copied from do_one_cmd(), with all the extra stuff /// we don't need/want deleted. Maybe this could be done better if we didn't /// repeat all this stuff. The only problem is that they may not stay @@ -4001,8 +4029,9 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in // When '/' or '?' follows another address, start from // there. - if (lnum != MAXLNUM) { - curwin->w_cursor.lnum = lnum; + if (lnum > 0 && lnum != MAXLNUM) { + curwin->w_cursor.lnum + = lnum > curbuf->b_ml.ml_line_count ? curbuf->b_ml.ml_line_count : lnum; } // Start a forward search at the end of the line (unless @@ -4121,7 +4150,11 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in if (!ascii_isdigit(*cmd)) { // '+' is '+1', but '+0' is not '+1' n = 1; } else { - n = getdigits(&cmd, true, 0); + n = getdigits(&cmd, false, MAXLNUM); + if (n == MAXLNUM) { + emsg(_(e_line_number_out_of_range)); + goto error; + } } if (addr_type == ADDR_TABS_RELATIVE) { @@ -4140,6 +4173,10 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in if (i == '-') { lnum -= n; } else { + if (n >= LONG_MAX - lnum) { + emsg(_(e_line_number_out_of_range)); + goto error; + } lnum += n; } } @@ -4151,9 +4188,7 @@ error: return lnum; } -/* - * Get flags from an Ex command argument. - */ +/// Get flags from an Ex command argument. static void get_flags(exarg_T *eap) { while (vim_strchr((char_u *)"lp#", *eap->arg) != NULL) { @@ -4188,10 +4223,9 @@ static void ex_script_ni(exarg_T *eap) } } -/* - * Check range in Ex command for validity. - * Return NULL when valid, error message when invalid. - */ +/// Check range in Ex command for validity. +/// +/// @return NULL when valid, error message when invalid. static char *invalid_range(exarg_T *eap) { buf_T *buf; @@ -4277,9 +4311,7 @@ static char *invalid_range(exarg_T *eap) return NULL; } -/* - * Correct the range for zero line number, if required. - */ +/// Correct the range for zero line number, if required. static void correct_range(exarg_T *eap) { if (!(eap->argt & EX_ZEROR)) { // zero in range not allowed @@ -4293,10 +4325,8 @@ static void correct_range(exarg_T *eap) } -/* - * For a ":vimgrep" or ":vimgrepadd" command return a pointer past the - * pattern. Otherwise return eap->arg. - */ +/// For a ":vimgrep" or ":vimgrepadd" command return a pointer past the +/// pattern. Otherwise return eap->arg. static char_u *skip_grep_pat(exarg_T *eap) { char_u *p = eap->arg; @@ -4313,10 +4343,8 @@ static char_u *skip_grep_pat(exarg_T *eap) return p; } -/* - * For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option - * in the command line, so that things like % get expanded. - */ +/// For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option +/// in the command line, so that things like % get expanded. static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) { char_u *new_cmdline; @@ -4358,7 +4386,7 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) ++i; } len = (int)STRLEN(p); - new_cmdline = xmalloc(STRLEN(program) + i * (len - 2) + 1); + new_cmdline = xmalloc(STRLEN(program) + (size_t)i * (len - 2) + 1); ptr = new_cmdline; while ((pos = (char_u *)strstr((char *)program, "$*")) != NULL) { i = (int)(pos - program); @@ -4384,9 +4412,10 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) return p; } -// Expand file name in Ex command argument. -// When an error is detected, "errormsgp" is set to a non-NULL pointer. -// Return FAIL for failure, OK otherwise. +/// Expand file name in Ex command argument. +/// When an error is detected, "errormsgp" is set to a non-NULL pointer. +/// +/// @return FAIL for failure, OK otherwise. int expand_filename(exarg_T *eap, char_u **cmdlinep, char **errormsgp) { int has_wildcards; // need to expand wildcards @@ -4554,13 +4583,12 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char **errormsgp) return OK; } -/* - * Replace part of the command line, keeping eap->cmd, eap->arg and - * eap->nextcmd correct. - * "src" points to the part that is to be replaced, of length "srclen". - * "repl" is the replacement string. - * Returns a pointer to the character after the replaced string. - */ +/// Replace part of the command line, keeping eap->cmd, eap->arg and +/// eap->nextcmd correct. +/// "src" points to the part that is to be replaced, of length "srclen". +/// "repl" is the replacement string. +/// +/// @return a pointer to the character after the replaced string. static char_u *repl_cmdline(exarg_T *eap, char_u *src, size_t srclen, char_u *repl, char_u **cmdlinep) { @@ -4606,9 +4634,7 @@ static char_u *repl_cmdline(exarg_T *eap, char_u *src, size_t srclen, char_u *re return src; } -/* - * Check for '|' to separate commands and '"' to start comments. - */ +/// Check for '|' to separate commands and '"' to start comments. void separate_nextcmd(exarg_T *eap) { char_u *p; @@ -4663,9 +4689,7 @@ void separate_nextcmd(exarg_T *eap) } } -/* - * get + command from ex argument - */ +/// get + command from ex argument static char_u *getargcmd(char_u **argp) { char_u *arg = *argp; @@ -4722,10 +4746,9 @@ int get_bad_opt(const char_u *p, exarg_T *eap) return OK; } -/* - * Get "++opt=arg" argument. - * Return FAIL or OK. - */ +/// Get "++opt=arg" argument. +/// +/// @return FAIL or OK. static int getargopt(exarg_T *eap) { char_u *arg = eap->arg + 2; @@ -4805,8 +4828,9 @@ static int getargopt(exarg_T *eap) } /// Handle the argument for a tabpage related ex command. -/// Returns a tabpage number. /// When an error is encountered then eap->errmsg is set. +/// +/// @return a tabpage number. static int get_tabpage_arg(exarg_T *eap) { int tab_number = 0; @@ -4833,7 +4857,13 @@ static int get_tabpage_arg(exarg_T *eap) if (STRCMP(p, "$") == 0) { tab_number = LAST_TAB_NR; } else if (STRCMP(p, "#") == 0) { - tab_number = tabpage_index(lastused_tabpage); + if (valid_tabpage(lastused_tabpage)) { + tab_number = tabpage_index(lastused_tabpage); + } else { + eap->errmsg = ex_errmsg(e_invargval, eap->arg); + tab_number = 0; + goto theend; + } } else if (p == p_save || *p_save == '-' || *p != NUL || tab_number > LAST_TAB_NR) { // No numbers as argument. @@ -4889,17 +4919,13 @@ theend: return tab_number; } -/* - * ":abbreviate" and friends. - */ +/// ":abbreviate" and friends. static void ex_abbreviate(exarg_T *eap) { do_exmap(eap, TRUE); // almost the same as mapping } -/* - * ":map" and friends. - */ +/// ":map" and friends. static void ex_map(exarg_T *eap) { /* @@ -4914,25 +4940,19 @@ static void ex_map(exarg_T *eap) do_exmap(eap, FALSE); } -/* - * ":unmap" and friends. - */ +/// ":unmap" and friends. static void ex_unmap(exarg_T *eap) { do_exmap(eap, FALSE); } -/* - * ":mapclear" and friends. - */ +/// ":mapclear" and friends. static void ex_mapclear(exarg_T *eap) { map_clear_mode(eap->cmd, eap->arg, eap->forceit, false); } -/* - * ":abclear" and friends. - */ +/// ":abclear" and friends. static void ex_abclear(exarg_T *eap) { map_clear_mode(eap->cmd, eap->arg, true, true); @@ -4952,9 +4972,7 @@ static void ex_autocmd(exarg_T *eap) } } -/* - * ":doautocmd": Apply the automatic commands to the current buffer. - */ +/// ":doautocmd": Apply the automatic commands to the current buffer. static void ex_doautocmd(exarg_T *eap) { char_u *arg = eap->arg; @@ -4968,11 +4986,9 @@ static void ex_doautocmd(exarg_T *eap) } } -/* - * :[N]bunload[!] [N] [bufname] unload buffer - * :[N]bdelete[!] [N] [bufname] delete buffer from buffer list - * :[N]bwipeout[!] [N] [bufname] delete buffer really - */ +/// :[N]bunload[!] [N] [bufname] unload buffer +/// :[N]bdelete[!] [N] [bufname] delete buffer from buffer list +/// :[N]bwipeout[!] [N] [bufname] delete buffer really static void ex_bunload(exarg_T *eap) { eap->errmsg = do_bufdel(eap->cmdidx == CMD_bdelete ? DOBUF_DEL @@ -4982,10 +4998,8 @@ static void ex_bunload(exarg_T *eap) eap->addr_count, (int)eap->line1, (int)eap->line2, eap->forceit); } -/* - * :[N]buffer [N] to buffer N - * :[N]sbuffer [N] to buffer N - */ +/// :[N]buffer [N] to buffer N +/// :[N]sbuffer [N] to buffer N static void ex_buffer(exarg_T *eap) { if (*eap->arg) { @@ -5002,10 +5016,8 @@ static void ex_buffer(exarg_T *eap) } } -/* - * :[N]bmodified [N] to next mod. buffer - * :[N]sbmodified [N] to next mod. buffer - */ +/// :[N]bmodified [N] to next mod. buffer +/// :[N]sbmodified [N] to next mod. buffer static void ex_bmodified(exarg_T *eap) { goto_buffer(eap, DOBUF_MOD, FORWARD, (int)eap->line2); @@ -5014,10 +5026,8 @@ static void ex_bmodified(exarg_T *eap) } } -/* - * :[N]bnext [N] to next buffer - * :[N]sbnext [N] split and to next buffer - */ +/// :[N]bnext [N] to next buffer +/// :[N]sbnext [N] split and to next buffer static void ex_bnext(exarg_T *eap) { goto_buffer(eap, DOBUF_CURRENT, FORWARD, (int)eap->line2); @@ -5026,12 +5036,10 @@ static void ex_bnext(exarg_T *eap) } } -/* - * :[N]bNext [N] to previous buffer - * :[N]bprevious [N] to previous buffer - * :[N]sbNext [N] split and to previous buffer - * :[N]sbprevious [N] split and to previous buffer - */ +/// :[N]bNext [N] to previous buffer +/// :[N]bprevious [N] to previous buffer +/// :[N]sbNext [N] split and to previous buffer +/// :[N]sbprevious [N] split and to previous buffer static void ex_bprevious(exarg_T *eap) { goto_buffer(eap, DOBUF_CURRENT, BACKWARD, (int)eap->line2); @@ -5040,12 +5048,10 @@ static void ex_bprevious(exarg_T *eap) } } -/* - * :brewind to first buffer - * :bfirst to first buffer - * :sbrewind split and to first buffer - * :sbfirst split and to first buffer - */ +/// :brewind to first buffer +/// :bfirst to first buffer +/// :sbrewind split and to first buffer +/// :sbfirst split and to first buffer static void ex_brewind(exarg_T *eap) { goto_buffer(eap, DOBUF_FIRST, FORWARD, 0); @@ -5054,10 +5060,8 @@ static void ex_brewind(exarg_T *eap) } } -/* - * :blast to last buffer - * :sblast split and to last buffer - */ +/// :blast to last buffer +/// :sblast split and to last buffer static void ex_blast(exarg_T *eap) { goto_buffer(eap, DOBUF_LAST, BACKWARD, 0); @@ -5071,10 +5075,8 @@ int ends_excmd(int c) FUNC_ATTR_CONST return c == NUL || c == '|' || c == '"' || c == '\n'; } -/* - * Return the next command, after the first '|' or '\n'. - * Return NULL if not found. - */ +/// @return the next command, after the first '|' or '\n' or, +/// NULL if not found. char_u *find_nextcmd(const char_u *p) { while (*p != '|' && *p != '\n') { @@ -5087,7 +5089,8 @@ char_u *find_nextcmd(const char_u *p) } /// Check if *p is a separator between Ex commands, skipping over white space. -/// Return NULL if it isn't, the following character if it is. +/// +/// @return NULL if it isn't, the following character if it is. char_u *check_nextcmd(char_u *p) { char_u *s = skipwhite(p); @@ -5103,9 +5106,10 @@ char_u *check_nextcmd(char_u *p) /// - and this is the last window /// - and forceit not used /// - and not repeated twice on a row -/// @return FAIL and give error message if 'message' TRUE, return OK otherwise /// /// @param message when FALSE check only, no messages +/// +/// @return FAIL and give error message if 'message' TRUE, return OK otherwise static int check_more(int message, bool forceit) { int n = ARGCOUNT - curwin->w_arg_idx - 1; @@ -5133,19 +5137,36 @@ static int check_more(int message, bool forceit) return OK; } -/* - * Function given to ExpandGeneric() to obtain the list of command names. - */ +/// Function given to ExpandGeneric() to obtain the list of command names. char_u *get_command_name(expand_T *xp, int idx) { if (idx >= CMD_SIZE) { - return get_user_command_name(idx); + return expand_user_command_name(idx); } return cmdnames[idx].cmd_name; } -static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t argt, long def, - int flags, int compl, char_u *compl_arg, cmd_addr_T addr_type, bool force) +/// Check for a valid user command name +/// +/// If the given {name} is valid, then a pointer to the end of the valid name is returned. +/// Otherwise, returns NULL. +char *uc_validate_name(char *name) +{ + if (ASCII_ISALPHA(*name)) { + while (ASCII_ISALNUM(*name)) { + name++; + } + } + if (!ends_excmd(*name) && !ascii_iswhite(*name)) { + return NULL; + } + + return name; +} + +int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t argt, long def, int flags, + int compl, char_u *compl_arg, LuaRef compl_luaref, cmd_addr_T addr_type, + LuaRef luaref, bool force) FUNC_ATTR_NONNULL_ARG(1, 3) { ucmd_T *cmd = NULL; @@ -5199,6 +5220,8 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t a XFREE_CLEAR(cmd->uc_rep); XFREE_CLEAR(cmd->uc_compl_arg); + NLUA_CLEAR_REF(cmd->uc_luaref); + NLUA_CLEAR_REF(cmd->uc_compl_luaref); break; } @@ -5228,14 +5251,19 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t a cmd->uc_compl = compl; cmd->uc_script_ctx = current_sctx; cmd->uc_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&cmd->uc_script_ctx); cmd->uc_compl_arg = compl_arg; + cmd->uc_compl_luaref = compl_luaref; cmd->uc_addr_type = addr_type; + cmd->uc_luaref = luaref; return OK; fail: xfree(rep_buf); xfree(compl_arg); + NLUA_CLEAR_REF(luaref); + NLUA_CLEAR_REF(compl_luaref); return FAIL; } @@ -5274,6 +5302,7 @@ static const char *command_complete[] = [EXPAND_CSCOPE] = "cscope", [EXPAND_USER_DEFINED] = "custom", [EXPAND_USER_LIST] = "customlist", + [EXPAND_USER_LUA] = "<Lua function>", [EXPAND_DIFF_BUFFERS] = "diff_buffer", [EXPAND_DIRECTORIES] = "dir", [EXPAND_ENV_VARS] = "environment", @@ -5323,11 +5352,9 @@ static void uc_list(char_u *name, size_t name_len) uint32_t a; // In cmdwin, the alternative buffer should be used. - garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL) - ? &prevwin->w_buffer->b_ucmds - : &curbuf->b_ucmds; + const garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds; for (;;) { - for (i = 0; i < gap->ga_len; ++i) { + for (i = 0; i < gap->ga_len; i++) { cmd = USER_CMD_GA(gap, i); a = cmd->uc_argt; @@ -5497,6 +5524,8 @@ static int uc_scan_attr(char_u *attr, size_t len, uint32_t *argt, long *def, int *flags |= UC_BUFFER; } else if (STRNICMP(attr, "register", len) == 0) { *argt |= EX_REGSTR; + } else if (STRNICMP(attr, "keepscript", len) == 0) { + *argt |= EX_KEEPSCRIPT; } else if (STRNICMP(attr, "bar", len) == 0) { *argt |= EX_TRLBAR; } else { @@ -5619,9 +5648,7 @@ invalid_count: static char e_complete_used_without_nargs[] = N_("E1208: -complete used without -nargs"); -/* - * ":command ..." - */ +/// ":command ..." static void ex_command(exarg_T *eap) { char_u *name; @@ -5651,23 +5678,18 @@ static void ex_command(exarg_T *eap) // Get the name (if any) and skip to the following argument. name = p; - if (ASCII_ISALPHA(*p)) { - while (ASCII_ISALNUM(*p)) { - p++; - } - } - if (!ends_excmd(*p) && !ascii_iswhite(*p)) { + end = (char_u *)uc_validate_name((char *)name); + if (!end) { emsg(_("E182: Invalid command name")); return; } - end = p; - name_len = (int)(end - name); + name_len = (size_t)(end - name); // If there is nothing after the name, and no attributes were specified, // we are listing commands p = skipwhite(end); if (!has_attr && ends_excmd(*p)) { - uc_list(name, end - name); + uc_list(name, name_len); } else if (!ASCII_ISUPPER(*name)) { emsg(_("E183: User defined commands must start with an uppercase letter")); } else if (name_len <= 4 && STRNCMP(name, "Next", name_len) == 0) { @@ -5675,31 +5697,29 @@ static void ex_command(exarg_T *eap) } else if (compl > 0 && (argt & EX_EXTRA) == 0) { emsg(_(e_complete_used_without_nargs)); } else { - uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg, - addr_type_arg, eap->forceit); + uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, + addr_type_arg, LUA_NOREF, eap->forceit); } } -/* - * ":comclear" - * Clear all user commands, global and for current buffer. - */ +/// ":comclear" +/// Clear all user commands, global and for current buffer. void ex_comclear(exarg_T *eap) { uc_clear(&ucmds); uc_clear(&curbuf->b_ucmds); } -static void free_ucmd(ucmd_T *cmd) +void free_ucmd(ucmd_T *cmd) { xfree(cmd->uc_name); xfree(cmd->uc_rep); xfree(cmd->uc_compl_arg); + NLUA_CLEAR_REF(cmd->uc_compl_luaref); + NLUA_CLEAR_REF(cmd->uc_luaref); } -/* - * Clear all user commands for "gap". - */ +/// Clear all user commands for "gap". void uc_clear(garray_T *gap) { GA_DEEP_CLEAR(gap, ucmd_T, free_ucmd); @@ -5709,32 +5729,40 @@ static void ex_delcommand(exarg_T *eap) { int i = 0; ucmd_T *cmd = NULL; - int cmp = -1; + int res = -1; garray_T *gap; + const char_u *arg = eap->arg; + bool buffer_only = false; + + if (STRNCMP(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) { + buffer_only = true; + arg = skipwhite(arg + 7); + } gap = &curbuf->b_ucmds; for (;;) { - for (i = 0; i < gap->ga_len; ++i) { + for (i = 0; i < gap->ga_len; i++) { cmd = USER_CMD_GA(gap, i); - cmp = STRCMP(eap->arg, cmd->uc_name); - if (cmp <= 0) { + res = STRCMP(arg, cmd->uc_name); + if (res <= 0) { break; } } - if (gap == &ucmds || cmp == 0) { + if (gap == &ucmds || res == 0 || buffer_only) { break; } gap = &ucmds; } - if (cmp != 0) { - semsg(_("E184: No such user-defined command: %s"), eap->arg); + if (res != 0) { + semsg(_(buffer_only + ? e_no_such_user_defined_command_in_current_buffer_str + : e_no_such_user_defined_command_str), + arg); return; } - xfree(cmd->uc_name); - xfree(cmd->uc_rep); - xfree(cmd->uc_compl_arg); + free_ucmd(cmd); --gap->ga_len; @@ -5743,9 +5771,50 @@ static void ex_delcommand(exarg_T *eap) } } -/* - * split and quote args for <f-args> - */ +/// Split a string by unescaped whitespace (space & tab), used for f-args on Lua commands callback. +/// Similar to uc_split_args(), but does not allocate, add quotes, add commas and is an iterator. +/// +/// @param[in] arg String to split +/// @param[in] arglen Length of {arg} +/// @param[inout] end Index of last character of previous iteration +/// @param[out] buf Buffer to copy string into +/// @param[out] len Length of string in {buf} +/// +/// @return true if iteration is complete, else false +bool uc_split_args_iter(const char_u *arg, size_t arglen, size_t *end, char *buf, size_t *len) +{ + if (!arglen) { + return true; + } + + size_t pos = *end; + while (pos < arglen && ascii_iswhite(arg[pos])) { + pos++; + } + + size_t l = 0; + for (; pos < arglen - 1; pos++) { + if (arg[pos] == '\\' && (arg[pos + 1] == '\\' || ascii_iswhite(arg[pos + 1]))) { + buf[l++] = arg[++pos]; + } else { + buf[l++] = arg[pos]; + if (ascii_iswhite(arg[pos + 1])) { + *end = pos + 1; + *len = l; + return false; + } + } + } + + if (pos < arglen && !ascii_iswhite(arg[pos])) { + buf[l++] = arg[pos]; + } + + *len = l; + return true; +} + +/// split and quote args for <f-args> static char_u *uc_split_args(char_u *arg, size_t *lenp) { char_u *buf; @@ -5816,7 +5885,7 @@ static char_u *uc_split_args(char_u *arg, size_t *lenp) return buf; } -static size_t add_cmd_modifier(char_u *buf, char *mod_str, bool *multi_mods) +static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods) { size_t result = STRLEN(mod_str); if (*multi_mods) { @@ -6008,7 +6077,7 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd, break; } - case ct_MODS: { + case ct_MODS: result = quote ? 2 : 0; if (buf != NULL) { if (quote) { @@ -6017,76 +6086,13 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd, *buf = '\0'; } - bool multi_mods = false; - - // :aboveleft and :leftabove - if (cmdmod.split & WSP_ABOVE) { - result += add_cmd_modifier(buf, "aboveleft", &multi_mods); - } - // :belowright and :rightbelow - if (cmdmod.split & WSP_BELOW) { - result += add_cmd_modifier(buf, "belowright", &multi_mods); - } - // :botright - if (cmdmod.split & WSP_BOT) { - result += add_cmd_modifier(buf, "botright", &multi_mods); - } - - typedef struct { - bool *set; - char *name; - } mod_entry_T; - static mod_entry_T mod_entries[] = { - { &cmdmod.browse, "browse" }, - { &cmdmod.confirm, "confirm" }, - { &cmdmod.hide, "hide" }, - { &cmdmod.keepalt, "keepalt" }, - { &cmdmod.keepjumps, "keepjumps" }, - { &cmdmod.keepmarks, "keepmarks" }, - { &cmdmod.keeppatterns, "keeppatterns" }, - { &cmdmod.lockmarks, "lockmarks" }, - { &cmdmod.noswapfile, "noswapfile" } - }; - // the modifiers that are simple flags - for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) { - if (*mod_entries[i].set) { - result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods); - } - } - - // TODO(vim): How to support :noautocmd? - // TODO(vim): How to support :sandbox? - - // :silent - if (msg_silent > 0) { - result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent", - &multi_mods); - } - // :tab - if (cmdmod.tab > 0) { - result += add_cmd_modifier(buf, "tab", &multi_mods); - } - // :topleft - if (cmdmod.split & WSP_TOP) { - result += add_cmd_modifier(buf, "topleft", &multi_mods); - } - - // TODO(vim): How to support :unsilent? + result += uc_mods((char *)buf); - // :verbose - if (p_verbose > 0) { - result += add_cmd_modifier(buf, "verbose", &multi_mods); - } - // :vertical - if (cmdmod.split & WSP_VERT) { - result += add_cmd_modifier(buf, "vertical", &multi_mods); - } if (quote && buf != NULL) { buf += result - 2; *buf = '"'; } break; - } case ct_REGISTER: result = eap->regname ? 1 : 0; @@ -6125,6 +6131,76 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd, return result; } +size_t uc_mods(char *buf) +{ + size_t result = 0; + bool multi_mods = false; + + // :aboveleft and :leftabove + if (cmdmod.split & WSP_ABOVE) { + result += add_cmd_modifier(buf, "aboveleft", &multi_mods); + } + // :belowright and :rightbelow + if (cmdmod.split & WSP_BELOW) { + result += add_cmd_modifier(buf, "belowright", &multi_mods); + } + // :botright + if (cmdmod.split & WSP_BOT) { + result += add_cmd_modifier(buf, "botright", &multi_mods); + } + + typedef struct { + bool *set; + char *name; + } mod_entry_T; + static mod_entry_T mod_entries[] = { + { &cmdmod.browse, "browse" }, + { &cmdmod.confirm, "confirm" }, + { &cmdmod.hide, "hide" }, + { &cmdmod.keepalt, "keepalt" }, + { &cmdmod.keepjumps, "keepjumps" }, + { &cmdmod.keepmarks, "keepmarks" }, + { &cmdmod.keeppatterns, "keeppatterns" }, + { &cmdmod.lockmarks, "lockmarks" }, + { &cmdmod.noswapfile, "noswapfile" } + }; + // the modifiers that are simple flags + for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) { + if (*mod_entries[i].set) { + result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods); + } + } + + // TODO(vim): How to support :noautocmd? + // TODO(vim): How to support :sandbox? + + // :silent + if (msg_silent > 0) { + result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent", &multi_mods); + } + // :tab + if (cmdmod.tab > 0) { + result += add_cmd_modifier(buf, "tab", &multi_mods); + } + // :topleft + if (cmdmod.split & WSP_TOP) { + result += add_cmd_modifier(buf, "topleft", &multi_mods); + } + + // TODO(vim): How to support :unsilent? + + // :verbose + if (p_verbose > 0) { + result += add_cmd_modifier(buf, "verbose", &multi_mods); + } + // :vertical + if (cmdmod.split & WSP_VERT) { + result += add_cmd_modifier(buf, "vertical", &multi_mods); + } + + return result; +} + static void do_ucmd(exarg_T *eap) { char_u *buf; @@ -6139,7 +6215,6 @@ static void do_ucmd(exarg_T *eap) size_t split_len = 0; char_u *split_buf = NULL; ucmd_T *cmd; - const sctx_T save_current_sctx = current_sctx; if (eap->cmdidx == CMD_USER) { cmd = USER_CMD(eap->useridx); @@ -6147,6 +6222,11 @@ static void do_ucmd(exarg_T *eap) cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx); } + if (cmd->uc_luaref > 0) { + nlua_do_ucmd(cmd, eap); + return; + } + /* * Replace <> in the command by the arguments. * First round: "buf" is NULL, compute length, allocate "buf". @@ -6172,7 +6252,6 @@ static void do_ucmd(exarg_T *eap) // K_SPECIAL has been put in the buffer as K_SPECIAL // KS_SPECIAL KE_FILLER, like for mappings, but // do_cmdline() doesn't handle that, so convert it back. - // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI. len = ksp - p; if (len > 0) { memmove(q, p, len); @@ -6225,36 +6304,42 @@ static void do_ucmd(exarg_T *eap) buf = xmalloc(totlen + 1); } - current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; + sctx_T save_current_sctx; + bool restore_current_sctx = false; + if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) { + restore_current_sctx = true; + save_current_sctx = current_sctx; + current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; + } (void)do_cmdline(buf, eap->getline, eap->cookie, DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); - current_sctx = save_current_sctx; + + // Careful: Do not use "cmd" here, it may have become invalid if a user + // command was added. + if (restore_current_sctx) { + current_sctx = save_current_sctx; + } xfree(buf); xfree(split_buf); } -static char_u *get_user_command_name(int idx) +static char_u *expand_user_command_name(int idx) { return get_user_commands(NULL, idx - CMD_SIZE); } -/* - * Function given to ExpandGeneric() to obtain the list of user address type names. - */ + +/// Function given to ExpandGeneric() to obtain the list of user address type names. char_u *get_user_cmd_addr_type(expand_T *xp, int idx) { return (char_u *)addr_type_complete[idx].name; } -/* - * Function given to ExpandGeneric() to obtain the list of user command names. - */ +/// Function given to ExpandGeneric() to obtain the list of user command names. char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // In cmdwin, the alternative buffer should be used. - const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL) - ? prevwin->w_buffer - : curbuf; + const buf_T *const buf = prevwin_curwin()->w_buffer; if (idx < buf->b_ucmds.ga_len) { return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; @@ -6266,15 +6351,33 @@ char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) return NULL; } -/* - * Function given to ExpandGeneric() to obtain the list of user command - * attributes. - */ +/// Get the name of user command "idx". "cmdidx" can be CMD_USER or +/// CMD_USER_BUF. +/// +/// @return NULL if the command is not found. +static char_u *get_user_command_name(int idx, int cmdidx) +{ + if (cmdidx == CMD_USER && idx < ucmds.ga_len) { + return USER_CMD(idx)->uc_name; + } + if (cmdidx == CMD_USER_BUF) { + // In cmdwin, the alternative buffer should be used. + const buf_T *const buf = prevwin_curwin()->w_buffer; + + if (idx < buf->b_ucmds.ga_len) { + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + } + } + return NULL; +} + +/// Function given to ExpandGeneric() to obtain the list of user command +/// attributes. char_u *get_user_cmd_flags(expand_T *xp, int idx) { static char *user_cmd_flags[] = { "addr", "bang", "bar", "buffer", "complete", "count", - "nargs", "range", "register" }; + "nargs", "range", "register", "keepscript" }; if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) { return NULL; @@ -6282,9 +6385,7 @@ char_u *get_user_cmd_flags(expand_T *xp, int idx) return (char_u *)user_cmd_flags[idx]; } -/* - * Function given to ExpandGeneric() to obtain the list of values for -nargs. - */ +/// Function given to ExpandGeneric() to obtain the list of values for -nargs. char_u *get_user_cmd_nargs(expand_T *xp, int idx) { static char *user_cmd_nargs[] = { "0", "1", "*", "?", "+" }; @@ -6295,9 +6396,7 @@ char_u *get_user_cmd_nargs(expand_T *xp, int idx) return (char_u *)user_cmd_nargs[idx]; } -/* - * Function given to ExpandGeneric() to obtain the list of values for -complete. - */ +/// Function given to ExpandGeneric() to obtain the list of values for -complete. char_u *get_user_cmd_complete(expand_T *xp, int idx) { if (idx >= (int)ARRAY_SIZE(command_complete)) { @@ -6311,9 +6410,7 @@ char_u *get_user_cmd_complete(expand_T *xp, int idx) } } -/* - * Parse address type argument - */ +/// Parse address type argument int parse_addr_type_arg(char_u *value, int vallen, cmd_addr_T *addr_type_arg) FUNC_ATTR_NONNULL_ALL { @@ -6340,13 +6437,12 @@ int parse_addr_type_arg(char_u *value, int vallen, cmd_addr_T *addr_type_arg) return OK; } -/* - * Parse a completion argument "value[vallen]". - * The detected completion goes in "*complp", argument type in "*argt". - * When there is an argument, for function and user defined completion, it's - * copied to allocated memory and stored in "*compl_arg". - * Returns FAIL if something is wrong. - */ +/// Parse a completion argument "value[vallen]". +/// The detected completion goes in "*complp", argument type in "*argt". +/// When there is an argument, for function and user defined completion, it's +/// copied to allocated memory and stored in "*compl_arg". +/// +/// @return FAIL if something is wrong. int parse_compl_arg(const char_u *value, int vallen, int *complp, uint32_t *argt, char_u **compl_arg) FUNC_ATTR_NONNULL_ALL @@ -6451,10 +6547,8 @@ static void ex_highlight(exarg_T *eap) } -/* - * Call this function if we thought we were going to exit, but we won't - * (because of an error). May need to restore the terminal mode. - */ +/// Call this function if we thought we were going to exit, but we won't +/// (because of an error). May need to restore the terminal mode. void not_exiting(void) { exiting = false; @@ -6489,8 +6583,8 @@ bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit) return false; } -// ":quit": quit current window, quit Vim if the last window is closed. -// ":{nr}quit": quit window {nr} +/// ":quit": quit current window, quit Vim if the last window is closed. +/// ":{nr}quit": quit window {nr} static void ex_quit(exarg_T *eap) { if (cmdwin_type != 0) { @@ -6550,12 +6644,13 @@ static void ex_quit(exarg_T *eap) } not_exiting(); // close window; may free buffer - win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit); + win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit, eap->forceit); } } /// ":cquit". static void ex_cquit(exarg_T *eap) + FUNC_ATTR_NORETURN { // this does not always pass on the exit code to the Manx compiler. why? getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE); @@ -6590,9 +6685,7 @@ static void ex_quit_all(exarg_T *eap) not_exiting(); } -/* - * ":close": close current window, unless it is the last one - */ +/// ":close": close current window, unless it is the last one static void ex_close(exarg_T *eap) { win_T *win = NULL; @@ -6618,9 +6711,7 @@ static void ex_close(exarg_T *eap) } } -/* - * ":pclose": Close any preview window. - */ +/// ":pclose": Close any preview window. static void ex_pclose(exarg_T *eap) { FOR_ALL_WINDOWS_IN_TAB(win, curtab) { @@ -6665,16 +6756,14 @@ void ex_win_close(int forceit, win_T *win, tabpage_T *tp) // free buffer when not hiding it or when it's a scratch buffer if (tp == NULL) { - win_close(win, !need_hide && !buf_hide(buf)); + win_close(win, !need_hide && !buf_hide(buf), forceit); } else { win_close_othertab(win, !need_hide && !buf_hide(buf), tp); } } -/* - * ":tabclose": close current tab page, unless it is the last one. - * ":tabclose N": close tab page N. - */ +/// ":tabclose": close current tab page, unless it is the last one. +/// ":tabclose N": close tab page N. static void ex_tabclose(exarg_T *eap) { tabpage_T *tp; @@ -6735,9 +6824,7 @@ static void ex_tabonly(exarg_T *eap) } } -/* - * Close the current tab page. - */ +/// Close the current tab page. void tabpage_close(int forceit) { // First close all the windows but the current one. If that worked then @@ -6753,12 +6840,10 @@ void tabpage_close(int forceit) } } -/* - * Close tab page "tp", which is not the current tab page. - * Note that autocommands may make "tp" invalid. - * Also takes care of the tab pages line disappearing when closing the - * last-but-one tab page. - */ +/// Close tab page "tp", which is not the current tab page. +/// Note that autocommands may make "tp" invalid. +/// Also takes care of the tab pages line disappearing when closing the +/// last-but-one tab page. void tabpage_close_other(tabpage_T *tp, int forceit) { int done = 0; @@ -6775,7 +6860,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit) // Autocommands may delete the tab page under our fingers and we may // fail to close a window with a modified buffer. - if (!valid_tabpage(tp) || tp->tp_firstwin == wp) { + if (!valid_tabpage(tp) || tp->tp_lastwin == wp) { break; } } @@ -6786,9 +6871,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit) } } -/* - * ":only". - */ +/// ":only". static void ex_only(exarg_T *eap) { win_T *wp; @@ -6812,10 +6895,8 @@ static void ex_only(exarg_T *eap) close_others(TRUE, eap->forceit); } -/* - * ":all" and ":sall". - * Also used for ":tab drop file ..." after setting the argument list. - */ +/// ":all" and ":sall". +/// Also used for ":tab drop file ..." after setting the argument list. void ex_all(exarg_T *eap) { if (eap->addr_count == 0) { @@ -6829,7 +6910,7 @@ static void ex_hide(exarg_T *eap) // ":hide" or ":hide | cmd": hide current window if (!eap->skip) { if (eap->addr_count == 0) { - win_close(curwin, false); // don't free buffer + win_close(curwin, false, eap->forceit); // don't free buffer } else { int winnr = 0; win_T *win = NULL; @@ -6844,7 +6925,7 @@ static void ex_hide(exarg_T *eap) if (win == NULL) { win = lastwin; } - win_close(win, false); + win_close(win, false, eap->forceit); } } } @@ -6870,7 +6951,7 @@ static void ex_stop(exarg_T *eap) apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); } -// ":exit", ":xit" and ":wq": Write file and quit the current window. +/// ":exit", ":xit" and ":wq": Write file and quit the current window. static void ex_exit(exarg_T *eap) { if (cmdwin_type != 0) { @@ -6902,13 +6983,11 @@ static void ex_exit(exarg_T *eap) } not_exiting(); // Quit current window, may free the buffer. - win_close(curwin, !buf_hide(curwin->w_buffer)); + win_close(curwin, !buf_hide(curwin->w_buffer), eap->forceit); } } -/* - * ":print", ":list", ":number". - */ +/// ":print", ":list", ":number". static void ex_print(exarg_T *eap) { if (curbuf->b_ml.ml_flags & ML_EMPTY) { @@ -6938,29 +7017,23 @@ static void ex_goto(exarg_T *eap) goto_byte(eap->line2); } -/* - * Clear an argument list: free all file names and reset it to zero entries. - */ +/// Clear an argument list: free all file names and reset it to zero entries. void alist_clear(alist_T *al) { #define FREE_AENTRY_FNAME(arg) xfree(arg->ae_fname) GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); } -/* - * Init an argument list. - */ +/// Init an argument list. void alist_init(alist_T *al) { ga_init(&al->al_ga, (int)sizeof(aentry_T), 5); } -/* - * Remove a reference from an argument list. - * Ignored when the argument list is the global one. - * If the argument list is no longer used by any window, free it. - */ +/// Remove a reference from an argument list. +/// Ignored when the argument list is the global one. +/// If the argument list is no longer used by any window, free it. void alist_unlink(alist_T *al) { if (al != &global_alist && --al->al_refcount <= 0) { @@ -6969,9 +7042,7 @@ void alist_unlink(alist_T *al) } } -/* - * Create a new argument list and use it for the current window. - */ +/// Create a new argument list and use it for the current window. void alist_new(void) { curwin->w_alist = xmalloc(sizeof(*curwin->w_alist)); @@ -6981,11 +7052,10 @@ void alist_new(void) } #if !defined(UNIX) -/* - * Expand the file names in the global argument list. - * If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer - * numbers to be re-used. - */ + +/// Expand the file names in the global argument list. +/// If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer +/// numbers to be re-used. void alist_expand(int *fnum_list, int fnum_len) { char_u **old_arg_files; @@ -7016,10 +7086,8 @@ void alist_expand(int *fnum_list, int fnum_len) } #endif -/* - * Set the argument list for the current window. - * Takes over the allocated files[] and the allocated fnames in it. - */ +/// Set the argument list for the current window. +/// Takes over the allocated files[] and the allocated fnames in it. void alist_set(alist_T *al, int count, char_u **files, int use_curbuf, int *fnum_list, int fnum_len) { int i; @@ -7083,9 +7151,8 @@ void alist_add(alist_T *al, char_u *fname, int set_fnum) } #if defined(BACKSLASH_IN_FILENAME) -/* - * Adjust slashes in file names. Called after 'shellslash' was set. - */ + +/// Adjust slashes in file names. Called after 'shellslash' was set. void alist_slash_adjust(void) { for (int i = 0; i < GARGCOUNT; ++i) { @@ -7110,7 +7177,6 @@ void alist_slash_adjust(void) /// ":preserve". static void ex_preserve(exarg_T *eap) { - curbuf->b_flags |= BF_PRESERVED; ml_preserve(curbuf, true, true); } @@ -7131,27 +7197,23 @@ static void ex_recover(exarg_T *eap) recoverymode = false; } -/* - * Command modifier used in a wrong way. - */ +/// Command modifier used in a wrong way. static void ex_wrongmodifier(exarg_T *eap) { eap->errmsg = e_invcmd; } -/* - * :sview [+command] file split window with new file, read-only - * :split [[+command] file] split window with current or new file - * :vsplit [[+command] file] split window vertically with current or new file - * :new [[+command] file] split window with no or new file - * :vnew [[+command] file] split vertically window with no or new file - * :sfind [+command] file split window with file in 'path' - * - * :tabedit open new Tab page with empty window - * :tabedit [+command] file open new Tab page and edit "file" - * :tabnew [[+command] file] just like :tabedit - * :tabfind [+command] file open new Tab page and find "file" - */ +/// :sview [+command] file split window with new file, read-only +/// :split [[+command] file] split window with current or new file +/// :vsplit [[+command] file] split window vertically with current or new file +/// :new [[+command] file] split window with no or new file +/// :vnew [[+command] file] split vertically window with no or new file +/// :sfind [+command] file split window with file in 'path' +/// +/// :tabedit open new Tab page with empty window +/// :tabedit [+command] file open new Tab page and edit "file" +/// :tabnew [[+command] file] just like :tabedit +/// :tabfind [+command] file open new Tab page and find "file" void ex_splitview(exarg_T *eap) { win_T *old_curwin = curwin; @@ -7214,9 +7276,7 @@ theend: xfree(fname); } -/* - * Open a new tab page. - */ +/// Open a new tab page. void tabpage_new(void) { exarg_T ea; @@ -7228,9 +7288,7 @@ void tabpage_new(void) ex_splitview(&ea); } -/* - * :tabnext command - */ +/// :tabnext command static void ex_tabnext(exarg_T *eap) { int tab_number; @@ -7277,9 +7335,7 @@ static void ex_tabnext(exarg_T *eap) } } -/* - * :tabmove command - */ +/// :tabmove command static void ex_tabmove(exarg_T *eap) { int tab_number = get_tabpage_arg(eap); @@ -7288,9 +7344,7 @@ static void ex_tabmove(exarg_T *eap) } } -/* - * :tabs command: List tabs and their contents. - */ +/// :tabs command: List tabs and their contents. static void ex_tabs(exarg_T *eap) { int tabcount = 1; @@ -7336,10 +7390,8 @@ static void ex_tabs(exarg_T *eap) } -/* - * ":mode": - * If no argument given, get the screen size and redraw. - */ +/// ":mode": +/// If no argument given, get the screen size and redraw. static void ex_mode(exarg_T *eap) { if (*eap->arg == NUL) { @@ -7350,10 +7402,8 @@ static void ex_mode(exarg_T *eap) } } -/* - * ":resize". - * set, increment or decrement current window height - */ +/// ":resize". +/// set, increment or decrement current window height static void ex_resize(exarg_T *eap) { int n; @@ -7383,9 +7433,7 @@ static void ex_resize(exarg_T *eap) } } -/* - * ":find [+command] <file>" command. - */ +/// ":find [+command] <file>" command. static void ex_find(exarg_T *eap) { char_u *fname; @@ -7416,7 +7464,7 @@ static void ex_edit(exarg_T *eap) do_exedit(eap, NULL); } -/// ":edit <file>" command and alikes. +/// ":edit <file>" command and alike. /// /// @param old_curwin curwin before doing a split or NULL void do_exedit(exarg_T *eap, win_T *old_curwin) @@ -7462,9 +7510,8 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) if ((eap->cmdidx == CMD_new || eap->cmdidx == CMD_tabnew || eap->cmdidx == CMD_tabedit - || eap->cmdidx == CMD_vnew - ) && *eap->arg == NUL) { - // ":new" or ":tabnew" without argument: edit an new empty buffer + || eap->cmdidx == CMD_vnew) && *eap->arg == NUL) { + // ":new" or ":tabnew" without argument: edit a new empty buffer setpcmark(); (void)do_ecmd(0, NULL, NULL, eap, ECMD_ONE, ECMD_HIDE + (eap->forceit ? ECMD_FORCEIT : 0), @@ -7504,7 +7551,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) // Reset the error/interrupt/exception state here so that // aborting() returns FALSE when closing a window. enter_cleanup(&cs); - win_close(curwin, !need_hide && !buf_hide(curbuf)); + win_close(curwin, !need_hide && !buf_hide(curbuf), false); // Restore the error/interrupt/exception state if not // discarded by a new aborting error, interrupt, or @@ -7563,11 +7610,9 @@ static void ex_swapname(exarg_T *eap) } } -/* - * ":syncbind" forces all 'scrollbind' windows to have the same relative - * offset. - * (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>) - */ +/// ":syncbind" forces all 'scrollbind' windows to have the same relative +/// offset. +/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>) static void ex_syncbind(exarg_T *eap) { win_T *save_curwin = curwin; @@ -7698,7 +7743,7 @@ void free_cd_dir(void) #endif -// Get the previous directory for the given chdir scope. +/// Get the previous directory for the given chdir scope. static char_u *get_prevdir(CdScope scope) { switch (scope) { @@ -7716,7 +7761,7 @@ static char_u *get_prevdir(CdScope scope) /// Deal with the side effects of changing the current directory. /// /// @param scope Scope of the function call (global, tab or window). -void post_chdir(CdScope scope, bool trigger_dirchanged) +static void post_chdir(CdScope scope, bool trigger_dirchanged) { // Always overwrite the window-local CWD. XFREE_CLEAR(curwin->w_localdir); @@ -7753,10 +7798,11 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) abort(); } + last_chdir_reason = NULL; shorten_fnames(true); if (trigger_dirchanged) { - do_autocmd_dirchanged(cwd, scope, kCdCauseManual); + do_autocmd_dirchanged(cwd, scope, kCdCauseManual, false); } } @@ -7766,14 +7812,11 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) /// @return true if the directory is successfully changed. bool changedir_func(char_u *new_dir, CdScope scope) { - char_u *tofree; - char_u *pdir = NULL; - bool retval = false; - if (new_dir == NULL || allbuf_locked()) { return false; } + char_u *pdir = NULL; // ":cd -": Change to previous directory if (STRCMP(new_dir, "-") == 0) { pdir = get_prevdir(scope); @@ -7784,45 +7827,51 @@ bool changedir_func(char_u *new_dir, CdScope scope) new_dir = pdir; } - // Free the previous directory - tofree = get_prevdir(scope); - if (os_dirname(NameBuff, MAXPATHL) == OK) { pdir = vim_strsave(NameBuff); } else { pdir = NULL; } - switch (scope) { - case kCdScopeTabpage: - curtab->tp_prevdir = pdir; - break; - case kCdScopeWindow: - curwin->w_prevdir = pdir; - break; - default: - prev_dir = pdir; - } - + // For UNIX ":cd" means: go to home directory. + // On other systems too if 'cdhome' is set. #if defined(UNIX) - // On Unix ":cd" means: go to home directory. if (*new_dir == NUL) { +#else + if (*new_dir == NUL && p_cdh) { +#endif // Use NameBuff for home directory name. expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); new_dir = NameBuff; } -#endif - if (vim_chdir(new_dir) == 0) { - bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; - post_chdir(scope, dir_differs); - retval = true; - } else { - emsg(_(e_failed)); + bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; + if (dir_differs) { + do_autocmd_dirchanged((char *)new_dir, scope, kCdCauseManual, true); + if (vim_chdir(new_dir) != 0) { + emsg(_(e_failed)); + xfree(pdir); + return false; + } } - xfree(tofree); - return retval; + char_u **pp; + switch (scope) { + case kCdScopeTabpage: + pp = &curtab->tp_prevdir; + break; + case kCdScopeWindow: + pp = &curwin->w_prevdir; + break; + default: + pp = &prev_dir; + } + xfree(*pp); + *pp = pdir; + + post_chdir(scope, dir_differs); + + return true; } /// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir". @@ -7830,9 +7879,9 @@ void ex_cd(exarg_T *eap) { char_u *new_dir; new_dir = eap->arg; -#if !defined(UNIX) && !defined(VMS) - // for non-UNIX ":cd" means: print current directory - if (*new_dir == NUL) { +#if !defined(UNIX) + // for non-UNIX ":cd" means: print current directory unless 'cdhome' is set + if (*new_dir == NUL && !p_cdh) { ex_pwd(NULL); } else #endif @@ -7859,9 +7908,7 @@ void ex_cd(exarg_T *eap) } } -/* - * ":pwd". - */ +/// ":pwd". static void ex_pwd(exarg_T *eap) { if (os_dirname(NameBuff, MAXPATHL) == OK) { @@ -7870,7 +7917,9 @@ static void ex_pwd(exarg_T *eap) #endif if (p_verbose > 0) { char *context = "global"; - if (curwin->w_localdir != NULL) { + if (last_chdir_reason != NULL) { + context = last_chdir_reason; + } else if (curwin->w_localdir != NULL) { context = "window"; } else if (curtab->tp_localdir != NULL) { context = "tabpage"; @@ -7884,9 +7933,7 @@ static void ex_pwd(exarg_T *eap) } } -/* - * ":=". - */ +/// ":=". static void ex_equal(exarg_T *eap) { smsg("%" PRId64, (int64_t)eap->line2); @@ -7917,9 +7964,7 @@ static void ex_sleep(exarg_T *eap) do_sleep(len); } -/* - * Sleep for "msec" milliseconds, but keep checking for a CTRL-C every second. - */ +/// Sleep for "msec" milliseconds, but keep checking for a CTRL-C every second. void do_sleep(long msec) { ui_flush(); // flush before waiting @@ -7955,9 +8000,7 @@ static void do_exmap(exarg_T *eap, int isabbrev) } } -/* - * ":winsize" command (obsolete). - */ +/// ":winsize" command (obsolete). static void ex_winsize(exarg_T *eap) { char_u *arg = eap->arg; @@ -8008,9 +8051,7 @@ static void ex_wincmd(exarg_T *eap) } } -/* - * Handle command that work like operators: ":delete", ":yank", ":>" and ":<". - */ +/// Handle command that work like operators: ":delete", ":yank", ":>" and ":<". static void ex_operators(exarg_T *eap) { oparg_T oa; @@ -8040,7 +8081,7 @@ static void ex_operators(exarg_T *eap) case CMD_yank: oa.op_type = OP_YANK; - (void)op_yank(&oa, true, false); + (void)op_yank(&oa, true); break; default: // CMD_rshift or CMD_lshift @@ -8057,9 +8098,7 @@ static void ex_operators(exarg_T *eap) ex_may_print(eap); } -/* - * ":put". - */ +/// ":put". static void ex_put(exarg_T *eap) { // ":0put" works like ":1put!". @@ -8068,13 +8107,12 @@ static void ex_put(exarg_T *eap) eap->forceit = TRUE; } curwin->w_cursor.lnum = eap->line2; + check_cursor_col(); do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1, PUT_LINE|PUT_CURSLINE); } -/* - * Handle ":copy" and ":move". - */ +/// Handle ":copy" and ":move". static void ex_copymove(exarg_T *eap) { long n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1); @@ -8104,9 +8142,7 @@ static void ex_copymove(exarg_T *eap) ex_may_print(eap); } -/* - * Print the current line if flags were given to the Ex command. - */ +/// Print the current line if flags were given to the Ex command. void ex_may_print(exarg_T *eap) { if (eap->flags != 0) { @@ -8126,9 +8162,7 @@ static void ex_submagic(exarg_T *eap) p_magic = magic_save; } -/* - * ":join". - */ +/// ":join". static void ex_join(exarg_T *eap) { curwin->w_cursor.lnum = eap->line1; @@ -8147,9 +8181,7 @@ static void ex_join(exarg_T *eap) ex_may_print(eap); } -/* - * ":[addr]@r": execute register - */ +/// ":[addr]@r": execute register static void ex_at(exarg_T *eap) { int prev_len = typebuf.tb_len; @@ -8185,17 +8217,13 @@ static void ex_at(exarg_T *eap) } } -/* - * ":!". - */ +/// ":!". static void ex_bang(exarg_T *eap) { do_bang(eap->addr_count, eap, eap->forceit, true, true); } -/* - * ":undo". - */ +/// ":undo". static void ex_undo(exarg_T *eap) { if (eap->addr_count == 1) { // :undo 123 @@ -8261,9 +8289,7 @@ static void ex_later(exarg_T *eap) } } -/* - * ":redir": start/stop redirection. - */ +/// ":redir": start/stop redirection. static void ex_redir(exarg_T *eap) { char *mode; @@ -8334,9 +8360,7 @@ static void ex_redir(exarg_T *eap) if (var_redir_start(skipwhite(arg), append) == OK) { redir_vname = 1; } - } - // TODO: redirect to a buffer - else { + } else { // TODO(vim): redirect to a buffer semsg(_(e_invarg2), eap->arg); } } @@ -8406,7 +8430,7 @@ static void ex_redrawstatus(exarg_T *eap) ui_flush(); } -// ":redrawtabline": force redraw of the tabline +/// ":redrawtabline": force redraw of the tabline static void ex_redrawtabline(exarg_T *eap FUNC_ATTR_UNUSED) { const int r = RedrawingDisabled; @@ -8480,9 +8504,7 @@ FILE *open_exfile(char_u *fname, int forceit, char *mode) return fd; } -/* - * ":mark" and ":k". - */ +/// ":mark" and ":k". static void ex_mark(exarg_T *eap) { pos_T pos; @@ -8502,9 +8524,7 @@ static void ex_mark(exarg_T *eap) } } -/* - * Update w_topline, w_leftcol and the cursor position. - */ +/// Update w_topline, w_leftcol and the cursor position. void update_topline_cursor(void) { check_cursor(); // put cursor on valid line @@ -8515,8 +8535,9 @@ void update_topline_cursor(void) update_curswant(); } -// Save the current State and go to Normal mode. -// Return true if the typeahead could be saved. +/// Save the current State and go to Normal mode. +/// +/// @return true if the typeahead could be saved. bool save_current_state(save_state_T *sst) FUNC_ATTR_NONNULL_ALL { @@ -8528,6 +8549,7 @@ bool save_current_state(save_state_T *sst) sst->save_finish_op = finish_op; sst->save_opcount = opcount; sst->save_reg_executing = reg_executing; + sst->save_pending_end_reg_executing = pending_end_reg_executing; msg_scroll = false; // no msg scrolling in Normal mode restart_edit = 0; // don't go to Insert mode @@ -8558,6 +8580,7 @@ void restore_current_state(save_state_T *sst) finish_op = sst->save_finish_op; opcount = sst->save_opcount; reg_executing = sst->save_reg_executing; + pending_end_reg_executing = sst->save_pending_end_reg_executing; // don't reset msg_didout now msg_didout |= sst->save_msg_didout; @@ -8568,9 +8591,7 @@ void restore_current_state(save_state_T *sst) ui_cursor_shape(); // may show different cursor shape } -/* - * ":normal[!] {commands}": Execute normal mode commands. - */ +/// ":normal[!] {commands}": Execute normal mode commands. static void ex_normal(exarg_T *eap) { if (curbuf->terminal && State & TERM_FOCUS) { @@ -8591,7 +8612,7 @@ static void ex_normal(exarg_T *eap) return; } - // vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do + // vgetc() expects K_SPECIAL to have been escaped. Don't do // this for the K_SPECIAL leading byte, otherwise special keys will not // work. { @@ -8600,8 +8621,7 @@ static void ex_normal(exarg_T *eap) // Count the number of characters to be escaped. for (p = eap->arg; *p != NUL; p++) { for (l = utfc_ptr2len(p) - 1; l > 0; l--) { - if (*++p == K_SPECIAL // trailbyte K_SPECIAL or CSI - ) { + if (*++p == K_SPECIAL) { // trailbyte K_SPECIAL len += 2; } } @@ -8652,9 +8672,7 @@ static void ex_normal(exarg_T *eap) xfree(arg); } -/* - * ":startinsert", ":startreplace" and ":startgreplace" - */ +/// ":startinsert", ":startreplace" and ":startgreplace" static void ex_startinsert(exarg_T *eap) { if (eap->forceit) { @@ -8691,9 +8709,7 @@ static void ex_startinsert(exarg_T *eap) } } -/* - * ":stopinsert" - */ +/// ":stopinsert" static void ex_stopinsert(exarg_T *eap) { restart_edit = 0; @@ -8701,10 +8717,8 @@ static void ex_stopinsert(exarg_T *eap) clearmode(); } -/* - * Execute normal mode command "cmd". - * "remap" can be REMAP_NONE or REMAP_YES. - */ +/// Execute normal mode command "cmd". +/// "remap" can be REMAP_NONE or REMAP_YES. void exec_normal_cmd(char_u *cmd, int remap, bool silent) { // Stuff the argument into the typeahead buffer. @@ -8737,9 +8751,7 @@ static void ex_checkpath(exarg_T *eap) (linenr_T)1, (linenr_T)MAXLNUM); } -/* - * ":psearch" - */ +/// ":psearch" static void ex_psearch(exarg_T *eap) { g_do_tagpreview = p_pvh; @@ -8802,18 +8814,14 @@ static void ex_findpat(exarg_T *eap) } -/* - * ":ptag", ":ptselect", ":ptjump", ":ptnext", etc. - */ +/// ":ptag", ":ptselect", ":ptjump", ":ptnext", etc. static void ex_ptag(exarg_T *eap) { g_do_tagpreview = p_pvh; // will be reset to 0 in ex_tag_cmd() ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1); } -/* - * ":pedit" - */ +/// ":pedit" static void ex_pedit(exarg_T *eap) { win_T *curwin_save = curwin; @@ -8834,9 +8842,7 @@ static void ex_pedit(exarg_T *eap) g_do_tagpreview = 0; } -/* - * ":stag", ":stselect" and ":stjump". - */ +/// ":stag", ":stselect" and ":stjump". static void ex_stag(exarg_T *eap) { postponed_split = -1; @@ -8847,9 +8853,7 @@ static void ex_stag(exarg_T *eap) postponed_split_tab = 0; } -/* - * ":tag", ":tselect", ":tjump", ":tnext", etc. - */ +/// ":tag", ":tselect", ":tjump", ":tnext", etc. static void ex_tag(exarg_T *eap) { ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name); @@ -8918,11 +8922,9 @@ enum { // 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 - * the variable. Otherwise return -1 and "*usedlen" is unchanged. - */ +/// Check "str" for starting with a special cmdline variable. +/// If found return one of the SPEC_ values and set "*usedlen" to the length of +/// the variable. Otherwise return -1 and "*usedlen" is unchanged. ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) FUNC_ATTR_NONNULL_ALL { @@ -9235,11 +9237,9 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum return result; } -/* - * Concatenate all files in the argument list, separated by spaces, and return - * it in one allocated string. - * Spaces and backslashes in the file names are escaped with a backslash. - */ +/// Concatenate all files in the argument list, separated by spaces, and return +/// it in one allocated string. +/// Spaces and backslashes in the file names are escaped with a backslash. static char_u *arg_all(void) { int len; @@ -9298,11 +9298,9 @@ static char_u *arg_all(void) return retval; } -/* - * Expand the <sfile> string in "arg". - * - * Returns an allocated string, or NULL for any error. - */ +/// Expand the <sfile> string in "arg". +/// +/// @return an allocated string, or NULL for any error. char_u *expand_sfile(char_u *arg) { char *errormsg; @@ -9348,9 +9346,7 @@ char_u *expand_sfile(char_u *arg) return result; } -/* - * ":rshada" and ":wshada". - */ +/// ":rshada" and ":wshada". static void ex_shada(exarg_T *eap) { char_u *save_shada; @@ -9367,10 +9363,8 @@ static void ex_shada(exarg_T *eap) p_shada = save_shada; } -/* - * Make a dialog message in "buff[DIALOG_MSG_SIZE]". - * "format" must contain "%s". - */ +/// Make a dialog message in "buff[DIALOG_MSG_SIZE]". +/// "format" must contain "%s". void dialog_msg(char_u *buff, char *format, char_u *fname) { if (fname == NULL) { @@ -9379,9 +9373,7 @@ void dialog_msg(char_u *buff, char *format, char_u *fname) vim_snprintf((char *)buff, DIALOG_MSG_SIZE, format, fname); } -/* - * ":behave {mswin,xterm}" - */ +/// ":behave {mswin,xterm}" static void ex_behave(exarg_T *eap) { if (STRCMP(eap->arg, "mswin") == 0) { @@ -9399,10 +9391,8 @@ static void ex_behave(exarg_T *eap) } } -/* - * Function given to ExpandGeneric() to obtain the possible arguments of the - * ":behave {mswin,xterm}" command. - */ +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":behave {mswin,xterm}" command. char_u *get_behave_arg(expand_T *xp, int idx) { if (idx == 0) { @@ -9414,8 +9404,8 @@ char_u *get_behave_arg(expand_T *xp, int idx) return NULL; } -// Function given to ExpandGeneric() to obtain the possible arguments of the -// ":messages {clear}" command. +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":messages {clear}" command. char_u *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) { if (idx == 0) { @@ -9436,15 +9426,13 @@ static TriState filetype_detect = kNone; static TriState filetype_plugin = kNone; static TriState filetype_indent = kNone; -/* - * ":filetype [plugin] [indent] {on,off,detect}" - * on: Load the filetype.vim file to install autocommands for file types. - * off: Load the ftoff.vim file to remove all autocommands for file types. - * plugin on: load filetype.vim and ftplugin.vim - * plugin off: load ftplugof.vim - * indent on: load filetype.vim and indent.vim - * indent off: load indoff.vim - */ +/// ":filetype [plugin] [indent] {on,off,detect}" +/// on: Load the filetype.vim file to install autocommands for file types. +/// off: Load the ftoff.vim file to remove all autocommands for file types. +/// plugin on: load filetype.vim and ftplugin.vim +/// plugin off: load ftplugof.vim +/// indent on: load filetype.vim and indent.vim +/// indent off: load indoff.vim static void ex_filetype(exarg_T *eap) { char_u *arg = eap->arg; @@ -9510,23 +9498,34 @@ static void ex_filetype(exarg_T *eap) } } -/// Set all :filetype options ON if user did not explicitly set any to OFF. -void filetype_maybe_enable(void) +/// Source ftplugin.vim and indent.vim to create the necessary FileType +/// autocommands. We do this separately from filetype.vim so that these +/// autocommands will always fire first (and thus can be overridden) while still +/// allowing general filetype detection to be disabled in the user's init file. +void filetype_plugin_enable(void) { - if (filetype_detect == kNone) { - source_runtime(FILETYPE_FILE, true); - filetype_detect = kTrue; - } if (filetype_plugin == kNone) { - source_runtime(FTPLUGIN_FILE, true); + source_runtime(FTPLUGIN_FILE, DIP_ALL); filetype_plugin = kTrue; } if (filetype_indent == kNone) { - source_runtime(INDENT_FILE, true); + source_runtime(INDENT_FILE, DIP_ALL); filetype_indent = kTrue; } } +/// Enable filetype detection if the user did not explicitly disable it. +void filetype_maybe_enable(void) +{ + if (filetype_detect == kNone) { + // Normally .vim files are sourced before .lua files when both are + // supported, but we reverse the order here because we want the Lua + // autocommand to be defined first so that it runs first + source_runtime(FILETYPE_FILE, DIP_ALL); + filetype_detect = kTrue; + } +} + /// ":setfiletype [FALLBACK] {name}" static void ex_setfiletype(exarg_T *eap) { @@ -9553,97 +9552,19 @@ static void ex_digraphs(exarg_T *eap) } } -static void ex_set(exarg_T *eap) -{ - int flags = 0; - - if (eap->cmdidx == CMD_setlocal) { - flags = OPT_LOCAL; - } else if (eap->cmdidx == CMD_setglobal) { - flags = OPT_GLOBAL; - } - (void)do_set(eap->arg, flags); -} - void set_no_hlsearch(bool flag) { no_hlsearch = flag; set_vim_var_nr(VV_HLSEARCH, !no_hlsearch && p_hls); } -/* - * ":nohlsearch" - */ +/// ":nohlsearch" static void ex_nohlsearch(exarg_T *eap) { set_no_hlsearch(true); redraw_all_later(SOME_VALID); } -// ":[N]match {group} {pattern}" -// Sets nextcmd to the start of the next command, if any. Also called when -// skipping commands to find the next command. -static void ex_match(exarg_T *eap) -{ - char_u *p; - char_u *g = NULL; - char_u *end; - int c; - int id; - - if (eap->line2 <= 3) { - id = eap->line2; - } else { - emsg(e_invcmd); - return; - } - - // First clear any old pattern. - if (!eap->skip) { - match_delete(curwin, id, false); - } - - if (ends_excmd(*eap->arg)) { - end = eap->arg; - } else if ((STRNICMP(eap->arg, "none", 4) == 0 - && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) { - end = eap->arg + 4; - } else { - p = skiptowhite(eap->arg); - if (!eap->skip) { - g = vim_strnsave(eap->arg, p - eap->arg); - } - p = skipwhite(p); - if (*p == NUL) { - // There must be two arguments. - xfree(g); - semsg(_(e_invarg2), eap->arg); - return; - } - end = skip_regexp(p + 1, *p, true, NULL); - if (!eap->skip) { - if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { - xfree(g); - eap->errmsg = e_trailing; - return; - } - if (*end != *p) { - xfree(g); - semsg(_(e_invarg2), p); - return; - } - - c = *end; - *end = NUL; - match_add(curwin, (const char *)g, (const char *)p + 1, 10, id, - NULL, NULL); - xfree(g); - *end = c; - } - } - eap->nextcmd = find_nextcmd(end); -} - static void ex_fold(exarg_T *eap) { if (foldManualAllowed(true)) { @@ -9673,8 +9594,8 @@ static void ex_folddo(exarg_T *eap) ml_clearmarked(); // clear rest of the marks } -// Returns true if the supplied Ex cmdidx is for a location list command -// instead of a quickfix command. +/// @return true if the supplied Ex cmdidx is for a location list command +/// instead of a quickfix command. bool is_loclist_cmd(int cmdidx) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -9799,6 +9720,7 @@ Dictionary commands_array(buf_T *buf) PUT(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG))); PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR))); PUT(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR))); + PUT(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT))); switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { case 0: diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index 7ec4fad277..874e0e599e 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -1,6 +1,7 @@ #ifndef NVIM_EX_DOCMD_H #define NVIM_EX_DOCMD_H +#include "nvim/eval/funcs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/globals.h" @@ -28,9 +29,30 @@ typedef struct { bool save_finish_op; long save_opcount; int save_reg_executing; + bool save_pending_end_reg_executing; tasave_T tabuf; } save_state_T; +typedef struct ucmd { + char_u *uc_name; // The command name + uint32_t uc_argt; // The argument type + char_u *uc_rep; // The command's replacement string + long uc_def; // The default value for a range/count + int uc_compl; // completion type + cmd_addr_T uc_addr_type; // The command's address type + sctx_T uc_script_ctx; // SCTX where the command was defined + char_u *uc_compl_arg; // completion argument if any + LuaRef uc_compl_luaref; // Reference to Lua completion function + LuaRef uc_luaref; // Reference to Lua function +} ucmd_T; + +#define UC_BUFFER 1 // -buffer: local to current buffer + +extern garray_T ucmds; + +#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i]) +#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i]) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_docmd.h.generated.h" #endif diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index f60f0ebe98..3c7c635d98 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -88,27 +88,26 @@ */ 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. -// "got_int" is also set by calling interrupt(). +/// @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; } -/* - * The value of "force_abort" is temporarily reset by the first emsg() call - * during an expression evaluation, and "cause_abort" is used instead. It might - * be necessary to restore "force_abort" even before the throw point for the - * error message has been reached. update_force_abort() should be called then. - */ +/// The value of "force_abort" is temporarily reset by the first emsg() call +/// during an expression evaluation, and "cause_abort" is used instead. It might +/// be necessary to restore "force_abort" even before the throw point for the +/// error message has been reached. update_force_abort() should be called then. void update_force_abort(void) { if (cause_abort) { @@ -116,23 +115,19 @@ void update_force_abort(void) } } -/* - * Return TRUE if a command with a subcommand resulting in "retcode" should - * abort the script processing. Can be used to suppress an autocommand after - * execution of a failing subcommand as long as the error message has not been - * displayed and actually caused the abortion. - */ +/// @return TRUE if a command with a subcommand resulting in "retcode" should +/// abort the script processing. Can be used to suppress an autocommand after +/// execution of a failing subcommand as long as the error message has not been +/// displayed and actually caused the abortion. int should_abort(int retcode) { return (retcode == FAIL && trylevel != 0 && !emsg_silent) || aborting(); } -/* - * Return TRUE if a function with the "abort" flag should not be considered - * ended on an error. This means that parsing commands is continued in order - * to find finally clauses to be executed, and that some errors in skipped - * commands are still reported. - */ +/// @return TRUE if a function with the "abort" flag should not be considered +/// ended on an error. This means that parsing commands is continued in order +/// to find finally clauses to be executed, and that some errors in skipped +/// commands are still reported. int aborted_in_try(void) { // This function is only called after an error. In this case, "force_abort" @@ -140,13 +135,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. +/// 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 { @@ -279,9 +276,7 @@ bool cause_errthrow(const char_u *mesg, bool severe, bool *ignore) } } -/* - * Free a "msg_list" and the messages it contains. - */ +/// Free a "msg_list" and the messages it contains. static void free_msglist(struct msglist *l) { struct msglist *messages, *next; @@ -295,21 +290,17 @@ static void free_msglist(struct msglist *l) } } -/* - * Free global "*msg_list" and the messages it contains, then set "*msg_list" - * to NULL. - */ +/// Free global "*msg_list" and the messages it contains, then set "*msg_list" +/// to NULL. void free_global_msglist(void) { free_msglist(*msg_list); *msg_list = NULL; } -/* - * Throw the message specified in the call to cause_errthrow() above as an - * error exception. If cstack is NULL, postpone the throw until do_cmdline() - * has returned (see do_one_cmd()). - */ +/// Throw the message specified in the call to cause_errthrow() above as an +/// error exception. If cstack is NULL, postpone the throw until do_cmdline() +/// has returned (see do_one_cmd()). void do_errthrow(cstack_T *cstack, char_u *cmdname) { /* @@ -339,11 +330,11 @@ void do_errthrow(cstack_T *cstack, char_u *cmdname) *msg_list = NULL; } -/* - * do_intthrow(): Replace the current exception by an interrupt or interrupt - * exception if appropriate. Return TRUE if the current exception is discarded, - * FALSE otherwise. - */ +/// do_intthrow(): Replace the current exception by an interrupt or interrupt +/// exception if appropriate. +/// +/// @return TRUE if the current exception is discarded or, +/// FALSE otherwise. int do_intthrow(cstack_T *cstack) { // If no interrupt occurred or no try conditional is active and no exception @@ -386,7 +377,7 @@ int do_intthrow(cstack_T *cstack) return true; } -// Get an exception message that is to be stored in current_exception->value. +/// Get an exception message that is to be stored in current_exception->value. char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int *should_free) { char *ret, *mesg; @@ -430,14 +421,14 @@ char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int STRCAT(val, p); p[-2] = NUL; - sprintf((char *)(val + STRLEN(p)), " (%s)", &mesg[1]); + snprintf(val + STRLEN(p), strlen(" (%s)"), " (%s)", &mesg[1]); p[-2] = '"'; } break; } } } else { - *should_free = FALSE; + *should_free = false; ret = value; } @@ -445,10 +436,12 @@ char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int } -// Throw a new exception. Return FAIL when out of memory or it was tried to -// throw an illegal user exception. "value" is the exception string for a -// user or interrupt exception, or points to a message list in case of an -// error exception. +/// Throw a new exception. "value" is the exception string for a +/// user or interrupt exception, or points to a message list in case of an +/// error exception. +/// +/// @return FAIL when out of memory or it was tried to throw an illegal user +/// exception. static int throw_exception(void *value, except_type_T type, char_u *cmdname) { except_T *excp; @@ -524,10 +517,8 @@ fail: return FAIL; } -/* - * Discard an exception. "was_finished" is set when the exception has been - * caught and the catch clause has been ended normally. - */ +/// Discard an exception. "was_finished" is set when the exception has been +/// caught and the catch clause has been ended normally. static void discard_exception(except_T *excp, bool was_finished) { char_u *saved_IObuff; @@ -579,9 +570,7 @@ static void discard_exception(except_T *excp, bool was_finished) xfree(excp); } -/* - * Discard the exception currently being thrown. - */ +/// Discard the exception currently being thrown. void discard_current_exception(void) { if (current_exception != NULL) { @@ -592,14 +581,12 @@ void discard_current_exception(void) need_rethrow = false; } -/* - * Put an exception on the caught stack. - */ +/// Put an exception on the caught stack. static void catch_exception(except_T *excp) { excp->caught = caught_stack; caught_stack = excp; - set_vim_var_string(VV_EXCEPTION, (char *)excp->value, -1); + set_vim_var_string(VV_EXCEPTION, excp->value, -1); if (*excp->throw_name != NUL) { if (excp->throw_lnum != 0) { vim_snprintf((char *)IObuff, IOSIZE, _("%s, line %" PRId64), @@ -640,9 +627,7 @@ static void catch_exception(except_T *excp) } } -/* - * Remove an exception from the caught stack. - */ +/// Remove an exception from the caught stack. static void finish_exception(except_T *excp) { if (excp != caught_stack) { @@ -650,7 +635,7 @@ static void finish_exception(except_T *excp) } caught_stack = caught_stack->caught; if (caught_stack != NULL) { - set_vim_var_string(VV_EXCEPTION, (char *)caught_stack->value, -1); + set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1); if (*caught_stack->throw_name != NUL) { if (caught_stack->throw_lnum != 0) { vim_snprintf((char *)IObuff, IOSIZE, @@ -682,13 +667,11 @@ static void finish_exception(except_T *excp) #define RP_RESUME 1 #define RP_DISCARD 2 -/* - * Report information about something pending in a finally clause if required by - * the 'verbose' option or when debugging. "action" tells whether something is - * made pending or something pending is resumed or discarded. "pending" tells - * what is pending. "value" specifies the return value for a pending ":return" - * or the exception value for a pending exception. - */ +/// Report information about something pending in a finally clause if required by +/// the 'verbose' option or when debugging. "action" tells whether something is +/// made pending or something pending is resumed or discarded. "pending" tells +/// what is pending. "value" specifies the return value for a pending ":return" +/// or the exception value for a pending exception. static void report_pending(int action, int pending, void *value) { char *mesg; @@ -733,7 +716,7 @@ static void report_pending(int action, int pending, void *value) vim_snprintf((char *)IObuff, IOSIZE, mesg, _("Exception")); mesg = (char *)concat_str(IObuff, (char_u *)": %s"); - s = (char *)((except_T *)value)->value; + s = ((except_T *)value)->value; } else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) { s = _("Error and interrupt"); } else if (pending & CSTP_ERROR) { @@ -764,10 +747,8 @@ static void report_pending(int action, int pending, void *value) } } -/* - * If something is made pending in a finally clause, report it if required by - * the 'verbose' option or when debugging. - */ +/// If something is made pending in a finally clause, report it if required by +/// the 'verbose' option or when debugging. void report_make_pending(int pending, void *value) { if (p_verbose >= 14 || debug_break_level > 0) { @@ -781,10 +762,8 @@ void report_make_pending(int pending, void *value) } } -/* - * If something pending in a finally clause is resumed at the ":endtry", report - * it if required by the 'verbose' option or when debugging. - */ +/// If something pending in a finally clause is resumed at the ":endtry", report +/// it if required by the 'verbose' option or when debugging. void report_resume_pending(int pending, void *value) { if (p_verbose >= 14 || debug_break_level > 0) { @@ -798,10 +777,8 @@ void report_resume_pending(int pending, void *value) } } -/* - * If something pending in a finally clause is discarded, report it if required - * by the 'verbose' option or when debugging. - */ +/// If something pending in a finally clause is discarded, report it if required +/// by the 'verbose' option or when debugging. void report_discard_pending(int pending, void *value) { if (p_verbose >= 14 || debug_break_level > 0) { @@ -815,7 +792,7 @@ void report_discard_pending(int pending, void *value) } } -// ":eval". +/// Handle ":eval". void ex_eval(exarg_T *eap) { typval_T tv; @@ -825,9 +802,7 @@ void ex_eval(exarg_T *eap) } } -/* - * ":if". - */ +/// Handle ":if". void ex_if(exarg_T *eap) { int skip; @@ -856,9 +831,7 @@ void ex_if(exarg_T *eap) } } -/* - * ":endif". - */ +/// Handle ":endif". void ex_endif(exarg_T *eap) { did_endif = true; @@ -883,16 +856,13 @@ void ex_endif(exarg_T *eap) } } -/* - * ":else" and ":elseif". - */ +/// Handle ":else" and ":elseif". void ex_else(exarg_T *eap) { - int skip; int result; cstack_T *const cstack = eap->cstack; - skip = CHECK_SKIP; + bool skip = CHECK_SKIP; if (cstack->cs_idx < 0 || (cstack->cs_flags[cstack->cs_idx] @@ -902,14 +872,14 @@ void ex_else(exarg_T *eap) return; } eap->errmsg = N_("E582: :elseif without :if"); - skip = TRUE; + skip = true; } else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE) { if (eap->cmdidx == CMD_else) { eap->errmsg = N_("E583: multiple :else"); return; } eap->errmsg = N_("E584: :elseif after :else"); - skip = TRUE; + skip = true; } // if skipping or the ":if" was TRUE, reset ACTIVE, otherwise set it @@ -917,7 +887,7 @@ void ex_else(exarg_T *eap) if (eap->errmsg == NULL) { cstack->cs_flags[cstack->cs_idx] = CSF_TRUE; } - skip = TRUE; // don't evaluate an ":elseif" + skip = true; // don't evaluate an ":elseif" } else { cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE; } @@ -932,7 +902,7 @@ void ex_else(exarg_T *eap) // later on. if (!skip && dbg_check_skipped(eap) && got_int) { (void)do_intthrow(cstack); - skip = TRUE; + skip = true; } if (eap->cmdidx == CMD_elseif) { @@ -959,9 +929,7 @@ void ex_else(exarg_T *eap) } } -/* - * Handle ":while" and ":for". - */ +/// Handle ":while" and ":for". void ex_while(exarg_T *eap) { bool error; @@ -1042,9 +1010,7 @@ void ex_while(exarg_T *eap) } } -/* - * ":continue" - */ +/// Handle ":continue" void ex_continue(exarg_T *eap) { int idx; @@ -1076,9 +1042,7 @@ void ex_continue(exarg_T *eap) } } -/* - * ":break" - */ +/// Handle ":break" void ex_break(exarg_T *eap) { int idx; @@ -1099,9 +1063,7 @@ void ex_break(exarg_T *eap) } } -/* - * ":endwhile" and ":endfor" - */ +/// Handle ":endwhile" and ":endfor" void ex_endwhile(exarg_T *eap) { cstack_T *const cstack = eap->cstack; @@ -1176,9 +1138,7 @@ void ex_endwhile(exarg_T *eap) } -/* - * ":throw expr" - */ +/// Handle ":throw expr" void ex_throw(exarg_T *eap) { const char *arg = (const char *)eap->arg; @@ -1203,11 +1163,9 @@ void ex_throw(exarg_T *eap) } } -/* - * Throw the current exception through the specified cstack. Common routine - * for ":throw" (user exception) and error and interrupt exceptions. Also - * used for rethrowing an uncaught exception. - */ +/// Throw the current exception through the specified cstack. Common routine +/// for ":throw" (user exception) and error and interrupt exceptions. Also +/// used for rethrowing an uncaught exception. void do_throw(cstack_T *cstack) { int idx; @@ -1264,9 +1222,7 @@ void do_throw(cstack_T *cstack) } } -/* - * ":try" - */ +/// Handle ":try" void ex_try(exarg_T *eap) { int skip; @@ -1316,15 +1272,13 @@ void ex_try(exarg_T *eap) } } -/* - * ":catch /{pattern}/" and ":catch" - */ +/// Handle ":catch /{pattern}/" and ":catch" void ex_catch(exarg_T *eap) { int idx = 0; - int give_up = FALSE; - int skip = FALSE; - int caught = FALSE; + bool give_up = false; + bool skip = false; + bool caught = false; char_u *end; char_u save_char = 0; char_u *save_cpo; @@ -1335,13 +1289,13 @@ void ex_catch(exarg_T *eap) if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { eap->errmsg = N_("E603: :catch without :try"); - give_up = TRUE; + give_up = true; } else { if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { // Report what's missing if the matching ":try" is not in its // finally clause. eap->errmsg = get_end_emsg(cstack); - skip = TRUE; + skip = true; } for (idx = cstack->cs_idx; idx > 0; --idx) { if (cstack->cs_flags[idx] & CSF_TRY) { @@ -1352,7 +1306,7 @@ void ex_catch(exarg_T *eap) // Give up for a ":catch" after ":finally" and ignore it. // Just parse. eap->errmsg = N_("E604: :catch after :finally"); - give_up = TRUE; + give_up = true; } else { rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, &cstack->cs_looplevel); @@ -1425,7 +1379,7 @@ void ex_catch(exarg_T *eap) // CTRL-C while matching should abort it. // prev_got_int = got_int; - got_int = FALSE; + got_int = false; caught = vim_regexec_nl(®match, (char_u *)current_exception->value, (colnr_T)0); got_int |= prev_got_int; @@ -1472,9 +1426,7 @@ void ex_catch(exarg_T *eap) } } -/* - * ":finally" - */ +/// Handle ":finally" void ex_finally(exarg_T *eap) { int idx; @@ -1596,15 +1548,12 @@ void ex_finally(exarg_T *eap) } } -/* - * ":endtry" - */ +/// Handle ":endtry" void ex_endtry(exarg_T *eap) { int idx; - int skip; - int rethrow = FALSE; - int pending = CSTP_NONE; + bool rethrow = false; + char pending = CSTP_NONE; void *rettv = NULL; cstack_T *const cstack = eap->cstack; @@ -1621,20 +1570,19 @@ void ex_endtry(exarg_T *eap) // made inactive by a ":continue", ":break", ":return", or ":finish" in // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. - skip = (did_emsg || got_int || current_exception - || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE)); + bool skip = did_emsg || got_int || current_exception + || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { eap->errmsg = get_end_emsg(cstack); // Find the matching ":try" and report what's missing. idx = cstack->cs_idx; do { - --idx; - } - while (idx > 0 && !(cstack->cs_flags[idx] & CSF_TRY)); + idx--; + } while (idx > 0 && !(cstack->cs_flags[idx] & CSF_TRY)); rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, &cstack->cs_looplevel); - skip = TRUE; + skip = true; /* * If an exception is being thrown, discard it to prevent it from @@ -1677,7 +1625,7 @@ void ex_endtry(exarg_T *eap) // before the ":endtry". That is, throw an interrupt exception and // set "skip" and "rethrow". if (got_int) { - skip = TRUE; + skip = true; (void)do_intthrow(cstack); // The do_intthrow() call may have reset current_exception or // cstack->cs_pending[idx]. @@ -1787,14 +1735,12 @@ void ex_endtry(exarg_T *eap) * error/interrupt/exception state. */ -/* - * This function works a bit like ex_finally() except that there was not - * actually an extra try block around the part that failed and an error or - * interrupt has not (yet) been converted to an exception. This function - * saves the error/interrupt/ exception state and prepares for the call to - * do_cmdline() that is going to be made for the cleanup autocommand - * execution. - */ +/// This function works a bit like ex_finally() except that there was not +/// actually an extra try block around the part that failed and an error or +/// interrupt has not (yet) been converted to an exception. This function +/// saves the error/interrupt/ exception state and prepares for the call to +/// do_cmdline() that is going to be made for the cleanup autocommand +/// execution. void enter_cleanup(cleanup_T *csp) { int pending = CSTP_NONE; @@ -1837,21 +1783,19 @@ void enter_cleanup(cleanup_T *csp) } } -/* - * See comment above enter_cleanup() for how this function is used. - * - * This function is a bit like ex_endtry() except that there was not actually - * an extra try block around the part that failed and an error or interrupt - * had not (yet) been converted to an exception when the cleanup autocommand - * sequence was invoked. - * - * This function has to be called with the address of the cleanup_T structure - * filled by enter_cleanup() as an argument; it restores the error/interrupt/ - * exception state saved by that function - except there was an aborting - * error, an interrupt or an uncaught exception during execution of the - * cleanup autocommands. In the latter case, the saved error/interrupt/ - * exception state is discarded. - */ +/// This function is a bit like ex_endtry() except that there was not actually +/// an extra try block around the part that failed and an error or interrupt +/// had not (yet) been converted to an exception when the cleanup autocommand +/// sequence was invoked. +/// +/// See comment above enter_cleanup() for how this function is used. +/// +/// This function has to be called with the address of the cleanup_T structure +/// filled by enter_cleanup() as an argument; it restores the error/interrupt/ +/// exception state saved by that function - except there was an aborting +/// error, an interrupt or an uncaught exception during execution of the +/// cleanup autocommands. In the latter case, the saved error/interrupt/ +/// exception state is discarded. void leave_cleanup(cleanup_T *csp) { int pending = csp->pending; @@ -1916,22 +1860,25 @@ void leave_cleanup(cleanup_T *csp) } -/* - * Make conditionals inactive and discard what's pending in finally clauses - * until the conditional type searched for or a try conditional not in its - * finally clause is reached. If this is in an active catch clause, finish - * the caught exception. - * Return the cstack index where the search stopped. - * Values used for "searched_cond" are (CSF_WHILE | CSF_FOR) or CSF_TRY or 0, - * the latter meaning the innermost try conditional not in its finally clause. - * "inclusive" tells whether the conditional searched for should be made - * inactive itself (a try conditional not in its finally clause possibly find - * before is always made inactive). If "inclusive" is TRUE and - * "searched_cond" is CSF_TRY|CSF_SILENT, the saved former value of - * "emsg_silent", if reset when the try conditional finally reached was - * entered, is restored (used by ex_endtry()). This is normally done only - * when such a try conditional is left. - */ +/// Make conditionals inactive and discard what's pending in finally clauses +/// until the conditional type searched for or a try conditional not in its +/// finally clause is reached. If this is in an active catch clause, finish +/// the caught exception. +/// +/// +/// @param searched_cond Possible values are (CSF_WHILE | CSF_FOR) or CSF_TRY or 0, +/// the latter meaning the innermost try conditional not +/// in its finally clause. +/// @param inclusive tells whether the conditional searched for should be made +/// inactive itself (a try conditional not in its finally +/// clause possibly find before is always made inactive). +/// +/// If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, the saved +/// former value of "emsg_silent", if reset when the try conditional finally +/// reached was entered, is restored (used by ex_endtry()). This is normally +/// done only when such a try conditional is left. +/// +/// @return the cstack index where the search stopped. int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) { int idx; @@ -2040,9 +1987,7 @@ int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) return idx; } -/* - * Return an appropriate error message for a missing endwhile/endfor/endif. - */ +/// @return an appropriate error message for a missing endwhile/endfor/endif. static char *get_end_emsg(cstack_T *cstack) { if (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE) { @@ -2055,13 +2000,11 @@ static char *get_end_emsg(cstack_T *cstack) } -/* - * Rewind conditionals until index "idx" is reached. "cond_type" and - * "cond_level" specify a conditional type and the address of a level variable - * which is to be decremented with each skipped conditional of the specified - * type. - * Also free "for info" structures where needed. - */ +/// Rewind conditionals until index "idx" is reached. "cond_type" and +/// "cond_level" specify a conditional type and the address of a level variable +/// which is to be decremented with each skipped conditional of the specified +/// type. +/// Also free "for info" structures where needed. void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_level) { while (cstack->cs_idx > idx) { @@ -2075,17 +2018,13 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev } } -/* - * ":endfunction" when not after a ":function" - */ +/// Handle ":endfunction" when not after a ":function" void ex_endfunction(exarg_T *eap) { emsg(_("E193: :endfunction not inside a function")); } -/* - * Return TRUE if the string "p" looks like a ":while" or ":for" command. - */ +/// @return TRUE if the string "p" looks like a ":while" or ":for" command. int has_loop_cmd(char_u *p) { int len; @@ -2107,4 +2046,3 @@ int has_loop_cmd(char_u *p) } return FALSE; } - diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index 25b30b2805..15da4b2d60 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -17,8 +17,8 @@ #define CSF_THROWN 0x0400 // exception thrown to this try conditional #define CSF_CAUGHT 0x0800 // exception caught by this try conditional #define CSF_SILENT 0x1000 // "emsg_silent" reset by ":try" -/* Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset - * (an ":if"), and CSF_SILENT is only used when CSF_TRY is set. */ +// Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset +// (an ":if"), and CSF_SILENT is only used when CSF_TRY is set. /* * What's pending for being reactivated at the ":endtry" of this try diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 3b5953ae69..a7c0b06050 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -35,6 +35,7 @@ #include "nvim/getchar.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" +#include "nvim/highlight_group.h" #include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/keymap.h" @@ -48,7 +49,6 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/ops.h" @@ -192,13 +192,12 @@ typedef struct command_line_state { typedef struct cmdline_info CmdlineInfo; -/* The current cmdline_info. It is initialized in getcmdline() and after that - * used by other functions. When invoking getcmdline() recursively it needs - * to be saved with save_cmdline() and restored with restore_cmdline(). - * TODO: make it local to getcmdline() and pass it around. */ +/// The current cmdline_info. It is initialized in getcmdline() and after that +/// used by other functions. When invoking getcmdline() recursively it needs +/// to be saved with save_cmdline() and restored with restore_cmdline(). static struct cmdline_info ccline; -static int cmd_showtail; // Only show path tail in lists ? +static int cmd_showtail; // Only show path tail in lists ? static int new_cmdpos; // position set by set_cmdline_pos() @@ -232,7 +231,7 @@ static int compl_selected; /// |:checkhealth| completion items /// -/// Regenerates on every new command line prompt, to accomodate changes on the +/// Regenerates on every new command line prompt, to accommodate changes on the /// runtime files. typedef struct { garray_T names; // healthcheck names @@ -312,7 +311,7 @@ static char_u *get_healthcheck_names(expand_T *xp, int idx) healthchecks.last_gen = last_prompt_id; } return idx < - (int)healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL; + healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL; } /// Transform healthcheck file path into it's name. @@ -633,7 +632,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat validate_cursor(); // May redraw the status line to show the cursor position. - if (p_ru && curwin->w_status_height > 0) { + if (p_ru && (curwin->w_status_height > 0 || global_stl_height() > 0)) { curwin->w_redr_status = true; } @@ -732,9 +731,10 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool /// Internal entry point for cmdline mode. /// -/// caller must use save_cmdline and restore_cmdline. Best is to use -/// getcmdline or getcmdline_prompt, instead of calling this directly. -static uint8_t *command_line_enter(int firstc, long count, int indent) +/// @param count only used for incremental search +/// @param indent indent for inside conditionals +/// @param init_ccline clear ccline first +static uint8_t *command_line_enter(int firstc, long count, int indent, bool init_ccline) { // can be invoked recursively, identify each level static int cmdline_level = 0; @@ -751,6 +751,20 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) CommandLineState *s = &state; s->save_p_icm = vim_strsave(p_icm); init_incsearch_state(&s->is_state); + CmdlineInfo save_ccline; + bool did_save_ccline = false; + + if (ccline.cmdbuff != NULL) { + // Currently ccline can never be in use if init_ccline is false. + // Some changes will be needed if this is no longer the case. + assert(init_ccline); + // Being called recursively. Since ccline is global, we need to save + // the current buffer and restore it when returning. + save_cmdline(&save_ccline); + did_save_ccline = true; + } else if (init_ccline) { + memset(&ccline, 0, sizeof(struct cmdline_info)); + } if (s->firstc == -1) { s->firstc = NUL; @@ -773,7 +787,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) ccline.cmdindent = (s->firstc > 0 ? s->indent : 0); // alloc initial ccline.cmdbuff - alloc_cmdbuff(exmode_active ? 250 : s->indent + 1); + alloc_cmdbuff(indent + 50); ccline.cmdlen = ccline.cmdpos = 0; ccline.cmdbuff[0] = NUL; @@ -880,7 +894,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) TryState tstate; Error err = ERROR_INIT; bool tl_ret = true; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; firstcbuf[0] = (char)(firstc > 0 ? firstc : '-'); firstcbuf[1] = 0; @@ -894,7 +909,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) apply_autocmds(EVENT_CMDLINEENTER, (char_u *)firstcbuf, (char_u *)firstcbuf, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); tl_ret = try_leave(&tstate, &err); @@ -906,6 +921,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } tl_ret = true; } + may_trigger_modechanged(); state_enter(&s->state); @@ -924,7 +940,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) if (tv_dict_get_number(dict, "abort") != 0) { s->gotesc = 1; } - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); } cmdmsg_rl = false; @@ -995,6 +1011,13 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } cmdline_level--; + + if (did_save_ccline) { + restore_cmdline(&save_ccline); + } else { + ccline.cmdbuff = NULL; + } + return p; } @@ -1023,11 +1046,13 @@ static int command_line_execute(VimState *state, int key) CommandLineState *s = (CommandLineState *)state; s->c = key; - if (s->c == K_EVENT || s->c == K_COMMAND) { + if (s->c == K_EVENT || s->c == K_COMMAND || s->c == K_LUA) { if (s->c == K_EVENT) { state_handle_k_event(); - } else { + } else if (s->c == K_COMMAND) { do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); + } else { + map_execute_lua(); } if (!cmdline_was_last_drawn) { @@ -1336,15 +1361,9 @@ static int command_line_execute(VimState *state, int key) s->c = get_expr_register(); if (s->c == '=') { - // Need to save and restore ccline. And set "textlock" - // to avoid nasty things like going to another buffer when - // evaluating an expression. - CmdlineInfo save_ccline; - save_cmdline(&save_ccline); textlock++; p = get_expr_line(); textlock--; - restore_cmdline(&save_ccline); if (p != NULL) { len = (int)STRLEN(p); @@ -1587,6 +1606,10 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ if (search_delim == ccline.cmdbuff[skiplen]) { pat = last_search_pattern(); + if (pat == NULL) { + restore_last_search_pattern(); + return FAIL; + } skiplen = 0; patlen = (int)STRLEN(pat); } else { @@ -1885,10 +1908,7 @@ static int command_line_handle_key(CommandLineState *s) beep_flush(); s->c = ESC; } else { - CmdlineInfo save_ccline; - save_cmdline(&save_ccline); s->c = get_expr_register(); - restore_cmdline(&save_ccline); } } @@ -2116,7 +2136,7 @@ static int command_line_handle_key(CommandLineState *s) int len = 0; int old_firstc; - xfree(ccline.cmdbuff); + XFREE_CLEAR(ccline.cmdbuff); s->xpc.xp_context = EXPAND_NOTHING; if (s->hiscnt == hislen) { p = s->lookfor; // back to the old one @@ -2289,7 +2309,8 @@ static int command_line_changed(CommandLineState *s) if (has_event(EVENT_CMDLINECHANGED)) { TryState tstate; Error err = ERROR_INIT; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-'); @@ -2303,7 +2324,7 @@ static int command_line_changed(CommandLineState *s) apply_autocmds(EVENT_CMDLINECHANGED, (char_u *)firstcbuf, (char_u *)firstcbuf, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); bool tl_ret = try_leave(&tstate, &err); if (!tl_ret && ERROR_SET(&err)) { @@ -2398,14 +2419,7 @@ static void abandon_cmdline(void) /// @param indent indent for inside conditionals char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED) { - // Be prepared for situations where cmdline can be invoked recursively. - // That includes cmd mappings, event handlers, as well as update_screen() - // (custom status line eval), which all may invoke ":normal :". - CmdlineInfo save_ccline; - save_cmdline(&save_ccline); - char_u *retval = command_line_enter(firstc, count, indent); - restore_cmdline(&save_ccline); - return retval; + return command_line_enter(firstc, count, indent, true); } /// Get a command line with a prompt @@ -2429,8 +2443,14 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, const int a const int msg_col_save = msg_col; CmdlineInfo save_ccline; - save_cmdline(&save_ccline); - + bool did_save_ccline = false; + if (ccline.cmdbuff != NULL) { + // Save the values of the current cmdline and restore them below. + save_cmdline(&save_ccline); + did_save_ccline = true; + } else { + memset(&ccline, 0, sizeof(struct cmdline_info)); + } ccline.prompt_id = last_prompt_id++; ccline.cmdprompt = (char_u *)prompt; ccline.cmdattr = attr; @@ -2442,9 +2462,11 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, const int a int msg_silent_saved = msg_silent; msg_silent = 0; - char *const ret = (char *)command_line_enter(firstc, 1L, 0); + char *const ret = (char *)command_line_enter(firstc, 1L, 0, false); - restore_cmdline(&save_ccline); + if (did_save_ccline) { + restore_cmdline(&save_ccline); + } msg_silent = msg_silent_saved; // Restore msg_col, the prompt from input() may have changed it. // But only if called recursively and the commandline is therefore being @@ -2488,27 +2510,25 @@ char *get_text_locked_msg(void) } /// Check if "curbuf->b_ro_locked" or "allbuf_lock" is set and -/// return TRUE when it is and give an error message. -int curbuf_locked(void) +/// return true when it is and give an error message. +bool curbuf_locked(void) { if (curbuf->b_ro_locked > 0) { emsg(_("E788: Not allowed to edit another buffer now")); - return TRUE; + return true; } return allbuf_locked(); } -/* - * Check if "allbuf_lock" is set and return TRUE when it is and give an error - * message. - */ -int allbuf_locked(void) +// Check if "allbuf_lock" is set and return true when it is and give an error +// message. +bool allbuf_locked(void) { if (allbuf_lock > 0) { emsg(_("E811: Not allowed to change buffer information now")); - return TRUE; + return true; } - return FALSE; + return false; } static int cmdline_charsize(int idx) @@ -2598,7 +2618,6 @@ bool cmdline_at_end(void) /* * Allocate a new command line buffer. * Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen. - * Returns the new value of ccline.cmdbuff and ccline.cmdbufflen. */ static void alloc_cmdbuff(int len) { @@ -3364,53 +3383,23 @@ void put_on_cmdline(char_u *str, int len, int redraw) } } -/* - * Save ccline, because obtaining the "=" register may execute "normal :cmd" - * and overwrite it. But get_cmdline_str() may need it, thus make it - * available globally in prev_ccline. - */ +/// Save ccline, because obtaining the "=" register may execute "normal :cmd" +/// and overwrite it. static void save_cmdline(struct cmdline_info *ccp) { *ccp = ccline; + memset(&ccline, 0, sizeof(struct cmdline_info)); ccline.prev_ccline = ccp; - ccline.cmdbuff = NULL; - ccline.cmdprompt = NULL; - ccline.xpc = NULL; - ccline.special_char = NUL; - ccline.level = 0; + ccline.cmdbuff = NULL; // signal that ccline is not in use } -/* - * Restore ccline after it has been saved with save_cmdline(). - */ +/// Restore ccline after it has been saved with save_cmdline(). static void restore_cmdline(struct cmdline_info *ccp) FUNC_ATTR_NONNULL_ALL { ccline = *ccp; } -/* - * Save the command line into allocated memory. Returns a pointer to be - * passed to restore_cmdline_alloc() later. - */ -char_u *save_cmdline_alloc(void) - FUNC_ATTR_NONNULL_RET -{ - struct cmdline_info *p = xmalloc(sizeof(struct cmdline_info)); - save_cmdline(p); - return (char_u *)p; -} - -/* - * Restore the command line from the return value of save_cmdline_alloc(). - */ -void restore_cmdline_alloc(char_u *p) - FUNC_ATTR_NONNULL_ALL -{ - restore_cmdline((struct cmdline_info *)p); - xfree(p); -} - /// Paste a yank register into the command line. /// Used by CTRL-R command in command-line mode. /// insert_reg() can't be used here, because special characters from the @@ -3426,7 +3415,6 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) char_u *arg; char_u *p; bool allocated; - struct cmdline_info save_ccline; // check for valid regname; also accept special characters for CTRL-R in // the command line @@ -3444,13 +3432,11 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) } - // Need to save and restore ccline. And set "textlock" to avoid nasty - // things like going to another buffer when evaluating an expression. - save_cmdline(&save_ccline); + // Need to set "textlock" to avoid nasty things like going to another + // buffer when evaluating an expression. textlock++; const bool i = get_spec_reg(regname, &arg, &allocated, true); textlock--; - restore_cmdline(&save_ccline); if (i) { // Got the value of a special register in "arg". @@ -3629,7 +3615,7 @@ void compute_cmdrow(void) } else { win_T *wp = lastwin_nofloating(); cmdline_row = wp->w_winrow + wp->w_height - + wp->w_status_height; + + wp->w_hsep_height + wp->w_status_height + global_stl_height(); } lines_left = cmdline_row; } @@ -4983,6 +4969,9 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u ** if (xp->xp_context == EXPAND_USER_LIST) { return ExpandUserList(xp, num_file, file); } + if (xp->xp_context == EXPAND_USER_LUA) { + return ExpandUserLua(xp, num_file, file); + } if (xp->xp_context == EXPAND_PACKADD) { return ExpandPackAddDir(pat, num_file, file); } @@ -5044,8 +5033,8 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u ** { EXPAND_SYNTAX, get_syntax_name, true, true }, { EXPAND_SYNTIME, get_syntime_arg, true, true }, { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true }, - { EXPAND_EVENTS, get_event_name, true, true }, - { EXPAND_AUGROUP, get_augroup_name, true, true }, + { EXPAND_EVENTS, expand_get_event_name, true, true }, + { EXPAND_AUGROUP, expand_get_augroup_name, true, true }, { EXPAND_CSCOPE, get_cscope_name, true, true }, { EXPAND_SIGN, get_sign_name, true, true }, { EXPAND_PROFILE, get_profile_name, true, true }, @@ -5301,7 +5290,6 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T typval_T args[4]; char_u *pat = NULL; const sctx_T save_current_sctx = current_sctx; - struct cmdline_info save_ccline; if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) { return NULL; @@ -5323,15 +5311,10 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T args[1].vval.v_string = xp->xp_line; args[2].vval.v_number = xp->xp_col; - // Save the cmdline, we don't know what the function may do. - save_ccline = ccline; - ccline.cmdbuff = NULL; - ccline.cmdprompt = NULL; current_sctx = xp->xp_script_ctx; void *const ret = user_expand_func(xp->xp_arg, 3, args); - ccline = save_ccline; current_sctx = save_current_sctx; if (ccline.cmdbuff != NULL) { ccline.cmdbuff[ccline.cmdlen] = keep; @@ -5411,6 +5394,35 @@ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file) return OK; } +static int ExpandUserLua(expand_T *xp, int *num_file, char_u ***file) +{ + typval_T rettv; + nlua_call_user_expand_func(xp, &rettv); + if (rettv.v_type != VAR_LIST) { + tv_clear(&rettv); + return FAIL; + } + + list_T *const retlist = rettv.vval.v_list; + + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 3); + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } + + GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); + tv_list_unref(retlist); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + /// Expand color scheme, compiler or filetype names. /// Search from 'runtimepath': /// 'runtimepath'/{dirnames}/{pat}.vim @@ -5912,10 +5924,8 @@ int get_history_idx(int histype) } -/* - * Get pointer to the command line info to use. cmdline_paste() may clear - * ccline and put the previous value in prev_ccline. - */ +/// Get pointer to the command line info to use. save_cmdline() may clear +/// ccline and put the previous value in ccline.prev_ccline. static struct cmdline_info *get_ccline_ptr(void) { if ((State & CMDLINE) == 0) { @@ -6315,6 +6325,11 @@ int hist_type2char(int type) return NUL; } +void cmdline_init(void) +{ + memset(&ccline, 0, sizeof(struct cmdline_info)); +} + /// Open a window on the current command line and history. Allow editing in /// the window. Returns when the window is closed. /// Returns: @@ -6323,7 +6338,6 @@ int hist_type2char(int type) /// K_IGNORE if editing continues static int open_cmdwin(void) { - struct cmdline_info save_ccline; bufref_T old_curbuf; bufref_T bufref; win_T *old_curwin = curwin; @@ -6424,9 +6438,6 @@ static int open_cmdwin(void) } redraw_later(curwin, SOME_VALID); - // Save the command line info, can be used recursively. - save_cmdline(&save_ccline); - // No Ex mode here! exmode_active = false; @@ -6464,8 +6475,6 @@ static int open_cmdwin(void) // Restore KeyTyped in case it is modified by autocommands KeyTyped = save_KeyTyped; - // Restore the command line info. - restore_cmdline(&save_ccline); cmdwin_type = 0; cmdwin_level = 0; @@ -6529,13 +6538,13 @@ static int open_cmdwin(void) set_bufref(&bufref, curbuf); win_goto(old_curwin); if (win_valid(wp) && wp != curwin) { - win_close(wp, true); + win_close(wp, true, false); } // win_close() may have already wiped the buffer when 'bh' is // set to 'wipe', autocommands may have closed other windows if (bufref_valid(&bufref) && bufref.br_buf != curbuf) { - close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false); + close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false, false); } // Restore window sizes. @@ -6547,11 +6556,19 @@ static int open_cmdwin(void) cmdmsg_rl = save_cmdmsg_rl; State = save_State; + may_trigger_modechanged(); setmouse(); return cmdwin_result; } +/// @return true if in the cmdwin, not editing the command line. +bool is_in_cmdwin(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return cmdwin_type != 0 && get_cmdline_type() == NUL; +} + /// Get script string /// /// Used for commands which accept either `:command script` or diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index f80a63560c..1235087500 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -28,7 +28,6 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/keymap.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -73,7 +72,7 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) n++; // restore height when not full height - if (wp->w_height + wp->w_status_height < topframe->fr_height + if (wp->w_height + wp->w_hsep_height + wp->w_status_height < topframe->fr_height && (fprintf(fd, "exe '%dresize ' . ((&lines * %" PRId64 " + %" PRId64 ") / %" PRId64 ")\n", @@ -99,10 +98,11 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) return OK; } -// Write commands to "fd" to recursively create windows for frame "fr", -// horizontally and vertically split. -// After the commands the last window in the frame is the current window. -// Returns FAIL when writing the commands to "fd" fails. +/// Write commands to "fd" to recursively create windows for frame "fr", +/// horizontally and vertically split. +/// After the commands the last window in the frame is the current window. +/// +/// @return FAIL when writing the commands to "fd" fails. static int ses_win_rec(FILE *fd, frame_T *fr) { frame_T *frc; @@ -145,8 +145,9 @@ static int ses_win_rec(FILE *fd, frame_T *fr) return OK; } -// Skip frames that don't contain windows we want to save in the Session. -// Returns NULL when there none. +/// Skip frames that don't contain windows we want to save in the Session. +/// +/// @return NULL when there none. static frame_T *ses_skipframe(frame_T *fr) { frame_T *frc; @@ -159,8 +160,8 @@ static frame_T *ses_skipframe(frame_T *fr) return frc; } -// Return true if frame "fr" has a window somewhere that we want to save in -// the Session. +/// @return true if frame "fr" has a window somewhere that we want to save in +/// the Session. static bool ses_do_frame(const frame_T *fr) FUNC_ATTR_NONNULL_ARG(1) { @@ -177,7 +178,7 @@ static bool ses_do_frame(const frame_T *fr) return false; } -/// Return non-zero if window "wp" is to be stored in the Session. +/// @return non-zero if window "wp" is to be stored in the Session. static int ses_do_win(win_T *wp) { if (wp->w_buffer->b_fname == NULL @@ -230,7 +231,7 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigne return OK; } -/// Gets the buffer name for `buf`. +/// @return the buffer name for `buf`. static char *ses_get_fname(buf_T *buf, unsigned *flagp) { // Use the short file name if the current directory is known at the time @@ -250,7 +251,8 @@ static char *ses_get_fname(buf_T *buf, unsigned *flagp) /// Write a buffer name to the session file. /// Also ends the line, if "add_eol" is true. -/// Returns FAIL if writing fails. +/// +/// @return FAIL if writing fails. static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) { char *name = ses_get_fname(buf, flagp); @@ -261,11 +263,11 @@ static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) return OK; } -// Escapes a filename for session writing. -// Takes care of "slash" flag in 'sessionoptions' and escapes special -// characters. -// -// Returns allocated string or NULL. +/// Escapes a filename for session writing. +/// Takes care of "slash" flag in 'sessionoptions' and escapes special +/// characters. +/// +/// @return allocated string or NULL. static char *ses_escape_fname(char *name, unsigned *flagp) { char *p; @@ -284,10 +286,11 @@ static char *ses_escape_fname(char *name, unsigned *flagp) return p; } -// Write a file name to the session file. -// Takes care of the "slash" option in 'sessionoptions' and escapes special -// characters. -// Returns FAIL if writing fails. +/// Write a file name to the session file. +/// Takes care of the "slash" option in 'sessionoptions' and escapes special +/// characters. +/// +/// @return FAIL if writing fails. static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) { char *p = ses_escape_fname((char *)name, flagp); @@ -339,14 +342,26 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr // Edit the file. Skip this when ":next" already did it. if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { - char *fname_esc = - ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); - // - // Load the file. - // - if (wp->w_buffer->b_ffname != NULL - && (!bt_nofile(wp->w_buffer) - || wp->w_buffer->terminal)) { + char *fname_esc = ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); + if (bt_help(wp->w_buffer)) { + char *curtag = ""; + + // A help buffer needs some options to be set. + // First, create a new empty buffer with "buftype=help". + // Then ":help" will re-use both the buffer and the window and set + // the options, even when "options" is not in 'sessionoptions'. + if (0 < wp->w_tagstackidx && wp->w_tagstackidx <= wp->w_tagstacklen) { + curtag = (char *)wp->w_tagstack[wp->w_tagstackidx - 1].tagname; + } + + if (put_line(fd, "enew | setl bt=help") == FAIL + || fprintf(fd, "help %s", curtag) < 0 || put_eol(fd) == FAIL) { + return FAIL; + } + } else if (wp->w_buffer->b_ffname != NULL + && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)) { + // Load the file. + // Editing a file in this buffer: use ":edit file". // This may have side effects! (e.g., compressed or network file). // @@ -354,7 +369,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr // edit that buffer, to not lose folding information (:edit resets // folds in other buffers) if (fprintf(fd, - "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n" + "if bufexists(fnamemodify(\"%s\", \":p\")) | buffer %s | else | edit %s | endif\n" // Fixup :terminal buffer name. #7836 "if &buftype ==# 'terminal'\n" " silent file %s\n" @@ -574,12 +589,36 @@ static int makeopens(FILE *fd, char_u *dirnow) "if expand('%') == '' && !&modified && line('$') <= 1" " && getline(1) == ''\n" " let s:wipebuf = bufnr('%')\n" - "endif\n" - // Now save the current files, current buffer first. - "set shortmess=aoO\n") < 0) { + "endif\n") < 0) { return FAIL; } + // save 'shortmess' if not storing options + if ((ssop_flags & SSOP_OPTIONS) == 0) { + PUTLINE_FAIL("let s:shortmess_save = &shortmess"); + } + + // Now save the current files, current buffer first. + PUTLINE_FAIL("set shortmess=aoO"); + + // Put all buffers into the buffer list. + // Do it very early to preserve buffer order after loading session (which + // can be disrupted by prior `edit` or `tabedit` calls). + FOR_ALL_BUFFERS(buf) { + if (!(only_save_windows && buf->b_nwindows == 0) + && !(buf->b_help && !(ssop_flags & SSOP_HELP)) + && buf->b_fname != NULL + && buf->b_p_bl) { + if (fprintf(fd, "badd +%" PRId64 " ", + buf->b_wininfo == NULL + ? (int64_t)1L + : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 + || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { + return FAIL; + } + } + } + // the global argument list if (ses_arglist(fd, "argglobal", &global_alist.al_ga, !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { @@ -615,7 +654,10 @@ static int makeopens(FILE *fd, char_u *dirnow) // Similar to ses_win_rec() below, populate the tab pages first so // later local options won't be copied to the new tabs. FOR_ALL_TABS(tp) { - if (tp->tp_next != NULL && put_line(fd, "tabnew") == FAIL) { + // Use `bufhidden=wipe` to remove empty "placeholder" buffers once + // they are not needed. This prevents creating extra buffers (see + // cause of Vim patch 8.1.0829) + if (tp->tp_next != NULL && put_line(fd, "tabnew +setlocal\\ bufhidden=wipe") == FAIL) { return FAIL; } } @@ -793,25 +835,6 @@ static int makeopens(FILE *fd, char_u *dirnow) return FAIL; } - // Now put the remaining buffers into the buffer list. - // This is near the end, so that when 'hidden' is set we don't create extra - // buffers. If the buffer was already created with another command the - // ":badd" will have no effect. - FOR_ALL_BUFFERS(buf) { - if (!(only_save_windows && buf->b_nwindows == 0) - && !(buf->b_help && !(ssop_flags & SSOP_HELP)) - && buf->b_fname != NULL - && buf->b_p_bl) { - if (fprintf(fd, "badd +%" PRId64 " ", - buf->b_wininfo == NULL - ? (int64_t)1L - : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 - || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { - return FAIL; - } - } - } - // // Wipe out an empty unnamed buffer we started in. // @@ -825,15 +848,21 @@ static int makeopens(FILE *fd, char_u *dirnow) return FAIL; } - // Re-apply 'winheight', 'winwidth' and 'shortmess'. - if (fprintf(fd, - "set winheight=%" PRId64 " winwidth=%" PRId64 - " shortmess=%s\n", - (int64_t)p_wh, - (int64_t)p_wiw, - p_shm) < 0) { + // Re-apply 'winheight' and 'winwidth'. + if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 "\n", + (int64_t)p_wh, (int64_t)p_wiw) < 0) { return FAIL; } + + // Restore 'shortmess'. + if (ssop_flags & SSOP_OPTIONS) { + if (fprintf(fd, "set shortmess=%s\n", p_shm) < 0) { + return FAIL; + } + } else { + PUTLINE_FAIL("let &shortmess = s:shortmess_save"); + } + if (tab_firstwin != NULL && tab_firstwin->w_next != NULL) { // Restore 'winminheight' and 'winminwidth'. PUTLINE_FAIL("let &winminheight = s:save_winminheight"); @@ -1039,7 +1068,7 @@ void ex_mkrc(exarg_T *eap) xfree(viewFile); } -/// Get the name of the view file for the current buffer. +/// @return the name of the view file for the current buffer. static char *get_view_file(int c) { if (curbuf->b_ffname == NULL) { @@ -1087,7 +1116,7 @@ static char *get_view_file(int c) return retval; } -// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +/// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. int put_eol(FILE *fd) { if (putc('\n', fd) < 0) { @@ -1096,7 +1125,7 @@ int put_eol(FILE *fd) return OK; } -// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +/// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. int put_line(FILE *fd, char *s) { if (fprintf(fd, "%s\n", s) < 0) { diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index c4d8f75a21..a3fcc7d784 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -48,28 +48,31 @@ # include "extmark.c.generated.h" #endif -static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) +static uint32_t *buf_ns_ref(buf_T *buf, uint32_t ns_id, bool put) { - return map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put); + return map_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, put); } /// Create or update an extmark /// /// must not be used during iteration! -/// @returns the internal mark id -uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T col, int end_row, - colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity, - ExtmarkOp op) +void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row, + colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity, + ExtmarkOp op) { - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); - assert(ns != NULL); - mtpos_t old_pos; - uint64_t mark = 0; - uint64_t id = idp ? *idp : 0; + uint32_t *ns = buf_ns_ref(buf, ns_id, true); + uint32_t id = idp ? *idp : 0; + bool decor_full = false; uint8_t decor_level = kDecorLevelNone; // no decor if (decor) { + if (kv_size(decor->virt_text) + || kv_size(decor->virt_lines) + || decor_has_sign(decor)) { + decor_full = true; + decor = xmemdup(decor, sizeof *decor); + } decor_level = kDecorLevelVisible; // decor affects redraw if (kv_size(decor->virt_lines)) { decor_level = kDecorLevelVirtLine; // decor affects horizontal size @@ -77,74 +80,94 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T } if (id == 0) { - id = ns->free_id++; + id = ++*ns; } else { - uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id); - if (old_mark) { - if (old_mark & MARKTREE_PAIRED_FLAG || end_row > -1) { + MarkTreeIter itr[1] = { 0 }; + mtkey_t old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); + if (old_mark.id) { + if (mt_paired(old_mark) || end_row > -1) { extmark_del(buf, ns_id, id); } else { - MarkTreeIter itr[1] = { 0 }; - old_pos = marktree_lookup(buf->b_marktree, old_mark, itr); + // TODO(bfredl): we need to do more if "revising" a decoration mark. assert(itr->node); - if (old_pos.row == row && old_pos.col == col) { - ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, - old_mark); - if (it.decor) { - decor_remove(buf, row, row, it.decor); + if (old_mark.pos.row == row && old_mark.pos.col == col) { + if (marktree_decor_level(old_mark) > kDecorLevelNone) { + decor_remove(buf, row, row, old_mark.decor_full); + old_mark.decor_full = NULL; + } + old_mark.flags = 0; + if (decor_full) { + old_mark.decor_full = decor; + } else if (decor) { + old_mark.hl_id = decor->hl_id; + // Workaround: the gcc compiler of functionaltest-lua build + // apparently incapable of handling basic integer constants. + // This can be underanged as soon as we bump minimal gcc version. + old_mark.flags = (uint16_t)(old_mark.flags + | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0)); + old_mark.priority = decor->priority; } - mark = marktree_revise(buf->b_marktree, itr, decor_level); + marktree_revise(buf->b_marktree, itr, decor_level, old_mark); goto revised; } marktree_del_itr(buf->b_marktree, itr, false); } } else { - ns->free_id = MAX(ns->free_id, id+1); + *ns = MAX(*ns, id); } } - if (end_row > -1) { - mark = marktree_put_pair(buf->b_marktree, - row, col, right_gravity, - end_row, end_col, end_right_gravity, decor_level); - } else { - mark = marktree_put(buf->b_marktree, row, col, right_gravity, decor_level); + mtkey_t mark = { { row, col }, ns_id, id, 0, + mt_flags(right_gravity, decor_level), 0, NULL }; + if (decor_full) { + mark.decor_full = decor; + } else if (decor) { + mark.hl_id = decor->hl_id; + // workaround: see above + mark.flags = (uint16_t)(mark.flags | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0)); + mark.priority = decor->priority; } -revised: - map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, - (ExtmarkItem){ ns_id, id, decor }); - map_put(uint64_t, uint64_t)(ns->map, id, mark); + marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity); +revised: if (op != kExtmarkNoUndo) { // TODO(bfredl): this doesn't cover all the cases and probably shouldn't // be done "prematurely". Any movement in undo history might necessitate - // adding new marks to old undo headers. - u_extmark_set(buf, mark, row, col); + // adding new marks to old undo headers. add a test case for this (doesn't + // fail extmark_spec.lua, and it should) + uint64_t mark_id = mt_lookup_id(ns_id, id, false); + u_extmark_set(buf, mark_id, row, col); } if (decor) { if (kv_size(decor->virt_lines)) { buf->b_virt_line_blocks++; } + if (decor_has_sign(decor)) { + buf->b_signs++; + } + if (decor->sign_text) { + // TODO(lewis6991): smarter invalidation + buf_signcols_add_check(buf, NULL); + } decor_redraw(buf, row, end_row > -1 ? end_row : row, decor); } if (idp) { *idp = id; } - return mark; } static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) { MarkTreeIter itr[1] = { 0 }; - mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); - if (pos.row == -1) { + mtkey_t key = marktree_lookup(buf->b_marktree, mark, itr); + if (key.pos.row == -1) { return false; } - if (pos.row == row && pos.col == col) { + if (key.pos.row == row && key.pos.col == col) { return true; } @@ -152,47 +175,38 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) return true; } -// Remove an extmark -// Returns 0 on missing id -bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) +/// Remove an extmark +/// +/// @return 0 on missing id +bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id) { - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); - if (!ns) { - return false; - } - - uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); - if (!mark) { + MarkTreeIter itr[1] = { 0 }; + mtkey_t key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); + if (!key.id) { return false; } - - MarkTreeIter itr[1] = { 0 }; - mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); - assert(pos.row >= 0); + assert(key.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) { - pos2 = marktree_lookup(buf->b_marktree, mark|MARKTREE_END_FLAG, itr); - assert(pos2.row >= 0); + mtkey_t key2 = key; + + if (mt_paired(key)) { + key2 = marktree_lookup_ns(buf->b_marktree, ns_id, id, true, itr); + assert(key2.pos.row >= 0); marktree_del_itr(buf->b_marktree, itr, false); } - if (item.decor) { - decor_remove(buf, pos.row, pos2.row, item.decor); + if (marktree_decor_level(key) > kDecorLevelNone) { + decor_remove(buf, key.pos.row, key2.pos.row, key.decor_full); } - 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; } -// Free extmarks in a ns between lines -// if ns = 0, it means clear all namespaces -bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col) +/// Free extmarks in a ns between lines +/// if ns = 0, it means clear all namespaces +bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col) { if (!map_size(buf->b_extmark_ns)) { return false; @@ -201,68 +215,58 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r bool marks_cleared = false; bool all_ns = (ns_id == 0); - ExtmarkNs *ns = NULL; + uint32_t *ns = NULL; if (!all_ns) { ns = buf_ns_ref(buf, ns_id, false); if (!ns) { // nothing to do return false; } - - // TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes - // it could be faster to iterate over the map instead } // the value is either zero or the lnum (row+1) if highlight was present. static Map(uint64_t, ssize_t) delete_set = MAP_INIT; - typedef struct { Decoration *decor; int row1; } DecorItem; + typedef struct { int row1; } DecorItem; static kvec_t(DecorItem) decors; MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, l_row, l_col, itr); while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 - || mark.row > u_row - || (mark.row == u_row && mark.col > u_col)) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 + || mark.pos.row > u_row + || (mark.pos.row == u_row && mark.pos.col > u_col)) { break; } - ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mark.id, + ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark), false); if (del_status) { marktree_del_itr(buf->b_marktree, itr, false); if (*del_status >= 0) { // we had a decor_id DecorItem it = kv_A(decors, *del_status); - decor_remove(buf, it.row1, mark.row, it.decor); + decor_remove(buf, it.row1, mark.pos.row, mark.decor_full); } - map_del(uint64_t, ssize_t)(&delete_set, mark.id); + map_del(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark)); continue; } - uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; - ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, - start_id); - - assert(item.ns_id > 0 && item.mark_id > 0); - if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) { + assert(mark.ns > 0 && mark.id > 0); + if (mark.ns == ns_id || all_ns) { marks_cleared = true; - if (mark.id & MARKTREE_PAIRED_FLAG) { - uint64_t other = mark.id ^ MARKTREE_END_FLAG; + if (mt_paired(mark)) { + uint64_t other = mt_lookup_id(mark.ns, mark.id, !mt_end(mark)); ssize_t decor_id = -1; - if (item.decor) { + if (marktree_decor_level(mark) > kDecorLevelNone) { // 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 })); + ((DecorItem) { .row1 = mark.pos.row })); } map_put(uint64_t, ssize_t)(&delete_set, other, decor_id); - } else if (item.decor) { - decor_remove(buf, mark.row, mark.row, item.decor); + } else if (mark.decor_full) { + decor_remove(buf, mark.pos.row, mark.pos.row, mark.decor_full); } - 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); - map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id); marktree_del_itr(buf->b_marktree, itr, false); } else { marktree_itr_next(buf->b_marktree, itr); @@ -271,12 +275,12 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r uint64_t id; ssize_t decor_id; map_foreach(&delete_set, id, decor_id, { - mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr); + mtkey_t mark = marktree_lookup(buf->b_marktree, id, itr); assert(itr->node); marktree_del_itr(buf->b_marktree, itr, false); if (decor_id >= 0) { DecorItem it = kv_A(decors, decor_id); - decor_remove(buf, it.row1, pos.row, it.decor); + decor_remove(buf, it.row1, mark.pos.row, mark.decor_full); } }); map_clear(uint64_t, ssize_t)(&delete_set); @@ -284,13 +288,14 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r return marks_cleared; } -// Returns the position of marks between a range, -// marks found at the start or end index will be included, -// if upper_lnum or upper_col are negative the buffer -// will be searched to the start, or end -// dir can be set to control the order of the array -// amount = amount of marks to find or -1 for all -ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row, +/// @return the position of marks between a range, +/// marks found at the start or end index will be included. +/// +/// if upper_lnum or upper_col are negative the buffer +/// will be searched to the start, or end +/// dir can be set to control the order of the array +/// amount = amount of marks to find or -1 for all +ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col, int64_t amount, bool reverse) { ExtmarkInfoArray array = KV_INITIAL_VALUE; @@ -300,30 +305,26 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_co 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)) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 + || (mark.pos.row - u_row) * order > 0 + || (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) { break; } - if (mark.id & MARKTREE_END_FLAG) { + if (mt_end(mark)) { 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, - .end_row = endpos.row, - .end_col = endpos.col, - .decor = item.decor })); + if (mark.ns == ns_id) { + mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL); + kv_push(array, ((ExtmarkInfo) { .ns_id = mark.ns, + .mark_id = mark.id, + .row = mark.pos.row, .col = mark.pos.col, + .end_row = end.pos.row, + .end_col = end.pos.col, + .right_gravity = mt_right(mark), + .end_right_gravity = mt_right(end), + .decor = get_decor(mark) })); } next_mark: if (reverse) { @@ -335,72 +336,62 @@ next_mark: return array; } -// Lookup an extmark by id -ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id) +/// Lookup an extmark by id +ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id) { - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); - ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, NULL }; - if (!ns) { + ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, false, false, DECORATION_INIT }; + mtkey_t mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL); + if (!mark.id) { return ret; } - - uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); - if (!mark) { - return ret; - } - - 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); + assert(mark.pos.row >= 0); + mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL); 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; + ret.row = mark.pos.row; + ret.col = mark.pos.col; + ret.end_row = end.pos.row; + ret.end_col = end.pos.col; + ret.right_gravity = mt_right(mark); + ret.end_right_gravity = mt_right(end); + ret.decor = get_decor(mark); return ret; } -// free extmarks from the buffer +/// free extmarks from the buffer void extmark_free_all(buf_T *buf) { if (!map_size(buf->b_extmark_ns)) { return; } - uint64_t id; - ExtmarkNs ns; - ExtmarkItem item; + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, 0, 0, itr); + while (true) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0) { + break; + } - marktree_clear(buf->b_marktree); + // don't free mark.decor_full twice for a paired mark. + if (!(mt_paired(mark) && mt_end(mark))) { + decor_free(mark.decor_full); + } - map_foreach(buf->b_extmark_ns, id, ns, { - (void)id; - map_destroy(uint64_t, uint64_t)(ns.map); - }); - map_destroy(uint64_t, ExtmarkNs)(buf->b_extmark_ns); - map_init(uint64_t, ExtmarkNs, buf->b_extmark_ns); + marktree_itr_next(buf->b_marktree, itr); + } - map_foreach(buf->b_extmark_index, id, item, { - (void)id; - decor_free(item.decor); - }); - map_destroy(uint64_t, ExtmarkItem)(buf->b_extmark_index); - map_init(uint64_t, ExtmarkItem, buf->b_extmark_index); + marktree_clear(buf->b_marktree); + + map_destroy(uint32_t, uint32_t)(buf->b_extmark_ns); + map_init(uint32_t, uint32_t, buf->b_extmark_ns); } -// Save info for undo/redo of set marks +/// Save info for undo/redo of set marks static void u_extmark_set(buf_T *buf, uint64_t mark, int row, colnr_T col) { u_header_T *uhp = u_force_get_undo_header(buf); @@ -437,16 +428,16 @@ void u_extmark_copy(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_c MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, l_row, l_col, itr); while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 - || mark.row > u_row - || (mark.row == u_row && mark.col > u_col)) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 + || mark.pos.row > u_row + || (mark.pos.row == u_row && mark.pos.col > u_col)) { break; } ExtmarkSavePos pos; - pos.mark = mark.id; - pos.old_row = mark.row; - pos.old_col = mark.col; + pos.mark = mt_lookup_key(mark); + pos.old_row = mark.pos.row; + pos.old_col = mark.pos.col; pos.row = -1; pos.col = -1; @@ -510,7 +501,7 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) } -// Adjust extmark row for inserted/deleted rows (columns stay fixed). +/// Adjust extmark row for inserted/deleted rows (columns stay fixed). void extmark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, long amount, long amount_after, ExtmarkOp undo) { diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index c70db9f7aa..b856a1148f 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -2,6 +2,7 @@ #define NVIM_EXTMARK_H #include "nvim/buffer_defs.h" +#include "nvim/decoration.h" #include "nvim/extmark_defs.h" #include "nvim/marktree.h" #include "nvim/pos.h" @@ -15,7 +16,9 @@ typedef struct { colnr_T col; int end_row; colnr_T end_col; - Decoration *decor; + bool right_gravity; + bool end_right_gravity; + Decoration decor; // TODO(bfredl): CHONKY } ExtmarkInfo; typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray; diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index bbe8504ebf..5570b5c71e 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -4,23 +4,11 @@ #include "nvim/lib/kvec.h" #include "nvim/types.h" -typedef struct Decoration Decoration; - typedef struct { char *text; int hl_id; } VirtTextChunk; - -typedef struct { - uint64_t ns_id; - uint64_t mark_id; - // TODO(bfredl): a lot of small allocations. Should probably use - // kvec_t(Decoration) as an arena. Alternatively, store ns_id/mark_id - // _inline_ in MarkTree and use the map only for decorations. - Decoration *decor; -} ExtmarkItem; - typedef struct undo_object ExtmarkUndoObject; typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 3a9acbd61c..15f1d3d065 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -57,7 +57,6 @@ #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" #include "nvim/os/input.h" @@ -484,8 +483,14 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le int len = 0; if (p > search_ctx->ffsc_fix_path) { + // do not add '..' to the path and start upwards searching len = (int)(p - search_ctx->ffsc_fix_path) - 1; - STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len); + if ((len >= 2 && STRNCMP(search_ctx->ffsc_fix_path, "..", 2) == 0) + && (len == 2 || search_ctx->ffsc_fix_path[2] == PATHSEP)) { + xfree(buf); + goto error_return; + } + STRLCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, eb_len + (size_t)len + 1); add_pathsep((char *)ff_expand_buffer); } else { len = (int)STRLEN(search_ctx->ffsc_fix_path); @@ -524,9 +529,7 @@ error_return: return NULL; } -/* - * Get the stopdir string. Check that ';' is not escaped. - */ +/// @return the stopdir string. Check that ';' is not escaped. char_u *vim_findfile_stopdir(char_u *buf) { char_u *r_ptr = buf; @@ -549,9 +552,7 @@ char_u *vim_findfile_stopdir(char_u *buf) return r_ptr; } -/* - * Clean up the given search context. Can handle a NULL pointer. - */ +/// Clean up the given search context. Can handle a NULL pointer. void vim_findfile_cleanup(void *ctx) { if (ctx == NULL) { @@ -563,18 +564,19 @@ void vim_findfile_cleanup(void *ctx) xfree(ctx); } -/* - * Find a file in a search context. - * The search context was created with vim_findfile_init() above. - * Return a pointer to an allocated file name or NULL if nothing found. - * To get all matching files call this function until you get NULL. - * - * If the passed search_context is NULL, NULL is returned. - * - * The search algorithm is depth first. To change this replace the - * stack with a list (don't forget to leave partly searched directories on the - * top of the list). - */ +/// Find a file in a search context. +/// The search context was created with vim_findfile_init() above. +/// +/// To get all matching files call this function until you get NULL. +/// +/// If the passed search_context is NULL, NULL is returned. +/// +/// The search algorithm is depth first. To change this replace the +/// stack with a list (don't forget to leave partly searched directories on the +/// top of the list). +/// +/// @return a pointer to an allocated file name or, +/// NULL if nothing found. char_u *vim_findfile(void *search_ctx_arg) { char_u *file_path; @@ -608,7 +610,7 @@ char_u *vim_findfile(void *search_ctx_arg) for (;;) { // downward search loop for (;;) { - // check if user user wants to stop the search + // check if user wants to stop the search os_breakcheck(); if (got_int) { break; @@ -994,10 +996,8 @@ fail: return NULL; } -/* - * Free the list of lists of visited files and directories - * Can handle it if the passed search_context is NULL; - */ +/// Free the list of lists of visited files and directories +/// Can handle it if the passed search_context is NULL; void vim_findfile_free_visited(void *search_ctx_arg) { ff_search_ctx_T *search_ctx; @@ -1039,10 +1039,8 @@ static void ff_free_visited_list(ff_visited_T *vl) vl = NULL; } -/* - * Returns the already visited list for the given filename. If none is found it - * allocates a new one. - */ +/// @return the already visited list for the given filename. If none is found it +/// allocates a new one. static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, ff_visited_list_hdr_T **list_headp) { @@ -1089,13 +1087,13 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, return retptr; } -// Check if two wildcard paths are equal. -// They are equal if: -// - both paths are NULL -// - they have the same length -// - char by char comparison is OK -// - the only differences are in the counters behind a '**', so -// '**\20' is equal to '**\24' +/// Check if two wildcard paths are equal. +/// They are equal if: +/// - both paths are NULL +/// - they have the same length +/// - char by char comparison is OK +/// - the only differences are in the counters behind a '**', so +/// '**\20' is equal to '**\24' static bool ff_wc_equal(char_u *s1, char_u *s2) { int i, j; @@ -1129,18 +1127,17 @@ static bool ff_wc_equal(char_u *s1, char_u *s2) return s1[i] == s2[j]; } -/* - * maintains the list of already visited files and dirs - * returns FAIL if the given file/dir is already in the list - * returns OK if it is newly added - */ +/// maintains the list of already visited files and dirs +/// +/// @return FAIL if the given file/dir is already in the list or, +/// OK if it is newly added static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u *wc_path) { ff_visited_T *vp; bool url = false; FileID file_id; - // For an URL we only compare the name, otherwise we compare the + // For a URL we only compare the name, otherwise we compare the // device/inode. if (path_with_url((char *)fname)) { STRLCPY(ff_expand_buffer, fname, MAXPATHL); @@ -1191,9 +1188,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u * return OK; } -/* - * create stack element from given path pieces - */ +/// create stack element from given path pieces static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, int level, int star_star_empty) { @@ -1221,9 +1216,7 @@ static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, in return new; } -/* - * Push a dir on the directory stack. - */ +/// Push a dir on the directory stack. static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr) { /* check for NULL pointer, not to return an error to the user, but @@ -1234,10 +1227,9 @@ static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr) } } -/* - * Pop a dir from the directory stack. - * Returns NULL if stack is empty. - */ +/// Pop a dir from the directory stack. +/// +/// @return NULL if stack is empty. static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx) { ff_stack_T *sptr; @@ -1250,9 +1242,7 @@ static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx) return sptr; } -/* - * free the given stack element - */ +/// free the given stack element static void ff_free_stack_element(ff_stack_T *const stack_ptr) { if (stack_ptr == NULL) { @@ -1270,9 +1260,7 @@ static void ff_free_stack_element(ff_stack_T *const stack_ptr) xfree(stack_ptr); } -/* - * Clear the search context, but NOT the visited list. - */ +/// Clear the search context, but NOT the visited list. static void ff_clear(ff_search_ctx_T *search_ctx) { ff_stack_T *sptr; @@ -1306,10 +1294,9 @@ static void ff_clear(ff_search_ctx_T *search_ctx) search_ctx->ffsc_level = 0; } -/* - * check if the given path is in the stopdirs - * returns TRUE if yes else FALSE - */ +/// check if the given path is in the stopdirs +/// +/// @return TRUE if yes else FALSE static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) { int i = 0; @@ -1434,7 +1421,11 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first rel_fname = NULL; } - if (first == TRUE) { + if (first == true) { + if (len == 0) { + return NULL; + } + // copy file name into NameBuff, expanding environment variables save_char = ptr[len]; ptr[len] = NUL; @@ -1591,11 +1582,13 @@ theend: return file_name; } -void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) +void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre) { static bool recursive = false; - if (recursive || !has_event(EVENT_DIRCHANGED)) { + event_T event = pre ? EVENT_DIRCHANGEDPRE : EVENT_DIRCHANGED; + + if (recursive || !has_event(event)) { // No autocommand was defined or we changed // the directory from this autocommand. return; @@ -1603,7 +1596,8 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) recursive = true; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); char buf[8]; switch (scope) { @@ -1628,8 +1622,12 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) new_dir = new_dir_buf; #endif + if (pre) { + tv_dict_add_str(dict, S_LEN("directory"), new_dir); + } else { + tv_dict_add_str(dict, S_LEN("cwd"), new_dir); + } 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"), cause == kCdCauseWindow); tv_dict_set_keys_readonly(dict); @@ -1645,17 +1643,17 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) abort(); } - apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, - curbuf); + apply_autocmds(event, (char_u *)buf, (char_u *)new_dir, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); recursive = false; } /// Change to a file's directory. /// Caller must call shorten_fnames()! -/// @return OK or FAIL +/// +/// @return OK or FAIL int vim_chdirfile(char_u *fname, CdCause cause) { char dir[MAXPATHL]; @@ -1667,14 +1665,23 @@ int vim_chdirfile(char_u *fname, CdCause cause) NameBuff[0] = NUL; } - if (os_chdir(dir) == 0) { - if (cause != kCdCauseOther && pathcmp(dir, (char *)NameBuff, -1) != 0) { - do_autocmd_dirchanged(dir, kCdScopeWindow, cause); - } - } else { + if (pathcmp(dir, (char *)NameBuff, -1) == 0) { + // nothing to do + return OK; + } + + if (cause != kCdCauseOther) { + do_autocmd_dirchanged(dir, kCdScopeWindow, cause, true); + } + + if (os_chdir(dir) != 0) { return FAIL; } + if (cause != kCdCauseOther) { + do_autocmd_dirchanged(dir, kCdScopeWindow, cause, false); + } + return OK; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 7573064fa9..d4407b4982 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3,6 +3,8 @@ // fileio.c: read from and write to a file +// uncrustify:off + #include <assert.h> #include <errno.h> #include <fcntl.h> @@ -20,6 +22,7 @@ #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/eval/userfunc.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" @@ -30,12 +33,12 @@ #include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/iconv.h" +#include "nvim/input.h" #include "nvim/mbyte.h" #include "nvim/memfile.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -77,7 +80,7 @@ #define FIO_ENDIAN_L 0x80 // little endian #define FIO_NOCONVERT 0x2000 // skip encoding conversion #define FIO_UCSBOM 0x4000 // check for BOM at start of file -#define FIO_ALL -1 // allow all formats +#define FIO_ALL (-1) // allow all formats /* When converting, a read() or write() may leave some bytes to be converted * for the next call. The value is guessed... */ @@ -100,8 +103,8 @@ struct bw_info { char_u bw_rest[CONV_RESTLEN]; // not converted bytes int bw_restlen; // nr of bytes in bw_rest[] int bw_first; // first write call - char_u *bw_conv_buf; // buffer for writing converted chars - int bw_conv_buflen; // size of bw_conv_buf + char_u *bw_conv_buf; // buffer for writing converted chars + size_t bw_conv_buflen; // size of bw_conv_buf int bw_conv_error; // set for conversion error linenr_T bw_conv_error_lnum; // first line with error or zero linenr_T bw_start_lnum; // line number at start of buffer @@ -242,6 +245,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski bool notconverted = false; // true if conversion wanted but it wasn't possible char_u conv_rest[CONV_RESTLEN]; int conv_restlen = 0; // nr of bytes in conv_rest[] + pos_T orig_start; buf_T *old_curbuf; char_u *old_b_ffname; char_u *old_b_fname; @@ -298,14 +302,10 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski fname = sfname; #endif - /* - * The BufReadCmd and FileReadCmd events intercept the reading process by - * executing the associated commands instead. - */ + // The BufReadCmd and FileReadCmd events intercept the reading process by + // executing the associated commands instead. if (!filtering && !read_stdin && !read_buffer) { - pos_T pos; - - pos = curbuf->b_op_start; + orig_start = curbuf->b_op_start; // Set '[ mark to the line above where the lines go (line 1 if zero). curbuf->b_op_start.lnum = ((from == 0) ? 1 : from); @@ -335,7 +335,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski return aborting() ? FAIL : OK; } - curbuf->b_op_start = pos; + curbuf->b_op_start = orig_start; } if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) { @@ -362,7 +362,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0); msg_end(); msg_scroll = msg_save; - return FAIL; + return NOTDONE; } } @@ -408,6 +408,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski if (os_fileinfo((char *)fname, &file_info)) { buf_store_file_info(curbuf, &file_info); curbuf->b_mtime_read = curbuf->b_mtime; + curbuf->b_mtime_read_ns = curbuf->b_mtime_ns; #ifdef UNIX /* * Use the protection bits of the original file for the swap file. @@ -424,7 +425,9 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski #endif } else { curbuf->b_mtime = 0; + curbuf->b_mtime_ns = 0; curbuf->b_mtime_read = 0; + curbuf->b_mtime_read_ns = 0; curbuf->b_orig_size = 0; curbuf->b_orig_mode = 0; } @@ -576,9 +579,8 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski ++no_wait_return; // don't wait for return yet - /* - * Set '[ mark to the line above where the lines go (line 1 if zero). - */ + // Set '[ mark to the line above where the lines go (line 1 if zero). + orig_start = curbuf->b_op_start; curbuf->b_op_start.lnum = ((from == 0) ? 1 : from); curbuf->b_op_start.col = 0; @@ -618,6 +620,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski try_mac = (vim_strchr(p_ffs, 'm') != NULL); try_dos = (vim_strchr(p_ffs, 'd') != NULL); try_unix = (vim_strchr(p_ffs, 'x') != NULL); + curbuf->b_op_start = orig_start; if (msg_scrolled == n) { msg_scroll = m; @@ -1012,27 +1015,27 @@ retry: } read_buf_col += n; break; - } else { - // Append whole line and new-line. Change NL - // to NUL to reverse the effect done below. - for (ni = 0; ni < n; ni++) { - if (p[ni] == NL) { - ptr[tlen++] = NUL; - } else { - ptr[tlen++] = p[ni]; - } + } + + // Append whole line and new-line. Change NL + // to NUL to reverse the effect done below. + for (ni = 0; ni < n; ni++) { + if (p[ni] == NL) { + ptr[tlen++] = NUL; + } else { + ptr[tlen++] = p[ni]; } - ptr[tlen++] = NL; - read_buf_col = 0; - if (++read_buf_lnum > from) { - // When the last line didn't have an - // end-of-line don't add it now either. - if (!curbuf->b_p_eol) { - --tlen; - } - size = tlen; - break; + } + ptr[tlen++] = NL; + read_buf_col = 0; + if (++read_buf_lnum > from) { + // When the last line didn't have an + // end-of-line don't add it now either. + if (!curbuf->b_p_eol) { + tlen--; } + size = tlen; + break; } } } @@ -1360,6 +1363,10 @@ retry: u8c += (unsigned)(*--p) << 16; u8c += (unsigned)(*--p) << 24; } + // Replace characters over INT_MAX with Unicode replacement character + if (u8c > INT_MAX) { + u8c = 0xfffd; + } } else { // UTF-8 if (*--p < 0x80) { u8c = *p; @@ -1847,7 +1854,7 @@ failed: msg_scrolled_ign = true; if (!read_stdin && !read_buffer) { - p = (char_u *)msg_trunc_attr((char *)IObuff, FALSE, 0); + p = (char_u *)msg_trunc_attr((char *)IObuff, false, 0); } if (read_stdin || read_buffer || restart_edit != 0 @@ -1884,13 +1891,13 @@ failed: check_cursor_lnum(); beginline(BL_WHITE | BL_FIX); // on first non-blank - /* - * Set '[ and '] marks to the newly read lines. - */ - curbuf->b_op_start.lnum = from + 1; - curbuf->b_op_start.col = 0; - curbuf->b_op_end.lnum = from + linecnt; - curbuf->b_op_end.col = 0; + if (!cmdmod.lockmarks) { + // Set '[ and '] marks to the newly read lines. + curbuf->b_op_start.lnum = from + 1; + curbuf->b_op_start.col = 0; + curbuf->b_op_end.lnum = from + linecnt; + curbuf->b_op_end.col = 0; + } } msg_scroll = msg_save; @@ -2009,10 +2016,8 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) return lnum; } -/* - * Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be - * equal to the buffer "buf". Used for calling readfile(). - */ +/// Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary' to be +/// equal to the buffer "buf". Used for calling readfile(). void prep_exarg(exarg_T *eap, const buf_T *buf) FUNC_ATTR_NONNULL_ALL { @@ -2029,9 +2034,7 @@ void prep_exarg(exarg_T *eap, const buf_T *buf) eap->forceit = FALSE; } -/* - * Set default or forced 'fileformat' and 'binary'. - */ +/// Set default or forced 'fileformat' and 'binary'. void set_file_options(int set_options, exarg_T *eap) { // set default 'fileformat' @@ -2052,9 +2055,7 @@ void set_file_options(int set_options, exarg_T *eap) } } -/* - * Set forced 'fileencoding'. - */ +/// Set forced 'fileencoding'. void set_forced_fenc(exarg_T *eap) { if (eap->force_enc != 0) { @@ -2064,12 +2065,12 @@ void set_forced_fenc(exarg_T *eap) } } -// Find next fileencoding to use from 'fileencodings'. -// "pp" points to fenc_next. It's advanced to the next item. -// When there are no more items, an empty string is returned and *pp is set to -// NULL. -// When *pp is not set to NULL, the result is in allocated memory and "alloced" -// is set to true. +/// Find next fileencoding to use from 'fileencodings'. +/// "pp" points to fenc_next. It's advanced to the next item. +/// When there are no more items, an empty string is returned and *pp is set to +/// NULL. +/// When *pp is not set to NULL, the result is in allocated memory and "alloced" +/// is set to true. static char_u *next_fenc(char_u **pp, bool *alloced) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { @@ -2145,10 +2146,8 @@ static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp) } -/* - * Read marks for the current buffer from the ShaDa file, when we support - * buffer marks and the buffer has a name. - */ +/// Read marks for the current buffer from the ShaDa file, when we support +/// buffer marks and the buffer has a name. static void check_marks_read(void) { if (!curbuf->b_marks_read && get_shada_parameter('\'') > 0 @@ -2248,6 +2247,8 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_ int write_undo_file = FALSE; context_sha256_T sha_ctx; unsigned int bkc = get_bkc_value(buf); + const pos_T orig_start = buf->b_op_start; + const pos_T orig_end = buf->b_op_end; if (fname == NULL || *fname == NUL) { // safety check return FAIL; @@ -2428,7 +2429,13 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_ if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline) || did_cmd || nofile_err || aborting()) { - --no_wait_return; + if (buf != NULL && cmdmod.lockmarks) { + // restore the original '[ and '] positions + buf->b_op_start = orig_start; + buf->b_op_end = orig_end; + } + + no_wait_return--; msg_scroll = msg_save; if (nofile_err) { emsg(_("E676: No matching autocommands for acwrite buffer")); @@ -2509,6 +2516,11 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_ } } + if (cmdmod.lockmarks) { + // restore the original '[ and '] positions + buf->b_op_start = orig_start; + buf->b_op_end = orig_end; + } if (shortmess(SHM_OVER) && !exiting) { msg_scroll = FALSE; // overwrite previous file message @@ -3304,7 +3316,7 @@ restore_backup: if (end == 0 || (lnum == end && (write_bin || !buf->b_p_fixeol) - && (lnum == buf->b_no_eol_lnum + && ((write_bin && lnum == buf->b_no_eol_lnum) || (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) { lnum++; // written the line, count it no_eol = true; @@ -3681,11 +3693,12 @@ nofail: msg_puts_attr(_("don't quit the editor until the file is successfully written!"), attr | MSG_HIST); - /* Update the timestamp to avoid an "overwrite changed file" - * prompt when writing again. */ + // Update the timestamp to avoid an "overwrite changed file" + // prompt when writing again. if (os_fileinfo((char *)fname, &file_info_old)) { buf_store_file_info(buf, &file_info_old); buf->b_mtime_read = buf->b_mtime; + buf->b_mtime_read_ns = buf->b_mtime_ns; } } } @@ -3743,10 +3756,8 @@ nofail: #undef SET_ERRMSG_NUM } -/* - * Set the name of the current buffer. Use when the buffer doesn't have a - * name and a ":r" or ":w" command with a file name is used. - */ +/// Set the name of the current buffer. Use when the buffer doesn't have a +/// name and a ":r" or ":w" command with a file name is used. static int set_rw_fname(char_u *fname, char_u *sfname) { buf_T *buf = curbuf; @@ -3780,7 +3791,7 @@ static int set_rw_fname(char_u *fname, char_u *sfname) // Do filetype detection now if 'filetype' is empty. if (*curbuf->b_p_ft == NUL) { - if (au_has_group((char_u *)"filetypedetect")) { + if (augroup_exists("filetypedetect")) { (void)do_doautocmd((char_u *)"filetypedetect BufRead", false, NULL); } do_modelines(0); @@ -3836,9 +3847,7 @@ static bool msg_add_fileformat(int eol_type) return false; } -/* - * Append line and character count to IObuff. - */ +/// Append line and character count to IObuff. void msg_add_lines(int insert_space, long lnum, off_T nchars) { char_u *p; @@ -3849,7 +3858,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars) *p++ = ' '; } if (shortmess(SHM_LINES)) { - vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "C", + vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "B", (int64_t)lnum, (int64_t)nchars); } else { vim_snprintf((char *)p, IOSIZE - (p - IObuff), @@ -3857,30 +3866,25 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars) (int64_t)lnum); p += STRLEN(p); vim_snprintf((char *)p, IOSIZE - (p - IObuff), - NGETTEXT("%" PRId64 " character", "%" PRId64 " characters", nchars), + NGETTEXT("%" PRId64 " byte", "%" PRId64 " bytes", nchars), (int64_t)nchars); } } -/* - * Append message for missing line separator to IObuff. - */ +/// Append message for missing line separator to IObuff. static void msg_add_eol(void) { STRCAT(IObuff, shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]")); } -/* - * Check modification time of file, before writing to it. - * The size isn't checked, because using a tool like "gzip" takes care of - * using the same timestamp but can't set the size. - */ +/// Check modification time of file, before writing to it. +/// The size isn't checked, because using a tool like "gzip" takes care of +/// using the same timestamp but can't set the size. static int check_mtime(buf_T *buf, FileInfo *file_info) { if (buf->b_mtime_read != 0 - && time_differs(file_info->stat.st_mtim.tv_sec, - buf->b_mtime_read)) { + && time_differs(file_info, buf->b_mtime_read, buf->b_mtime_read_ns)) { msg_scroll = true; // Don't overwrite messages here. msg_silent = 0; // Must give this prompt. // Don't use emsg() here, don't want to flush the buffers. @@ -3894,28 +3898,24 @@ static int check_mtime(buf_T *buf, FileInfo *file_info) return OK; } -/// Return true if the times differ -/// -/// @param t1 first time -/// @param t2 second time -static bool time_differs(long t1, long t2) FUNC_ATTR_CONST +static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST { + return file_info->stat.st_mtim.tv_nsec != mtime_ns #if defined(__linux__) || defined(MSWIN) - // On a FAT filesystem, esp. under Linux, there are only 5 bits to store - // the seconds. Since the roundoff is done when flushing the inode, the - // time may change unexpectedly by one second!!! - return t1 - t2 > 1 || t2 - t1 > 1; + // On a FAT filesystem, esp. under Linux, there are only 5 bits to store + // the seconds. Since the roundoff is done when flushing the inode, the + // time may change unexpectedly by one second!!! + || file_info->stat.st_mtim.tv_sec - mtime > 1 + || mtime - file_info->stat.st_mtim.tv_sec > 1; #else - return t1 != t2; + || (long)file_info->stat.st_mtim.tv_sec != mtime; #endif } -/* - * Call write() to write a number of bytes to the file. - * Handles 'encoding' conversion. - * - * Return FAIL for failure, OK otherwise. - */ +/// Call write() to write a number of bytes to the file. +/// Handles 'encoding' conversion. +/// +/// @return FAIL for failure, OK otherwise. static int buf_write_bytes(struct bw_info *ip) { int wlen; @@ -4243,12 +4243,11 @@ static int get_fio_flags(const char_u *name) } -/* - * Check for a Unicode BOM (Byte Order Mark) at the start of p[size]. - * "size" must be at least 2. - * Return the name of the encoding and set "*lenp" to the length. - * Returns NULL when no BOM found. - */ +/// Check for a Unicode BOM (Byte Order Mark) at the start of p[size]. +/// "size" must be at least 2. +/// +/// @return the name of the encoding and set "*lenp" to the length or, +/// NULL when no BOM found. static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags) { char *name = NULL; @@ -4289,10 +4288,9 @@ static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags) return (char_u *)name; } -/* - * Generate a BOM in "buf[4]" for encoding "name". - * Return the length of the BOM (zero when no BOM). - */ +/// Generate a BOM in "buf[4]" for encoding "name". +/// +/// @return the length of the BOM (zero when no BOM). static int make_bom(char_u *buf, char_u *name) { int flags; @@ -4317,10 +4315,12 @@ static int make_bom(char_u *buf, char_u *name) } /// Shorten filename of a buffer. -/// When "force" is TRUE: Use full path from now on for files currently being -/// edited, both for file name and swap file name. Try to shorten the file -/// names a bit, if safe to do so. -/// When "force" is FALSE: Only try to shorten absolute file names. +/// +/// @param force when TRUE: Use full path from now on for files currently being +/// edited, both for file name and swap file name. Try to shorten the file +/// names a bit, if safe to do so. +/// when FALSE: Only try to shorten absolute file names. +/// /// For buffers that have buftype "nofile" or "scratch": never change the file /// name. void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) @@ -4498,7 +4498,8 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL } /// Read 2 bytes from "fd" and turn them into an int, MSB first. -/// Returns -1 when encountering EOF. +/// +/// @return -1 when encountering EOF. int get2c(FILE *fd) { const int n = getc(fd); @@ -4513,7 +4514,8 @@ int get2c(FILE *fd) } /// Read 3 bytes from "fd" and turn them into an int, MSB first. -/// Returns -1 when encountering EOF. +/// +/// @return -1 when encountering EOF. int get3c(FILE *fd) { int n = getc(fd); @@ -4533,7 +4535,8 @@ int get3c(FILE *fd) } /// Read 4 bytes from "fd" and turn them into an int, MSB first. -/// Returns -1 when encountering EOF. +/// +/// @return -1 when encountering EOF. int get4c(FILE *fd) { // Use unsigned rather than int otherwise result is undefined @@ -4564,7 +4567,8 @@ int get4c(FILE *fd) } /// Read 8 bytes from `fd` and turn them into a time_t, MSB first. -/// Returns -1 when encountering EOF. +/// +/// @return -1 when encountering EOF. time_t get8ctime(FILE *fd) { time_t n = 0; @@ -4580,7 +4584,8 @@ time_t get8ctime(FILE *fd) } /// Reads a string of length "cnt" from "fd" into allocated memory. -/// @return pointer to the string or NULL when unable to read that many bytes. +/// +/// @return pointer to the string or NULL when unable to read that many bytes. char *read_string(FILE *fd, size_t cnt) { char *str = xmallocz(cnt); @@ -4596,7 +4601,8 @@ char *read_string(FILE *fd, size_t cnt) } /// Writes a number to file "fd", most significant bit first, in "len" bytes. -/// @returns false in case of an error. +/// +/// @return false in case of an error. bool put_bytes(FILE *fd, uintmax_t number, size_t len) { assert(len > 0); @@ -4609,7 +4615,8 @@ bool put_bytes(FILE *fd, uintmax_t number, size_t len) } /// Writes time_t to file "fd" in 8 bytes. -/// @returns FAIL when the write failed. +/// +/// @return FAIL when the write failed. int put_time(FILE *fd, time_t time_) { uint8_t buf[8]; @@ -4620,7 +4627,7 @@ int put_time(FILE *fd, time_t time_) /// os_rename() only works if both files are on the same file system, this /// function will (attempts to?) copy the file across if rename fails -- webb /// -/// @return -1 for failure, 0 for success +/// @return -1 for failure, 0 for success int vim_rename(const char_u *from, const char_u *to) FUNC_ATTR_NONNULL_ALL { @@ -4845,11 +4852,10 @@ int check_timestamps(int focus) return didit; } -/* - * Move all the lines from buffer "frombuf" to buffer "tobuf". - * Return OK or FAIL. When FAIL "tobuf" is incomplete and/or "frombuf" is not - * empty. - */ +/// Move all the lines from buffer "frombuf" to buffer "tobuf". +/// +/// @return OK or FAIL. +/// When FAIL "tobuf" is incomplete and/or "frombuf" is not empty. static int move_lines(buf_T *frombuf, buf_T *tobuf) { buf_T *tbuf = curbuf; @@ -4886,13 +4892,12 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf) return retval; } -/* - * Check if buffer "buf" has been changed. - * Also check if the file for a new buffer unexpectedly appeared. - * return 1 if a changed buffer was found. - * return 2 if a message has been displayed. - * return 0 otherwise. - */ +/// Check if buffer "buf" has been changed. +/// Also check if the file for a new buffer unexpectedly appeared. +/// +/// @return 1 if a changed buffer was found or, +/// 2 if a message has been displayed or, +/// 0 otherwise. int buf_check_timestamp(buf_T *buf) FUNC_ATTR_NONNULL_ALL { @@ -4901,7 +4906,11 @@ int buf_check_timestamp(buf_T *buf) char *mesg = NULL; char *mesg2 = ""; bool helpmesg = false; - bool reload = false; + enum { + RELOAD_NONE, + RELOAD_NORMAL, + RELOAD_DETECT + } reload = RELOAD_NONE; bool can_reload = false; uint64_t orig_size = buf->b_orig_size; int orig_mode = buf->b_orig_mode; @@ -4929,7 +4938,7 @@ int buf_check_timestamp(buf_T *buf) if (!(buf->b_flags & BF_NOTEDITED) && buf->b_mtime != 0 && (!(file_info_ok = os_fileinfo((char *)buf->b_ffname, &file_info)) - || time_differs(file_info.stat.st_mtim.tv_sec, buf->b_mtime) + || time_differs(&file_info, buf->b_mtime, buf->b_mtime_ns) || (int)file_info.stat.st_mode != buf->b_orig_mode)) { const long prev_b_mtime = buf->b_mtime; @@ -4946,15 +4955,14 @@ int buf_check_timestamp(buf_T *buf) buf_store_file_info(buf, &file_info); } - // Don't do anything for a directory. Might contain the file - // explorer. if (os_isdir(buf->b_fname)) { + // Don't do anything for a directory. Might contain the file explorer. } else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar) && !bufIsChanged(buf) && file_info_ok) { // If 'autoread' is set, the buffer has no changes and the file still // exists, reload the buffer. Use the buffer-local option value if it // was set, the global option value otherwise. - reload = true; + reload = RELOAD_NORMAL; } else { if (!file_info_ok) { reason = "deleted"; @@ -4985,7 +4993,9 @@ int buf_check_timestamp(buf_T *buf) } s = get_vim_var_str(VV_FCS_CHOICE); if (STRCMP(s, "reload") == 0 && *reason != 'd') { - reload = true; + reload = RELOAD_NORMAL; + } else if (STRCMP(s, "edit") == 0) { + reload = RELOAD_DETECT; } else if (STRCMP(s, "ask") == 0) { n = false; } else { @@ -5020,6 +5030,7 @@ int buf_check_timestamp(buf_T *buf) // Only timestamp changed, store it to avoid a warning // in check_mtime() later. buf->b_mtime_read = buf->b_mtime; + buf->b_mtime_read_ns = buf->b_mtime_ns; } } } @@ -5048,9 +5059,15 @@ int buf_check_timestamp(buf_T *buf) xstrlcat(tbuf, "\n", tbuf_len - 1); xstrlcat(tbuf, mesg2, tbuf_len - 1); } - if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf, - (char_u *)_("&OK\n&Load File"), 1, NULL, true) == 2) { - reload = true; + switch (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf, + (char_u *)_("&OK\n&Load File\nLoad File &and Options"), + 1, NULL, true)) { + case 2: + reload = RELOAD_NORMAL; + break; + case 3: + reload = RELOAD_DETECT; + break; } } else if (State > NORMAL_BUSY || (State & CMDLINE) || already_warned) { if (*mesg2 != NUL) { @@ -5084,9 +5101,9 @@ int buf_check_timestamp(buf_T *buf) xfree(tbuf); } - if (reload) { + if (reload != RELOAD_NONE) { // Reload the buffer. - buf_reload(buf, orig_mode); + buf_reload(buf, orig_mode, reload == RELOAD_DETECT); if (buf->b_p_udf && buf->b_ffname != NULL) { char_u hash[UNDO_HASH_SIZE]; @@ -5104,13 +5121,11 @@ int buf_check_timestamp(buf_T *buf) return retval; } -/* - * Reload a buffer that is already loaded. - * Used when the file was changed outside of Vim. - * "orig_mode" is buf->b_orig_mode before the need for reloading was detected. - * buf->b_orig_mode may have been reset already. - */ -void buf_reload(buf_T *buf, int orig_mode) +/// Reload a buffer that is already loaded. +/// Used when the file was changed outside of Vim. +/// "orig_mode" is buf->b_orig_mode before the need for reloading was detected. +/// buf->b_orig_mode may have been reset already. +void buf_reload(buf_T *buf, int orig_mode, bool reload_options) { exarg_T ea; pos_T old_cursor; @@ -5125,11 +5140,15 @@ void buf_reload(buf_T *buf, int orig_mode) // set curwin/curbuf for "buf" and save some things aucmd_prepbuf(&aco, buf); - // We only want to read the text from the file, not reset the syntax - // highlighting, clear marks, diff status, etc. Force the fileformat and - // encoding to be the same. + // Unless reload_options is set, we only want to read the text from the + // file, not reset the syntax highlighting, clear marks, diff status, etc. + // Force the fileformat and encoding to be the same. + if (reload_options) { + memset(&ea, 0, sizeof(ea)); + } else { + prep_exarg(&ea, buf); + } - prep_exarg(&ea, buf); old_cursor = curwin->w_cursor; old_topline = curwin->w_topline; @@ -5248,14 +5267,13 @@ void buf_store_file_info(buf_T *buf, FileInfo *file_info) FUNC_ATTR_NONNULL_ALL { buf->b_mtime = file_info->stat.st_mtim.tv_sec; + buf->b_mtime_ns = file_info->stat.st_mtim.tv_nsec; buf->b_orig_size = os_fileinfo_size(file_info); buf->b_orig_mode = (int)file_info->stat.st_mode; } -/* - * Adjust the line with missing eol, used for the next write. - * Used for do_filter(), when the input lines for the filter are deleted. - */ +/// Adjust the line with missing eol, used for the next write. +/// Used for do_filter(), when the input lines for the filter are deleted. void write_lnum_adjust(linenr_T offset) { if (curbuf->b_no_eol_lnum != 0) { // only if there is a missing eol @@ -5326,35 +5344,85 @@ static void vim_maketempdir(void) (void)umask(umask_save); } +/// Core part of "readdir()" function. +/// Retrieve the list of files/directories of "path" into "gap". +/// +/// @return OK for success, FAIL for failure. +int readdir_core(garray_T *gap, const char *path, void *context, CheckItem checkitem) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + ga_init(gap, (int)sizeof(char *), 20); + + Directory dir; + if (!os_scandir(&dir, path)) { + smsg(_(e_notopen), path); + return FAIL; + } + + for (;;) { + const char *p = os_scandir_next(&dir); + if (p == NULL) { + break; + } + + bool ignore = (p[0] == '.' && (p[1] == NUL || (p[1] == '.' && p[2] == NUL))); + if (!ignore && checkitem != NULL) { + varnumber_T r = checkitem(context, p); + if (r < 0) { + break; + } + if (r == 0) { + ignore = true; + } + } + + if (!ignore) { + ga_grow(gap, 1); + ((char **)gap->ga_data)[gap->ga_len++] = xstrdup(p); + } + } + + os_closedir(&dir); + + if (gap->ga_len > 0) { + sort_strings((char_u **)gap->ga_data, gap->ga_len); + } + + return OK; +} + /// Delete "name" and everything in it, recursively. -/// @param name The path which should be deleted. -/// @return 0 for success, -1 if some file was not deleted. +/// +/// @param name The path which should be deleted. +/// +/// @return 0 for success, -1 if some file was not deleted. int delete_recursive(const char *name) + FUNC_ATTR_NONNULL_ALL { int result = 0; if (os_isrealdir(name)) { - snprintf((char *)NameBuff, MAXPATHL, "%s/*", name); // NOLINT - - char_u **files; - int file_count; - char_u *exp = vim_strsave(NameBuff); - if (gen_expand_wildcards(1, &exp, &file_count, &files, - EW_DIR | EW_FILE | EW_SILENT | EW_ALLLINKS - | EW_DODOT | EW_EMPTYOK) == OK) { - for (int i = 0; i < file_count; i++) { - if (delete_recursive((const char *)files[i]) != 0) { + char *exp = xstrdup(name); + garray_T ga; + if (readdir_core(&ga, exp, NULL, NULL) == OK) { + for (int i = 0; i < ga.ga_len; i++) { + vim_snprintf((char *)NameBuff, MAXPATHL, "%s/%s", exp, ((char_u **)ga.ga_data)[i]); + if (delete_recursive((const char *)NameBuff) != 0) { + // Remember the failure but continue deleting any further + // entries. result = -1; } } - FreeWild(file_count, files); + ga_clear_strings(&ga); + if (os_rmdir(exp) != 0) { + result = -1; + } } else { result = -1; } - xfree(exp); - os_rmdir(name); } else { + // Delete symlink only. result = os_remove(name) == 0 ? 0 : -1; } @@ -5372,8 +5440,8 @@ void vim_deltempdir(void) } } -/// Get the name of temp directory. This directory would be created on the first -/// call to this function. +/// @return the name of temp directory. This directory would be created on the first +/// call to this function. char_u *vim_gettempdir(void) { if (vim_tempdir == NULL) { @@ -5407,8 +5475,8 @@ static bool vim_settempdir(char *tempdir) /// /// @note The temp file is NOT created. /// -/// @return pointer to the temp file name or NULL if Neovim can't create -/// temporary directory for its own temporary files. +/// @return pointer to the temp file name or NULL if Neovim can't create +/// temporary directory for its own temporary files. char_u *vim_tempname(void) { // Temp filename counter. @@ -5719,10 +5787,9 @@ char_u *file_pat_to_reg_pat(const char_u *pat, const char_u *pat_end, char *allo } #if defined(EINTR) -/* - * Version of read() that retries when interrupted by EINTR (possibly - * by a SIGWINCH). - */ + +/// Version of read() that retries when interrupted by EINTR (possibly +/// by a SIGWINCH). long read_eintr(int fd, void *buf, size_t bufsize) { long ret; @@ -5736,10 +5803,8 @@ long read_eintr(int fd, void *buf, size_t bufsize) return ret; } -/* - * Version of write() that retries when interrupted by EINTR (possibly - * by a SIGWINCH). - */ +/// Version of write() that retries when interrupted by EINTR (possibly +/// by a SIGWINCH). long write_eintr(int fd, void *buf, size_t bufsize) { long ret = 0; diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h index 186d0b90ab..62d8b6142e 100644 --- a/src/nvim/fileio.h +++ b/src/nvim/fileio.h @@ -3,6 +3,8 @@ #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/typval.h" +#include "nvim/garray.h" #include "nvim/os/os.h" // Values for readfile() flags @@ -17,6 +19,8 @@ #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) +typedef varnumber_T (*CheckItem)(void *expr, const char *name); + #ifdef INCLUDE_GENERATED_DECLARATIONS // Events for autocommands # include "fileio.h.generated.h" diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 4a8be7a31b..3643ceb460 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -28,10 +28,10 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/plines.h" #include "nvim/screen.h" #include "nvim/strings.h" @@ -121,9 +121,7 @@ static size_t foldendmarkerlen; // Exported folding functions. {{{1 // copyFoldingState() {{{2 -/* - * Copy that folding state from window "wp_from" to window "wp_to". - */ +/// Copy that folding state from window "wp_from" to window "wp_to". void copyFoldingState(win_T *wp_from, win_T *wp_to) { wp_to->w_fold_manual = wp_from->w_fold_manual; @@ -132,9 +130,7 @@ void copyFoldingState(win_T *wp_from, win_T *wp_to) } // hasAnyFolding() {{{2 -/* - * Return TRUE if there may be folded lines in the current window. - */ +/// @return TRUE if there may be folded lines in the current window. int hasAnyFolding(win_T *win) { // very simple now, but can become more complex later @@ -259,9 +255,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp } // foldLevel() {{{2 -/* - * Return fold level at line number "lnum" in the current window. - */ +/// @return fold level at line number "lnum" in the current window. int foldLevel(linenr_T lnum) { // While updating the folds lines between invalid_top and invalid_bot have @@ -283,15 +277,16 @@ int foldLevel(linenr_T lnum) } // lineFolded() {{{2 -// Low level function to check if a line is folded. Doesn't use any caching. -// Return true if line is folded. -// Return false if line is not folded. +/// Low level function to check if a line is folded. Doesn't use any caching. +/// +/// @return true if line is folded or, +/// false if line is not folded. bool lineFolded(win_T *const win, const linenr_T lnum) { return fold_info(win, lnum).fi_lines != 0; } -/// fold_info() {{{2 +// 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 @@ -316,61 +311,49 @@ foldinfo_T fold_info(win_T *win, linenr_T lnum) } // foldmethodIsManual() {{{2 -/* - * Return TRUE if 'foldmethod' is "manual" - */ +/// @return TRUE if 'foldmethod' is "manual" int foldmethodIsManual(win_T *wp) { return wp->w_p_fdm[3] == 'u'; } // foldmethodIsIndent() {{{2 -/* - * Return TRUE if 'foldmethod' is "indent" - */ +/// @return TRUE if 'foldmethod' is "indent" int foldmethodIsIndent(win_T *wp) { return wp->w_p_fdm[0] == 'i'; } // foldmethodIsExpr() {{{2 -/* - * Return TRUE if 'foldmethod' is "expr" - */ +/// @return TRUE if 'foldmethod' is "expr" int foldmethodIsExpr(win_T *wp) { return wp->w_p_fdm[1] == 'x'; } // foldmethodIsMarker() {{{2 -/* - * Return TRUE if 'foldmethod' is "marker" - */ +/// @return TRUE if 'foldmethod' is "marker" int foldmethodIsMarker(win_T *wp) { return wp->w_p_fdm[2] == 'r'; } // foldmethodIsSyntax() {{{2 -/* - * Return TRUE if 'foldmethod' is "syntax" - */ +/// @return TRUE if 'foldmethod' is "syntax" int foldmethodIsSyntax(win_T *wp) { return wp->w_p_fdm[0] == 's'; } // foldmethodIsDiff() {{{2 -/* - * Return TRUE if 'foldmethod' is "diff" - */ +/// @return TRUE if 'foldmethod' is "diff" int foldmethodIsDiff(win_T *wp) { return wp->w_p_fdm[0] == 'd'; } // closeFold() {{{2 -/// Close fold for current window at line "lnum". +/// Close fold for current window at position "pos". /// Repeat "count" times. void closeFold(pos_T pos, long count) { @@ -378,9 +361,7 @@ void closeFold(pos_T pos, long count) } // closeFoldRecurse() {{{2 -/* - * Close fold for current window at line "lnum" recursively. - */ +/// Close fold for current window at position `pos` recursively. void closeFoldRecurse(pos_T pos) { (void)setManualFold(pos, false, true, NULL); @@ -427,28 +408,22 @@ void opFoldRange(pos_T firstpos, pos_T lastpos, int opening, int recurse, int ha } // openFold() {{{2 -/* - * Open fold for current window at line "lnum". - * Repeat "count" times. - */ +/// Open fold for current window at position "pos". +/// Repeat "count" times. void openFold(pos_T pos, long count) { setFoldRepeat(pos, count, true); } // openFoldRecurse() {{{2 -/* - * Open fold for current window at line "lnum" recursively. - */ +/// Open fold for current window at position `pos` recursively. void openFoldRecurse(pos_T pos) { (void)setManualFold(pos, true, true, NULL); } // foldOpenCursor() {{{2 -/* - * Open folds until the cursor line is not in a closed fold. - */ +/// Open folds until the cursor line is not in a closed fold. void foldOpenCursor(void) { int done; @@ -466,9 +441,7 @@ void foldOpenCursor(void) } // newFoldLevel() {{{2 -/* - * Set new foldlevel for current window. - */ +/// Set new foldlevel for current window. void newFoldLevel(void) { newFoldLevelWin(curwin); @@ -505,9 +478,7 @@ static void newFoldLevelWin(win_T *wp) } // foldCheckClose() {{{2 -/* - * Apply 'foldlevel' to all folds that don't contain the cursor. - */ +/// Apply 'foldlevel' to all folds that don't contain the cursor. void foldCheckClose(void) { if (*p_fcl != NUL) { // can only be "all" right now @@ -543,8 +514,8 @@ static int checkCloseRec(garray_T *gap, linenr_T lnum, int level) } // foldCreateAllowed() {{{2 -/// Return TRUE if it's allowed to manually create or delete a fold. -/// Give an error message and return FALSE if not. +/// @return TRUE if it's allowed to manually create or delete a fold or, +/// give an error message and return FALSE if not. int foldManualAllowed(bool create) { if (foldmethodIsManual(curwin) || foldmethodIsMarker(curwin)) { @@ -790,9 +761,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const } // clearFolding() {{{2 -/* - * Remove all folding for window "win". - */ +/// Remove all folding for window "win". void clearFolding(win_T *win) { deleteFoldRecurse(win->w_buffer, &win->w_folds); @@ -800,12 +769,10 @@ void clearFolding(win_T *win) } // foldUpdate() {{{2 -/* - * Update folds for changes in the buffer of a window. - * Note that inserted/deleted lines must have already been taken care of by - * calling foldMarkAdjust(). - * The changes in lines from top to bot (inclusive). - */ +/// Update folds for changes in the buffer of a window. +/// Note that inserted/deleted lines must have already been taken care of by +/// calling foldMarkAdjust(). +/// The changes in lines from top to bot (inclusive). void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) { if (compl_busy || State & INSERT) { @@ -856,12 +823,10 @@ void foldUpdateAfterInsert(void) } // foldUpdateAll() {{{2 -/* - * Update all lines in a window for folding. - * Used when a fold setting changes or after reloading the buffer. - * The actual updating is postponed until fold info is used, to avoid doing - * every time a setting is changed or a syntax item is added. - */ +/// Update all lines in a window for folding. +/// Used when a fold setting changes or after reloading the buffer. +/// The actual updating is postponed until fold info is used, to avoid doing +/// every time a setting is changed or a syntax item is added. void foldUpdateAll(win_T *win) { win->w_foldinvalid = true; @@ -992,21 +957,18 @@ int foldMoveTo(const bool updown, const int dir, const long count) } // foldInitWin() {{{2 -/* - * Init the fold info in a new window. - */ +/// Init the fold info in a new window. void foldInitWin(win_T *new_win) { ga_init(&new_win->w_folds, (int)sizeof(fold_T), 10); } // find_wl_entry() {{{2 -/* - * Find an entry in the win->w_lines[] array for buffer line "lnum". - * Only valid entries are considered (for entries where wl_valid is FALSE the - * line number can be wrong). - * Returns index of entry or -1 if not found. - */ +/// Find an entry in the win->w_lines[] array for buffer line "lnum". +/// Only valid entries are considered (for entries where wl_valid is FALSE the +/// line number can be wrong). +/// +/// @return index of entry or -1 if not found. int find_wl_entry(win_T *win, linenr_T lnum) { int i; @@ -1025,9 +987,7 @@ int find_wl_entry(win_T *win, linenr_T lnum) } // foldAdjustVisual() {{{2 -/* - * Adjust the Visual area to include any fold at the start or end completely. - */ +/// Adjust the Visual area to include any fold at the start or end completely. void foldAdjustVisual(void) { pos_T *start, *end; @@ -1059,9 +1019,7 @@ void foldAdjustVisual(void) } // cursor_foldstart() {{{2 -/* - * Move the cursor to the first line of a closed fold. - */ +/// Move the cursor to the first line of a closed fold. void foldAdjustCursor(void) { (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); @@ -1069,9 +1027,7 @@ void foldAdjustCursor(void) // Internal functions for "fold_T" {{{1 // cloneFoldGrowArray() {{{2 -/* - * Will "clone" (i.e deep copy) a garray_T of folds. - */ +/// Will "clone" (i.e deep copy) a garray_T of folds. void cloneFoldGrowArray(garray_T *from, garray_T *to) { fold_T *from_p; @@ -1143,9 +1099,7 @@ static bool foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp) } // foldLevelWin() {{{2 -/* - * Return fold level at line number "lnum" in window "wp". - */ +/// @return fold level at line number "lnum" in window "wp". static int foldLevelWin(win_T *wp, linenr_T lnum) { fold_T *fp; @@ -1169,9 +1123,7 @@ static int foldLevelWin(win_T *wp, linenr_T lnum) } // checkupdate() {{{2 -/* - * Check if the folds in window "wp" are invalid and update them if needed. - */ +/// Check if the folds in window "wp" are invalid and update them if needed. static void checkupdate(win_T *wp) { if (wp->w_foldinvalid) { @@ -1181,10 +1133,8 @@ static void checkupdate(win_T *wp) } // setFoldRepeat() {{{2 -/* - * Open or close fold for current window at line "lnum". - * Repeat "count" times. - */ +/// Open or close fold for current window at position `pos`. +/// Repeat "count" times. static void setFoldRepeat(pos_T pos, long count, int do_open) { int done; @@ -1204,7 +1154,6 @@ static void setFoldRepeat(pos_T pos, long count, int do_open) } // setManualFold() {{{2 -/// /// Open or close the fold in the current window which contains "lnum". /// Also does this for other windows in diff mode when needed. /// @@ -1344,9 +1293,7 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, int opening, int recu } // foldOpenNested() {{{2 -/* - * Open all nested folds in fold "fpr" recursively. - */ +/// Open all nested folds in fold "fpr" recursively. static void foldOpenNested(fold_T *fpr) { fold_T *fp; @@ -1359,9 +1306,10 @@ static void foldOpenNested(fold_T *fpr) } // deleteFoldEntry() {{{2 -// Delete fold "idx" from growarray "gap". -// When "recursive" is true also delete all the folds contained in it. -// When "recursive" is false contained folds are moved one level up. +/// Delete fold "idx" from growarray "gap". +/// +/// @param recursive when true, also delete all the folds contained in it. +/// when false, contained folds are moved one level up. static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx, const bool recursive) { @@ -1408,9 +1356,7 @@ static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx, } // deleteFoldRecurse() {{{2 -/* - * Delete nested folds in a fold. - */ +/// Delete nested folds in a fold. void deleteFoldRecurse(buf_T *bp, garray_T *gap) { #define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(bp, &((fd)->fd_nested)) @@ -1418,9 +1364,7 @@ void deleteFoldRecurse(buf_T *bp, garray_T *gap) } // foldMarkAdjust() {{{2 -/* - * Update line numbers of folds for inserted/deleted lines. - */ +/// Update line numbers of folds for inserted/deleted lines. void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long amount_after) { // If deleting marks from line1 to line2, but not deleting all those @@ -1538,10 +1482,8 @@ static void foldMarkAdjustRecurse(win_T *wp, garray_T *gap, linenr_T line1, line } // getDeepestNesting() {{{2 -/* - * Get the lowest 'foldlevel' value that makes the deepest nested fold in the - * current window open. - */ +/// Get the lowest 'foldlevel' value that makes the deepest nested fold in +/// window `wp`. int getDeepestNesting(win_T *wp) { checkupdate(wp); @@ -1633,7 +1575,7 @@ static void checkSmall(win_T *const wp, fold_T *const fp, const linenr_T lnum_of } // setSmallMaybe() {{{2 -// Set small flags in "gap" to kNone. +/// Set small flags in "gap" to kNone. static void setSmallMaybe(garray_T *gap) { fold_T *fp = (fold_T *)gap->ga_data; @@ -1643,10 +1585,8 @@ static void setSmallMaybe(garray_T *gap) } // foldCreateMarkers() {{{2 -/* - * Create a fold from line "start" to line "end" (inclusive) in the current - * window by adding markers. - */ +/// Create a fold from line "start" to line "end" (inclusive) in window `wp` +/// by adding markers. static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) { buf_T *buf = wp->w_buffer; @@ -1672,9 +1612,7 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) } // foldAddMarker() {{{2 -/* - * Add "marker[markerlen]" in 'commentstring' to line "lnum". - */ +/// Add "marker[markerlen]" in 'commentstring' to position `pos`. static void foldAddMarker(buf_T *buf, pos_T pos, const char_u *marker, size_t markerlen) { char_u *cms = buf->b_p_cms; @@ -1731,11 +1669,10 @@ static void deleteFoldMarkers(win_T *wp, fold_T *fp, int recursive, linenr_T lnu } // foldDelMarker() {{{2 -// -// Delete marker "marker[markerlen]" at the end of line "lnum". -// Delete 'commentstring' if it matches. -// If the marker is not found, there is no error message. Could be a missing -// close-marker. +/// Delete marker "marker[markerlen]" at the end of line "lnum". +/// Delete 'commentstring' if it matches. +/// If the marker is not found, there is no error message. Could be a missing +/// close-marker. static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t markerlen) { char_u *newline; @@ -1892,9 +1829,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin } // foldtext_cleanup() {{{2 -/* - * Remove 'foldmarker' and 'commentstring' from "str" (in-place). - */ +/// Remove 'foldmarker' and 'commentstring' from "str" (in-place). void foldtext_cleanup(char_u *str) { char_u *s; @@ -1974,10 +1909,8 @@ void foldtext_cleanup(char_u *str) // Function declarations. {{{2 // foldUpdateIEMS() {{{2 -/* - * Update the folding for window "wp", at least from lines "top" to "bot". - * IEMS = "Indent Expr Marker Syntax" - */ +/// Update the folding for window "wp", at least from lines "top" to "bot". +/// IEMS = "Indent Expr Marker Syntax" static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) { fline_T fline; @@ -2332,7 +2265,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, * firstlnum. */ while (!got_int) { - // set concat to 1 if it's allowed to concatenated this fold + // set concat to 1 if it's allowed to concatenate this fold // with a previous one that touches it. if (flp->start != 0 || flp->had_end <= MAX_LEVEL) { concat = 0; @@ -2640,9 +2573,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, } // foldInsert() {{{2 -/* - * Insert a new fold in "gap" at position "i". - */ +/// Insert a new fold in "gap" at position "i". static void foldInsert(garray_T *gap, int i) { fold_T *fp; @@ -2658,13 +2589,11 @@ static void foldInsert(garray_T *gap, int i) } // foldSplit() {{{2 -/* - * Split the "i"th fold in "gap", which starts before "top" and ends below - * "bot" in two pieces, one ending above "top" and the other starting below - * "bot". - * The caller must first have taken care of any nested folds from "top" to - * "bot"! - */ +/// Split the "i"th fold in "gap", which starts before "top" and ends below +/// "bot" in two pieces, one ending above "top" and the other starting below +/// "bot". +/// The caller must first have taken care of any nested folds from "top" to +/// "bot"! static void foldSplit(buf_T *buf, garray_T *const gap, const int i, const linenr_T top, const linenr_T bot) { @@ -2704,24 +2633,22 @@ static void foldSplit(buf_T *buf, garray_T *const gap, const int i, const linenr } // foldRemove() {{{2 -/* - * Remove folds within the range "top" to and including "bot". - * Check for these situations: - * 1 2 3 - * 1 2 3 - * top 2 3 4 5 - * 2 3 4 5 - * bot 2 3 4 5 - * 3 5 6 - * 3 5 6 - * - * 1: not changed - * 2: truncate to stop above "top" - * 3: split in two parts, one stops above "top", other starts below "bot". - * 4: deleted - * 5: made to start below "bot". - * 6: not changed - */ +/// Remove folds within the range "top" to and including "bot". +/// Check for these situations: +/// 1 2 3 +/// 1 2 3 +/// top 2 3 4 5 +/// 2 3 4 5 +/// bot 2 3 4 5 +/// 3 5 6 +/// 3 5 6 +/// +/// 1: not changed +/// 2: truncate to stop above "top" +/// 3: split in two parts, one stops above "top", other starts below "bot". +/// 4: deleted +/// 5: made to start below "bot". +/// 6: not changed static void foldRemove(win_T *const wp, garray_T *gap, linenr_T top, linenr_T bot) { fold_T *fp = NULL; @@ -2786,35 +2713,35 @@ static void foldReverseOrder(garray_T *gap, const linenr_T start_arg, const line } // foldMoveRange() {{{2 -// Move folds within the inclusive range "line1" to "line2" to after "dest" -// require "line1" <= "line2" <= "dest" -// -// There are the following situations for the first fold at or below line1 - 1. -// 1 2 3 4 -// 1 2 3 4 -// line1 2 3 4 -// 2 3 4 5 6 7 -// line2 3 4 5 6 7 -// 3 4 6 7 8 9 -// dest 4 7 8 9 -// 4 7 8 10 -// 4 7 8 10 -// -// In the following descriptions, "moved" means moving in the buffer, *and* in -// the fold array. -// Meanwhile, "shifted" just means moving in the buffer. -// 1. not changed -// 2. truncated above line1 -// 3. length reduced by line2 - line1, folds starting between the end of 3 and -// dest are truncated and shifted up -// 4. internal folds moved (from [line1, line2] to dest) -// 5. moved to dest. -// 6. truncated below line2 and moved. -// 7. length reduced by line2 - dest, folds starting between line2 and dest are -// removed, top is moved down by move_len. -// 8. truncated below dest and shifted up. -// 9. shifted up -// 10. not changed +/// Move folds within the inclusive range "line1" to "line2" to after "dest" +/// require "line1" <= "line2" <= "dest" +/// +/// There are the following situations for the first fold at or below line1 - 1. +/// 1 2 3 4 +/// 1 2 3 4 +/// line1 2 3 4 +/// 2 3 4 5 6 7 +/// line2 3 4 5 6 7 +/// 3 4 6 7 8 9 +/// dest 4 7 8 9 +/// 4 7 8 10 +/// 4 7 8 10 +/// +/// In the following descriptions, "moved" means moving in the buffer, *and* in +/// the fold array. +/// Meanwhile, "shifted" just means moving in the buffer. +/// 1. not changed +/// 2. truncated above line1 +/// 3. length reduced by line2 - line1, folds starting between the end of 3 and +/// dest are truncated and shifted up +/// 4. internal folds moved (from [line1, line2] to dest) +/// 5. moved to dest. +/// 6. truncated below line2 and moved. +/// 7. length reduced by line2 - dest, folds starting between line2 and dest are +/// removed, top is moved down by move_len. +/// 8. truncated below dest and shifted up. +/// 9. shifted up +/// 10. not changed static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end) { // I want to stop *at here*, foldRemove() stops *above* top @@ -2826,7 +2753,7 @@ 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) \ ((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))) +#define FOLD_INDEX(fp, gap) ((size_t)((fp) - ((fold_T *)(gap)->ga_data))) void foldMoveRange(win_T *const wp, garray_T *gap, const linenr_T line1, const linenr_T line2, const linenr_T dest) { @@ -2929,13 +2856,11 @@ void foldMoveRange(win_T *const wp, garray_T *gap, const linenr_T line1, const l #undef FOLD_INDEX // foldMerge() {{{2 -/* - * Merge two adjacent folds (and the nested ones in them). - * This only works correctly when the folds are really adjacent! Thus "fp1" - * must end just above "fp2". - * The resulting fold is "fp1", nested folds are moved from "fp2" to "fp1". - * Fold entry "fp2" in "gap" is deleted. - */ +/// Merge two adjacent folds (and the nested ones in them). +/// This only works correctly when the folds are really adjacent! Thus "fp1" +/// must end just above "fp2". +/// The resulting fold is "fp1", nested folds are moved from "fp2" to "fp1". +/// Fold entry "fp2" in "gap" is deleted. static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2) { fold_T *fp3; @@ -2968,11 +2893,10 @@ static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2) } // foldlevelIndent() {{{2 -/* - * Low level function to get the foldlevel for the "indent" method. - * Doesn't use any caching. - * Returns a level of -1 if the foldlevel depends on surrounding lines. - */ +/// Low level function to get the foldlevel for the "indent" method. +/// Doesn't use any caching. +/// +/// @return a level of -1 if the foldlevel depends on surrounding lines. static void foldlevelIndent(fline_T *flp) { char_u *s; @@ -3000,10 +2924,8 @@ static void foldlevelIndent(fline_T *flp) } // foldlevelDiff() {{{2 -/* - * Low level function to get the foldlevel for the "diff" method. - * Doesn't use any caching. - */ +/// Low level function to get the foldlevel for the "diff" method. +/// Doesn't use any caching. static void foldlevelDiff(fline_T *flp) { if (diff_infold(flp->wp, flp->lnum + flp->off)) { @@ -3014,11 +2936,10 @@ static void foldlevelDiff(fline_T *flp) } // foldlevelExpr() {{{2 -/* - * Low level function to get the foldlevel for the "expr" method. - * Doesn't use any caching. - * Returns a level of -1 if the foldlevel depends on surrounding lines. - */ +/// Low level function to get the foldlevel for the "expr" method. +/// Doesn't use any caching. +/// +/// @return a level of -1 if the foldlevel depends on surrounding lines. static void foldlevelExpr(fline_T *flp) { win_T *win; @@ -3113,11 +3034,9 @@ static void foldlevelExpr(fline_T *flp) } // parseMarker() {{{2 -/* - * Parse 'foldmarker' and set "foldendmarker", "foldstartmarkerlen" and - * "foldendmarkerlen". - * Relies on the option value to have been checked for correctness already. - */ +/// Parse 'foldmarker' and set "foldendmarker", "foldstartmarkerlen" and +/// "foldendmarkerlen". +/// Relies on the option value to have been checked for correctness already. static void parseMarker(win_T *wp) { foldendmarker = vim_strchr(wp->w_p_fmr, ','); @@ -3126,15 +3045,13 @@ static void parseMarker(win_T *wp) } // foldlevelMarker() {{{2 -/* - * Low level function to get the foldlevel for the "marker" method. - * "foldendmarker", "foldstartmarkerlen" and "foldendmarkerlen" must have been - * set before calling this. - * Requires that flp->lvl is set to the fold level of the previous line! - * Careful: This means you can't call this function twice on the same line. - * Doesn't use any caching. - * Sets flp->start when a start marker was found. - */ +/// Low level function to get the foldlevel for the "marker" method. +/// "foldendmarker", "foldstartmarkerlen" and "foldendmarkerlen" must have been +/// set before calling this. +/// Requires that flp->lvl is set to the fold level of the previous line! +/// Careful: This means you can't call this function twice on the same line. +/// Doesn't use any caching. +/// Sets flp->start when a start marker was found. static void foldlevelMarker(fline_T *flp) { char_u *startmarker; @@ -3205,10 +3122,8 @@ static void foldlevelMarker(fline_T *flp) } // foldlevelSyntax() {{{2 -/* - * Low level function to get the foldlevel for the "syntax" method. - * Doesn't use any caching. - */ +/// Low level function to get the foldlevel for the "syntax" method. +/// Doesn't use any caching. static void foldlevelSyntax(fline_T *flp) { linenr_T lnum = flp->lnum + flp->off; @@ -3228,11 +3143,9 @@ static void foldlevelSyntax(fline_T *flp) // functions for storing the fold state in a View {{{1 // put_folds() {{{2 - -/* - * Write commands to "fd" to restore the manual folds in window "wp". - * Return FAIL if writing fails. - */ +/// Write commands to "fd" to restore the manual folds in window "wp". +/// +/// @return FAIL if writing fails. int put_folds(FILE *fd, win_T *wp) { if (foldmethodIsManual(wp)) { @@ -3252,10 +3165,9 @@ int put_folds(FILE *fd, win_T *wp) } // put_folds_recurse() {{{2 -/* - * Write commands to "fd" to recreate manually created folds. - * Returns FAIL when writing failed. - */ +/// Write commands to "fd" to recreate manually created folds. +/// +/// @return FAIL when writing failed. static int put_folds_recurse(FILE *fd, garray_T *gap, linenr_T off) { fold_T *fp = (fold_T *)gap->ga_data; @@ -3276,10 +3188,9 @@ static int put_folds_recurse(FILE *fd, garray_T *gap, linenr_T off) } // put_foldopen_recurse() {{{2 -/* - * Write commands to "fd" to open and close manually opened/closed folds. - * Returns FAIL when writing failed. - */ +/// Write commands to "fd" to open and close manually opened/closed folds. +/// +/// @return FAIL when writing failed. static int put_foldopen_recurse(FILE *fd, win_T *wp, garray_T *gap, linenr_T off) { int level; @@ -3325,10 +3236,9 @@ static int put_foldopen_recurse(FILE *fd, win_T *wp, garray_T *gap, linenr_T off } // put_fold_open_close() {{{2 -/* - * Write the open or close command to "fd". - * Returns FAIL when writing failed. - */ +/// Write the open or close command to "fd". +/// +/// @return FAIL when writing failed. static int put_fold_open_close(FILE *fd, fold_T *fp, linenr_T off) { if (fprintf(fd, "%" PRId64, (int64_t)(fp->fd_top + off)) < 0 diff --git a/src/nvim/fold.h b/src/nvim/fold.h index a96ea8a039..6b29214760 100644 --- a/src/nvim/fold.h +++ b/src/nvim/fold.h @@ -14,10 +14,10 @@ */ typedef struct foldinfo { linenr_T fi_lnum; // line number where fold starts - int fi_level; /* level of the fold; when this is zero the - other fields are invalid */ - int fi_low_level; /* lowest fold level that starts in the same - line */ + int fi_level; // level of the fold; when this is zero the + // 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/c_grammar.lua b/src/nvim/generators/c_grammar.lua index f35817c466..70a7be86b5 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -49,6 +49,7 @@ local c_proto = Ct( (fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) * (fill * Cg((P('FUNC_API_BRIDGE_IMPL') * Cc(true)), 'bridge_impl') ^ -1) * (fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) * + (fill * Cg((P('FUNC_API_CLIENT_IMPL') * Cc(true)), 'client_impl') ^ -1) * fill * P(';') ) diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 21f8c3855e..c6dd25154b 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -441,8 +441,8 @@ local function process_function(fn) local cparam = string.format('arg%u', j) local param_type = real_type(param[1]) local lc_param_type = real_type(param[1]):lower() - local extra = ((param_type == "Object" or param_type == "Dictionary") and "false, ") or "" - if param[1] == "DictionaryOf(LuaRef)" then + local extra = param_type == "Dictionary" and "false, " or "" + if param[1] == "Object" or param[1] == "DictionaryOf(LuaRef)" then extra = "true, " end local errshift = 0 diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index 3cb117d8b5..5e70442dce 100644..100755 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -3,13 +3,14 @@ local mpack = require('mpack') local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path -assert(#arg == 7) +assert(#arg == 8) local input = io.open(arg[2], 'rb') local proto_output = io.open(arg[3], 'wb') local call_output = io.open(arg[4], 'wb') local remote_output = io.open(arg[5], 'wb') local bridge_output = io.open(arg[6], 'wb') local metadata_output = io.open(arg[7], 'wb') +local client_output = io.open(arg[8], 'wb') local c_grammar = require('generators.c_grammar') local events = c_grammar.grammar:match(input:read('*all')) @@ -50,6 +51,52 @@ local function write_arglist(output, ev, need_copy) end end +local function call_ui_event_method(output, ev) + output:write('void ui_client_event_'..ev.name..'(Array args)\n{\n') + + local hlattrs_args_count = 0 + if #ev.parameters > 0 then + output:write(' if (args.size < '..(#ev.parameters)) + for j = 1, #ev.parameters do + local kind = ev.parameters[j][1] + if kind ~= "Object" then + if kind == 'HlAttrs' then kind = 'Dictionary' end + output:write('\n || args.items['..(j-1)..'].type != kObjectType'..kind..'') + end + end + output:write(') {\n') + output:write(' ELOG("Error handling ui event \''..ev.name..'\'");\n') + output:write(' return;\n') + output:write(' }\n') + end + + for j = 1, #ev.parameters do + local param = ev.parameters[j] + local kind = param[1] + output:write(' '..kind..' arg_'..j..' = ') + if kind == 'HlAttrs' then + -- The first HlAttrs argument is rgb_attrs and second is cterm_attrs + output:write('ui_client_dict2hlattrs(args.items['..(j-1)..'].data.dictionary, '..(hlattrs_args_count == 0 and 'true' or 'false')..');\n') + hlattrs_args_count = hlattrs_args_count + 1 + elseif kind == 'Object' then + output:write('args.items['..(j-1)..'];\n') + else + output:write('args.items['..(j-1)..'].data.'..string.lower(kind)..';\n') + end + end + + output:write(' ui_call_'..ev.name..'(') + for j = 1, #ev.parameters do + output:write('arg_'..j) + if j ~= #ev.parameters then + output:write(', ') + end + end + output:write(');\n') + + output:write('}\n\n') +end + for i = 1, #events do local ev = events[i] assert(ev.return_type == 'void') @@ -160,12 +207,35 @@ for i = 1, #events do call_output:write(";\n") call_output:write("}\n\n") end + + if (not ev.remote_only) and (not ev.noexport) and (not ev.client_impl) then + call_ui_event_method(client_output, ev) + end end +-- Generate the map_init method for client handlers +client_output:write([[ +void ui_client_methods_table_init(void) +{ + +]]) + +for i = 1, #events do + local fn = events[i] + if (not fn.noexport) and ((not fn.remote_only) or fn.client_impl) then + client_output:write(' add_ui_client_event_handler('.. + '(String) {.data = "'..fn.name..'", '.. + '.size = sizeof("'..fn.name..'") - 1}, '.. + '(UIClientHandler) ui_client_event_'..fn.name..');\n') + end +end + +client_output:write('\n}\n\n') + proto_output:close() call_output:close() remote_output:close() -bridge_output:close() +client_output:close() -- don't expose internal attributes like "impl_name" in public metadata local exported_attributes = {'name', 'parameters', diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua index a7dad50d48..11f6cbcc13 100644 --- a/src/nvim/generators/gen_char_blob.lua +++ b/src/nvim/generators/gen_char_blob.lua @@ -1,12 +1,26 @@ if arg[1] == '--help' then print('Usage:') - print(' '..arg[0]..' target source varname [source varname]...') + print(' '..arg[0]..' [-c] target source varname [source varname]...') print('') print('Generates C file with big uint8_t blob.') print('Blob will be stored in a static const array named varname.') os.exit() end +-- Recognized options: +-- -c compile Lua bytecode +local options = {} + +while true do + local opt = string.match(arg[1], "^-(%w)") + if not opt then + break + end + + options[opt] = true + table.remove(arg, 1) +end + assert(#arg >= 3 and (#arg - 1) % 2 == 0) local target_file = arg[1] or error('Need a target file') @@ -14,20 +28,39 @@ local target = io.open(target_file, 'w') target:write('#include <stdint.h>\n\n') -local varnames = {} +local index_items = {} + +local warn_on_missing_compiler = true +local modnames = {} for argi = 2, #arg, 2 do local source_file = arg[argi] - local varname = arg[argi + 1] - if varnames[varname] then - error(string.format("varname %q is already specified for file %q", varname, varnames[varname])) + local modname = arg[argi + 1] + if modnames[modname] then + error(string.format("modname %q is already specified for file %q", modname, modnames[modname])) end - varnames[varname] = source_file - - local source = io.open(source_file, 'r') - or error(string.format("source_file %q doesn't exist", source_file)) + modnames[modname] = source_file + local varname = string.gsub(modname,'%.','_dot_').."_module" target:write(('static const uint8_t %s[] = {\n'):format(varname)) + local output + if options.c then + local luac = os.getenv("LUAC_PRG") + if luac and luac ~= "" then + output = io.popen(luac:format(source_file), "r"):read("*a") + elseif warn_on_missing_compiler then + print("LUAC_PRG is missing, embedding raw source") + warn_on_missing_compiler = false + end + end + + if not output then + local source = io.open(source_file, "r") + or error(string.format("source_file %q doesn't exist", source_file)) + output = source:read("*a") + source:close() + end + local num_bytes = 0 local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line target:write(' ') @@ -41,19 +74,20 @@ for argi = 2, #arg, 2 do end end - for line in source:lines() do - for i = 1, string.len(line) do - local byte = line:byte(i) - assert(byte ~= 0) - target:write(string.format(' %3u,', byte)) - increase_num_bytes() - end - target:write(string.format(' %3u,', string.byte('\n', 1))) + for i = 1, string.len(output) do + local byte = output:byte(i) + target:write(string.format(' %3u,', byte)) increase_num_bytes() end - target:write(' 0};\n') - source:close() + target:write(' 0};\n') + if modname ~= "_" then + table.insert(index_items, ' { "'..modname..'", '..varname..', sizeof '..varname..' },\n\n') + end end +target:write('static ModuleDef builtin_modules[] = {\n') +target:write(table.concat(index_items)) +target:write('};\n') + target:close() diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua index 63ef202fe1..633c5da184 100644 --- a/src/nvim/generators/gen_keysets.lua +++ b/src/nvim/generators/gen_keysets.lua @@ -26,6 +26,18 @@ local defspipe = io.open(defs_file, 'wb') local keysets = require'api.keysets' +local keywords = { + register = true; + default = true; +} + +local function sanitize(key) + if keywords[key] then + return key .. "_" + end + return key +end + for name, keys in pairs(keysets) do local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx) return name.."_table["..idx.."].str" @@ -33,7 +45,7 @@ for name, keys in pairs(keysets) do defspipe:write("typedef struct {\n") for _, key in ipairs(neworder) do - defspipe:write(" Object "..key..";\n") + defspipe:write(" Object "..sanitize(key)..";\n") end defspipe:write("} KeyDict_"..name..";\n\n") @@ -41,7 +53,7 @@ for name, keys in pairs(keysets) do funcspipe:write("KeySetLink "..name.."_table[] = {\n") for _, key in ipairs(neworder) do - funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..key..")},\n") + funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..sanitize(key)..")},\n") end funcspipe:write(' {NULL, 0},\n') funcspipe:write("};\n\n") diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 2f4b59837a..85479b220a 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -15,6 +15,7 @@ #include <stdbool.h> #include <string.h> +#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/buffer_defs.h" @@ -29,6 +30,7 @@ #include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/input.h" #include "nvim/keymap.h" #include "nvim/lua/executor.h" #include "nvim/main.h" @@ -36,7 +38,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" @@ -58,25 +59,18 @@ static int curscript = 0; FileDescriptor *scriptin[NSCRIPT] = { NULL }; -/* - * These buffers are used for storing: - * - stuffed characters: A command that is translated into another command. - * - redo characters: will redo the last change. - * - recorded characters: for the "q" command. - * - * The bytes are stored like in the typeahead buffer: - * - K_SPECIAL introduces a special key (two more bytes follow). A literal - * K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER. - * - CSI introduces a GUI termcap code (also when gui.in_use is FALSE, - * otherwise switching the GUI on would make mappings invalid). - * A literal CSI is stored as CSI KS_EXTRA KE_CSI. - * These translations are also done on multi-byte characters! - * - * Escaping CSI bytes is done by the system-specific input functions, called - * by ui_inchar(). - * Escaping K_SPECIAL is done by inchar(). - * Un-escaping is done by vgetc(). - */ +// These buffers are used for storing: +// - stuffed characters: A command that is translated into another command. +// - redo characters: will redo the last change. +// - recorded characters: for the "q" command. +// +// The bytes are stored like in the typeahead buffer: +// - K_SPECIAL introduces a special key (two more bytes follow). A literal +// K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER. +// These translations are also done on multi-byte characters! +// +// Escaping K_SPECIAL is done by inchar(). +// Un-escaping is done by vgetc(). #define MINIMAL_SIZE 20 // minimal size for b_str @@ -146,7 +140,7 @@ static int KeyNoremap = 0; // remapping flags // typebuf.tb_buf has three parts: room in front (for result of mappings), the // middle for typeahead and room for new characters (which needs to be 3 * -// MAXMAPLEN) for the Amiga). +// MAXMAPLEN for the Amiga). #define TYPELEN_INIT (5 * (MAXMAPLEN + 3)) static char_u typebuf_init[TYPELEN_INIT]; // initial typebuf.tb_buf static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap @@ -169,10 +163,11 @@ void free_buff(buffheader_T *buf) xfree(p); } buf->bh_first.b_next = NULL; + buf->bh_curr = NULL; } /// Return the contents of a buffer as a single string. -/// K_SPECIAL and CSI in the returned string are escaped. +/// K_SPECIAL in the returned string is escaped. /// /// @param dozero count == zero is not an error static char_u *get_buffcont(buffheader_T *buffer, int dozero) @@ -201,11 +196,9 @@ static char_u *get_buffcont(buffheader_T *buffer, int dozero) return p; } -/* - * Return the contents of the record buffer as a single string - * and clear the record buffer. - * K_SPECIAL and CSI in the returned string are escaped. - */ +/// Return the contents of the record buffer as a single string +/// and clear the record buffer. +/// K_SPECIAL in the returned string is escaped. char_u *get_recorded(void) { char_u *p; @@ -235,10 +228,8 @@ char_u *get_recorded(void) return p; } -/* - * Return the contents of the redo buffer as a single string. - * K_SPECIAL and CSI in the returned string are escaped. - */ +/// Return the contents of the redo buffer as a single string. +/// K_SPECIAL in the returned string is escaped. char_u *get_inserted(void) { return get_buffcont(&redobuff, FALSE); @@ -246,7 +237,7 @@ char_u *get_inserted(void) /// Add string after the current block of the given buffer /// -/// K_SPECIAL and CSI should have been escaped already. +/// K_SPECIAL should have been escaped already. /// /// @param[out] buf Buffer to add to. /// @param[in] s String to add. @@ -294,9 +285,23 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle } } -/* - * Add number "n" to buffer "buf". - */ +/// Delete "slen" bytes from the end of "buf". +/// Only works when it was just added. +static void delete_buff_tail(buffheader_T *buf, int slen) +{ + int len; + + if (buf->bh_curr == NULL) { + return; // nothing to delete + } + len = (int)STRLEN(buf->bh_curr->b_str); + if (len >= slen) { + buf->bh_curr->b_str[len - slen] = NUL; + buf->bh_space += (size_t)slen; + } +} + +/// Add number "n" to buffer "buf". static void add_num_buff(buffheader_T *buf, long n) { char number[32]; @@ -304,10 +309,8 @@ static void add_num_buff(buffheader_T *buf, long n) add_buff(buf, number, -1L); } -/* - * Add character 'c' to buffer "buf". - * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters. - */ +/// Add character 'c' to buffer "buf". +/// Translates special keys, NUL, K_SPECIAL and multibyte characters. static void add_char_buff(buffheader_T *buf, int c) { uint8_t bytes[MB_MAXBYTES + 1]; @@ -339,12 +342,10 @@ static void add_char_buff(buffheader_T *buf, int c) } } -/* - * Get one byte from the read buffers. Use readbuf1 one first, use readbuf2 - * if that one is empty. - * If advance == TRUE go to the next char. - * No translation is done K_SPECIAL and CSI are escaped. - */ +/// Get one byte from the read buffers. Use readbuf1 one first, use readbuf2 +/// if that one is empty. +/// If advance == TRUE go to the next char. +/// No translation is done K_SPECIAL is escaped. static int read_readbuffers(int advance) { int c; @@ -458,6 +459,15 @@ void flush_buffers(flush_buffers_T flush_typeahead) } } +/// flush map and typeahead buffers and give a warning for an error +void beep_flush(void) +{ + if (emsg_silent == 0) { + flush_buffers(FLUSH_MINIMAL); + vim_beep(BO_ERROR); + } +} + /* * The previous contents of the redo buffer is kept in old_redobuffer. * This is used for the CTRL-O <.> command in insert mode. @@ -514,10 +524,8 @@ void restoreRedobuff(save_redo_T *save_redo) old_redobuff = save_redo->sr_old_redobuff; } -/* - * Append "s" to the redo buffer. - * K_SPECIAL and CSI should already have been escaped. - */ +/// Append "s" to the redo buffer. +/// K_SPECIAL should already have been escaped. void AppendToRedobuff(const char *s) { if (!block_redo) { @@ -526,7 +534,7 @@ void AppendToRedobuff(const char *s) } /// Append to Redo buffer literally, escaping special characters with CTRL-V. -/// K_SPECIAL and CSI are escaped as well. +/// K_SPECIAL is escaped as well. /// /// @param str String to append /// @param len Length of `str` or -1 for up to the NUL. @@ -574,10 +582,8 @@ void AppendToRedobuffLit(const char_u *str, int len) } } -/* - * Append a character to the redo buffer. - * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters. - */ +/// Append a character to the redo buffer. +/// Translates special keys, NUL, K_SPECIAL and multibyte characters. void AppendCharToRedobuff(int c) { if (!block_redo) { @@ -595,17 +601,15 @@ void AppendNumberToRedobuff(long n) } } -/* - * Append string "s" to the stuff buffer. - * CSI and K_SPECIAL must already have been escaped. - */ +/// Append string "s" to the stuff buffer. +/// K_SPECIAL must already have been escaped. void stuffReadbuff(const char *s) { add_buff(&readbuf1, s, -1L); } /// Append string "s" to the redo stuff buffer. -/// @remark CSI and K_SPECIAL must already have been escaped. +/// @remark K_SPECIAL must already have been escaped. void stuffRedoReadbuff(const char *s) { add_buff(&readbuf2, s, -1L); @@ -616,11 +620,9 @@ void stuffReadbuffLen(const char *s, long len) add_buff(&readbuf1, s, len); } -/* - * Stuff "s" into the stuff buffer, leaving special key codes unmodified and - * escaping other K_SPECIAL and CSI bytes. - * Change CR, LF and ESC into a space. - */ +/// Stuff "s" into the stuff buffer, leaving special key codes unmodified and +/// escaping other K_SPECIAL bytes. +/// Change CR, LF and ESC into a space. void stuffReadbuffSpec(const char *s) { while (*s != NUL) { @@ -638,10 +640,8 @@ void stuffReadbuffSpec(const char *s) } } -/* - * Append a character to the stuff buffer. - * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters. - */ +/// Append a character to the stuff buffer. +/// Translates special keys, NUL, K_SPECIAL and multibyte characters. void stuffcharReadbuff(int c) { add_char_buff(&readbuf1, c); @@ -655,12 +655,12 @@ void stuffnumReadbuff(long n) add_num_buff(&readbuf1, n); } -// Read a character from the redo buffer. Translates K_SPECIAL, CSI and -// multibyte characters. -// The redo buffer is left as it is. -// If init is true, prepare for redo, return FAIL if nothing to redo, OK -// otherwise. -// If old_redo is true, use old_redobuff instead of redobuff. +/// Read a character from the redo buffer. Translates K_SPECIAL and +/// multibyte characters. +/// The redo buffer is left as it is. +/// If init is true, prepare for redo, return FAIL if nothing to redo, OK +/// otherwise. +/// If old_redo is true, use old_redobuff instead of redobuff. static int read_redo(bool init, bool old_redo) { static buffblock_T *bp; @@ -714,9 +714,9 @@ static int read_redo(bool init, bool old_redo) return c; } -// Copy the rest of the redo buffer into the stuff buffer (in a slow way). -// If old_redo is true, use old_redobuff instead of redobuff. -// The escaped K_SPECIAL and CSI are copied without translation. +/// Copy the rest of the redo buffer into the stuff buffer (in a slow way). +/// If old_redo is true, use old_redobuff instead of redobuff. +/// The escaped K_SPECIAL is copied without translation. static void copy_redo(bool old_redo) { int c; @@ -842,7 +842,10 @@ static void init_typebuf(void) void init_default_mappings(void) { add_map((char_u *)"Y y$", NORMAL, true); - add_map((char_u *)"<C-L> <Cmd>nohlsearch<Bar>diffupdate<CR><C-L>", NORMAL, true); + + // Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O> + // See https://github.com/neovim/neovim/issues/17473 + add_map((char_u *)"<C-L> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>", NORMAL, true); add_map((char_u *)"<C-U> <C-G>u<C-U>", INSERT, true); add_map((char_u *)"<C-W> <C-G>u<C-W>", INSERT, true); } @@ -852,7 +855,7 @@ void init_default_mappings(void) // // If noremap is REMAP_YES, new string can be mapped again. // If noremap is REMAP_NONE, new string cannot be mapped again. -// If noremap is REMAP_SKIP, fist char of new string cannot be mapped again, +// If noremap is REMAP_SKIP, first char of new string cannot be mapped again, // but abbreviations are allowed. // If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for // script-local mappings. @@ -982,36 +985,34 @@ int ins_typebuf(char_u *str, int noremap, int offset, bool nottyped, bool silent return OK; } -/* - * Put character "c" back into the typeahead buffer. - * Can be used for a character obtained by vgetc() that needs to be put back. - * Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to - * the char. - */ -void ins_char_typebuf(int c) +/// Put character "c" back into the typeahead buffer. +/// Can be used for a character obtained by vgetc() that needs to be put back. +/// Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to +/// the char. +/// @return the length of what was inserted +int ins_char_typebuf(int c, int modifier) { - char_u buf[MB_MAXBYTES + 1]; - if (IS_SPECIAL(c)) { + char_u buf[MB_MAXBYTES * 3 + 4]; + int len = 0; + if (modifier != 0) { buf[0] = K_SPECIAL; - buf[1] = (char_u)K_SECOND(c); - buf[2] = (char_u)K_THIRD(c); + buf[1] = KS_MODIFIER; + buf[2] = (char_u)modifier; buf[3] = NUL; + len = 3; + } + if (IS_SPECIAL(c)) { + buf[len] = K_SPECIAL; + buf[len + 1] = (char_u)K_SECOND(c); + buf[len + 2] = (char_u)K_THIRD(c); + buf[len + 3] = NUL; } else { - buf[utf_char2bytes(c, buf)] = NUL; - char_u *p = buf; - while (*p) { - if ((uint8_t)(*p) == CSI || (uint8_t)(*p) == K_SPECIAL) { - bool is_csi = (uint8_t)(*p) == CSI; - memmove(p + 3, p + 1, STRLEN(p + 1) + 1); - *p++ = K_SPECIAL; - *p++ = is_csi ? KS_EXTRA : KS_SPECIAL; - *p++ = is_csi ? KE_CSI : KE_FILLER; - } else { - p++; - } - } + char_u *end = add_char2buf(c, buf + len); + *end = NUL; + len = (int)(end - buf); } (void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent); + return len; } /// Return TRUE if the typeahead buffer was changed (while waiting for a @@ -1171,6 +1172,18 @@ static void gotchars(const char_u *chars, size_t len) maptick++; } +/// Undo the last gotchars() for "len" bytes. To be used when putting a typed +/// character back into the typeahead buffer, thus gotchars() will be called +/// again. +/// Only affects recorded characters. +void ungetchars(int len) +{ + if (reg_recording != 0) { + delete_buff_tail(&recordbuff, len); + last_recorded_len -= (size_t)len; + } +} + /* * Sync undo. Called when typed characters are obtained from the typeahead * buffer, or when a menu is used. @@ -1240,7 +1253,14 @@ static int old_mod_mask; // mod_mask for ungotten character static int old_mouse_grid; // mouse_grid related to old_char static int old_mouse_row; // mouse_row related to old_char static int old_mouse_col; // mouse_col related to old_char +static int old_KeyStuffed; // whether old_char was stuffed +static bool can_get_old_char(void) +{ + // If the old character was not stuffed and characters have been added to + // the stuff buffer, need to first get the stuffed characters instead. + return old_char != -1 && (old_KeyStuffed || stuff_empty()); +} /* * Save all three kinds of typeahead, so that the user must type at a prompt. @@ -1417,15 +1437,13 @@ static void updatescript(int c) } } -/* - * Get the next input character. - * Can return a special key or a multi-byte character. - * Can return NUL when called recursively, use safe_vgetc() if that's not - * wanted. - * This translates escaped K_SPECIAL and CSI bytes to a K_SPECIAL or CSI byte. - * Collects the bytes of a multibyte character into the whole character. - * Returns the modifiers in the global "mod_mask". - */ +/// Get the next input character. +/// Can return a special key or a multi-byte character. +/// Can return NUL when called recursively, use safe_vgetc() if that's not +/// wanted. +/// This translates escaped K_SPECIAL bytes to a K_SPECIAL byte. +/// Collects the bytes of a multibyte character into the whole character. +/// Returns the modifiers in the global "mod_mask". int vgetc(void) { int c, c2; @@ -1443,7 +1461,7 @@ int vgetc(void) * If a character was put back with vungetc, it was already processed. * Return it directly. */ - if (old_char != -1) { + if (can_get_old_char()) { c = old_char; old_char = -1; mod_mask = old_mod_mask; @@ -1451,8 +1469,15 @@ int vgetc(void) mouse_row = old_mouse_row; mouse_col = old_mouse_col; } else { - mod_mask = 0x0; - last_recorded_len = 0; + // number of characters recorded from the last vgetc() call + static size_t last_vgetc_recorded_len = 0; + + mod_mask = 0; + + // last_recorded_len can be larger than last_vgetc_recorded_len + // if peeking records more + last_recorded_len -= last_vgetc_recorded_len; + for (;;) { // this is done twice if there are modifiers bool did_inc = false; if (mod_mask) { // no mapping after modifier has been read @@ -1562,20 +1587,31 @@ int vgetc(void) buf[i] = (char_u)vgetorpeek(true); if (buf[i] == K_SPECIAL) { // Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence, - // which represents a K_SPECIAL (0x80), - // or a CSI - KS_EXTRA - KE_CSI sequence, which represents - // a CSI (0x9B), - // of a K_SPECIAL - KS_EXTRA - KE_CSI, which is CSI too. - c = vgetorpeek(true); - if (vgetorpeek(true) == KE_CSI && c == KS_EXTRA) { - buf[i] = CSI; - } + // which represents a K_SPECIAL (0x80). + (void)vgetorpeek(true); // skip KS_SPECIAL + (void)vgetorpeek(true); // skip KE_FILLER } } no_mapping--; c = utf_ptr2char(buf); } + // A modifier was not used for a mapping, apply it to ASCII + // keys. Shift would already have been applied. + if (mod_mask & MOD_MASK_CTRL) { + if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')) { + c &= 0x1f; + mod_mask &= ~MOD_MASK_CTRL; + if (c == 0) { + c = K_ZERO; + } + } else if (c == '6') { + // CTRL-6 is equivalent to CTRL-^ + c = 0x1e; + mod_mask &= ~MOD_MASK_CTRL; + } + } + // 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 @@ -1583,13 +1619,16 @@ int vgetc(void) if (!no_mapping && KeyTyped && !(State & TERM_FOCUS) && (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META)) { mod_mask = 0; - ins_char_typebuf(c); - ins_char_typebuf(ESC); + int len = ins_char_typebuf(c, 0); + (void)ins_char_typebuf(ESC, 0); + ungetchars(len + 3); // The ALT/META modifier takes three more bytes continue; } break; } + + last_vgetc_recorded_len = last_recorded_len; } /* @@ -1644,7 +1683,7 @@ int plain_vgetc(void) */ int vpeekc(void) { - if (old_char != -1) { + if (can_get_old_char()) { return old_char; } return vgetorpeek(false); @@ -1680,7 +1719,365 @@ int char_avail(void) return retval != NUL; } -// unget one character (can only be done once!) +typedef enum { + map_result_fail, // failed, break loop + map_result_get, // get a character from typeahead + map_result_retry, // try to map again + map_result_nomatch, // no matching mapping, get char +} map_result_T; + +/// Handle mappings in the typeahead buffer. +/// - When something was mapped, return map_result_retry for recursive mappings. +/// - When nothing mapped and typeahead has a character: return map_result_get. +/// - When there is no match yet, return map_result_nomatch, need to get more +/// typeahead. +static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) +{ + mapblock_T *mp = NULL; + mapblock_T *mp2; + mapblock_T *mp_match; + int mp_match_len = 0; + int max_mlen = 0; + int tb_c1; + int mlen; + int nolmaplen; + int keylen = *keylenp; + int i; + int local_State = get_real_state(); + bool is_plug_map = false; + + // If typehead starts with <Plug> then remap, even for a "noremap" mapping. + if (typebuf.tb_buf[typebuf.tb_off] == K_SPECIAL + && typebuf.tb_buf[typebuf.tb_off + 1] == KS_EXTRA + && typebuf.tb_buf[typebuf.tb_off + 2] == KE_PLUG) { + is_plug_map = true; + } + + // Check for a mappable key sequence. + // Walk through one maphash[] list until we find an entry that matches. + // + // Don't look for mappings if: + // - no_mapping set: mapping disabled (e.g. for CTRL-V) + // - maphash_valid not set: no mappings present. + // - typebuf.tb_buf[typebuf.tb_off] should not be remapped + // - in insert or cmdline mode and 'paste' option set + // - waiting for "hit return to continue" and CR or SPACE typed + // - waiting for a char with --more-- + // - in Ctrl-X mode, and we get a valid char for that mode + tb_c1 = typebuf.tb_buf[typebuf.tb_off]; + if (no_mapping == 0 && maphash_valid + && (no_zero_mapping == 0 || tb_c1 != '0') + && (typebuf.tb_maplen == 0 || is_plug_map + || (p_remap + && !(typebuf.tb_noremap[typebuf.tb_off] & (RM_NONE|RM_ABBR)))) + && !(p_paste && (State & (INSERT + CMDLINE))) + && !(State == HITRETURN && (tb_c1 == CAR || tb_c1 == ' ')) + && State != ASKMORE + && State != CONFIRM + && !((ctrl_x_mode_not_default() && vim_is_ctrl_x_key(tb_c1)) + || ((compl_cont_status & CONT_LOCAL) + && (tb_c1 == Ctrl_N || tb_c1 == Ctrl_P)))) { + if (tb_c1 == K_SPECIAL) { + nolmaplen = 2; + } else { + LANGMAP_ADJUST(tb_c1, !(State & (CMDLINE | INSERT)) && get_real_state() != SELECTMODE); + nolmaplen = 0; + } + // First try buffer-local mappings. + mp = curbuf->b_maphash[MAP_HASH(local_State, tb_c1)]; + mp2 = maphash[MAP_HASH(local_State, tb_c1)]; + if (mp == NULL) { + // There are no buffer-local mappings. + mp = mp2; + mp2 = NULL; + } + // Loop until a partly matching mapping is found or all (local) + // mappings have been checked. + // The longest full match is remembered in "mp_match". + // A full match is only accepted if there is no partly match, so "aa" + // and "aaa" can both be mapped. + mp_match = NULL; + mp_match_len = 0; + for (; mp != NULL; mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : (mp = mp->m_next)) { + // Only consider an entry if the first character matches and it is + // for the current state. + // Skip ":lmap" mappings if keys were mapped. + if (mp->m_keys[0] == tb_c1 && (mp->m_mode & local_State) + && ((mp->m_mode & LANGMAP) == 0 || typebuf.tb_maplen == 0)) { + int nomap = nolmaplen; + int c2; + // find the match length of this mapping + for (mlen = 1; mlen < typebuf.tb_len; mlen++) { + c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; + if (nomap > 0) { + nomap--; + } else if (c2 == K_SPECIAL) { + nomap = 2; + } else { + LANGMAP_ADJUST(c2, true); + } + if (mp->m_keys[mlen] != c2) { + break; + } + } + + // Don't allow mapping the first byte(s) of a multi-byte char. + // Happens when mapping <M-a> and then changing 'encoding'. + // Beware that 0x80 is escaped. + char_u *p1 = mp->m_keys; + char_u *p2 = (char_u *)mb_unescape((const char **)&p1); + + if (p2 != NULL && MB_BYTE2LEN(tb_c1) > utfc_ptr2len(p2)) { + mlen = 0; + } + + // Check an entry whether it matches. + // - Full match: mlen == keylen + // - Partly match: mlen == typebuf.tb_len + keylen = mp->m_keylen; + if (mlen == keylen || (mlen == typebuf.tb_len && typebuf.tb_len < keylen)) { + char_u *s; + int n; + + // If only script-local mappings are allowed, check if the + // mapping starts with K_SNR. + s = typebuf.tb_noremap + typebuf.tb_off; + if (*s == RM_SCRIPT + && (mp->m_keys[0] != K_SPECIAL + || mp->m_keys[1] != KS_EXTRA + || mp->m_keys[2] != KE_SNR)) { + continue; + } + + // If one of the typed keys cannot be remapped, skip the entry. + for (n = mlen; --n >= 0;) { + if (*s++ & (RM_NONE|RM_ABBR)) { + break; + } + } + if (!is_plug_map && n >= 0) { + continue; + } + + if (keylen > typebuf.tb_len) { + if (!*timedout && !(mp_match != NULL && mp_match->m_nowait)) { + // break at a partly match + keylen = KEYLEN_PART_MAP; + break; + } + } else if (keylen > mp_match_len + || (keylen == mp_match_len + && mp_match != NULL + && (mp_match->m_mode & LANGMAP) == 0 + && (mp->m_mode & LANGMAP) != 0)) { + // found a longer match + mp_match = mp; + mp_match_len = keylen; + } + } else { + // No match; may have to check for termcode at next character. + if (max_mlen < mlen) { + max_mlen = mlen; + } + } + } + } + + // If no partly match found, use the longest full match. + if (keylen != KEYLEN_PART_MAP) { + mp = mp_match; + keylen = mp_match_len; + } + } + + // Check for match with 'pastetoggle' + if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL))) { + bool match = typebuf_match_len(p_pt, &mlen); + if (match) { + // write chars to script file(s) + if (mlen > typebuf.tb_maplen) { + gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, + (size_t)(mlen - typebuf.tb_maplen)); + } + + del_typebuf(mlen, 0); // remove the chars + set_option_value("paste", !p_paste, NULL, 0); + if (!(State & INSERT)) { + msg_col = 0; + msg_row = Rows - 1; + msg_clr_eos(); // clear ruler + } + status_redraw_all(); + redraw_statuslines(); + showmode(); + setcursor(); + *keylenp = keylen; + return map_result_retry; + } + // Need more chars for partly match. + if (mlen == typebuf.tb_len) { + keylen = KEYLEN_PART_KEY; + } else if (max_mlen < mlen) { + // no match, may have to check for termcode at next character + max_mlen = mlen + 1; + } + } + + if ((mp == NULL || max_mlen >= mp_match_len) && keylen != KEYLEN_PART_MAP) { + // No matching mapping found or found a non-matching mapping that + // matches at least what the matching mapping matched + keylen = 0; + (void)keylen; // suppress clang/dead assignment + // If there was no mapping, use the character from the typeahead + // buffer right here. Otherwise, use the mapping (loop around). + if (mp == NULL) { + *keylenp = keylen; + return map_result_get; // get character from typeahead + } else { + keylen = mp_match_len; + } + } + + // complete match + if (keylen >= 0 && keylen <= typebuf.tb_len) { + char_u *map_str = NULL; + + // Write chars to script file(s). + // Note: :lmap mappings are written *after* being applied. #5658 + if (keylen > typebuf.tb_maplen && (mp->m_mode & LANGMAP) == 0) { + gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, + (size_t)(keylen - typebuf.tb_maplen)); + } + + cmd_silent = (typebuf.tb_silent > 0); + del_typebuf(keylen, 0); // remove the mapped keys + + // Put the replacement string in front of mapstr. + // The depth check catches ":map x y" and ":map y x". + if (++*mapdepth >= p_mmd) { + emsg(_("E223: recursive mapping")); + if (State & CMDLINE) { + redrawcmdline(); + } else { + setcursor(); + } + flush_buffers(FLUSH_MINIMAL); + *mapdepth = 0; // for next one + *keylenp = keylen; + return map_result_fail; + } + + // In Select mode and a Visual mode mapping is used: Switch to Visual + // mode temporarily. Append K_SELECT to switch back to Select mode. + if (VIsual_active && VIsual_select && (mp->m_mode & VISUAL)) { + VIsual_select = false; + (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, true, false); + } + + // Copy the values from *mp that are used, because evaluating the + // expression may invoke a function that redefines the mapping, thereby + // making *mp invalid. + char save_m_expr = mp->m_expr; + int save_m_noremap = mp->m_noremap; + char save_m_silent = mp->m_silent; + char_u *save_m_keys = NULL; // only saved when needed + char_u *save_m_str = NULL; // only saved when needed + LuaRef save_m_luaref = mp->m_luaref; + + // Handle ":map <expr>": evaluate the {rhs} as an + // expression. Also save and restore the command line + // for "normal :". + if (mp->m_expr) { + const int save_vgetc_busy = vgetc_busy; + const bool save_may_garbage_collect = may_garbage_collect; + const int save_cursor_row = ui_current_row(); + const int save_cursor_col = ui_current_col(); + const int prev_did_emsg = did_emsg; + + vgetc_busy = 0; + may_garbage_collect = false; + + save_m_keys = vim_strsave(mp->m_keys); + if (save_m_luaref == LUA_NOREF) { + save_m_str = vim_strsave(mp->m_str); + } + map_str = eval_map_expr(mp, NUL); + + // The mapping may do anything, but we expect it to take care of + // redrawing. Do put the cursor back where it was. + ui_cursor_goto(save_cursor_row, save_cursor_col); + ui_flush(); + + // If an error was displayed and the expression returns an empty + // string, generate a <Nop> to allow for a redraw. + if (prev_did_emsg != did_emsg && (map_str == NULL || *map_str == NUL)) { + char_u buf[4]; + xfree(map_str); + buf[0] = K_SPECIAL; + buf[1] = KS_EXTRA; + buf[2] = KE_IGNORE; + buf[3] = NUL; + map_str = vim_strsave(buf); + if (State & CMDLINE) { + // redraw the command below the error + msg_didout = true; + if (msg_row < cmdline_row) { + msg_row = cmdline_row; + } + redrawcmd(); + } + } + + vgetc_busy = save_vgetc_busy; + may_garbage_collect = save_may_garbage_collect; + } else { + map_str = mp->m_str; + } + + // Insert the 'to' part in the typebuf.tb_buf. + // If 'from' field is the same as the start of the 'to' field, don't + // remap the first character (but do allow abbreviations). + // If m_noremap is set, don't remap the whole 'to' part. + if (map_str == NULL) { + i = FAIL; + } else { + int noremap; + + // If this is a LANGMAP mapping, then we didn't record the keys + // at the start of the function and have to record them now. + if (keylen > typebuf.tb_maplen && (mp->m_mode & LANGMAP) != 0) { + gotchars(map_str, STRLEN(map_str)); + } + + if (save_m_noremap != REMAP_YES) { + noremap = save_m_noremap; + } else if (STRNCMP(map_str, save_m_keys != NULL ? save_m_keys : mp->m_keys, + (size_t)keylen) != 0) { + noremap = REMAP_YES; + } else { + noremap = REMAP_SKIP; + } + i = ins_typebuf(map_str, noremap, 0, true, cmd_silent || save_m_silent); + if (save_m_expr) { + xfree(map_str); + } + } + xfree(save_m_keys); + xfree(save_m_str); + *keylenp = keylen; + if (i == FAIL) { + return map_result_fail; + } + return map_result_retry; + } + + *keylenp = keylen; + return map_result_nomatch; +} + +/// unget one character (can only be done once!) +/// If the character was stuffed, vgetc() will get it next time it is called. +/// Otherwise vgetc() will only get it when the stuff buffer is empty. void vungetc(int c) { old_char = c; @@ -1688,6 +2085,21 @@ void vungetc(int c) old_mouse_grid = mouse_grid; old_mouse_row = mouse_row; old_mouse_col = mouse_col; + old_KeyStuffed = KeyStuffed; +} + +/// When peeking and not getting a character, reg_executing cannot be cleared +/// yet, so set a flag to clear it later. +void check_end_reg_executing(bool advance) +{ + if (reg_executing != 0 && (typebuf.tb_maplen == 0 || pending_end_reg_executing)) { + if (advance) { + reg_executing = 0; + pending_end_reg_executing = false; + } else { + pending_end_reg_executing = true; + } + } } /// Gets a byte: @@ -1711,48 +2123,31 @@ void vungetc(int c) /// /// When `no_mapping` (global) is zero, checks for mappings in the current mode. /// Only returns one byte (of a multi-byte character). -/// K_SPECIAL and CSI may be escaped, need to get two more bytes then. +/// K_SPECIAL may be escaped, need to get two more bytes then. static int vgetorpeek(bool advance) { int c, c1; - int keylen; - char_u *s; - mapblock_T *mp; - mapblock_T *mp2; - mapblock_T *mp_match; - int mp_match_len = 0; - bool timedout = false; // waited for more than 1 second - // for mapping to complete + bool timedout = false; // waited for more than 1 second for mapping to complete int mapdepth = 0; // check for recursive mapping bool mode_deleted = false; // set when mode has been deleted - int local_State; - int mlen; - int max_mlen; - int i; int new_wcol, new_wrow; int n; - int nolmaplen; int old_wcol, old_wrow; int wait_tb_len; - /* - * This function doesn't work very well when called recursively. This may - * happen though, because of: - * 1. The call to add_to_showcmd(). char_avail() is then used to check if - * there is a character available, which calls this function. In that - * case we must return NUL, to indicate no character is available. - * 2. A GUI callback function writes to the screen, causing a - * wait_return(). - * Using ":normal" can also do this, but it saves the typeahead buffer, - * thus it should be OK. But don't get a key from the user then. - */ - if (vgetc_busy > 0 - && ex_normal_busy == 0) { + // This function doesn't work very well when called recursively. This may + // happen though, because of: + // 1. The call to add_to_showcmd(). char_avail() is then used to check if + // there is a character available, which calls this function. In that + // case we must return NUL, to indicate no character is available. + // 2. A GUI callback function writes to the screen, causing a + // wait_return(). + // Using ":normal" can also do this, but it saves the typeahead buffer, + // thus it should be OK. But don't get a key from the user then. + if (vgetc_busy > 0 && ex_normal_busy == 0) { return NUL; } - local_State = get_real_state(); - ++vgetc_busy; if (advance) { @@ -1761,13 +2156,9 @@ static int vgetorpeek(bool advance) init_typebuf(); start_stuff(); - if (advance && typebuf.tb_maplen == 0) { - reg_executing = 0; - } + check_end_reg_executing(advance); do { - /* - * get a character: 1. from the stuffbuffer - */ + // get a character: 1. from the stuffbuffer if (typeahead_char != 0) { c = typeahead_char; if (advance) { @@ -1784,30 +2175,28 @@ static int vgetorpeek(bool advance) KeyStuffed = true; } if (typebuf.tb_no_abbr_cnt == 0) { - typebuf.tb_no_abbr_cnt = 1; // no abbreviations now + typebuf.tb_no_abbr_cnt = 1; // no abbreviations now } } else { - /* - * Loop until we either find a matching mapped key, or we - * are sure that it is not a mapped key. - * If a mapped key sequence is found we go back to the start to - * try re-mapping. - */ + // Loop until we either find a matching mapped key, or we + // are sure that it is not a mapped key. + // If a mapped key sequence is found we go back to the start to + // try re-mapping. for (;;) { - /* - * os_breakcheck() is slow, don't use it too often when - * inside a mapping. But call it each time for typed - * characters. - */ + check_end_reg_executing(advance); + // os_breakcheck() is slow, don't use it too often when + // inside a mapping. But call it each time for typed + // characters. if (typebuf.tb_maplen) { line_breakcheck(); } else { - os_breakcheck(); // check for CTRL-C + os_breakcheck(); // check for CTRL-C } - keylen = 0; + int keylen = 0; if (got_int) { // flush all input c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L); + // If inchar() returns TRUE (script file was active) or we // are inside a mapping, get out of Insert mode. // Otherwise we behave like having gotten a CTRL-C. @@ -1831,361 +2220,49 @@ static int vgetorpeek(bool advance) break; } else if (typebuf.tb_len > 0) { - /* - * Check for a mappable key sequence. - * Walk through one maphash[] list until we find an - * entry that matches. - * - * Don't look for mappings if: - * - no_mapping set: mapping disabled (e.g. for CTRL-V) - * - maphash_valid not set: no mappings present. - * - typebuf.tb_buf[typebuf.tb_off] should not be remapped - * - in insert or cmdline mode and 'paste' option set - * - waiting for "hit return to continue" and CR or SPACE - * typed - * - waiting for a char with --more-- - * - in Ctrl-X mode, and we get a valid char for that mode - */ - mp = NULL; - max_mlen = 0; - c1 = typebuf.tb_buf[typebuf.tb_off]; - if (no_mapping == 0 && maphash_valid - && (no_zero_mapping == 0 || c1 != '0') - && (typebuf.tb_maplen == 0 - || (p_remap - && (typebuf.tb_noremap[typebuf.tb_off] - & (RM_NONE|RM_ABBR)) == 0)) - && !(p_paste && (State & (INSERT + CMDLINE))) - && !(State == HITRETURN && (c1 == CAR || c1 == ' ')) - && State != ASKMORE - && State != CONFIRM - && !((ctrl_x_mode_not_default() && vim_is_ctrl_x_key(c1)) - || ((compl_cont_status & CONT_LOCAL) - && (c1 == Ctrl_N - || c1 == Ctrl_P)))) { - if (c1 == K_SPECIAL) { - nolmaplen = 2; - } else { - LANGMAP_ADJUST(c1, (State & (CMDLINE | INSERT)) == 0 - && get_real_state() != SELECTMODE); - nolmaplen = 0; - } - // First try buffer-local mappings. - mp = curbuf->b_maphash[MAP_HASH(local_State, c1)]; - mp2 = maphash[MAP_HASH(local_State, c1)]; - if (mp == NULL) { - // There are no buffer-local mappings. - mp = mp2; - mp2 = NULL; - } - /* - * Loop until a partly matching mapping is found or - * all (local) mappings have been checked. - * The longest full match is remembered in "mp_match". - * A full match is only accepted if there is no partly - * match, so "aa" and "aaa" can both be mapped. - */ - mp_match = NULL; - mp_match_len = 0; - for (; mp != NULL; - mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : - (mp = mp->m_next)) { - /* - * Only consider an entry if the first character - * matches and it is for the current state. - * Skip ":lmap" mappings if keys were mapped. - */ - if (mp->m_keys[0] == c1 - && (mp->m_mode & local_State) - && ((mp->m_mode & LANGMAP) == 0 - || typebuf.tb_maplen == 0)) { - int nomap = nolmaplen; - int c2; - // find the match length of this mapping - for (mlen = 1; mlen < typebuf.tb_len; mlen++) { - c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; - if (nomap > 0) { - --nomap; - } else if (c2 == K_SPECIAL) { - nomap = 2; - } else { - LANGMAP_ADJUST(c2, TRUE); - } - if (mp->m_keys[mlen] != c2) { - break; - } - } + // Check for a mapping in "typebuf". + map_result_T result = (map_result_T)handle_mapping(&keylen, &timedout, &mapdepth); - /* Don't allow mapping the first byte(s) of a - * multi-byte char. Happens when mapping - * <M-a> and then changing 'encoding'. Beware - * that 0x80 is escaped. */ - char_u *p1 = mp->m_keys; - char_u *p2 = (char_u *)mb_unescape((const char **)&p1); - - if (p2 != NULL && MB_BYTE2LEN(c1) > utfc_ptr2len(p2)) { - mlen = 0; - } - - // Check an entry whether it matches. - // - Full match: mlen == keylen - // - Partly match: mlen == typebuf.tb_len - keylen = mp->m_keylen; - if (mlen == keylen - || (mlen == typebuf.tb_len - && typebuf.tb_len < keylen)) { - /* - * If only script-local mappings are - * allowed, check if the mapping starts - * with K_SNR. - */ - s = typebuf.tb_noremap + typebuf.tb_off; - if (*s == RM_SCRIPT - && (mp->m_keys[0] != K_SPECIAL - || mp->m_keys[1] != KS_EXTRA - || mp->m_keys[2] - != KE_SNR)) { - continue; - } - /* - * If one of the typed keys cannot be - * remapped, skip the entry. - */ - for (n = mlen; --n >= 0;) { - if (*s++ & (RM_NONE|RM_ABBR)) { - break; - } - } - if (n >= 0) { - continue; - } - - if (keylen > typebuf.tb_len) { - if (!timedout && !(mp_match != NULL - && mp_match->m_nowait)) { - // break at a partly match - keylen = KEYLEN_PART_MAP; - break; - } - } else if (keylen > mp_match_len - || (keylen == mp_match_len - && mp_match != NULL - && (mp_match->m_mode & LANGMAP) == 0 - && (mp->m_mode & LANGMAP) != 0)) { - // found a longer match - mp_match = mp; - mp_match_len = keylen; - } - } else { - // No match; may have to check for termcode at next character. - if (max_mlen < mlen) { - max_mlen = mlen; - } - } - } - } - - /* If no partly match found, use the longest full - * match. */ - if (keylen != KEYLEN_PART_MAP) { - mp = mp_match; - keylen = mp_match_len; - } - } - - if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL))) { - bool match = typebuf_match_len(p_pt, &mlen); - if (match) { - // write chars to script file(s) - if (mlen > typebuf.tb_maplen) { - gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, - (size_t)(mlen - typebuf.tb_maplen)); - } - - del_typebuf(mlen, 0); // Remove the chars. - set_option_value("paste", !p_paste, NULL, 0); - if (!(State & INSERT)) { - msg_col = 0; - msg_row = Rows - 1; - msg_clr_eos(); // clear ruler - } - status_redraw_all(); - redraw_statuslines(); - showmode(); - setcursor(); - continue; - } - // Need more chars for partly match. - if (mlen == typebuf.tb_len) { - keylen = KEYLEN_PART_KEY; - } else if (max_mlen < mlen) { - // no match, may have to check for termcode at - // next character - max_mlen = mlen + 1; - } + if (result == map_result_retry) { + // try mapping again + continue; } - if ((mp == NULL || max_mlen >= mp_match_len) - && keylen != KEYLEN_PART_MAP) { - // No matching mapping found or found a non-matching mapping that - // matches at least what the matching mapping matched - keylen = 0; - (void)keylen; // suppress clang/dead assignment - // If there was no mapping, use the character from the typeahead - // buffer right here. Otherwise, use the mapping (loop around). - if (mp == NULL) { - // get a character: 2. from the typeahead buffer - c = typebuf.tb_buf[typebuf.tb_off] & 255; - if (advance) { // remove chars from tb_buf - cmd_silent = (typebuf.tb_silent > 0); - if (typebuf.tb_maplen > 0) { - KeyTyped = false; - } else { - KeyTyped = true; - // write char to script file(s) - gotchars(typebuf.tb_buf + typebuf.tb_off, 1); - } - KeyNoremap = typebuf.tb_noremap[typebuf.tb_off]; - del_typebuf(1, 0); - } - break; // got character, break for loop - } else { - keylen = mp_match_len; - } + if (result == map_result_fail) { + // failed, use the outer loop + c = -1; + break; } - // complete match - if (keylen >= 0 && keylen <= typebuf.tb_len) { - int save_m_expr; - int save_m_noremap; - int save_m_silent; - - // Write chars to script file(s) - // Note: :lmap mappings are written *after* being applied. #5658 - if (keylen > typebuf.tb_maplen && (mp->m_mode & LANGMAP) == 0) { - gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, - (size_t)(keylen - typebuf.tb_maplen)); - } - - cmd_silent = (typebuf.tb_silent > 0); - del_typebuf(keylen, 0); // remove the mapped keys - - /* - * Put the replacement string in front of mapstr. - * The depth check catches ":map x y" and ":map y x". - */ - if (++mapdepth >= p_mmd) { - emsg(_("E223: recursive mapping")); - if (State & CMDLINE) { - redrawcmdline(); - } else { - setcursor(); - } - flush_buffers(FLUSH_MINIMAL); - mapdepth = 0; // for next one - c = -1; - break; - } - - /* - * In Select mode and a Visual mode mapping is used: - * Switch to Visual mode temporarily. Append K_SELECT - * to switch back to Select mode. - */ - if (VIsual_active && VIsual_select - && (mp->m_mode & VISUAL)) { - VIsual_select = false; - (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, true, false); - } - - /* Copy the values from *mp that are used, because - * evaluating the expression may invoke a function - * that redefines the mapping, thereby making *mp - * invalid. */ - save_m_expr = mp->m_expr; - save_m_noremap = mp->m_noremap; - save_m_silent = mp->m_silent; - char_u *save_m_keys = NULL; // only saved when needed - char_u *save_m_str = NULL; // only saved when needed - - /* - * Handle ":map <expr>": evaluate the {rhs} as an - * expression. Also save and restore the command line - * for "normal :". - */ - 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; - may_garbage_collect = save_may_garbage_collect; - } else { - s = mp->m_str; - } - - /* - * Insert the 'to' part in the typebuf.tb_buf. - * If 'from' field is the same as the start of the - * 'to' field, don't remap the first character (but do - * allow abbreviations). - * If m_noremap is set, don't remap the whole 'to' - * part. - */ - if (s == NULL) { - i = FAIL; - } else { - int noremap; - - // If this is a LANGMAP mapping, then we didn't record the keys - // at the start of the function and have to record them now. - if (keylen > typebuf.tb_maplen && (mp->m_mode & LANGMAP) != 0) { - gotchars(s, STRLEN(s)); - } - - if (save_m_noremap != REMAP_YES) { - noremap = save_m_noremap; - } else if ( - STRNCMP(s, save_m_keys != NULL - ? save_m_keys : mp->m_keys, - (size_t)keylen) - != 0) { - noremap = REMAP_YES; + if (result == map_result_get) { + // get a character: 2. from the typeahead buffer + c = typebuf.tb_buf[typebuf.tb_off] & 255; + if (advance) { // remove chars from tb_buf + cmd_silent = (typebuf.tb_silent > 0); + if (typebuf.tb_maplen > 0) { + KeyTyped = false; } else { - noremap = REMAP_SKIP; + KeyTyped = true; + // write char to script file(s) + gotchars(typebuf.tb_buf + typebuf.tb_off, 1); } - i = ins_typebuf(s, noremap, - 0, TRUE, cmd_silent || save_m_silent); - if (save_m_expr) { - xfree(s); - } - } - xfree(save_m_keys); - xfree(save_m_str); - if (i == FAIL) { - c = -1; - break; + KeyNoremap = typebuf.tb_noremap[typebuf.tb_off]; + del_typebuf(1, 0); } - continue; + break; // got character, break the for loop } + + // not enough characters, get more } - /* - * get a character: 3. from the user - handle <Esc> in Insert mode - */ - /* - * special case: if we get an <ESC> in insert mode and there - * are no more characters at once, we pretend to go out of - * insert mode. This prevents the one second delay after - * typing an <ESC>. If we get something after all, we may - * have to redisplay the mode. That the cursor is in the wrong - * place does not matter. - */ + // get a character: 3. from the user - handle <Esc> in Insert mode + + // special case: if we get an <ESC> in insert mode and there + // are no more characters at once, we pretend to go out of + // insert mode. This prevents the one second delay after + // typing an <ESC>. If we get something after all, we may + // have to redisplay the mode. That the cursor is in the wrong + // place does not matter. c = 0; new_wcol = curwin->w_wcol; new_wrow = curwin->w_wrow; @@ -2196,10 +2273,8 @@ static int vgetorpeek(bool advance) && ex_normal_busy == 0 && typebuf.tb_maplen == 0 && (State & INSERT) - && (p_timeout - || (keylen == KEYLEN_PART_KEY && p_ttimeout)) - && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, - 3, 25L)) == 0) { + && (p_timeout || (keylen == KEYLEN_PART_KEY && p_ttimeout)) + && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25L)) == 0) { colnr_T col = 0, vcol; char_u *ptr; @@ -2215,11 +2290,9 @@ static int vgetorpeek(bool advance) if (curwin->w_cursor.col != 0) { if (curwin->w_wcol > 0) { if (did_ai) { - /* - * We are expecting to truncate the trailing - * white-space, so find the last non-white - * character -- webb - */ + // We are expecting to truncate the trailing + // white-space, so find the last non-white + // character -- webb col = vcol = curwin->w_wcol = 0; ptr = get_cursor_line_ptr(); while (col < curwin->w_cursor.col) { @@ -2233,7 +2306,7 @@ static int vgetorpeek(bool advance) + curwin->w_wcol / curwin->w_width_inner; curwin->w_wcol %= curwin->w_width_inner; curwin->w_wcol += curwin_col_off(); - col = 0; // no correction needed + col = 0; // no correction needed } else { --curwin->w_wcol; col = curwin->w_cursor.col - 1; @@ -2261,7 +2334,7 @@ static int vgetorpeek(bool advance) curwin->w_wrow = old_wrow; } if (c < 0) { - continue; // end of input script reached + continue; // end of input script reached } // Allow mapping for just typed characters. When we get here c @@ -2296,20 +2369,21 @@ static int vgetorpeek(bool advance) // cmdline window. if (p_im && (State & INSERT)) { c = Ctrl_L; - } else if (exmode_active) { - c = '\n'; } else if ((State & CMDLINE) || (cmdwin_type > 0 && tc == ESC)) { c = Ctrl_C; } else { c = ESC; } tc = c; + + // no chars to block abbreviations for + typebuf.tb_no_abbr_cnt = 0; + break; } - /* - * get a character: 3. from the user - update display - */ + // get a character: 3. from the user - update display + // In insert mode a screen update is skipped when characters // are still available. But when those available characters // are part of a mapping, and we are going to do a blocking @@ -2320,26 +2394,21 @@ static int vgetorpeek(bool advance) if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0 && advance && must_redraw != 0 && !need_wait_return) { update_screen(0); - setcursor(); // put cursor back where it belongs + setcursor(); // put cursor back where it belongs } - /* - * If we have a partial match (and are going to wait for more - * input from the user), show the partially matched characters - * to the user with showcmd. - */ - i = 0; + // If we have a partial match (and are going to wait for more + // input from the user), show the partially matched characters + // to the user with showcmd. + int showcmd_idx = 0; c1 = 0; if (typebuf.tb_len > 0 && advance && !exmode_active) { - if (((State & (NORMAL | INSERT)) || State == LANGMAP) - && State != HITRETURN) { + if (((State & (NORMAL | INSERT)) || State == LANGMAP) && State != HITRETURN) { // this looks nice when typing a dead character map if (State & INSERT - && ptr2cells(typebuf.tb_buf + typebuf.tb_off - + typebuf.tb_len - 1) == 1) { - edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1], - false); - setcursor(); // put cursor back where it belongs + && ptr2cells(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1) == 1) { + edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1], false); + setcursor(); // put cursor back where it belongs c1 = 1; } // need to use the col and row from above here @@ -2349,11 +2418,10 @@ static int vgetorpeek(bool advance) curwin->w_wrow = new_wrow; push_showcmd(); if (typebuf.tb_len > SHOWCMD_COLS) { - i = typebuf.tb_len - SHOWCMD_COLS; + showcmd_idx = typebuf.tb_len - SHOWCMD_COLS; } - while (i < typebuf.tb_len) { - (void)add_to_showcmd(typebuf.tb_buf[typebuf.tb_off - + i++]); + while (showcmd_idx < typebuf.tb_len) { + (void)add_to_showcmd(typebuf.tb_buf[typebuf.tb_off + showcmd_idx++]); } curwin->w_wcol = old_wcol; curwin->w_wrow = old_wrow; @@ -2369,9 +2437,7 @@ static int vgetorpeek(bool advance) } } - /* - * get a character: 3. from the user - get it - */ + // get a character: 3. from the user - get it if (typebuf.tb_len == 0) { // timedout may have been set while waiting for a mapping // that has a <Nop> RHS. @@ -2381,8 +2447,7 @@ static int vgetorpeek(bool advance) long wait_time = 0; if (advance) { - if (typebuf.tb_len == 0 - || !(p_timeout || (p_ttimeout && keylen == KEYLEN_PART_KEY))) { + if (typebuf.tb_len == 0 || !(p_timeout || (p_ttimeout && keylen == KEYLEN_PART_KEY))) { // blocking wait wait_time = -1L; } else if (keylen == KEYLEN_PART_KEY && p_ttm >= 0) { @@ -2397,7 +2462,7 @@ static int vgetorpeek(bool advance) typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1, wait_time); - if (i != 0) { + if (showcmd_idx != 0) { pop_showcmd(); } if (c1 == 1) { @@ -2407,47 +2472,45 @@ static int vgetorpeek(bool advance) if (State & CMDLINE) { unputcmdline(); } else { - setcursor(); // put cursor back where it belongs + setcursor(); // put cursor back where it belongs } } if (c < 0) { - continue; // end of input script reached + continue; // end of input script reached } - if (c == NUL) { // no character available + if (c == NUL) { // no character available if (!advance) { break; } - if (wait_tb_len > 0) { // timed out + if (wait_tb_len > 0) { // timed out timedout = true; continue; } - } else { // allow mapping for just typed characters + } else { // allow mapping for just typed characters while (typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] != NUL) { typebuf.tb_noremap[typebuf.tb_off + typebuf.tb_len++] = RM_YES; } } - } // for (;;) - } // if (!character from stuffbuf) + } // for (;;) + } // if (!character from stuffbuf) // if advance is false don't loop on NULs } while (c < 0 || (advance && c == NUL)); - /* - * The "INSERT" message is taken care of here: - * if we return an ESC to exit insert mode, the message is deleted - * if we don't return an ESC but deleted the message before, redisplay it - */ + // The "INSERT" message is taken care of here: + // if we return an ESC to exit insert mode, the message is deleted + // if we don't return an ESC but deleted the message before, redisplay it if (advance && p_smd && msg_silent == 0 && (State & INSERT)) { if (c == ESC && !mode_deleted && !no_mapping && mode_displayed) { if (typebuf.tb_len && !KeyTyped) { - redraw_cmdline = true; // delete mode later + redraw_cmdline = true; // delete mode later } else { unshowmode(false); } } else if (c != ESC && mode_deleted) { if (typebuf.tb_len && !KeyTyped) { - redraw_cmdline = true; // show mode later + redraw_cmdline = true; // show mode later } else { showmode(); } @@ -2474,7 +2537,7 @@ static int vgetorpeek(bool advance) /// 1. a scriptfile /// 2. the keyboard /// -/// As much characters as we can get (up to 'maxlen') are put in "buf" and +/// As many characters as we can get (up to 'maxlen') are put in "buf" and /// NUL terminated (buffer length must be 'maxlen' + 1). /// Minimum for "maxlen" is 3!!!! /// @@ -2492,7 +2555,7 @@ static int vgetorpeek(bool advance) /// Return the number of obtained characters. /// Return -1 when end of input script reached. /// -/// @param wait_time milli seconds +/// @param wait_time milliseconds int inchar(char_u *buf, int maxlen, long wait_time) { int len = 0; // Init for GCC. @@ -2547,7 +2610,7 @@ int inchar(char_u *buf, int maxlen, long wait_time) // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] // and buf may be pointing inside typebuf.tb_buf[]. if (got_int) { -#define DUM_LEN MAXMAPLEN * 3 + 3 +#define DUM_LEN (MAXMAPLEN * 3 + 3) char_u dum[DUM_LEN + 1]; for (;;) { @@ -2591,7 +2654,7 @@ int fix_input_buffer(char_u *buf, int len) FUNC_ATTR_NONNULL_ALL { if (!using_script()) { - // Should not escape K_SPECIAL/CSI reading input from the user because vim + // Should not escape K_SPECIAL reading input from the user because vim // key codes keys are processed in input.c/input_enqueue. buf[len] = NUL; return len; @@ -2602,9 +2665,8 @@ int fix_input_buffer(char_u *buf, int len) char_u *p = buf; // Two characters are special: NUL and K_SPECIAL. - // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER + // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER // Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER - // Replace CSI by K_SPECIAL KS_EXTRA KE_CSI for (i = len; --i >= 0; ++p) { if (p[0] == NUL || (p[0] == K_SPECIAL @@ -2640,11 +2702,13 @@ int fix_input_buffer(char_u *buf, int len) /// @param[in] orig_lhs Original mapping LHS, with characters to replace. /// @param[in] orig_lhs_len `strlen` of orig_lhs. /// @param[in] orig_rhs Original mapping RHS, with characters to replace. +/// @param[in] rhs_lua Lua reference for Lua maps. /// @param[in] orig_rhs_len `strlen` of orig_rhs. /// @param[in] cpo_flags See param docs for @ref replace_termcodes. /// @param[out] mapargs MapArguments struct holding the replaced strings. void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const char_u *orig_rhs, - const size_t orig_rhs_len, int cpo_flags, MapArguments *mapargs) + const size_t orig_rhs_len, LuaRef rhs_lua, int cpo_flags, + MapArguments *mapargs) { char_u *lhs_buf = NULL; char_u *rhs_buf = NULL; @@ -2660,22 +2724,34 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const true, true, true, cpo_flags); mapargs->lhs_len = STRLEN(replaced); STRLCPY(mapargs->lhs, replaced, sizeof(mapargs->lhs)); + mapargs->rhs_lua = rhs_lua; - mapargs->orig_rhs_len = orig_rhs_len; - mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u)); - STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1); + if (rhs_lua == LUA_NOREF) { + mapargs->orig_rhs_len = orig_rhs_len; + mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u)); + STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1); - if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing - mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char - mapargs->rhs_len = 0; - mapargs->rhs_is_noop = true; + if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing + mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char + mapargs->rhs_len = 0; + mapargs->rhs_is_noop = true; + } else { + replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, + false, true, true, cpo_flags); + mapargs->rhs_len = STRLEN(replaced); + mapargs->rhs_is_noop = false; + mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u)); + STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1); + } } else { - replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, - false, true, true, cpo_flags); - mapargs->rhs_len = STRLEN(replaced); - mapargs->rhs_is_noop = false; - mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u)); - STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1); + char tmp_buf[64]; + // orig_rhs is not used for Lua mappings, but still needs to be a string. + mapargs->orig_rhs = xcalloc(1, sizeof(char_u)); + mapargs->orig_rhs_len = 0; + // stores <lua>ref_no<cr> in map_str + mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL, + (char_u)KS_EXTRA, KE_LUA, rhs_lua); + mapargs->rhs = vim_strsave((char_u *)tmp_buf); } xfree(lhs_buf); @@ -2787,7 +2863,7 @@ int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs) size_t orig_rhs_len = STRLEN(rhs_start); set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len, - rhs_start, orig_rhs_len, + rhs_start, orig_rhs_len, LUA_NOREF, CPO_TO_CPO_FLAGS, &parsed_args); xfree(lhs_to_replace); @@ -2849,7 +2925,7 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T validate_maphash(); bool has_lhs = (args->lhs[0] != NUL); - bool has_rhs = (args->rhs[0] != NUL) || args->rhs_is_noop; + bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop; // check for :unmap without argument if (maptype == 1 && !has_lhs) { @@ -3039,10 +3115,14 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T } else { // new rhs for existing entry mp->m_mode &= ~mode; // remove mode bits if (mp->m_mode == 0 && !did_it) { // reuse entry - xfree(mp->m_str); + XFREE_CLEAR(mp->m_str); + XFREE_CLEAR(mp->m_orig_str); + XFREE_CLEAR(mp->m_desc); + NLUA_CLEAR_REF(mp->m_luaref); + mp->m_str = vim_strsave(rhs); - xfree(mp->m_orig_str); mp->m_orig_str = vim_strsave(orig_rhs); + mp->m_luaref = args->rhs_lua; mp->m_noremap = noremap; mp->m_nowait = args->nowait; mp->m_silent = args->silent; @@ -3050,6 +3130,10 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T mp->m_expr = args->expr; mp->m_script_ctx = current_sctx; mp->m_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&mp->m_script_ctx); + if (args->desc != NULL) { + mp->m_desc = xstrdup(args->desc); + } did_it = true; } } @@ -3118,6 +3202,7 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T mp->m_keys = vim_strsave(lhs); mp->m_str = vim_strsave(rhs); mp->m_orig_str = vim_strsave(orig_rhs); + mp->m_luaref = args->rhs_lua; mp->m_keylen = (int)STRLEN(mp->m_keys); mp->m_noremap = noremap; mp->m_nowait = args->nowait; @@ -3126,6 +3211,11 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T mp->m_expr = args->expr; mp->m_script_ctx = current_sctx; mp->m_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&mp->m_script_ctx); + mp->m_desc = NULL; + if (args->desc != NULL) { + mp->m_desc = xstrdup(args->desc); + } // add the new entry in front of the abbrlist or maphash[] list if (is_abbrev) { @@ -3222,8 +3312,10 @@ static void mapblock_free(mapblock_T **mpp) mp = *mpp; xfree(mp->m_keys); - xfree(mp->m_str); - xfree(mp->m_orig_str); + NLUA_CLEAR_REF(mp->m_luaref); + XFREE_CLEAR(mp->m_str); + XFREE_CLEAR(mp->m_orig_str); + XFREE_CLEAR(mp->m_desc); *mpp = mp->m_next; xfree(mp); } @@ -3414,7 +3506,8 @@ static void showmap(mapblock_T *mp, bool local) { size_t len = 1; - if (message_filtered(mp->m_keys) && message_filtered(mp->m_str)) { + if (message_filtered(mp->m_keys) && message_filtered(mp->m_str) + && (mp->m_desc == NULL || message_filtered((char_u *)mp->m_desc))) { return; } @@ -3459,19 +3552,29 @@ static void showmap(mapblock_T *mp, bool local) /* Use FALSE below if we only want things like <Up> to show up as such on * the rhs, and not M-x etc, TRUE gets both -- webb */ - if (*mp->m_str == NUL) { + if (mp->m_luaref != LUA_NOREF) { + char msg[100]; + snprintf(msg, sizeof(msg), "<Lua function %d>", mp->m_luaref); + msg_puts_attr(msg, HL_ATTR(HLF_8)); + } else if (mp->m_str[0] == NUL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { - // Remove escaping of CSI, because "m_str" is in a format to be used + // Remove escaping of K_SPECIAL, because "m_str" is in a format to be used // as typeahead. char_u *s = vim_strsave(mp->m_str); - vim_unescape_csi(s); + vim_unescape_ks(s); msg_outtrans_special(s, false, 0); xfree(s); } + + if (mp->m_desc != NULL) { + msg_puts("\n "); // Shift line to same level as rhs. + msg_puts(mp->m_desc); + } if (p_verbose > 0) { last_set_msg(mp->m_script_ctx); } + msg_clr_eos(); ui_flush(); // show one line at a time } @@ -3554,8 +3657,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) mp = maphash[hash]; } for (; mp; mp = mp->m_next) { - if ((mp->m_mode & mode) - && strstr((char *)mp->m_str, rhs) != NULL) { + if ((mp->m_mode & mode) && strstr((char *)mp->m_str, rhs) != NULL) { return true; } } @@ -3840,9 +3942,9 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) int match; if (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) { - // Might have CSI escaped mp->m_keys. + // Might have K_SPECIAL escaped mp->m_keys. q = vim_strsave(mp->m_keys); - vim_unescape_csi(q); + vim_unescape_ks(q); qlen = (int)STRLEN(q); } // find entries with right mode and keys @@ -3888,7 +3990,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) int newlen = utf_char2bytes(c, tb + j); tb[j + newlen] = NUL; // Need to escape K_SPECIAL. - char_u *escaped = vim_strsave_escape_csi(tb + j); + char_u *escaped = vim_strsave_escape_ks(tb + j); if (escaped != NULL) { newlen = (int)STRLEN(escaped); memmove(tb + j, escaped, (size_t)newlen); @@ -3901,7 +4003,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) (void)ins_typebuf(tb, 1, 0, true, mp->m_silent); } if (mp->m_expr) { - s = eval_map_expr(mp->m_str, c); + s = eval_map_expr(mp, c); } else { s = mp->m_str; } @@ -3931,22 +4033,21 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) /// special characters. /// /// @param c NUL or typed character for abbreviation -static char_u *eval_map_expr(char_u *str, int c) +static char_u *eval_map_expr(mapblock_T *mp, int c) { char_u *res; - char_u *p; - char_u *expr; - char_u *save_cmd; + char_u *p = NULL; + char_u *expr = NULL; pos_T save_cursor; int save_msg_col; int save_msg_row; - /* Remove escaping of CSI, because "str" is in a format to be used as - * typeahead. */ - expr = vim_strsave(str); - vim_unescape_csi(expr); - - save_cmd = save_cmdline_alloc(); + // Remove escaping of K_SPECIAL, because "str" is in a format to be used as + // typeahead. + if (mp->m_luaref == LUA_NOREF) { + expr = vim_strsave(mp->m_str); + vim_unescape_ks(expr); + } // Forbid changing text or using ":normal" to avoid most of the bad side // effects. Also restore the cursor position. @@ -3956,31 +4057,41 @@ static char_u *eval_map_expr(char_u *str, int c) save_cursor = curwin->w_cursor; save_msg_col = msg_col; save_msg_row = msg_row; - p = eval_to_string(expr, NULL, false); + if (mp->m_luaref != LUA_NOREF) { + Error err = ERROR_INIT; + Array args = ARRAY_DICT_INIT; + Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err); + if (ret.type == kObjectTypeString) { + p = (char_u *)xstrndup(ret.data.string.data, ret.data.string.size); + } + api_free_object(ret); + if (err.type != kErrorTypeNone) { + semsg_multiline("E5108: %s", err.msg); + api_clear_error(&err); + } + } else { + p = eval_to_string(expr, NULL, false); + xfree(expr); + } textlock--; ex_normal_lock--; curwin->w_cursor = save_cursor; msg_col = save_msg_col; msg_row = save_msg_row; - restore_cmdline_alloc(save_cmd); - xfree(expr); - if (p == NULL) { return NULL; } - // Escape CSI in the result to be able to use the string as typeahead. - res = vim_strsave_escape_csi(p); + // Escape K_SPECIAL in the result to be able to use the string as typeahead. + res = vim_strsave_escape_ks(p); xfree(p); return res; } -/* - * Copy "p" to allocated memory, escaping K_SPECIAL and CSI so that the result - * can be put in the typeahead buffer. - */ -char_u *vim_strsave_escape_csi(char_u *p) +/// Copy "p" to allocated memory, escaping K_SPECIAL so that the result +/// can be put in the typeahead buffer. +char_u *vim_strsave_escape_ks(char_u *p) { // Need a buffer to hold up to three times as much. Four in case of an // illegal utf-8 byte: @@ -3995,7 +4106,7 @@ char_u *vim_strsave_escape_csi(char_u *p) *d++ = *s++; } else { // Add character, possibly multi-byte to destination, escaping - // CSI and K_SPECIAL. Be careful, it can be an illegal byte! + // K_SPECIAL. Be careful, it can be an illegal byte! d = add_char2buf(utf_ptr2char(s), d); s += utf_ptr2len(s); } @@ -4005,11 +4116,9 @@ char_u *vim_strsave_escape_csi(char_u *p) return res; } -/* - * Remove escaping from CSI and K_SPECIAL characters. Reverse of - * vim_strsave_escape_csi(). Works in-place. - */ -void vim_unescape_csi(char_u *p) +/// Remove escaping from K_SPECIAL characters. Reverse of +/// vim_strsave_escape_ks(). Works in-place. +void vim_unescape_ks(char_u *p) { char_u *s = p, *d = p; @@ -4017,10 +4126,6 @@ void vim_unescape_csi(char_u *p) if (s[0] == K_SPECIAL && s[1] == KS_SPECIAL && s[2] == KE_FILLER) { *d++ = K_SPECIAL; s += 3; - } else if ((s[0] == K_SPECIAL || s[0] == CSI) - && s[1] == KS_EXTRA && s[2] == (int)KE_CSI) { - *d++ = CSI; - s += 3; } else { *d++ = *s++; } @@ -4071,11 +4176,14 @@ int makemap(FILE *fd, buf_T *buf) continue; } - // skip mappings that contain a <SNR> (script-local thing), + // skip lua mappings and mappings that contain a <SNR> (script-local thing), // they probably don't work when loaded again + if (mp->m_luaref != LUA_NOREF) { + continue; + } for (p = mp->m_str; *p != NUL; p++) { if (p[0] == K_SPECIAL && p[1] == KS_EXTRA - && p[2] == (int)KE_SNR) { + && p[2] == KE_SNR) { break; } } @@ -4260,7 +4368,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what) for (; *str != NUL; str++) { // Check for a multi-byte character, which may contain escaped - // K_SPECIAL and CSI bytes. + // K_SPECIAL bytes. const char *p = mb_unescape((const char **)&str); if (p != NULL) { while (*p != NUL) { @@ -4353,10 +4461,11 @@ int put_escstr(FILE *fd, char_u *strstart, int what) /// @param mp_ptr return: pointer to mapblock or NULL /// @param local_ptr return: buffer-local mapping or NULL char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr, - int *local_ptr) + int *local_ptr, int *rhs_lua) { int len, minlen; mapblock_T *mp; + *rhs_lua = LUA_NOREF; validate_maphash(); @@ -4397,7 +4506,8 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb if (local_ptr != NULL) { *local_ptr = local; } - return mp->m_str; + *rhs_lua = mp->m_luaref; + return mp->m_luaref == LUA_NOREF ? mp->m_str : NULL; } } } @@ -4582,3 +4692,47 @@ char_u *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) return (char_u *)line_ga.ga_data; } + +bool map_execute_lua(void) +{ + garray_T line_ga; + int c1 = -1; + bool aborted = false; + + ga_init(&line_ga, 1, 32); + + no_mapping++; + + got_int = false; + while (c1 != NUL && !aborted) { + ga_grow(&line_ga, 32); + // Get one character at a time. + c1 = vgetorpeek(true); + if (got_int) { + aborted = true; + } else if (c1 == '\r' || c1 == '\n') { + c1 = NUL; // end the line + } else { + ga_append(&line_ga, (char)c1); + } + } + + no_mapping--; + + if (aborted) { + ga_clear(&line_ga); + return false; + } + + LuaRef ref = (LuaRef)atoi(line_ga.ga_data); + Error err = ERROR_INIT; + Array args = ARRAY_DICT_INIT; + nlua_call_ref(ref, NULL, args, false, &err); + if (err.type != kErrorTypeNone) { + semsg_multiline("E5108: %s", err.msg); + api_clear_error(&err); + } + + ga_clear(&line_ga); + return true; +} diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 5950611d3f..9b8605f1df 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -50,17 +50,19 @@ struct map_arguments { char_u *rhs; /// The {rhs} of the mapping. size_t rhs_len; + LuaRef rhs_lua; /// lua function as rhs bool rhs_is_noop; /// True when the {orig_rhs} is <nop>. char_u *orig_rhs; /// The original text of the {rhs}. size_t orig_rhs_len; + char *desc; /// map description }; typedef struct map_arguments MapArguments; #define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \ - { 0 }, 0, NULL, 0, false, NULL, 0 } + { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL } -#define KEYLEN_PART_KEY -1 // keylen value for incomplete key-code -#define KEYLEN_PART_MAP -2 // keylen value for incomplete mapping +#define KEYLEN_PART_KEY (-1) // keylen value for incomplete key-code +#define KEYLEN_PART_MAP (-2) // keylen value for incomplete mapping /// Maximum number of streams to read script from enum { NSCRIPT = 15, }; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 9c065c09f4..2b85b6a208 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -4,6 +4,7 @@ #include <inttypes.h> #include <stdbool.h> +#include "nvim/ascii.h" #include "nvim/event/loop.h" #include "nvim/ex_eval.h" #include "nvim/iconv.h" @@ -27,7 +28,7 @@ #endif #ifndef FILETYPE_FILE -# define FILETYPE_FILE "filetype.vim" +# define FILETYPE_FILE "filetype.lua filetype.vim" #endif #ifndef FTPLUGIN_FILE @@ -126,7 +127,7 @@ typedef off_t off_T; // When vgetc() is called, it sets mod_mask to the set of modifiers that are // held down based on the MOD_MASK_* symbols that are read first. -EXTERN int mod_mask INIT(= 0x0); // current key modifiers +EXTERN int mod_mask INIT(= 0); // current key modifiers // Cmdline_row is the row where the command line starts, just below the @@ -201,7 +202,6 @@ EXTERN bool msg_did_scroll INIT(= false); EXTERN char_u *keep_msg INIT(= NULL); // msg to be shown after redraw EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg -EXTERN bool keep_msg_more INIT(= false); // keep_msg was set by msgmore() EXTERN bool need_fileinfo INIT(= false); // do fileinfo() after redraw EXTERN int msg_scroll INIT(= false); // msg_start() will scroll EXTERN bool msg_didout INIT(= false); // msg_outstr() was used in line @@ -326,22 +326,25 @@ EXTERN int want_garbage_collect INIT(= false); EXTERN int garbage_collect_at_exit INIT(= false); // Special values for current_SID. -#define SID_MODELINE -1 // when using a modeline -#define SID_CMDARG -2 // for "--cmd" argument -#define SID_CARG -3 // for "-c" argument -#define SID_ENV -4 // for sourcing environment variable -#define SID_ERROR -5 // option was reset because of an error -#define SID_NONE -6 // don't set scriptID -#define SID_WINLAYOUT -7 // changing window size -#define SID_LUA -8 // for Lua scripts/chunks -#define SID_API_CLIENT -9 // for API clients -#define SID_STR -10 // for sourcing a string with no script item +#define SID_MODELINE (-1) // when using a modeline +#define SID_CMDARG (-2) // for "--cmd" argument +#define SID_CARG (-3) // for "-c" argument +#define SID_ENV (-4) // for sourcing environment variable +#define SID_ERROR (-5) // option was reset because of an error +#define SID_NONE (-6) // don't set scriptID +#define SID_WINLAYOUT (-7) // changing window size +#define SID_LUA (-8) // for Lua scripts/chunks +#define SID_API_CLIENT (-9) // for API clients +#define SID_STR (-10) // for sourcing a string with no script item // Script CTX being sourced or was sourced to define the current function. EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); // ID of the current channel making a client API call EXTERN uint64_t current_channel_id INIT(= 0); +// ID of the client channel. Used by ui client +EXTERN uint64_t ui_client_channel_id INIT(= 0); + EXTERN bool did_source_packages INIT(= false); // Scope information for the code that indirectly triggered the current @@ -358,6 +361,11 @@ EXTERN int provider_call_nesting INIT(= 0); EXTERN int t_colors INIT(= 256); // int value of T_CCO +// Flags to indicate an additional string for highlight name completion. +EXTERN int include_none INIT(= 0); // when 1 include "None" +EXTERN int include_default INIT(= 0); // when 1 include "default" +EXTERN int include_link INIT(= 0); // when 2 include "link" and "clear" + // When highlight_match is true, highlight a match, starting at the cursor // 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 @@ -422,7 +430,7 @@ EXTERN win_T *lastwin; // last window EXTERN win_T *prevwin INIT(= NULL); // previous window #define ONE_WINDOW (firstwin == lastwin) #define FOR_ALL_FRAMES(frp, first_frame) \ - for (frp = first_frame; frp != NULL; frp = frp->fr_next) // NOLINT + for ((frp) = first_frame; (frp) != NULL; (frp) = (frp)->fr_next) // NOLINT // When using this macro "break" only breaks out of the inner loop. Use "goto" // to break out of the tabpage loop. @@ -445,14 +453,15 @@ EXTERN int aucmd_win_used INIT(= false); // aucmd_win is being used EXTERN frame_T *topframe; // top of the window frame tree // Tab pages are alternative topframes. "first_tabpage" points to the first -// one in the list, "curtab" is the current one. +// one in the list, "curtab" is the current one. "lastused_tabpage" is the +// last used one. EXTERN tabpage_T *first_tabpage; -EXTERN tabpage_T *lastused_tabpage; EXTERN tabpage_T *curtab; +EXTERN tabpage_T *lastused_tabpage; EXTERN bool redraw_tabline INIT(= false); // need to redraw tabline // Iterates over all tabs in the tab list -#define FOR_ALL_TABS(tp) for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) +#define FOR_ALL_TABS(tp) for (tabpage_T *(tp) = first_tabpage; (tp) != NULL; (tp) = (tp)->tp_next) // All buffers are linked in a list. 'firstbuf' points to the first entry, // 'lastbuf' to the last entry and 'curbuf' to the currently active buffer. @@ -468,7 +477,7 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer // Iterate through all the signs placed in a buffer #define FOR_ALL_SIGNS_IN_BUF(buf, sign) \ - for (sign = buf->b_signlist; sign != NULL; sign = sign->se_next) // NOLINT + for ((sign) = (buf)->b_signlist; (sign) != NULL; (sign) = (sign)->se_next) // NOLINT // List of files being edited (global argument list). curwin->w_alist points @@ -524,6 +533,8 @@ EXTERN pos_T VIsual; EXTERN int VIsual_active INIT(= false); /// Whether Select mode is active. EXTERN int VIsual_select INIT(= false); +/// Register name for Select mode +EXTERN int VIsual_select_reg INIT(= 0); /// Restart Select mode when next cmd finished EXTERN int restart_VIsual_select INIT(= 0); /// Whether to restart the selection after a Select-mode mapping or menu. @@ -533,6 +544,11 @@ EXTERN int VIsual_mode INIT(= 'v'); /// true when redoing Visual. EXTERN int redo_VIsual_busy INIT(= false); +// The Visual area is remembered for reselection. +EXTERN int resel_VIsual_mode INIT(= NUL); // 'v', 'V', or Ctrl-V +EXTERN linenr_T resel_VIsual_line_count; // number of lines +EXTERN colnr_T resel_VIsual_vcol; // nr of cols or end col + /// When pasting text with the middle mouse button in visual mode with /// restart_edit set, remember where it started so we can set Insstart. EXTERN pos_T where_paste_started; @@ -602,7 +618,7 @@ EXTERN int inhibit_delete_count INIT(= 0); #define DBCS_CHT 950 // taiwan #define DBCS_CHTU 9950 // euc-tw #define DBCS_2BYTE 1 // 2byte- -#define DBCS_DEBUG -1 +#define DBCS_DEBUG (-1) /// Encoding used when 'fencs' is set to "default" EXTERN char_u *fenc_default INIT(= NULL); @@ -627,6 +643,9 @@ EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p. EXTERN int reg_recording INIT(= 0); // register for recording or zero EXTERN int reg_executing INIT(= 0); // register being executed or zero +// Flag set when peeking a character and found the end of executed register +EXTERN bool pending_end_reg_executing INIT(= false); +EXTERN int reg_recorded INIT(= 0); // last recorded register or zero EXTERN int no_mapping INIT(= false); // currently no mapping allowed EXTERN int no_zero_mapping INIT(= 0); // mapping zero not allowed @@ -661,11 +680,8 @@ EXTERN bool cmd_silent INIT(= false); // don't echo the command line #define SEA_QUIT 2 // quit editing the file #define SEA_RECOVER 3 // recover the file -EXTERN int swap_exists_action INIT(= SEA_NONE); -// For dialog when swap file already -// exists. -EXTERN bool swap_exists_did_quit INIT(= false); -// Selected "quit" at the dialog. +EXTERN int swap_exists_action INIT(= SEA_NONE); ///< For dialog when swap file already exists. +EXTERN bool swap_exists_did_quit INIT(= false); ///< Selected "quit" at the dialog. EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc. EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names @@ -718,15 +734,16 @@ EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL); // Set by vim_regexec() to store \z\(...\) matches EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL); -EXTERN bool did_outofmem_msg INIT(= false); -// set after out of memory msg -EXTERN bool did_swapwrite_msg INIT(= false); -// set after swap write error msg -EXTERN int global_busy INIT(= 0); // set when :global is executing -EXTERN bool listcmd_busy INIT(= false); // set when :argdo, :windo or - // :bufdo is executing -EXTERN bool need_start_insertmode INIT(= false); -// start insert mode soon +EXTERN bool did_outofmem_msg INIT(= false); ///< set after out of memory msg +EXTERN bool did_swapwrite_msg INIT(= false); ///< set after swap write error msg +EXTERN int global_busy INIT(= 0); ///< set when :global is executing +EXTERN bool listcmd_busy INIT(= false); ///< set when :argdo, :windo or :bufdo is executing +EXTERN bool need_start_insertmode INIT(= false); ///< start insert mode soon + +#define MODE_MAX_LENGTH 4 // max mode length returned in get_mode() + // including the final NUL character + +EXTERN char last_mode[MODE_MAX_LENGTH] INIT(= "n"); EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) EXTERN char_u *repeat_cmdline INIT(= NULL); // command line for "." EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline @@ -746,8 +763,7 @@ EXTERN bool g_tag_at_cursor INIT(= false); // whether the tag command comes EXTERN int replace_offset INIT(= 0); // offset for replace_push() -EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); -// need backslash in cmd line +EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); // need backslash in cmd line EXTERN int keep_help_flag INIT(= false); // doing :ta from help file @@ -789,6 +805,8 @@ extern char_u *compiled_sys; // directory is not a local directory, globaldir is NULL. EXTERN char_u *globaldir INIT(= NULL); +EXTERN char *last_chdir_reason INIT(= NULL); + // Whether 'keymodel' contains "stopsel" and "startsel". EXTERN bool km_stopsel INIT(= false); EXTERN bool km_startsel INIT(= false); @@ -968,6 +986,7 @@ EXTERN char e_invalidreg[] INIT(= N_("E850: Invalid register name")); EXTERN char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\"")); EXTERN char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior")); EXTERN char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window")); +EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List")); EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported")); EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String")); @@ -988,6 +1007,12 @@ EXTERN char e_non_empty_string_required[] INIT(= N_("E1142: Non-empty string req EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events")); +EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long")); + +EXTERN char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range")); + +EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long")); + EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP")); diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index 32b471efb0..575b239f5a 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -22,6 +22,7 @@ #include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/hardcopy.h" +#include "nvim/highlight_group.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -386,30 +387,46 @@ static uint32_t prt_get_term_color(int colorindex) return cterm_color_8[colorindex % 8]; } -static void prt_get_attr(int hl_id, prt_text_attr_T *pattr, int modec) +static uint32_t prt_get_color(int hl_id, int modec) { int colorindex; uint32_t fg_color; + const char *color = highlight_color(hl_id, "fg#", 'g'); + if (color != NULL) { + RgbValue rgb = name_to_color(color); + if (rgb != -1) { + return (uint32_t)rgb; + } + } + + color = highlight_color(hl_id, "fg", modec); + if (color == NULL) { + colorindex = 0; + } else { + colorindex = atoi(color); + } + + if (colorindex >= 0 && colorindex < t_colors) { + fg_color = prt_get_term_color(colorindex); + } else { + fg_color = PRCOLOR_BLACK; + } + + return fg_color; +} + +static void prt_get_attr(int hl_id, prt_text_attr_T *pattr, int modec) +{ pattr->bold = (highlight_has_attr(hl_id, HL_BOLD, modec) != NULL); pattr->italic = (highlight_has_attr(hl_id, HL_ITALIC, modec) != NULL); pattr->underline = (highlight_has_attr(hl_id, HL_UNDERLINE, modec) != NULL); + pattr->underlineline = (highlight_has_attr(hl_id, HL_UNDERLINELINE, modec) != NULL); pattr->undercurl = (highlight_has_attr(hl_id, HL_UNDERCURL, modec) != NULL); + pattr->underdot = (highlight_has_attr(hl_id, HL_UNDERDOT, modec) != NULL); + pattr->underdash = (highlight_has_attr(hl_id, HL_UNDERDASH, modec) != NULL); - { - const char *color = highlight_color(hl_id, "fg", modec); - if (color == NULL) { - colorindex = 0; - } else { - colorindex = atoi(color); - } - - if (colorindex >= 0 && colorindex < t_colors) { - fg_color = prt_get_term_color(colorindex); - } else { - fg_color = PRCOLOR_BLACK; - } - } + uint32_t fg_color = prt_get_color(hl_id, modec); if (fg_color == PRCOLOR_WHITE) { fg_color = PRCOLOR_BLACK; @@ -986,8 +1003,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T #define PRT_PS_DEFAULT_DPI (72) // Default user space resolution #define PRT_PS_DEFAULT_FONTSIZE (10) -#define PRT_MEDIASIZE_LEN (sizeof(prt_mediasize) / \ - sizeof(struct prt_mediasize_S)) +#define PRT_MEDIASIZE_LEN ARRAY_SIZE(prt_mediasize) static struct prt_mediasize_S prt_mediasize[] = { @@ -1254,7 +1270,7 @@ static struct prt_dsc_comment_S prt_dsc_table[] = * Variables for the output PostScript file. */ static FILE *prt_ps_fd; -static int prt_file_error; +static bool prt_file_error; static char_u *prt_ps_file_name = NULL; /* @@ -1330,7 +1346,7 @@ static void prt_write_file_raw_len(char_u *buffer, size_t bytes) if (!prt_file_error && fwrite(buffer, sizeof(char_u), bytes, prt_ps_fd) != bytes) { emsg(_("E455: Error writing to PostScript output file")); - prt_file_error = TRUE; + prt_file_error = true; } } @@ -1982,7 +1998,7 @@ void mch_print_cleanup(void) if (prt_ps_fd != NULL) { fclose(prt_ps_fd); prt_ps_fd = NULL; - prt_file_error = FALSE; + prt_file_error = false; } if (prt_ps_file_name != NULL) { XFREE_CLEAR(prt_ps_file_name); @@ -2204,7 +2220,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) // Check encoding and character set are compatible if ((p_mbenc->needs_charset & p_mbchar->has_charset) == 0) { emsg(_("E673: Incompatible multi-byte encoding and character set.")); - return FALSE; + return false; } // Add charset name if not empty @@ -2216,7 +2232,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) // Add custom CMap character set name if (*p_pmcs == NUL) { emsg(_("E674: printmbcharset cannot be empty with multi-byte encoding.")); - return FALSE; + return false; } STRLCPY(prt_cmap, p_pmcs, sizeof(prt_cmap) - 2); STRCAT(prt_cmap, "-"); @@ -2232,7 +2248,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) if (!mbfont_opts[OPT_MBFONT_REGULAR].present) { emsg(_("E675: No default font specified for multi-byte printing.")); - return FALSE; + return false; } // Derive CID font names with fallbacks if not defined @@ -2426,12 +2442,12 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) prt_need_bgcol = false; prt_need_underline = false; - prt_file_error = FALSE; + prt_file_error = false; return OK; } -static int prt_add_resource(struct prt_ps_resource_S *resource) +static bool prt_add_resource(struct prt_ps_resource_S *resource) { FILE *fd_resource; char_u resource_buffer[512]; @@ -2440,7 +2456,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) fd_resource = os_fopen((char *)resource->filename, READBIN); if (fd_resource == NULL) { semsg(_("E456: Can't open file \"%s\""), resource->filename); - return FALSE; + return false; } switch (resource->type) { case PRT_RESOURCE_TYPE_PROCSET: @@ -2450,7 +2466,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) (char *)resource->title); break; default: - return FALSE; + return false; } prt_dsc_textline("BeginDocument", (char *)resource->filename); @@ -2462,7 +2478,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) semsg(_("E457: Can't read PostScript resource file \"%s\""), resource->filename); fclose(fd_resource); - return FALSE; + return false; } if (bytes_read == 0) { break; @@ -2470,7 +2486,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) prt_write_file_raw_len(resource_buffer, bytes_read); if (prt_file_error) { fclose(fd_resource); - return FALSE; + return false; } } fclose(fd_resource); @@ -2479,10 +2495,10 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) prt_dsc_noarg("EndResource"); - return TRUE; + return true; } -int mch_print_begin(prt_settings_T *psettings) +bool mch_print_begin(prt_settings_T *psettings) { int bbox[4]; double left; @@ -2496,7 +2512,6 @@ int mch_print_begin(prt_settings_T *psettings) char_u *p; struct prt_ps_resource_S res_cidfont; struct prt_ps_resource_S res_cmap; - int retval = FALSE; /* * PS DSC Header comments - no PS code! @@ -2568,25 +2583,25 @@ int mch_print_begin(prt_settings_T *psettings) // Search for external resources VIM supplies if (!prt_find_resource("prolog", &res_prolog)) { emsg(_("E456: Can't find PostScript resource file \"prolog.ps\"")); - return FALSE; + return false; } if (!prt_open_resource(&res_prolog)) { - return FALSE; + return false; } if (!prt_check_resource(&res_prolog, PRT_PROLOG_VERSION)) { - return FALSE; + return false; } if (prt_out_mbyte) { // Look for required version of multi-byte printing procset if (!prt_find_resource("cidfont", &res_cidfont)) { emsg(_("E456: Can't find PostScript resource file \"cidfont.ps\"")); - return FALSE; + return false; } if (!prt_open_resource(&res_cidfont)) { - return FALSE; + return false; } if (!prt_check_resource(&res_cidfont, PRT_CID_PROLOG_VERSION)) { - return FALSE; + return false; } } @@ -2611,12 +2626,12 @@ int mch_print_begin(prt_settings_T *psettings) if (!prt_find_resource(p_encoding, &res_encoding)) { semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), p_encoding); - return FALSE; + return false; } } } if (!prt_open_resource(&res_encoding)) { - return FALSE; + return false; } // For the moment there are no checks on encoding resource files to // perform @@ -2630,10 +2645,10 @@ int mch_print_begin(prt_settings_T *psettings) if (!prt_find_resource(prt_ascii_encoding, &res_encoding)) { semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), prt_ascii_encoding); - return FALSE; + return false; } if (!prt_open_resource(&res_encoding)) { - return FALSE; + return false; } // For the moment there are no checks on encoding resource files to // perform @@ -2656,10 +2671,10 @@ int mch_print_begin(prt_settings_T *psettings) if (!prt_find_resource(prt_cmap, &res_cmap)) { semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), prt_cmap); - return FALSE; + return false; } if (!prt_open_resource(&res_cmap)) { - return FALSE; + return false; } } @@ -2737,7 +2752,7 @@ int mch_print_begin(prt_settings_T *psettings) // There will be only one Roman font encoding to be included in the PS // file. if (!prt_add_resource(&res_encoding)) { - return FALSE; + return false; } } @@ -2847,9 +2862,7 @@ int mch_print_begin(prt_settings_T *psettings) prt_dsc_noarg("EndSetup"); // Fail if any problems writing out to the PS file - retval = !prt_file_error; - - return retval; + return !prt_file_error; } void mch_print_end(prt_settings_T *psettings) diff --git a/src/nvim/hardcopy.h b/src/nvim/hardcopy.h index eba769d342..16119c5d2d 100644 --- a/src/nvim/hardcopy.h +++ b/src/nvim/hardcopy.h @@ -18,6 +18,9 @@ typedef struct { TriState italic; TriState underline; int undercurl; + int underlineline; + int underdot; + int underdash; } prt_text_attr_T; /* diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 3050ca02de..7d91f38d56 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -5,15 +5,16 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/decoration_provider.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/map.h" #include "nvim/message.h" #include "nvim/option.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -144,13 +145,19 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) } } -void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) +void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict) { - DecorProvider *p = get_decor_provider(ns_id, true); if ((attrs.rgb_ae_attr & HL_DEFAULT) && map_has(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id))) { return; } + if (ns_id == 0) { + assert(dict); + // set in global (':highlight') namespace + set_hl_group(hl_id, attrs, dict, link_id); + return; + } + DecorProvider *p = get_decor_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, @@ -192,23 +199,17 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) 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; + Dict(highlight) dict = { 0 }; + if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field, + ret.data.dictionary, &err)) { + attrs = dict2hlattrs(&dict, true, &it.link_id, &err); + fallback = api_object_to_bool(dict.fallback, "fallback", true, &err); + tmp = api_object_to_bool(dict.fallback, "tmp", false, &err); + if (it.link_id >= 0) { + fallback = true; } } - if (it.link_id >= 0) { - fallback = true; - } } it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs); @@ -327,7 +328,7 @@ void update_window_hl(win_T *wp, bool invalid) wp->w_hl_attr_normal); } - for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { + for (int hlf = 0; hlf < HLF_COUNT; hlf++) { int attr; if (wp->w_hl_ids[hlf] != 0) { attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false); @@ -558,7 +559,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) cattrs = battrs; cattrs.rgb_fg_color = rgb_blend(ratio, battrs.rgb_fg_color, fattrs.rgb_bg_color); - if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { + if (cattrs.rgb_ae_attr & (HL_ANY_UNDERLINE)) { cattrs.rgb_sp_color = rgb_blend(ratio, battrs.rgb_sp_color, fattrs.rgb_bg_color); } else { @@ -576,7 +577,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) } cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color, fattrs.rgb_fg_color); - if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { + if (cattrs.rgb_ae_attr & (HL_ANY_UNDERLINE)) { cattrs.rgb_sp_color = rgb_blend(ratio/2, battrs.rgb_bg_color, fattrs.rgb_sp_color); } else { @@ -743,10 +744,23 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) PUT(hl, "underline", BOOLEAN_OBJ(true)); } + if (mask & HL_UNDERLINELINE) { + PUT(hl, "underlineline", BOOLEAN_OBJ(true)); + } + if (mask & HL_UNDERCURL) { PUT(hl, "undercurl", BOOLEAN_OBJ(true)); } + if (mask & HL_UNDERDOT) { + PUT(hl, "underdot", BOOLEAN_OBJ(true)); + } + + if (mask & HL_UNDERDASH) { + PUT(hl, "underdash", BOOLEAN_OBJ(true)); + } + + if (mask & HL_ITALIC) { PUT(hl, "italic", BOOLEAN_OBJ(true)); } @@ -796,112 +810,123 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) return hl; } -HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) +HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err) { HlAttrs hlattrs = HLATTRS_INIT; - int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1; + int blend = -1; int16_t mask = 0; int16_t cterm_mask = 0; bool cterm_mask_provided = false; - 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 }, - { "default", HL_DEFAULT }, - { "global", HL_GLOBAL }, - { 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; - } +#define CHECK_FLAG(d, m, name, extra, flag) \ + if (api_object_to_bool(d->name##extra, #name, false, err)) { \ + m = m | flag; \ + } + + CHECK_FLAG(dict, mask, bold, , HL_BOLD); + CHECK_FLAG(dict, mask, standout, , HL_STANDOUT); + CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE); + CHECK_FLAG(dict, mask, underlineline, , HL_UNDERLINELINE); + CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL); + CHECK_FLAG(dict, mask, underdot, , HL_UNDERDOT); + CHECK_FLAG(dict, mask, underdash, , HL_UNDERDASH); + CHECK_FLAG(dict, mask, italic, , HL_ITALIC); + CHECK_FLAG(dict, mask, reverse, , HL_INVERSE); + CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH); + CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE); + CHECK_FLAG(dict, mask, default, _, HL_DEFAULT); + CHECK_FLAG(dict, mask, global, , HL_GLOBAL); + + if (HAS_KEY(dict->fg)) { + fg = object_to_color(dict->fg, "fg", true, err); + } else if (HAS_KEY(dict->foreground)) { + fg = object_to_color(dict->foreground, "foreground", true, err); + } + if (ERROR_SET(err)) { + return hlattrs; + } + + if (HAS_KEY(dict->bg)) { + bg = object_to_color(dict->bg, "bg", true, err); + } else if (HAS_KEY(dict->background)) { + bg = object_to_color(dict->background, "background", true, err); + } + if (ERROR_SET(err)) { + return hlattrs; + } + + if (HAS_KEY(dict->sp)) { + sp = object_to_color(dict->sp, "sp", true, err); + } else if (HAS_KEY(dict->special)) { + sp = object_to_color(dict->special, "special", true, err); + } + if (ERROR_SET(err)) { + return hlattrs; + } + + if (dict->blend.type == kObjectTypeInteger) { + Integer blend0 = dict->blend.data.integer; + if (blend0 < 0 || blend0 > 100) { + api_set_error(err, kErrorTypeValidation, "'blend' is not between 0 to 100"); + } else { + blend = (int)blend0; } + } else if (HAS_KEY(dict->blend)) { + api_set_error(err, kErrorTypeValidation, "'blend' must be an integer"); + } + if (ERROR_SET(err)) { + return hlattrs; + } - // Handle cterm attrs - if (strequal(key, "cterm") && val.type == kObjectTypeDictionary) { - cterm_mask_provided = true; - Dictionary cterm_dict = val.data.dictionary; - for (size_t l = 0; l < cterm_dict.size; l++) { - char *cterm_dict_key = cterm_dict.items[l].key.data; - Object cterm_dict_val = cterm_dict.items[l].value; - for (int m = 0; flags[m].name; m++) { - if (strequal(flags[m].name, cterm_dict_key)) { - if (api_object_to_bool(cterm_dict_val, cterm_dict_key, false, - err)) { - cterm_mask |= flags[m].flag; - } - break; - } - } + if (HAS_KEY(dict->link)) { + if (link_id) { + *link_id = object_to_hl_id(dict->link, "link", err); + if (ERROR_SET(err)) { + return hlattrs; } + } else { + api_set_error(err, kErrorTypeValidation, "Invalid Key: 'link'"); } + } - struct { - const char *name; - const char *shortname; - int *dest; - } colors[] = { - { "foreground", "fg", &fg }, - { "background", "bg", &bg }, - { "ctermfg", NULL, &ctermfg }, - { "ctermbg", NULL, &ctermbg }, - { "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; - } + // Handle cterm attrs + if (dict->cterm.type == kObjectTypeDictionary) { + Dict(highlight_cterm) cterm[1] = { 0 }; + if (!api_dict_to_keydict(cterm, KeyDict_highlight_cterm_get_field, + dict->cterm.data.dictionary, err)) { + return hlattrs; } - 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(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"); - } + cterm_mask_provided = true; + CHECK_FLAG(cterm, cterm_mask, bold, , HL_BOLD); + CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT); + CHECK_FLAG(cterm, cterm_mask, underline, , HL_UNDERLINE); + CHECK_FLAG(cterm, cterm_mask, undercurl, , HL_UNDERCURL); + CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC); + CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE); + CHECK_FLAG(cterm, cterm_mask, strikethrough, , HL_STRIKETHROUGH); + CHECK_FLAG(cterm, cterm_mask, nocombine, , HL_NOCOMBINE); + } else if (dict->cterm.type == kObjectTypeArray && dict->cterm.data.array.size == 0) { + // empty list from Lua API should clear all cterm attributes + // TODO(clason): handle via gen_api_dispatch + cterm_mask_provided = true; + } else if (HAS_KEY(dict->cterm)) { + api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary."); + } +#undef CHECK_FLAG + + if (HAS_KEY(dict->ctermfg)) { + ctermfg = object_to_color(dict->ctermfg, "ctermfg", false, err); + if (ERROR_SET(err)) { + return hlattrs; } + } + if (HAS_KEY(dict->ctermbg)) { + ctermbg = object_to_color(dict->ctermbg, "ctermbg", false, err); if (ERROR_SET(err)) { - return hlattrs; // error set, caller should not use retval + return hlattrs; } } @@ -914,19 +939,45 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) hlattrs.rgb_bg_color = bg; hlattrs.rgb_fg_color = fg; hlattrs.rgb_sp_color = sp; - hlattrs.cterm_bg_color = - ctermbg == -1 ? cterm_normal_bg_color : ctermbg + 1; - hlattrs.cterm_fg_color = - ctermfg == -1 ? cterm_normal_fg_color : ctermfg + 1; + hlattrs.hl_blend = blend; + hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : ctermbg + 1; + hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1; hlattrs.cterm_ae_attr = cterm_mask; } else { hlattrs.cterm_ae_attr = cterm_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; + hlattrs.cterm_bg_color = bg == -1 ? 0 : bg + 1; + hlattrs.cterm_fg_color = fg == -1 ? 0 : fg + 1; } return hlattrs; } + +int object_to_color(Object val, char *key, bool rgb, Error *err) +{ + if (val.type == kObjectTypeInteger) { + return (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 || STRICMP(str.data, "NONE") == 0) { + return -1; + } + int color; + if (rgb) { + color = name_to_color(str.data); + } else { + color = name_to_ctermcolor(str.data); + } + if (color < 0) { + api_set_error(err, kErrorTypeValidation, "'%s' is not a valid color", str.data); + } + return color; + } else { + api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key); + return 0; + } +} + 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 d4d53c4126..e0ee649013 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -24,6 +24,10 @@ typedef enum { HL_FG_INDEXED = 0x0200, HL_DEFAULT = 0x0400, HL_GLOBAL = 0x0800, + HL_UNDERLINELINE = 0x1000, + HL_UNDERDOT = 0x2000, + HL_UNDERDASH = 0x4000, + HL_ANY_UNDERLINE = HL_UNDERLINE | HL_UNDERLINELINE | HL_UNDERCURL | HL_UNDERDOT | HL_UNDERDASH, } HlAttrFlags; /// Stores a complete highlighting entry, including colors and attributes @@ -65,10 +69,13 @@ typedef enum { HLF_LNA, // LineNrAbove HLF_LNB, // LineNrBelow HLF_CLN, // current line number when 'cursorline' is set + HLF_CLS, // current line sign column + HLF_CLF, // current line fold HLF_R, // return to continue message and yes/no questions HLF_S, // status lines HLF_SNC, // status lines of not-current windows - HLF_C, // column to separate vertically split windows + HLF_C, // window split separators + HLF_VSP, // VertSplit HLF_T, // Titles for output from ":set all", ":autocmd" etc. HLF_V, // Visual mode HLF_VNC, // Visual mode, autoselecting and not clipboard owner @@ -122,13 +129,16 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_LNA] = "LineNrAbove", [HLF_LNB] = "LineNrBelow", [HLF_CLN] = "CursorLineNr", + [HLF_CLS] = "CursorLineSign", + [HLF_CLF] = "CursorLineFold", [HLF_R] = "Question", [HLF_S] = "StatusLine", [HLF_SNC] = "StatusLineNC", - [HLF_C] = "VertSplit", + [HLF_C] = "WinSeparator", [HLF_T] = "Title", [HLF_V] = "Visual", [HLF_VNC] = "VisualNC", + [HLF_VSP] = "VertSplit", [HLF_W] = "WarningMsg", [HLF_WM] = "WildMenu", [HLF_FL] = "Folded", diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c new file mode 100644 index 0000000000..d448f3a646 --- /dev/null +++ b/src/nvim/highlight_group.c @@ -0,0 +1,2799 @@ +// 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 + +// highlight_group.c: code for managing highlight groups + +#include <stdbool.h> +#include "nvim/autocmd.h" +#include "nvim/api/private/helpers.h" +#include "nvim/charset.h" +#include "nvim/cursor_shape.h" +#include "nvim/fold.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/lua/executor.h" +#include "nvim/match.h" +#include "nvim/option.h" +#include "nvim/runtime.h" +#include "nvim/screen.h" + +/// \addtogroup SG_SET +/// @{ +#define SG_CTERM 2 // cterm has been set +#define SG_GUI 4 // gui has been set +#define SG_LINK 8 // link has been set +/// @} + +#define MAX_SYN_NAME 200 + +// builtin |highlight-groups| +static garray_T highlight_ga = GA_EMPTY_INIT_VALUE; + +Map(cstr_t, int) highlight_unames = MAP_INIT; + +/// The "term", "cterm" and "gui" arguments can be any combination of the +/// following names, separated by commas (but no spaces!). +static char *(hl_name_table[]) = + { "bold", "standout", "underline", "underlineline", "undercurl", "underdot", + "underdash", "italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" }; +static int hl_attr_table[] = + { HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERLINELINE, HL_UNDERCURL, HL_UNDERDOT, HL_UNDERDASH, + HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 }; + +/// Structure that stores information about a highlight group. +/// The ID of a highlight group is also called group ID. It is the index in +/// the highlight_ga array PLUS ONE. +typedef struct { + char_u *sg_name; ///< highlight group name + char *sg_name_u; ///< uppercase of sg_name + bool sg_cleared; ///< "hi clear" was used + int sg_attr; ///< Screen attr @see ATTR_ENTRY + int sg_link; ///< link to this highlight group ID + int sg_deflink; ///< default link; restored in highlight_clear() + int sg_set; ///< combination of flags in \ref SG_SET + sctx_T sg_deflink_sctx; ///< script where the default link was set + sctx_T sg_script_ctx; ///< script in which the group was last set + // for terminal UIs + int sg_cterm; ///< "cterm=" highlighting attr + ///< (combination of \ref HlAttrFlags) + int sg_cterm_fg; ///< terminal fg color number + 1 + int sg_cterm_bg; ///< terminal bg color number + 1 + bool sg_cterm_bold; ///< bold attr was set for light color + // for RGB UIs + int sg_gui; ///< "gui=" highlighting attributes + ///< (combination of \ref HlAttrFlags) + RgbValue sg_rgb_fg; ///< RGB foreground color + RgbValue sg_rgb_bg; ///< RGB background color + RgbValue sg_rgb_sp; ///< RGB special color + 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 +} HlGroup; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "highlight_group.c.generated.h" +#endif + +static inline HlGroup *HL_TABLE(void) +{ + return ((HlGroup *)((highlight_ga.ga_data))); +} + +// The default highlight groups. These are compiled-in for fast startup and +// they still work when the runtime files can't be found. +// +// When making changes here, also change runtime/colors/default.vim! + +static const char *highlight_init_both[] = { + "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", + "Cursor guibg=fg guifg=bg", + "lCursor guibg=fg guifg=bg", + "DiffText cterm=bold ctermbg=Red gui=bold guibg=Red", + "ErrorMsg ctermbg=DarkRed ctermfg=White guibg=Red guifg=White", + "IncSearch cterm=reverse gui=reverse", + "ModeMsg cterm=bold gui=bold", + "NonText ctermfg=Blue gui=bold guifg=Blue", + "Normal cterm=NONE gui=NONE", + "PmenuSbar ctermbg=Grey guibg=Grey", + "StatusLine cterm=reverse,bold gui=reverse,bold", + "StatusLineNC cterm=reverse gui=reverse", + "TabLineFill cterm=reverse gui=reverse", + "TabLineSel cterm=bold gui=bold", + "TermCursor cterm=reverse gui=reverse", + "VertSplit cterm=reverse gui=reverse", + "WildMenu ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black", + "default link WinSeparator VertSplit", + "default link EndOfBuffer NonText", + "default link LineNrAbove LineNr", + "default link LineNrBelow LineNr", + "default link QuickFixLine Search", + "default link CursorLineSign SignColumn", + "default link CursorLineFold FoldColumn", + "default link Substitute Search", + "default link Whitespace NonText", + "default link MsgSeparator StatusLine", + "default link NormalFloat Pmenu", + "default link FloatBorder WinSeparator", + "default FloatShadow blend=80 guibg=Black", + "default FloatShadowThrough blend=100 guibg=Black", + "RedrawDebugNormal cterm=reverse gui=reverse", + "RedrawDebugClear ctermbg=Yellow guibg=Yellow", + "RedrawDebugComposed ctermbg=Green guibg=Green", + "RedrawDebugRecompose ctermbg=Red guibg=Red", + "Error term=reverse cterm=NONE ctermfg=White ctermbg=Red gui=NONE guifg=White guibg=Red", + "Todo term=standout cterm=NONE ctermfg=Black ctermbg=Yellow gui=NONE guifg=Blue guibg=Yellow", + "default link String Constant", + "default link Character Constant", + "default link Number Constant", + "default link Boolean Constant", + "default link Float Number", + "default link Function Identifier", + "default link Conditional Statement", + "default link Repeat Statement", + "default link Label Statement", + "default link Operator Statement", + "default link Keyword Statement", + "default link Exception Statement", + "default link Include PreProc", + "default link Define PreProc", + "default link Macro PreProc", + "default link PreCondit PreProc", + "default link StorageClass Type", + "default link Structure Type", + "default link Typedef Type", + "default link Tag Special", + "default link SpecialChar Special", + "default link Delimiter Special", + "default link SpecialComment Special", + "default link Debug Special", + "default DiagnosticError ctermfg=1 guifg=Red", + "default DiagnosticWarn ctermfg=3 guifg=Orange", + "default DiagnosticInfo ctermfg=4 guifg=LightBlue", + "default DiagnosticHint ctermfg=7 guifg=LightGrey", + "default DiagnosticUnderlineError cterm=underline gui=underline guisp=Red", + "default DiagnosticUnderlineWarn cterm=underline gui=underline guisp=Orange", + "default DiagnosticUnderlineInfo cterm=underline gui=underline guisp=LightBlue", + "default DiagnosticUnderlineHint cterm=underline gui=underline guisp=LightGrey", + "default link DiagnosticVirtualTextError DiagnosticError", + "default link DiagnosticVirtualTextWarn DiagnosticWarn", + "default link DiagnosticVirtualTextInfo DiagnosticInfo", + "default link DiagnosticVirtualTextHint DiagnosticHint", + "default link DiagnosticFloatingError DiagnosticError", + "default link DiagnosticFloatingWarn DiagnosticWarn", + "default link DiagnosticFloatingInfo DiagnosticInfo", + "default link DiagnosticFloatingHint DiagnosticHint", + "default link DiagnosticSignError DiagnosticError", + "default link DiagnosticSignWarn DiagnosticWarn", + "default link DiagnosticSignInfo DiagnosticInfo", + "default link DiagnosticSignHint DiagnosticHint", + NULL +}; + +// Default colors only used with a light background. +static const char *highlight_init_light[] = { + "ColorColumn ctermbg=LightRed guibg=LightRed", + "CursorColumn ctermbg=LightGrey guibg=Grey90", + "CursorLine cterm=underline guibg=Grey90", + "CursorLineNr cterm=underline ctermfg=Brown gui=bold guifg=Brown", + "DiffAdd ctermbg=LightBlue guibg=LightBlue", + "DiffChange ctermbg=LightMagenta guibg=LightMagenta", + "DiffDelete ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan", + "Directory ctermfg=DarkBlue guifg=Blue", + "FoldColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue", + "Folded ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue", + "LineNr ctermfg=Brown guifg=Brown", + "MatchParen ctermbg=Cyan guibg=Cyan", + "MoreMsg ctermfg=DarkGreen gui=bold guifg=SeaGreen", + "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta", + "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey", + "PmenuThumb ctermbg=Black guibg=Black", + "Question ctermfg=DarkGreen gui=bold guifg=SeaGreen", + "Search ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE", + "SignColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue", + "SpecialKey ctermfg=DarkBlue guifg=Blue", + "SpellBad ctermbg=LightRed guisp=Red gui=undercurl", + "SpellCap ctermbg=LightBlue guisp=Blue gui=undercurl", + "SpellLocal ctermbg=Cyan guisp=DarkCyan gui=undercurl", + "SpellRare ctermbg=LightMagenta guisp=Magenta gui=undercurl", + "TabLine cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey", + "Title ctermfg=DarkMagenta gui=bold guifg=Magenta", + "Visual guibg=LightGrey", + "WarningMsg ctermfg=DarkRed guifg=Red", + "Comment term=bold cterm=NONE ctermfg=DarkBlue ctermbg=NONE gui=NONE guifg=Blue guibg=NONE", + "Constant term=underline cterm=NONE ctermfg=DarkRed ctermbg=NONE gui=NONE guifg=Magenta guibg=NONE", + "Special term=bold cterm=NONE ctermfg=DarkMagenta ctermbg=NONE gui=NONE guifg=#6a5acd guibg=NONE", + "Identifier term=underline cterm=NONE ctermfg=DarkCyan ctermbg=NONE gui=NONE guifg=DarkCyan guibg=NONE", + "Statement term=bold cterm=NONE ctermfg=Brown ctermbg=NONE gui=bold guifg=Brown guibg=NONE", + "PreProc term=underline cterm=NONE ctermfg=DarkMagenta ctermbg=NONE gui=NONE guifg=#6a0dad guibg=NONE", + "Type term=underline cterm=NONE ctermfg=DarkGreen ctermbg=NONE gui=bold guifg=SeaGreen guibg=NONE", + "Underlined term=underline cterm=underline ctermfg=DarkMagenta gui=underline guifg=SlateBlue", + "Ignore term=NONE cterm=NONE ctermfg=white ctermbg=NONE gui=NONE guifg=bg guibg=NONE", + NULL +}; + +// Default colors only used with a dark background. +static const char *highlight_init_dark[] = { + "ColorColumn ctermbg=DarkRed guibg=DarkRed", + "CursorColumn ctermbg=DarkGrey guibg=Grey40", + "CursorLine cterm=underline guibg=Grey40", + "CursorLineNr cterm=underline ctermfg=Yellow gui=bold guifg=Yellow", + "DiffAdd ctermbg=DarkBlue guibg=DarkBlue", + "DiffChange ctermbg=DarkMagenta guibg=DarkMagenta", + "DiffDelete ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan", + "Directory ctermfg=LightCyan guifg=Cyan", + "FoldColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan", + "Folded ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan", + "LineNr ctermfg=Yellow guifg=Yellow", + "MatchParen ctermbg=DarkCyan guibg=DarkCyan", + "MoreMsg ctermfg=LightGreen gui=bold guifg=SeaGreen", + "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta", + "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey", + "PmenuThumb ctermbg=White guibg=White", + "Question ctermfg=LightGreen gui=bold guifg=Green", + "Search ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black", + "SignColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan", + "SpecialKey ctermfg=LightBlue guifg=Cyan", + "SpellBad ctermbg=Red guisp=Red gui=undercurl", + "SpellCap ctermbg=Blue guisp=Blue gui=undercurl", + "SpellLocal ctermbg=Cyan guisp=Cyan gui=undercurl", + "SpellRare ctermbg=Magenta guisp=Magenta gui=undercurl", + "TabLine cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey", + "Title ctermfg=LightMagenta gui=bold guifg=Magenta", + "Visual guibg=DarkGrey", + "WarningMsg ctermfg=LightRed guifg=Red", + "Comment term=bold cterm=NONE ctermfg=Cyan ctermbg=NONE gui=NONE guifg=#80a0ff guibg=NONE", + "Constant term=underline cterm=NONE ctermfg=Magenta ctermbg=NONE gui=NONE guifg=#ffa0a0 guibg=NONE", + "Special term=bold cterm=NONE ctermfg=LightRed ctermbg=NONE gui=NONE guifg=Orange guibg=NONE", + "Identifier term=underline cterm=bold ctermfg=Cyan ctermbg=NONE gui=NONE guifg=#40ffff guibg=NONE", + "Statement term=bold cterm=NONE ctermfg=Yellow ctermbg=NONE gui=bold guifg=#ffff60 guibg=NONE", + "PreProc term=underline cterm=NONE ctermfg=LightBlue ctermbg=NONE gui=NONE guifg=#ff80ff guibg=NONE", + "Type term=underline cterm=NONE ctermfg=LightGreen ctermbg=NONE gui=bold guifg=#60ff60 guibg=NONE", + "Underlined term=underline cterm=underline ctermfg=LightBlue gui=underline guifg=#80a0ff", + "Ignore term=NONE cterm=NONE ctermfg=black ctermbg=NONE gui=NONE guifg=bg guibg=NONE", + NULL +}; + +const char *const highlight_init_cmdline[] = { + // XXX When modifying a list modify it in both valid and invalid halves. + // TODO(ZyX-I): merge valid and invalid groups via a macros. + + // NvimInternalError should appear only when highlighter has a bug. + "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", + + // Highlight groups (links) used by parser: + + "default link NvimAssignment Operator", + "default link NvimPlainAssignment NvimAssignment", + "default link NvimAugmentedAssignment NvimAssignment", + "default link NvimAssignmentWithAddition NvimAugmentedAssignment", + "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment", + "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment", + + "default link NvimOperator Operator", + + "default link NvimUnaryOperator NvimOperator", + "default link NvimUnaryPlus NvimUnaryOperator", + "default link NvimUnaryMinus NvimUnaryOperator", + "default link NvimNot NvimUnaryOperator", + + "default link NvimBinaryOperator NvimOperator", + "default link NvimComparison NvimBinaryOperator", + "default link NvimComparisonModifier NvimComparison", + "default link NvimBinaryPlus NvimBinaryOperator", + "default link NvimBinaryMinus NvimBinaryOperator", + "default link NvimConcat NvimBinaryOperator", + "default link NvimConcatOrSubscript NvimConcat", + "default link NvimOr NvimBinaryOperator", + "default link NvimAnd NvimBinaryOperator", + "default link NvimMultiplication NvimBinaryOperator", + "default link NvimDivision NvimBinaryOperator", + "default link NvimMod NvimBinaryOperator", + + "default link NvimTernary NvimOperator", + "default link NvimTernaryColon NvimTernary", + + "default link NvimParenthesis Delimiter", + "default link NvimLambda NvimParenthesis", + "default link NvimNestingParenthesis NvimParenthesis", + "default link NvimCallingParenthesis NvimParenthesis", + + "default link NvimSubscript NvimParenthesis", + "default link NvimSubscriptBracket NvimSubscript", + "default link NvimSubscriptColon NvimSubscript", + "default link NvimCurly NvimSubscript", + + "default link NvimContainer NvimParenthesis", + "default link NvimDict NvimContainer", + "default link NvimList NvimContainer", + + "default link NvimIdentifier Identifier", + "default link NvimIdentifierScope NvimIdentifier", + "default link NvimIdentifierScopeDelimiter NvimIdentifier", + "default link NvimIdentifierName NvimIdentifier", + "default link NvimIdentifierKey NvimIdentifier", + + "default link NvimColon Delimiter", + "default link NvimComma Delimiter", + "default link NvimArrow Delimiter", + + "default link NvimRegister SpecialChar", + "default link NvimNumber Number", + "default link NvimFloat NvimNumber", + "default link NvimNumberPrefix Type", + + "default link NvimOptionSigil Type", + "default link NvimOptionName NvimIdentifier", + "default link NvimOptionScope NvimIdentifierScope", + "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter", + + "default link NvimEnvironmentSigil NvimOptionSigil", + "default link NvimEnvironmentName NvimIdentifier", + + "default link NvimString String", + "default link NvimStringBody NvimString", + "default link NvimStringQuote NvimString", + "default link NvimStringSpecial SpecialChar", + + "default link NvimSingleQuote NvimStringQuote", + "default link NvimSingleQuotedBody NvimStringBody", + "default link NvimSingleQuotedQuote NvimStringSpecial", + + "default link NvimDoubleQuote NvimStringQuote", + "default link NvimDoubleQuotedBody NvimStringBody", + "default link NvimDoubleQuotedEscape NvimStringSpecial", + + "default link NvimFigureBrace NvimInternalError", + "default link NvimSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimSpacing Normal", + + // NvimInvalid groups: + + "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimInvalid Error", + + "default link NvimInvalidAssignment NvimInvalid", + "default link NvimInvalidPlainAssignment NvimInvalidAssignment", + "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment", + "default link NvimInvalidAssignmentWithAddition NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithSubtraction NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithConcatenation NvimInvalidAugmentedAssignment", + + "default link NvimInvalidOperator NvimInvalid", + + "default link NvimInvalidUnaryOperator NvimInvalidOperator", + "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator", + "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator", + "default link NvimInvalidNot NvimInvalidUnaryOperator", + + "default link NvimInvalidBinaryOperator NvimInvalidOperator", + "default link NvimInvalidComparison NvimInvalidBinaryOperator", + "default link NvimInvalidComparisonModifier NvimInvalidComparison", + "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator", + "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator", + "default link NvimInvalidConcat NvimInvalidBinaryOperator", + "default link NvimInvalidConcatOrSubscript NvimInvalidConcat", + "default link NvimInvalidOr NvimInvalidBinaryOperator", + "default link NvimInvalidAnd NvimInvalidBinaryOperator", + "default link NvimInvalidMultiplication NvimInvalidBinaryOperator", + "default link NvimInvalidDivision NvimInvalidBinaryOperator", + "default link NvimInvalidMod NvimInvalidBinaryOperator", + + "default link NvimInvalidTernary NvimInvalidOperator", + "default link NvimInvalidTernaryColon NvimInvalidTernary", + + "default link NvimInvalidDelimiter NvimInvalid", + + "default link NvimInvalidParenthesis NvimInvalidDelimiter", + "default link NvimInvalidLambda NvimInvalidParenthesis", + "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis", + "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis", + + "default link NvimInvalidSubscript NvimInvalidParenthesis", + "default link NvimInvalidSubscriptBracket NvimInvalidSubscript", + "default link NvimInvalidSubscriptColon NvimInvalidSubscript", + "default link NvimInvalidCurly NvimInvalidSubscript", + + "default link NvimInvalidContainer NvimInvalidParenthesis", + "default link NvimInvalidDict NvimInvalidContainer", + "default link NvimInvalidList NvimInvalidContainer", + + "default link NvimInvalidValue NvimInvalid", + + "default link NvimInvalidIdentifier NvimInvalidValue", + "default link NvimInvalidIdentifierScope NvimInvalidIdentifier", + "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier", + "default link NvimInvalidIdentifierName NvimInvalidIdentifier", + "default link NvimInvalidIdentifierKey NvimInvalidIdentifier", + + "default link NvimInvalidColon NvimInvalidDelimiter", + "default link NvimInvalidComma NvimInvalidDelimiter", + "default link NvimInvalidArrow NvimInvalidDelimiter", + + "default link NvimInvalidRegister NvimInvalidValue", + "default link NvimInvalidNumber NvimInvalidValue", + "default link NvimInvalidFloat NvimInvalidNumber", + "default link NvimInvalidNumberPrefix NvimInvalidNumber", + + "default link NvimInvalidOptionSigil NvimInvalidIdentifier", + "default link NvimInvalidOptionName NvimInvalidIdentifier", + "default link NvimInvalidOptionScope NvimInvalidIdentifierScope", + "default link NvimInvalidOptionScopeDelimiter NvimInvalidIdentifierScopeDelimiter", + + "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil", + "default link NvimInvalidEnvironmentName NvimInvalidIdentifier", + + // Invalid string bodies and specials are still highlighted as valid ones to + // minimize the red area. + "default link NvimInvalidString NvimInvalidValue", + "default link NvimInvalidStringBody NvimStringBody", + "default link NvimInvalidStringQuote NvimInvalidString", + "default link NvimInvalidStringSpecial NvimStringSpecial", + + "default link NvimInvalidSingleQuote NvimInvalidStringQuote", + "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial", + + "default link NvimInvalidDoubleQuote NvimInvalidStringQuote", + "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial", + "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue", + + "default link NvimInvalidFigureBrace NvimInvalidDelimiter", + + "default link NvimInvalidSpacing ErrorMsg", + + // Not actually invalid, but we highlight user that he is doing something + // wrong. + "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue", + NULL, +}; + +/// Returns the number of highlight groups. +int highlight_num_groups(void) +{ + return highlight_ga.ga_len; +} + +/// Returns the name of a highlight group. +char_u *highlight_group_name(int id) +{ + return HL_TABLE()[id].sg_name; +} + +/// Returns the ID of the link to a highlight group. +int highlight_link_id(int id) +{ + return HL_TABLE()[id].sg_link; +} + +/// Create default links for Nvim* highlight groups used for cmdline coloring +void syn_init_cmdline_highlight(bool reset, bool init) +{ + for (size_t i = 0; highlight_init_cmdline[i] != NULL; i++) { + do_highlight(highlight_init_cmdline[i], reset, init); + } +} + +/// Load colors from a file if "g:colors_name" is set, otherwise load builtin +/// colors +/// +/// @param both include groups where 'bg' doesn't matter +/// @param reset clear groups first +void init_highlight(bool both, bool reset) +{ + static int had_both = false; + + // Try finding the color scheme file. Used when a color file was loaded + // and 'background' or 't_Co' is changed. + char_u *p = get_var_value("g:colors_name"); + if (p != NULL) { + // Value of g:colors_name could be freed in load_colors() and make + // p invalid, so copy it. + char_u *copy_p = vim_strsave(p); + bool okay = load_colors(copy_p); + xfree(copy_p); + if (okay) { + return; + } + } + + // Didn't use a color file, use the compiled-in colors. + if (both) { + had_both = true; + const char *const *const pp = highlight_init_both; + for (size_t i = 0; pp[i] != NULL; i++) { + do_highlight(pp[i], reset, true); + } + } else if (!had_both) { + // Don't do anything before the call with both == true from main(). + // Not everything has been setup then, and that call will overrule + // everything anyway. + return; + } + + const char *const *const pp = ((*p_bg == 'l') + ? highlight_init_light + : highlight_init_dark); + for (size_t i = 0; pp[i] != NULL; i++) { + do_highlight(pp[i], reset, true); + } + + // Reverse looks ugly, but grey may not work for 8 colors. Thus let it + // depend on the number of colors available. + // With 8 colors brown is equal to yellow, need to use black for Search fg + // to avoid Statement highlighted text disappears. + // Clear the attributes, needed when changing the t_Co value. + if (t_colors > 8) { + do_highlight((*p_bg == 'l' + ? "Visual cterm=NONE ctermbg=LightGrey" + : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); + } else { + do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); + if (*p_bg == 'l') { + do_highlight("Search ctermfg=black", false, true); + } + } + + syn_init_cmdline_highlight(false, false); +} + +/// Load color file "name". +/// Return OK for success, FAIL for failure. +int load_colors(char_u *name) +{ + char_u *buf; + int retval = FAIL; + static bool recursive = false; + + // When being called recursively, this is probably because setting + // 'background' caused the highlighting to be reloaded. This means it is + // working, thus we should return OK. + if (recursive) { + return OK; + } + + recursive = true; + size_t buflen = STRLEN(name) + 12; + buf = xmalloc(buflen); + apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf); + snprintf((char *)buf, buflen, "colors/%s.vim", name); + retval = source_runtime((char *)buf, DIP_START + DIP_OPT); + if (retval == FAIL) { + snprintf((char *)buf, buflen, "colors/%s.lua", name); + retval = source_runtime((char *)buf, DIP_START + DIP_OPT); + } + xfree(buf); + apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, false, curbuf); + + recursive = false; + + return retval; +} + +static char *(color_names[28]) = { + "Black", "DarkBlue", "DarkGreen", "DarkCyan", + "DarkRed", "DarkMagenta", "Brown", "DarkYellow", + "Gray", "Grey", "LightGray", "LightGrey", + "DarkGray", "DarkGrey", + "Blue", "LightBlue", "Green", "LightGreen", + "Cyan", "LightCyan", "Red", "LightRed", "Magenta", + "LightMagenta", "Yellow", "LightYellow", "White", "NONE" +}; +// indices: +// 0, 1, 2, 3, +// 4, 5, 6, 7, +// 8, 9, 10, 11, +// 12, 13, +// 14, 15, 16, 17, +// 18, 19, 20, 21, 22, +// 23, 24, 25, 26, 27 +static int color_numbers_16[28] = { 0, 1, 2, 3, + 4, 5, 6, 6, + 7, 7, 7, 7, + 8, 8, + 9, 9, 10, 10, + 11, 11, 12, 12, 13, + 13, 14, 14, 15, -1 }; +// for xterm with 88 colors... +static int color_numbers_88[28] = { 0, 4, 2, 6, + 1, 5, 32, 72, + 84, 84, 7, 7, + 82, 82, + 12, 43, 10, 61, + 14, 63, 9, 74, 13, + 75, 11, 78, 15, -1 }; +// for xterm with 256 colors... +static int color_numbers_256[28] = { 0, 4, 2, 6, + 1, 5, 130, 3, + 248, 248, 7, 7, + 242, 242, + 12, 81, 10, 121, + 14, 159, 9, 224, 13, + 225, 11, 229, 15, -1 }; +// for terminals with less than 16 colors... +static int color_numbers_8[28] = { 0, 4, 2, 6, + 1, 5, 3, 3, + 7, 7, 7, 7, + 0+8, 0+8, + 4+8, 4+8, 2+8, 2+8, + 6+8, 6+8, 1+8, 1+8, 5+8, + 5+8, 3+8, 3+8, 7+8, -1 }; + +// Lookup the "cterm" value to be used for color with index "idx" in +// color_names[]. +// "boldp" will be set to TRUE or FALSE for a foreground color when using 8 +// colors, otherwise it will be unchanged. +int lookup_color(const int idx, const bool foreground, TriState *const boldp) +{ + int color = color_numbers_16[idx]; + + // Use the _16 table to check if it's a valid color name. + if (color < 0) { + return -1; + } + + if (t_colors == 8) { + // t_Co is 8: use the 8 colors table + color = color_numbers_8[idx]; + if (foreground) { + // set/reset bold attribute to get light foreground + // colors (on some terminals, e.g. "linux") + if (color & 8) { + *boldp = kTrue; + } else { + *boldp = kFalse; + } + } + color &= 7; // truncate to 8 colors + } else if (t_colors == 16) { + color = color_numbers_8[idx]; + } else if (t_colors == 88) { + color = color_numbers_88[idx]; + } else if (t_colors >= 256) { + color = color_numbers_256[idx]; + } + return color; +} + +void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) +{ + int idx = id - 1; // Index is ID minus one. + + bool is_default = attrs.rgb_ae_attr & HL_DEFAULT; + + // Return if "default" was used and the group already has settings + if (is_default && hl_has_settings(idx, true)) { + return; + } + + HlGroup *g = &HL_TABLE()[idx]; + + if (link_id > 0) { + g->sg_cleared = false; + g->sg_link = link_id; + g->sg_script_ctx = current_sctx; + g->sg_script_ctx.sc_lnum += sourcing_lnum; + g->sg_set |= SG_LINK; + if (is_default) { + g->sg_deflink = link_id; + g->sg_deflink_sctx = current_sctx; + g->sg_deflink_sctx.sc_lnum += sourcing_lnum; + } + return; + } + + g->sg_cleared = false; + g->sg_link = 0; + g->sg_gui = attrs.rgb_ae_attr; + + g->sg_rgb_fg = attrs.rgb_fg_color; + g->sg_rgb_bg = attrs.rgb_bg_color; + g->sg_rgb_sp = attrs.rgb_sp_color; + + struct { + char **dest; RgbValue val; Object name; + } cattrs[] = { + { &g->sg_rgb_fg_name, g->sg_rgb_fg, HAS_KEY(dict->fg) ? dict->fg : dict->foreground }, + { &g->sg_rgb_bg_name, g->sg_rgb_bg, HAS_KEY(dict->bg) ? dict->bg : dict->background }, + { &g->sg_rgb_sp_name, g->sg_rgb_sp, HAS_KEY(dict->sp) ? dict->sp : dict->special }, + { NULL, -1, NIL }, + }; + + char hex_name[8]; + char *name; + + for (int j = 0; cattrs[j].dest; j++) { + if (cattrs[j].val < 0) { + XFREE_CLEAR(*cattrs[j].dest); + continue; + } + + if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) { + name = cattrs[j].name.data.string.data; + } else { + snprintf(hex_name, sizeof(hex_name), "#%06x", cattrs[j].val); + name = hex_name; + } + + if (!*cattrs[j].dest + || STRCMP(*cattrs[j].dest, name) != 0) { + xfree(*cattrs[j].dest); + *cattrs[j].dest = xstrdup(name); + } + } + + g->sg_cterm = attrs.cterm_ae_attr; + g->sg_cterm_bg = attrs.cterm_bg_color; + g->sg_cterm_fg = attrs.cterm_fg_color; + g->sg_cterm_bold = g->sg_cterm & HL_BOLD; + g->sg_blend = attrs.hl_blend; + + g->sg_script_ctx = current_sctx; + g->sg_script_ctx.sc_lnum += sourcing_lnum; + + // 'Normal' is special + if (STRCMP(g->sg_name_u, "NORMAL") == 0) { + cterm_normal_fg_color = g->sg_cterm_fg; + cterm_normal_bg_color = g->sg_cterm_bg; + normal_fg = g->sg_rgb_fg; + normal_bg = g->sg_rgb_bg; + normal_sp = g->sg_rgb_sp; + ui_default_colors_set(); + } else { + g->sg_attr = hl_get_syn_attr(0, id, attrs); + + // a cursor style uses this syn_id, make sure its attribute is updated. + if (cursor_mode_uses_syn_id(id)) { + ui_mode_info_set(); + } + } +} + +/// Handle ":highlight" command +/// +/// When using ":highlight clear" this is called recursively for each group with +/// forceit and init being both true. +/// +/// @param[in] line Command arguments. +/// @param[in] forceit True when bang is given, allows to link group even if +/// it has its own settings. +/// @param[in] init True when initializing. +void do_highlight(const char *line, const bool forceit, const bool init) + FUNC_ATTR_NONNULL_ALL +{ + const char *name_end; + const char *linep; + const char *key_start; + const char *arg_start; + int off; + int len; + int attr; + int id; + int idx; + HlGroup item_before; + bool did_change = false; + bool dodefault = false; + bool doclear = false; + bool dolink = false; + bool error = false; + int color; + bool is_normal_group = false; // "Normal" group + bool did_highlight_changed = false; + + // If no argument, list current highlighting. + if (ends_excmd((uint8_t)(*line))) { + for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { + // TODO(brammool): only call when the group has attributes set + highlight_list_one(i); + } + return; + } + + // Isolate the name. + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); + + // Check for "default" argument. + if (strncmp(line, "default", (size_t)(name_end - line)) == 0) { + dodefault = true; + line = linep; + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); + } + + // Check for "clear" or "link" argument. + if (strncmp(line, "clear", (size_t)(name_end - line)) == 0) { + doclear = true; + } else if (strncmp(line, "link", (size_t)(name_end - line)) == 0) { + dolink = true; + } + + // ":highlight {group-name}": list highlighting for one group. + if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { + id = syn_name2id_len((const char_u *)line, (size_t)(name_end - line)); + if (id == 0) { + semsg(_("E411: highlight group not found: %s"), line); + } else { + highlight_list_one(id); + } + return; + } + + // Handle ":highlight link {from} {to}" command. + if (dolink) { + const char *from_start = linep; + const char *from_end; + const char *to_start; + const char *to_end; + int from_id; + int to_id; + HlGroup *hlgroup = NULL; + + from_end = (const char *)skiptowhite((const char_u *)from_start); + to_start = (const char *)skipwhite((const char_u *)from_end); + to_end = (const char *)skiptowhite((const char_u *)to_start); + + if (ends_excmd((uint8_t)(*from_start)) + || ends_excmd((uint8_t)(*to_start))) { + semsg(_("E412: Not enough arguments: \":highlight link %s\""), + from_start); + return; + } + + if (!ends_excmd(*skipwhite((const char_u *)to_end))) { + semsg(_("E413: Too many arguments: \":highlight link %s\""), from_start); + return; + } + + from_id = syn_check_group(from_start, (size_t)(from_end - from_start)); + if (strncmp(to_start, "NONE", 4) == 0) { + to_id = 0; + } else { + to_id = syn_check_group(to_start, (size_t)(to_end - to_start)); + } + + if (from_id > 0) { + hlgroup = &HL_TABLE()[from_id - 1]; + if (dodefault && (forceit || hlgroup->sg_deflink == 0)) { + hlgroup->sg_deflink = to_id; + hlgroup->sg_deflink_sctx = current_sctx; + hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&hlgroup->sg_deflink_sctx); + } + } + + if (from_id > 0 && (!init || hlgroup->sg_set == 0)) { + // Don't allow a link when there already is some highlighting + // for the group, unless '!' is used + if (to_id > 0 && !forceit && !init + && hl_has_settings(from_id - 1, dodefault)) { + if (sourcing_name == NULL && !dodefault) { + emsg(_("E414: group has settings, highlight link ignored")); + } + } else if (hlgroup->sg_link != to_id + || hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid + || hlgroup->sg_cleared) { + if (!init) { + hlgroup->sg_set |= SG_LINK; + } + hlgroup->sg_link = to_id; + hlgroup->sg_script_ctx = current_sctx; + hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&hlgroup->sg_script_ctx); + hlgroup->sg_cleared = false; + redraw_all_later(SOME_VALID); + + // Only call highlight changed() once after multiple changes + need_highlight_changed = true; + } + } + + return; + } + + if (doclear) { + // ":highlight clear [group]" command. + line = linep; + if (ends_excmd((uint8_t)(*line))) { + do_unlet(S_LEN("colors_name"), true); + restore_cterm_colors(); + + // Clear all default highlight groups and load the defaults. + for (int j = 0; j < highlight_ga.ga_len; j++) { + highlight_clear(j); + } + init_highlight(true, true); + highlight_changed(); + redraw_all_later(NOT_VALID); + return; + } + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); + } + + // Find the group name in the table. If it does not exist yet, add it. + id = syn_check_group(line, (size_t)(name_end - line)); + if (id == 0) { // Failed (out of memory). + return; + } + idx = id - 1; // Index is ID minus one. + + // Return if "default" was used and the group already has settings + if (dodefault && hl_has_settings(idx, true)) { + return; + } + + // Make a copy so we can check if any attribute actually changed + item_before = HL_TABLE()[idx]; + is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0); + + // Clear the highlighting for ":hi clear {group}" and ":hi clear". + if (doclear || (forceit && init)) { + highlight_clear(idx); + if (!doclear) { + HL_TABLE()[idx].sg_set = 0; + } + } + + char *key = NULL; + char *arg = NULL; + if (!doclear) { + while (!ends_excmd((uint8_t)(*linep))) { + key_start = linep; + if (*linep == '=') { + semsg(_("E415: unexpected equal sign: %s"), key_start); + error = true; + break; + } + + // Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg", + // "guibg" or "guisp"). + while (*linep && !ascii_iswhite(*linep) && *linep != '=') { + linep++; + } + xfree(key); + key = (char *)vim_strnsave_up((const char_u *)key_start, + (size_t)(linep - key_start)); + linep = (const char *)skipwhite((const char_u *)linep); + + if (strcmp(key, "NONE") == 0) { + if (!init || HL_TABLE()[idx].sg_set == 0) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI; + } + highlight_clear(idx); + } + continue; + } + + // Check for the equal sign. + if (*linep != '=') { + semsg(_("E416: missing equal sign: %s"), key_start); + error = true; + break; + } + linep++; + + // Isolate the argument. + linep = (const char *)skipwhite((const char_u *)linep); + if (*linep == '\'') { // guifg='color name' + arg_start = ++linep; + linep = strchr(linep, '\''); + if (linep == NULL) { + semsg(_(e_invarg2), key_start); + error = true; + break; + } + } else { + arg_start = linep; + linep = (const char *)skiptowhite((const char_u *)linep); + } + if (linep == arg_start) { + semsg(_("E417: missing argument: %s"), key_start); + error = true; + break; + } + xfree(arg); + arg = xstrndup(arg_start, (size_t)(linep - arg_start)); + + if (*linep == '\'') { + linep++; + } + + // Store the argument. + if (strcmp(key, "TERM") == 0 + || strcmp(key, "CTERM") == 0 + || strcmp(key, "GUI") == 0) { + attr = 0; + off = 0; + int i; + while (arg[off] != NUL) { + for (i = ARRAY_SIZE(hl_attr_table); --i >= 0;) { + len = (int)STRLEN(hl_name_table[i]); + if (STRNICMP(arg + off, hl_name_table[i], len) == 0) { + attr |= hl_attr_table[i]; + off += len; + break; + } + } + if (i < 0) { + semsg(_("E418: Illegal value: %s"), arg); + error = true; + break; + } + if (arg[off] == ',') { // Another one follows. + off++; + } + } + if (error) { + break; + } + if (*key == 'C') { + if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_CTERM; + } + HL_TABLE()[idx].sg_cterm = attr; + HL_TABLE()[idx].sg_cterm_bold = false; + } + } else if (*key == 'G') { + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_GUI; + } + HL_TABLE()[idx].sg_gui = attr; + } + } + } else if (STRCMP(key, "FONT") == 0) { + // in non-GUI fonts are simply ignored + } else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) { + if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_CTERM; + } + + // When setting the foreground color, and previously the "bold" + // flag was set for a light color, reset it now + if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold) { + HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; + HL_TABLE()[idx].sg_cterm_bold = false; + } + + if (ascii_isdigit(*arg)) { + color = atoi(arg); + } else if (STRICMP(arg, "fg") == 0) { + if (cterm_normal_fg_color) { + color = cterm_normal_fg_color - 1; + } else { + emsg(_("E419: FG color unknown")); + error = true; + break; + } + } else if (STRICMP(arg, "bg") == 0) { + if (cterm_normal_bg_color > 0) { + color = cterm_normal_bg_color - 1; + } else { + emsg(_("E420: BG color unknown")); + error = true; + break; + } + } else { + // Reduce calls to STRICMP a bit, it can be slow. + off = TOUPPER_ASC(*arg); + int i; + for (i = ARRAY_SIZE(color_names); --i >= 0;) { + if (off == color_names[i][0] + && STRICMP(arg + 1, color_names[i] + 1) == 0) { + break; + } + } + if (i < 0) { + semsg(_("E421: Color name or number not recognized: %s"), + key_start); + error = true; + break; + } + + TriState bold = kNone; + color = lookup_color(i, key[5] == 'F', &bold); + + // set/reset bold attribute to get light foreground + // colors (on some terminals, e.g. "linux") + if (bold == kTrue) { + HL_TABLE()[idx].sg_cterm |= HL_BOLD; + HL_TABLE()[idx].sg_cterm_bold = true; + } else if (bold == kFalse) { + HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; + } + } + // Add one to the argument, to avoid zero. Zero is used for + // "NONE", then "color" is -1. + if (key[5] == 'F') { + HL_TABLE()[idx].sg_cterm_fg = color + 1; + if (is_normal_group) { + cterm_normal_fg_color = color + 1; + } + } else { + HL_TABLE()[idx].sg_cterm_bg = color + 1; + if (is_normal_group) { + cterm_normal_bg_color = color + 1; + if (!ui_rgb_attached()) { + if (color >= 0) { + int dark = -1; + + if (t_colors < 16) { + dark = (color == 0 || color == 4); + } else if (color < 16) { + // Limit the heuristic to the standard 16 colors + dark = (color < 7 || color == 8); + } + // Set the 'background' option if the value is + // wrong. + if (dark != -1 + && dark != (*p_bg == 'd') + && !option_was_set("bg")) { + set_option_value("bg", 0L, (dark ? "dark" : "light"), 0); + reset_option_was_set("bg"); + } + } + } + } + } + } + } else if (strcmp(key, "GUIFG") == 0) { + char **namep = &HL_TABLE()[idx].sg_rgb_fg_name; + + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_GUI; + } + + if (*namep == NULL || STRCMP(*namep, arg) != 0) { + xfree(*namep); + if (strcmp(arg, "NONE") != 0) { + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); + } else { + *namep = NULL; + HL_TABLE()[idx].sg_rgb_fg = -1; + } + did_change = true; + } + } + + if (is_normal_group) { + normal_fg = HL_TABLE()[idx].sg_rgb_fg; + } + } else if (STRCMP(key, "GUIBG") == 0) { + char **const namep = &HL_TABLE()[idx].sg_rgb_bg_name; + + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_GUI; + } + + if (*namep == NULL || STRCMP(*namep, arg) != 0) { + xfree(*namep); + if (STRCMP(arg, "NONE") != 0) { + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); + } else { + *namep = NULL; + HL_TABLE()[idx].sg_rgb_bg = -1; + } + did_change = true; + } + } + + if (is_normal_group) { + normal_bg = HL_TABLE()[idx].sg_rgb_bg; + } + } else if (strcmp(key, "GUISP") == 0) { + char **const namep = &HL_TABLE()[idx].sg_rgb_sp_name; + + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_GUI; + } + + if (*namep == NULL || STRCMP(*namep, arg) != 0) { + xfree(*namep); + if (strcmp(arg, "NONE") != 0) { + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); + } else { + *namep = NULL; + HL_TABLE()[idx].sg_rgb_sp = -1; + } + did_change = true; + } + } + + if (is_normal_group) { + normal_sp = HL_TABLE()[idx].sg_rgb_sp; + } + } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) { + // Ignored for now + } else if (strcmp(key, "BLEND") == 0) { + if (strcmp(arg, "NONE") != 0) { + HL_TABLE()[idx].sg_blend = (int)strtol(arg, NULL, 10); + } else { + HL_TABLE()[idx].sg_blend = -1; + } + } else { + semsg(_("E423: Illegal argument: %s"), key_start); + error = true; + break; + } + HL_TABLE()[idx].sg_cleared = false; + + // When highlighting has been given for a group, don't link it. + if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { + HL_TABLE()[idx].sg_link = 0; + } + + // Continue with next argument. + linep = (const char *)skipwhite((const char_u *)linep); + } + } + + // If there is an error, and it's a new entry, remove it from the table. + if (error && idx == highlight_ga.ga_len) { + syn_unadd_group(); + } else { + if (!error && is_normal_group) { + // Need to update all groups, because they might be using "bg" and/or + // "fg", which have been changed now. + highlight_attr_set_all(); + + if (!ui_has(kUILinegrid) && starting == 0) { + // Older UIs assume that we clear the screen after normal group is + // changed + ui_refresh(); + } else { + // TUI and newer UIs will repaint the screen themselves. NOT_VALID + // redraw below will still handle usages of guibg=fg etc. + ui_default_colors_set(); + } + did_highlight_changed = true; + redraw_all_later(NOT_VALID); + } else { + set_hl_attr(idx); + } + HL_TABLE()[idx].sg_script_ctx = current_sctx; + HL_TABLE()[idx].sg_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&HL_TABLE()[idx].sg_script_ctx); + } + xfree(key); + xfree(arg); + + // Only call highlight_changed() once, after a sequence of highlight + // commands, and only if an attribute actually changed + if ((did_change + || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0) + && !did_highlight_changed) { + // Do not trigger a redraw when highlighting is changed while + // redrawing. This may happen when evaluating 'statusline' changes the + // StatusLine group. + if (!updating_screen) { + redraw_all_later(NOT_VALID); + } + need_highlight_changed = true; + } +} + +#if defined(EXITFREE) +void free_highlight(void) +{ + for (int i = 0; i < highlight_ga.ga_len; i++) { + highlight_clear(i); + xfree(HL_TABLE()[i].sg_name); + xfree(HL_TABLE()[i].sg_name_u); + } + ga_clear(&highlight_ga); + map_destroy(cstr_t, int)(&highlight_unames); +} + +#endif + +/// Reset the cterm colors to what they were before Vim was started, if +/// possible. Otherwise reset them to zero. +void restore_cterm_colors(void) +{ + normal_fg = -1; + normal_bg = -1; + normal_sp = -1; + cterm_normal_fg_color = 0; + cterm_normal_bg_color = 0; +} + +/// @param check_link if true also check for an existing link. +/// +/// @return TRUE if highlight group "idx" has any settings. +static int hl_has_settings(int idx, bool check_link) +{ + return HL_TABLE()[idx].sg_cleared == 0 + && (HL_TABLE()[idx].sg_attr != 0 + || HL_TABLE()[idx].sg_cterm_fg != 0 + || HL_TABLE()[idx].sg_cterm_bg != 0 + || HL_TABLE()[idx].sg_rgb_fg_name != NULL + || HL_TABLE()[idx].sg_rgb_bg_name != NULL + || HL_TABLE()[idx].sg_rgb_sp_name != NULL + || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK))); +} + +/// Clear highlighting for one group. +static void highlight_clear(int idx) +{ + HL_TABLE()[idx].sg_cleared = true; + + HL_TABLE()[idx].sg_attr = 0; + HL_TABLE()[idx].sg_cterm = 0; + HL_TABLE()[idx].sg_cterm_bold = false; + HL_TABLE()[idx].sg_cterm_fg = 0; + HL_TABLE()[idx].sg_cterm_bg = 0; + HL_TABLE()[idx].sg_gui = 0; + HL_TABLE()[idx].sg_rgb_fg = -1; + HL_TABLE()[idx].sg_rgb_bg = -1; + HL_TABLE()[idx].sg_rgb_sp = -1; + XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_fg_name); + XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name); + XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name); + HL_TABLE()[idx].sg_blend = -1; + // Restore default link and context if they exist. Otherwise clears. + HL_TABLE()[idx].sg_link = HL_TABLE()[idx].sg_deflink; + // Since we set the default link, set the location to where the default + // link was set. + HL_TABLE()[idx].sg_script_ctx = HL_TABLE()[idx].sg_deflink_sctx; +} + +/// \addtogroup LIST_XXX +/// @{ +#define LIST_ATTR 1 +#define LIST_STRING 2 +#define LIST_INT 3 +/// @} + +static void highlight_list_one(const int id) +{ + const HlGroup *sgp = &HL_TABLE()[id - 1]; // index is ID minus one + bool didh = false; + + if (message_filtered(sgp->sg_name)) { + return; + } + + didh = highlight_list_arg(id, didh, LIST_ATTR, + sgp->sg_cterm, NULL, "cterm"); + didh = highlight_list_arg(id, didh, LIST_INT, + sgp->sg_cterm_fg, NULL, "ctermfg"); + didh = highlight_list_arg(id, didh, LIST_INT, + sgp->sg_cterm_bg, NULL, "ctermbg"); + + didh = highlight_list_arg(id, didh, LIST_ATTR, + sgp->sg_gui, NULL, "gui"); + didh = highlight_list_arg(id, didh, LIST_STRING, + 0, sgp->sg_rgb_fg_name, "guifg"); + didh = highlight_list_arg(id, didh, LIST_STRING, + 0, sgp->sg_rgb_bg_name, "guibg"); + didh = highlight_list_arg(id, didh, LIST_STRING, + 0, sgp->sg_rgb_sp_name, "guisp"); + + didh = highlight_list_arg(id, didh, LIST_INT, + sgp->sg_blend+1, NULL, "blend"); + + if (sgp->sg_link && !got_int) { + (void)syn_list_header(didh, 0, id, true); + didh = true; + msg_puts_attr("links to", HL_ATTR(HLF_D)); + msg_putchar(' '); + msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); + } + + 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; + HlGroup *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 +/// @param iarg integer argument used if \p type == LIST_INT +/// @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 *const sarg, + const char *const name) +{ + char buf[100]; + + if (got_int) { + return false; + } + if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) { + char *ts = buf; + if (type == LIST_INT) { + snprintf((char *)buf, sizeof(buf), "%d", iarg - 1); + } else if (type == LIST_STRING) { + ts = sarg; + } else { // type == LIST_ATTR + buf[0] = NUL; + for (int i = 0; hl_attr_table[i] != 0; i++) { + if (iarg & hl_attr_table[i]) { + if (buf[0] != NUL) { + xstrlcat(buf, ",", 100); + } + xstrlcat(buf, hl_name_table[i], 100); + iarg &= ~hl_attr_table[i]; // don't want "inverse" + } + } + } + + (void)syn_list_header(didh, (int)(vim_strsize((char_u *)ts) + (int)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((char_u *)ts); + } + } + return didh; +} + +/// Check whether highlight group has attribute +/// +/// @param[in] id Highlight group to check. +/// @param[in] flag Attribute to check. +/// @param[in] modec 'g' for GUI, 'c' for term. +/// +/// @return "1" if highlight group has attribute, NULL otherwise. +const char *highlight_has_attr(const int id, const int flag, const int modec) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + int attr; + + if (id <= 0 || id > highlight_ga.ga_len) { + return NULL; + } + + if (modec == 'g') { + attr = HL_TABLE()[id - 1].sg_gui; + } else { + attr = HL_TABLE()[id - 1].sg_cterm; + } + + return (attr & flag) ? "1" : NULL; +} + +/// Return color name of the given highlight group +/// +/// @param[in] id Highlight group to work with. +/// @param[in] what What to return: one of "font", "fg", "bg", "sp", "fg#", +/// "bg#" or "sp#". +/// @param[in] modec 'g' for GUI, 'c' for cterm and 't' for term. +/// +/// @return color name, possibly in a static buffer. Buffer will be overwritten +/// on next highlight_color() call. May return NULL. +const char *highlight_color(const int id, const char *const what, const int modec) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + static char name[20]; + int n; + bool fg = false; + bool sp = false; + bool font = false; + + if (id <= 0 || id > highlight_ga.ga_len) { + return NULL; + } + + if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g') { + fg = true; + } else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o' + && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't') { + font = true; + } else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p') { + sp = true; + } else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g')) { + return NULL; + } + if (modec == 'g') { + if (what[2] == '#' && ui_rgb_attached()) { + if (fg) { + n = HL_TABLE()[id - 1].sg_rgb_fg; + } else if (sp) { + n = HL_TABLE()[id - 1].sg_rgb_sp; + } else { + n = HL_TABLE()[id - 1].sg_rgb_bg; + } + if (n < 0 || n > 0xffffff) { + return NULL; + } + snprintf(name, sizeof(name), "#%06x", n); + return name; + } + if (fg) { + return (const char *)HL_TABLE()[id - 1].sg_rgb_fg_name; + } + if (sp) { + return (const char *)HL_TABLE()[id - 1].sg_rgb_sp_name; + } + return (const char *)HL_TABLE()[id - 1].sg_rgb_bg_name; + } + if (font || sp) { + return NULL; + } + if (modec == 'c') { + if (fg) { + n = HL_TABLE()[id - 1].sg_cterm_fg - 1; + } else { + n = HL_TABLE()[id - 1].sg_cterm_bg - 1; + } + if (n < 0) { + return NULL; + } + snprintf(name, sizeof(name), "%d", n); + return name; + } + // term doesn't have color. + return NULL; +} + +/// Output the syntax list header. +/// +/// @param did_header did header already +/// @param outlen length of string that comes +/// @param id highlight group id +/// @param force_newline always start a new line +/// @return true when started a new line. +bool syn_list_header(const bool did_header, const int outlen, const int id, + bool force_newline) +{ + int endcol = 19; + bool newline = true; + int name_col = 0; + bool adjust = true; + + if (!did_header) { + msg_putchar('\n'); + if (got_int) { + return true; + } + msg_outtrans(HL_TABLE()[id - 1].sg_name); + name_col = msg_col; + endcol = 15; + } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) { + msg_putchar(' '); + adjust = false; + } else if (msg_col + outlen + 1 >= Columns || force_newline) { + msg_putchar('\n'); + if (got_int) { + return true; + } + } else { + if (msg_col >= endcol) { // wrap around is like starting a new line + newline = false; + } + } + + if (adjust) { + if (msg_col >= endcol) { + // output at least one space + endcol = msg_col + 1; + } + + msg_advance(endcol); + } + + // Show "xxx" with the attributes. + if (!did_header) { + if (endcol == Columns - 1 && endcol <= name_col) { + msg_putchar(' '); + } + msg_puts_attr("xxx", syn_id2attr(id)); + msg_putchar(' '); + } + + return newline; +} + +/// Set the attribute numbers for a highlight group. +/// Called after one of the attributes has changed. +/// @param idx corrected highlight index +static void set_hl_attr(int idx) +{ + HlAttrs at_en = HLATTRS_INIT; + HlGroup *sgp = HL_TABLE() + idx; + + at_en.cterm_ae_attr = (int16_t)sgp->sg_cterm; + at_en.cterm_fg_color = sgp->sg_cterm_fg; + at_en.cterm_bg_color = sgp->sg_cterm_bg; + at_en.rgb_ae_attr = (int16_t)sgp->sg_gui; + // FIXME(tarruda): The "unset value" for rgb is -1, but since hlgroup is + // initialized with 0(by garray functions), check for sg_rgb_{f,b}g_name + // before setting attr_entry->{f,g}g_color to a other than -1 + at_en.rgb_fg_color = sgp->sg_rgb_fg_name ? sgp->sg_rgb_fg : -1; + at_en.rgb_bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1; + 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(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)) { + ui_mode_info_set(); + } +} + +int syn_name2id(const char *name) + FUNC_ATTR_NONNULL_ALL +{ + return syn_name2id_len((char_u *)name, STRLEN(name)); +} + +/// Lookup a highlight group name and return its ID. +/// +/// @param highlight name e.g. 'Cursor', 'Normal' +/// @return the highlight id, else 0 if \p name does not exist +int syn_name2id_len(const char_u *name, size_t len) + FUNC_ATTR_NONNULL_ALL +{ + char name_u[MAX_SYN_NAME + 1]; + + if (len == 0 || len > MAX_SYN_NAME) { + return 0; + } + + // Avoid using stricmp() too much, it's slow on some systems */ + // Avoid alloc()/free(), these are slow too. + memcpy(name_u, name, len); + name_u[len] = '\0'; + vim_strup((char_u *)name_u); + + // map_get(..., int) returns 0 when no key is present, which is + // the expected value for missing highlight group. + return map_get(cstr_t, int)(&highlight_unames, name_u); +} + +/// Lookup a highlight group name and return its attributes. +/// Return zero if not found. +int syn_name2attr(const char_u *name) + FUNC_ATTR_NONNULL_ALL +{ + int id = syn_name2id((char *)name); + + if (id != 0) { + return syn_id2attr(id); + } + return 0; +} + +/// Return TRUE if highlight group "name" exists. +int highlight_exists(const char *name) +{ + return syn_name2id(name) > 0; +} + +/// Return the name of highlight group "id". +/// When not a valid ID return an empty string. +char_u *syn_id2name(int id) +{ + if (id <= 0 || id > highlight_ga.ga_len) { + return (char_u *)""; + } + return HL_TABLE()[id - 1].sg_name; +} + +/// Find highlight group name in the table and return its ID. +/// If it doesn't exist yet, a new entry is created. +/// +/// @param pp Highlight group name +/// @param len length of \p pp +/// +/// @return 0 for failure else the id of the group +int syn_check_group(const char *name, size_t len) +{ + if (len > MAX_SYN_NAME) { + emsg(_(e_highlight_group_name_too_long)); + return 0; + } + int id = syn_name2id_len((char_u *)name, len); + if (id == 0) { // doesn't exist yet + return syn_add_group(vim_strnsave((char_u *)name, len)); + } + return id; +} + +/// Add new highlight group and return its ID. +/// +/// @param name must be an allocated string, it will be consumed. +/// @return 0 for failure, else the allocated group id +/// @see syn_check_group syn_unadd_group +static int syn_add_group(char_u *name) +{ + char_u *p; + + // Check that the name is ASCII letters, digits and underscore. + for (p = name; *p != NUL; p++) { + if (!vim_isprintc(*p)) { + emsg(_("E669: Unprintable character in group name")); + xfree(name); + return 0; + } else if (!ASCII_ISALNUM(*p) && *p != '_') { + // This is an error, but since there previously was no check only give a warning. + msg_source(HL_ATTR(HLF_W)); + msg(_("W18: Invalid character in group name")); + break; + } + } + + // First call for this growarray: init growing array. + if (highlight_ga.ga_data == NULL) { + highlight_ga.ga_itemsize = sizeof(HlGroup); + ga_set_growsize(&highlight_ga, 10); + } + + if (highlight_ga.ga_len >= MAX_HL_ID) { + emsg(_("E849: Too many highlight and syntax groups")); + xfree(name); + return 0; + } + + char *const name_up = (char *)vim_strsave_up(name); + + // Append another syntax_highlight entry. + HlGroup *hlgp = GA_APPEND_VIA_PTR(HlGroup, &highlight_ga); + memset(hlgp, 0, sizeof(*hlgp)); + hlgp->sg_name = name; + hlgp->sg_rgb_bg = -1; + hlgp->sg_rgb_fg = -1; + hlgp->sg_rgb_sp = -1; + hlgp->sg_blend = -1; + hlgp->sg_name_u = name_up; + + int id = highlight_ga.ga_len; // ID is index plus one + + map_put(cstr_t, int)(&highlight_unames, name_up, id); + + return id; +} + +/// When, just after calling syn_add_group(), an error is discovered, this +/// function deletes the new name. +static void syn_unadd_group(void) +{ + highlight_ga.ga_len--; + HlGroup *item = &HL_TABLE()[highlight_ga.ga_len]; + map_del(cstr_t, int)(&highlight_unames, item->sg_name_u); + xfree(item->sg_name); + xfree(item->sg_name_u); +} + +/// Translate a group ID to highlight attributes. +/// @see syn_attr2entry +int syn_id2attr(int hl_id) +{ + hl_id = syn_get_final_id(hl_id); + HlGroup *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one + + int attr = ns_get_hl(-1, hl_id, false, sgp->sg_set); + if (attr >= 0) { + return attr; + } + return sgp->sg_attr; +} + +/// Translate a group ID to the final group ID (following links). +int syn_get_final_id(int hl_id) +{ + int count; + + if (hl_id > highlight_ga.ga_len || hl_id < 1) { + return 0; // Can be called from eval!! + } + + // Follow links until there is no more. + // Look out for loops! Break after 100 links. + for (count = 100; --count >= 0;) { + HlGroup *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one + + // 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, sgp->sg_set); + if (check == 0) { + return hl_id; // how dare! it broke the link! + } else if (check > 0) { + hl_id = check; + continue; + } + + + if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { + break; + } + hl_id = sgp->sg_link; + } + + return hl_id; +} + +/// Refresh the color attributes of all highlight groups. +void highlight_attr_set_all(void) +{ + for (int idx = 0; idx < highlight_ga.ga_len; idx++) { + HlGroup *sgp = &HL_TABLE()[idx]; + if (sgp->sg_rgb_bg_name != NULL) { + sgp->sg_rgb_bg = name_to_color(sgp->sg_rgb_bg_name); + } + if (sgp->sg_rgb_fg_name != NULL) { + sgp->sg_rgb_fg = name_to_color(sgp->sg_rgb_fg_name); + } + if (sgp->sg_rgb_sp_name != NULL) { + sgp->sg_rgb_sp = name_to_color(sgp->sg_rgb_sp_name); + } + set_hl_attr(idx); + } +} + +// Apply difference between User[1-9] and HLF_S to HLF_SNC. +static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i, int hlf, int *table) + FUNC_ATTR_NONNULL_ALL +{ + HlGroup *const hlt = HL_TABLE(); + + if (id_alt == 0) { + memset(&hlt[hlcnt + i], 0, sizeof(HlGroup)); + hlt[hlcnt + i].sg_cterm = highlight_attr[hlf]; + hlt[hlcnt + i].sg_gui = highlight_attr[hlf]; + } else { + memmove(&hlt[hlcnt + i], &hlt[id_alt - 1], sizeof(HlGroup)); + } + hlt[hlcnt + i].sg_link = 0; + + hlt[hlcnt + i].sg_cterm ^= hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm; + if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg) { + hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg; + } + if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg) { + hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg; + } + hlt[hlcnt + i].sg_gui ^= hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui; + if (hlt[id - 1].sg_rgb_fg != hlt[id_S - 1].sg_rgb_fg) { + hlt[hlcnt + i].sg_rgb_fg = hlt[id - 1].sg_rgb_fg; + } + if (hlt[id - 1].sg_rgb_bg != hlt[id_S - 1].sg_rgb_bg) { + hlt[hlcnt + i].sg_rgb_bg = hlt[id - 1].sg_rgb_bg; + } + if (hlt[id - 1].sg_rgb_sp != hlt[id_S - 1].sg_rgb_sp) { + hlt[hlcnt + i].sg_rgb_sp = hlt[id - 1].sg_rgb_sp; + } + highlight_ga.ga_len = hlcnt + i + 1; + set_hl_attr(hlcnt + i); // At long last we can apply + table[i] = syn_id2attr(hlcnt + i + 1); +} + +/// Translate highlight groups into attributes in highlight_attr[] and set up +/// the user highlights User1..9. A set of corresponding highlights to use on +/// top of HLF_SNC is computed. Called only when nvim starts and upon first +/// screen redraw after any :highlight command. +void highlight_changed(void) +{ + int id; + char userhl[30]; // use 30 to avoid compiler warning + int id_S = -1; + int id_SNC = 0; + int hlcnt; + + need_highlight_changed = false; + + /// Translate builtin highlight groups into attributes for quick lookup. + for (int hlf = 0; hlf < HLF_COUNT; hlf++) { + id = syn_check_group(hlf_names[hlf], STRLEN(hlf_names[hlf])); + if (id == 0) { + abort(); + } + int final_id = syn_get_final_id(id); + if (hlf == HLF_SNC) { + id_SNC = final_id; + } else if (hlf == HLF_S) { + id_S = final_id; + } + + highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id, + hlf == HLF_INACTIVE); + + if (highlight_attr[hlf] != highlight_attr_last[hlf]) { + if (hlf == HLF_MSG) { + clear_cmdline = true; + } + ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]), + highlight_attr[hlf]); + highlight_attr_last[hlf] = highlight_attr[hlf]; + } + } + + // + // Setup the user highlights + // + // Temporarily utilize 10 more hl entries: + // 9 for User1-User9 combined with StatusLineNC + // 1 for StatusLine default + // Must to be in there simultaneously in case of table overflows in + // get_attr_entry() + ga_grow(&highlight_ga, 10); + hlcnt = highlight_ga.ga_len; + if (id_S == -1) { + // Make sure id_S is always valid to simplify code below. Use the last entry + memset(&HL_TABLE()[hlcnt + 9], 0, sizeof(HlGroup)); + id_S = hlcnt + 10; + } + for (int i = 0; i < 9; i++) { + snprintf(userhl, sizeof(userhl), "User%d", i + 1); + id = syn_name2id(userhl); + if (id == 0) { + highlight_user[i] = 0; + highlight_stlnc[i] = 0; + } else { + highlight_user[i] = syn_id2attr(id); + combine_stl_hlt(id, id_S, id_SNC, hlcnt, i, HLF_SNC, highlight_stlnc); + } + } + highlight_ga.ga_len = hlcnt; +} + +/// Handle command line completion for :highlight command. +void set_context_in_highlight_cmd(expand_T *xp, const char *arg) +{ + // Default: expand group names. + xp->xp_context = EXPAND_HIGHLIGHT; + xp->xp_pattern = (char_u *)arg; + include_link = 2; + include_default = 1; + + // (part of) subcommand already typed + if (*arg != NUL) { + const char *p = (const char *)skiptowhite((const char_u *)arg); + if (*p != NUL) { // Past "default" or group name. + include_default = 0; + if (strncmp("default", arg, (unsigned)(p - arg)) == 0) { + arg = (const char *)skipwhite((const char_u *)p); + xp->xp_pattern = (char_u *)arg; + p = (const char *)skiptowhite((const char_u *)arg); + } + if (*p != NUL) { // past group name + include_link = 0; + if (arg[1] == 'i' && arg[0] == 'N') { + highlight_list(); + } + if (strncmp("link", arg, (unsigned)(p - arg)) == 0 + || strncmp("clear", arg, (unsigned)(p - arg)) == 0) { + xp->xp_pattern = skipwhite((const char_u *)p); + p = (const char *)skiptowhite(xp->xp_pattern); + if (*p != NUL) { // Past first group name. + xp->xp_pattern = skipwhite((const char_u *)p); + p = (const char *)skiptowhite(xp->xp_pattern); + } + } + if (*p != NUL) { // Past group name(s). + xp->xp_context = EXPAND_NOTHING; + } + } + } + } +} + +/// List highlighting matches in a nice way. +static void highlight_list(void) +{ + int i; + + for (i = 10; --i >= 0;) { + highlight_list_two(i, HL_ATTR(HLF_D)); + } + for (i = 40; --i >= 0;) { + highlight_list_two(99, 0); + } +} + +static void highlight_list_two(int cnt, int attr) +{ + msg_puts_attr(&("N \bI \b! \b"[cnt / 11]), attr); + msg_clr_eos(); + ui_flush(); + os_delay(cnt == 99 ? 40L : (uint64_t)cnt * 50L, false); +} + +/// Function given to ExpandGeneric() to obtain the list of group names. +const char *get_highlight_name(expand_T *const xp, int idx) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + return get_highlight_name_ext(xp, idx, true); +} + +/// Obtain a highlight group name. +/// +/// @param skip_cleared if true don't return a cleared entry. +const char *get_highlight_name_ext(expand_T *xp, int idx, bool skip_cleared) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (idx < 0) { + return NULL; + } + + // Items are never removed from the table, skip the ones that were cleared. + if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared) { + return ""; + } + + if (idx == highlight_ga.ga_len && include_none != 0) { + return "none"; + } else if (idx == highlight_ga.ga_len + include_none + && include_default != 0) { + return "default"; + } else if (idx == highlight_ga.ga_len + include_none + include_default + && include_link != 0) { + return "link"; + } else if (idx == highlight_ga.ga_len + include_none + include_default + 1 + && include_link != 0) { + return "clear"; + } else if (idx >= highlight_ga.ga_len) { + return NULL; + } + return (const char *)HL_TABLE()[idx].sg_name; +} + +color_name_table_T color_name_table[] = { + // Colors from rgb.txt + { "AliceBlue", RGB_(0xf0, 0xf8, 0xff) }, + { "AntiqueWhite", RGB_(0xfa, 0xeb, 0xd7) }, + { "AntiqueWhite1", RGB_(0xff, 0xef, 0xdb) }, + { "AntiqueWhite2", RGB_(0xee, 0xdf, 0xcc) }, + { "AntiqueWhite3", RGB_(0xcd, 0xc0, 0xb0) }, + { "AntiqueWhite4", RGB_(0x8b, 0x83, 0x78) }, + { "Aqua", RGB_(0x00, 0xff, 0xff) }, + { "Aquamarine", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine1", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine2", RGB_(0x76, 0xee, 0xc6) }, + { "Aquamarine3", RGB_(0x66, 0xcd, 0xaa) }, + { "Aquamarine4", RGB_(0x45, 0x8b, 0x74) }, + { "Azure", RGB_(0xf0, 0xff, 0xff) }, + { "Azure1", RGB_(0xf0, 0xff, 0xff) }, + { "Azure2", RGB_(0xe0, 0xee, 0xee) }, + { "Azure3", RGB_(0xc1, 0xcd, 0xcd) }, + { "Azure4", RGB_(0x83, 0x8b, 0x8b) }, + { "Beige", RGB_(0xf5, 0xf5, 0xdc) }, + { "Bisque", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque1", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque2", RGB_(0xee, 0xd5, 0xb7) }, + { "Bisque3", RGB_(0xcd, 0xb7, 0x9e) }, + { "Bisque4", RGB_(0x8b, 0x7d, 0x6b) }, + { "Black", RGB_(0x00, 0x00, 0x00) }, + { "BlanchedAlmond", RGB_(0xff, 0xeb, 0xcd) }, + { "Blue", RGB_(0x00, 0x00, 0xff) }, + { "Blue1", RGB_(0x0, 0x0, 0xff) }, + { "Blue2", RGB_(0x0, 0x0, 0xee) }, + { "Blue3", RGB_(0x0, 0x0, 0xcd) }, + { "Blue4", RGB_(0x0, 0x0, 0x8b) }, + { "BlueViolet", RGB_(0x8a, 0x2b, 0xe2) }, + { "Brown", RGB_(0xa5, 0x2a, 0x2a) }, + { "Brown1", RGB_(0xff, 0x40, 0x40) }, + { "Brown2", RGB_(0xee, 0x3b, 0x3b) }, + { "Brown3", RGB_(0xcd, 0x33, 0x33) }, + { "Brown4", RGB_(0x8b, 0x23, 0x23) }, + { "BurlyWood", RGB_(0xde, 0xb8, 0x87) }, + { "Burlywood1", RGB_(0xff, 0xd3, 0x9b) }, + { "Burlywood2", RGB_(0xee, 0xc5, 0x91) }, + { "Burlywood3", RGB_(0xcd, 0xaa, 0x7d) }, + { "Burlywood4", RGB_(0x8b, 0x73, 0x55) }, + { "CadetBlue", RGB_(0x5f, 0x9e, 0xa0) }, + { "CadetBlue1", RGB_(0x98, 0xf5, 0xff) }, + { "CadetBlue2", RGB_(0x8e, 0xe5, 0xee) }, + { "CadetBlue3", RGB_(0x7a, 0xc5, 0xcd) }, + { "CadetBlue4", RGB_(0x53, 0x86, 0x8b) }, + { "ChartReuse", RGB_(0x7f, 0xff, 0x00) }, + { "Chartreuse1", RGB_(0x7f, 0xff, 0x0) }, + { "Chartreuse2", RGB_(0x76, 0xee, 0x0) }, + { "Chartreuse3", RGB_(0x66, 0xcd, 0x0) }, + { "Chartreuse4", RGB_(0x45, 0x8b, 0x0) }, + { "Chocolate", RGB_(0xd2, 0x69, 0x1e) }, + { "Chocolate1", RGB_(0xff, 0x7f, 0x24) }, + { "Chocolate2", RGB_(0xee, 0x76, 0x21) }, + { "Chocolate3", RGB_(0xcd, 0x66, 0x1d) }, + { "Chocolate4", RGB_(0x8b, 0x45, 0x13) }, + { "Coral", RGB_(0xff, 0x7f, 0x50) }, + { "Coral1", RGB_(0xff, 0x72, 0x56) }, + { "Coral2", RGB_(0xee, 0x6a, 0x50) }, + { "Coral3", RGB_(0xcd, 0x5b, 0x45) }, + { "Coral4", RGB_(0x8b, 0x3e, 0x2f) }, + { "CornFlowerBlue", RGB_(0x64, 0x95, 0xed) }, + { "Cornsilk", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk1", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk2", RGB_(0xee, 0xe8, 0xcd) }, + { "Cornsilk3", RGB_(0xcd, 0xc8, 0xb1) }, + { "Cornsilk4", RGB_(0x8b, 0x88, 0x78) }, + { "Crimson", RGB_(0xdc, 0x14, 0x3c) }, + { "Cyan", RGB_(0x00, 0xff, 0xff) }, + { "Cyan1", RGB_(0x0, 0xff, 0xff) }, + { "Cyan2", RGB_(0x0, 0xee, 0xee) }, + { "Cyan3", RGB_(0x0, 0xcd, 0xcd) }, + { "Cyan4", RGB_(0x0, 0x8b, 0x8b) }, + { "DarkBlue", RGB_(0x00, 0x00, 0x8b) }, + { "DarkCyan", RGB_(0x00, 0x8b, 0x8b) }, + { "DarkGoldenRod", RGB_(0xb8, 0x86, 0x0b) }, + { "DarkGoldenrod1", RGB_(0xff, 0xb9, 0xf) }, + { "DarkGoldenrod2", RGB_(0xee, 0xad, 0xe) }, + { "DarkGoldenrod3", RGB_(0xcd, 0x95, 0xc) }, + { "DarkGoldenrod4", RGB_(0x8b, 0x65, 0x8) }, + { "DarkGray", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkGreen", RGB_(0x00, 0x64, 0x00) }, + { "DarkGrey", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkKhaki", RGB_(0xbd, 0xb7, 0x6b) }, + { "DarkMagenta", RGB_(0x8b, 0x00, 0x8b) }, + { "DarkOliveGreen", RGB_(0x55, 0x6b, 0x2f) }, + { "DarkOliveGreen1", RGB_(0xca, 0xff, 0x70) }, + { "DarkOliveGreen2", RGB_(0xbc, 0xee, 0x68) }, + { "DarkOliveGreen3", RGB_(0xa2, 0xcd, 0x5a) }, + { "DarkOliveGreen4", RGB_(0x6e, 0x8b, 0x3d) }, + { "DarkOrange", RGB_(0xff, 0x8c, 0x00) }, + { "DarkOrange1", RGB_(0xff, 0x7f, 0x0) }, + { "DarkOrange2", RGB_(0xee, 0x76, 0x0) }, + { "DarkOrange3", RGB_(0xcd, 0x66, 0x0) }, + { "DarkOrange4", RGB_(0x8b, 0x45, 0x0) }, + { "DarkOrchid", RGB_(0x99, 0x32, 0xcc) }, + { "DarkOrchid1", RGB_(0xbf, 0x3e, 0xff) }, + { "DarkOrchid2", RGB_(0xb2, 0x3a, 0xee) }, + { "DarkOrchid3", RGB_(0x9a, 0x32, 0xcd) }, + { "DarkOrchid4", RGB_(0x68, 0x22, 0x8b) }, + { "DarkRed", RGB_(0x8b, 0x00, 0x00) }, + { "DarkSalmon", RGB_(0xe9, 0x96, 0x7a) }, + { "DarkSeaGreen", RGB_(0x8f, 0xbc, 0x8f) }, + { "DarkSeaGreen1", RGB_(0xc1, 0xff, 0xc1) }, + { "DarkSeaGreen2", RGB_(0xb4, 0xee, 0xb4) }, + { "DarkSeaGreen3", RGB_(0x9b, 0xcd, 0x9b) }, + { "DarkSeaGreen4", RGB_(0x69, 0x8b, 0x69) }, + { "DarkSlateBlue", RGB_(0x48, 0x3d, 0x8b) }, + { "DarkSlateGray", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkSlateGray1", RGB_(0x97, 0xff, 0xff) }, + { "DarkSlateGray2", RGB_(0x8d, 0xee, 0xee) }, + { "DarkSlateGray3", RGB_(0x79, 0xcd, 0xcd) }, + { "DarkSlateGray4", RGB_(0x52, 0x8b, 0x8b) }, + { "DarkSlateGrey", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkTurquoise", RGB_(0x00, 0xce, 0xd1) }, + { "DarkViolet", RGB_(0x94, 0x00, 0xd3) }, + { "DarkYellow", RGB_(0xbb, 0xbb, 0x00) }, + { "DeepPink", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink1", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink2", RGB_(0xee, 0x12, 0x89) }, + { "DeepPink3", RGB_(0xcd, 0x10, 0x76) }, + { "DeepPink4", RGB_(0x8b, 0xa, 0x50) }, + { "DeepSkyBlue", RGB_(0x00, 0xbf, 0xff) }, + { "DeepSkyBlue1", RGB_(0x0, 0xbf, 0xff) }, + { "DeepSkyBlue2", RGB_(0x0, 0xb2, 0xee) }, + { "DeepSkyBlue3", RGB_(0x0, 0x9a, 0xcd) }, + { "DeepSkyBlue4", RGB_(0x0, 0x68, 0x8b) }, + { "DimGray", RGB_(0x69, 0x69, 0x69) }, + { "DimGrey", RGB_(0x69, 0x69, 0x69) }, + { "DodgerBlue", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue1", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue2", RGB_(0x1c, 0x86, 0xee) }, + { "DodgerBlue3", RGB_(0x18, 0x74, 0xcd) }, + { "DodgerBlue4", RGB_(0x10, 0x4e, 0x8b) }, + { "Firebrick", RGB_(0xb2, 0x22, 0x22) }, + { "Firebrick1", RGB_(0xff, 0x30, 0x30) }, + { "Firebrick2", RGB_(0xee, 0x2c, 0x2c) }, + { "Firebrick3", RGB_(0xcd, 0x26, 0x26) }, + { "Firebrick4", RGB_(0x8b, 0x1a, 0x1a) }, + { "FloralWhite", RGB_(0xff, 0xfa, 0xf0) }, + { "ForestGreen", RGB_(0x22, 0x8b, 0x22) }, + { "Fuchsia", RGB_(0xff, 0x00, 0xff) }, + { "Gainsboro", RGB_(0xdc, 0xdc, 0xdc) }, + { "GhostWhite", RGB_(0xf8, 0xf8, 0xff) }, + { "Gold", RGB_(0xff, 0xd7, 0x00) }, + { "Gold1", RGB_(0xff, 0xd7, 0x0) }, + { "Gold2", RGB_(0xee, 0xc9, 0x0) }, + { "Gold3", RGB_(0xcd, 0xad, 0x0) }, + { "Gold4", RGB_(0x8b, 0x75, 0x0) }, + { "GoldenRod", RGB_(0xda, 0xa5, 0x20) }, + { "Goldenrod1", RGB_(0xff, 0xc1, 0x25) }, + { "Goldenrod2", RGB_(0xee, 0xb4, 0x22) }, + { "Goldenrod3", RGB_(0xcd, 0x9b, 0x1d) }, + { "Goldenrod4", RGB_(0x8b, 0x69, 0x14) }, + { "Gray", RGB_(0x80, 0x80, 0x80) }, + { "Gray0", RGB_(0x0, 0x0, 0x0) }, + { "Gray1", RGB_(0x3, 0x3, 0x3) }, + { "Gray10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Gray100", RGB_(0xff, 0xff, 0xff) }, + { "Gray11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Gray12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Gray13", RGB_(0x21, 0x21, 0x21) }, + { "Gray14", RGB_(0x24, 0x24, 0x24) }, + { "Gray15", RGB_(0x26, 0x26, 0x26) }, + { "Gray16", RGB_(0x29, 0x29, 0x29) }, + { "Gray17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Gray18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Gray19", RGB_(0x30, 0x30, 0x30) }, + { "Gray2", RGB_(0x5, 0x5, 0x5) }, + { "Gray20", RGB_(0x33, 0x33, 0x33) }, + { "Gray21", RGB_(0x36, 0x36, 0x36) }, + { "Gray22", RGB_(0x38, 0x38, 0x38) }, + { "Gray23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Gray24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Gray25", RGB_(0x40, 0x40, 0x40) }, + { "Gray26", RGB_(0x42, 0x42, 0x42) }, + { "Gray27", RGB_(0x45, 0x45, 0x45) }, + { "Gray28", RGB_(0x47, 0x47, 0x47) }, + { "Gray29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Gray3", RGB_(0x8, 0x8, 0x8) }, + { "Gray30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Gray31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Gray32", RGB_(0x52, 0x52, 0x52) }, + { "Gray33", RGB_(0x54, 0x54, 0x54) }, + { "Gray34", RGB_(0x57, 0x57, 0x57) }, + { "Gray35", RGB_(0x59, 0x59, 0x59) }, + { "Gray36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Gray37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Gray38", RGB_(0x61, 0x61, 0x61) }, + { "Gray39", RGB_(0x63, 0x63, 0x63) }, + { "Gray4", RGB_(0xa, 0xa, 0xa) }, + { "Gray40", RGB_(0x66, 0x66, 0x66) }, + { "Gray41", RGB_(0x69, 0x69, 0x69) }, + { "Gray42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Gray43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Gray44", RGB_(0x70, 0x70, 0x70) }, + { "Gray45", RGB_(0x73, 0x73, 0x73) }, + { "Gray46", RGB_(0x75, 0x75, 0x75) }, + { "Gray47", RGB_(0x78, 0x78, 0x78) }, + { "Gray48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Gray49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Gray5", RGB_(0xd, 0xd, 0xd) }, + { "Gray50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Gray51", RGB_(0x82, 0x82, 0x82) }, + { "Gray52", RGB_(0x85, 0x85, 0x85) }, + { "Gray53", RGB_(0x87, 0x87, 0x87) }, + { "Gray54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Gray55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Gray56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Gray57", RGB_(0x91, 0x91, 0x91) }, + { "Gray58", RGB_(0x94, 0x94, 0x94) }, + { "Gray59", RGB_(0x96, 0x96, 0x96) }, + { "Gray6", RGB_(0xf, 0xf, 0xf) }, + { "Gray60", RGB_(0x99, 0x99, 0x99) }, + { "Gray61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Gray62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Gray63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Gray64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Gray65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Gray66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Gray67", RGB_(0xab, 0xab, 0xab) }, + { "Gray68", RGB_(0xad, 0xad, 0xad) }, + { "Gray69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Gray7", RGB_(0x12, 0x12, 0x12) }, + { "Gray70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Gray71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Gray72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Gray73", RGB_(0xba, 0xba, 0xba) }, + { "Gray74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Gray75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Gray76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Gray77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Gray78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Gray79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Gray8", RGB_(0x14, 0x14, 0x14) }, + { "Gray80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Gray81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Gray82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Gray83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Gray84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Gray85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Gray86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Gray87", RGB_(0xde, 0xde, 0xde) }, + { "Gray88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Gray89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Gray9", RGB_(0x17, 0x17, 0x17) }, + { "Gray90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Gray91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Gray92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Gray93", RGB_(0xed, 0xed, 0xed) }, + { "Gray94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Gray95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Gray96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Gray97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Gray98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Gray99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Green", RGB_(0x00, 0x80, 0x00) }, + { "Green1", RGB_(0x0, 0xff, 0x0) }, + { "Green2", RGB_(0x0, 0xee, 0x0) }, + { "Green3", RGB_(0x0, 0xcd, 0x0) }, + { "Green4", RGB_(0x0, 0x8b, 0x0) }, + { "GreenYellow", RGB_(0xad, 0xff, 0x2f) }, + { "Grey", RGB_(0x80, 0x80, 0x80) }, + { "Grey0", RGB_(0x0, 0x0, 0x0) }, + { "Grey1", RGB_(0x3, 0x3, 0x3) }, + { "Grey10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Grey100", RGB_(0xff, 0xff, 0xff) }, + { "Grey11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Grey12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Grey13", RGB_(0x21, 0x21, 0x21) }, + { "Grey14", RGB_(0x24, 0x24, 0x24) }, + { "Grey15", RGB_(0x26, 0x26, 0x26) }, + { "Grey16", RGB_(0x29, 0x29, 0x29) }, + { "Grey17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Grey18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Grey19", RGB_(0x30, 0x30, 0x30) }, + { "Grey2", RGB_(0x5, 0x5, 0x5) }, + { "Grey20", RGB_(0x33, 0x33, 0x33) }, + { "Grey21", RGB_(0x36, 0x36, 0x36) }, + { "Grey22", RGB_(0x38, 0x38, 0x38) }, + { "Grey23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Grey24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Grey25", RGB_(0x40, 0x40, 0x40) }, + { "Grey26", RGB_(0x42, 0x42, 0x42) }, + { "Grey27", RGB_(0x45, 0x45, 0x45) }, + { "Grey28", RGB_(0x47, 0x47, 0x47) }, + { "Grey29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Grey3", RGB_(0x8, 0x8, 0x8) }, + { "Grey30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Grey31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Grey32", RGB_(0x52, 0x52, 0x52) }, + { "Grey33", RGB_(0x54, 0x54, 0x54) }, + { "Grey34", RGB_(0x57, 0x57, 0x57) }, + { "Grey35", RGB_(0x59, 0x59, 0x59) }, + { "Grey36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Grey37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Grey38", RGB_(0x61, 0x61, 0x61) }, + { "Grey39", RGB_(0x63, 0x63, 0x63) }, + { "Grey4", RGB_(0xa, 0xa, 0xa) }, + { "Grey40", RGB_(0x66, 0x66, 0x66) }, + { "Grey41", RGB_(0x69, 0x69, 0x69) }, + { "Grey42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Grey43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Grey44", RGB_(0x70, 0x70, 0x70) }, + { "Grey45", RGB_(0x73, 0x73, 0x73) }, + { "Grey46", RGB_(0x75, 0x75, 0x75) }, + { "Grey47", RGB_(0x78, 0x78, 0x78) }, + { "Grey48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Grey49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Grey5", RGB_(0xd, 0xd, 0xd) }, + { "Grey50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Grey51", RGB_(0x82, 0x82, 0x82) }, + { "Grey52", RGB_(0x85, 0x85, 0x85) }, + { "Grey53", RGB_(0x87, 0x87, 0x87) }, + { "Grey54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Grey55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Grey56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Grey57", RGB_(0x91, 0x91, 0x91) }, + { "Grey58", RGB_(0x94, 0x94, 0x94) }, + { "Grey59", RGB_(0x96, 0x96, 0x96) }, + { "Grey6", RGB_(0xf, 0xf, 0xf) }, + { "Grey60", RGB_(0x99, 0x99, 0x99) }, + { "Grey61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Grey62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Grey63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Grey64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Grey65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Grey66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Grey67", RGB_(0xab, 0xab, 0xab) }, + { "Grey68", RGB_(0xad, 0xad, 0xad) }, + { "Grey69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Grey7", RGB_(0x12, 0x12, 0x12) }, + { "Grey70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Grey71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Grey72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Grey73", RGB_(0xba, 0xba, 0xba) }, + { "Grey74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Grey75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Grey76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Grey77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Grey78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Grey79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Grey8", RGB_(0x14, 0x14, 0x14) }, + { "Grey80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Grey81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Grey82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Grey83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Grey84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Grey85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Grey86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Grey87", RGB_(0xde, 0xde, 0xde) }, + { "Grey88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Grey89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Grey9", RGB_(0x17, 0x17, 0x17) }, + { "Grey90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Grey91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Grey92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Grey93", RGB_(0xed, 0xed, 0xed) }, + { "Grey94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Grey95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Grey96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Grey97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Grey98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Grey99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Honeydew", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew1", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew2", RGB_(0xe0, 0xee, 0xe0) }, + { "Honeydew3", RGB_(0xc1, 0xcd, 0xc1) }, + { "Honeydew4", RGB_(0x83, 0x8b, 0x83) }, + { "HotPink", RGB_(0xff, 0x69, 0xb4) }, + { "HotPink1", RGB_(0xff, 0x6e, 0xb4) }, + { "HotPink2", RGB_(0xee, 0x6a, 0xa7) }, + { "HotPink3", RGB_(0xcd, 0x60, 0x90) }, + { "HotPink4", RGB_(0x8b, 0x3a, 0x62) }, + { "IndianRed", RGB_(0xcd, 0x5c, 0x5c) }, + { "IndianRed1", RGB_(0xff, 0x6a, 0x6a) }, + { "IndianRed2", RGB_(0xee, 0x63, 0x63) }, + { "IndianRed3", RGB_(0xcd, 0x55, 0x55) }, + { "IndianRed4", RGB_(0x8b, 0x3a, 0x3a) }, + { "Indigo", RGB_(0x4b, 0x00, 0x82) }, + { "Ivory", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory1", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory2", RGB_(0xee, 0xee, 0xe0) }, + { "Ivory3", RGB_(0xcd, 0xcd, 0xc1) }, + { "Ivory4", RGB_(0x8b, 0x8b, 0x83) }, + { "Khaki", RGB_(0xf0, 0xe6, 0x8c) }, + { "Khaki1", RGB_(0xff, 0xf6, 0x8f) }, + { "Khaki2", RGB_(0xee, 0xe6, 0x85) }, + { "Khaki3", RGB_(0xcd, 0xc6, 0x73) }, + { "Khaki4", RGB_(0x8b, 0x86, 0x4e) }, + { "Lavender", RGB_(0xe6, 0xe6, 0xfa) }, + { "LavenderBlush", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush1", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush2", RGB_(0xee, 0xe0, 0xe5) }, + { "LavenderBlush3", RGB_(0xcd, 0xc1, 0xc5) }, + { "LavenderBlush4", RGB_(0x8b, 0x83, 0x86) }, + { "LawnGreen", RGB_(0x7c, 0xfc, 0x00) }, + { "LemonChiffon", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon1", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon2", RGB_(0xee, 0xe9, 0xbf) }, + { "LemonChiffon3", RGB_(0xcd, 0xc9, 0xa5) }, + { "LemonChiffon4", RGB_(0x8b, 0x89, 0x70) }, + { "LightBlue", RGB_(0xad, 0xd8, 0xe6) }, + { "LightBlue1", RGB_(0xbf, 0xef, 0xff) }, + { "LightBlue2", RGB_(0xb2, 0xdf, 0xee) }, + { "LightBlue3", RGB_(0x9a, 0xc0, 0xcd) }, + { "LightBlue4", RGB_(0x68, 0x83, 0x8b) }, + { "LightCoral", RGB_(0xf0, 0x80, 0x80) }, + { "LightCyan", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan1", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan2", RGB_(0xd1, 0xee, 0xee) }, + { "LightCyan3", RGB_(0xb4, 0xcd, 0xcd) }, + { "LightCyan4", RGB_(0x7a, 0x8b, 0x8b) }, + { "LightGoldenrod", RGB_(0xee, 0xdd, 0x82) }, + { "LightGoldenrod1", RGB_(0xff, 0xec, 0x8b) }, + { "LightGoldenrod2", RGB_(0xee, 0xdc, 0x82) }, + { "LightGoldenrod3", RGB_(0xcd, 0xbe, 0x70) }, + { "LightGoldenrod4", RGB_(0x8b, 0x81, 0x4c) }, + { "LightGoldenRodYellow", RGB_(0xfa, 0xfa, 0xd2) }, + { "LightGray", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightGreen", RGB_(0x90, 0xee, 0x90) }, + { "LightGrey", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightMagenta", RGB_(0xff, 0xbb, 0xff) }, + { "LightPink", RGB_(0xff, 0xb6, 0xc1) }, + { "LightPink1", RGB_(0xff, 0xae, 0xb9) }, + { "LightPink2", RGB_(0xee, 0xa2, 0xad) }, + { "LightPink3", RGB_(0xcd, 0x8c, 0x95) }, + { "LightPink4", RGB_(0x8b, 0x5f, 0x65) }, + { "LightRed", RGB_(0xff, 0xbb, 0xbb) }, + { "LightSalmon", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon1", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon2", RGB_(0xee, 0x95, 0x72) }, + { "LightSalmon3", RGB_(0xcd, 0x81, 0x62) }, + { "LightSalmon4", RGB_(0x8b, 0x57, 0x42) }, + { "LightSeaGreen", RGB_(0x20, 0xb2, 0xaa) }, + { "LightSkyBlue", RGB_(0x87, 0xce, 0xfa) }, + { "LightSkyBlue1", RGB_(0xb0, 0xe2, 0xff) }, + { "LightSkyBlue2", RGB_(0xa4, 0xd3, 0xee) }, + { "LightSkyBlue3", RGB_(0x8d, 0xb6, 0xcd) }, + { "LightSkyBlue4", RGB_(0x60, 0x7b, 0x8b) }, + { "LightSlateBlue", RGB_(0x84, 0x70, 0xff) }, + { "LightSlateGray", RGB_(0x77, 0x88, 0x99) }, + { "LightSlateGrey", RGB_(0x77, 0x88, 0x99) }, + { "LightSteelBlue", RGB_(0xb0, 0xc4, 0xde) }, + { "LightSteelBlue1", RGB_(0xca, 0xe1, 0xff) }, + { "LightSteelBlue2", RGB_(0xbc, 0xd2, 0xee) }, + { "LightSteelBlue3", RGB_(0xa2, 0xb5, 0xcd) }, + { "LightSteelBlue4", RGB_(0x6e, 0x7b, 0x8b) }, + { "LightYellow", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow1", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow2", RGB_(0xee, 0xee, 0xd1) }, + { "LightYellow3", RGB_(0xcd, 0xcd, 0xb4) }, + { "LightYellow4", RGB_(0x8b, 0x8b, 0x7a) }, + { "Lime", RGB_(0x00, 0xff, 0x00) }, + { "LimeGreen", RGB_(0x32, 0xcd, 0x32) }, + { "Linen", RGB_(0xfa, 0xf0, 0xe6) }, + { "Magenta", RGB_(0xff, 0x00, 0xff) }, + { "Magenta1", RGB_(0xff, 0x0, 0xff) }, + { "Magenta2", RGB_(0xee, 0x0, 0xee) }, + { "Magenta3", RGB_(0xcd, 0x0, 0xcd) }, + { "Magenta4", RGB_(0x8b, 0x0, 0x8b) }, + { "Maroon", RGB_(0x80, 0x00, 0x00) }, + { "Maroon1", RGB_(0xff, 0x34, 0xb3) }, + { "Maroon2", RGB_(0xee, 0x30, 0xa7) }, + { "Maroon3", RGB_(0xcd, 0x29, 0x90) }, + { "Maroon4", RGB_(0x8b, 0x1c, 0x62) }, + { "MediumAquamarine", RGB_(0x66, 0xcd, 0xaa) }, + { "MediumBlue", RGB_(0x00, 0x00, 0xcd) }, + { "MediumOrchid", RGB_(0xba, 0x55, 0xd3) }, + { "MediumOrchid1", RGB_(0xe0, 0x66, 0xff) }, + { "MediumOrchid2", RGB_(0xd1, 0x5f, 0xee) }, + { "MediumOrchid3", RGB_(0xb4, 0x52, 0xcd) }, + { "MediumOrchid4", RGB_(0x7a, 0x37, 0x8b) }, + { "MediumPurple", RGB_(0x93, 0x70, 0xdb) }, + { "MediumPurple1", RGB_(0xab, 0x82, 0xff) }, + { "MediumPurple2", RGB_(0x9f, 0x79, 0xee) }, + { "MediumPurple3", RGB_(0x89, 0x68, 0xcd) }, + { "MediumPurple4", RGB_(0x5d, 0x47, 0x8b) }, + { "MediumSeaGreen", RGB_(0x3c, 0xb3, 0x71) }, + { "MediumSlateBlue", RGB_(0x7b, 0x68, 0xee) }, + { "MediumSpringGreen", RGB_(0x00, 0xfa, 0x9a) }, + { "MediumTurquoise", RGB_(0x48, 0xd1, 0xcc) }, + { "MediumVioletRed", RGB_(0xc7, 0x15, 0x85) }, + { "MidnightBlue", RGB_(0x19, 0x19, 0x70) }, + { "MintCream", RGB_(0xf5, 0xff, 0xfa) }, + { "MistyRose", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose1", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose2", RGB_(0xee, 0xd5, 0xd2) }, + { "MistyRose3", RGB_(0xcd, 0xb7, 0xb5) }, + { "MistyRose4", RGB_(0x8b, 0x7d, 0x7b) }, + { "Moccasin", RGB_(0xff, 0xe4, 0xb5) }, + { "NavajoWhite", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite1", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite2", RGB_(0xee, 0xcf, 0xa1) }, + { "NavajoWhite3", RGB_(0xcd, 0xb3, 0x8b) }, + { "NavajoWhite4", RGB_(0x8b, 0x79, 0x5e) }, + { "Navy", RGB_(0x00, 0x00, 0x80) }, + { "NavyBlue", RGB_(0x0, 0x0, 0x80) }, + { "OldLace", RGB_(0xfd, 0xf5, 0xe6) }, + { "Olive", RGB_(0x80, 0x80, 0x00) }, + { "OliveDrab", RGB_(0x6b, 0x8e, 0x23) }, + { "OliveDrab1", RGB_(0xc0, 0xff, 0x3e) }, + { "OliveDrab2", RGB_(0xb3, 0xee, 0x3a) }, + { "OliveDrab3", RGB_(0x9a, 0xcd, 0x32) }, + { "OliveDrab4", RGB_(0x69, 0x8b, 0x22) }, + { "Orange", RGB_(0xff, 0xa5, 0x00) }, + { "Orange1", RGB_(0xff, 0xa5, 0x0) }, + { "Orange2", RGB_(0xee, 0x9a, 0x0) }, + { "Orange3", RGB_(0xcd, 0x85, 0x0) }, + { "Orange4", RGB_(0x8b, 0x5a, 0x0) }, + { "OrangeRed", RGB_(0xff, 0x45, 0x00) }, + { "OrangeRed1", RGB_(0xff, 0x45, 0x0) }, + { "OrangeRed2", RGB_(0xee, 0x40, 0x0) }, + { "OrangeRed3", RGB_(0xcd, 0x37, 0x0) }, + { "OrangeRed4", RGB_(0x8b, 0x25, 0x0) }, + { "Orchid", RGB_(0xda, 0x70, 0xd6) }, + { "Orchid1", RGB_(0xff, 0x83, 0xfa) }, + { "Orchid2", RGB_(0xee, 0x7a, 0xe9) }, + { "Orchid3", RGB_(0xcd, 0x69, 0xc9) }, + { "Orchid4", RGB_(0x8b, 0x47, 0x89) }, + { "PaleGoldenRod", RGB_(0xee, 0xe8, 0xaa) }, + { "PaleGreen", RGB_(0x98, 0xfb, 0x98) }, + { "PaleGreen1", RGB_(0x9a, 0xff, 0x9a) }, + { "PaleGreen2", RGB_(0x90, 0xee, 0x90) }, + { "PaleGreen3", RGB_(0x7c, 0xcd, 0x7c) }, + { "PaleGreen4", RGB_(0x54, 0x8b, 0x54) }, + { "PaleTurquoise", RGB_(0xaf, 0xee, 0xee) }, + { "PaleTurquoise1", RGB_(0xbb, 0xff, 0xff) }, + { "PaleTurquoise2", RGB_(0xae, 0xee, 0xee) }, + { "PaleTurquoise3", RGB_(0x96, 0xcd, 0xcd) }, + { "PaleTurquoise4", RGB_(0x66, 0x8b, 0x8b) }, + { "PaleVioletRed", RGB_(0xdb, 0x70, 0x93) }, + { "PaleVioletRed1", RGB_(0xff, 0x82, 0xab) }, + { "PaleVioletRed2", RGB_(0xee, 0x79, 0x9f) }, + { "PaleVioletRed3", RGB_(0xcd, 0x68, 0x89) }, + { "PaleVioletRed4", RGB_(0x8b, 0x47, 0x5d) }, + { "PapayaWhip", RGB_(0xff, 0xef, 0xd5) }, + { "PeachPuff", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff1", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff2", RGB_(0xee, 0xcb, 0xad) }, + { "PeachPuff3", RGB_(0xcd, 0xaf, 0x95) }, + { "PeachPuff4", RGB_(0x8b, 0x77, 0x65) }, + { "Peru", RGB_(0xcd, 0x85, 0x3f) }, + { "Pink", RGB_(0xff, 0xc0, 0xcb) }, + { "Pink1", RGB_(0xff, 0xb5, 0xc5) }, + { "Pink2", RGB_(0xee, 0xa9, 0xb8) }, + { "Pink3", RGB_(0xcd, 0x91, 0x9e) }, + { "Pink4", RGB_(0x8b, 0x63, 0x6c) }, + { "Plum", RGB_(0xdd, 0xa0, 0xdd) }, + { "Plum1", RGB_(0xff, 0xbb, 0xff) }, + { "Plum2", RGB_(0xee, 0xae, 0xee) }, + { "Plum3", RGB_(0xcd, 0x96, 0xcd) }, + { "Plum4", RGB_(0x8b, 0x66, 0x8b) }, + { "PowderBlue", RGB_(0xb0, 0xe0, 0xe6) }, + { "Purple", RGB_(0x80, 0x00, 0x80) }, + { "Purple1", RGB_(0x9b, 0x30, 0xff) }, + { "Purple2", RGB_(0x91, 0x2c, 0xee) }, + { "Purple3", RGB_(0x7d, 0x26, 0xcd) }, + { "Purple4", RGB_(0x55, 0x1a, 0x8b) }, + { "RebeccaPurple", RGB_(0x66, 0x33, 0x99) }, + { "Red", RGB_(0xff, 0x00, 0x00) }, + { "Red1", RGB_(0xff, 0x0, 0x0) }, + { "Red2", RGB_(0xee, 0x0, 0x0) }, + { "Red3", RGB_(0xcd, 0x0, 0x0) }, + { "Red4", RGB_(0x8b, 0x0, 0x0) }, + { "RosyBrown", RGB_(0xbc, 0x8f, 0x8f) }, + { "RosyBrown1", RGB_(0xff, 0xc1, 0xc1) }, + { "RosyBrown2", RGB_(0xee, 0xb4, 0xb4) }, + { "RosyBrown3", RGB_(0xcd, 0x9b, 0x9b) }, + { "RosyBrown4", RGB_(0x8b, 0x69, 0x69) }, + { "RoyalBlue", RGB_(0x41, 0x69, 0xe1) }, + { "RoyalBlue1", RGB_(0x48, 0x76, 0xff) }, + { "RoyalBlue2", RGB_(0x43, 0x6e, 0xee) }, + { "RoyalBlue3", RGB_(0x3a, 0x5f, 0xcd) }, + { "RoyalBlue4", RGB_(0x27, 0x40, 0x8b) }, + { "SaddleBrown", RGB_(0x8b, 0x45, 0x13) }, + { "Salmon", RGB_(0xfa, 0x80, 0x72) }, + { "Salmon1", RGB_(0xff, 0x8c, 0x69) }, + { "Salmon2", RGB_(0xee, 0x82, 0x62) }, + { "Salmon3", RGB_(0xcd, 0x70, 0x54) }, + { "Salmon4", RGB_(0x8b, 0x4c, 0x39) }, + { "SandyBrown", RGB_(0xf4, 0xa4, 0x60) }, + { "SeaGreen", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaGreen1", RGB_(0x54, 0xff, 0x9f) }, + { "SeaGreen2", RGB_(0x4e, 0xee, 0x94) }, + { "SeaGreen3", RGB_(0x43, 0xcd, 0x80) }, + { "SeaGreen4", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaShell", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell1", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell2", RGB_(0xee, 0xe5, 0xde) }, + { "Seashell3", RGB_(0xcd, 0xc5, 0xbf) }, + { "Seashell4", RGB_(0x8b, 0x86, 0x82) }, + { "Sienna", RGB_(0xa0, 0x52, 0x2d) }, + { "Sienna1", RGB_(0xff, 0x82, 0x47) }, + { "Sienna2", RGB_(0xee, 0x79, 0x42) }, + { "Sienna3", RGB_(0xcd, 0x68, 0x39) }, + { "Sienna4", RGB_(0x8b, 0x47, 0x26) }, + { "Silver", RGB_(0xc0, 0xc0, 0xc0) }, + { "SkyBlue", RGB_(0x87, 0xce, 0xeb) }, + { "SkyBlue1", RGB_(0x87, 0xce, 0xff) }, + { "SkyBlue2", RGB_(0x7e, 0xc0, 0xee) }, + { "SkyBlue3", RGB_(0x6c, 0xa6, 0xcd) }, + { "SkyBlue4", RGB_(0x4a, 0x70, 0x8b) }, + { "SlateBlue", RGB_(0x6a, 0x5a, 0xcd) }, + { "SlateBlue1", RGB_(0x83, 0x6f, 0xff) }, + { "SlateBlue2", RGB_(0x7a, 0x67, 0xee) }, + { "SlateBlue3", RGB_(0x69, 0x59, 0xcd) }, + { "SlateBlue4", RGB_(0x47, 0x3c, 0x8b) }, + { "SlateGray", RGB_(0x70, 0x80, 0x90) }, + { "SlateGray1", RGB_(0xc6, 0xe2, 0xff) }, + { "SlateGray2", RGB_(0xb9, 0xd3, 0xee) }, + { "SlateGray3", RGB_(0x9f, 0xb6, 0xcd) }, + { "SlateGray4", RGB_(0x6c, 0x7b, 0x8b) }, + { "SlateGrey", RGB_(0x70, 0x80, 0x90) }, + { "Snow", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow1", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow2", RGB_(0xee, 0xe9, 0xe9) }, + { "Snow3", RGB_(0xcd, 0xc9, 0xc9) }, + { "Snow4", RGB_(0x8b, 0x89, 0x89) }, + { "SpringGreen", RGB_(0x00, 0xff, 0x7f) }, + { "SpringGreen1", RGB_(0x0, 0xff, 0x7f) }, + { "SpringGreen2", RGB_(0x0, 0xee, 0x76) }, + { "SpringGreen3", RGB_(0x0, 0xcd, 0x66) }, + { "SpringGreen4", RGB_(0x0, 0x8b, 0x45) }, + { "SteelBlue", RGB_(0x46, 0x82, 0xb4) }, + { "SteelBlue1", RGB_(0x63, 0xb8, 0xff) }, + { "SteelBlue2", RGB_(0x5c, 0xac, 0xee) }, + { "SteelBlue3", RGB_(0x4f, 0x94, 0xcd) }, + { "SteelBlue4", RGB_(0x36, 0x64, 0x8b) }, + { "Tan", RGB_(0xd2, 0xb4, 0x8c) }, + { "Tan1", RGB_(0xff, 0xa5, 0x4f) }, + { "Tan2", RGB_(0xee, 0x9a, 0x49) }, + { "Tan3", RGB_(0xcd, 0x85, 0x3f) }, + { "Tan4", RGB_(0x8b, 0x5a, 0x2b) }, + { "Teal", RGB_(0x00, 0x80, 0x80) }, + { "Thistle", RGB_(0xd8, 0xbf, 0xd8) }, + { "Thistle1", RGB_(0xff, 0xe1, 0xff) }, + { "Thistle2", RGB_(0xee, 0xd2, 0xee) }, + { "Thistle3", RGB_(0xcd, 0xb5, 0xcd) }, + { "Thistle4", RGB_(0x8b, 0x7b, 0x8b) }, + { "Tomato", RGB_(0xff, 0x63, 0x47) }, + { "Tomato1", RGB_(0xff, 0x63, 0x47) }, + { "Tomato2", RGB_(0xee, 0x5c, 0x42) }, + { "Tomato3", RGB_(0xcd, 0x4f, 0x39) }, + { "Tomato4", RGB_(0x8b, 0x36, 0x26) }, + { "Turquoise", RGB_(0x40, 0xe0, 0xd0) }, + { "Turquoise1", RGB_(0x0, 0xf5, 0xff) }, + { "Turquoise2", RGB_(0x0, 0xe5, 0xee) }, + { "Turquoise3", RGB_(0x0, 0xc5, 0xcd) }, + { "Turquoise4", RGB_(0x0, 0x86, 0x8b) }, + { "Violet", RGB_(0xee, 0x82, 0xee) }, + { "VioletRed", RGB_(0xd0, 0x20, 0x90) }, + { "VioletRed1", RGB_(0xff, 0x3e, 0x96) }, + { "VioletRed2", RGB_(0xee, 0x3a, 0x8c) }, + { "VioletRed3", RGB_(0xcd, 0x32, 0x78) }, + { "VioletRed4", RGB_(0x8b, 0x22, 0x52) }, + { "WebGray", RGB_(0x80, 0x80, 0x80) }, + { "WebGreen", RGB_(0x0, 0x80, 0x0) }, + { "WebGrey", RGB_(0x80, 0x80, 0x80) }, + { "WebMaroon", RGB_(0x80, 0x0, 0x0) }, + { "WebPurple", RGB_(0x80, 0x0, 0x80) }, + { "Wheat", RGB_(0xf5, 0xde, 0xb3) }, + { "Wheat1", RGB_(0xff, 0xe7, 0xba) }, + { "Wheat2", RGB_(0xee, 0xd8, 0xae) }, + { "Wheat3", RGB_(0xcd, 0xba, 0x96) }, + { "Wheat4", RGB_(0x8b, 0x7e, 0x66) }, + { "White", RGB_(0xff, 0xff, 0xff) }, + { "WhiteSmoke", RGB_(0xf5, 0xf5, 0xf5) }, + { "X11Gray", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Green", RGB_(0x0, 0xff, 0x0) }, + { "X11Grey", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Maroon", RGB_(0xb0, 0x30, 0x60) }, + { "X11Purple", RGB_(0xa0, 0x20, 0xf0) }, + { "Yellow", RGB_(0xff, 0xff, 0x00) }, + { "Yellow1", RGB_(0xff, 0xff, 0x0) }, + { "Yellow2", RGB_(0xee, 0xee, 0x0) }, + { "Yellow3", RGB_(0xcd, 0xcd, 0x0) }, + { "Yellow4", RGB_(0x8b, 0x8b, 0x0) }, + { "YellowGreen", RGB_(0x9a, 0xcd, 0x32) }, + { NULL, 0 }, +}; + +/// Translate to RgbValue if \p name is an hex value (e.g. #XXXXXX), +/// else look into color_name_table to translate a color name to its +/// hex value +/// +/// @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 *name) +{ + if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) + && isxdigit(name[3]) && isxdigit(name[4]) && isxdigit(name[5]) + && isxdigit(name[6]) && name[7] == NUL) { + // rgb hex string + return (RgbValue)strtol((char *)(name + 1), NULL, 16); + } else if (!STRICMP(name, "bg") || !STRICMP(name, "background")) { + return normal_bg; + } else if (!STRICMP(name, "fg") || !STRICMP(name, "foreground")) { + return normal_fg; + } + + for (int i = 0; color_name_table[i].name != NULL; i++) { + if (!STRICMP(name, color_name_table[i].name)) { + return color_name_table[i].color; + } + } + + return -1; +} + +int name_to_ctermcolor(const char *name) +{ + int i; + int off = TOUPPER_ASC(*name); + for (i = ARRAY_SIZE(color_names); --i >= 0;) { + if (off == color_names[i][0] + && STRICMP(name+1, color_names[i]+1) == 0) { + break; + } + } + if (i < 0) { + return -1; + } + TriState bold = kNone; + return lookup_color(i, false, &bold); +} diff --git a/src/nvim/highlight_group.h b/src/nvim/highlight_group.h new file mode 100644 index 0000000000..325113a4ab --- /dev/null +++ b/src/nvim/highlight_group.h @@ -0,0 +1,19 @@ +#ifndef NVIM_HIGHLIGHT_GROUP_H +#define NVIM_HIGHLIGHT_GROUP_H + +#include "nvim/types.h" +#include "nvim/eval.h" + +#define MAX_HL_ID 20000 // maximum value for a highlight ID. + +typedef struct { + char *name; + RgbValue color; +} color_name_table_T; +extern color_name_table_T color_name_table[]; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "highlight_group.h.generated.h" +#endif + +#endif // NVIM_HIGHLIGHT_GROUP_H diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 6f02ebfb48..9ca01137cf 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -223,9 +223,9 @@ void ex_cstag(exarg_T *eap) switch (p_csto) { case 0: if (cs_check_for_connections()) { - ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, FALSE, - FALSE, *eap->cmdlinep); - if (ret == FALSE) { + ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, false, + false, *eap->cmdlinep); + if (ret == false) { cs_free_tags(); if (msg_col) { msg_putchar('\n'); @@ -249,16 +249,16 @@ void ex_cstag(exarg_T *eap) if (cs_check_for_connections()) { ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, - FALSE, FALSE, *eap->cmdlinep); - if (ret == FALSE) { + false, false, *eap->cmdlinep); + if (ret == false) { cs_free_tags(); } } } } else if (cs_check_for_connections()) { - ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, FALSE, - FALSE, *eap->cmdlinep); - if (ret == FALSE) { + ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, false, + false, *eap->cmdlinep); + if (ret == false) { cs_free_tags(); } } @@ -520,7 +520,7 @@ add_err: } -static int cs_check_for_connections(void) +static bool cs_check_for_connections(void) { return cs_cnt_connections() > 0; } @@ -887,20 +887,20 @@ static int cs_find(exarg_T *eap) { char *opt, *pat; - if (cs_check_for_connections() == FALSE) { + if (cs_check_for_connections() == false) { (void)emsg(_("E567: no cscope connections")); - return FALSE; + return false; } if ((opt = strtok((char *)NULL, (const char *)" ")) == NULL) { cs_usage_msg(Find); - return FALSE; + return false; } pat = opt + strlen(opt) + 1; if (pat >= (char *)eap->arg + eap_arg_len) { cs_usage_msg(Find); - return FALSE; + return false; } /* @@ -919,8 +919,8 @@ static int cs_find(exarg_T *eap) /// Common code for cscope find, shared by cs_find() and ex_cstag(). -static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int use_ll, - char_u *cmdline) +static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, bool use_ll, + char_u *cmdline) { char *cmd; int *nummatches; @@ -967,7 +967,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us // next symbol must be + or - if (strchr(CSQF_FLAGS, *qfpos) == NULL) { (void)semsg(_("E469: invalid cscopequickfix flag %c for %c"), *qfpos, *(qfpos - 1)); - return FALSE; + return false; } if (*qfpos != '0' @@ -982,7 +982,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us // create the actual command to send to cscope cmd = cs_create_cmd(opt, pat); if (cmd == NULL) { - return FALSE; + return false; } nummatches = xmalloc(sizeof(int) * csinfo_size); @@ -1019,7 +1019,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us (void)semsg(_("E259: no matches found for cscope query %s of %s"), opt, pat); } xfree(nummatches); - return FALSE; + return false; } if (qfpos != NULL && *qfpos != '0') { @@ -1064,7 +1064,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us os_remove((char *)tmp); xfree(tmp); xfree(nummatches); - return TRUE; + return true; } else { char **matches = NULL, **contexts = NULL; size_t matched = 0; @@ -1073,7 +1073,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us cs_fill_results(pat, totmatches, nummatches, &matches, &contexts, &matched); xfree(nummatches); if (matches == NULL) { - return FALSE; + return false; } (void)cs_manage_matches(matches, contexts, matched, Store); @@ -1499,12 +1499,13 @@ static void cs_file_results(FILE *f, int *nummatches_a) continue; } - context = xmalloc(strlen(cntx) + 5); + size_t context_len = strlen(cntx) + 5; + context = xmalloc(context_len); if (strcmp(cntx, "<global>") == 0) { - strcpy(context, "<<global>>"); + xstrlcpy(context, "<<global>>", context_len); } else { - sprintf(context, "<<%s>>", cntx); + snprintf(context, context_len, "<<%s>>", cntx); } if (search == NULL) { @@ -1593,7 +1594,6 @@ static char *cs_pathcomponents(char *path) char *s = path + strlen(path) - 1; for (int i = 0; i < p_cspc; i++) { while (s > path && *--s != '/') { - continue; } } if ((s > path && *s == '/')) { @@ -1812,7 +1812,6 @@ static int cs_read_prompt(size_t i) static void sig_handler(int s) { // do nothing - return; } #endif diff --git a/src/nvim/indent.c b/src/nvim/indent.c index f49aff6643..59ba2c92f7 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -17,7 +17,6 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" @@ -71,7 +70,7 @@ int get_indent_str(const char_u *ptr, int ts, bool list) { int count = 0; - for (; *ptr; ++ptr) { + for (; *ptr; ptr++) { // Count a tab for what it is worth. if (*ptr == TAB) { if (!list || curwin->w_p_lcs_chars.tab1) { @@ -443,10 +442,9 @@ int get_breakindent_win(win_T *wp, char_u *line) static long *prev_vts = NULL; // Cached vartabs values. int bri = 0; // window width minus window margin space, i.e. what rests for text - const int eff_wwidth = wp->w_width_inner - - ((wp->w_p_nu || wp->w_p_rnu) - && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) - ? number_width(wp) + 1 : 0); + const int eff_wwidth = wp->w_width_inner - + ((wp->w_p_nu || wp->w_p_rnu) + && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0); // used cached indent, unless pointer or 'tabstop' changed if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts @@ -513,7 +511,7 @@ int inindent(int extra) char_u *ptr; colnr_T col; - for (col = 0, ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr); ++col) { + for (col = 0, ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr); col++) { ptr++; } @@ -631,7 +629,7 @@ int get_lisp_indent(void) continue; } - for (that = get_cursor_line_ptr(); *that != NUL; ++that) { + for (that = get_cursor_line_ptr(); *that != NUL; that++) { if (*that == ';') { while (*(that + 1) != NUL) { that++; diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 3e3e07e9d6..f2ae8079d8 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -8,7 +8,6 @@ #include "nvim/vim.h" #include "nvim/ascii.h" -#include "nvim/misc1.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" @@ -42,9 +41,7 @@ static pos_T *ind_find_start_comment(void) // XXX pos_T *find_start_comment(int ind_maxcomment) // XXX { - pos_T *pos; - char_u *line; - char_u *p; + pos_T *pos; int64_t cur_maxcomment = ind_maxcomment; for (;; ) { @@ -56,11 +53,9 @@ pos_T *find_start_comment(int ind_maxcomment) // XXX * Check if the comment start we found is inside a string. * If it is then restrict the search to below this line and try again. */ - line = ml_get(pos->lnum); - for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p) - p = skip_string(p); - if ((colnr_T)(p - line) <= pos->col) + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) { break; + } cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; if (cur_maxcomment <= 0) { pos = NULL; @@ -111,8 +106,6 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw) static pos_T *find_start_rawstring(int ind_maxcomment) // XXX { pos_T *pos; - char_u *line; - char_u *p; long cur_maxcomment = ind_maxcomment; for (;;) @@ -125,11 +118,9 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX * Check if the raw string start we found is inside a string. * If it is then restrict the search to below this line and try again. */ - line = ml_get(pos->lnum); - for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p) - p = skip_string(p); - if ((colnr_T)(p - line) <= pos->col) - break; + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) { + break; + } cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; if (cur_maxcomment <= 0) { @@ -144,7 +135,7 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX * Skip to the end of a "string" and a 'c' character. * If there is no string or character, return argument unmodified. */ -static char_u *skip_string(char_u *p) +static const char_u *skip_string(const char_u *p) { int i; @@ -153,11 +144,11 @@ static char_u *skip_string(char_u *p) */ for (;; p++) { if (p[0] == '\'') { // 'c' or '\n' or '\000' - if (!p[1]) { // ' at end of line + if (p[1] == NUL) { // ' at end of line break; } i = 2; - if (p[1] == '\\') { // '\n' or '\000' + if (p[1] == '\\' && p[2] != NUL) { // '\n' or '\000' i++; while (ascii_isdigit(p[i - 1])) { // '\000' i++; @@ -179,24 +170,24 @@ static char_u *skip_string(char_u *p) continue; // continue for another string } } else if (p[0] == 'R' && p[1] == '"') { - // Raw string: R"[delim](...)[delim]" - char_u *delim = p + 2; - char_u *paren = vim_strchr(delim, '('); - - if (paren != NULL) { - const ptrdiff_t delim_len = paren - delim; - - for (p += 3; *p; ++p) - if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0 - && p[delim_len + 1] == '"') - { - p += delim_len + 1; - break; - } - if (p[0] == '"') { - continue; // continue for another string - } + // Raw string: R"[delim](...)[delim]" + const char_u *delim = p + 2; + const char_u *paren = vim_strchr(delim, '('); + + if (paren != NULL) { + const ptrdiff_t delim_len = paren - delim; + + for (p += 3; *p; p++) { + if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0 + && p[delim_len + 1] == '"') { + p += delim_len + 1; + break; + } + } + if (p[0] == '"') { + continue; // continue for another string } + } } break; // no string found } @@ -206,6 +197,16 @@ static char_u *skip_string(char_u *p) return p; } +/// @returns true if "line[col]" is inside a C string. +int is_pos_in_string(const char_u *line, colnr_T col) +{ + const char_u *p; + + for (p = line; *p && (colnr_T)(p - line) < col; p++) { + p = skip_string(p); + } + return !((colnr_T)(p - line) <= col); +} /* * Functions for C-indenting. @@ -219,7 +220,7 @@ static char_u *skip_string(char_u *p) /* * Return true if the string "line" starts with a word from 'cinwords'. */ -bool cin_is_cinword(char_u *line) +bool cin_is_cinword(const char_u *line) { bool retval = false; @@ -247,10 +248,10 @@ bool cin_is_cinword(char_u *line) * Skip over white space and C comments within the line. * Also skip over Perl/shell comments if desired. */ -static char_u *cin_skipcomment(char_u *s) +static const char_u *cin_skipcomment(const char_u *s) { while (*s) { - char_u *prev_s = s; + const char_u *prev_s = s; s = skipwhite(s); @@ -284,7 +285,7 @@ static char_u *cin_skipcomment(char_u *s) * Return TRUE if there is no code at *s. White space and comments are * not considered code. */ -static int cin_nocode(char_u *s) +static int cin_nocode(const char_u *s) { return *cin_skipcomment(s) == NUL; } @@ -313,9 +314,9 @@ static pos_T *find_line_comment(void) // XXX } /// Checks if `text` starts with "key:". -static bool cin_has_js_key(char_u *text) +static bool cin_has_js_key(const char_u *text) { - char_u *s = skipwhite(text); + const char_u *s = skipwhite(text); char_u quote = 0; if (*s == '\'' || *s == '"') { @@ -342,7 +343,7 @@ static bool cin_has_js_key(char_u *text) /// Checks if string matches "label:"; move to character after ':' if true. /// "*s" must point to the start of the label, if there is one. -static bool cin_islabel_skip(char_u **s) +static bool cin_islabel_skip(const char_u **s) FUNC_ATTR_NONNULL_ALL { if (!vim_isIDc(**s)) { // need at least one ID character @@ -362,7 +363,7 @@ static bool cin_islabel_skip(char_u **s) // Note: curwin->w_cursor must be where we are looking for the label. bool cin_islabel(void) // XXX { - char_u *s = cin_skipcomment(get_cursor_line_ptr()); + const char_u *s = cin_skipcomment(get_cursor_line_ptr()); // Exclude "default" from labels, since it should be indented // like a switch label. Same for C++ scope declarations. @@ -381,8 +382,8 @@ bool cin_islabel(void) // XXX * label. */ pos_T cursor_save; - pos_T *trypos; - char_u *line; + pos_T *trypos; + const char_u *line; cursor_save = curwin->w_cursor; while (curwin->w_cursor.lnum > 1) { @@ -425,8 +426,8 @@ bool cin_islabel(void) // XXX */ static int cin_isinit(void) { - char_u *s; - static char *skip[] = {"static", "public", "protected", "private"}; + const char_u *s; + static char *skip[] = { "static", "public", "protected", "private" }; s = cin_skipcomment(get_cursor_line_ptr()); @@ -461,7 +462,7 @@ static int cin_isinit(void) * Recognize a switch label: "case .*:" or "default:". */ bool cin_iscase( - char_u *s, + const char_u *s, bool strict // Allow relaxed check of case statement for JS ) { @@ -504,44 +505,55 @@ bool cin_iscase( /* * Recognize a "default" switch label. */ -static int cin_isdefault(char_u *s) +static int cin_isdefault(const char_u *s) { return STRNCMP(s, "default", 7) == 0 && *(s = cin_skipcomment(s + 7)) == ':' && s[1] != ':'; } -/* - * Recognize a "public/private/protected" scope declaration label. - */ -bool cin_isscopedecl(char_u *s) +/// Recognize a scope declaration label set in 'cinscopedecls'. +bool cin_isscopedecl(const char_u *p) { - int i; + const char_u *s = cin_skipcomment(p); - s = cin_skipcomment(s); - if (STRNCMP(s, "public", 6) == 0) { - i = 6; - } else if (STRNCMP(s, "protected", 9) == 0) { - i = 9; - } else if (STRNCMP(s, "private", 7) == 0) { - i = 7; - } else { - return false; + const size_t cinsd_len = STRLEN(curbuf->b_p_cinsd) + 1; + char_u *cinsd_buf = xmalloc(cinsd_len); + + bool found = false; + + for (char_u *cinsd = curbuf->b_p_cinsd; *cinsd; ) { + const size_t len = copy_option_part(&cinsd, cinsd_buf, cinsd_len, ","); + if (STRNCMP(s, cinsd_buf, len) == 0) { + const char_u *skip = cin_skipcomment(s + len); + if (*skip == ':' && skip[1] != ':') { + found = true; + break; + } + } } - return *(s = cin_skipcomment(s + i)) == ':' && s[1] != ':'; + + xfree(cinsd_buf); + + return found; } // Maximum number of lines to search back for a "namespace" line. #define FIND_NAMESPACE_LIM 20 // Recognize a "namespace" scope declaration. -static bool cin_is_cpp_namespace(char_u *s) +static bool cin_is_cpp_namespace(const char_u *s) { - char_u *p; + const char_u *p; bool has_name = false; bool has_name_start = false; s = cin_skipcomment(s); + + if (STRNCMP(s, "inline", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { + s = cin_skipcomment(skipwhite(s + 6)); + } + if (STRNCMP(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc(s[9]))) { p = cin_skipcomment(skipwhite(s + 9)); while (*p != NUL) { @@ -577,7 +589,7 @@ static bool cin_is_cpp_namespace(char_u *s) * case 234: a = b; * ^ */ -static char_u *after_label(char_u *l) +static const char_u *after_label(const char_u *l) { for (; *l; ++l) { if (*l == ':') { @@ -604,10 +616,10 @@ static char_u *after_label(char_u *l) */ static int get_indent_nolabel(linenr_T lnum) // XXX { - char_u *l; + const char_u *l; pos_T fp; colnr_T col; - char_u *p; + const char_u *p; l = ml_get(lnum); p = after_label(l); @@ -626,9 +638,9 @@ static int get_indent_nolabel(linenr_T lnum) // XXX * label: if (asdf && asdfasdf) * ^ */ -static int skip_label(linenr_T lnum, char_u **pp) +static int skip_label(linenr_T lnum, const char_u **pp) { - char_u *l; + const char_u *l; int amount; pos_T cursor_save; @@ -709,8 +721,8 @@ static int cin_first_id_amount(void) */ static int cin_get_equal_amount(linenr_T lnum) { - char_u *line; - char_u *s; + const char_u *line; + const char_u *s; colnr_T col; pos_T fp; @@ -748,7 +760,7 @@ static int cin_get_equal_amount(linenr_T lnum) /* * Recognize a preprocessor statement: Any line that starts with '#'. */ -static int cin_ispreproc(char_u *s) +static int cin_ispreproc(const char_u *s) { if (*skipwhite(s) == '#') return TRUE; @@ -759,9 +771,9 @@ static int cin_ispreproc(char_u *s) /// continuation line of a preprocessor statement. Decrease "*lnump" to the /// start and return the line in "*pp". /// Put the amount of indent in "*amount". -static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount) +static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) { - char_u *line = *pp; + const char_u *line = *pp; linenr_T lnum = *lnump; int retval = false; int candidate_amount = *amount; @@ -795,7 +807,7 @@ static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount) /* * Recognize the start of a C or C++ comment. */ -static int cin_iscomment(char_u *p) +static int cin_iscomment(const char_u *p) { return p[0] == '/' && (p[1] == '*' || p[1] == '/'); } @@ -803,7 +815,7 @@ static int cin_iscomment(char_u *p) /* * Recognize the start of a "//" comment. */ -static int cin_islinecomment(char_u *p) +static int cin_islinecomment(const char_u *p) { return p[0] == '/' && p[1] == '/'; } @@ -818,8 +830,8 @@ static int cin_islinecomment(char_u *p) * both apply in order to determine initializations). */ static char_u -cin_isterminated ( - char_u *s, +cin_isterminated( + const char_u *s, int incl_open, // include '{' at the end as terminator int incl_comma // recognize a trailing comma ) @@ -868,9 +880,9 @@ cin_isterminated ( /// lines here. /// @param[in] first_lnum Where to start looking. /// @param[in] min_lnum The line before which we will not be looking. -static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum) +static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_lnum) { - char_u *s; + const char_u *s; linenr_T lnum = first_lnum; linenr_T save_lnum = curwin->w_cursor.lnum; int retval = false; @@ -924,11 +936,10 @@ static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum) while (*s && *s != ';' && *s != '\'' && *s != '"') { if (*s == ')' && cin_nocode(s + 1)) { - /* ')' at the end: may have found a match - * Check for he previous line not to end in a backslash: - * #if defined(x) && \ - * defined(y) - */ + // ')' at the end: may have found a match + // Check for the previous line not to end in a backslash: + // #if defined(x) && {backslash} + // defined(y) lnum = first_lnum - 1; s = ml_get(lnum); if (*s == NUL || s[STRLEN(s) - 1] != '\\') @@ -972,12 +983,12 @@ done: return retval; } -static int cin_isif(char_u *p) +static int cin_isif(const char_u *p) { return STRNCMP(p, "if", 2) == 0 && !vim_isIDc(p[2]); } -static int cin_iselse(char_u *p) +static int cin_iselse(const char_u *p) { if (*p == '}') { // accept "} else" p = cin_skipcomment(p + 1); @@ -985,7 +996,7 @@ static int cin_iselse(char_u *p) return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]); } -static int cin_isdo(char_u *p) +static int cin_isdo(const char_u *p) { return STRNCMP(p, "do", 2) == 0 && !vim_isIDc(p[2]); } @@ -995,7 +1006,7 @@ static int cin_isdo(char_u *p) * We only accept a "while (condition) ;", with only white space between the * ')' and ';'. The condition may be spread over several lines. */ -static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX +static int cin_iswhileofdo(const char_u *p, linenr_T lnum) // XXX { pos_T cursor_save; pos_T *trypos; @@ -1029,7 +1040,7 @@ static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX * Otherwise return !0 and update "*poffset" to point to the place where the * string was found. */ -static int cin_is_if_for_while_before_offset(char_u *line, int *poffset) +static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset) { int offset = *poffset; @@ -1073,10 +1084,10 @@ probablyFound: */ static int cin_iswhileofdo_end(int terminated) { - char_u *line; - char_u *p; - char_u *s; - pos_T *trypos; + const char_u *line; + const char_u *p; + const char_u *s; + pos_T *trypos; int i; if (terminated != ';') { // there must be a ';' at the end @@ -1116,7 +1127,7 @@ static int cin_iswhileofdo_end(int terminated) return FALSE; } -static int cin_isbreak(char_u *p) +static int cin_isbreak(const char_u *p) { return STRNCMP(p, "break", 5) == 0 && !vim_isIDc(p[5]); } @@ -1136,10 +1147,10 @@ static int cin_isbreak(char_u *p) */ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { lpos_T *pos = &cached->lpos; // find position - char_u *s; + const char_u *s; int class_or_struct, lookfor_ctor_init, cpp_base_class; linenr_T lnum = curwin->w_cursor.lnum; - char_u *line = get_cursor_line_ptr(); + const char_u *line = get_cursor_line_ptr(); if (pos->lnum <= lnum) { return cached->found; // Use the cached result @@ -1307,10 +1318,10 @@ static int get_baseclass_amount(int col) * white space and comments. Skip strings and comments. * Ignore "ignore" after "find" if it's not NULL. */ -static int cin_ends_in(char_u *s, char_u *find, char_u *ignore) +static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore) { - char_u *p = s; - char_u *r; + const char_u *p = s; + const char_u *r; int len = (int)STRLEN(find); while (*p != NUL) { @@ -1331,7 +1342,7 @@ static int cin_ends_in(char_u *s, char_u *find, char_u *ignore) /* * Return TRUE when "s" starts with "word" and then a non-ID character. */ -static int cin_starts_with(char_u *s, char *word) +static int cin_starts_with(const char_u *s, const char *word) { int l = (int)STRLEN(word); @@ -1339,10 +1350,10 @@ static int cin_starts_with(char_u *s, char *word) } /// Recognize a `extern "C"` or `extern "C++"` linkage specifications. -static int cin_is_cpp_extern_c(char_u *s) +static int cin_is_cpp_extern_c(const char_u *s) { - char_u *p; - int has_string_literal = false; + const char_u *p; + int has_string_literal = false; s = cin_skipcomment(s); if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { @@ -1381,9 +1392,9 @@ static int cin_is_cpp_extern_c(char_u *s) */ static int cin_skip2pos(pos_T *trypos) { - char_u *line; - char_u *p; - char_u *new_p; + const char_u *line; + const char_u *p; + const char_u *new_p; p = line = ml_get(trypos->lnum); while (*p && (colnr_T)(p - line) < trypos->col) { @@ -1413,8 +1424,8 @@ static int cin_skip2pos(pos_T *trypos) static pos_T *find_start_brace(void) // XXX { pos_T cursor_save; - pos_T *trypos; - pos_T *pos; + pos_T *trypos; + pos_T *pos; static pos_T pos_copy; cursor_save = curwin->w_cursor; @@ -1429,7 +1440,7 @@ static pos_T *find_start_brace(void) // XXX break; } if (pos != NULL) { - curwin->w_cursor.lnum = pos->lnum; + curwin->w_cursor = *pos; } } curwin->w_cursor = cursor_save; @@ -1526,7 +1537,7 @@ static int corr_ind_maxparen(pos_T *startpos) * Set w_cursor.col to the column number of the last unmatched ')' or '{' in * line "l". "l" must point to the start of the line. */ -static int find_last_paren(char_u *l, int start, int end) +static int find_last_paren(const char_u *l, int start, int end) { int i; int retval = FALSE; @@ -1635,8 +1646,8 @@ void parse_cino(buf_T *buf) * itself is also unclosed. */ buf->b_ind_unclosed2 = sw; - /* Suppress ignoring spaces from the indent of a line starting with an - * unclosed parentheses. */ + // Suppress ignoring spaces from the indent of a line starting with an + // unclosed parenthesis. buf->b_ind_unclosed_noignore = 0; /* If the opening paren is the last nonwhite character on the line, and @@ -1648,11 +1659,11 @@ void parse_cino(buf_T *buf) * an unclosed parentheses. */ buf->b_ind_unclosed_whiteok = 0; - /* Indent a closing parentheses under the line start of the matching - * opening parentheses. */ + // Indent a closing parenthesis under the line start of the matching + // opening parenthesis. buf->b_ind_matching_paren = 0; - // Indent a closing parentheses under the previous line. + // Indent a closing parenthesis under the previous line. buf->b_ind_paren_prev = 0; // Extra indent for comments. @@ -1798,8 +1809,8 @@ int get_c_indent(void) #define BRACE_AT_START 2 // '{' is at start of line #define BRACE_AT_END 3 // '{' is at end of line linenr_T ourscope; - char_u *l; - char_u *look; + const char_u *l; + const char_u *look; char_u terminated; int lookfor; #define LOOKFOR_INITIAL 0 @@ -1903,12 +1914,25 @@ int get_c_indent(void) * If we're inside a "//" comment and there is a "//" comment in a * previous line, lineup with that one. */ - if (cin_islinecomment(theline) - && (trypos = find_line_comment()) != NULL) { // XXX - // find how indented the line beginning the comment is - getvcol(curwin, trypos, &col, NULL, NULL); - amount = col; - goto theend; + if (cin_islinecomment(theline)) { + pos_T linecomment_pos; + + trypos = find_line_comment(); // XXX + if (trypos == NULL && curwin->w_cursor.lnum > 1) { + // There may be a statement before the comment, search from the end + // of the line for a comment start. + linecomment_pos.col = check_linecomment(ml_get(curwin->w_cursor.lnum - 1)); + if (linecomment_pos.col != MAXCOL) { + trypos = &linecomment_pos; + trypos->lnum = curwin->w_cursor.lnum - 1; + } + } + if (trypos != NULL) { + // find how indented the line beginning the comment is + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + goto theend; + } } /* * If we're inside a comment and not looking at the start of the @@ -3599,9 +3623,9 @@ laterend: static int find_match(int lookfor, linenr_T ourscope) { - char_u *look; - pos_T *theirscope; - char_u *mightbeif; + const char_u *look; + pos_T *theirscope; + const char_u *mightbeif; int elselevel; int whilelevel; diff --git a/src/nvim/input.c b/src/nvim/input.c new file mode 100644 index 0000000000..ff6b559710 --- /dev/null +++ b/src/nvim/input.c @@ -0,0 +1,255 @@ +// 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 + +// input.c: high level functions for prompting the user or input +// like yes/no or number prompts. + +#include <inttypes.h> +#include <stdbool.h> + +#include "nvim/func_attr.h" +#include "nvim/getchar.h" +#include "nvim/input.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" +#include "nvim/mouse.h" +#include "nvim/os/input.h" +#include "nvim/ui.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "input.c.generated.h" +#endif + +/// Ask for a reply from the user, 'y' or 'n' +/// +/// No other characters are accepted, the message is repeated until a valid +/// reply is entered or <C-c> is hit. +/// +/// @param[in] str Prompt: question to ask user. Is always followed by +/// " (y/n)?". +/// @param[in] direct Determines what function to use to get user input. If +/// true then ui_inchar() will be used, otherwise vgetc(). +/// I.e. when direct is true then characters are obtained +/// directly from the user without buffers involved. +/// +/// @return 'y' or 'n'. Last is also what will be returned in case of interrupt. +int ask_yesno(const char *const str, const bool direct) +{ + const int save_State = State; + + no_wait_return++; + State = CONFIRM; // Mouse behaves like with :confirm. + setmouse(); // Disable mouse in xterm. + no_mapping++; + + int r = ' '; + while (r != 'y' && r != 'n') { + // Same highlighting as for wait_return. + smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); + if (direct) { + r = get_keystroke(NULL); + } else { + r = plain_vgetc(); + } + if (r == Ctrl_C || r == ESC) { + r = 'n'; + } + msg_putchar(r); // Show what you typed. + ui_flush(); + } + no_wait_return--; + State = save_State; + setmouse(); + no_mapping--; + + return r; +} + +/// Get a key stroke directly from the user. +/// +/// Ignores mouse clicks and scrollbar events, except a click for the left +/// button (used at the more prompt). +/// Doesn't use vgetc(), because it syncs undo and eats mapped characters. +/// Disadvantage: typeahead is ignored. +/// Translates the interrupt character for unix to ESC. +int get_keystroke(MultiQueue *events) +{ + char_u *buf = NULL; + int buflen = 150; + int maxlen; + int len = 0; + int n; + int save_mapped_ctrl_c = mapped_ctrl_c; + int waited = 0; + + mapped_ctrl_c = 0; // mappings are not used here + for (;;) { + // flush output before waiting + ui_flush(); + // Leave some room for check_termcode() to insert a key code into (max + // 5 chars plus NUL). And fix_input_buffer() can triple the number of + // bytes. + maxlen = (buflen - 6 - len) / 3; + if (buf == NULL) { + buf = xmalloc((size_t)buflen); + } else if (maxlen < 10) { + // Need some more space. This might happen when receiving a long + // escape sequence. + buflen += 100; + buf = xrealloc(buf, (size_t)buflen); + maxlen = (buflen - 6 - len) / 3; + } + + // First time: blocking wait. Second time: wait up to 100ms for a + // terminal code to complete. + n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events); + if (n > 0) { + // Replace zero and K_SPECIAL by a special key code. + n = fix_input_buffer(buf + len, n); + len += n; + waited = 0; + } else if (len > 0) { + waited++; // keep track of the waiting time + } + if (n > 0) { // found a termcode: adjust length + len = n; + } + if (len == 0) { // nothing typed yet + continue; + } + + // Handle modifier and/or special key code. + n = buf[0]; + if (n == K_SPECIAL) { + n = TO_SPECIAL(buf[1], buf[2]); + if (buf[1] == KS_MODIFIER + || n == K_IGNORE + || (is_mouse_key(n) && n != K_LEFTMOUSE)) { + if (buf[1] == KS_MODIFIER) { + mod_mask = buf[2]; + } + len -= 3; + if (len > 0) { + memmove(buf, buf + 3, (size_t)len); + } + continue; + } + break; + } + if (MB_BYTE2LEN(n) > len) { + // more bytes to get. + continue; + } + buf[len >= buflen ? buflen - 1 : len] = NUL; + n = utf_ptr2char(buf); + break; + } + xfree(buf); + + mapped_ctrl_c = save_mapped_ctrl_c; + return n; +} + +/// Get a number from the user. +/// When "mouse_used" is not NULL allow using the mouse. +/// +/// @param colon allow colon to abort +int get_number(int colon, int *mouse_used) +{ + int n = 0; + int c; + int typed = 0; + + if (mouse_used != NULL) { + *mouse_used = false; + } + + // When not printing messages, the user won't know what to type, return a + // zero (as if CR was hit). + if (msg_silent != 0) { + return 0; + } + + no_mapping++; + for (;;) { + ui_cursor_goto(msg_row, msg_col); + c = safe_vgetc(); + if (ascii_isdigit(c)) { + n = n * 10 + c - '0'; + msg_putchar(c); + typed++; + } else if (c == K_DEL || c == K_KDEL || c == K_BS || c == Ctrl_H) { + if (typed > 0) { + msg_puts("\b \b"); + typed--; + } + n /= 10; + } else if (mouse_used != NULL && c == K_LEFTMOUSE) { + *mouse_used = true; + n = mouse_row + 1; + break; + } else if (n == 0 && c == ':' && colon) { + stuffcharReadbuff(':'); + if (!exmode_active) { + cmdline_row = msg_row; + } + skip_redraw = true; // skip redraw once + do_redraw = false; + break; + } else if (c == Ctrl_C || c == ESC || c == 'q') { + n = 0; + break; + } else if (c == CAR || c == NL) { + break; + } + } + no_mapping--; + return n; +} + +/// Ask the user to enter a number. +/// +/// When "mouse_used" is not NULL allow using the mouse and in that case return +/// the line number. +int prompt_for_number(int *mouse_used) +{ + int i; + int save_cmdline_row; + int save_State; + + // When using ":silent" assume that <CR> was entered. + if (mouse_used != NULL) { + msg_puts(_("Type number and <Enter> or click with the mouse " + "(q or empty cancels): ")); + } else { + msg_puts(_("Type number and <Enter> (q or empty cancels): ")); + } + + // Set the state such that text can be selected/copied/pasted and we still + // get mouse events. + save_cmdline_row = cmdline_row; + cmdline_row = 0; + save_State = State; + State = ASKMORE; // prevents a screen update when using a timer + // May show different mouse shape. + setmouse(); + + i = get_number(true, mouse_used); + if (KeyTyped) { + // don't call wait_return() now + if (msg_row > 0) { + cmdline_row = msg_row - 1; + } + need_wait_return = false; + msg_didany = false; + msg_didout = false; + } else { + cmdline_row = save_cmdline_row; + } + State = save_State; + // May need to restore mouse shape. + setmouse(); + + return i; +} diff --git a/src/nvim/input.h b/src/nvim/input.h new file mode 100644 index 0000000000..7975f21215 --- /dev/null +++ b/src/nvim/input.h @@ -0,0 +1,9 @@ +#ifndef NVIM_INPUT_H +#define NVIM_INPUT_H + +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "input.h.generated.h" +#endif +#endif // NVIM_INPUT_H diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index abf016b832..1e305528ba 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -158,7 +158,6 @@ static const struct key_name_entry { { ESC, "Esc" }, { ESC, "Escape" }, // Alternative name { CSI, "CSI" }, - { K_CSI, "xCSI" }, { '|', "Bar" }, { '\\', "Bslash" }, { K_DEL, "Del" }, @@ -222,6 +221,35 @@ static const struct key_name_entry { { K_F35, "F35" }, { K_F36, "F36" }, { K_F37, "F37" }, + { K_F38, "F38" }, + { K_F39, "F39" }, + { K_F40, "F40" }, + + { K_F41, "F41" }, + { K_F42, "F42" }, + { K_F43, "F43" }, + { K_F44, "F44" }, + { K_F45, "F45" }, + { K_F46, "F46" }, + { K_F47, "F47" }, + { K_F48, "F48" }, + { K_F49, "F49" }, + { K_F50, "F50" }, + + { K_F51, "F51" }, + { K_F52, "F52" }, + { K_F53, "F53" }, + { K_F54, "F54" }, + { K_F55, "F55" }, + { K_F56, "F56" }, + { K_F57, "F57" }, + { K_F58, "F58" }, + { K_F59, "F59" }, + { K_F60, "F60" }, + + { K_F61, "F61" }, + { K_F62, "F62" }, + { K_F63, "F63" }, { K_XF1, "xF1" }, { K_XF2, "xF2" }, @@ -745,12 +773,15 @@ static int extract_modifiers(int key, int *modp) modifiers &= ~MOD_MASK_SHIFT; } } - if ((modifiers & MOD_MASK_CTRL) - && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) { - key = Ctrl_chr(key); - modifiers &= ~MOD_MASK_CTRL; - if (key == 0) { // <C-@> is <Nul> - key = K_ZERO; + if ((modifiers & MOD_MASK_CTRL) && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) { + key = TOUPPER_ASC(key); + int new_key = Ctrl_chr(key); + if (new_key != TAB && new_key != CAR && new_key != ESC) { + key = new_key; + modifiers &= ~MOD_MASK_CTRL; + if (key == 0) { // <C-@> is <Nul> + key = K_ZERO; + } } } @@ -964,7 +995,6 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bu for (i = utfc_ptr2len_len(src, (int)(end - src) + 1); i > 0; i--) { // If the character is K_SPECIAL, replace it with K_SPECIAL // KS_SPECIAL KE_FILLER. - // If compiled with the GUI replace CSI with K_CSI. if (*src == K_SPECIAL) { result[dlen++] = K_SPECIAL; result[dlen++] = KS_SPECIAL; diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 5ff5a38614..9dff8ba333 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -122,8 +122,6 @@ // // Entries must be in the range 0x02-0x7f (see comment at K_SPECIAL). enum key_extra { - KE_NAME = 3, // name of this terminal entry - KE_S_UP = 4, // shift-up KE_S_DOWN = 5, // shift-down @@ -220,7 +218,7 @@ enum key_extra { KE_KINS = 79, // keypad Insert key KE_KDEL = 80, // keypad Delete key - KE_CSI = 81, // CSI typed directly + // KE_CSI = 81, // Nvim doesn't need escaping CSI KE_SNR = 82, // <SNR> KE_PLUG = 83, // <Plug> KE_CMDWIN = 84, // open command-line window from Command-line Mode @@ -245,6 +243,7 @@ enum key_extra { KE_MOUSEMOVE = 100, // mouse moved with no button down // , KE_CANCEL = 101 // return from vgetc KE_EVENT = 102, // event + KE_LUA = 103, // lua special key KE_COMMAND = 104, // <Cmd> special key }; @@ -326,6 +325,35 @@ enum key_extra { #define K_F35 TERMCAP2KEY('F', 'P') #define K_F36 TERMCAP2KEY('F', 'Q') #define K_F37 TERMCAP2KEY('F', 'R') +#define K_F38 TERMCAP2KEY('F', 'S') +#define K_F39 TERMCAP2KEY('F', 'T') +#define K_F40 TERMCAP2KEY('F', 'U') + +#define K_F41 TERMCAP2KEY('F', 'V') +#define K_F42 TERMCAP2KEY('F', 'W') +#define K_F43 TERMCAP2KEY('F', 'X') +#define K_F44 TERMCAP2KEY('F', 'Y') +#define K_F45 TERMCAP2KEY('F', 'Z') +#define K_F46 TERMCAP2KEY('F', 'a') +#define K_F47 TERMCAP2KEY('F', 'b') +#define K_F48 TERMCAP2KEY('F', 'c') +#define K_F49 TERMCAP2KEY('F', 'd') +#define K_F50 TERMCAP2KEY('F', 'e') + +#define K_F51 TERMCAP2KEY('F', 'f') +#define K_F52 TERMCAP2KEY('F', 'g') +#define K_F53 TERMCAP2KEY('F', 'h') +#define K_F54 TERMCAP2KEY('F', 'i') +#define K_F55 TERMCAP2KEY('F', 'j') +#define K_F56 TERMCAP2KEY('F', 'k') +#define K_F57 TERMCAP2KEY('F', 'l') +#define K_F58 TERMCAP2KEY('F', 'm') +#define K_F59 TERMCAP2KEY('F', 'n') +#define K_F60 TERMCAP2KEY('F', 'o') + +#define K_F61 TERMCAP2KEY('F', 'p') +#define K_F62 TERMCAP2KEY('F', 'q') +#define K_F63 TERMCAP2KEY('F', 'r') // extra set of shifted function keys F1-F4, for vt100 compatible xterm #define K_S_XF1 TERMCAP2KEY(KS_EXTRA, KE_S_XF1) @@ -434,7 +462,6 @@ enum key_extra { #define K_MOUSELEFT TERMCAP2KEY(KS_EXTRA, KE_MOUSELEFT) #define K_MOUSERIGHT TERMCAP2KEY(KS_EXTRA, KE_MOUSERIGHT) -#define K_CSI TERMCAP2KEY(KS_EXTRA, KE_CSI) #define K_SNR TERMCAP2KEY(KS_EXTRA, KE_SNR) #define K_PLUG TERMCAP2KEY(KS_EXTRA, KE_PLUG) #define K_CMDWIN TERMCAP2KEY(KS_EXTRA, KE_CMDWIN) @@ -443,6 +470,7 @@ enum key_extra { #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) #define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND) +#define K_LUA TERMCAP2KEY(KS_EXTRA, KE_LUA) // Bits for modifier mask // 0x01 cannot be used, because the modifier must be 0x02 or higher diff --git a/src/nvim/lib/khash.h b/src/nvim/lib/khash.h index 8cfeb03cc4..e81db43038 100644 --- a/src/nvim/lib/khash.h +++ b/src/nvim/lib/khash.h @@ -666,7 +666,7 @@ static kh_inline khint_t __ac_Wang_hash(khint_t key) } \ } -// More conenient interfaces +// More convenient interfaces /*! @function @abstract Instantiate a hash set containing integer keys diff --git a/src/nvim/log.c b/src/nvim/log.c index 5539e3d6c5..7d50ecf69e 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -282,6 +282,7 @@ static bool do_log_to_file(FILE *log_file, int log_level, const char *context, static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context, const char *func_name, int line_num, bool eol, const char *fmt, va_list args) + FUNC_ATTR_PRINTF(7, 0) { static const char *log_levels[] = { [DEBUG_LOG_LEVEL] = "DEBUG", diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 9f2372f831..ef49a03660 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -156,7 +156,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) && ret.string_keys_num == 0)) { ret.type = kObjectTypeArray; if (tsize == 0 && lua_getmetatable(lstate, -1)) { - nlua_pushref(lstate, nlua_empty_dict_ref); + nlua_pushref(lstate, nlua_global_refs->empty_dict_ref); if (lua_rawequal(lstate, -2, -1)) { ret.type = kObjectTypeDictionary; } @@ -286,9 +286,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) break; case LUA_TBOOLEAN: cur.tv->v_type = VAR_BOOL; - cur.tv->vval.v_bool = (lua_toboolean(lstate, -1) - ? kBoolVarTrue - : kBoolVarFalse); + cur.tv->vval.v_bool = (lua_toboolean(lstate, -1) ? kBoolVarTrue : kBoolVarFalse); break; case LUA_TSTRING: { size_t len; @@ -316,7 +314,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) LuaRef table_ref = LUA_NOREF; if (lua_getmetatable(lstate, -1)) { lua_pop(lstate, 1); - table_ref = nlua_ref(lstate, -1); + table_ref = nlua_ref_global(lstate, -1); } const LuaTableProps table_props = nlua_traverse_table(lstate); @@ -389,7 +387,7 @@ nlua_pop_typval_table_processing_end: } case LUA_TFUNCTION: { LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); - state->lua_callable.func_ref = nlua_ref(lstate, -1); + state->lua_callable.func_ref = nlua_ref_global(lstate, -1); char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, @@ -401,7 +399,7 @@ nlua_pop_typval_table_processing_end: } case LUA_TUSERDATA: { // TODO(bfredl): check mt.__call and convert to function? - nlua_pushref(lstate, nlua_nil_ref); + nlua_pushref(lstate, nlua_global_refs->nil_ref); bool is_nil = lua_rawequal(lstate, -2, -1); lua_pop(lstate, 1); if (is_nil) { @@ -445,7 +443,7 @@ static bool typval_conv_special = false; if (typval_conv_special) { \ lua_pushnil(lstate); \ } else { \ - nlua_pushref(lstate, nlua_nil_ref); \ + nlua_pushref(lstate, nlua_global_refs->nil_ref); \ } \ } while (0) @@ -478,7 +476,12 @@ static bool typval_conv_special = false; #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - TYPVAL_ENCODE_CONV_NIL(tv); \ + ufunc_T *fp = find_func(fun); \ + if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \ + nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \ + } else { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + } \ goto typval_encode_stop_converting_one_item; \ } while (0) @@ -495,7 +498,7 @@ static bool typval_conv_special = false; nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \ } else { \ lua_createtable(lstate, 0, 0); \ - nlua_pushref(lstate, nlua_empty_dict_ref); \ + nlua_pushref(lstate, nlua_global_refs->empty_dict_ref); \ lua_setmetatable(lstate, -2); \ } \ } while (0) @@ -551,8 +554,8 @@ static bool typval_conv_special = false; const MPConvStackVal mpval = kv_A(*mpstack, backref - 1); \ if (mpval.type == conv_type) { \ if (conv_type == kMPConvDict \ - ? (void *)mpval.data.d.dict == (void *)(val) \ - : (void *)mpval.data.l.list == (void *)(val)) { \ + ? (void *)mpval.data.d.dict == (void *)(val) \ + : (void *)mpval.data.l.list == (void *)(val)) { \ lua_pushvalue(lstate, \ -((int)((kv_size(*mpstack) - backref + 1) * 2))); \ break; \ @@ -726,7 +729,7 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, bool special } else { lua_createtable(lstate, 0, (int)dict.size); if (dict.size == 0 && !special) { - nlua_pushref(lstate, nlua_empty_dict_ref); + nlua_pushref(lstate, nlua_global_refs->empty_dict_ref); lua_setmetatable(lstate, -2); } } @@ -774,7 +777,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) if (special) { lua_pushnil(lstate); } else { - nlua_pushref(lstate, nlua_nil_ref); + nlua_pushref(lstate, nlua_global_refs->nil_ref); } break; case kObjectTypeLuaRef: { @@ -782,10 +785,10 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) break; } #define ADD_TYPE(type, data_key) \ -case kObjectType##type: { \ - nlua_push_##type(lstate, obj.data.data_key, special); \ - break; \ -} + case kObjectType##type: { \ + nlua_push_##type(lstate, obj.data.data_key, special); \ + break; \ + } ADD_TYPE(Boolean, boolean) ADD_TYPE(Integer, integer) ADD_TYPE(Float, floating) @@ -794,10 +797,10 @@ case kObjectType##type: { \ ADD_TYPE(Dictionary, dictionary) #undef ADD_TYPE #define ADD_REMOTE_TYPE(type) \ -case kObjectType##type: { \ - nlua_push_##type(lstate, (type)obj.data.integer, special); \ - break; \ -} + case kObjectType##type: { \ + nlua_push_##type(lstate, (type)obj.data.integer, special); \ + break; \ + } ADD_REMOTE_TYPE(Buffer) ADD_REMOTE_TYPE(Window) ADD_REMOTE_TYPE(Tabpage) @@ -1125,10 +1128,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) case LUA_TSTRING: { size_t len; const char *s = lua_tolstring(lstate, -1, &len); - *cur.obj = STRING_OBJ(((String) { - .data = xmemdupz(s, len), - .size = len, - })); + *cur.obj = STRING_OBJ(((String) { .data = xmemdupz(s, len), .size = len })); break; } case LUA_TNUMBER: { @@ -1146,11 +1146,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) switch (table_props.type) { case kObjectTypeArray: - *cur.obj = ARRAY_OBJ(((Array) { - .items = NULL, - .size = 0, - .capacity = 0, - })); + *cur.obj = ARRAY_OBJ(((Array) { .items = NULL, .size = 0, .capacity = 0 })); if (table_props.maxidx != 0) { cur.obj->data.array.items = xcalloc(table_props.maxidx, @@ -1161,11 +1157,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) } break; case kObjectTypeDictionary: - *cur.obj = DICTIONARY_OBJ(((Dictionary) { - .items = NULL, - .size = 0, - .capacity = 0, - })); + *cur.obj = DICTIONARY_OBJ(((Dictionary) { .items = NULL, .size = 0, .capacity = 0 })); if (table_props.string_keys_num != 0) { cur.obj->data.dictionary.items = xcalloc(table_props.string_keys_num, @@ -1191,14 +1183,14 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) case LUA_TFUNCTION: if (ref) { - *cur.obj = LUAREF_OBJ(nlua_ref(lstate, -1)); + *cur.obj = LUAREF_OBJ(nlua_ref_global(lstate, -1)); } else { goto type_error; } break; case LUA_TUSERDATA: { - nlua_pushref(lstate, nlua_nil_ref); + nlua_pushref(lstate, nlua_global_refs->nil_ref); bool is_nil = lua_rawequal(lstate, -2, -1); lua_pop(lstate, 1); if (is_nil) { @@ -1232,7 +1224,7 @@ type_error: LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err) { - LuaRef rv = nlua_ref(lstate, -1); + LuaRef rv = nlua_ref_global(lstate, -1); lua_pop(lstate, 1); return rv; } @@ -1242,7 +1234,12 @@ LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ { \ type ret; \ - ret = (type)lua_tonumber(lstate, -1); \ + if (lua_type(lstate, -1) != LUA_TNUMBER) { \ + api_set_error(err, kErrorTypeValidation, "Expected Lua number"); \ + ret = (type)-1; \ + } else { \ + ret = (type)lua_tonumber(lstate, -1); \ + } \ lua_pop(lstate, 1); \ return ret; \ } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 6a8b70a158..81396f1715 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -4,6 +4,7 @@ #include <lauxlib.h> #include <lua.h> #include <lualib.h> +#include <tree_sitter/api.h> #include "luv/luv.h" #include "nvim/api/private/defs.h" @@ -14,10 +15,12 @@ #include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cursor.h" +#include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/func_attr.h" @@ -31,7 +34,6 @@ #include "nvim/map.h" #include "nvim/memline.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/os.h" #include "nvim/screen.h" @@ -44,11 +46,19 @@ static int in_fast_callback = 0; // Initialized in nlua_init(). static lua_State *global_lstate = NULL; +static uv_thread_t main_thread; + typedef struct { Error err; String lua_err_str; } LuaError; +typedef struct { + char *name; + const uint8_t *data; + size_t size; +} ModuleDef; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/executor.c.generated.h" # include "lua/vim_module.generated.h" @@ -64,11 +74,16 @@ typedef struct { } #if __has_feature(address_sanitizer) -static PMap(handle_T) nlua_ref_markers = MAP_INIT; static bool nlua_track_refs = false; # define NLUA_TRACK_REFS #endif +typedef enum luv_err_type { + kCallback, + kThread, + kThreadCallback, +} luv_err_t; + /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -77,7 +92,22 @@ static void nlua_error(lua_State *const lstate, const char *const msg) FUNC_ATTR_NONNULL_ALL { size_t len; - const char *const str = lua_tolstring(lstate, -1, &len); + const char *str = NULL; + + if (luaL_getmetafield(lstate, -1, "__tostring")) { + if (lua_isfunction(lstate, -1) && luaL_callmeta(lstate, -2, "__tostring")) { + // call __tostring, convert the result and pop result. + str = lua_tolstring(lstate, -1, &len); + lua_pop(lstate, 1); + } + // pop __tostring. + lua_pop(lstate, 1); + } + + if (!str) { + // defer to lua default conversion, this will render tables as [NULL]. + str = lua_tolstring(lstate, -1, &len); + } msg_ext_set_kind("lua_error"); semsg_multiline(msg, (int)len, str); @@ -121,8 +151,21 @@ static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL static void nlua_luv_error_event(void **argv) { char *error = (char *)argv[0]; + luv_err_t type = (luv_err_t)(intptr_t)argv[1]; msg_ext_set_kind("lua_error"); - semsg_multiline("Error executing luv callback:\n%s", error); + switch (type) { + case kCallback: + semsg_multiline("Error executing luv callback:\n%s", error); + break; + case kThread: + semsg_multiline("Error in luv thread:\n%s", error); + break; + case kThreadCallback: + semsg_multiline("Error in luv callback, thread:\n%s", error); + break; + default: + break; + } xfree(error); } @@ -147,7 +190,7 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags const char *error = lua_tostring(lstate, -1); multiqueue_put(main_loop.events, nlua_luv_error_event, - 1, xstrdup(error)); + 2, xstrdup(error), (intptr_t)kCallback); lua_pop(lstate, 1); // error message retval = -status; } else { // LUA_OK @@ -161,12 +204,109 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags return retval; } +static int nlua_luv_thread_cb_cfpcall(lua_State *lstate, int nargs, int nresult, int flags) +{ + return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, true); +} + +static int nlua_luv_thread_cfpcall(lua_State *lstate, int nargs, int nresult, int flags) + FUNC_ATTR_NONNULL_ALL +{ + return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, false); +} + +static int nlua_luv_thread_cfcpcall(lua_State *lstate, lua_CFunction func, void *ud, int flags) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + lua_pushcfunction(lstate, func); + lua_pushlightuserdata(lstate, ud); + int retval = nlua_luv_thread_cfpcall(lstate, 1, 0, flags); + return retval; +} + +static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nresult, int flags, + bool is_callback) + FUNC_ATTR_NONNULL_ALL +{ + int retval; + + int top = lua_gettop(lstate); + int status = lua_pcall(lstate, nargs, nresult, 0); + if (status) { + if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) { + // Terminate this thread, as the main thread may be able to continue + // execution. + mch_errmsg(e_outofmem); + mch_errmsg("\n"); + lua_close(lstate); +#ifdef WIN32 + ExitThread(0); +#else + pthread_exit(0); +#endif + } + const char *error = lua_tostring(lstate, -1); + + loop_schedule_deferred(&main_loop, + event_create(nlua_luv_error_event, 2, + xstrdup(error), + is_callback + ? (intptr_t)kThreadCallback + : (intptr_t)kThread)); + lua_pop(lstate, 1); // error message + retval = -status; + } else { // LUA_OK + if (nresult == LUA_MULTRET) { + nresult = lua_gettop(lstate) - top + nargs + 1; + } + retval = nresult; + } + + return retval; +} + +static int nlua_thr_api_nvim__get_runtime(lua_State *lstate) +{ + if (lua_gettop(lstate) != 3) { + return luaL_error(lstate, "Expected 3 arguments"); + } + + luaL_checktype(lstate, -1, LUA_TTABLE); + lua_getfield(lstate, -1, "is_lua"); + if (!lua_isboolean(lstate, -1)) { + return luaL_error(lstate, "is_lua is not a boolean"); + } + bool is_lua = lua_toboolean(lstate, -1); + lua_pop(lstate, 2); + + luaL_checktype(lstate, -1, LUA_TBOOLEAN); + bool all = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + + Error err = ERROR_INIT; + const Array pat = nlua_pop_Array(lstate, &err); + if (ERROR_SET(&err)) { + luaL_where(lstate, 1); + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + lua_concat(lstate, 2); + return lua_error(lstate); + } + + ArrayOf(String) ret = runtime_get_named_thread(is_lua, pat, all); + nlua_push_Array(lstate, ret, true); + api_free_array(ret); + api_free_array(pat); + + return 1; +} + static void nlua_schedule_event(void **argv) { LuaRef cb = (LuaRef)(ptrdiff_t)argv[0]; lua_State *const lstate = global_lstate; nlua_pushref(lstate, cb); - nlua_unref(lstate, cb); + nlua_unref_global(lstate, cb); if (nlua_pcall(lstate, 0, 0)) { nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s")); } @@ -183,7 +323,7 @@ static int nlua_schedule(lua_State *const lstate) return lua_error(lstate); } - LuaRef cb = nlua_ref(lstate, 1); + LuaRef cb = nlua_ref_global(lstate, 1); multiqueue_put(main_loop.events, nlua_schedule_event, 1, (void *)(ptrdiff_t)cb); @@ -301,10 +441,154 @@ static int nlua_wait(lua_State *lstate) return 2; } +static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread) + FUNC_ATTR_NONNULL_ALL +{ + nlua_ref_state_t *ref_state = lua_newuserdata(lstate, sizeof(*ref_state)); + memset(ref_state, 0, sizeof(*ref_state)); + ref_state->nil_ref = LUA_NOREF; + ref_state->empty_dict_ref = LUA_NOREF; + if (!is_thread) { + nlua_global_refs = ref_state; + } + return ref_state; +} + +static nlua_ref_state_t *nlua_get_ref_state(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + lua_getfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state"); + nlua_ref_state_t *ref_state = lua_touserdata(lstate, -1); + lua_pop(lstate, 1); + + return ref_state; +} + +LuaRef nlua_get_nil_ref(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate); + return ref_state->nil_ref; +} + +LuaRef nlua_get_empty_dict_ref(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate); + return ref_state->empty_dict_ref; +} + +int nlua_get_global_ref_count(void) +{ + return nlua_global_refs->ref_count; +} + +static void nlua_common_vim_init(lua_State *lstate, bool is_thread) + FUNC_ATTR_NONNULL_ARG(1) +{ + nlua_ref_state_t *ref_state = nlua_new_ref_state(lstate, is_thread); + lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state"); + + // vim.is_thread + lua_pushboolean(lstate, is_thread); + lua_setfield(lstate, LUA_REGISTRYINDEX, "nvim.thread"); + lua_pushcfunction(lstate, &nlua_is_thread); + lua_setfield(lstate, -2, "is_thread"); + + // vim.NIL + lua_newuserdata(lstate, 0); + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, &nlua_nil_tostring); + lua_setfield(lstate, -2, "__tostring"); + lua_setmetatable(lstate, -2); + ref_state->nil_ref = nlua_ref(lstate, ref_state, -1); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL"); + lua_setfield(lstate, -2, "NIL"); + + // vim._empty_dict_mt + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, &nlua_empty_dict_tostring); + lua_setfield(lstate, -2, "__tostring"); + ref_state->empty_dict_ref = nlua_ref(lstate, ref_state, -1); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict"); + lua_setfield(lstate, -2, "_empty_dict_mt"); + + // vim.loop + if (is_thread) { + luv_set_callback(lstate, nlua_luv_thread_cb_cfpcall); + luv_set_thread(lstate, nlua_luv_thread_cfpcall); + luv_set_cthread(lstate, nlua_luv_thread_cfcpcall); + } else { + luv_set_loop(lstate, &main_loop.uv); + luv_set_callback(lstate, nlua_luv_cfpcall); + } + luaopen_luv(lstate); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, -3, "loop"); + + // package.loaded.luv = vim.loop + // otherwise luv will be reinitialized when require'luv' + lua_getglobal(lstate, "package"); + lua_getfield(lstate, -1, "loaded"); + lua_pushvalue(lstate, -3); + lua_setfield(lstate, -2, "luv"); + lua_pop(lstate, 3); +} + +static int nlua_module_preloader(lua_State *lstate) +{ + size_t i = (size_t)lua_tointeger(lstate, lua_upvalueindex(1)); + ModuleDef def = builtin_modules[i]; + char name[256]; + name[0] = '@'; + size_t off = xstrlcpy(name+1, def.name, (sizeof name) - 2); + strchrsub(name+1, '.', '/'); + xstrlcpy(name+1+off, ".lua", (sizeof name)-2-off); + + if (luaL_loadbuffer(lstate, (const char *)def.data, def.size - 1, name)) { + return lua_error(lstate); + } + + lua_call(lstate, 0, 1); // propagates error to caller + return 1; +} + +static bool nlua_init_packages(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + // put builtin packages in preload + lua_getglobal(lstate, "package"); // [package] + lua_getfield(lstate, -1, "preload"); // [package, preload] + for (size_t i = 0; i < ARRAY_SIZE(builtin_modules); i++) { + ModuleDef def = builtin_modules[i]; + lua_pushinteger(lstate, (long)i); // [package, preload, i] + lua_pushcclosure(lstate, nlua_module_preloader, 1); // [package, preload, cclosure] + lua_setfield(lstate, -2, def.name); // [package, preload] + + if (nlua_disable_preload && strequal(def.name, "vim.inspect")) { + break; + } + } + + lua_pop(lstate, 2); // [] + + lua_getglobal(lstate, "require"); + lua_pushstring(lstate, "vim._init_packages"); + if (nlua_pcall(lstate, 1, 0)) { + mch_errmsg(lua_tostring(lstate, -1)); + mch_errmsg("\n"); + return false; + } + + return true; +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. -static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { // print lua_pushcfunction(lstate, &nlua_print); @@ -361,108 +645,20 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_wait); lua_setfield(lstate, -2, "wait"); - // vim.NIL - lua_newuserdata(lstate, 0); - lua_createtable(lstate, 0, 0); - lua_pushcfunction(lstate, &nlua_nil_tostring); - lua_setfield(lstate, -2, "__tostring"); - lua_setmetatable(lstate, -2); - nlua_nil_ref = nlua_ref(lstate, -1); - lua_pushvalue(lstate, -1); - lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL"); - lua_setfield(lstate, -2, "NIL"); - - // vim._empty_dict_mt - lua_createtable(lstate, 0, 0); - lua_pushcfunction(lstate, &nlua_empty_dict_tostring); - lua_setfield(lstate, -2, "__tostring"); - nlua_empty_dict_ref = nlua_ref(lstate, -1); - lua_pushvalue(lstate, -1); - lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict"); - lua_setfield(lstate, -2, "_empty_dict_mt"); + nlua_common_vim_init(lstate, false); // internal vim._treesitter... API nlua_add_treesitter(lstate); - // vim.loop - luv_set_loop(lstate, &main_loop.uv); - luv_set_callback(lstate, nlua_luv_cfpcall); - luaopen_luv(lstate); - lua_pushvalue(lstate, -1); - lua_setfield(lstate, -3, "loop"); - - // package.loaded.luv = vim.loop - // otherwise luv will be reinitialized when require'luv' - lua_getglobal(lstate, "package"); - lua_getfield(lstate, -1, "loaded"); - lua_pushvalue(lstate, -3); - lua_setfield(lstate, -2, "luv"); - lua_pop(lstate, 3); - - nlua_state_add_stdlib(lstate); + nlua_state_add_stdlib(lstate, false); lua_setglobal(lstate, "vim"); - { - const char *code = (char *)&shared_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/shared.lua") - || nlua_pcall(lstate, 0, 0)) { - nlua_error(lstate, _("E5106: Error while creating shared module: %.*s")); - return 1; - } - } - - { - 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") - || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s")); - return 1; - } - // [package, loaded, inspect] - lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded] - - code = (char *)&lua_F_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/F.lua") - || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s")); - return 1; - } - // [package, loaded, module] - lua_setfield(lstate, -2, "vim.F"); // [package, loaded] - - lua_pop(lstate, 2); // [] - } - - { - const char *code = (char *)&vim_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") - || nlua_pcall(lstate, 0, 0)) { - nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); - return 1; - } - } - - { - lua_getglobal(lstate, "package"); // [package] - lua_getfield(lstate, -1, "loaded"); // [package, loaded] - - const char *code = (char *)&lua_meta_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/_meta.lua") - || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating vim._meta module: %.*s")); - return 1; - } - // [package, loaded, module] - lua_setfield(lstate, -2, "vim._meta"); // [package, loaded] - - lua_pop(lstate, 2); // [] + if (!nlua_init_packages(lstate)) { + return false; } - return 0; + return true; } /// Initialize global lua interpreter @@ -479,15 +675,66 @@ void nlua_init(void) lua_State *lstate = luaL_newstate(); if (lstate == NULL) { - emsg(_("E970: Failed to initialize lua interpreter")); - preserve_exit(); + mch_errmsg(_("E970: Failed to initialize lua interpreter\n")); + os_exit(1); } luaL_openlibs(lstate); - nlua_state_init(lstate); + if (!nlua_state_init(lstate)) { + mch_errmsg(_("E970: Failed to initialize builtin lua modules\n")); + os_exit(1); + } + + luv_set_thread_cb(nlua_thread_acquire_vm, nlua_common_free_all_mem); global_lstate = lstate; + + main_thread = uv_thread_self(); } +static lua_State *nlua_thread_acquire_vm(void) +{ + // If it is called from the main thread, it will attempt to rebuild the cache. + const uv_thread_t self = uv_thread_self(); + if (uv_thread_equal(&main_thread, &self)) { + runtime_search_path_validate(); + } + + lua_State *lstate = luaL_newstate(); + + // Add in the lua standard libraries + luaL_openlibs(lstate); + + // print + lua_pushcfunction(lstate, &nlua_print); + lua_setglobal(lstate, "print"); + + lua_pushinteger(lstate, 0); + lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.refcount"); + + // vim + lua_newtable(lstate); + + nlua_common_vim_init(lstate, true); + + nlua_state_add_stdlib(lstate, true); + + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, nlua_thr_api_nvim__get_runtime); + lua_setfield(lstate, -2, "nvim__get_runtime"); + lua_setfield(lstate, -2, "api"); + + lua_setglobal(lstate, "vim"); + + nlua_init_packages(lstate); + + lua_getglobal(lstate, "package"); + lua_getfield(lstate, -1, "loaded"); + lua_getglobal(lstate, "vim"); + lua_setfield(lstate, -2, "vim"); + lua_pop(lstate, 2); + + return lstate; +} void nlua_free_all_mem(void) { @@ -495,32 +742,39 @@ void nlua_free_all_mem(void) return; } lua_State *lstate = global_lstate; + nlua_common_free_all_mem(lstate); +} - nlua_unref(lstate, nlua_nil_ref); - nlua_unref(lstate, nlua_empty_dict_ref); +static void nlua_common_free_all_mem(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate); + nlua_unref(lstate, ref_state, ref_state->nil_ref); + nlua_unref(lstate, ref_state, ref_state->empty_dict_ref); #ifdef NLUA_TRACK_REFS - if (nlua_refcount) { - fprintf(stderr, "%d lua references were leaked!", nlua_refcount); + if (ref_state->ref_count) { + fprintf(stderr, "%d lua references were leaked!", ref_state->ref_count); } if (nlua_track_refs) { // in case there are leaked luarefs, leak the associated memory // to get LeakSanitizer stacktraces on exit - pmap_destroy(handle_T)(&nlua_ref_markers); + pmap_destroy(handle_T)(&ref_state->ref_markers); } #endif - nlua_refcount = 0; lua_close(lstate); } - static void nlua_print_event(void **argv) { char *str = argv[0]; const size_t len = (size_t)(intptr_t)argv[1]-1; // exclude final NUL for (size_t i = 0; i < len;) { + if (got_int) { + break; + } const size_t start = i; while (i < len) { switch (str[i]) { @@ -589,9 +843,18 @@ static int nlua_print(lua_State *const lstate) #undef PRINT_ERROR ga_append(&msg_ga, NUL); - if (in_fast_callback) { + lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread"); + bool is_thread = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + + if (is_thread) { + loop_schedule_deferred(&main_loop, + event_create(nlua_print_event, 2, + msg_ga.ga_data, + (intptr_t)msg_ga.ga_len)); + } else if (in_fast_callback) { multiqueue_put(main_loop.events, nlua_print_event, - 2, msg_ga.ga_data, msg_ga.ga_len); + 2, msg_ga.ga_data, (intptr_t)msg_ga.ga_len); } else { nlua_print_event((void *[]){ msg_ga.ga_data, (void *)(intptr_t)msg_ga.ga_len }); @@ -600,10 +863,12 @@ static int nlua_print(lua_State *const lstate) nlua_print_error: ga_clear(&msg_ga); + char *buff = xmalloc(IOSIZE); const char *fmt = _("E5114: Error while converting print argument #%i: %.*s"); - size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx, + size_t len = (size_t)vim_snprintf(buff, IOSIZE, fmt, curargidx, (int)errmsg_len, errmsg); - lua_pushlstring(lstate, (char *)IObuff, len); + lua_pushlstring(lstate, buff, len); + xfree(buff); return lua_error(lstate); } @@ -669,7 +934,7 @@ int nlua_call(lua_State *lstate) typval_T vim_args[MAX_FUNC_ARGS + 1]; int i = 0; // also used for freeing the variables for (; i < nargs; i++) { - lua_pushvalue(lstate, (int)i+2); + lua_pushvalue(lstate, i+2); if (!nlua_pop_typval(lstate, &vim_args[i])) { api_set_error(&err, kErrorTypeException, "error converting argument %d", i+1); @@ -734,7 +999,7 @@ static int nlua_rpc(lua_State *lstate, bool request) Array args = ARRAY_DICT_INIT; for (int i = 0; i < nargs; i++) { - lua_pushvalue(lstate, (int)i+3); + lua_pushvalue(lstate, i+3); ADD(args, nlua_pop_Object(lstate, false, &err)); if (ERROR_SET(&err)) { api_free_array(args); @@ -793,40 +1058,53 @@ static int nlua_getenv(lua_State *lstate) /// add the value to the registry -LuaRef nlua_ref(lua_State *lstate, int index) +/// The current implementation does not support calls from threads. +LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index) { lua_pushvalue(lstate, index); LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX); if (ref > 0) { - nlua_refcount++; + ref_state->ref_count++; #ifdef NLUA_TRACK_REFS if (nlua_track_refs) { // dummy allocation to make LeakSanitizer track our luarefs - pmap_put(handle_T)(&nlua_ref_markers, ref, xmalloc(3)); + pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3)); } #endif } return ref; } + +LuaRef nlua_ref_global(lua_State *lstate, int index) +{ + return nlua_ref(lstate, nlua_global_refs, index); +} + /// remove the value from the registry -void nlua_unref(lua_State *lstate, LuaRef ref) +void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref) { if (ref > 0) { - nlua_refcount--; + ref_state->ref_count--; #ifdef NLUA_TRACK_REFS // NB: don't remove entry from map to track double-unref if (nlua_track_refs) { - xfree(pmap_get(handle_T)(&nlua_ref_markers, ref)); + xfree(pmap_get(handle_T)(&ref_state->ref_markers, ref)); } #endif luaL_unref(lstate, LUA_REGISTRYINDEX, ref); } } +void nlua_unref_global(lua_State *lstate, LuaRef ref) +{ + nlua_unref(lstate, nlua_global_refs, ref); +} + + void api_free_luaref(LuaRef ref) { - nlua_unref(global_lstate, ref); + nlua_unref_global(global_lstate, ref); } /// push a value referenced in the registry @@ -848,7 +1126,7 @@ LuaRef api_new_luaref(LuaRef original_ref) lua_State *const lstate = global_lstate; nlua_pushref(lstate, original_ref); - LuaRef new_ref = nlua_ref(lstate, -1); + LuaRef new_ref = nlua_ref_global(lstate, -1); lua_pop(lstate, 1); return new_ref; } @@ -912,6 +1190,24 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg } } +void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv) + FUNC_ATTR_NONNULL_ALL +{ + lua_State *const lstate = global_lstate; + + nlua_pushref(lstate, xp->xp_luaref); + lua_pushstring(lstate, (char *)xp->xp_pattern); + lua_pushstring(lstate, (char *)xp->xp_line); + lua_pushinteger(lstate, xp->xp_col); + + if (nlua_pcall(lstate, 3, 1)) { + nlua_error(lstate, _("E5108: Error executing Lua function: %.*s")); + return; + } + + nlua_pop_typval(lstate, ret_tv); +} + static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name, typval_T *const args, int argcount, bool special, typval_T *ret_tv) { @@ -1032,6 +1328,19 @@ Object nlua_exec(const String str, const Array args, Error *err) return nlua_pop_Object(lstate, false, err); } +bool nlua_ref_is_function(LuaRef ref) +{ + lua_State *const lstate = global_lstate; + nlua_pushref(lstate, ref); + + // TODO(tjdevries): This should probably check for callable tables as well. + // We should put some work maybe into simplifying how all of that works + bool is_function = (lua_type(lstate, -1) == LUA_TFUNCTION); + lua_pop(lstate, 1); + + return is_function; +} + /// call a LuaRef as a function (or table with __call metamethod) /// /// @param ref the reference to call (not consumed) @@ -1094,11 +1403,23 @@ void ex_lua(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { size_t len; - char *const code = script_get(eap, &len); + char *code = script_get(eap, &len); if (eap->skip) { xfree(code); return; } + // When =expr is used transform it to print(vim.inspect(expr)) + if (code[0] == '=') { + len += sizeof("vim.pretty_print()") - sizeof("="); + // code_buf needs to be 1 char larger then len for null byte in the end. + // lua nlua_typval_exec doesn't expect null terminated string so len + // needs to end before null byte. + char *code_buf = xmallocz(len); + vim_snprintf(code_buf, len+1, "vim.pretty_print(%s)", code+1); + xfree(code); + code = code_buf; + } + nlua_typval_exec(code, len, ":lua", NULL, 0, false, NULL); xfree(code); @@ -1198,6 +1519,9 @@ void ex_luafile(exarg_T *const eap) /// execute lua code from a file. /// +/// Note: we call the lua global loadfile as opposed to calling luaL_loadfile +/// in case loadfile has been overridden in the users environment. +/// /// @param path path of the file /// /// @return true if everything ok, false if there was an error (echoed) @@ -1206,11 +1530,30 @@ bool nlua_exec_file(const char *path) { lua_State *const lstate = global_lstate; - if (luaL_loadfile(lstate, path)) { + lua_getglobal(lstate, "loadfile"); + lua_pushstring(lstate, path); + + if (nlua_pcall(lstate, 1, 2)) { + nlua_error(lstate, _("E5111: Error calling lua: %.*s")); + return false; + } + + // loadstring() returns either: + // 1. nil, error + // 2. chunk, nil + + if (lua_isnil(lstate, -2)) { + // 1 nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); + assert(lua_isnil(lstate, -1)); + lua_pop(lstate, 1); return false; } + // 2 + assert(lua_isnil(lstate, -1)); + lua_pop(lstate, 1); + if (nlua_pcall(lstate, 0, 0)) { nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); return false; @@ -1225,6 +1568,12 @@ int tslua_get_language_version(lua_State *L) return 1; } +int tslua_get_minimum_language_version(lua_State *L) +{ + lua_pushnumber(L, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION); + return 1; +} + static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { tslua_init(lstate); @@ -1246,6 +1595,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_get_language_version); lua_setfield(lstate, -2, "_ts_get_language_version"); + + lua_pushcfunction(lstate, tslua_get_minimum_language_version); + lua_setfield(lstate, -2, "_ts_get_minimum_language_version"); } int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char_u ***results) @@ -1317,6 +1669,13 @@ cleanup: return ret; } +static int nlua_is_thread(lua_State *lstate) +{ + lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread"); + + return 1; +} + // Required functions for lua c functions as VimL callbacks int nlua_CFunction_func_call(int argcount, typval_T *argvars, typval_T *rettv, void *state) @@ -1333,7 +1692,7 @@ void nlua_CFunction_func_free(void *state) lua_State *const lstate = global_lstate; LuaCFunctionState *funcstate = (LuaCFunctionState *)state; - nlua_unref(lstate, funcstate->lua_callable.func_ref); + nlua_unref_global(lstate, funcstate->lua_callable.func_ref); xfree(funcstate); } @@ -1383,7 +1742,7 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) lua_pop(lstate, 2); // [table] LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); - state->lua_callable.func_ref = nlua_ref(lstate, -1); + state->lua_callable.func_ref = nlua_ref_global(lstate, -1); char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state); @@ -1430,3 +1789,127 @@ void nlua_execute_on_key(int c) #endif } +// Sets the editor "script context" during Lua execution. Used by :verbose. +// @param[out] current +void nlua_set_sctx(sctx_T *current) +{ + if (p_verbose <= 0 || current->sc_sid != SID_LUA) { + return; + } + lua_State *const lstate = global_lstate; + lua_Debug *info = (lua_Debug *)xmalloc(sizeof(lua_Debug)); + + // Files where internal wrappers are defined so we can ignore them + // like vim.o/opt etc are defined in _meta.lua + char *ignorelist[] = { + "vim/_meta.lua", + "vim/keymap.lua", + }; + int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]); + + for (int level = 1; true; level++) { + if (lua_getstack(lstate, level, info) != 1) { + goto cleanup; + } + if (lua_getinfo(lstate, "nSl", info) == 0) { + goto cleanup; + } + + bool is_ignored = false; + if (info->what[0] == 'C' || info->source[0] != '@') { + is_ignored = true; + } else { + for (int i = 0; i < ignorelist_size; i++) { + if (strncmp(ignorelist[i], info->source+1, strlen(ignorelist[i])) == 0) { + is_ignored = true; + break; + } + } + } + if (is_ignored) { + continue; + } + break; + } + char *source_path = fix_fname(info->source + 1); + get_current_script_id((char_u *)source_path, current); + xfree(source_path); + current->sc_lnum = info->currentline; + current->sc_seq = -1; + +cleanup: + xfree(info); +} + +void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap) +{ + lua_State *const lstate = global_lstate; + + nlua_pushref(lstate, cmd->uc_luaref); + + lua_newtable(lstate); + lua_pushboolean(lstate, eap->forceit == 1); + lua_setfield(lstate, -2, "bang"); + + lua_pushinteger(lstate, eap->line1); + lua_setfield(lstate, -2, "line1"); + + lua_pushinteger(lstate, eap->line2); + lua_setfield(lstate, -2, "line2"); + + lua_newtable(lstate); // f-args table + lua_pushstring(lstate, (const char *)eap->arg); + lua_pushvalue(lstate, -1); // Reference for potential use on f-args + lua_setfield(lstate, -4, "args"); + + // Split args by unescaped whitespace |<f-args>| (nargs dependent) + if (cmd->uc_argt & EX_NOSPC) { + // Commands where nargs = 1 or "?" fargs is the same as args + lua_rawseti(lstate, -2, 1); + } else { + // Commands with more than one possible argument we split + lua_pop(lstate, 1); // Pop the reference of opts.args + size_t length = STRLEN(eap->arg); + size_t end = 0; + size_t len = 0; + int i = 1; + char *buf = xcalloc(length, sizeof(char)); + bool done = false; + while (!done) { + done = uc_split_args_iter(eap->arg, length, &end, buf, &len); + if (len > 0) { + lua_pushlstring(lstate, buf, len); + lua_rawseti(lstate, -2, i); + i++; + } + } + xfree(buf); + } + lua_setfield(lstate, -2, "fargs"); + + lua_pushstring(lstate, (const char *)&eap->regname); + lua_setfield(lstate, -2, "reg"); + + lua_pushinteger(lstate, eap->addr_count); + lua_setfield(lstate, -2, "range"); + + if (eap->addr_count > 0) { + lua_pushinteger(lstate, eap->line2); + } else { + lua_pushinteger(lstate, cmd->uc_def); + } + lua_setfield(lstate, -2, "count"); + + // The size of this buffer is chosen empirically to be large enough to hold + // every possible modifier (with room to spare). If the list of possible + // modifiers grows this may need to be updated. + char buf[200] = { 0 }; + (void)uc_mods(buf); + lua_pushstring(lstate, buf); + lua_setfield(lstate, -2, "mods"); + + if (nlua_pcall(lstate, 1, 0)) { + nlua_error(lstate, _("Error executing Lua callback: %.*s")); + } +} + diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index a1f66bd02b..e96494ec5a 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -5,26 +5,24 @@ #include <lua.h> #include "nvim/api/private/defs.h" +#include "nvim/assert.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/func_attr.h" #include "nvim/lua/converter.h" // Generated by msgpack-gen.lua void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; -EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF); -EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF); - -EXTERN int nlua_refcount INIT(= 0); - -#define set_api_error(s, err) \ - do { \ - Error *err_ = (err); \ - err_->type = kErrorTypeException; \ - err_->set = true; \ - memcpy(&err_->msg[0], s, sizeof(s)); \ - } while (0) +typedef struct { + LuaRef nil_ref; + LuaRef empty_dict_ref; + int ref_count; +#if __has_feature(address_sanitizer) + PMap(handle_T) ref_markers; +#endif +} nlua_ref_state_t; #define NLUA_CLEAR_REF(x) \ do { \ @@ -38,4 +36,8 @@ EXTERN int nlua_refcount INIT(= 0); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/executor.h.generated.h" #endif + +EXTERN nlua_ref_state_t *nlua_global_refs INIT(= NULL); +EXTERN bool nlua_disable_preload INIT(= false); + #endif // NVIM_LUA_EXECUTOR_H diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c new file mode 100644 index 0000000000..31a2b2d19f --- /dev/null +++ b/src/nvim/lua/spell.c @@ -0,0 +1,101 @@ +// 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 <lauxlib.h> +#include <lua.h> + +#include "nvim/lua/spell.h" +#include "nvim/spell.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/spell.c.generated.h" +#endif + +int nlua_spell_check(lua_State *lstate) +{ + if (lua_gettop(lstate) < 1) { + return luaL_error(lstate, "Expected 1 argument"); + } + + if (lua_type(lstate, 1) != LUA_TSTRING) { + luaL_argerror(lstate, 1, "expected string"); + } + + const char *str = lua_tolstring(lstate, 1, NULL); + + // spell.c requires that 'spell' is enabled, so we need to temporarily enable + // it before we can call spell functions. + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + // Check 'spelllang' + if (*curwin->w_s->b_p_spl == NUL) { + emsg(_(e_no_spell)); + curwin->w_p_spell = wo_spell_save; + return 0; + } + + hlf_T attr = HLF_COUNT; + size_t len = 0; + size_t pos = 0; + int capcol = -1; + int no_res = 0; + const char *result; + + lua_createtable(lstate, 0, 0); + + while (*str != NUL) { + attr = HLF_COUNT; + len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); + assert(len <= INT_MAX); + + if (attr != HLF_COUNT) { + lua_createtable(lstate, 3, 0); + + lua_pushlstring(lstate, str, len); + lua_rawseti(lstate, -2, 1); + + result = attr == HLF_SPB ? "bad" : + attr == HLF_SPR ? "rare" : + attr == HLF_SPL ? "local" : + attr == HLF_SPC ? "caps" : + NULL; + + assert(result != NULL); + + lua_pushstring(lstate, result); + lua_rawseti(lstate, -2, 2); + + // +1 for 1-indexing + lua_pushinteger(lstate, (long)pos + 1); + lua_rawseti(lstate, -2, 3); + + lua_rawseti(lstate, -2, ++no_res); + } + + str += len; + pos += len; + capcol -= (int)len; + } + + // Restore 'spell' + curwin->w_p_spell = wo_spell_save; + return 1; +} + +static const luaL_Reg spell_functions[] = { + { "check", nlua_spell_check }, + { NULL, NULL } +}; + +int luaopen_spell(lua_State *L) +{ + lua_newtable(L); + luaL_register(L, NULL, spell_functions); + return 1; +} diff --git a/src/nvim/lua/spell.h b/src/nvim/lua/spell.h new file mode 100644 index 0000000000..8f798a5191 --- /dev/null +++ b/src/nvim/lua/spell.h @@ -0,0 +1,12 @@ +#ifndef NVIM_LUA_SPELL_H +#define NVIM_LUA_SPELL_H + +#include <lauxlib.h> +#include <lua.h> +#include <lualib.h> + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/spell.h.generated.h" +#endif + +#endif // NVIM_LUA_SPELL_H diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index db79e9e7e9..e94c61b37c 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -25,8 +25,10 @@ #include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" +#include "nvim/lua/spell.h" #include "nvim/lua/stdlib.h" #include "nvim/lua/treesitter.h" #include "nvim/lua/xdiff.h" @@ -34,7 +36,6 @@ #include "nvim/map.h" #include "nvim/memline.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/os.h" #include "nvim/regexp.h" @@ -175,13 +176,13 @@ int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL size_t s1_len; const char *s1 = luaL_checklstring(lstate, 1, &s1_len); intptr_t idx; - if (lua_gettop(lstate) >= 2) { + if (lua_isnoneornil(lstate, 2)) { + idx = (intptr_t)s1_len; + } else { idx = luaL_checkinteger(lstate, 2); if (idx < 0 || idx > (intptr_t)s1_len) { return luaL_error(lstate, "index out of range"); } - } else { - idx = (intptr_t)s1_len; } size_t codepoints = 0, codeunits = 0; @@ -231,8 +232,8 @@ static int nlua_str_utf_start(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL if (offset < 0 || offset > (intptr_t)s1_len) { return luaL_error(lstate, "index out of range"); } - int tail_offset = mb_head_off((char_u *)s1, (char_u *)s1 + (char_u)offset - 1); - lua_pushinteger(lstate, tail_offset); + int head_offset = mb_head_off((char_u *)s1, (char_u *)s1 + offset - 1); + lua_pushinteger(lstate, head_offset); return 1; } @@ -251,7 +252,7 @@ static int nlua_str_utf_end(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL if (offset < 0 || offset > (intptr_t)s1_len) { return luaL_error(lstate, "index out of range"); } - int tail_offset = mb_tail_off((char_u *)s1, (char_u *)s1 + (char_u)offset - 1); + int tail_offset = mb_tail_off((char_u *)s1, (char_u *)s1 + offset - 1); lua_pushinteger(lstate, tail_offset); return 1; } @@ -408,6 +409,12 @@ int nlua_getvar(lua_State *lstate) const char *name = luaL_checklstring(lstate, 3, &len); dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len); + if (di == NULL && dict == &globvardict) { // try to autoload script + if (!script_autoload(name, len, false) || aborting()) { + return 0; // nil + } + di = tv_dict_find(dict, name, (ptrdiff_t)len); + } if (di == NULL) { return 0; // nil } @@ -464,43 +471,52 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL } -void nlua_state_add_stdlib(lua_State *const lstate) +void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) { - // stricmp - lua_pushcfunction(lstate, &nlua_stricmp); - lua_setfield(lstate, -2, "stricmp"); - // str_utfindex - lua_pushcfunction(lstate, &nlua_str_utfindex); - lua_setfield(lstate, -2, "str_utfindex"); - // str_byteindex - lua_pushcfunction(lstate, &nlua_str_byteindex); - lua_setfield(lstate, -2, "str_byteindex"); - // str_utf_pos - lua_pushcfunction(lstate, &nlua_str_utf_pos); - lua_setfield(lstate, -2, "str_utf_pos"); - // str_utf_start - lua_pushcfunction(lstate, &nlua_str_utf_start); - lua_setfield(lstate, -2, "str_utf_start"); - // str_utf_end - lua_pushcfunction(lstate, &nlua_str_utf_end); - lua_setfield(lstate, -2, "str_utf_end"); - // regex - lua_pushcfunction(lstate, &nlua_regex); - lua_setfield(lstate, -2, "regex"); - luaL_newmetatable(lstate, "nvim_regex"); - luaL_register(lstate, NULL, regex_meta); - - lua_pushvalue(lstate, -1); // [meta, meta] - lua_setfield(lstate, -2, "__index"); // [meta] - lua_pop(lstate, 1); // don't use metatable now - - // _getvar - lua_pushcfunction(lstate, &nlua_getvar); - lua_setfield(lstate, -2, "_getvar"); - - // _setvar - lua_pushcfunction(lstate, &nlua_setvar); - lua_setfield(lstate, -2, "_setvar"); + if (!is_thread) { + // TODO(bfredl): some of basic string functions should already be + // (or be easy to make) threadsafe + + // stricmp + lua_pushcfunction(lstate, &nlua_stricmp); + lua_setfield(lstate, -2, "stricmp"); + // str_utfindex + lua_pushcfunction(lstate, &nlua_str_utfindex); + lua_setfield(lstate, -2, "str_utfindex"); + // str_byteindex + lua_pushcfunction(lstate, &nlua_str_byteindex); + lua_setfield(lstate, -2, "str_byteindex"); + // str_utf_pos + lua_pushcfunction(lstate, &nlua_str_utf_pos); + lua_setfield(lstate, -2, "str_utf_pos"); + // str_utf_start + lua_pushcfunction(lstate, &nlua_str_utf_start); + lua_setfield(lstate, -2, "str_utf_start"); + // str_utf_end + lua_pushcfunction(lstate, &nlua_str_utf_end); + lua_setfield(lstate, -2, "str_utf_end"); + // regex + lua_pushcfunction(lstate, &nlua_regex); + lua_setfield(lstate, -2, "regex"); + luaL_newmetatable(lstate, "nvim_regex"); + luaL_register(lstate, NULL, regex_meta); + + lua_pushvalue(lstate, -1); // [meta, meta] + lua_setfield(lstate, -2, "__index"); // [meta] + lua_pop(lstate, 1); // don't use metatable now + + // _getvar + lua_pushcfunction(lstate, &nlua_getvar); + lua_setfield(lstate, -2, "_getvar"); + + // _setvar + lua_pushcfunction(lstate, &nlua_setvar); + lua_setfield(lstate, -2, "_setvar"); + + // vim.spell + luaopen_spell(lstate); + lua_setfield(lstate, -2, "spell"); + } // vim.mpack luaopen_mpack(lstate); @@ -519,6 +535,7 @@ void nlua_state_add_stdlib(lua_State *const lstate) lua_pushcfunction(lstate, &nlua_xdl_diff); lua_setfield(lstate, -2, "diff"); + // vim.json lua_cjson_new(lstate); lua_setfield(lstate, -2, "json"); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 60a000843f..f4067ad02f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -129,6 +129,10 @@ void tslua_init(lua_State *L) build_meta(L, TS_META_QUERY, query_meta); build_meta(L, TS_META_QUERYCURSOR, querycursor_meta); build_meta(L, TS_META_TREECURSOR, treecursor_meta); + +#ifdef NVIM_TS_HAS_SET_ALLOCATOR + ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree); +#endif } int tslua_has_language(lua_State *L) diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua deleted file mode 100644 index 30c7034209..0000000000 --- a/src/nvim/lua/vim.lua +++ /dev/null @@ -1,665 +0,0 @@ --- Nvim-Lua stdlib: the `vim` module (:help lua-stdlib) --- --- Lua code lives in one of three places: --- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the --- `inspect` and `lpeg` modules. --- 2. runtime/lua/vim/shared.lua: Code shared between Nvim and tests. --- (This will go away if we migrate to nvim as the test-runner.) --- 3. src/nvim/lua/: Compiled-into Nvim itself. --- --- Guideline: "If in doubt, put it in the runtime". --- --- Most functions should live directly in `vim.`, not in submodules. --- The only "forbidden" names are those claimed by legacy `if_lua`: --- $ vim --- :lua for k,v in pairs(vim) do print(k) end --- buffer --- open --- window --- lastline --- firstline --- type --- line --- eval --- dict --- beep --- list --- command --- --- Reference (#6580): --- - https://github.com/luafun/luafun --- - https://github.com/rxi/lume --- - http://leafo.net/lapis/reference/utilities.html --- - https://github.com/torch/paths --- - https://github.com/bakpakin/Fennel (pretty print, repl) --- - https://github.com/howl-editor/howl/tree/master/lib/howl/util - -local vim = vim -assert(vim) - -vim.inspect = package.loaded['vim.inspect'] -assert(vim.inspect) - -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"} - local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true}) - if #found > 0 then - local f, err = loadfile(found[1]) - return f or error(err) - end - - local so_paths = {} - for _,trail in ipairs(vim._so_trails) do - local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash - table.insert(so_paths, path) - end - - found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true}) - if #found > 0 then - -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is - -- a) strip prefix up to and including the first dash, if any - -- b) replace all dots by underscores - -- c) prepend "luaopen_" - -- So "foo-bar.baz" should result in "luaopen_bar_baz" - local dash = name:find("-", 1, true) - local modname = dash and name:sub(dash + 1) or name - local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_")) - return f or error(err) - end - return nil -end - -table.insert(package.loaders, 1, vim._load_package) - --- These are for loading runtime modules lazily since they aren't available in --- the nvim binary as specified in executor.c -setmetatable(vim, { - __index = function(t, key) - if key == 'treesitter' then - t.treesitter = require('vim.treesitter') - return t.treesitter - elseif key == 'F' then - t.F = require('vim.F') - return t.F - elseif require('vim.uri')[key] ~= nil then - -- Expose all `vim.uri` functions on the `vim` module. - t[key] = require('vim.uri')[key] - return t[key] - elseif key == 'lsp' then - t.lsp = require('vim.lsp') - return t.lsp - elseif key == 'highlight' then - t.highlight = require('vim.highlight') - return t.highlight - elseif key == 'diagnostic' then - t.diagnostic = require('vim.diagnostic') - return t.diagnostic - elseif key == 'ui' then - t.ui = require('vim.ui') - return t.ui - end - end -}) - -vim.log = { - levels = { - TRACE = 0; - DEBUG = 1; - INFO = 2; - WARN = 3; - ERROR = 4; - } -} - --- Internal-only until comments in #8107 are addressed. --- Returns: --- {errcode}, {output} -function vim._system(cmd) - local out = vim.fn.system(cmd) - local err = vim.v.shell_error - return err, out -end - --- Gets process info from the `ps` command. --- Used by nvim_get_proc() as a fallback. -function vim._os_proc_info(pid) - if pid == nil or pid <= 0 or type(pid) ~= 'number' then - error('invalid pid') - end - local cmd = { 'ps', '-p', pid, '-o', 'comm=', } - local err, name = vim._system(cmd) - if 1 == err and vim.trim(name) == '' then - return {} -- Process not found. - elseif 0 ~= err then - error('command failed: '..vim.fn.string(cmd)) - end - local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', }) - -- Remove trailing whitespace. - name = vim.trim(name):gsub('^.*/', '') - ppid = tonumber(ppid) or -1 - return { - name = name, - pid = pid, - ppid = ppid, - } -end - --- Gets process children from the `pgrep` command. --- Used by nvim_get_proc_children() as a fallback. -function vim._os_proc_children(ppid) - if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then - error('invalid ppid') - end - local cmd = { 'pgrep', '-P', ppid, } - local err, rv = vim._system(cmd) - if 1 == err and vim.trim(rv) == '' then - return {} -- Process not found. - elseif 0 ~= err then - error('command failed: '..vim.fn.string(cmd)) - end - local children = {} - for s in rv:gmatch('%S+') do - local i = tonumber(s) - if i ~= nil then - table.insert(children, i) - end - end - return children -end - --- TODO(ZyX-I): Create compatibility layer. - ---- Return a human-readable representation of the given object. ---- ----@see https://github.com/kikito/inspect.lua ----@see https://github.com/mpeterv/vinspect -local function inspect(object, options) -- luacheck: no unused - error(object, options) -- Stub for gen_vimdoc.py -end - -do - local tdots, tick, got_line1 = 0, 0, false - - --- Paste handler, invoked by |nvim_paste()| when a conforming UI - --- (such as the |TUI|) pastes text into the editor. - --- - --- Example: To remove ANSI color codes when pasting: - --- <pre> - --- vim.paste = (function(overridden) - --- return function(lines, phase) - --- for i,line in ipairs(lines) do - --- -- Scrub ANSI color codes from paste input. - --- lines[i] = line:gsub('\27%[[0-9;mK]+', '') - --- end - --- overridden(lines, phase) - --- end - --- end)(vim.paste) - --- </pre> - --- - ---@see |paste| - --- - ---@param lines |readfile()|-style list of lines to paste. |channel-lines| - ---@param phase -1: "non-streaming" paste: the call contains all lines. - --- If paste is "streamed", `phase` indicates the stream state: - --- - 1: starts the paste (exactly once) - --- - 2: continues the paste (zero or more times) - --- - 3: ends the paste (exactly once) - ---@returns false if client should cancel the paste. - function vim.paste(lines, phase) - local call = vim.api.nvim_call_function - local now = vim.loop.now() - local mode = call('mode', {}):sub(1,1) - if phase < 2 then -- Reset flags. - tdots, tick, got_line1 = now, 0, false - elseif mode ~= 'c' then - vim.api.nvim_command('undojoin') - end - if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line. - got_line1 = (#lines > 1) - vim.api.nvim_set_option('paste', true) -- For nvim_input(). - local line1 = lines[1]:gsub('<', '<lt>'):gsub('[\r\n\012\027]', ' ') -- Scrub. - vim.api.nvim_input(line1) - vim.api.nvim_set_option('paste', false) - elseif mode ~= 'c' then - if phase < 2 and mode:find('^[vV\22sS\19]') then - vim.api.nvim_command([[exe "normal! \<Del>"]]) - vim.api.nvim_put(lines, 'c', false, true) - elseif phase < 2 and not mode:find('^[iRt]') then - vim.api.nvim_put(lines, 'c', true, true) - -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. - vim.api.nvim_command('normal! a') - elseif phase < 2 and mode == 'R' then - local nchars = 0 - for _, line in ipairs(lines) do - nchars = nchars + line:len() - end - local row, col = unpack(vim.api.nvim_win_get_cursor(0)) - local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] - local firstline = lines[1] - firstline = bufline:sub(1, col)..firstline - lines[1] = firstline - lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) - vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) - else - vim.api.nvim_put(lines, 'c', false, true) - end - end - if phase ~= -1 and (now - tdots >= 100) then - local dots = ('.'):rep(tick % 4) - tdots = now - tick = tick + 1 - -- Use :echo because Lua print('') is a no-op, and we want to clear the - -- message when there are zero dots. - vim.api.nvim_command(('echo "%s"'):format(dots)) - end - if phase == -1 or phase == 3 then - vim.api.nvim_command('redraw'..(tick > 1 and '|echo ""' or '')) - end - return true -- Paste will not continue if not returning `true`. - end -end - ---- Defers callback `cb` until the Nvim API is safe to call. ---- ----@see |lua-loop-callbacks| ----@see |vim.schedule()| ----@see |vim.in_fast_event()| -function vim.schedule_wrap(cb) - return (function (...) - local args = vim.F.pack_len(...) - vim.schedule(function() cb(vim.F.unpack_len(args)) end) - end) -end - ---- <Docs described in |vim.empty_dict()| > ----@private -function vim.empty_dict() - return setmetatable({}, vim._empty_dict_mt) -end - --- vim.fn.{func}(...) -vim.fn = setmetatable({}, { - __index = function(t, key) - local _fn - if vim.api[key] ~= nil then - _fn = function() - error(string.format("Tried to call API function with vim.fn: use vim.api.%s instead", key)) - end - else - _fn = function(...) - return vim.call(key, ...) - end - end - t[key] = _fn - return _fn - end -}) - -vim.funcref = function(viml_func_name) - return vim.fn[viml_func_name] -end - --- An easier alias for commands. -vim.cmd = function(command) - return vim.api.nvim_exec(command, false) -end - --- These are the vim.env/v/g/o/bo/wo variable magic accessors. -do - local validate = vim.validate - - local function make_dict_accessor(scope, handle) - validate { - scope = {scope, 's'}; - } - local mt = {} - function mt:__newindex(k, v) - return vim._setvar(scope, handle or 0, k, v) - end - function mt:__index(k) - if handle == nil and type(k) == 'number' then - return make_dict_accessor(scope, k) - end - return vim._getvar(scope, handle or 0, k) - end - return setmetatable({}, mt) - end - - vim.g = make_dict_accessor('g', false) - vim.v = make_dict_accessor('v', false) - vim.b = make_dict_accessor('b') - vim.w = make_dict_accessor('w') - vim.t = make_dict_accessor('t') -end - ---- Get a table of lines with start, end columns for a region marked by two points ---- ----@param bufnr number of buffer ----@param pos1 (line, column) tuple marking beginning of region ----@param pos2 (line, column) tuple marking end of region ----@param regtype type of selection (:help setreg) ----@param inclusive boolean indicating whether the selection is end-inclusive ----@return region lua table of the form {linenr = {startcol,endcol}} -function vim.region(bufnr, pos1, pos2, regtype, inclusive) - if not vim.api.nvim_buf_is_loaded(bufnr) then - vim.fn.bufload(bufnr) - end - - -- check that region falls within current buffer - local buf_line_count = vim.api.nvim_buf_line_count(bufnr) - pos1[1] = math.min(pos1[1], buf_line_count - 1) - pos2[1] = math.min(pos2[1], buf_line_count - 1) - - -- in case of block selection, columns need to be adjusted for non-ASCII characters - -- TODO: handle double-width characters - local bufline - if regtype:byte() == 22 then - bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1] - pos1[2] = vim.str_utfindex(bufline, pos1[2]) - end - - local region = {} - for l = pos1[1], pos2[1] do - local c1, c2 - if regtype:byte() == 22 then -- block selection: take width from regtype - c1 = pos1[2] - c2 = c1 + regtype:sub(2) - -- and adjust for non-ASCII characters - bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1] - if c1 < #bufline then - c1 = vim.str_byteindex(bufline, c1) - end - if c2 < #bufline then - c2 = vim.str_byteindex(bufline, c2) - end - else - c1 = (l == pos1[1]) and (pos1[2]) or 0 - c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1 - end - table.insert(region, l, {c1, c2}) - end - return region -end - ---- Defers calling `fn` until `timeout` ms passes. ---- ---- Use to do a one-shot timer that calls `fn` ---- Note: The {fn} is |schedule_wrap|ped automatically, so API functions are ---- safe to call. ----@param fn Callback to call once `timeout` expires ----@param timeout Number of milliseconds to wait before calling `fn` ----@return timer luv timer object -function vim.defer_fn(fn, timeout) - vim.validate { fn = { fn, 'c', true}; } - local timer = vim.loop.new_timer() - timer:start(timeout, 0, vim.schedule_wrap(function() - timer:stop() - timer:close() - - fn() - end)) - - return timer -end - - ---- Notification provider ---- ---- Without a runtime, writes to :Messages ----@see :help nvim_notify ----@param msg Content of the notification to show to the user ----@param log_level Optional log level ----@param opts Dictionary with optional options (timeout, etc) -function vim.notify(msg, log_level, _opts) - - if log_level == vim.log.levels.ERROR then - vim.api.nvim_err_writeln(msg) - elseif log_level == vim.log.levels.WARN then - vim.api.nvim_echo({{msg, 'WarningMsg'}}, true, {}) - else - vim.api.nvim_echo({{msg}}, true, {}) - end -end - - ----@private -function vim.register_keystroke_callback() - error('vim.register_keystroke_callback is deprecated, instead use: vim.on_key') -end - -local on_key_cbs = {} - ---- Adds Lua function {fn} with namespace id {ns_id} as a listener to every, ---- yes every, input key. ---- ---- The Nvim command-line option |-w| is related but does not support callbacks ---- and cannot be toggled dynamically. ---- ----@param fn function: Callback function. It should take one string argument. ---- On each key press, Nvim passes the key char to fn(). |i_CTRL-V| ---- If {fn} is nil, it removes the callback for the associated {ns_id} ----@param ns_id number? Namespace ID. If nil or 0, generates and returns a new ---- |nvim_create_namesapce()| id. ---- ----@return number Namespace id associated with {fn}. Or count of all callbacks ----if on_key() is called without arguments. ---- ----@note {fn} will be removed if an error occurs while calling. ----@note {fn} will not be cleared by |nvim_buf_clear_namespace()| ----@note {fn} will receive the keys after mappings have been evaluated -function vim.on_key(fn, ns_id) - if fn == nil and ns_id == nil then - return #on_key_cbs - end - - 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_key_cbs[ns_id] = fn - return ns_id -end - ---- Executes the on_key callbacks. ----@private -function vim._on_key(char) - local failed_ns_ids = {} - local failed_messages = {} - for k, v in pairs(on_key_cbs) do - local ok, err_msg = pcall(v, char) - if not ok then - vim.on_key(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_key' with ns_ids '%s'\n Messages: %s", - table.concat(failed_ns_ids, ", "), - table.concat(failed_messages, "\n"))) - end -end - ---- Generate a list of possible completions for the string. ---- String starts with ^ and then has the pattern. ---- ---- 1. Can we get it to just return things in the global namespace with that name prefix ---- 2. Can we get it to return things from global namespace even with `print(` in front. -function vim._expand_pat(pat, env) - env = env or _G - - pat = string.sub(pat, 2, #pat) - - if pat == '' then - local result = vim.tbl_keys(env) - table.sort(result) - return result, 0 - end - - -- TODO: We can handle spaces in [] ONLY. - -- We should probably do that at some point, just for cooler completion. - -- TODO: We can suggest the variable names to go in [] - -- This would be difficult as well. - -- Probably just need to do a smarter match than just `:match` - - -- Get the last part of the pattern - local last_part = pat:match("[%w.:_%[%]'\"]+$") - if not last_part then return {}, 0 end - - local parts, search_index = vim._expand_pat_get_parts(last_part) - - local match_part = string.sub(last_part, search_index, #last_part) - local prefix_match_pat = string.sub(pat, 1, #pat - #match_part) or '' - - local final_env = env - - for _, part in ipairs(parts) do - if type(final_env) ~= 'table' then - return {}, 0 - end - local key - - -- Normally, we just have a string - -- Just attempt to get the string directly from the environment - if type(part) == "string" then - key = part - else - -- However, sometimes you want to use a variable, and complete on it - -- With this, you have the power. - - -- MY_VAR = "api" - -- vim[MY_VAR] - -- -> _G[MY_VAR] -> "api" - local result_key = part[1] - if not result_key then - return {}, 0 - end - - local result = rawget(env, result_key) - - if result == nil then - return {}, 0 - end - - key = result - end - local field = rawget(final_env, key) - if field == nil then - local mt = getmetatable(final_env) - if mt and type(mt.__index) == "table" then - field = rawget(mt.__index, key) - end - end - final_env = field - - if not final_env then - return {}, 0 - end - end - - local keys = {} - local function insert_keys(obj) - for k,_ in pairs(obj) do - if type(k) == "string" and string.sub(k,1,string.len(match_part)) == match_part then - table.insert(keys,k) - end - end - end - - if type(final_env) == "table" then - insert_keys(final_env) - end - local mt = getmetatable(final_env) - if mt and type(mt.__index) == "table" then - insert_keys(mt.__index) - end - - table.sort(keys) - - return keys, #prefix_match_pat -end - -vim._expand_pat_get_parts = function(lua_string) - local parts = {} - - local accumulator, search_index = '', 1 - local in_brackets, bracket_end = false, -1 - local string_char = nil - for idx = 1, #lua_string do - local s = lua_string:sub(idx, idx) - - if not in_brackets and (s == "." or s == ":") then - table.insert(parts, accumulator) - accumulator = '' - - search_index = idx + 1 - elseif s == "[" then - in_brackets = true - - table.insert(parts, accumulator) - accumulator = '' - - search_index = idx + 1 - elseif in_brackets then - if idx == bracket_end then - in_brackets = false - search_index = idx + 1 - - if string_char == "VAR" then - table.insert(parts, { accumulator }) - accumulator = '' - - string_char = nil - end - elseif not string_char then - bracket_end = string.find(lua_string, ']', idx, true) - - if s == '"' or s == "'" then - string_char = s - elseif s ~= ' ' then - string_char = "VAR" - accumulator = s - end - elseif string_char then - if string_char ~= s then - accumulator = accumulator .. s - else - table.insert(parts, accumulator) - accumulator = '' - - string_char = nil - end - end - else - accumulator = accumulator .. s - end - end - - parts = vim.tbl_filter(function(val) return #val > 0 end, parts) - - return parts, search_index -end - -return module diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index b2e971f9f3..37855630d1 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -171,6 +171,7 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, goto exit_1; } if (strequal("unified", v->data.string.data)) { + // the default } else if (strequal("indices", v->data.string.data)) { had_result_type_indices = true; } else { @@ -184,11 +185,11 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, if (strequal("myers", v->data.string.data)) { // default } else if (strequal("minimal", v->data.string.data)) { - cfg->flags |= XDF_NEED_MINIMAL; + params->flags |= XDF_NEED_MINIMAL; } else if (strequal("patience", v->data.string.data)) { - cfg->flags |= XDF_PATIENCE_DIFF; + params->flags |= XDF_PATIENCE_DIFF; } else if (strequal("histogram", v->data.string.data)) { - cfg->flags |= XDF_HISTOGRAM_DIFF; + params->flags |= XDF_HISTOGRAM_DIFF; } else { api_set_error(err, kErrorTypeValidation, "not a valid algorithm"); goto exit_1; diff --git a/src/nvim/macros.h b/src/nvim/macros.h index c2b2c89abf..be1ab935c0 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -103,7 +103,10 @@ // MB_PTR_BACK(): backup a pointer to the previous character, taking care of // multi-byte characters if needed. Only use with "p" > "s" ! #define MB_PTR_BACK(s, p) \ - (p -= utf_head_off((char_u *)s, (char_u *)p - 1) + 1) + (p -= utf_head_off((char_u *)(s), (char_u *)(p) - 1) + 1) + +// MB_CHAR2BYTES(): convert character to bytes and advance pointer to bytes +#define MB_CHAR2BYTES(c, b) ((b) += utf_char2bytes((c), (b))) #define RESET_BINDING(wp) \ do { \ @@ -130,7 +133,7 @@ #define ARRAY_LAST_ENTRY(arr) (arr)[ARRAY_SIZE(arr) - 1] // Duplicated in os/win_defs.h to avoid include-order sensitivity. -#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) +#define RGB_(r, g, b) (((r) << 16) | ((g) << 8) | (b)) #define STR_(x) #x #define STR(x) STR_(x) diff --git a/src/nvim/main.c b/src/nvim/main.c index 921bc883cf..6ea1cb0875 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -9,25 +9,29 @@ #include <string.h> #include "nvim/ascii.h" -#include "nvim/aucmd.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/iconv.h" #include "nvim/if_cscope.h" #include "nvim/lua/executor.h" #include "nvim/main.h" +#include "nvim/ui_client.h" #include "nvim/vim.h" #ifdef HAVE_LOCALE_H # include <locale.h> @@ -39,7 +43,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -111,7 +114,6 @@ static const char *err_too_many_args = N_("Too many edit arguments"); static const char *err_extra_cmd = N_("Too many \"+command\", \"-c command\" or \"--cmd command\" arguments"); - void event_init(void) { loop_init(&main_loop, NULL); @@ -155,10 +157,11 @@ bool event_teardown(void) void early_init(mparm_T *paramp) { env_init(); - fs_init(); + cmdline_init(); eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. + runtime_init(); highlight_init(); #ifdef WIN32 @@ -231,6 +234,10 @@ int main(int argc, char **argv) // `argc` and `argv` are also copied, so that they can be changed. init_params(¶ms, argc, argv); + // Since os_open is called during the init_startuptime, we need to call + // fs_init before it. + fs_init(); + init_startuptime(¶ms); // Need to find "--clean" before actually parsing arguments. @@ -250,12 +257,12 @@ int main(int argc, char **argv) // Check if we have an interactive window. check_and_set_isatty(¶ms); - nlua_init(); - // Process the command line arguments. File names are put in the global // argument list "global_alist". command_line_scan(¶ms); + nlua_init(); + if (embedded_mode) { const char *err; if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { @@ -264,6 +271,9 @@ int main(int argc, char **argv) } server_init(params.listen_addr); + if (params.remote) { + remote_request(¶ms, params.remote, params.server_addr, argc, argv); + } if (GARGCOUNT > 0) { fname = get_fname(¶ms, cwd); @@ -336,15 +346,23 @@ int main(int argc, char **argv) TIME_MSG("init screen for UI"); } + if (ui_client_channel_id) { + ui_client_init(ui_client_channel_id); + ui_client_execute(ui_client_channel_id); + abort(); // unreachable + } + init_default_mappings(); // Default mappings. TIME_MSG("init default mappings"); init_default_autocmds(); TIME_MSG("init default autocommands"); + bool vimrc_none = params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE"); + // Reset 'loadplugins' for "-u NONE" before "--cmd" arguments. // Allows for setting 'loadplugins' there. - if (params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE")) { + if (vimrc_none) { // When using --clean we still want to load plugins p_lpl = params.clean; } @@ -352,14 +370,23 @@ int main(int argc, char **argv) // Execute --cmd arguments. exe_pre_commands(¶ms); + if (!vimrc_none || params.clean) { + // Sources ftplugin.vim and indent.vim. We do this *before* the user startup scripts to ensure + // ftplugins run before FileType autocommands defined in the init file (which allows those + // autocommands to overwrite settings from ftplugins). + filetype_plugin_enable(); + } + // Source startup scripts. source_startup_scripts(¶ms); // If using the runtime (-u is not NONE), enable syntax & filetype plugins. - if (params.use_vimrc == NULL || !strequal(params.use_vimrc, "NONE")) { - // Does ":filetype plugin indent on". + if (!vimrc_none || params.clean) { + // Sources filetype.lua and filetype.vim unless the user explicitly disabled it with :filetype + // off. filetype_maybe_enable(); - // Sources syntax/syntax.vim, which calls `:filetype on`. + // Sources syntax/syntax.vim. We do this *after* the user startup scripts so that users can + // disable syntax highlighting with `:syntax off` if they wish. syn_maybe_enable(); } @@ -696,6 +723,50 @@ void getout(int exitval) os_exit(exitval); } +/// Preserve files and exit. +/// @note IObuff must contain a message. +/// @note This may be called from deadly_signal() in a signal handler, avoid +/// unsafe functions, such as allocating memory. +void preserve_exit(void) + FUNC_ATTR_NORETURN +{ + // 'true' when we are sure to exit, e.g., after a deadly signal + static bool really_exiting = false; + + // Prevent repeated calls into this method. + if (really_exiting) { + if (input_global_fd() >= 0) { + // normalize stream (#2598) + stream_set_blocking(input_global_fd(), true); + } + exit(2); + } + + really_exiting = true; + // Ignore SIGHUP while we are already exiting. #9274 + signal_reject_deadly(); + mch_errmsg(IObuff); + mch_errmsg("\n"); + ui_flush(); + + ml_close_notmod(); // close all not-modified buffers + + FOR_ALL_BUFFERS(buf) { + if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { + mch_errmsg("Vim: preserving files...\r\n"); + ui_flush(); + ml_sync_all(false, false, true); // preserve all swap files + break; + } + } + + ml_close_all(false); // close all memfiles, without deleting + + mch_errmsg("Vim: Finished.\r\n"); + + getout(1); +} + /// Gets the integer value of a numeric command line argument if given, /// such as '-o10'. /// @@ -744,6 +815,114 @@ static void init_locale(void) } #endif + +static uint64_t server_connect(char *server_addr, const char **errmsg) +{ + if (server_addr == NULL) { + *errmsg = "no address specified"; + return 0; + } + CallbackReader on_data = CALLBACK_READER_INIT; + const char *error = NULL; + bool is_tcp = strrchr(server_addr, ':') ? true : false; + // connected to channel + uint64_t chan = channel_connect(is_tcp, server_addr, true, on_data, 50, &error); + if (error) { + *errmsg = error; + return 0; + } + return chan; +} + +/// Handle remote subcommands +static void remote_request(mparm_T *params, int remote_args, + char *server_addr, int argc, char **argv) +{ + const char *connect_error = NULL; + uint64_t chan = server_connect(server_addr, &connect_error); + Object rvobj = OBJECT_INIT; + + if (strequal(argv[remote_args], "--remote-ui-test")) { + if (!chan) { + emsg(connect_error); + exit(1); + } + + ui_client_channel_id = chan; + return; + } + + Array args = ARRAY_DICT_INIT; + String arg_s; + for (int t_argc = remote_args; t_argc < argc; t_argc++) { + arg_s = cstr_to_string(argv[t_argc]); + ADD(args, STRING_OBJ(arg_s)); + } + + Error err = ERROR_INIT; + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ((int)chan)); + ADD(a, CSTR_TO_OBJ(server_addr)); + ADD(a, CSTR_TO_OBJ(connect_error)); + ADD(a, ARRAY_OBJ(args)); + String s = STATIC_CSTR_AS_STRING("return vim._cs_remote(...)"); + Object o = nlua_exec(s, a, &err); + api_free_array(a); + if (ERROR_SET(&err)) { + mch_errmsg(err.msg); + mch_errmsg("\n"); + os_exit(2); + } + + if (o.type == kObjectTypeDictionary) { + rvobj.data.dictionary = o.data.dictionary; + } else { + mch_errmsg("vim._cs_remote returned unexpected value\n"); + os_exit(2); + } + + TriState should_exit = kNone; + TriState tabbed = kNone; + + for (size_t i = 0; i < rvobj.data.dictionary.size ; i++) { + if (strcmp(rvobj.data.dictionary.items[i].key.data, "errmsg") == 0) { + if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) { + mch_errmsg("vim._cs_remote returned an unexpected type for 'errmsg'\n"); + os_exit(2); + } + mch_errmsg(rvobj.data.dictionary.items[i].value.data.string.data); + mch_errmsg("\n"); + os_exit(2); + } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "tabbed") == 0) { + if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) { + mch_errmsg("vim._cs_remote returned an unexpected type for 'tabbed'\n"); + os_exit(2); + } + tabbed = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse; + } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "should_exit") == 0) { + if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) { + mch_errmsg("vim._cs_remote returned an unexpected type for 'should_exit'\n"); + os_exit(2); + } + should_exit = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse; + } + } + if (should_exit == kNone || tabbed == kNone) { + mch_errmsg("vim._cs_remote didn't return a value for should_exit or tabbed, bailing\n"); + os_exit(2); + } + api_free_object(o); + + if (should_exit == kTrue) { + os_exit(0); + } + if (tabbed == kTrue) { + params->window_count = argc - remote_args - 1; + params->window_layout = WIN_TABS; + } +} + + /// Decides whether text (as opposed to commands) will be read from stdin. /// @see EDIT_STDIN static bool edit_stdin(bool explicit, mparm_T *parmp) @@ -765,7 +944,6 @@ static void command_line_scan(mparm_T *parmp) bool had_stdin_file = false; // found explicit "-" argument bool had_minmin = false; // found "--" argument int want_argument; // option argument with argument - int c; long n; argc--; @@ -787,7 +965,7 @@ static void command_line_scan(mparm_T *parmp) // Optional argument. } else if (argv[0][0] == '-' && !had_minmin) { want_argument = false; - c = argv[0][argv_idx++]; + char c = argv[0][argv_idx++]; switch (c) { case NUL: // "nvim -" read from stdin if (exmode_active) { @@ -810,6 +988,8 @@ static void command_line_scan(mparm_T *parmp) // "--version" give version message // "--noplugin[s]" skip plugins // "--cmd <cmd>" execute cmd before vimrc + // "--remote" execute commands remotey on a server + // "--server" name of vim server to send remote commands to if (STRICMP(argv[0] + argv_idx, "help") == 0) { usage(); os_exit(0); @@ -848,6 +1028,11 @@ static void command_line_scan(mparm_T *parmp) argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { // Do nothing: file args are always literal. #7679 + } else if (STRNICMP(argv[0] + argv_idx, "remote", 6) == 0) { + parmp->remote = parmp->argc - argc; + } else if (STRNICMP(argv[0] + argv_idx, "server", 6) == 0) { + want_argument = true; + argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "noplugin", 8) == 0) { p_lpl = false; } else if (STRNICMP(argv[0] + argv_idx, "cmd", 3) == 0) { @@ -860,6 +1045,8 @@ static void command_line_scan(mparm_T *parmp) parmp->use_vimrc = "NONE"; parmp->clean = true; set_option_value("shadafile", 0L, "NONE", 0); + } else if (STRNICMP(argv[0] + argv_idx, "luamod-dev", 9) == 0) { + nlua_disable_preload = true; } else { if (argv[0][argv_idx]) { mainerr(err_opt_unknown, argv[0]); @@ -1077,6 +1264,9 @@ static void command_line_scan(mparm_T *parmp) } else if (strequal(argv[-1], "--listen")) { // "--listen {address}" parmp->listen_addr = argv[0]; + } else if (strequal(argv[-1], "--server")) { + // "--server {address}" + parmp->server_addr = argv[0]; } // "--startuptime <file>" already handled break; @@ -1231,6 +1421,8 @@ static void init_params(mparm_T *paramp, int argc, char **argv) paramp->use_debug_break_level = -1; paramp->window_count = -1; paramp->listen_addr = NULL; + paramp->server_addr = NULL; + paramp->remote = 0; } /// Initialize global startuptime file if "--startuptime" passed as an argument. @@ -1506,7 +1698,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) // When w_arg_idx is -1 remove the window (see create_windows()). if (curwin->w_arg_idx == -1) { - win_close(curwin, true); + win_close(curwin, true, false); advance = false; } @@ -1518,7 +1710,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) // When w_arg_idx is -1 remove the window (see create_windows()). if (curwin->w_arg_idx == -1) { arg_idx++; - win_close(curwin, true); + win_close(curwin, true, false); advance = false; continue; } @@ -1565,7 +1757,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) did_emsg = FALSE; // avoid hit-enter prompt getout(1); } - win_close(curwin, true); + win_close(curwin, true, false); advance = false; } if (arg_idx == GARGCOUNT - 1) { @@ -1909,6 +2101,7 @@ static bool file_owned(const char *fname) /// @param errstr string containing an error message /// @param str string to append to the primary error message, or NULL static void mainerr(const char *errstr, const char *str) + FUNC_ATTR_NORETURN { char *prgname = (char *)path_tail((char_u *)argv0); @@ -1932,6 +2125,8 @@ static void mainerr(const char *errstr, const char *str) /// Prints version information for "nvim -v" or "nvim --version". static void version(void) { + // TODO(bfred): not like this? + nlua_init(); info_message = true; // use mch_msg(), not mch_errmsg() list_version(); msg_putchar('\n'); @@ -1979,6 +2174,8 @@ static void usage(void) mch_msg(_(" --headless Don't start a user interface\n")); mch_msg(_(" --listen <address> Serve RPC API from this address\n")); mch_msg(_(" --noplugin Don't load plugins\n")); + mch_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n")); + mch_msg(_(" --server <address> Specify RPC server to send commands to\n")); mch_msg(_(" --startuptime <file> Write startup timing messages to <file>\n")); mch_msg(_("\nSee \":help startup-options\" for all options.\n")); } diff --git a/src/nvim/main.h b/src/nvim/main.h index f73af5c288..e55bef6e33 100644 --- a/src/nvim/main.h +++ b/src/nvim/main.h @@ -39,6 +39,8 @@ typedef struct { int diff_mode; // start with 'diff' set char *listen_addr; // --listen {address} + int remote; // --remote-[subcmd] {file1} {file2} + char *server_addr; // --server {address} } mparm_T; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/map.c b/src/nvim/map.c index 1d9abe3ef2..b3f48ad5d6 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -29,8 +29,6 @@ #define uint32_t_eq kh_int_hash_equal #define int_hash kh_int_hash_func #define int_eq kh_int_hash_equal -#define linenr_T_hash kh_int_hash_func -#define linenr_T_eq kh_int_hash_equal #define handle_T_hash kh_int_hash_func #define handle_T_eq kh_int_hash_equal @@ -173,15 +171,15 @@ MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER) MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER) -#define EXTMARK_NS_INITIALIZER { { MAP_INIT }, 1 } -MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER) -#define EXTMARK_ITEM_INITIALIZER { 0, 0, NULL } -MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER) +MAP_IMPL(uint32_t, uint32_t, DEFAULT_INITIALIZER) MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER) #define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false } MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(String, handle_T, 0) +MAP_IMPL(String, int, DEFAULT_INITIALIZER) +MAP_IMPL(int, String, DEFAULT_INITIALIZER) +MAP_IMPL(String, UIClientHandler, NULL) MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index dbd85a4e1f..693ef50127 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -8,6 +8,7 @@ #include "nvim/extmark_defs.h" #include "nvim/highlight_defs.h" #include "nvim/map_defs.h" +#include "nvim/ui_client.h" #if defined(__NetBSD__) # undef uint64_t @@ -40,20 +41,15 @@ MAP_DECLS(ptr_t, ptr_t) MAP_DECLS(uint64_t, ptr_t) MAP_DECLS(uint64_t, ssize_t) MAP_DECLS(uint64_t, uint64_t) +MAP_DECLS(uint32_t, uint32_t) -// NB: this is the only way to define a struct both containing and contained -// in a map... -typedef struct ExtmarkNs { // For namespacing extmarks - Map(uint64_t, uint64_t) map[1]; // For fast lookup - uint64_t free_id; // For automatically assigning id's -} ExtmarkNs; - -MAP_DECLS(uint64_t, ExtmarkNs) -MAP_DECLS(uint64_t, ExtmarkItem) MAP_DECLS(handle_T, ptr_t) MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(HlEntry, int) MAP_DECLS(String, handle_T) +MAP_DECLS(String, int) +MAP_DECLS(int, String) +MAP_DECLS(String, UIClientHandler) MAP_DECLS(ColorKey, ColorItem) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 39f18b333d..6790bf8240 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -217,8 +217,8 @@ void checkpcmark(void) && (equalpos(curwin->w_pcmark, curwin->w_cursor) || curwin->w_pcmark.lnum == 0)) { curwin->w_pcmark = curwin->w_prev_pcmark; - curwin->w_prev_pcmark.lnum = 0; // Show it has been checked } + curwin->w_prev_pcmark.lnum = 0; // it has been checked } /* @@ -630,7 +630,7 @@ static char_u *mark_line(pos_T *mp, int lead_len) if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) { return vim_strsave((char_u *)"-invalid-"); } - assert(Columns >= 0 && (size_t)Columns <= SIZE_MAX); + assert(Columns >= 0); // Allow for up to 5 bytes per character. s = vim_strnsave(skipwhite(ml_get(mp->lnum)), (size_t)Columns * 5); @@ -844,6 +844,11 @@ void ex_jumps(exarg_T *eap) if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { name = fm_getname(&curwin->w_jumplist[i].fmark, 16); + // Make sure to output the current indicator, even when on an wiped + // out buffer. ":filter" may still skip it. + if (name == NULL && i == curwin->w_jumplistidx) { + name = vim_strsave((char_u *)"-invalid-"); + } // apply :filter /pat/ or file name not available if (name == NULL || message_filtered(name)) { xfree(name); diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 38014ab375..937582572b 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -56,12 +56,6 @@ #define T MT_BRANCH_FACTOR #define ILEN (sizeof(mtnode_t)+(2 * T) * sizeof(void *)) -#define RIGHT_GRAVITY (((uint64_t)1) << 63) -#define ANTIGRAVITY(id) ((id)&(RIGHT_GRAVITY-1)) -#define IS_RIGHT(id) ((id)&RIGHT_GRAVITY) - -#define PAIRED MARKTREE_PAIRED_FLAG -#define END_FLAG MARKTREE_END_FLAG #define ID_INCR (((uint64_t)1) << 2) #define rawkey(itr) (itr->node->key[itr->i]) @@ -119,7 +113,7 @@ static int key_cmp(mtkey_t a, mtkey_t b) } // NB: keeping the events at the same pos sorted by id is actually not // necessary only make sure that START is before END etc. - return mt_generic_cmp(a.id, b.id); + return mt_generic_cmp(a.flags, b.flags); } static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r) @@ -148,7 +142,7 @@ static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r) static inline void refkey(MarkTree *b, mtnode_t *x, int i) { - pmap_put(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id), x); + pmap_put(uint64_t)(b->id2node, mt_lookup_key(x->key[i]), x); } // put functions @@ -221,38 +215,28 @@ static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k) } } -uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity, uint8_t decor_level) +void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_right) { - uint64_t id = (b->next_id+=ID_INCR); - assert(decor_level < DECOR_LEVELS); - id = id | ((uint64_t)decor_level << DECOR_OFFSET); - uint64_t keyid = id; - if (right_gravity) { - // order all right gravity keys after the left ones, for effortless - // insertion (but not deletion!) - keyid |= RIGHT_GRAVITY; - } - marktree_put_key(b, row, col, keyid); - return id; -} + assert(!(key.flags & ~MT_FLAG_EXTERNAL_MASK)); + if (end_row >= 0) { + key.flags |= MT_FLAG_PAIRED; + } -uint64_t marktree_put_pair(MarkTree *b, int start_row, int start_col, bool start_right, int end_row, - int end_col, bool end_right, uint8_t decor_level) -{ - uint64_t id = (b->next_id+=ID_INCR)|PAIRED; - assert(decor_level < DECOR_LEVELS); - id = id | ((uint64_t)decor_level << DECOR_OFFSET); - uint64_t start_id = id|(start_right?RIGHT_GRAVITY:0); - uint64_t end_id = id|END_FLAG|(end_right?RIGHT_GRAVITY:0); - marktree_put_key(b, start_row, start_col, start_id); - marktree_put_key(b, end_row, end_col, end_id); - return id; + marktree_put_key(b, key); + + if (end_row >= 0) { + mtkey_t end_key = key; + end_key.flags = (uint16_t)((uint16_t)(key.flags & ~MT_FLAG_RIGHT_GRAVITY) + |(uint16_t)MT_FLAG_END + |(uint16_t)(end_right ? MT_FLAG_RIGHT_GRAVITY : 0)); + end_key.pos = (mtpos_t){ end_row, end_col }; + marktree_put_key(b, end_key); + } } -void marktree_put_key(MarkTree *b, int row, int col, uint64_t id) +void marktree_put_key(MarkTree *b, mtkey_t k) { - mtkey_t k = { .pos = { .row = row, .col = col }, .id = id }; - + k.flags |= MT_FLAG_REAL; // let's be real. if (!b->root) { b->root = (mtnode_t *)xcalloc(1, ILEN); b->n_nodes++; @@ -302,7 +286,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) mtnode_t *cur = itr->node; int curi = itr->i; - uint64_t id = cur->key[curi].id; + uint64_t id = mt_lookup_key(cur->key[curi]); // fprintf(stderr, "\nDELET %lu\n", id); if (itr->node->level) { @@ -364,7 +348,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) } b->n_keys--; - pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(id)); + pmap_del(uint64_t)(b->id2node, id); // 5. bool itr_dirty = false; @@ -570,23 +554,29 @@ void marktree_free_node(mtnode_t *x) } /// NB: caller must check not pair! -uint64_t marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level) +void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_t key) { - uint64_t old_id = rawkey(itr).id; - pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(old_id)); - uint64_t new_id = (b->next_id += ID_INCR) + ((uint64_t)decor_level << DECOR_OFFSET); - rawkey(itr).id = new_id + (RIGHT_GRAVITY&old_id); - refkey(b, itr->node, itr->i); - return new_id; + // TODO(bfredl): clean up this mess and re-instantiate &= and |= forms + // once we upgrade to a non-broken version of gcc in functionaltest-lua CI + rawkey(itr).flags = (uint16_t)(rawkey(itr).flags & (uint16_t) ~MT_FLAG_DECOR_MASK); + rawkey(itr).flags = (uint16_t)(rawkey(itr).flags + | (uint16_t)(decor_level << MT_FLAG_DECOR_OFFSET) + | (uint16_t)(key.flags & MT_FLAG_DECOR_MASK)); + rawkey(itr).decor_full = key.decor_full; + rawkey(itr).hl_id = key.hl_id; + rawkey(itr).priority = key.priority; } void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col) { - uint64_t old_id = rawkey(itr).id; + mtkey_t key = rawkey(itr); // TODO(bfredl): optimize when moving a mark within a leaf without moving it // across neighbours! marktree_del_itr(b, itr, false); - marktree_put_key(b, row, col, old_id); + key.pos = (mtpos_t){ row, col }; + + + marktree_put_key(b, key); itr->node = NULL; // itr might become invalid by put } @@ -602,14 +592,15 @@ bool marktree_itr_get(MarkTree *b, int row, int col, MarkTreeIter *itr) bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, bool gravity, mtpos_t *oldbase) { - mtkey_t k = { .pos = p, .id = gravity ? RIGHT_GRAVITY : 0 }; - if (last && !gravity) { - k.id = UINT64_MAX; - } if (b->n_keys == 0) { itr->node = NULL; return false; } + + mtkey_t k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 }; + if (last && !gravity) { + k.flags = MT_FLAG_LAST; + } itr->pos = (mtpos_t){ 0, 0 }; itr->node = b->root; itr->lvl = 0; @@ -816,25 +807,29 @@ mtpos_t marktree_itr_pos(MarkTreeIter *itr) return pos; } -mtmark_t marktree_itr_current(MarkTreeIter *itr) +mtkey_t marktree_itr_current(MarkTreeIter *itr) { if (itr->node) { - uint64_t keyid = rawkey(itr).id; - mtpos_t pos = marktree_itr_pos(itr); - mtmark_t mark = { .row = pos.row, - .col = pos.col, - .id = ANTIGRAVITY(keyid), - .right_gravity = keyid & RIGHT_GRAVITY }; - return mark; - } - return (mtmark_t){ -1, -1, 0, false }; + mtkey_t key = rawkey(itr); + key.pos = marktree_itr_pos(itr); + return key; + } + return MT_INVALID_KEY; } -static void swap_id(uint64_t *id1, uint64_t *id2) +static bool itr_eq(MarkTreeIter *itr1, MarkTreeIter *itr2) { - uint64_t temp = *id1; - *id1 = *id2; - *id2 = temp; + return (&rawkey(itr1) == &rawkey(itr2)); +} + +static void itr_swap(MarkTreeIter *itr1, MarkTreeIter *itr2) +{ + mtkey_t key1 = rawkey(itr1); + mtkey_t key2 = rawkey(itr2); + rawkey(itr1) = key2; + rawkey(itr1).pos = key1.pos; + rawkey(itr2) = key1; + rawkey(itr2).pos = key2.pos; } bool marktree_splice(MarkTree *b, int start_line, int start_col, int old_extent_line, @@ -865,7 +860,7 @@ bool marktree_splice(MarkTree *b, int start_line, int start_col, int old_extent_ mtpos_t ipos = marktree_itr_pos(itr); if (!pos_leq(old_extent, ipos) || (old_extent.row == ipos.row && old_extent.col == ipos.col - && !IS_RIGHT(rawkey(itr).id))) { + && !mt_right(rawkey(itr)))) { marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL); assert(enditr->node); // "assert" (itr <= enditr) @@ -895,13 +890,13 @@ continue_same_node: break; } - if (IS_RIGHT(rawkey(itr).id)) { - while (rawkey(itr).id != rawkey(enditr).id - && IS_RIGHT(rawkey(enditr).id)) { + if (mt_right(rawkey(itr))) { + while (!itr_eq(itr, enditr) + && mt_right(rawkey(enditr))) { marktree_itr_prev(b, enditr); } - if (!IS_RIGHT(rawkey(enditr).id)) { - swap_id(&rawkey(itr).id, &rawkey(enditr).id); + if (!mt_right(rawkey(enditr))) { + itr_swap(itr, enditr); refkey(b, itr->node, itr->i); refkey(b, enditr->node, enditr->i); } else { @@ -911,7 +906,7 @@ continue_same_node: } } - if (rawkey(itr).id == rawkey(enditr).id) { + if (itr_eq(itr, enditr)) { // actually, will be past_right after this key past_right = true; } @@ -1006,13 +1001,13 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext marktree_itr_get_ext(b, start, itr, false, true, NULL); kvec_t(mtkey_t) saved = KV_INITIAL_VALUE; while (itr->node) { - mtpos_t pos = marktree_itr_pos(itr); - if (!pos_leq(pos, end) || (pos.row == end.row && pos.col == end.col - && rawkey(itr).id & RIGHT_GRAVITY)) { + mtkey_t k = marktree_itr_current(itr); + if (!pos_leq(k.pos, end) || (k.pos.row == end.row && k.pos.col == end.col + && mt_right(k))) { break; } - relative(start, &pos); - kv_push(saved, ((mtkey_t){ .pos = pos, .id = rawkey(itr).id })); + relative(start, &k.pos); + kv_push(saved, k); marktree_del_itr(b, itr, false); } @@ -1024,30 +1019,36 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext for (size_t i = 0; i < kv_size(saved); i++) { mtkey_t item = kv_A(saved, i); unrelative(new, &item.pos); - marktree_put_key(b, item.pos.row, item.pos.col, item.id); + marktree_put_key(b, item); } kv_destroy(saved); } /// @param itr OPTIONAL. set itr to pos. -mtpos_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr) +mtkey_t marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr) +{ + return marktree_lookup(b, mt_lookup_id(ns, id, end), itr); +} + +/// @param itr OPTIONAL. set itr to pos. +mtkey_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr) { mtnode_t *n = pmap_get(uint64_t)(b->id2node, id); if (n == NULL) { if (itr) { itr->node = NULL; } - return (mtpos_t){ -1, -1 }; + return MT_INVALID_KEY; } int i = 0; for (i = 0; i < n->n; i++) { - if (ANTIGRAVITY(n->key[i].id) == id) { + if (mt_lookup_key(n->key[i]) == id) { goto found; } } abort(); found: {} - mtpos_t pos = n->key[i].pos; + mtkey_t key = n->key[i]; if (itr) { itr->i = i; itr->node = n; @@ -1066,14 +1067,28 @@ found_node: itr->s[b->root->level-p->level].i = i; } if (i > 0) { - unrelative(p->key[i-1].pos, &pos); + unrelative(p->key[i-1].pos, &key.pos); } n = p; } if (itr) { marktree_itr_fix_pos(b, itr); } - return pos; + return key; +} + +mtpos_t marktree_get_altpos(MarkTree *b, mtkey_t mark, MarkTreeIter *itr) +{ + return marktree_get_alt(b, mark, itr).pos; +} + +mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr) +{ + mtkey_t end = MT_INVALID_KEY; + if (mt_paired(mark)) { + end = marktree_lookup_ns(b, mark.ns, mark.id, !mt_end(mark), itr); + } + return end; } static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) @@ -1092,6 +1107,20 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) assert(x == itr->node); } +// for unit test +void marktree_put_test(MarkTree *b, uint32_t id, int row, int col, bool right_gravity) +{ + mtkey_t key = { { row, col }, UINT32_MAX, id, 0, + mt_flags(right_gravity, 0), 0, NULL }; + marktree_put(b, key, -1, -1, false); +} + +// for unit test +bool mt_right_test(mtkey_t key) +{ + return mt_right(key); +} + void marktree_check(MarkTree *b) { #ifndef NDEBUG @@ -1130,15 +1159,13 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig if (i > 0) { unrelative(x->key[i-1].pos, last); } - if (x->level) { - } assert(pos_leq(*last, x->key[i].pos)); if (last->row == x->key[i].pos.row && last->col == x->key[i].pos.col) { - assert(!*last_right || IS_RIGHT(x->key[i].id)); + assert(!*last_right || mt_right(x->key[i])); } - *last_right = IS_RIGHT(x->key[i].id); + *last_right = mt_right(x->key[i]); assert(x->key[i].pos.col >= 0); - assert(pmap_get(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id)) == x); + assert(pmap_get(uint64_t)(b->id2node, mt_lookup_key(x->key[i])) == x); } if (x->level) { diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index a1dcdf5164..ae6da92106 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -1,11 +1,14 @@ #ifndef NVIM_MARKTREE_H #define NVIM_MARKTREE_H +#include <assert.h> #include <stdint.h> +#include "nvim/assert.h" #include "nvim/garray.h" #include "nvim/map.h" #include "nvim/pos.h" +#include "nvim/types.h" #define MT_MAX_DEPTH 20 #define MT_BRANCH_FACTOR 10 @@ -15,13 +18,6 @@ typedef struct { int32_t col; } mtpos_t; -typedef struct { - int32_t row; - int32_t col; - uint64_t id; - bool right_gravity; -} mtmark_t; - typedef struct mtnode_s mtnode_t; typedef struct { int oldcol; @@ -39,12 +35,80 @@ typedef struct { // Internal storage // -// NB: actual marks have id > 0, so we can use (row,col,0) pseudo-key for +// NB: actual marks have flags > 0, so we can use (row,col,0) pseudo-key for // "space before (row,col)" typedef struct { mtpos_t pos; - uint64_t id; + uint32_t ns; + uint32_t id; + int32_t hl_id; + uint16_t flags; + uint16_t priority; + Decoration *decor_full; } mtkey_t; +#define MT_INVALID_KEY (mtkey_t) { { -1, -1 }, 0, 0, 0, 0, 0, NULL } + +#define MT_FLAG_REAL (((uint16_t)1) << 0) +#define MT_FLAG_END (((uint16_t)1) << 1) +#define MT_FLAG_PAIRED (((uint16_t)1) << 2) +#define MT_FLAG_HL_EOL (((uint16_t)1) << 3) + +#define DECOR_LEVELS 4 +#define MT_FLAG_DECOR_OFFSET 4 +#define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS-1)) << MT_FLAG_DECOR_OFFSET) + +// next flag is (((uint16_t)1) << 6) + +// These _must_ be last to preserve ordering of marks +#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14) +#define MT_FLAG_LAST (((uint16_t)1) << 15) + +#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_RIGHT_GRAVITY | MT_FLAG_HL_EOL) + +#define MARKTREE_END_FLAG (((uint64_t)1) << 63) +static inline uint64_t mt_lookup_id(uint32_t ns, uint32_t id, bool enda) +{ + return (uint64_t)ns << 32 | id | (enda?MARKTREE_END_FLAG:0); +} +#undef MARKTREE_END_FLAG + +static inline uint64_t mt_lookup_key(mtkey_t key) +{ + return mt_lookup_id(key.ns, key.id, key.flags & MT_FLAG_END); +} + +static inline bool mt_paired(mtkey_t key) +{ + return key.flags & MT_FLAG_PAIRED; +} + +static inline bool mt_end(mtkey_t key) +{ + return key.flags & MT_FLAG_END; +} + +static inline bool mt_start(mtkey_t key) +{ + return mt_paired(key) && !mt_end(key); +} + +static inline bool mt_right(mtkey_t key) +{ + return key.flags & MT_FLAG_RIGHT_GRAVITY; +} + +static inline uint8_t marktree_decor_level(mtkey_t key) +{ + return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET); +} + +static inline uint16_t mt_flags(bool right_gravity, uint8_t decor_level) +{ + assert(decor_level < DECOR_LEVELS); + return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0) + | (decor_level << MT_FLAG_DECOR_OFFSET)); +} + struct mtnode_s { int32_t n; @@ -61,7 +125,6 @@ struct mtnode_s { typedef struct { mtnode_t *root; size_t n_keys, n_nodes; - uint64_t next_id; // TODO(bfredl): the pointer to node could be part of the larger // Map(uint64_t, ExtmarkItem) essentially; PMap(uint64_t) id2node[1]; @@ -72,16 +135,4 @@ typedef struct { # include "marktree.h.generated.h" #endif -#define MARKTREE_PAIRED_FLAG (((uint64_t)1) << 1) -#define MARKTREE_END_FLAG (((uint64_t)1) << 0) - -#define DECOR_LEVELS 4 -#define DECOR_OFFSET 61 -#define DECOR_MASK (((uint64_t)(DECOR_LEVELS-1)) << DECOR_OFFSET) - -static inline uint8_t marktree_decor_level(uint64_t id) -{ - return (uint8_t)((id&DECOR_MASK) >> DECOR_OFFSET); -} - #endif // NVIM_MARKTREE_H diff --git a/src/nvim/match.c b/src/nvim/match.c new file mode 100644 index 0000000000..af89319a09 --- /dev/null +++ b/src/nvim/match.c @@ -0,0 +1,1181 @@ +// 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 + +// match.c: functions for highlighting matches + +#include <stdbool.h> +#include "nvim/charset.h" +#include "nvim/fold.h" +#include "nvim/highlight_group.h" +#include "nvim/match.h" +#include "nvim/memline.h" +#include "nvim/regexp.h" +#include "nvim/runtime.h" +#include "nvim/screen.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "match.c.generated.h" +#endif + +static char *e_invalwindow = N_("E957: Invalid window number"); + +#define SEARCH_HL_PRIORITY 0 + +/// Add match to the match list of window 'wp'. The pattern 'pat' will be +/// highlighted with the group 'grp' with priority 'prio'. +/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1). +/// +/// @param[in] id a desired ID 'id' can be specified +/// (greater than or equal to 1). -1 must be specified if no +/// particular ID is desired +/// @param[in] conceal_char pointer to conceal replacement char +/// @return ID of added match, -1 on failure. +static int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id, + list_T *pos_list, const char *const conceal_char) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + matchitem_T *cur; + matchitem_T *prev; + matchitem_T *m; + int hlg_id; + regprog_T *regprog = NULL; + int rtype = SOME_VALID; + + if (*grp == NUL || (pat != NULL && *pat == NUL)) { + return -1; + } + if (id < -1 || id == 0) { + semsg(_("E799: Invalid ID: %" PRId64 + " (must be greater than or equal to 1)"), + (int64_t)id); + return -1; + } + if (id != -1) { + cur = wp->w_match_head; + while (cur != NULL) { + if (cur->id == id) { + semsg(_("E801: ID already taken: %" PRId64), (int64_t)id); + return -1; + } + cur = cur->next; + } + } + if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) { + return -1; + } + if (pat != NULL && (regprog = vim_regcomp((char_u *)pat, RE_MAGIC)) == NULL) { + semsg(_(e_invarg2), pat); + return -1; + } + + // Find available match ID. + while (id == -1) { + cur = wp->w_match_head; + while (cur != NULL && cur->id != wp->w_next_match_id) { + cur = cur->next; + } + if (cur == NULL) { + id = wp->w_next_match_id; + } + wp->w_next_match_id++; + } + + // Build new match. + m = xcalloc(1, sizeof(matchitem_T)); + m->id = id; + m->priority = prio; + m->pattern = pat == NULL ? NULL: (char_u *)xstrdup(pat); + m->hlg_id = hlg_id; + m->match.regprog = regprog; + m->match.rmm_ic = false; + m->match.rmm_maxcol = 0; + m->conceal_char = 0; + if (conceal_char != NULL) { + m->conceal_char = utf_ptr2char((const char_u *)conceal_char); + } + + // Set up position matches + if (pos_list != NULL) { + linenr_T toplnum = 0; + linenr_T botlnum = 0; + + int i = 0; + TV_LIST_ITER(pos_list, li, { + linenr_T lnum = 0; + colnr_T col = 0; + int len = 1; + bool error = false; + + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { + const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; + const listitem_T *subli = tv_list_first(subl); + if (subli == NULL) { + semsg(_("E5030: Empty list at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); + goto fail; + } + lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (error) { + goto fail; + } + if (lnum <= 0) { + continue; + } + m->pos.pos[i].lnum = lnum; + subli = TV_LIST_ITEM_NEXT(subl, subli); + if (subli != NULL) { + col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (error) { + goto fail; + } + if (col < 0) { + continue; + } + subli = TV_LIST_ITEM_NEXT(subl, subli); + if (subli != NULL) { + len = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (len < 0) { + continue; + } + if (error) { + goto fail; + } + } + } + m->pos.pos[i].col = col; + m->pos.pos[i].len = len; + } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { + if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) { + continue; + } + m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; + m->pos.pos[i].col = 0; + m->pos.pos[i].len = 0; + } else { + semsg(_("E5031: List or number required at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); + goto fail; + } + if (toplnum == 0 || lnum < toplnum) { + toplnum = lnum; + } + if (botlnum == 0 || lnum >= botlnum) { + botlnum = lnum + 1; + } + i++; + if (i >= MAXPOSMATCH) { + break; + } + }); + + // Calculate top and bottom lines for redrawing area + if (toplnum != 0) { + if (wp->w_buffer->b_mod_set) { + if (wp->w_buffer->b_mod_top > toplnum) { + wp->w_buffer->b_mod_top = toplnum; + } + if (wp->w_buffer->b_mod_bot < botlnum) { + wp->w_buffer->b_mod_bot = botlnum; + } + } else { + wp->w_buffer->b_mod_set = true; + wp->w_buffer->b_mod_top = toplnum; + wp->w_buffer->b_mod_bot = botlnum; + wp->w_buffer->b_mod_xlines = 0; + } + m->pos.toplnum = toplnum; + m->pos.botlnum = botlnum; + rtype = VALID; + } + } + + // Insert new match. The match list is in ascending order with regard to + // the match priorities. + cur = wp->w_match_head; + prev = cur; + while (cur != NULL && prio >= cur->priority) { + prev = cur; + cur = cur->next; + } + if (cur == prev) { + wp->w_match_head = m; + } else { + prev->next = m; + } + m->next = cur; + + redraw_later(wp, rtype); + return id; + +fail: + xfree(m); + return -1; +} + +/// Delete match with ID 'id' in the match list of window 'wp'. +/// +/// @param perr print error messages if true. +static int match_delete(win_T *wp, int id, bool perr) +{ + matchitem_T *cur = wp->w_match_head; + matchitem_T *prev = cur; + int rtype = SOME_VALID; + + if (id < 1) { + if (perr) { + semsg(_("E802: Invalid ID: %" PRId64 + " (must be greater than or equal to 1)"), + (int64_t)id); + } + return -1; + } + while (cur != NULL && cur->id != id) { + prev = cur; + cur = cur->next; + } + if (cur == NULL) { + if (perr) { + semsg(_("E803: ID not found: %" PRId64), (int64_t)id); + } + return -1; + } + if (cur == prev) { + wp->w_match_head = cur->next; + } else { + prev->next = cur->next; + } + vim_regfree(cur->match.regprog); + xfree(cur->pattern); + if (cur->pos.toplnum != 0) { + if (wp->w_buffer->b_mod_set) { + if (wp->w_buffer->b_mod_top > cur->pos.toplnum) { + wp->w_buffer->b_mod_top = cur->pos.toplnum; + } + if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) { + wp->w_buffer->b_mod_bot = cur->pos.botlnum; + } + } else { + wp->w_buffer->b_mod_set = true; + wp->w_buffer->b_mod_top = cur->pos.toplnum; + wp->w_buffer->b_mod_bot = cur->pos.botlnum; + wp->w_buffer->b_mod_xlines = 0; + } + rtype = VALID; + } + xfree(cur); + redraw_later(wp, rtype); + return 0; +} + +/// Delete all matches in the match list of window 'wp'. +void clear_matches(win_T *wp) +{ + matchitem_T *m; + + while (wp->w_match_head != NULL) { + m = wp->w_match_head->next; + vim_regfree(wp->w_match_head->match.regprog); + xfree(wp->w_match_head->pattern); + xfree(wp->w_match_head); + wp->w_match_head = m; + } + redraw_later(wp, SOME_VALID); +} + +/// Get match from ID 'id' in window 'wp'. +/// Return NULL if match not found. +matchitem_T *get_match(win_T *wp, int id) +{ + matchitem_T *cur = wp->w_match_head; + + while (cur != NULL && cur->id != id) { + cur = cur->next; + } + return cur; +} + +/// Init for calling prepare_search_hl(). +void init_search_hl(win_T *wp, match_T *search_hl) + FUNC_ATTR_NONNULL_ALL +{ + // Setup for match and 'hlsearch' highlighting. Disable any previous + // match + matchitem_T *cur = wp->w_match_head; + while (cur != NULL) { + cur->hl.rm = cur->match; + if (cur->hlg_id == 0) { + cur->hl.attr = 0; + } else { + cur->hl.attr = syn_id2attr(cur->hlg_id); + } + cur->hl.buf = wp->w_buffer; + cur->hl.lnum = 0; + cur->hl.first_lnum = 0; + // Set the time limit to 'redrawtime'. + cur->hl.tm = profile_setlimit(p_rdt); + cur = cur->next; + } + search_hl->buf = wp->w_buffer; + search_hl->lnum = 0; + search_hl->first_lnum = 0; + search_hl->attr = win_hl_attr(wp, HLF_L); + + // time limit is set at the toplevel, for all windows +} + +/// @param shl points to a match. Fill on match. +/// @param posmatch match positions +/// @param mincol minimal column for a match +/// +/// @return one on match, otherwise return zero. +static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, colnr_T mincol) + FUNC_ATTR_NONNULL_ALL +{ + int i; + int found = -1; + + shl->lnum = 0; + for (i = posmatch->cur; i < MAXPOSMATCH; i++) { + llpos_T *pos = &posmatch->pos[i]; + + if (pos->lnum == 0) { + break; + } + if (pos->len == 0 && pos->col < mincol) { + continue; + } + if (pos->lnum == lnum) { + if (found >= 0) { + // if this match comes before the one at "found" then swap + // them + if (pos->col < posmatch->pos[found].col) { + llpos_T tmp = *pos; + + *pos = posmatch->pos[found]; + posmatch->pos[found] = tmp; + } + } else { + found = i; + } + } + } + posmatch->cur = 0; + if (found >= 0) { + colnr_T start = posmatch->pos[found].col == 0 + ? 0: posmatch->pos[found].col - 1; + colnr_T end = posmatch->pos[found].col == 0 + ? MAXCOL : start + posmatch->pos[found].len; + + shl->lnum = lnum; + shl->rm.startpos[0].lnum = 0; + shl->rm.startpos[0].col = start; + shl->rm.endpos[0].lnum = 0; + shl->rm.endpos[0].col = end; + shl->is_addpos = true; + posmatch->cur = found + 1; + return 1; + } + return 0; +} + +/// Search for a next 'hlsearch' or match. +/// Uses shl->buf. +/// Sets shl->lnum and shl->rm contents. +/// Note: Assumes a previous match is always before "lnum", unless +/// shl->lnum is zero. +/// Careful: Any pointers for buffer lines will become invalid. +/// +/// @param shl points to search_hl or a match +/// @param mincol minimal column for a match +/// @param cur to retrieve match positions if any +static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_T lnum, + colnr_T mincol, matchitem_T *cur) + FUNC_ATTR_NONNULL_ARG(2) +{ + linenr_T l; + colnr_T matchcol; + 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. + // 2. If the previous match includes "mincol", use it. + // 3. Continue after the previous match. + l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum; + if (lnum > l) { + shl->lnum = 0; + } else if (lnum < l || shl->rm.endpos[0].col > mincol) { + return; + } + } + + // Repeat searching for a match until one is found that includes "mincol" + // or none is found in this line. + called_emsg = false; + for (;;) { + // Stop searching after passing the time limit. + if (profile_passed_limit(shl->tm)) { + shl->lnum = 0; // no match found in time + break; + } + // Three situations: + // 1. No useful previous match: search from start of line. + // 2. Not Vi compatible or empty match: continue at next character. + // Break the loop if this is beyond the end of the line. + // 3. Vi compatible searching: continue at end of previous match. + if (shl->lnum == 0) { + matchcol = 0; + } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL + || (shl->rm.endpos[0].lnum == 0 + && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { + char_u *ml; + + matchcol = shl->rm.startpos[0].col; + ml = ml_get_buf(shl->buf, lnum, false) + matchcol; + if (*ml == NUL) { + matchcol++; + shl->lnum = 0; + break; + } + matchcol += utfc_ptr2len(ml); + } else { + matchcol = shl->rm.endpos[0].col; + } + + shl->lnum = lnum; + if (shl->rm.regprog != NULL) { + // Remember whether shl->rm is using a copy of the regprog in + // cur->match. + bool regprog_is_copy = (shl != search_hl + && cur != NULL + && shl == &cur->hl + && cur->match.regprog == cur->hl.rm.regprog); + int timed_out = false; + + nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, + &(shl->tm), &timed_out); + // Copy the regprog, in case it got freed and recompiled. + if (regprog_is_copy) { + cur->match.regprog = cur->hl.rm.regprog; + } + if (called_emsg || got_int || timed_out) { + // Error while handling regexp: stop using this regexp. + if (shl == search_hl) { + // don't free regprog in the match list, it's a copy + vim_regfree(shl->rm.regprog); + set_no_hlsearch(true); + } + shl->rm.regprog = NULL; + shl->lnum = 0; + got_int = false; // avoid the "Type :quit to exit Vim" message + break; + } + } else if (cur != NULL) { + nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol); + } + if (nmatched == 0) { + shl->lnum = 0; // no match found + break; + } + if (shl->rm.startpos[0].lnum > 0 + || shl->rm.startpos[0].col >= mincol + || nmatched > 1 + || shl->rm.endpos[0].col > mincol) { + shl->lnum += shl->rm.startpos[0].lnum; + break; // useful match found + } + + // Restore called_emsg for assert_fails(). + called_emsg = save_called_emsg; + } +} + +/// Advance to the match in window "wp" line "lnum" or past it. +void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum) + FUNC_ATTR_NONNULL_ALL +{ + matchitem_T *cur; // points to the match list + match_T *shl; // points to search_hl or a match + bool shl_flag; // flag to indicate whether search_hl + // has been processed or not + + // When using a multi-line pattern, start searching at the top + // of the window or just after a closed fold. + // Do this both for search_hl and the match list. + cur = wp->w_match_head; + shl_flag = false; + while (cur != NULL || shl_flag == false) { + if (shl_flag == false) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; // -V595 + } + if (shl->rm.regprog != NULL + && shl->lnum == 0 + && re_multiline(shl->rm.regprog)) { + if (shl->first_lnum == 0) { + for (shl->first_lnum = lnum; + shl->first_lnum > wp->w_topline; + shl->first_lnum--) { + if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) { + break; + } + } + } + if (cur != NULL) { + cur->pos.cur = 0; + } + bool pos_inprogress = true; // mark that a position match search is + // in progress + int n = 0; + while (shl->first_lnum < lnum && (shl->rm.regprog != NULL + || (cur != NULL && pos_inprogress))) { + next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n, + shl == search_hl ? NULL : cur); + pos_inprogress = !(cur == NULL || cur->pos.cur == 0); + if (shl->lnum != 0) { + shl->first_lnum = shl->lnum + + shl->rm.endpos[0].lnum + - shl->rm.startpos[0].lnum; + n = shl->rm.endpos[0].col; + } else { + shl->first_lnum++; + n = 0; + } + } + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } +} + +/// Prepare for 'hlsearch' and match highlighting in one window line. +/// Return true if there is such highlighting and set "search_attr" to the +/// current highlight attribute. +bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **line, + match_T *search_hl, int *search_attr, bool *search_attr_from_match) +{ + matchitem_T *cur = wp->w_match_head; // points to the match list + match_T *shl; // points to search_hl or a match + bool shl_flag = false; // flag to indicate whether search_hl + // has been processed or not + bool area_highlighting = false; + + // Handle highlighting the last used search pattern and matches. + // Do this for both search_hl and the match list. + while (cur != NULL || !shl_flag) { + if (!shl_flag) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; // -V595 + } + shl->startcol = MAXCOL; + shl->endcol = MAXCOL; + shl->attr_cur = 0; + shl->is_addpos = false; + if (cur != NULL) { + cur->pos.cur = 0; + } + next_search_hl(wp, search_hl, shl, lnum, mincol, + shl == search_hl ? NULL : cur); + + // Need to get the line again, a multi-line regexp may have made it + // invalid. + *line = ml_get_buf(wp->w_buffer, lnum, false); + + if (shl->lnum != 0 && shl->lnum <= lnum) { + if (shl->lnum == lnum) { + shl->startcol = shl->rm.startpos[0].col; + } else { + shl->startcol = 0; + } + if (lnum == shl->lnum + shl->rm.endpos[0].lnum + - shl->rm.startpos[0].lnum) { + shl->endcol = shl->rm.endpos[0].col; + } else { + shl->endcol = MAXCOL; + } + // Highlight one character for an empty match. + if (shl->startcol == shl->endcol) { + if ((*line)[shl->endcol] != NUL) { + shl->endcol += utfc_ptr2len(*line + shl->endcol); + } else { + shl->endcol++; + } + } + if ((long)shl->startcol < mincol) { // match at leftcol + shl->attr_cur = shl->attr; + *search_attr = shl->attr; + *search_attr_from_match = shl != search_hl; + } + area_highlighting = true; + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } + return area_highlighting; +} + +/// For a position in a line: Check for start/end of 'hlsearch' and other +/// matches. +/// After end, check for start/end of next match. +/// When another match, have to check for start again. +/// Watch out for matching an empty string! +/// Return the updated search_attr. +int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match_T *search_hl, + int *has_match_conc, int *match_conc, int lcs_eol_one, + bool *search_attr_from_match) +{ + matchitem_T *cur = wp->w_match_head; // points to the match list + match_T *shl; // points to search_hl or a match + bool shl_flag = false; // flag to indicate whether search_hl + // has been processed or not + int search_attr = 0; + + // Do this for 'search_hl' and the match list (ordered by priority). + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; + } + if (cur != NULL) { + cur->pos.cur = 0; + } + bool pos_inprogress = true; // mark that a position match search is + // in progress + while (shl->rm.regprog != NULL + || (cur != NULL && pos_inprogress)) { + if (shl->startcol != MAXCOL + && col >= shl->startcol + && col < shl->endcol) { + int next_col = col + utfc_ptr2len(*line + col); + + if (shl->endcol < next_col) { + shl->endcol = next_col; + } + shl->attr_cur = shl->attr; + // Match with the "Conceal" group results in hiding + // the match. + if (cur != NULL + && shl != search_hl + && syn_name2id("Conceal") == cur->hlg_id) { + *has_match_conc = col == shl->startcol ? 2 : 1; + *match_conc = cur->conceal_char; + } else { + *has_match_conc = 0; + } + } else if (col == shl->endcol) { + shl->attr_cur = 0; + + next_search_hl(wp, search_hl, shl, lnum, col, + shl == search_hl ? NULL : cur); + pos_inprogress = !(cur == NULL || cur->pos.cur == 0); + + // Need to get the line again, a multi-line regexp + // may have made it invalid. + *line = ml_get_buf(wp->w_buffer, lnum, false); + + if (shl->lnum == lnum) { + shl->startcol = shl->rm.startpos[0].col; + if (shl->rm.endpos[0].lnum == 0) { + shl->endcol = shl->rm.endpos[0].col; + } else { + shl->endcol = MAXCOL; + } + + if (shl->startcol == shl->endcol) { + // highlight empty match, try again after it + shl->endcol += utfc_ptr2len(*line + shl->endcol); + } + + // Loop to check if the match starts at the + // current position + continue; + } + } + break; + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } + + // Use attributes from match with highest priority among + // 'search_hl' and the match list. + *search_attr_from_match = false; + search_attr = search_hl->attr_cur; + cur = wp->w_match_head; + shl_flag = false; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; + } + if (shl->attr_cur != 0) { + search_attr = shl->attr_cur; + *search_attr_from_match = shl != search_hl; + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } + // Only highlight one character after the last column. + if (*(*line + col) == NUL && (wp->w_p_list && lcs_eol_one == -1)) { + search_attr = 0; + } + return search_attr; +} + +bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol) +{ + long prevcol = curcol; + matchitem_T *cur; // points to the match list + + // we're not really at that column when skipping some text + if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) { + prevcol++; + } + + if (!search_hl->is_addpos && prevcol == search_hl->startcol) { + return true; + } else { + cur = wp->w_match_head; + while (cur != NULL) { + if (!cur->hl.is_addpos && prevcol == cur->hl.startcol) { + return true; + } + cur = cur->next; + } + } + return false; +} + +/// Get highlighting for the char after the text in "char_attr" from 'hlsearch' +/// or match highlighting. +void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr) +{ + matchitem_T *cur = wp->w_match_head; // points to the match list + match_T *shl; // points to search_hl or a match + bool shl_flag = false; // flag to indicate whether search_hl + // has been processed or not + + *char_attr = search_hl->attr; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; + } + if (col - 1 == (long)shl->startcol + && (shl == search_hl || !shl->is_addpos)) { + *char_attr = shl->attr; + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } +} + +static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **win) +{ + dictitem_T *di; + + if (tv->v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return FAIL; + } + + if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) { + *conceal_char = tv_get_string(&di->di_tv); + } + + if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) { + *win = find_win_by_nr_or_id(&di->di_tv); + if (*win == NULL) { + emsg(_(e_invalwindow)); + return FAIL; + } + } + + return OK; +} + +/// "clearmatches()" function +void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *win = get_optional_window(argvars, 0); + + if (win != NULL) { + clear_matches(win); + } +} + +/// "getmatches()" function +void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + matchitem_T *cur; + int i; + win_T *win = get_optional_window(argvars, 0); + + if (win == NULL) { + return; + } + + tv_list_alloc_ret(rettv, kListLenMayKnow); + cur = win->w_match_head; + while (cur != NULL) { + dict_T *dict = tv_dict_alloc(); + if (cur->match.regprog == NULL) { + // match added with matchaddpos() + for (i = 0; i < MAXPOSMATCH; i++) { + llpos_T *llpos; + char buf[30]; // use 30 to avoid compiler warning + + llpos = &cur->pos.pos[i]; + if (llpos->lnum == 0) { + break; + } + list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); + tv_list_append_number(l, (varnumber_T)llpos->lnum); + if (llpos->col > 0) { + tv_list_append_number(l, (varnumber_T)llpos->col); + tv_list_append_number(l, (varnumber_T)llpos->len); + } + int len = snprintf(buf, sizeof(buf), "pos%d", i + 1); + assert((size_t)len < sizeof(buf)); + tv_dict_add_list(dict, buf, (size_t)len, l); + } + } else { + tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern); + } + tv_dict_add_str(dict, S_LEN("group"), + (const char *)syn_id2name(cur->hlg_id)); + tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority); + tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id); + + if (cur->conceal_char) { + char buf[MB_MAXBYTES + 1]; + + buf[utf_char2bytes(cur->conceal_char, (char_u *)buf)] = NUL; + tv_dict_add_str(dict, S_LEN("conceal"), buf); + } + + tv_list_append_dict(rettv->vval.v_list, dict); + cur = cur->next; + } +} + +/// "setmatches()" function +void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *d; + list_T *s = NULL; + win_T *win = get_optional_window(argvars, 1); + + rettv->vval.v_number = -1; + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } + if (win == NULL) { + return; + } + + list_T *const l = argvars[0].vval.v_list; + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int li_idx = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + semsg(_("E474: List item %d is either not a dictionary " + "or an empty one"), li_idx); + return; + } + if (!(tv_dict_find(d, S_LEN("group")) != NULL + && (tv_dict_find(d, S_LEN("pattern")) != NULL + || tv_dict_find(d, S_LEN("pos1")) != NULL) + && tv_dict_find(d, S_LEN("priority")) != NULL + && tv_dict_find(d, S_LEN("id")) != NULL)) { + semsg(_("E474: List item %d is missing one of the required keys"), + li_idx); + return; + } + li_idx++; + }); + + clear_matches(win); + bool match_add_failed = false; + TV_LIST_ITER_CONST(l, li, { + int i = 0; + + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); + if (di == NULL) { + if (s == NULL) { + s = tv_list_alloc(9); + } + + // match from matchaddpos() + for (i = 1; i < 9; i++) { + char buf[30]; // use 30 to avoid compiler warning + snprintf(buf, sizeof(buf), "pos%d", i); + dictitem_T *const pos_di = tv_dict_find(d, buf, -1); + if (pos_di != NULL) { + if (pos_di->di_tv.v_type != VAR_LIST) { + return; + } + + tv_list_append_tv(s, &pos_di->di_tv); + tv_list_ref(s); + } else { + break; + } + } + } + + // Note: there are three number buffers involved: + // - group_buf below. + // - numbuf in tv_dict_get_string(). + // - mybuf in tv_get_string(). + // + // If you change this code make sure that buffers will not get + // accidentally reused. + char group_buf[NUMBUFLEN]; + const char *const group = tv_dict_get_string_buf(d, "group", group_buf); + const int priority = (int)tv_dict_get_number(d, "priority"); + const int id = (int)tv_dict_get_number(d, "id"); + dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); + const char *const conceal = (conceal_di != NULL + ? tv_get_string(&conceal_di->di_tv) + : NULL); + if (i == 0) { + if (match_add(win, group, + tv_dict_get_string(d, "pattern", false), + priority, id, NULL, conceal) != id) { + match_add_failed = true; + } + } else { + if (match_add(win, group, NULL, priority, id, s, conceal) != id) { + match_add_failed = true; + } + tv_list_unref(s); + s = NULL; + } + }); + if (!match_add_failed) { + rettv->vval.v_number = 0; + } +} + +/// "matchadd()" function +void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char grpbuf[NUMBUFLEN]; + char patbuf[NUMBUFLEN]; + // group + const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf); + // pattern + const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); + // default priority + int prio = 10; + int id = -1; + bool error = false; + const char *conceal_char = NULL; + win_T *win = curwin; + + rettv->vval.v_number = -1; + + if (grp == NULL || pat == NULL) { + return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = (int)tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = (int)tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; + } + } + } + if (error) { + return; + } + if (id >= 1 && id <= 3) { + semsg(_("E798: ID is reserved for \":match\": %" PRId64), (int64_t)id); + return; + } + + rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); +} + +/// "matchaddpo()" function +void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + char buf[NUMBUFLEN]; + const char *const group = tv_get_string_buf_chk(&argvars[0], buf); + if (group == NULL) { + return; + } + + if (argvars[1].v_type != VAR_LIST) { + semsg(_(e_listarg), "matchaddpos()"); + return; + } + + list_T *l; + l = argvars[1].vval.v_list; + if (l == NULL) { + return; + } + + bool error = false; + int prio = 10; + int id = -1; + const char *conceal_char = NULL; + win_T *win = curwin; + + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = (int)tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = (int)tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; + } + } + } + if (error == true) { + return; + } + + // id == 3 is ok because matchaddpos() is supposed to substitute :3match + if (id == 1 || id == 2) { + semsg(_("E798: ID is reserved for \"match\": %" PRId64), (int64_t)id); + return; + } + + rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); +} + +/// "matcharg()" function +void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const int id = (int)tv_get_number(&argvars[0]); + + tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 + ? 2 + : 0)); + + if (id >= 1 && id <= 3) { + matchitem_T *const m = get_match(curwin, id); + + if (m != NULL) { + tv_list_append_string(rettv->vval.v_list, + (const char *)syn_id2name(m->hlg_id), -1); + tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); + } else { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } + } +} + +/// "matchdelete()" function +void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *win = get_optional_window(argvars, 1); + if (win == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = match_delete(win, + (int)tv_get_number(&argvars[0]), true); + } +} + +/// ":[N]match {group} {pattern}" +/// Sets nextcmd to the start of the next command, if any. Also called when +/// skipping commands to find the next command. +void ex_match(exarg_T *eap) +{ + char_u *p; + char_u *g = NULL; + char_u *end; + int c; + int id; + + if (eap->line2 <= 3) { + id = (int)eap->line2; + } else { + emsg(e_invcmd); + return; + } + + // First clear any old pattern. + if (!eap->skip) { + match_delete(curwin, id, false); + } + + if (ends_excmd(*eap->arg)) { + end = eap->arg; + } else if ((STRNICMP(eap->arg, "none", 4) == 0 + && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) { + end = eap->arg + 4; + } else { + p = skiptowhite(eap->arg); + if (!eap->skip) { + g = vim_strnsave(eap->arg, (size_t)(p - eap->arg)); + } + p = skipwhite(p); + if (*p == NUL) { + // There must be two arguments. + xfree(g); + semsg(_(e_invarg2), eap->arg); + return; + } + end = skip_regexp(p + 1, *p, true, NULL); + if (!eap->skip) { + if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { + xfree(g); + eap->errmsg = e_trailing; + return; + } + if (*end != *p) { + xfree(g); + semsg(_(e_invarg2), p); + return; + } + + c = *end; + *end = NUL; + match_add(curwin, (const char *)g, (const char *)p + 1, 10, id, + NULL, NULL); + xfree(g); + *end = (char_u)c; + } + } + eap->nextcmd = find_nextcmd(end); +} + diff --git a/src/nvim/match.h b/src/nvim/match.h new file mode 100644 index 0000000000..fdcec0ae05 --- /dev/null +++ b/src/nvim/match.h @@ -0,0 +1,12 @@ +#ifndef NVIM_MATCH_H +#define NVIM_MATCH_H + +#include "nvim/buffer_defs.h" +#include "nvim/eval/funcs.h" +#include "nvim/ex_cmds_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "match.h.generated.h" +#endif + +#endif // NVIM_MATCH_H diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 42117bc762..f634c7dda8 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -42,13 +42,13 @@ #include "nvim/eval.h" #include "nvim/fileio.h" #include "nvim/func_attr.h" +#include "nvim/getchar.h" #include "nvim/iconv.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/path.h" @@ -1067,7 +1067,7 @@ bool utf_printable(int c) static struct interval nonprint[] = { { 0x070f, 0x070f }, { 0x180b, 0x180e }, { 0x200b, 0x200f }, { 0x202a, 0x202e }, - { 0x206a, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb }, + { 0x2060, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb }, { 0xfffe, 0xffff } }; @@ -1317,6 +1317,12 @@ bool mb_isupper(int a) return mb_tolower(a) != a; } +bool mb_isalpha(int a) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + return mb_islower(a) || mb_isupper(a); +} + static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2) { int c1, c2, cdiff; @@ -1609,7 +1615,8 @@ void show_utf8(void) msg((char *)IObuff); } -/// Return offset from "p" to the first byte of the character it points into. +/// Return offset from "p" to the start of a character, including composing characters. +/// "base" must be the start of the string, which must be NUL terminated. /// If "p" points to the NUL at the end of the string return 0. /// Returns 0 when already at the first byte of a character. int utf_head_off(const char_u *base, const char_u *p) @@ -1820,12 +1827,10 @@ void mb_copy_char(const char_u **const fp, char_u **const tp) *fp += l; } -/* - * Return the offset from "p" to the first byte of a character. When "p" is - * at the start of a character 0 is returned, otherwise the offset to the next - * character. Can start anywhere in a stream of bytes. - */ -int mb_off_next(char_u *base, char_u *p) +/// Return the offset from "p" to the first byte of a character. When "p" is +/// at the start of a character 0 is returned, otherwise the offset to the next +/// character. Can start anywhere in a stream of bytes. +int mb_off_next(const char_u *base, const char_u *p) { int i; int j; @@ -1850,11 +1855,10 @@ int mb_off_next(char_u *base, char_u *p) return i; } -/* - * Return the offset from "p" to the last byte of the character it points - * into. Can start anywhere in a stream of bytes. - */ -int mb_tail_off(char_u *base, char_u *p) +/// Return the offset from "p" to the last byte of the character it points +/// into. Can start anywhere in a stream of bytes. +/// Composing characters are not included. +int mb_tail_off(const char_u *base, const char_u *p) { int i; int j; @@ -1882,12 +1886,13 @@ int mb_tail_off(char_u *base, char_u *p) /// Return the offset from "p" to the first byte of the character it points /// into. Can start anywhere in a stream of bytes. +/// Unlike utf_head_off() this doesn't include composing characters and returns a negative value. /// /// @param[in] base Pointer to start of string /// @param[in] p Pointer to byte for which to return the offset to the previous codepoint // /// @return 0 if invalid sequence, else offset to previous codepoint -int mb_head_off(char_u *base, char_u *p) +int mb_head_off(const char_u *base, const char_u *p) { int i; int j; @@ -2037,13 +2042,11 @@ char_u *mb_prevptr(char_u *line, char_u *p) return p; } -/* - * Return the character length of "str". Each multi-byte character (with - * following composing characters) counts as one. - */ -int mb_charlen(char_u *str) +/// Return the character length of "str". Each multi-byte character (with +/// following composing characters) counts as one. +int mb_charlen(const char_u *str) { - char_u *p = str; + const char_u *p = str; int count; if (p == NULL) { @@ -2057,12 +2060,10 @@ int mb_charlen(char_u *str) return count; } -/* - * Like mb_charlen() but for a string with specified length. - */ -int mb_charlen_len(char_u *str, int len) +/// Like mb_charlen() but for a string with specified length. +int mb_charlen_len(const char_u *str, int len) { - char_u *p = str; + const char_u *p = str; int count; for (count = 0; *p != NUL && p < str + len; count++) { @@ -2089,8 +2090,7 @@ const char *mb_unescape(const char **const pp) size_t buf_idx = 0; uint8_t *str = (uint8_t *)(*pp); - // Must translate K_SPECIAL KS_SPECIAL KE_FILLER to K_SPECIAL and CSI - // KS_EXTRA KE_CSI to CSI. + // Must translate K_SPECIAL KS_SPECIAL KE_FILLER to K_SPECIAL. // Maximum length of a utf-8 character is 4 bytes. for (size_t str_idx = 0; str[str_idx] != NUL && buf_idx < 4; str_idx++) { if (str[str_idx] == K_SPECIAL @@ -2098,11 +2098,6 @@ const char *mb_unescape(const char **const pp) && str[str_idx + 2] == KE_FILLER) { buf[buf_idx++] = (char)K_SPECIAL; str_idx += 2; - } else if ((str[str_idx] == K_SPECIAL) - && str[str_idx + 1] == KS_EXTRA - && str[str_idx + 2] == KE_CSI) { - buf[buf_idx++] = (char)CSI; - str_idx += 2; } else if (str[str_idx] == K_SPECIAL) { break; // A special key can't be a multibyte char. } else { @@ -2207,11 +2202,9 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET return r; } -/* - * Search for an encoding alias of "name". - * Returns -1 when not found. - */ -static int enc_alias_search(char_u *name) +/// Search for an encoding alias of "name". +/// Returns -1 when not found. +static int enc_alias_search(const char_u *name) { int i; diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 3397296b3a..2a72d1e6a0 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -247,7 +247,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) } else { // need to allocate memory for this block // If the number of pages matches use the bhdr_T from the free list and // allocate the data. - void *p = xmalloc(mfp->mf_page_size * page_count); + void *p = xmalloc((size_t)mfp->mf_page_size * page_count); hp = mf_rem_free(mfp); hp->bh_data = p; } @@ -269,7 +269,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) // Init the data to all zero, to avoid reading uninitialized data. // This also avoids that the passwd file ends up in the swap file! - (void)memset(hp->bh_data, 0, mfp->mf_page_size * page_count); + (void)memset(hp->bh_data, 0, (size_t)mfp->mf_page_size * page_count); return hp; } @@ -528,7 +528,7 @@ bool mf_release_all(void) static bhdr_T *mf_alloc_bhdr(memfile_T *mfp, unsigned page_count) { bhdr_T *hp = xmalloc(sizeof(bhdr_T)); - hp->bh_data = xmalloc(mfp->mf_page_size * page_count); + hp->bh_data = xmalloc((size_t)mfp->mf_page_size * page_count); hp->bh_page_count = page_count; return hp; } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 08202a6d5c..5f6e1ea273 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -51,6 +51,7 @@ #include "nvim/fileio.h" #include "nvim/func_attr.h" #include "nvim/getchar.h" +#include "nvim/input.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -58,7 +59,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" @@ -230,7 +230,7 @@ static linenr_T lowest_marked = 0; #define ML_INSERT 0x12 // insert line #define ML_FIND 0x13 // just find the line #define ML_FLUSH 0x02 // flush locked block -#define ML_SIMPLE(x) (x & 0x10) // DEL, INS or FIND +#define ML_SIMPLE(x) ((x) & 0x10) // DEL, INS or FIND // argument for ml_upd_block0() typedef enum { @@ -242,11 +242,9 @@ typedef enum { # include "memline.c.generated.h" #endif -/* - * Open a new memline for "buf". - * - * Return FAIL for failure, OK otherwise. - */ +/// Open a new memline for "buf". +/// +/// @return FAIL for failure, OK otherwise. int ml_open(buf_T *buf) { bhdr_T *hp = NULL; @@ -303,7 +301,7 @@ int ml_open(buf_T *buf) b0p->b0_id[0] = BLOCK0_ID0; b0p->b0_id[1] = BLOCK0_ID1; - b0p->b0_magic_long = (long)B0_MAGIC_LONG; + b0p->b0_magic_long = B0_MAGIC_LONG; b0p->b0_magic_int = (int)B0_MAGIC_INT; b0p->b0_magic_short = (short)B0_MAGIC_SHORT; b0p->b0_magic_char = B0_MAGIC_CHAR; @@ -379,10 +377,8 @@ error: return FAIL; } -/* - * ml_setname() is called when the file name of "buf" has been changed. - * It may rename the swap file. - */ +/// ml_setname() is called when the file name of "buf" has been changed. +/// It may rename the swap file. void ml_setname(buf_T *buf) { bool success = false; @@ -458,11 +454,9 @@ void ml_setname(buf_T *buf) } } -/* - * Open a file for the memfile for all buffers that are not readonly or have - * been modified. - * Used when 'updatecount' changes from zero to non-zero. - */ +/// Open a file for the memfile for all buffers that are not readonly or have +/// been modified. +/// Used when 'updatecount' changes from zero to non-zero. void ml_open_files(void) { FOR_ALL_BUFFERS(buf) { @@ -472,11 +466,9 @@ void ml_open_files(void) } } -/* - * Open a swap file for an existing memfile, if there is no swap file yet. - * If we are unable to find a file name, mf_fname will be NULL - * and the memfile will be in memory only (no recovery possible). - */ +/// Open a swap file for an existing memfile, if there is no swap file yet. +/// If we are unable to find a file name, mf_fname will be NULL +/// and the memfile will be in memory only (no recovery possible). void ml_open_file(buf_T *buf) { memfile_T *mfp; @@ -563,10 +555,9 @@ void check_need_swap(bool newfile) msg_silent = old_msg_silent; } -/* - * Close memline for buffer 'buf'. - * If 'del_file' is TRUE, delete the swap file - */ +/// Close memline for buffer 'buf'. +/// +/// @param del_file if TRUE, delete the swap file void ml_close(buf_T *buf, int del_file) { if (buf->b_ml.ml_mfp == NULL) { // not open @@ -585,25 +576,21 @@ void ml_close(buf_T *buf, int del_file) buf->b_flags &= ~BF_RECOVERED; } -/* - * Close all existing memlines and memfiles. - * Only used when exiting. - * When 'del_file' is TRUE, delete the memfiles. - * But don't delete files that were ":preserve"d when we are POSIX compatible. - */ -void ml_close_all(int del_file) +/// Close all existing memlines and memfiles. +/// Only used when exiting. +/// +/// @param del_file if true, delete the memfiles. +void ml_close_all(bool del_file) { FOR_ALL_BUFFERS(buf) { - ml_close(buf, del_file && ((buf->b_flags & BF_PRESERVED) == 0)); + ml_close(buf, del_file); } spell_delete_wordlist(); // delete the internal wordlist vim_deltempdir(); // delete created temp directory } -/* - * Close all memfiles for not modified buffers. - * Only use just before exiting! - */ +/// Close all memfiles for not modified buffers. +/// Only use just before exiting! void ml_close_notmod(void) { FOR_ALL_BUFFERS(buf) { @@ -613,10 +600,8 @@ void ml_close_notmod(void) } } -/* - * Update the timestamp in the .swp file. - * Used when the file has been written. - */ +/// Update the timestamp in the .swp file. +/// Used when the file has been written. void ml_timestamp(buf_T *buf) { ml_upd_block0(buf, UB_FNAME); @@ -639,9 +624,7 @@ static bool ml_check_b0_strings(ZERO_BL *b0p) && memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT)); // -V512 } -/* - * Update the timestamp or the B0_SAME_DIR flag of the .swp file. - */ +/// Update the timestamp or the B0_SAME_DIR flag of the .swp file. static void ml_upd_block0(buf_T *buf, upd_block0_T what) { memfile_T *mfp; @@ -665,11 +648,9 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what) mf_put(mfp, hp, true, false); } -/* - * Write file name and timestamp into block 0 of a swap file. - * Also set buf->b_mtime. - * Don't use NameBuff[]!!! - */ +/// Write file name and timestamp into block 0 of a swap file. +/// Also set buf->b_mtime. +/// Don't use NameBuff[]!!! static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) { if (buf->b_ffname == NULL) { @@ -704,11 +685,14 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) long_to_char((long)os_fileinfo_inode(&file_info), b0p->b0_ino); buf_store_file_info(buf, &file_info); buf->b_mtime_read = buf->b_mtime; + buf->b_mtime_read_ns = buf->b_mtime_ns; } else { long_to_char(0L, b0p->b0_mtime); long_to_char(0L, b0p->b0_ino); buf->b_mtime = 0; + buf->b_mtime_ns = 0; buf->b_mtime_read = 0; + buf->b_mtime_read_ns = 0; buf->b_orig_size = 0; buf->b_orig_mode = 0; } @@ -718,12 +702,10 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) add_b0_fenc(b0p, curbuf); } -/* - * Update the B0_SAME_DIR flag of the swap file. It's set if the file and the - * swapfile for "buf" are in the same directory. - * This is fail safe: if we are not sure the directories are equal the flag is - * not set. - */ +/// Update the B0_SAME_DIR flag of the swap file. It's set if the file and the +/// swapfile for "buf" are in the same directory. +/// This is fail safe: if we are not sure the directories are equal the flag is +/// not set. static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) { if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) { @@ -733,9 +715,7 @@ static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) } } -/* - * When there is room, add the 'fileencoding' to block zero. - */ +/// When there is room, add the 'fileencoding' to block zero. static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) { int n; @@ -754,8 +734,9 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) /// Try to recover curbuf from the .swp file. -/// @param checkext If true, check the extension and detect whether it is a -/// swap file. +/// +/// @param checkext if true, check the extension and detect whether it is a +/// swap file. void ml_recover(bool checkext) { buf_T *buf = NULL; @@ -1032,9 +1013,9 @@ void ml_recover(bool checkext) line_count = 0; idx = 0; // start with first index in block 1 error = 0; - buf->b_ml.ml_stack_top = 0; + buf->b_ml.ml_stack_top = 0; // -V1048 buf->b_ml.ml_stack = NULL; - buf->b_ml.ml_stack_size = 0; // no stack yet + buf->b_ml.ml_stack_size = 0; // -V1048 if (curbuf->b_ffname == NULL) { cannot_open = true; @@ -1462,10 +1443,8 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) return file_count; } -/* - * Append the full path to name with path separators made into percent - * signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"") - */ +/// Append the full path to name with path separators made into percent +/// signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"") char *make_percent_swname(const char *dir, const char *name) FUNC_ATTR_NONNULL_ARG(1) { @@ -1487,8 +1466,9 @@ char *make_percent_swname(const char *dir, const char *name) static bool process_still_running; -/// Return information found in swapfile "fname" in dictionary "d". /// This is used by the swapinfo() function. +/// +/// @return information found in swapfile "fname" in dictionary "d". void get_b0_dict(const char *fname, dict_T *d) { int fd; @@ -1525,7 +1505,8 @@ void get_b0_dict(const char *fname, dict_T *d) } /// Give information about an existing swap file. -/// Returns timestamp (0 when unknown). +/// +/// @return timestamp (0 when unknown). static time_t swapfile_info(char_u *fname) { assert(fname != NULL); @@ -1615,9 +1596,9 @@ static time_t swapfile_info(char_u *fname) return x; } -/// Returns TRUE if the swap file looks OK and there are no changes, thus it -/// can be safely deleted. -static time_t swapfile_unchanged(char *fname) +/// @return true if the swap file looks OK and there are no changes, thus it +/// can be safely deleted. +static bool swapfile_unchanged(char *fname) { struct block0 b0; int ret = true; @@ -1695,13 +1676,12 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot) return num_names; } -/* - * sync all memlines - * - * If 'check_file' is TRUE, check if original file exists and was not changed. - * If 'check_char' is TRUE, stop syncing when character becomes available, but - * always sync at least one block. - */ +/// sync all memlines +/// +/// @param check_file if TRUE, check if original file exists and was not changed. +/// @param check_char if TRUE, stop syncing when character becomes available, but +/// +/// always sync at least one block. void ml_sync_all(int check_file, int check_char, bool do_fsync) { FOR_ALL_BUFFERS(buf) { @@ -1720,6 +1700,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync) FileInfo file_info; if (!os_fileinfo((char *)buf->b_ffname, &file_info) || file_info.stat.st_mtim.tv_sec != buf->b_mtime_read + || file_info.stat.st_mtim.tv_nsec != buf->b_mtime_read_ns || os_fileinfo_size(&file_info) != buf->b_orig_size) { ml_preserve(buf, false, do_fsync); did_check_timestamps = false; @@ -1736,16 +1717,14 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync) } } -/* - * sync one buffer, including negative blocks - * - * after this all the blocks are in the swap file - * - * Used for the :preserve command and when the original file has been - * changed or deleted. - * - * when message is TRUE the success of preserving is reported - */ +/// sync one buffer, including negative blocks +/// +/// after this all the blocks are in the swap file +/// +/// Used for the :preserve command and when the original file has been +/// changed or deleted. +/// +/// @param message if TRUE, the success of preserving is reported. void ml_preserve(buf_T *buf, int message, bool do_fsync) { bhdr_T *hp; @@ -1821,29 +1800,37 @@ theend: * line2 = ml_get(2); // line1 is now invalid! * Make a copy of the line if necessary. */ -/* - * Return a pointer to a (read-only copy of a) line. - * - * On failure an error message is given and IObuff is returned (to avoid - * having to check for error everywhere). - */ + +/// @return a pointer to a (read-only copy of a) line. +/// +/// On failure an error message is given and IObuff is returned (to avoid +/// having to check for error everywhere). char_u *ml_get(linenr_T lnum) { return ml_get_buf(curbuf, lnum, false); } -/* - * Return pointer to position "pos". - */ +/// @return pointer to position "pos". char_u *ml_get_pos(const pos_T *pos) FUNC_ATTR_NONNULL_ALL { return ml_get_buf(curbuf, pos->lnum, false) + pos->col; } -/// Return a pointer to a line in a specific buffer -/// +/// @return codepoint at pos. pos must be either valid or have col set to MAXCOL! +int gchar_pos(pos_T *pos) + FUNC_ATTR_NONNULL_ARG(1) +{ + // When searching columns is sometimes put at the end of a line. + if (pos->col == MAXCOL) { + return NUL; + } + return utf_ptr2char(ml_get_pos(pos)); +} + /// @param will_change true mark the buffer dirty (chars in the line will be changed) +/// +/// @return a pointer to a line in a specific buffer char_u *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change) FUNC_ATTR_NONNULL_ALL { @@ -1916,10 +1903,8 @@ errorret: return buf->b_ml.ml_line_ptr; } -/* - * Check if a line that was just obtained by a call to ml_get - * is in allocated memory. - */ +/// Check if a line that was just obtained by a call to ml_get +/// is in allocated memory. int ml_line_alloced(void) { return curbuf->b_ml.ml_flags & ML_LINE_DIRTY; @@ -2342,95 +2327,88 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b * We are finished, break the loop here. */ break; - } else { // pointer block full - /* - * split the pointer block - * allocate a new pointer block - * move some of the pointer into the new block - * prepare for updating the parent block - */ - for (;;) { // do this twice when splitting block 1 - hp_new = ml_new_ptr(mfp); - if (hp_new == NULL) { // TODO: try to fix tree - return FAIL; - } - pp_new = hp_new->bh_data; - - if (hp->bh_bnum != 1) { - break; - } - - /* - * if block 1 becomes full the tree is given an extra level - * The pointers from block 1 are moved into the new block. - * block 1 is updated to point to the new block - * then continue to split the new block - */ - memmove(pp_new, pp, (size_t)page_size); - pp->pb_count = 1; - pp->pb_pointer[0].pe_bnum = hp_new->bh_bnum; - pp->pb_pointer[0].pe_line_count = buf->b_ml.ml_line_count; - pp->pb_pointer[0].pe_old_lnum = 1; - pp->pb_pointer[0].pe_page_count = 1; - mf_put(mfp, hp, true, false); // release block 1 - hp = hp_new; // new block is to be split - pp = pp_new; - CHECK(stack_idx != 0, _("stack_idx should be 0")); - ip->ip_index = 0; - ++stack_idx; // do block 1 again later - } - /* - * move the pointers after the current one to the new block - * If there are none, the new entry will be in the new block. - */ - total_moved = pp->pb_count - pb_idx - 1; - if (total_moved) { - memmove(&pp_new->pb_pointer[0], - &pp->pb_pointer[pb_idx + 1], - (size_t)(total_moved) * sizeof(PTR_EN)); - pp_new->pb_count = total_moved; - pp->pb_count -= total_moved - 1; - pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; - pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; - pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; - if (lnum_right) { - pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; - } - } else { - pp_new->pb_count = 1; - pp_new->pb_pointer[0].pe_bnum = bnum_right; - pp_new->pb_pointer[0].pe_line_count = line_count_right; - pp_new->pb_pointer[0].pe_page_count = page_count_right; - pp_new->pb_pointer[0].pe_old_lnum = lnum_right; - } - pp->pb_pointer[pb_idx].pe_bnum = bnum_left; - pp->pb_pointer[pb_idx].pe_line_count = line_count_left; - pp->pb_pointer[pb_idx].pe_page_count = page_count_left; - if (lnum_left) { - pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; + } + // pointer block full + // + // split the pointer block + // allocate a new pointer block + // move some of the pointer into the new block + // prepare for updating the parent block + for (;;) { // do this twice when splitting block 1 + hp_new = ml_new_ptr(mfp); + if (hp_new == NULL) { // TODO(vim): try to fix tree + return FAIL; } - lnum_left = 0; - lnum_right = 0; + pp_new = hp_new->bh_data; - /* - * recompute line counts - */ - line_count_right = 0; - for (i = 0; i < (int)pp_new->pb_count; ++i) { - line_count_right += pp_new->pb_pointer[i].pe_line_count; + if (hp->bh_bnum != 1) { + break; } - line_count_left = 0; - for (i = 0; i < (int)pp->pb_count; ++i) { - line_count_left += pp->pb_pointer[i].pe_line_count; + + // if block 1 becomes full the tree is given an extra level + // The pointers from block 1 are moved into the new block. + // block 1 is updated to point to the new block + // then continue to split the new block + memmove(pp_new, pp, (size_t)page_size); + pp->pb_count = 1; + pp->pb_pointer[0].pe_bnum = hp_new->bh_bnum; + pp->pb_pointer[0].pe_line_count = buf->b_ml.ml_line_count; + pp->pb_pointer[0].pe_old_lnum = 1; + pp->pb_pointer[0].pe_page_count = 1; + mf_put(mfp, hp, true, false); // release block 1 + hp = hp_new; // new block is to be split + pp = pp_new; + CHECK(stack_idx != 0, _("stack_idx should be 0")); + ip->ip_index = 0; + stack_idx++; // do block 1 again later + } + // move the pointers after the current one to the new block + // If there are none, the new entry will be in the new block. + total_moved = pp->pb_count - pb_idx - 1; + if (total_moved) { + memmove(&pp_new->pb_pointer[0], + &pp->pb_pointer[pb_idx + 1], + (size_t)(total_moved) * sizeof(PTR_EN)); + pp_new->pb_count = total_moved; + pp->pb_count -= total_moved - 1; + pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; + pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; + pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; + if (lnum_right) { + pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; } + } else { + pp_new->pb_count = 1; + pp_new->pb_pointer[0].pe_bnum = bnum_right; + pp_new->pb_pointer[0].pe_line_count = line_count_right; + pp_new->pb_pointer[0].pe_page_count = page_count_right; + pp_new->pb_pointer[0].pe_old_lnum = lnum_right; + } + pp->pb_pointer[pb_idx].pe_bnum = bnum_left; + pp->pb_pointer[pb_idx].pe_line_count = line_count_left; + pp->pb_pointer[pb_idx].pe_page_count = page_count_left; + if (lnum_left) { + pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; + } + lnum_left = 0; + lnum_right = 0; - bnum_left = hp->bh_bnum; - bnum_right = hp_new->bh_bnum; - page_count_left = 1; - page_count_right = 1; - mf_put(mfp, hp, true, false); - mf_put(mfp, hp_new, true, false); + // recompute line counts + line_count_right = 0; + for (i = 0; i < (int)pp_new->pb_count; i++) { + line_count_right += pp_new->pb_pointer[i].pe_line_count; + } + line_count_left = 0; + for (i = 0; i < (int)pp->pb_count; i++) { + line_count_left += pp->pb_pointer[i].pe_line_count; } + + bnum_left = hp->bh_bnum; + bnum_right = hp_new->bh_bnum; + page_count_left = 1; + page_count_right = 1; + mf_put(mfp, hp, true, false); + mf_put(mfp, hp_new, true, false); } /* @@ -2476,17 +2454,18 @@ 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. -// 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 +/// Replace line "lnum", with buffering, in current buffer. +/// +/// @param copy if true, make a copy of the line, otherwise the line has been +/// copied to allocated memory already. +/// if 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... @@ -2529,7 +2508,8 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) /// deleted_lines() after this. /// /// @param message Show "--No lines in buffer--" message. -/// @return FAIL for failure, OK otherwise +/// +/// @return FAIL for failure, OK otherwise int ml_delete(linenr_T lnum, bool message) { ml_flush_line(curbuf); @@ -2686,9 +2666,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) return OK; } -/* - * set the B_MARKED flag for line 'lnum' - */ +/// set the B_MARKED flag for line 'lnum' void ml_setmarked(linenr_T lnum) { bhdr_T *hp; @@ -2715,9 +2693,7 @@ void ml_setmarked(linenr_T lnum) curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; } -/* - * find the first line with its B_MARKED flag set - */ +/// find the first line with its B_MARKED flag set linenr_T ml_firstmarked(void) { bhdr_T *hp; @@ -2758,9 +2734,7 @@ linenr_T ml_firstmarked(void) return (linenr_T)0; } -/* - * clear all DB_MARKED flags - */ +/// clear all DB_MARKED flags void ml_clearmarked(void) { bhdr_T *hp; @@ -2809,9 +2783,7 @@ size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits) return ret; } -/* - * flush ml_line if necessary - */ +/// flush ml_line if necessary static void ml_flush_line(buf_T *buf) { bhdr_T *hp; @@ -2908,9 +2880,7 @@ static void ml_flush_line(buf_T *buf) buf->b_ml.ml_line_offset = 0; } -/* - * create a new, empty, data block - */ +/// create a new, empty, data block static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count) { assert(page_count >= 0); @@ -2924,9 +2894,7 @@ static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count) return hp; } -/* - * create a new, empty, pointer block - */ +/// create a new, empty, pointer block static bhdr_T *ml_new_ptr(memfile_T *mfp) { bhdr_T *hp = mf_new(mfp, false, 1); @@ -2938,21 +2906,19 @@ static bhdr_T *ml_new_ptr(memfile_T *mfp) return hp; } -/* - * lookup line 'lnum' in a memline - * - * action: if ML_DELETE or ML_INSERT the line count is updated while searching - * if ML_FLUSH only flush a locked block - * if ML_FIND just find the line - * - * If the block was found it is locked and put in ml_locked. - * The stack is updated to lead to the locked block. The ip_high field in - * the stack is updated to reflect the last line in the block AFTER the - * insert or delete, also if the pointer block has not been updated yet. But - * if ml_locked != NULL ml_locked_lineadd must be added to ip_high. - * - * return: NULL for failure, pointer to block header otherwise - */ +/// lookup line 'lnum' in a memline +/// +/// @param action: if ML_DELETE or ML_INSERT the line count is updated while searching +/// if ML_FLUSH only flush a locked block +/// if ML_FIND just find the line +/// +/// If the block was found it is locked and put in ml_locked. +/// The stack is updated to lead to the locked block. The ip_high field in +/// the stack is updated to reflect the last line in the block AFTER the +/// insert or delete, also if the pointer block has not been updated yet. But +/// if ml_locked != NULL ml_locked_lineadd must be added to ip_high. +/// +/// @return NULL for failure, pointer to block header otherwise static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) { DATA_BL *dp; @@ -3133,11 +3099,9 @@ error_noblock: return NULL; } -/* - * add an entry to the info pointer stack - * - * return number of the new entry - */ +/// add an entry to the info pointer stack +/// +/// @return number of the new entry static int ml_add_stack(buf_T *buf) { int top = buf->b_ml.ml_stack_top; @@ -3155,16 +3119,14 @@ static int ml_add_stack(buf_T *buf) return top; } -/* - * Update the pointer blocks on the stack for inserted/deleted lines. - * The stack itself is also updated. - * - * When an insert/delete line action fails, the line is not inserted/deleted, - * but the pointer blocks have already been updated. That is fixed here by - * walking through the stack. - * - * Count is the number of lines added, negative if lines have been deleted. - */ +/// Update the pointer blocks on the stack for inserted/deleted lines. +/// The stack itself is also updated. +/// +/// When an insert/delete line action fails, the line is not inserted/deleted, +/// but the pointer blocks have already been updated. That is fixed here by +/// walking through the stack. +/// +/// Count is the number of lines added, negative if lines have been deleted. static void ml_lineadd(buf_T *buf, int count) { int idx; @@ -3191,13 +3153,13 @@ static void ml_lineadd(buf_T *buf, int count) } #if defined(HAVE_READLINK) -/* - * Resolve a symlink in the last component of a file name. - * Note that f_resolve() does it for every part of the path, we don't do that - * here. - * If it worked returns OK and the resolved link in "buf[MAXPATHL]". - * Otherwise returns FAIL. - */ + +/// Resolve a symlink in the last component of a file name. +/// Note that f_resolve() does it for every part of the path, we don't do that +/// here. +/// +/// @return OK if it worked and the resolved link in "buf[MAXPATHL]", +/// FAIL otherwise int resolve_symlink(const char_u *fname, char_u *buf) { char_u tmp[MAXPATHL]; @@ -3261,10 +3223,9 @@ int resolve_symlink(const char_u *fname, char_u *buf) } #endif -/* - * Make swap file name out of the file name and a directory name. - * Returns pointer to allocated memory or NULL. - */ +/// Make swap file name out of the file name and a directory name. +/// +/// @return pointer to allocated memory or NULL. char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name) { char_u *r, *s; @@ -3394,17 +3355,16 @@ static void attention_message(buf_T *buf, char_u *fname) } -/* - * Trigger the SwapExists autocommands. - * Returns a value for equivalent to do_dialog() (see below): - * 0: still need to ask for a choice - * 1: open read-only - * 2: edit anyway - * 3: recover - * 4: delete it - * 5: quit - * 6: abort - */ +/// Trigger the SwapExists autocommands. +/// +/// @return a value for equivalent to do_dialog() (see below): +/// 0: still need to ask for a choice +/// 1: open read-only +/// 2: edit anyway +/// 3: recover +/// 4: delete it +/// 5: quit +/// 6: abort static int do_swapexists(buf_T *buf, char_u *fname) { set_vim_var_string(VV_SWAPNAME, (char *)fname, -1); @@ -3694,7 +3654,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ static int b0_magic_wrong(ZERO_BL *b0p) { - return b0p->b0_magic_long != (long)B0_MAGIC_LONG + return b0p->b0_magic_long != B0_MAGIC_LONG || b0p->b0_magic_int != (int)B0_MAGIC_INT || b0p->b0_magic_short != (short)B0_MAGIC_SHORT || b0p->b0_magic_char != B0_MAGIC_CHAR; @@ -3798,10 +3758,8 @@ static bool fnamecmp_ino(char_u *fname_c, char_u *fname_s, long ino_block0) return true; } -/* - * Move a long integer into a four byte character array. - * Used for machine independency in block zero. - */ +/// Move a long integer into a four byte character array. +/// Used for machine independency in block zero. static void long_to_char(long n, char_u *s) { s[0] = (char_u)(n & 0xff); @@ -3828,12 +3786,10 @@ static long char_to_long(char_u *s) return retval; } -/* - * Set the flags in the first block of the swap file: - * - file is modified or not: buf->b_changed - * - 'fileformat' - * - 'fileencoding' - */ +/// Set the flags in the first block of the swap file: +/// - file is modified or not: buf->b_changed +/// - 'fileformat' +/// - 'fileencoding' void ml_setflags(buf_T *buf) { bhdr_T *hp; @@ -3859,13 +3815,13 @@ void ml_setflags(buf_T *buf) #define MLCS_MAXL 800 // max no of lines in chunk #define MLCS_MINL 400 // should be half of MLCS_MAXL -/* - * Keep information for finding byte offset of a line, updtype may be one of: - * ML_CHNK_ADDLINE: Add len to parent chunk, possibly splitting it - * Careful: ML_CHNK_ADDLINE may cause ml_find_line() to be called. - * ML_CHNK_DELLINE: Subtract len from parent chunk, possibly deleting it - * ML_CHNK_UPDLINE: Add len to parent chunk, as a signed entity. - */ +/// Keep information for finding byte offset of a line +/// +/// @param updtype may be one of: +/// ML_CHNK_ADDLINE: Add len to parent chunk, possibly splitting it +/// Careful: ML_CHNK_ADDLINE may cause ml_find_line() to be called. +/// ML_CHNK_DELLINE: Subtract len from parent chunk, possibly deleting it +/// ML_CHNK_UPDLINE: Add len to parent chunk, as a signed entity. static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) { static buf_T *ml_upd_lastbuf = NULL; @@ -4068,7 +4024,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) /// 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 +/// @return -1 if information is not available long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) { linenr_T curline; @@ -4128,7 +4084,7 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) || (offset != 0 && offset > size + buf->b_ml.ml_chunksize[curix].mlcs_totalsize - + ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) { + + (long)ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) { curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines; size += buf->b_ml.ml_chunksize[curix].mlcs_totalsize; if (offset && ffdos) { @@ -4243,10 +4199,11 @@ void goto_byte(long cnt) } /// Increment the line pointer "lp" crossing line boundaries as necessary. -/// Return 1 when going to the next line. -/// Return 2 when moving forward onto a NUL at the end of the line). -/// Return -1 when at the end of file. -/// Return 0 otherwise. +/// +/// @return 1 when going to the next line. +/// 2 when moving forward onto a NUL at the end of the line). +/// -1 when at the end of file. +/// 0 otherwise. int inc(pos_T *lp) { // when searching position may be set to end of a line diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 9683499eae..14930238e5 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -10,14 +10,14 @@ #include "nvim/api/extmark.h" #include "nvim/context.h" -#include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/eval.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/memfile.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/sign.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -573,7 +573,6 @@ void free_all_mem(void) return; } entered_free_all_mem = true; - // Don't want to trigger autocommands from here on. block_autocmds(); @@ -691,7 +690,7 @@ void free_all_mem(void) bufref_T bufref; set_bufref(&bufref, buf); nextbuf = buf->b_next; - close_buffer(NULL, buf, DOBUF_WIPE, false); + close_buffer(NULL, buf, DOBUF_WIPE, false, false); // Didn't work, try next one. buf = bufref_valid(&bufref) ? nextbuf : firstbuf; } diff --git a/src/nvim/menu.c b/src/nvim/menu.c index d596b31062..0db9d69a7e 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -22,7 +22,6 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -362,7 +361,7 @@ static int add_menu_path(const char_u *const menu_path, vimmenu_T *menuarg, goto erret; } - // Not already there, so lets add it + // Not already there, so let's add it menu = xcalloc(1, sizeof(vimmenu_T)); menu->modes = modes; diff --git a/src/nvim/message.c b/src/nvim/message.c index 6fcd4cef8a..6708001495 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -23,12 +23,12 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/input.h" #include "nvim/keymap.h" #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/normal.h" #include "nvim/ops.h" @@ -76,6 +76,8 @@ static int msg_hist_len = 0; static FILE *verbose_fd = NULL; static int verbose_did_open = FALSE; +bool keep_msg_more = false; // keep_msg was set by msgmore() + /* * When writing messages to the screen, there are many different situations. * A number of variables is used to remember the current state: @@ -204,11 +206,10 @@ void msg_grid_validate(void) } } -/* - * msg(s) - displays the string 's' on the status line - * When terminal not initialized (yet) mch_errmsg(..) is used. - * return TRUE if wait_return not called - */ +/// Displays the string 's' on the status line +/// When terminal not initialized (yet) mch_errmsg(..) is used. +/// +/// @return TRUE if wait_return not called int msg(char *s) { return msg_attr_keep(s, 0, false, false); @@ -230,7 +231,7 @@ int msg_attr(const char *s, const int attr) return msg_attr_keep(s, attr, false, false); } -/// similar to msg_outtrans_attr, but support newlines and tabs. +/// Similar to msg_outtrans_attr, but support newlines and tabs. void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clear) FUNC_ATTR_NONNULL_ALL { @@ -260,7 +261,6 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea if (*s != NUL) { msg_outtrans_attr((char_u *)s, attr); } - return; } @@ -327,18 +327,20 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) } retval = msg_end(); - if (keep && retval && vim_strsize((char_u *)s) < (int)(Rows - cmdline_row - 1) - * Columns + sc_col) { + if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1) * Columns + sc_col) { set_keep_msg((char *)s, 0); } + need_fileinfo = false; + xfree(buf); --entered; return retval; } /// Truncate a string such that it can be printed without causing a scroll. -/// Returns an allocated string or NULL when no truncating is done. +/// +/// @return an allocated string or NULL when no truncating is done. /// /// @param force always truncate char_u *msg_strtrunc(char_u *s, int force) @@ -354,10 +356,10 @@ char_u *msg_strtrunc(char_u *s, int force) len = vim_strsize(s); if (msg_scrolled != 0) { // Use all the columns. - room = (int)(Rows - msg_row) * Columns - 1; + room = (Rows - msg_row) * Columns - 1; } else { // Use up to 'showcmd' column. - room = (int)(Rows - msg_row - 1) * Columns + sc_col - 1; + room = (Rows - msg_row - 1) * Columns + sc_col - 1; } if (len > room && room > 0) { // may have up to 18 bytes per cell (6 per char, up to two @@ -370,10 +372,8 @@ char_u *msg_strtrunc(char_u *s, int force) return buf; } -/* - * Truncate a string "s" to "buf" with cell width "room". - * "s" and "buf" may be equal. - */ +/// Truncate a string "s" to "buf" with cell width "room". +/// "s" and "buf" may be equal. void trunc_string(char_u *s, char_u *buf, int room_in, int buflen) { size_t room = room_in - 3; // "..." takes 3 chars @@ -383,6 +383,13 @@ void trunc_string(char_u *s, char_u *buf, int room_in, int buflen) int i; int n; + if (*s == NUL) { + if (buflen > 0) { + *buf = NUL; + } + return; + } + if (room_in < 3) { room = 0; } @@ -497,19 +504,15 @@ int smsg_attr_keep(int attr, const char *s, ...) static int last_sourcing_lnum = 0; static char_u *last_sourcing_name = NULL; -/* - * Reset the last used sourcing name/lnum. Makes sure it is displayed again - * for the next error message; - */ +/// Reset the last used sourcing name/lnum. Makes sure it is displayed again +/// for the next error message; void reset_last_sourcing(void) { XFREE_CLEAR(last_sourcing_name); last_sourcing_lnum = 0; } -/* - * Return TRUE if "sourcing_name" differs from "last_sourcing_name". - */ +/// @return TRUE if "sourcing_name" differs from "last_sourcing_name". static int other_sourcing_name(void) { if (sourcing_name != NULL) { @@ -559,11 +562,9 @@ static char *get_emsg_lnum(void) return NULL; } -/* - * Display name and line number for the source of an error. - * Remember the file name and line number, so that for the next error the info - * is only displayed if it changed. - */ +/// Display name and line number for the source of an error. +/// Remember the file name and line number, so that for the next error the info +/// is only displayed if it changed. void msg_source(int attr) { no_wait_return++; @@ -591,12 +592,10 @@ void msg_source(int attr) --no_wait_return; } -/* - * Return TRUE if not giving error messages right now: - * If "emsg_off" is set: no error messages at the moment. - * If "msg" is in 'debug': do error message but without side effects. - * If "emsg_skip" is set: never do error messages. - */ +/// @return TRUE if not giving error messages right now: +/// If "emsg_off" is set: no error messages at the moment. +/// If "msg" is in 'debug': do error message but without side effects. +/// If "emsg_skip" is set: never do error messages. int emsg_not_now(void) { if ((emsg_off > 0 && vim_strchr(p_debug, 'm') == NULL @@ -839,13 +838,12 @@ void msg_schedule_semsg(const char *const fmt, ...) multiqueue_put(main_loop.events, msg_semsg_event, 1, s); } -/* - * Like msg(), but truncate to a single line if p_shm contains 't', or when - * "force" is TRUE. This truncates in another way as for normal messages. - * Careful: The string may be changed by msg_may_trunc()! - * Returns a pointer to the printed message, if wait_return() not called. - */ -char *msg_trunc_attr(char *s, int force, int attr) +/// Like msg(), but truncate to a single line if p_shm contains 't', or when +/// "force" is true. This truncates in another way as for normal messages. +/// Careful: The string may be changed by msg_may_trunc()! +/// +/// @return a pointer to the printed message, if wait_return() not called. +char *msg_trunc_attr(char *s, bool force, int attr) { int n; @@ -864,16 +862,16 @@ char *msg_trunc_attr(char *s, int force, int attr) return NULL; } -/* - * Check if message "s" should be truncated at the start (for filenames). - * Return a pointer to where the truncated message starts. - * Note: May change the message by replacing a character with '<'. - */ -char_u *msg_may_trunc(int force, char_u *s) +/// Check if message "s" should be truncated at the start (for filenames). +/// +/// @return a pointer to where the truncated message starts. +/// +/// @note: May change the message by replacing a character with '<'. +char_u *msg_may_trunc(bool force, char_u *s) { int room; - room = (int)(Rows - cmdline_row - 1) * Columns + sc_col - 1; + room = (Rows - cmdline_row - 1) * Columns + sc_col - 1; if ((force || (shortmess(SHM_TRUNC) && !exmode_active)) && (int)STRLEN(s) - room > 0) { int size = vim_strsize(s); @@ -968,10 +966,9 @@ static void add_msg_hist(const char *s, int len, int attr, bool multiline) msg_hist_len++; } -/* - * Delete the first (oldest) message from the history. - * Returns FAIL if there are no messages. - */ +/// Delete the first (oldest) message from the history. +/// +/// @return FAIL if there are no messages. int delete_first_msg(void) { struct msg_hist *p; @@ -1057,10 +1054,8 @@ void ex_messages(void *const eap_p) } } -/* - * Call this after prompting the user. This will avoid a hit-return message - * and a delay. - */ +/// Call this after prompting the user. This will avoid a hit-return message +/// and a delay. void msg_end_prompt(void) { msg_ext_clear_later(); @@ -1074,9 +1069,9 @@ void msg_end_prompt(void) /// Wait for the user to hit a key (normally Enter) /// -/// If 'redraw' is true, redraw the entire screen NOT_VALID -/// If 'redraw' is false, do a normal redraw -/// If 'redraw' is -1, don't redraw at all +/// @param redraw if true, redraw the entire screen NOT_VALID +/// if false, do a normal redraw +/// if -1, don't redraw at all void wait_return(int redraw) { int c; @@ -1095,6 +1090,10 @@ void wait_return(int redraw) return; } + if (headless_mode && !ui_active()) { + return; + } + /* * When inside vgetc(), we can't wait for a typed character at all. * With the global command (and some others) we only need one return at @@ -1215,7 +1214,7 @@ void wait_return(int redraw) } else if (vim_strchr((char_u *)"\r\n ", c) == NULL && c != Ctrl_C) { // Put the character back in the typeahead buffer. Don't use the // stuff buffer, because lmaps wouldn't work. - ins_char_typebuf(c); + ins_char_typebuf(c, mod_mask); do_redraw = true; // need a redraw even though there is // typeahead } @@ -1262,14 +1261,12 @@ void wait_return(int redraw) } } -/* - * Write the hit-return prompt. - */ +/// Write the hit-return prompt. static void hit_return_msg(void) { int save_p_more = p_more; - p_more = FALSE; // don't want see this message when scrolling back + p_more = false; // don't want to see this message when scrolling back if (msg_didout) { // start on a new line msg_putchar('\n'); } @@ -1285,9 +1282,7 @@ static void hit_return_msg(void) p_more = save_p_more; } -/* - * Set "keep_msg" to "s". Free the old value and check for NULL pointer. - */ +/// Set "keep_msg" to "s". Free the old value and check for NULL pointer. void set_keep_msg(char *s, int attr) { xfree(keep_msg); @@ -1300,6 +1295,49 @@ void set_keep_msg(char *s, int attr) keep_msg_attr = attr; } +void msgmore(long n) +{ + long pn; + + if (global_busy // no messages now, wait until global is finished + || !messaging()) { // 'lazyredraw' set, don't do messages now + return; + } + + // We don't want to overwrite another important message, but do overwrite + // a previous "more lines" or "fewer lines" message, so that "5dd" and + // then "put" reports the last action. + if (keep_msg != NULL && !keep_msg_more) { + return; + } + + if (n > 0) { + pn = n; + } else { + pn = -n; + } + + if (pn > p_report) { + if (n > 0) { + vim_snprintf(msg_buf, MSG_BUF_LEN, + NGETTEXT("%ld more line", "%ld more lines", pn), + pn); + } else { + vim_snprintf(msg_buf, MSG_BUF_LEN, + NGETTEXT("%ld line less", "%ld fewer lines", pn), + pn); + } + if (got_int) { + xstrlcat(msg_buf, _(" (Interrupted)"), MSG_BUF_LEN); + } + if (msg(msg_buf)) { + set_keep_msg(msg_buf, 0); + keep_msg_more = true; + } + } +} + + void msg_ext_set_kind(const char *msg_kind) { // Don't change the label of an existing batch: @@ -1311,15 +1349,14 @@ void msg_ext_set_kind(const char *msg_kind) msg_ext_kind = msg_kind; } -/* - * Prepare for outputting characters in the command line. - */ +/// Prepare for outputting characters in the command line. void msg_start(void) { int did_return = false; if (!msg_silent) { XFREE_CLEAR(keep_msg); // don't display old message now + need_fileinfo = false; } if (need_clr_eos) { @@ -1360,9 +1397,7 @@ void msg_start(void) } } -/* - * Note that the current msg position is where messages start. - */ +/// Note that the current msg position is where messages start. void msg_starthere(void) { lines_left = cmdline_row; @@ -1416,12 +1451,11 @@ static void msg_home_replace_attr(char_u *fname, int attr) xfree(name); } -/* - * Output 'len' characters in 'str' (including NULs) with translation - * if 'len' is -1, output up to a NUL character. - * Use attributes 'attr'. - * Return the number of characters it takes on the screen. - */ +/// Output 'len' characters in 'str' (including NULs) with translation +/// if 'len' is -1, output up to a NUL character. +/// Use attributes 'attr'. +/// +/// @return the number of characters it takes on the screen. int msg_outtrans(char_u *str) { return msg_outtrans_attr(str, 0); @@ -1437,10 +1471,10 @@ int msg_outtrans_len(const char_u *str, int len) return msg_outtrans_len_attr(str, len, 0); } -/* - * Output one character at "p". Return pointer to the next character. - * Handles multi-byte characters. - */ +/// Output one character at "p". +/// Handles multi-byte characters. +/// +/// @return pointer to the next character. char_u *msg_outtrans_one(char_u *p, int attr) { int l; @@ -1461,6 +1495,10 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) char_u *s; int mb_l; int c; + int save_got_int = got_int; + + // Only quit when got_int was set in here. + got_int = false; // if MSG_HIST flag set, add message to history if (attr & MSG_HIST) { @@ -1478,7 +1516,7 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) * Go over the string. Special characters are translated and printed. * Normal characters are printed several at a time. */ - while (--len >= 0) { + while (--len >= 0 && !got_int) { // Don't include composing chars after the end. mb_l = utfc_ptr2len_len((char_u *)str, len + 1); if (mb_l > 1) { @@ -1517,11 +1555,13 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) } } - if (str > plain_start) { + if (str > plain_start && !got_int) { // Print the printable chars at the end. msg_puts_attr_len(plain_start, str - plain_start, attr); } + got_int |= save_got_int; + return retval; } @@ -1706,9 +1746,7 @@ void str2specialbuf(const char *sp, char *buf, size_t len) *buf = NUL; } -/* - * print line for :print or :list command - */ +/// print line for :print or :list command void msg_prt_line(char_u *s, int list) { int c; @@ -1857,8 +1895,9 @@ void msg_prt_line(char_u *s, int list) msg_clr_eos(); } -// Use grid_puts() to output one multi-byte character. -// Return the pointer "s" advanced to the next character. +/// Use grid_puts() to output one multi-byte character. +/// +/// @return the pointer "s" advanced to the next character. static char_u *screen_puts_mbyte(char_u *s, int l, int attr) { int cw; @@ -1890,10 +1929,8 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr) return s + l; } -/* - * Output a string to the screen at position msg_row, msg_col. - * Update msg_row and msg_col for the next message. - */ +/// Output a string to the screen at position msg_row, msg_col. +/// Update msg_row and msg_col for the next message. void msg_puts(const char *s) { msg_puts_attr(s, 0); @@ -1904,11 +1941,9 @@ void msg_puts_title(const char *s) msg_puts_attr(s, HL_ATTR(HLF_T)); } -/* - * Show a message in such a way that it always fits in the line. Cut out a - * part in the middle and replace it with "..." when necessary. - * Does not handle multi-byte characters! - */ +/// Show a message in such a way that it always fits in the line. Cut out a +/// part in the middle and replace it with "..." when necessary. +/// Does not handle multi-byte characters! void msg_outtrans_long_attr(char_u *longstr, int attr) { msg_outtrans_long_len_attr(longstr, (int)STRLEN(longstr), attr); @@ -1928,9 +1963,7 @@ void msg_outtrans_long_len_attr(char_u *longstr, int len, int attr) msg_outtrans_len_attr(longstr + len - slen, slen, attr); } -/* - * Basic function for writing a message with highlight attributes. - */ +/// Basic function for writing a message with highlight attributes. void msg_puts_attr(const char *const s, const int attr) { msg_puts_attr_len(s, -1, attr); @@ -1995,6 +2028,8 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) if (!msg_use_printf() || (headless_mode && default_grid.chars)) { msg_puts_display((const char_u *)str, len, attr, false); } + + need_fileinfo = false; } /// Print a formatted message @@ -2032,10 +2067,8 @@ static void msg_ext_emit_chunk(void) ADD(msg_ext_chunks, ARRAY_OBJ(chunk)); } -/* - * The display part of msg_puts_attr_len(). - * May be called recursively to display scroll-back text. - */ +/// The display part of msg_puts_attr_len(). +/// May be called recursively to display scroll-back text. static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurse) { const char_u *s = str; @@ -2056,7 +2089,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs msg_ext_last_attr = attr; } // Concat pieces with the same highlight - size_t len = strnlen((char *)str, maxlen); // -V781 + size_t len = STRNLEN(str, maxlen); // -V781 ga_concat_len(&msg_ext_last_chunk, (char *)str, len); msg_ext_cur_len += len; return; @@ -2240,8 +2273,8 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs msg_check(); } -/// Return true when ":filter pattern" was used and "msg" does not match -/// "pattern". +/// @return true when ":filter pattern" was used and "msg" does not match +/// "pattern". bool message_filtered(char_u *msg) { if (cmdmod.filter_regmatch.regprog == NULL) { @@ -2373,9 +2406,7 @@ void msg_reset_scroll(void) msg_scrolled_at_flush = 0; } -/* - * Increment "msg_scrolled". - */ +/// Increment "msg_scrolled". static void inc_msg_scrolled(void) { if (*get_vim_var_str(VV_SCROLLSTART) == NUL) { @@ -2454,9 +2485,7 @@ static void store_sb_text(char_u **sb_str, char_u *s, int attr, int *sb_col, int *sb_col = 0; } -/* - * Finished showing messages, clear the scroll-back text on the next message. - */ +/// Finished showing messages, clear the scroll-back text on the next message. void may_clear_sb_text(void) { do_clear_sb_text = SB_CLEAR_ALL; @@ -2499,9 +2528,7 @@ void clear_sb_text(int all) } } -/* - * "g<" command. - */ +/// "g<" command. void show_sb_text(void) { msgchunk_T *mp; @@ -2517,9 +2544,7 @@ void show_sb_text(void) } } -/* - * Move to the start of screen line in already displayed text. - */ +/// Move to the start of screen line in already displayed text. static msgchunk_T *msg_sb_start(msgchunk_T *mps) { msgchunk_T *mp = mps; @@ -2530,9 +2555,7 @@ static msgchunk_T *msg_sb_start(msgchunk_T *mps) return mp; } -/* - * Mark the last message chunk as finishing the line. - */ +/// Mark the last message chunk as finishing the line. void msg_sb_eol(void) { if (last_msgchunk != NULL) { @@ -2540,10 +2563,9 @@ void msg_sb_eol(void) } } -/* - * Display a screen line from previously displayed text at row "row". - * Returns a pointer to the text for the next line (can be NULL). - */ +/// Display a screen line from previously displayed text at row "row". +/// +/// @return a pointer to the text for the next line (can be NULL). static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) { msgchunk_T *mp = smp; @@ -2566,9 +2588,7 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) return mp->sb_next; } -/* - * Output any postponed text for msg_puts_attr_len(). - */ +/// Output any postponed text for msg_puts_attr_len(). static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) { attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); @@ -2589,9 +2609,9 @@ static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) } } -// Returns TRUE when messages should be printed to stdout/stderr: -// - "batch mode" ("silent mode", -es/-Es) -// - no UI and not embedded +/// @return TRUE when messages should be printed to stdout/stderr: +/// - "batch mode" ("silent mode", -es/-Es) +/// - no UI and not embedded int msg_use_printf(void) { return !embedded_mode && !ui_active(); @@ -2604,6 +2624,17 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) char buf[7]; char *p; + if (on_print.type != kCallbackNone) { + typval_T argv[1]; + argv[0].v_type = VAR_STRING; + argv[0].v_lock = VAR_UNLOCKED; + argv[0].vval.v_string = (char_u *)str; + typval_T rettv = TV_INITIAL_VALUE; + callback_call(&on_print, 1, argv, &rettv); + tv_clear(&rettv); + return; + } + while ((maxlen < 0 || s - str < maxlen) && *s != NUL) { int len = utf_ptr2len((const char_u *)s); if (!(silent_mode && p_verbose == 0)) { @@ -2641,13 +2672,12 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) msg_didout = true; // assume that line is not empty } -/* - * Show the more-prompt and handle the user response. - * This takes care of scrolling back and displaying previously displayed text. - * When at hit-enter prompt "typed_char" is the already typed character, - * otherwise it's NUL. - * Returns TRUE when jumping ahead to "confirm_msg_tail". - */ +/// Show the more-prompt and handle the user response. +/// This takes care of scrolling back and displaying previously displayed text. +/// When at hit-enter prompt "typed_char" is the already typed character, +/// otherwise it's NUL. +/// +/// @return TRUE when jumping ahead to "confirm_msg_tail". static int do_more_prompt(int typed_char) { static bool entered = false; @@ -2905,7 +2935,7 @@ void mch_errmsg(char *str) } } -// Give a message. To be used when the UI is not initialized yet. +/// Give a message. To be used when the UI is not initialized yet. void mch_msg(char *str) { assert(str != NULL); @@ -2920,10 +2950,8 @@ void mch_msg(char *str) } #endif // WIN32 -/* - * Put a character on the screen at the current message position and advance - * to the next position. Only for printable ASCII! - */ +/// Put a character on the screen at the current message position and advance +/// to the next position. Only for printable ASCII! static void msg_screen_putchar(int c, int attr) { attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); @@ -2956,10 +2984,8 @@ void msg_moremsg(int full) } } -/* - * Repeat the message for the current mode: ASKMORE, EXTERNCMD, CONFIRM or - * exmode_active. - */ +/// Repeat the message for the current mode: ASKMORE, EXTERNCMD, CONFIRM or +/// exmode_active. void repeat_message(void) { if (State == ASKMORE) { @@ -2984,10 +3010,8 @@ void repeat_message(void) } } -/* - * Clear from current message position to end of screen. - * Skip this when ":silent" was used, no need to clear for redirection. - */ +/// Clear from current message position to end of screen. +/// Skip this when ":silent" was used, no need to clear for redirection. void msg_clr_eos(void) { if (msg_silent == 0) { @@ -2995,11 +3019,9 @@ void msg_clr_eos(void) } } -/* - * Clear from current message position to end of screen. - * Note: msg_col is not updated, so we remember the end of the message - * for msg_check(). - */ +/// Clear from current message position to end of screen. +/// Note: msg_col is not updated, so we remember the end of the message +/// for msg_check(). void msg_clr_eos_force(void) { if (ui_has(kUIMessages)) { @@ -3026,9 +3048,7 @@ void msg_clr_eos_force(void) } } -/* - * Clear the command line. - */ +/// Clear the command line. void msg_clr_cmdline(void) { msg_row = cmdline_row; @@ -3036,11 +3056,10 @@ void msg_clr_cmdline(void) msg_clr_eos_force(); } -/* - * end putting a message on the screen - * call wait_return if the message does not fit in the available space - * return TRUE if wait_return not called. - */ +/// end putting a message on the screen +/// call wait_return if the message does not fit in the available space +/// +/// @return TRUE if wait_return not called. int msg_end(void) { /* @@ -3130,10 +3149,8 @@ bool msg_ext_is_visible(void) return ui_has(kUIMessages) && msg_ext_visible > 0; } -/* - * If the written message runs into the shown command or ruler, we have to - * wait for hit-return and redraw the window later. - */ +/// If the written message runs into the shown command or ruler, we have to +/// wait for hit-return and redraw the window later. void msg_check(void) { if (ui_has(kUIMessages)) { @@ -3145,10 +3162,9 @@ void msg_check(void) } } -/* - * May write a string to the redirection file. - * When "maxlen" is -1 write the whole string, otherwise up to "maxlen" bytes. - */ +/// May write a string to the redirection file. +/// +/// @param maxlen if -1, write the whole string, otherwise up to "maxlen" bytes. static void redir_write(const char *const str, const ptrdiff_t maxlen) { const char_u *s = (char_u *)str; @@ -3233,10 +3249,8 @@ int redirecting(void) || redir_reg || redir_vname || capture_ga != NULL; } -/* - * Before giving verbose message. - * Must always be called paired with verbose_leave()! - */ +/// Before giving verbose message. +/// Must always be called paired with verbose_leave()! void verbose_enter(void) { if (*p_vfile != NUL) { @@ -3244,10 +3258,8 @@ void verbose_enter(void) } } -/* - * After giving verbose message. - * Must always be called paired with verbose_enter()! - */ +/// After giving verbose message. +/// Must always be called paired with verbose_enter()! void verbose_leave(void) { if (*p_vfile != NUL) { @@ -3257,9 +3269,7 @@ void verbose_leave(void) } } -/* - * Like verbose_enter() and set msg_scroll when displaying the message. - */ +/// Like verbose_enter() and set msg_scroll when displaying the message. void verbose_enter_scroll(void) { if (*p_vfile != NUL) { @@ -3270,9 +3280,7 @@ void verbose_enter_scroll(void) } } -/* - * Like verbose_leave() and set cmdline_row when displaying the message. - */ +/// Like verbose_leave() and set cmdline_row when displaying the message. void verbose_leave_scroll(void) { if (*p_vfile != NUL) { @@ -3284,9 +3292,7 @@ void verbose_leave_scroll(void) } } -/* - * Called when 'verbosefile' is set: stop writing to the file. - */ +/// Called when 'verbosefile' is set: stop writing to the file. void verbose_stop(void) { if (verbose_fd != NULL) { @@ -3296,10 +3302,9 @@ void verbose_stop(void) verbose_did_open = FALSE; } -/* - * Open the file 'verbosefile'. - * Return FAIL or OK. - */ +/// Open the file 'verbosefile'. +/// +/// @return FAIL or OK. int verbose_open(void) { if (verbose_fd == NULL && !verbose_did_open) { @@ -3315,10 +3320,8 @@ int verbose_open(void) return OK; } -/* - * Give a warning message (for searching). - * Use 'w' highlighting and may repeat the message after redrawing - */ +/// Give a warning message (for searching). +/// Use 'w' highlighting and may repeat the message after redrawing void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1) { // Don't do this for ":silent". @@ -3357,9 +3360,7 @@ void give_warning2(char_u *const message, char_u *const a1, bool hl) give_warning(IObuff, hl); } -/* - * Advance msg cursor to column "col". - */ +/// Advance msg cursor to column "col". void msg_advance(int col) { if (msg_silent != 0) { // nothing to advance to @@ -3407,6 +3408,7 @@ void msg_advance(int col) /// /// @param textfiel IObuff for inputdialog(), NULL otherwise /// @param ex_cmd when TRUE pressing : accepts default and starts Ex command +/// @returns 0 if cancelled, otherwise the nth button (1-indexed). int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton, char_u *textfield, int ex_cmd) { @@ -3454,7 +3456,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl } if (c == ':' && ex_cmd) { retval = dfltbutton; - ins_char_typebuf(':'); + ins_char_typebuf(':', 0); break; } @@ -3568,15 +3570,13 @@ static char_u *console_dialog_alloc(const char_u *message, char_u *buttons, bool return xmalloc(lenhotkey); } -/* - * Format the dialog string, and display it at the bottom of - * the screen. Return a string of hotkey chars (if defined) for - * each 'button'. If a button has no hotkey defined, the first character of - * the button is used. - * The hotkeys can be multi-byte characters, but without combining chars. - * - * Returns an allocated string with hotkeys. - */ +/// Format the dialog string, and display it at the bottom of +/// the screen. Return a string of hotkey chars (if defined) for +/// each 'button'. If a button has no hotkey defined, the first character of +/// the button is used. +/// The hotkeys can be multi-byte characters, but without combining chars. +/// +/// @return an allocated string with hotkeys. static char_u *msg_show_console_dialog(char_u *message, char_u *buttons, int dfltbutton) FUNC_ATTR_NONNULL_RET { @@ -3669,9 +3669,7 @@ static void copy_hotkeys_and_msg(const char_u *message, char_u *buttons, int def *msgp = NUL; } -/* - * Display the ":confirm" message. Also called when screen resized. - */ +/// Display the ":confirm" message. Also called when screen resized. void display_confirm_msg(void) { // Avoid that 'q' at the more prompt truncates the message here. diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c deleted file mode 100644 index fd5d154cea..0000000000 --- a/src/nvim/misc1.c +++ /dev/null @@ -1,1061 +0,0 @@ -// 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 - -/* - * misc1.c: functions that didn't seem to fit elsewhere - */ - -#include <assert.h> -#include <inttypes.h> -#include <limits.h> -#include <stdbool.h> -#include <string.h> - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/buffer_updates.h" -#include "nvim/charset.h" -#include "nvim/cursor.h" -#include "nvim/diff.h" -#include "nvim/edit.h" -#include "nvim/eval.h" -#include "nvim/event/stream.h" -#include "nvim/ex_cmds.h" -#include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" -#include "nvim/fileio.h" -#include "nvim/fold.h" -#include "nvim/func_attr.h" -#include "nvim/garray.h" -#include "nvim/getchar.h" -#include "nvim/indent.h" -#include "nvim/indent_c.h" -#include "nvim/main.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/misc1.h" -#include "nvim/mouse.h" -#include "nvim/move.h" -#include "nvim/option.h" -#include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/os/shell.h" -#include "nvim/os/signal.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" -#include "nvim/quickfix.h" -#include "nvim/regexp.h" -#include "nvim/screen.h" -#include "nvim/search.h" -#include "nvim/state.h" -#include "nvim/strings.h" -#include "nvim/tag.h" -#include "nvim/ui.h" -#include "nvim/undo.h" -#include "nvim/vim.h" -#include "nvim/window.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "misc1.c.generated.h" -#endif -// All user names (for ~user completion as done by shell). -static garray_T ga_users = GA_EMPTY_INIT_VALUE; - -/* - * get_leader_len() returns the length in bytes of the prefix of the given - * string which introduces a comment. If this string is not a comment then - * 0 is returned. - * When "flags" is not NULL, it is set to point to the flags of the recognized - * comment leader. - * "backward" must be true for the "O" command. - * If "include_space" is set, include trailing whitespace while calculating the - * length. - */ -int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_space) -{ - int i, j; - int result; - int got_com = FALSE; - int found_one; - char_u part_buf[COM_MAX_LEN]; // buffer for one option part - char_u *string; // pointer to comment string - char_u *list; - int middle_match_len = 0; - char_u *prev_list; - char_u *saved_flags = NULL; - - result = i = 0; - while (ascii_iswhite(line[i])) { // leading white space is ignored - ++i; - } - - /* - * Repeat to match several nested comment strings. - */ - while (line[i] != NUL) { - /* - * scan through the 'comments' option for a match - */ - found_one = FALSE; - for (list = curbuf->b_p_com; *list;) { - // Get one option part into part_buf[]. Advance "list" to next - // one. Put "string" at start of string. - if (!got_com && flags != NULL) { - *flags = list; // remember where flags started - } - prev_list = list; - (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); - string = vim_strchr(part_buf, ':'); - if (string == NULL) { // missing ':', ignore this part - continue; - } - *string++ = NUL; // isolate flags from string - - // If we found a middle match previously, use that match when this - // is not a middle or end. - if (middle_match_len != 0 - && vim_strchr(part_buf, COM_MIDDLE) == NULL - && vim_strchr(part_buf, COM_END) == NULL) { - break; - } - - // When we already found a nested comment, only accept further - // nested comments. - if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) { - continue; - } - - // When 'O' flag present and using "O" command skip this one. - if (backward && vim_strchr(part_buf, COM_NOBACK) != NULL) { - continue; - } - - // Line contents and string must match. - // When string starts with white space, must have some white space - // (but the amount does not need to match, there might be a mix of - // TABs and spaces). - if (ascii_iswhite(string[0])) { - if (i == 0 || !ascii_iswhite(line[i - 1])) { - continue; // missing white space - } - while (ascii_iswhite(string[0])) { - ++string; - } - } - for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j) { - } - if (string[j] != NUL) { - continue; // string doesn't match - } - // When 'b' flag used, there must be white space or an - // end-of-line after the string in the line. - if (vim_strchr(part_buf, COM_BLANK) != NULL - && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { - continue; - } - - // We have found a match, stop searching unless this is a middle - // comment. The middle comment can be a substring of the end - // comment in which case it's better to return the length of the - // end comment and its flags. Thus we keep searching with middle - // and end matches and use an end match if it matches better. - if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { - if (middle_match_len == 0) { - middle_match_len = j; - saved_flags = prev_list; - } - continue; - } - if (middle_match_len != 0 && j > middle_match_len) { - // Use this match instead of the middle match, since it's a - // longer thus better match. - middle_match_len = 0; - } - - if (middle_match_len == 0) { - i += j; - } - found_one = TRUE; - break; - } - - if (middle_match_len != 0) { - // Use the previously found middle match after failing to find a - // match with an end. - if (!got_com && flags != NULL) { - *flags = saved_flags; - } - i += middle_match_len; - found_one = TRUE; - } - - // No match found, stop scanning. - if (!found_one) { - break; - } - - result = i; - - // Include any trailing white space. - while (ascii_iswhite(line[i])) { - ++i; - } - - if (include_space) { - result = i; - } - - // If this comment doesn't nest, stop here. - got_com = TRUE; - if (vim_strchr(part_buf, COM_NEST) == NULL) { - break; - } - } - return result; -} - -/* - * Return the offset at which the last comment in line starts. If there is no - * comment in the whole line, -1 is returned. - * - * When "flags" is not null, it is set to point to the flags describing the - * recognized comment leader. - */ -int get_last_leader_offset(char_u *line, char_u **flags) -{ - int result = -1; - int i, j; - int lower_check_bound = 0; - char_u *string; - char_u *com_leader; - char_u *com_flags; - char_u *list; - int found_one; - char_u part_buf[COM_MAX_LEN]; // buffer for one option part - - /* - * Repeat to match several nested comment strings. - */ - i = (int)STRLEN(line); - while (--i >= lower_check_bound) { - /* - * scan through the 'comments' option for a match - */ - found_one = FALSE; - for (list = curbuf->b_p_com; *list;) { - char_u *flags_save = list; - - /* - * Get one option part into part_buf[]. Advance list to next one. - * put string at start of string. - */ - (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); - string = vim_strchr(part_buf, ':'); - if (string == NULL) { // If everything is fine, this cannot actually - // happen. - continue; - } - *string++ = NUL; // Isolate flags from string. - com_leader = string; - - /* - * Line contents and string must match. - * When string starts with white space, must have some white space - * (but the amount does not need to match, there might be a mix of - * TABs and spaces). - */ - if (ascii_iswhite(string[0])) { - if (i == 0 || !ascii_iswhite(line[i - 1])) { - continue; - } - while (ascii_iswhite(*string)) { - string++; - } - } - for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j) { - // do nothing - } - if (string[j] != NUL) { - continue; - } - - /* - * When 'b' flag used, there must be white space or an - * end-of-line after the string in the line. - */ - if (vim_strchr(part_buf, COM_BLANK) != NULL - && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { - continue; - } - - if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { - // For a middlepart comment, only consider it to match if - // everything before the current position in the line is - // whitespace. Otherwise we would think we are inside a - // comment if the middle part appears somewhere in the middle - // of the line. E.g. for C the "*" appears often. - for (j = 0; j <= i && ascii_iswhite(line[j]); j++) { - } - if (j < i) { - continue; - } - } - - /* - * We have found a match, stop searching. - */ - found_one = TRUE; - - if (flags) { - *flags = flags_save; - } - com_flags = flags_save; - - break; - } - - if (found_one) { - char_u part_buf2[COM_MAX_LEN]; // buffer for one option part - int len1, len2, off; - - result = i; - /* - * If this comment nests, continue searching. - */ - if (vim_strchr(part_buf, COM_NEST) != NULL) { - continue; - } - - lower_check_bound = i; - - // Let's verify whether the comment leader found is a substring - // of other comment leaders. If it is, let's adjust the - // lower_check_bound so that we make sure that we have determined - // the comment leader correctly. - - while (ascii_iswhite(*com_leader)) { - ++com_leader; - } - len1 = (int)STRLEN(com_leader); - - for (list = curbuf->b_p_com; *list;) { - char_u *flags_save = list; - - (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); - if (flags_save == com_flags) { - continue; - } - string = vim_strchr(part_buf2, ':'); - ++string; - while (ascii_iswhite(*string)) { - ++string; - } - len2 = (int)STRLEN(string); - if (len2 == 0) { - continue; - } - - // Now we have to verify whether string ends with a substring - // beginning the com_leader. - for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) { - --off; - if (!STRNCMP(string + off, com_leader, len2 - off)) { - if (i - off < lower_check_bound) { - lower_check_bound = i - off; - } - } - } - } - } - } - return result; -} - -int gchar_pos(pos_T *pos) - FUNC_ATTR_NONNULL_ARG(1) -{ - // When searching columns is sometimes put at the end of a line. - if (pos->col == MAXCOL) { - return NUL; - } - return utf_ptr2char(ml_get_pos(pos)); -} - -/* - * check_status: called when the status bars for the buffer 'buf' - * need to be updated - */ -void check_status(buf_T *buf) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf && wp->w_status_height) { - wp->w_redr_status = TRUE; - if (must_redraw < VALID) { - must_redraw = VALID; - } - } - } -} - -/// Ask for a reply from the user, 'y' or 'n' -/// -/// No other characters are accepted, the message is repeated until a valid -/// reply is entered or <C-c> is hit. -/// -/// @param[in] str Prompt: question to ask user. Is always followed by -/// " (y/n)?". -/// @param[in] direct Determines what function to use to get user input. If -/// true then ui_inchar() will be used, otherwise vgetc(). -/// I.e. when direct is true then characters are obtained -/// directly from the user without buffers involved. -/// -/// @return 'y' or 'n'. Last is also what will be returned in case of interrupt. -int ask_yesno(const char *const str, const bool direct) -{ - const int save_State = State; - - no_wait_return++; - State = CONFIRM; // Mouse behaves like with :confirm. - setmouse(); // Disable mouse in xterm. - no_mapping++; - - int r = ' '; - while (r != 'y' && r != 'n') { - // Same highlighting as for wait_return. - smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); - if (direct) { - r = get_keystroke(NULL); - } else { - r = plain_vgetc(); - } - if (r == Ctrl_C || r == ESC) { - r = 'n'; - } - msg_putchar(r); // Show what you typed. - ui_flush(); - } - no_wait_return--; - State = save_State; - setmouse(); - no_mapping--; - - return r; -} - -/* - * Return TRUE if "c" is a mouse key. - */ -int is_mouse_key(int c) -{ - return c == K_LEFTMOUSE - || c == K_LEFTMOUSE_NM - || c == K_LEFTDRAG - || c == K_LEFTRELEASE - || c == K_LEFTRELEASE_NM - || c == K_MOUSEMOVE - || c == K_MIDDLEMOUSE - || c == K_MIDDLEDRAG - || c == K_MIDDLERELEASE - || c == K_RIGHTMOUSE - || c == K_RIGHTDRAG - || c == K_RIGHTRELEASE - || c == K_MOUSEDOWN - || c == K_MOUSEUP - || c == K_MOUSELEFT - || c == K_MOUSERIGHT - || c == K_X1MOUSE - || c == K_X1DRAG - || c == K_X1RELEASE - || c == K_X2MOUSE - || c == K_X2DRAG - || c == K_X2RELEASE; -} - -/* - * Get a key stroke directly from the user. - * Ignores mouse clicks and scrollbar events, except a click for the left - * button (used at the more prompt). - * Doesn't use vgetc(), because it syncs undo and eats mapped characters. - * Disadvantage: typeahead is ignored. - * Translates the interrupt character for unix to ESC. - */ -int get_keystroke(MultiQueue *events) -{ - char_u *buf = NULL; - int buflen = 150; - int maxlen; - int len = 0; - int n; - int save_mapped_ctrl_c = mapped_ctrl_c; - int waited = 0; - - mapped_ctrl_c = 0; // mappings are not used here - for (;;) { - // flush output before waiting - ui_flush(); - // Leave some room for check_termcode() to insert a key code into (max - // 5 chars plus NUL). And fix_input_buffer() can triple the number of - // bytes. - maxlen = (buflen - 6 - len) / 3; - if (buf == NULL) { - buf = xmalloc((size_t)buflen); - } else if (maxlen < 10) { - // Need some more space. This might happen when receiving a long - // escape sequence. - buflen += 100; - buf = xrealloc(buf, (size_t)buflen); - maxlen = (buflen - 6 - len) / 3; - } - - // First time: blocking wait. Second time: wait up to 100ms for a - // terminal code to complete. - n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events); - if (n > 0) { - // Replace zero and CSI by a special key code. - n = fix_input_buffer(buf + len, n); - len += n; - waited = 0; - } else if (len > 0) { - ++waited; // keep track of the waiting time - } - if (n > 0) { // found a termcode: adjust length - len = n; - } - if (len == 0) { // nothing typed yet - continue; - } - - // Handle modifier and/or special key code. - n = buf[0]; - if (n == K_SPECIAL) { - n = TO_SPECIAL(buf[1], buf[2]); - if (buf[1] == KS_MODIFIER - || n == K_IGNORE - || (is_mouse_key(n) && n != K_LEFTMOUSE)) { - if (buf[1] == KS_MODIFIER) { - mod_mask = buf[2]; - } - len -= 3; - if (len > 0) { - memmove(buf, buf + 3, (size_t)len); - } - continue; - } - break; - } - if (MB_BYTE2LEN(n) > len) { - // more bytes to get. - continue; - } - buf[len >= buflen ? buflen - 1 : len] = NUL; - n = utf_ptr2char(buf); - break; - } - xfree(buf); - - mapped_ctrl_c = save_mapped_ctrl_c; - return n; -} - -/// Get a number from the user. -/// When "mouse_used" is not NULL allow using the mouse. -/// -/// @param colon allow colon to abort -int get_number(int colon, int *mouse_used) -{ - int n = 0; - int c; - int typed = 0; - - if (mouse_used != NULL) { - *mouse_used = FALSE; - } - - // When not printing messages, the user won't know what to type, return a - // zero (as if CR was hit). - if (msg_silent != 0) { - return 0; - } - - no_mapping++; - for (;;) { - ui_cursor_goto(msg_row, msg_col); - c = safe_vgetc(); - if (ascii_isdigit(c)) { - n = n * 10 + c - '0'; - msg_putchar(c); - ++typed; - } else if (c == K_DEL || c == K_KDEL || c == K_BS || c == Ctrl_H) { - if (typed > 0) { - msg_puts("\b \b"); - --typed; - } - n /= 10; - } else if (mouse_used != NULL && c == K_LEFTMOUSE) { - *mouse_used = TRUE; - n = mouse_row + 1; - break; - } else if (n == 0 && c == ':' && colon) { - stuffcharReadbuff(':'); - if (!exmode_active) { - cmdline_row = msg_row; - } - skip_redraw = true; // skip redraw once - do_redraw = false; - break; - } else if (c == Ctrl_C || c == ESC || c == 'q') { - n = 0; - break; - } else if (c == CAR || c == NL) { - break; - } - } - no_mapping--; - return n; -} - -/* - * Ask the user to enter a number. - * When "mouse_used" is not NULL allow using the mouse and in that case return - * the line number. - */ -int prompt_for_number(int *mouse_used) -{ - int i; - int save_cmdline_row; - int save_State; - - // When using ":silent" assume that <CR> was entered. - if (mouse_used != NULL) { - msg_puts(_("Type number and <Enter> or click with the mouse " - "(q or empty cancels): ")); - } else { - msg_puts(_("Type number and <Enter> (q or empty cancels): ")); - } - - /* Set the state such that text can be selected/copied/pasted and we still - * get mouse events. */ - save_cmdline_row = cmdline_row; - cmdline_row = 0; - save_State = State; - State = ASKMORE; // prevents a screen update when using a timer - // May show different mouse shape. - setmouse(); - - i = get_number(TRUE, mouse_used); - if (KeyTyped) { - // don't call wait_return() now - if (msg_row > 0) { - cmdline_row = msg_row - 1; - } - need_wait_return = false; - msg_didany = false; - msg_didout = false; - } else { - cmdline_row = save_cmdline_row; - } - State = save_State; - // May need to restore mouse shape. - setmouse(); - - return i; -} - -void msgmore(long n) -{ - long pn; - - if (global_busy // no messages now, wait until global is finished - || !messaging()) { // 'lazyredraw' set, don't do messages now - return; - } - - // We don't want to overwrite another important message, but do overwrite - // a previous "more lines" or "fewer lines" message, so that "5dd" and - // then "put" reports the last action. - if (keep_msg != NULL && !keep_msg_more) { - return; - } - - if (n > 0) { - pn = n; - } else { - pn = -n; - } - - if (pn > p_report) { - if (n > 0) { - vim_snprintf(msg_buf, MSG_BUF_LEN, - NGETTEXT("%ld more line", "%ld more lines", pn), - pn); - } else { - vim_snprintf(msg_buf, MSG_BUF_LEN, - NGETTEXT("%ld line less", "%ld fewer lines", pn), - pn); - } - if (got_int) { - xstrlcat(msg_buf, _(" (Interrupted)"), MSG_BUF_LEN); - } - if (msg(msg_buf)) { - set_keep_msg(msg_buf, 0); - keep_msg_more = true; - } - } -} - -/* - * flush map and typeahead buffers and give a warning for an error - */ -void beep_flush(void) -{ - if (emsg_silent == 0) { - flush_buffers(FLUSH_MINIMAL); - vim_beep(BO_ERROR); - } -} - -// Give a warning for an error -// val is one of the BO_ values, e.g., BO_OPER -void vim_beep(unsigned val) -{ - called_vim_beep = true; - - if (emsg_silent == 0) { - if (!((bo_flags & val) || (bo_flags & BO_ALL))) { - static int beeps = 0; - static uint64_t start_time = 0; - - // Only beep up to three times per half a second, - // otherwise a sequence of beeps would freeze Vim. - if (start_time == 0 || os_hrtime() - start_time > 500000000u) { - beeps = 0; - start_time = os_hrtime(); - } - beeps++; - if (beeps <= 3) { - if (p_vb) { - ui_call_visual_bell(); - } else { - ui_call_bell(); - } - } - } - - // When 'debug' contains "beep" produce a message. If we are sourcing - // a script or executing a function give the user a hint where the beep - // comes from. - if (vim_strchr(p_debug, 'e') != NULL) { - msg_source(HL_ATTR(HLF_W)); - msg_attr(_("Beep!"), HL_ATTR(HLF_W)); - } - } -} - -#if defined(EXITFREE) - -void free_users(void) -{ - ga_clear_strings(&ga_users); -} - -#endif - -/* - * Find all user names for user completion. - * Done only once and then cached. - */ -static void init_users(void) -{ - static int lazy_init_done = FALSE; - - if (lazy_init_done) { - return; - } - - lazy_init_done = TRUE; - - os_get_usernames(&ga_users); -} - -/* - * Function given to ExpandGeneric() to obtain an user names. - */ -char_u *get_users(expand_T *xp, int idx) -{ - init_users(); - if (idx < ga_users.ga_len) { - return ((char_u **)ga_users.ga_data)[idx]; - } - return NULL; -} - -/* - * Check whether name matches a user name. Return: - * 0 if name does not match any user name. - * 1 if name partially matches the beginning of a user name. - * 2 is name fully matches a user name. - */ -int match_user(char_u *name) -{ - int n = (int)STRLEN(name); - int result = 0; - - init_users(); - for (int i = 0; i < ga_users.ga_len; i++) { - if (STRCMP(((char_u **)ga_users.ga_data)[i], name) == 0) { - return 2; // full match - } - if (STRNCMP(((char_u **)ga_users.ga_data)[i], name, n) == 0) { - result = 1; // partial match - } - } - return result; -} - -/// Preserve files and exit. -/// @note IObuff must contain a message. -/// @note This may be called from deadly_signal() in a signal handler, avoid -/// unsafe functions, such as allocating memory. -void preserve_exit(void) - FUNC_ATTR_NORETURN -{ - // 'true' when we are sure to exit, e.g., after a deadly signal - static bool really_exiting = false; - - // Prevent repeated calls into this method. - if (really_exiting) { - if (input_global_fd() >= 0) { - // normalize stream (#2598) - stream_set_blocking(input_global_fd(), true); - } - exit(2); - } - - really_exiting = true; - // Ignore SIGHUP while we are already exiting. #9274 - signal_reject_deadly(); - mch_errmsg(IObuff); - mch_errmsg("\n"); - ui_flush(); - - ml_close_notmod(); // close all not-modified buffers - - FOR_ALL_BUFFERS(buf) { - if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { - mch_errmsg("Vim: preserving files...\r\n"); - ui_flush(); - ml_sync_all(false, false, true); // preserve all swap files - break; - } - } - - ml_close_all(false); // close all memfiles, without deleting - - mch_errmsg("Vim: Finished.\r\n"); - - getout(1); -} - -/* - * Check for CTRL-C pressed, but only once in a while. - * Should be used instead of os_breakcheck() for functions that check for - * each line in the file. Calling os_breakcheck() each time takes too much - * time, because it can be a system call. - */ - -#ifndef BREAKCHECK_SKIP -# define BREAKCHECK_SKIP 1000 -#endif - -static int breakcheck_count = 0; - -void line_breakcheck(void) -{ - if (++breakcheck_count >= BREAKCHECK_SKIP) { - breakcheck_count = 0; - os_breakcheck(); - } -} - -/* - * Like line_breakcheck() but check 10 times less often. - */ -void fast_breakcheck(void) -{ - if (++breakcheck_count >= BREAKCHECK_SKIP * 10) { - breakcheck_count = 0; - os_breakcheck(); - } -} - -// Like line_breakcheck() but check 100 times less often. -void veryfast_breakcheck(void) -{ - if (++breakcheck_count >= BREAKCHECK_SKIP * 100) { - breakcheck_count = 0; - os_breakcheck(); - } -} - -/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. -/// Invalidates cached tags. -/// -/// @return shell command exit code -int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) -{ - int retval; - proftime_T wait_time; - - if (p_verbose > 3) { - verbose_enter(); - smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd); - msg_putchar('\n'); - verbose_leave(); - } - - if (do_profiling == PROF_YES) { - prof_child_enter(&wait_time); - } - - if (*p_sh == NUL) { - emsg(_(e_shellempty)); - retval = -1; - } else { - // The external command may update a tags file, clear cached tags. - tag_freematch(); - - retval = os_call_shell(cmd, opts, extra_shell_arg); - } - - set_vim_var_nr(VV_SHELL_ERROR, (varnumber_T)retval); - if (do_profiling == PROF_YES) { - prof_child_exit(&wait_time); - } - - return retval; -} - -/// Get the stdout of an external command. -/// If "ret_len" is NULL replace NUL characters with NL. When "ret_len" is not -/// NULL store the length there. -/// -/// @param cmd command to execute -/// @param infile optional input file name -/// @param flags can be kShellOptSilent or 0 -/// @param ret_len length of the stdout -/// -/// @return an allocated string, or NULL for error. -char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret_len) -{ - char_u *buffer = NULL; - - if (check_secure()) { - return NULL; - } - - // get a name for the temp file - char_u *tempname = vim_tempname(); - if (tempname == NULL) { - emsg(_(e_notmp)); - return NULL; - } - - // Add the redirection stuff - char_u *command = make_filter_cmd(cmd, infile, tempname); - - /* - * Call the shell to execute the command (errors are ignored). - * Don't check timestamps here. - */ - ++no_check_timestamps; - call_shell(command, kShellOptDoOut | kShellOptExpand | flags, NULL); - --no_check_timestamps; - - xfree(command); - - // read the names from the file into memory - FILE *fd = os_fopen((char *)tempname, READBIN); - - if (fd == NULL) { - semsg(_(e_notopen), tempname); - goto done; - } - - fseek(fd, 0L, SEEK_END); - size_t len = (size_t)ftell(fd); // get size of temp file - fseek(fd, 0L, SEEK_SET); - - buffer = xmalloc(len + 1); - size_t i = fread((char *)buffer, 1, len, fd); - fclose(fd); - os_remove((char *)tempname); - if (i != len) { - semsg(_(e_notread), tempname); - XFREE_CLEAR(buffer); - } else if (ret_len == NULL) { - // Change NUL into SOH, otherwise the string is truncated. - for (i = 0; i < len; ++i) { - if (buffer[i] == NUL) { - buffer[i] = 1; - } - } - - buffer[len] = NUL; // make sure the buffer is terminated - } else { - *ret_len = len; - } - -done: - xfree(tempname); - return buffer; -} - -/* - * Free the list of files returned by expand_wildcards() or other expansion - * functions. - */ -void FreeWild(int count, char_u **files) -{ - if (count <= 0 || files == NULL) { - return; - } - while (count--) { - xfree(files[count]); - } - xfree(files); -} - -/* - * Return TRUE when need to go to Insert mode because of 'insertmode'. - * Don't do this when still processing a command or a mapping. - * Don't do this when inside a ":normal" command. - */ -int goto_im(void) -{ - return p_im && stuff_empty() && typebuf_typed(); -} - -/// Put the timestamp of an undo header in "buf[buflen]" in a nice format. -void add_time(char_u *buf, size_t buflen, time_t tt) -{ - struct tm curtime; - - if (time(NULL) - tt >= 100) { - os_localtime_r(&tt, &curtime); - if (time(NULL) - tt < (60L * 60L * 12L)) { - // within 12 hours - (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); - } else { - // longer ago - (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); - } - } else { - int64_t seconds = time(NULL) - tt; - vim_snprintf((char *)buf, buflen, - NGETTEXT("%" PRId64 " second ago", - "%" PRId64 " seconds ago", (uint32_t)seconds), - seconds); - } -} diff --git a/src/nvim/misc1.h b/src/nvim/misc1.h deleted file mode 100644 index 4ce142c4c5..0000000000 --- a/src/nvim/misc1.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef NVIM_MISC1_H -#define NVIM_MISC1_H - -#include "nvim/os/shell.h" -#include "nvim/vim.h" - -// flags for open_line() -#define OPENLINE_DELSPACES 1 // delete spaces after cursor -#define OPENLINE_DO_COM 2 // format comments -#define OPENLINE_KEEPTRAIL 4 // keep trailing spaces -#define OPENLINE_MARKFIX 8 // fix mark positions -#define OPENLINE_COM_LIST 16 // format comments with list/2nd line indent - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "misc1.h.generated.h" -#endif -#endif // NVIM_MISC1_H diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 386094e509..6b27ce9d7b 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -10,7 +10,6 @@ #include "nvim/diff.h" #include "nvim/fold.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/os_unix.h" @@ -31,6 +30,32 @@ static linenr_T orig_topline = 0; static int orig_topfill = 0; +/// Return true if "c" is a mouse key. +bool is_mouse_key(int c) +{ + return c == K_LEFTMOUSE + || c == K_LEFTMOUSE_NM + || c == K_LEFTDRAG + || c == K_LEFTRELEASE + || c == K_LEFTRELEASE_NM + || c == K_MOUSEMOVE + || c == K_MIDDLEMOUSE + || c == K_MIDDLEDRAG + || c == K_MIDDLERELEASE + || c == K_RIGHTMOUSE + || c == K_RIGHTDRAG + || c == K_RIGHTRELEASE + || c == K_MOUSEDOWN + || c == K_MOUSEUP + || c == K_MOUSELEFT + || c == K_MOUSERIGHT + || c == K_X1MOUSE + || c == K_X1DRAG + || c == K_X1RELEASE + || c == K_X2MOUSE + || c == K_X2DRAG + || c == K_X2RELEASE; +} /// Move the cursor to the specified row and column on the screen. /// Change current window if necessary. Returns an integer with the /// CURSOR_MOVED bit set if the cursor has moved or unset otherwise. @@ -510,6 +535,22 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) return NULL; } +/// Convert a virtual (screen) column to a character column. +/// The first column is one. +colnr_T vcol2col(win_T *const wp, const linenr_T lnum, const colnr_T vcol) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + // try to advance to the specified column + char_u *ptr = ml_get_buf(wp->w_buffer, lnum, false); + char_u *const line = ptr; + colnr_T count = 0; + while (count < vcol && *ptr != NUL) { + count += win_lbr_chartabsize(wp, line, ptr, count, NULL); + MB_PTR_ADV(ptr); + } + return (colnr_T)(ptr - line); +} + /// Set UI mouse depending on current mode and 'mouse'. /// /// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty). diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index bf4f9c57e5..04acb88bb3 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -38,8 +38,8 @@ // Direction for nv_mousescroll() and ins_mousescroll() #define MSCR_DOWN 0 // DOWN must be FALSE #define MSCR_UP 1 -#define MSCR_LEFT -1 -#define MSCR_RIGHT -2 +#define MSCR_LEFT (-1) +#define MSCR_RIGHT (-2) #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/move.c b/src/nvim/move.c index d80e63e79d..eda3298101 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -23,9 +23,9 @@ #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/fold.h" +#include "nvim/getchar.h" #include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" @@ -95,33 +95,38 @@ static void comp_botline(win_T *wp) win_check_anchored_floats(wp); } -void reset_cursorline(void) +/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. +/// Also when concealing is on and 'concealcursor' is not active. +void redraw_for_cursorline(win_T *wp) + FUNC_ATTR_NONNULL_ALL { - curwin->w_last_cursorline = 0; + if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible() + && (wp->w_p_rnu || win_cursorline_standout(wp))) { + // win_line() will redraw the number column and cursorline only. + redraw_later(wp, VALID); + } } -// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. -void redraw_for_cursorline(win_T *wp) +/// Redraw when w_virtcol changes and 'cursorcolumn' is set or 'cursorlineopt' +/// contains "screenline". +/// Also when concealing is on and 'concealcursor' is active. +static void redraw_for_cursorcolumn(win_T *wp) FUNC_ATTR_NONNULL_ALL { - if ((wp->w_p_rnu || win_cursorline_standout(wp)) - && (wp->w_valid & VALID_CROW) == 0 - && !pum_visible()) { - if (wp->w_p_rnu) { - // win_line() will redraw the number column only. + if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) { + if (wp->w_p_cuc) { + // When 'cursorcolumn' is set need to redraw with SOME_VALID. + redraw_later(wp, SOME_VALID); + } else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) { + // When 'cursorlineopt' contains "screenline" need to redraw with VALID. redraw_later(wp, VALID); } - if (win_cursorline_standout(wp)) { - if (wp->w_redr_type <= VALID && wp->w_last_cursorline != 0) { - // "w_last_cursorline" may be outdated, worst case we redraw - // too much. This is optimized for moving the cursor around in - // the current window. - redrawWinline(wp, wp->w_last_cursorline); - redrawWinline(wp, wp->w_cursor.lnum); - } else { - redraw_later(wp, SOME_VALID); - } - } + } + // If the cursor moves horizontally when 'concealcursor' is active, then the + // current line needs to be redrawn in order to calculate the correct + // cursor position. + if ((wp->w_valid & VALID_VIRTCOL) == 0 && wp->w_p_cole > 0 && conceal_cursor_line(wp)) { + redrawWinline(wp, wp->w_cursor.lnum); } } @@ -346,10 +351,10 @@ void update_topline(win_T *wp) */ void update_topline_win(win_T *win) { - win_T *save_curwin; - switch_win(&save_curwin, NULL, win, NULL, true); + switchwin_T switchwin; + switch_win(&switchwin, win, NULL, true); update_topline(curwin); - restore_win(save_curwin, NULL, true); + restore_win(&switchwin, true); } /* @@ -641,11 +646,8 @@ void validate_virtcol_win(win_T *wp) check_cursor_moved(wp); if (!(wp->w_valid & VALID_VIRTCOL)) { getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL); + redraw_for_cursorcolumn(wp); wp->w_valid |= VALID_VIRTCOL; - if (wp->w_p_cuc - && !pum_visible()) { - redraw_later(wp, SOME_VALID); - } } } @@ -776,8 +778,13 @@ void curs_columns(win_T *wp, int may_scroll) int textwidth = wp->w_width_inner - extra; if (textwidth <= 0) { // No room for text, put cursor in last char of window. + // If not wrapping, the last non-empty line. wp->w_wcol = wp->w_width_inner - 1; - wp->w_wrow = wp->w_height_inner - 1; + if (wp->w_p_wrap) { + wp->w_wrow = wp->w_height_inner - 1; + } else { + wp->w_wrow = wp->w_height_inner - 1 - wp->w_empty_rows; + } } else if (wp->w_p_wrap && wp->w_width_inner != 0) { width = textwidth + win_col_off2(wp); @@ -909,7 +916,7 @@ void curs_columns(win_T *wp, int may_scroll) } wp->w_skipcol = n * width; } else if (extra == 1) { - // less then 'scrolloff' lines above, decrease skipcol + // less than 'scrolloff' lines above, decrease skipcol assert(so <= INT_MAX); extra = (wp->w_skipcol + (int)so * width - wp->w_virtcol + width - 1) / width; @@ -920,7 +927,7 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_skipcol -= extra * width; } } else if (extra == 2) { - // less then 'scrolloff' lines below, increase skipcol + // less than 'scrolloff' lines below, increase skipcol endcol = (n - wp->w_height_inner + 1) * width; while (endcol > wp->w_virtcol) { endcol -= width; @@ -948,11 +955,7 @@ void curs_columns(win_T *wp, int may_scroll) redraw_later(wp, NOT_VALID); } - // Redraw when w_virtcol changes and 'cursorcolumn' is set - if (wp->w_p_cuc && (wp->w_valid & VALID_VIRTCOL) == 0 - && !pum_visible()) { - redraw_later(wp, SOME_VALID); - } + redraw_for_cursorcolumn(curwin); // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise wp->w_valid_leftcol = wp->w_leftcol; @@ -1011,7 +1014,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, col -= wp->w_leftcol; if (col >= 0 && col < wp->w_width) { - coloff = col - scol + (local ? 0 : wp->w_wincol) + 1; + coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_border_adj[3]) + 1; } else { scol = ccol = ecol = 0; // character is left or right of the window @@ -1022,7 +1025,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, } } } - *rowp = (local ? 0 : wp->w_winrow) + row + rowoff; + *rowp = (local ? 0 : wp->w_winrow + wp->w_border_adj[0]) + row + rowoff; *scolp = scol + coloff; *ccolp = ccol + coloff; *ecolp = ecol + coloff; diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index a1a1f0f8c0..48ecd5d0ea 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -24,7 +24,6 @@ #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/os/input.h" @@ -548,7 +547,8 @@ void rpc_close(Channel *channel) channel->rpc.closed = true; channel_decref(channel); - if (channel->streamtype == kChannelStreamStdio) { + if (channel->streamtype == kChannelStreamStdio + || channel->id == ui_client_channel_id) { multiqueue_put(main_loop.fast_events, exit_event, 0); } } diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index f805858904..32014fcf2b 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -92,15 +92,15 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) break; } #define STR_CASE(type, attr, obj, dest, conv) \ -case type: { \ - dest = conv(((String) { \ + case type: { \ + dest = conv(((String) { \ .size = obj->via.attr.size, \ .data = (obj->via.attr.ptr == NULL || obj->via.attr.size == 0 \ ? xmemdupz("", 0) \ : xmemdupz(obj->via.attr.ptr, obj->via.attr.size)), \ })); \ - break; \ -} + break; \ + } STR_CASE(MSGPACK_OBJECT_STR, str, cur.mobj, *cur.aobj, STRING_OBJ) STR_CASE(MSGPACK_OBJECT_BIN, bin, cur.mobj, *cur.aobj, STRING_OBJ) case MSGPACK_OBJECT_ARRAY: { @@ -143,10 +143,10 @@ case type: { \ const msgpack_object *const key = &cur.mobj->via.map.ptr[idx].key; switch (key->type) { #define ID(x) x - STR_CASE(MSGPACK_OBJECT_STR, str, key, - cur.aobj->data.dictionary.items[idx].key, ID) - STR_CASE(MSGPACK_OBJECT_BIN, bin, key, - cur.aobj->data.dictionary.items[idx].key, ID) + STR_CASE(MSGPACK_OBJECT_STR, str, key, + cur.aobj->data.dictionary.items[idx].key, ID) + STR_CASE(MSGPACK_OBJECT_BIN, bin, key, + cur.aobj->data.dictionary.items[idx].key, ID) #undef ID case MSGPACK_OBJECT_NIL: case MSGPACK_OBJECT_BOOLEAN: diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 03312e5df4..a8769c4c80 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -31,8 +31,8 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/indent.h" -#include "nvim/indent_c.h" #include "nvim/keymap.h" #include "nvim/log.h" #include "nvim/main.h" @@ -40,7 +40,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -84,12 +83,6 @@ typedef struct normal_state { pos_T old_pos; } NormalState; -/* - * The Visual area is remembered for reselection. - */ -static int resel_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V -static linenr_T resel_VIsual_line_count; // number of lines -static colnr_T resel_VIsual_vcol; // nr of cols or end col static int VIsual_mode_orig = NUL; // saved Visual mode @@ -171,7 +164,7 @@ static const struct nv_cmd { { Ctrl_O, nv_ctrlo, 0, 0 }, { Ctrl_P, nv_up, NV_STS, false }, { Ctrl_Q, nv_visual, 0, false }, - { Ctrl_R, nv_redo, 0, 0 }, + { Ctrl_R, nv_redo_or_register, 0, 0 }, { Ctrl_S, nv_ignore, 0, 0 }, { Ctrl_T, nv_tagpop, NV_NCW, 0 }, { Ctrl_U, nv_halfpage, 0, 0 }, @@ -236,7 +229,7 @@ static const struct nv_cmd { { 'N', nv_next, 0, SEARCH_REV }, { 'O', nv_open, 0, 0 }, { 'P', nv_put, 0, 0 }, - { 'Q', nv_exmode, NV_NCW, 0 }, + { 'Q', nv_regreplay, 0, 0 }, { 'R', nv_Replace, 0, false }, { 'S', nv_subst, NV_KEEPREG, 0 }, { 'T', nv_csearch, NV_NCH_ALW|NV_LANG, BACKWARD }, @@ -341,6 +334,7 @@ static const struct nv_cmd { { K_SELECT, nv_select, 0, 0 }, { K_EVENT, nv_event, NV_KEEPREG, 0 }, { K_COMMAND, nv_colon, 0, 0 }, + { K_LUA, nv_colon, 0, 0 }, }; // Number of commands in nv_cmds[]. @@ -487,6 +481,7 @@ static void normal_prepare(NormalState *s) if (finish_op != c) { ui_cursor_shape(); // may show different cursor shape } + may_trigger_modechanged(); // When not finishing an operator and no register name typed, reset the count. if (!finish_op && !s->oa.regname) { @@ -829,15 +824,12 @@ static bool normal_get_command_count(NormalState *s) if (s->c == K_DEL || s->c == K_KDEL) { s->ca.count0 /= 10; del_from_showcmd(4); // delete the digit and ~@% + } else if (s->ca.count0 > 99999999L) { + s->ca.count0 = 999999999L; } else { s->ca.count0 = s->ca.count0 * 10 + (s->c - '0'); } - if (s->ca.count0 < 0) { - // overflow - s->ca.count0 = 999999999L; - } - // Set v:count here, when called from main() and not a stuffed // command, so that v:count can be used in an expression mapping // right after the count. Do set it for redo. @@ -928,6 +920,7 @@ normal_end: // Reset finish_op, in case it was set s->c = finish_op; finish_op = false; + may_trigger_modechanged(); // Redraw the cursor with another shape, if we were in Operator-pending // mode or did a replace command. if (s->c || s->ca.cmdchar == 'r') { @@ -965,6 +958,8 @@ normal_end: && s->oa.regname == 0) { if (restart_VIsual_select == 1) { VIsual_select = true; + VIsual_select_reg = 0; + may_trigger_modechanged(); showmode(); restart_VIsual_select = 0; } @@ -1013,7 +1008,14 @@ static int normal_execute(VimState *state, int key) // restart automatically. // Insert the typed character in the typeahead buffer, so that it can // be mapped in Insert mode. Required for ":lmap" to work. - ins_char_typebuf(s->c); + int len = ins_char_typebuf(s->c, mod_mask); + + // When recording and gotchars() was called the character will be + // recorded again, remove the previous recording. + if (KeyTyped) { + ungetchars(len); + } + if (restart_edit != 0) { s->c = 'd'; } else { @@ -1025,7 +1027,7 @@ static int normal_execute(VimState *state, int key) s->need_flushbuf = add_to_showcmd(s->c); - while (normal_get_command_count(s)) { continue; } + while (normal_get_command_count(s)) { } if (s->c == K_EVENT) { // Save the count values so that ca.opcount and ca.count0 are exactly @@ -1041,14 +1043,14 @@ static int normal_execute(VimState *state, int key) // If you give a count before AND after the operator, they are // multiplied. if (s->ca.count0) { - s->ca.count0 = (long)((uint64_t)s->ca.count0 * (uint64_t)s->ca.opcount); + if (s->ca.opcount >= 999999999L / s->ca.count0) { + s->ca.count0 = 999999999L; + } else { + s->ca.count0 *= s->ca.opcount; + } } else { s->ca.count0 = s->ca.opcount; } - if (s->ca.count0 < 0) { - // overflow - s->ca.count0 = 999999999L; - } } // Always remember the count. It will be set to zero (on the next call, @@ -1221,22 +1223,18 @@ 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); + if (!finish_op) { + // Trigger Scroll if the viewport changed. + may_trigger_winscrolled(); } } static void normal_check_cursor_moved(NormalState *s) { // Trigger CursorMoved if the cursor moved. - if (!finish_op && (has_event(EVENT_CURSORMOVED) || curwin->w_p_cole > 0) + if (!finish_op && has_event(EVENT_CURSORMOVED) && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor)) { - if (has_event(EVENT_CURSORMOVED)) { - apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf); - } - + apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf); curwin->w_last_cursormoved = curwin->w_cursor; } } @@ -1286,22 +1284,6 @@ static void normal_redraw(NormalState *s) update_topline(curwin); validate_cursor(); - // If the cursor moves horizontally when 'concealcursor' is active, then the - // current line needs to be redrawn in order to calculate the correct - // cursor position. - if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin)) { - redrawWinline(curwin, curwin->w_cursor.lnum); - } - - // Might need to update for 'cursorline'. - // When 'cursorlineopt' is "screenline" need to redraw always. - if (curwin->w_p_cul - && (curwin->w_last_cursorline != curwin->w_cursor.lnum - || (curwin->w_p_culopt_flags & CULOPT_SCRLINE)) - && !char_avail()) { - redraw_later(curwin, VALID); - } - if (VIsual_active) { update_curbuf(INVERTED); // update inverted part } else if (must_redraw) { @@ -1370,9 +1352,10 @@ 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. + // Ensure curwin->w_topline and curwin->w_leftcol are up to date + // before triggering a WinScrolled autocommand. update_topline(curwin); + validate_cursor(); normal_check_cursor_moved(s); normal_check_text_changed(s); @@ -1452,758 +1435,6 @@ static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount) *set_prevcount = false; // only set v:prevcount once } -// Handle an operator after Visual mode or when the movement is finished. -// "gui_yank" is true when yanking text for the clipboard. -void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) -{ - oparg_T *oap = cap->oap; - pos_T old_cursor; - bool empty_region_error; - int restart_edit_save; - int lbr_saved = curwin->w_p_lbr; - - - // The visual area is remembered for redo - static int redo_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V - static linenr_T redo_VIsual_line_count; // number of lines - static colnr_T redo_VIsual_vcol; // number of cols or end column - static long redo_VIsual_count; // count for Visual operator - static int redo_VIsual_arg; // extra argument - bool include_line_break = false; - - old_cursor = curwin->w_cursor; - - /* - * If an operation is pending, handle it... - */ - if ((finish_op - || VIsual_active) - && oap->op_type != OP_NOP) { - // Yank can be redone when 'y' is in 'cpoptions', but not when yanking - // for the clipboard. - const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank; - - // Avoid a problem with unwanted linebreaks in block mode - if (curwin->w_p_lbr) { - curwin->w_valid &= ~VALID_VIRTCOL; - } - curwin->w_p_lbr = false; - oap->is_VIsual = VIsual_active; - if (oap->motion_force == 'V') { - oap->motion_type = kMTLineWise; - } else if (oap->motion_force == 'v') { - // If the motion was linewise, "inclusive" will not have been set. - // Use "exclusive" to be consistent. Makes "dvj" work nice. - if (oap->motion_type == kMTLineWise) { - oap->inclusive = false; - } else if (oap->motion_type == kMTCharWise) { - // If the motion already was charwise, toggle "inclusive" - oap->inclusive = !oap->inclusive; - } - oap->motion_type = kMTCharWise; - } else if (oap->motion_force == Ctrl_V) { - // Change line- or charwise motion into Visual block mode. - if (!VIsual_active) { - VIsual_active = true; - VIsual = oap->start; - } - VIsual_mode = Ctrl_V; - VIsual_select = false; - VIsual_reselect = false; - } - - // Only redo yank when 'y' flag is in 'cpoptions'. - // Never redo "zf" (define fold). - if ((redo_yank || oap->op_type != OP_YANK) - && ((!VIsual_active || oap->motion_force) - // Also redo Operator-pending Visual mode mappings. - || ((cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) - && oap->op_type != OP_COLON)) - && cap->cmdchar != 'D' - && oap->op_type != OP_FOLD - && oap->op_type != OP_FOLDOPEN - && oap->op_type != OP_FOLDOPENREC - && oap->op_type != OP_FOLDCLOSE - && oap->op_type != OP_FOLDCLOSEREC - && oap->op_type != OP_FOLDDEL - && oap->op_type != OP_FOLDDELREC) { - prep_redo(oap->regname, cap->count0, - get_op_char(oap->op_type), get_extra_op_char(oap->op_type), - oap->motion_force, cap->cmdchar, cap->nchar); - if (cap->cmdchar == '/' || cap->cmdchar == '?') { // was a search - /* - * If 'cpoptions' does not contain 'r', insert the search - * pattern to really repeat the same command. - */ - if (vim_strchr(p_cpo, CPO_REDO) == NULL) { - AppendToRedobuffLit(cap->searchbuf, -1); - } - AppendToRedobuff(NL_STR); - } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { - // do_cmdline() has stored the first typed line in - // "repeat_cmdline". When several lines are typed repeating - // won't be possible. - if (repeat_cmdline == NULL) { - ResetRedobuff(); - } else { - AppendToRedobuffLit(repeat_cmdline, -1); - AppendToRedobuff(NL_STR); - XFREE_CLEAR(repeat_cmdline); - } - } - } - - if (redo_VIsual_busy) { - /* Redo of an operation on a Visual area. Use the same size from - * redo_VIsual_line_count and redo_VIsual_vcol. */ - oap->start = curwin->w_cursor; - curwin->w_cursor.lnum += redo_VIsual_line_count - 1; - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - } - VIsual_mode = redo_VIsual_mode; - if (redo_VIsual_vcol == MAXCOL || VIsual_mode == 'v') { - if (VIsual_mode == 'v') { - if (redo_VIsual_line_count <= 1) { - validate_virtcol(); - curwin->w_curswant = - curwin->w_virtcol + redo_VIsual_vcol - 1; - } else { - curwin->w_curswant = redo_VIsual_vcol; - } - } else { - curwin->w_curswant = MAXCOL; - } - coladvance(curwin->w_curswant); - } - cap->count0 = redo_VIsual_count; - cap->count1 = (cap->count0 == 0 ? 1 : cap->count0); - } else if (VIsual_active) { - if (!gui_yank) { - // Save the current VIsual area for '< and '> marks, and "gv" - curbuf->b_visual.vi_start = VIsual; - curbuf->b_visual.vi_end = curwin->w_cursor; - curbuf->b_visual.vi_mode = VIsual_mode; - if (VIsual_mode_orig != NUL) { - curbuf->b_visual.vi_mode = VIsual_mode_orig; - VIsual_mode_orig = NUL; - } - curbuf->b_visual.vi_curswant = curwin->w_curswant; - curbuf->b_visual_mode_eval = VIsual_mode; - } - - // In Select mode, a linewise selection is operated upon like a - // charwise selection. - // Special case: gH<Del> deletes the last line. - if (VIsual_select && VIsual_mode == 'V' - && cap->oap->op_type != OP_DELETE) { - if (lt(VIsual, curwin->w_cursor)) { - VIsual.col = 0; - curwin->w_cursor.col = - (colnr_T)STRLEN(ml_get(curwin->w_cursor.lnum)); - } else { - curwin->w_cursor.col = 0; - VIsual.col = (colnr_T)STRLEN(ml_get(VIsual.lnum)); - } - VIsual_mode = 'v'; - } - /* If 'selection' is "exclusive", backup one character for - * charwise selections. */ - else if (VIsual_mode == 'v') { - include_line_break = - unadjust_for_sel(); - } - - oap->start = VIsual; - if (VIsual_mode == 'V') { - oap->start.col = 0; - oap->start.coladd = 0; - } - } - - /* - * Set oap->start to the first position of the operated text, oap->end - * to the end of the operated text. w_cursor is equal to oap->start. - */ - if (lt(oap->start, curwin->w_cursor)) { - // Include folded lines completely. - if (!VIsual_active) { - if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL)) { - oap->start.col = 0; - } - if ((curwin->w_cursor.col > 0 - || oap->inclusive - || oap->motion_type == kMTLineWise) - && hasFolding(curwin->w_cursor.lnum, NULL, - &curwin->w_cursor.lnum)) { - curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr()); - } - } - oap->end = curwin->w_cursor; - curwin->w_cursor = oap->start; - - /* w_virtcol may have been updated; if the cursor goes back to its - * previous position w_virtcol becomes invalid and isn't updated - * automatically. */ - curwin->w_valid &= ~VALID_VIRTCOL; - } else { - // Include folded lines completely. - if (!VIsual_active && oap->motion_type == kMTLineWise) { - if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, - NULL)) { - curwin->w_cursor.col = 0; - } - if (hasFolding(oap->start.lnum, NULL, &oap->start.lnum)) { - oap->start.col = (colnr_T)STRLEN(ml_get(oap->start.lnum)); - } - } - oap->end = oap->start; - oap->start = curwin->w_cursor; - } - - // Just in case lines were deleted that make the position invalid. - check_pos(curwin->w_buffer, &oap->end); - oap->line_count = oap->end.lnum - oap->start.lnum + 1; - - // Set "virtual_op" before resetting VIsual_active. - virtual_op = virtual_active(); - - if (VIsual_active || redo_VIsual_busy) { - get_op_vcol(oap, redo_VIsual_vcol, true); - - if (!redo_VIsual_busy && !gui_yank) { - /* - * Prepare to reselect and redo Visual: this is based on the - * size of the Visual text - */ - resel_VIsual_mode = VIsual_mode; - if (curwin->w_curswant == MAXCOL) { - resel_VIsual_vcol = MAXCOL; - } else { - if (VIsual_mode != Ctrl_V) { - getvvcol(curwin, &(oap->end), - NULL, NULL, &oap->end_vcol); - } - if (VIsual_mode == Ctrl_V || oap->line_count <= 1) { - if (VIsual_mode != Ctrl_V) { - getvvcol(curwin, &(oap->start), - &oap->start_vcol, NULL, NULL); - } - resel_VIsual_vcol = oap->end_vcol - oap->start_vcol + 1; - } else { - resel_VIsual_vcol = oap->end_vcol; - } - } - resel_VIsual_line_count = oap->line_count; - } - - // can't redo yank (unless 'y' is in 'cpoptions') and ":" - if ((redo_yank || oap->op_type != OP_YANK) - && oap->op_type != OP_COLON - && oap->op_type != OP_FOLD - && oap->op_type != OP_FOLDOPEN - && oap->op_type != OP_FOLDOPENREC - && oap->op_type != OP_FOLDCLOSE - && oap->op_type != OP_FOLDCLOSEREC - && oap->op_type != OP_FOLDDEL - && oap->op_type != OP_FOLDDELREC - && oap->motion_force == NUL) { - /* Prepare for redoing. Only use the nchar field for "r", - * otherwise it might be the second char of the operator. */ - if (cap->cmdchar == 'g' && (cap->nchar == 'n' - || cap->nchar == 'N')) { - prep_redo(oap->regname, cap->count0, - get_op_char(oap->op_type), get_extra_op_char(oap->op_type), - oap->motion_force, cap->cmdchar, cap->nchar); - } else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND) { - int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL; - - // reverse what nv_replace() did - if (nchar == REPLACE_CR_NCHAR) { - nchar = CAR; - } else if (nchar == REPLACE_NL_NCHAR) { - nchar = NL; - } - prep_redo(oap->regname, 0L, NUL, 'v', get_op_char(oap->op_type), - get_extra_op_char(oap->op_type), nchar); - } - if (!redo_VIsual_busy) { - redo_VIsual_mode = resel_VIsual_mode; - redo_VIsual_vcol = resel_VIsual_vcol; - redo_VIsual_line_count = resel_VIsual_line_count; - redo_VIsual_count = cap->count0; - redo_VIsual_arg = cap->arg; - } - } - - // oap->inclusive defaults to true. - // If oap->end is on a NUL (empty line) oap->inclusive becomes - // false. This makes "d}P" and "v}dP" work the same. - if (oap->motion_force == NUL || oap->motion_type == kMTLineWise) { - oap->inclusive = true; - } - if (VIsual_mode == 'V') { - oap->motion_type = kMTLineWise; - } else if (VIsual_mode == 'v') { - oap->motion_type = kMTCharWise; - if (*ml_get_pos(&(oap->end)) == NUL - && (include_line_break || !virtual_op)) { - oap->inclusive = false; - // Try to include the newline, unless it's an operator - // that works on lines only. - if (*p_sel != 'o' - && !op_on_lines(oap->op_type) - && oap->end.lnum < curbuf->b_ml.ml_line_count) { - oap->end.lnum++; - oap->end.col = 0; - oap->end.coladd = 0; - oap->line_count++; - } - } - } - - redo_VIsual_busy = false; - - /* - * Switch Visual off now, so screen updating does - * not show inverted text when the screen is redrawn. - * With OP_YANK and sometimes with OP_COLON and OP_FILTER there is - * no screen redraw, so it is done here to remove the inverted - * part. - */ - if (!gui_yank) { - VIsual_active = false; - setmouse(); - mouse_dragging = 0; - may_clear_cmdline(); - if ((oap->op_type == OP_YANK - || oap->op_type == OP_COLON - || oap->op_type == OP_FUNCTION - || oap->op_type == OP_FILTER) - && oap->motion_force == NUL) { - // Make sure redrawing is correct. - curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); - } - } - } - - // Include the trailing byte of a multi-byte char. - if (oap->inclusive) { - const int l = utfc_ptr2len(ml_get_pos(&oap->end)); - if (l > 1) { - oap->end.col += l - 1; - } - } - curwin->w_set_curswant = true; - - /* - * oap->empty is set when start and end are the same. The inclusive - * flag affects this too, unless yanking and the end is on a NUL. - */ - oap->empty = (oap->motion_type != kMTLineWise - && (!oap->inclusive - || (oap->op_type == OP_YANK - && gchar_pos(&oap->end) == NUL)) - && equalpos(oap->start, oap->end) - && !(virtual_op && oap->start.coladd != oap->end.coladd) - ); - /* - * For delete, change and yank, it's an error to operate on an - * empty region, when 'E' included in 'cpoptions' (Vi compatible). - */ - empty_region_error = (oap->empty - && vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL); - - /* Force a redraw when operating on an empty Visual region, when - * 'modifiable is off or creating a fold. */ - if (oap->is_VIsual && (oap->empty || !MODIFIABLE(curbuf) - || oap->op_type == OP_FOLD - )) { - curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); - } - - /* - * If the end of an operator is in column one while oap->motion_type - * is kMTCharWise and oap->inclusive is false, we put op_end after the last - * character in the previous line. If op_start is on or before the - * first non-blank in the line, the operator becomes linewise - * (strange, but that's the way vi does it). - */ - if (oap->motion_type == kMTCharWise - && oap->inclusive == false - && !(cap->retval & CA_NO_ADJ_OP_END) - && oap->end.col == 0 - && (!oap->is_VIsual || *p_sel == 'o') - && oap->line_count > 1) { - oap->end_adjusted = true; // remember that we did this - oap->line_count--; - oap->end.lnum--; - if (inindent(0)) { - oap->motion_type = kMTLineWise; - } else { - oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); - if (oap->end.col) { - --oap->end.col; - oap->inclusive = true; - } - } - } else { - oap->end_adjusted = false; - } - - switch (oap->op_type) { - case OP_LSHIFT: - case OP_RSHIFT: - op_shift(oap, true, - oap->is_VIsual ? (int)cap->count1 : - 1); - auto_format(false, true); - break; - - case OP_JOIN_NS: - case OP_JOIN: - if (oap->line_count < 2) { - oap->line_count = 2; - } - if (curwin->w_cursor.lnum + oap->line_count - 1 > - curbuf->b_ml.ml_line_count) { - beep_flush(); - } else { - do_join((size_t)oap->line_count, oap->op_type == OP_JOIN, - true, true, true); - auto_format(false, true); - } - break; - - case OP_DELETE: - VIsual_reselect = false; // don't reselect now - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - (void)op_delete(oap); - // save cursor line for undo if it wasn't saved yet - if (oap->motion_type == kMTLineWise - && has_format_option(FO_AUTO) - && u_save_cursor() == OK) { - auto_format(false, true); - } - } - break; - - case OP_YANK: - if (empty_region_error) { - if (!gui_yank) { - vim_beep(BO_OPER); - CancelRedo(); - } - } else { - curwin->w_p_lbr = lbr_saved; - oap->excl_tr_ws = cap->cmdchar == 'z'; - (void)op_yank(oap, !gui_yank, false); - } - check_cursor_col(); - break; - - case OP_CHANGE: - VIsual_reselect = false; // don't reselect now - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - /* This is a new edit command, not a restart. Need to - * remember it to make 'insertmode' work with mappings for - * Visual mode. But do this only once and not when typed and - * 'insertmode' isn't set. */ - if (p_im || !KeyTyped) { - restart_edit_save = restart_edit; - } else { - restart_edit_save = 0; - } - restart_edit = 0; - - // Restore linebreak, so that when the user edits it looks as before. - curwin->w_p_lbr = lbr_saved; - - // Reset finish_op now, don't want it set inside edit(). - finish_op = false; - if (op_change(oap)) { // will call edit() - cap->retval |= CA_COMMAND_BUSY; - } - if (restart_edit == 0) { - restart_edit = restart_edit_save; - } - } - break; - - case OP_FILTER: - if (vim_strchr(p_cpo, CPO_FILTER) != NULL) { - AppendToRedobuff("!\r"); // Use any last used !cmd. - } else { - bangredo = true; // do_bang() will put cmd in redo buffer. - } - FALLTHROUGH; - - case OP_INDENT: - case OP_COLON: - - /* - * If 'equalprg' is empty, do the indenting internally. - */ - if (oap->op_type == OP_INDENT && *get_equalprg() == NUL) { - if (curbuf->b_p_lisp) { - op_reindent(oap, get_lisp_indent); - break; - } - op_reindent(oap, - *curbuf->b_p_inde != NUL ? get_expr_indent : - get_c_indent); - break; - } - - op_colon(oap); - break; - - case OP_TILDE: - case OP_UPPER: - case OP_LOWER: - case OP_ROT13: - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - op_tilde(oap); - } - check_cursor_col(); - break; - - case OP_FORMAT: - if (*curbuf->b_p_fex != NUL) { - op_formatexpr(oap); // use expression - } else { - if (*p_fp != NUL || *curbuf->b_p_fp != NUL) { - op_colon(oap); // use external command - } else { - op_format(oap, false); // use internal function - } - } - break; - - case OP_FORMAT2: - op_format(oap, true); // use internal function - break; - - case OP_FUNCTION: - // Restore linebreak, so that when the user edits it looks as - // before. - curwin->w_p_lbr = lbr_saved; - op_function(oap); // call 'operatorfunc' - break; - - case OP_INSERT: - case OP_APPEND: - VIsual_reselect = false; // don't reselect now - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - /* This is a new edit command, not a restart. Need to - * remember it to make 'insertmode' work with mappings for - * Visual mode. But do this only once. */ - restart_edit_save = restart_edit; - restart_edit = 0; - - // Restore linebreak, so that when the user edits it looks as before. - curwin->w_p_lbr = lbr_saved; - - op_insert(oap, cap->count1); - - // Reset linebreak, so that formatting works correctly. - curwin->w_p_lbr = false; - - /* TODO: when inserting in several lines, should format all - * the lines. */ - auto_format(false, true); - - if (restart_edit == 0) { - restart_edit = restart_edit_save; - } else { - cap->retval |= CA_COMMAND_BUSY; - } - } - break; - - case OP_REPLACE: - VIsual_reselect = false; // don't reselect now - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - // Restore linebreak, so that when the user edits it looks as before. - curwin->w_p_lbr = lbr_saved; - - op_replace(oap, cap->nchar); - } - break; - - case OP_FOLD: - VIsual_reselect = false; // don't reselect now - 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, 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: - case OP_FOLDDELREC: - VIsual_reselect = false; // don't reselect now - deleteFold(curwin, oap->start.lnum, oap->end.lnum, - oap->op_type == OP_FOLDDELREC, oap->is_VIsual); - break; - - case OP_NR_ADD: - case OP_NR_SUB: - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - VIsual_active = true; - curwin->w_p_lbr = lbr_saved; - op_addsub(oap, cap->count1, redo_VIsual_arg); - VIsual_active = false; - } - check_cursor_col(); - break; - default: - clearopbeep(oap); - } - virtual_op = kNone; - if (!gui_yank) { - /* - * if 'sol' not set, go back to old column for some commands - */ - if (!p_sol && oap->motion_type == kMTLineWise && !oap->end_adjusted - && (oap->op_type == OP_LSHIFT || oap->op_type == OP_RSHIFT - || oap->op_type == OP_DELETE)) { - curwin->w_p_lbr = false; - coladvance(curwin->w_curswant = old_col); - } - } else { - curwin->w_cursor = old_cursor; - } - clearop(oap); - motion_force = NUL; - } - curwin->w_p_lbr = lbr_saved; -} - -/* - * Handle indent and format operators and visual mode ":". - */ -static void op_colon(oparg_T *oap) -{ - stuffcharReadbuff(':'); - if (oap->is_VIsual) { - stuffReadbuff("'<,'>"); - } else { - // Make the range look nice, so it can be repeated. - if (oap->start.lnum == curwin->w_cursor.lnum) { - stuffcharReadbuff('.'); - } else { - stuffnumReadbuff((long)oap->start.lnum); - } - if (oap->end.lnum != oap->start.lnum) { - stuffcharReadbuff(','); - if (oap->end.lnum == curwin->w_cursor.lnum) { - stuffcharReadbuff('.'); - } else if (oap->end.lnum == curbuf->b_ml.ml_line_count) { - stuffcharReadbuff('$'); - } else if (oap->start.lnum == curwin->w_cursor.lnum) { - stuffReadbuff(".+"); - stuffnumReadbuff(oap->line_count - 1); - } else { - stuffnumReadbuff((long)oap->end.lnum); - } - } - } - if (oap->op_type != OP_COLON) { - stuffReadbuff("!"); - } - if (oap->op_type == OP_INDENT) { - stuffReadbuff((const char *)get_equalprg()); - stuffReadbuff("\n"); - } else if (oap->op_type == OP_FORMAT) { - if (*curbuf->b_p_fp != NUL) { - stuffReadbuff((const char *)curbuf->b_p_fp); - } else if (*p_fp != NUL) { - stuffReadbuff((const char *)p_fp); - } else { - stuffReadbuff("fmt"); - } - stuffReadbuff("\n']"); - } - - /* - * do_cmdline() does the rest - */ -} - -/* - * Handle the "g@" operator: call 'operatorfunc'. - */ -static void op_function(const oparg_T *oap) - FUNC_ATTR_NONNULL_ALL -{ - const TriState save_virtual_op = virtual_op; - const bool save_finish_op = finish_op; - - if (*p_opfunc == NUL) { - emsg(_("E774: 'operatorfunc' is empty")); - } else { - // Set '[ and '] marks to text to be operated on. - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; - if (oap->motion_type != kMTLineWise && !oap->inclusive) { - // Exclude the end position. - decl(&curbuf->b_op_end); - } - - typval_T argv[2]; - argv[0].v_type = VAR_STRING; - argv[1].v_type = VAR_UNKNOWN; - argv[0].vval.v_string = - (char_u *)(((const char *const[]) { - [kMTBlockWise] = "block", - [kMTLineWise] = "line", - [kMTCharWise] = "char", - })[oap->motion_type]); - - // Reset virtual_op so that 'virtualedit' can be changed in the - // function. - virtual_op = kNone; - - // Reset finish_op so that mode() returns the right value. - finish_op = false; - - (void)call_func_retnr(p_opfunc, 1, argv); - - virtual_op = save_virtual_op; - finish_op = save_finish_op; - } -} - // Move the current tab to tab in same column as mouse or to end of the // tabline if there is no tab there. static void move_tab_to_mouse(void) @@ -2286,9 +1517,12 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) for (;;) { which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); if (is_drag) { - /* If the next character is the same mouse event then use that - * one. Speeds up dragging the status line. */ - if (vpeekc() != NUL) { + // If the next character is the same mouse event then use that + // one. Speeds up dragging the status line. + // Note: Since characters added to the stuff buffer in the code + // below need to come before the next character, do not do this + // when the current character was stuffed. + if (!KeyStuffed && vpeekc() != NUL) { int nc; int save_mouse_grid = mouse_grid; int save_mouse_row = mouse_row; @@ -2855,10 +2089,9 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) } else { // MOUSE_RIGHT stuffcharReadbuff('#'); } - } - // Handle double clicks, unless on status line - else if (in_status_line) { - } else if (in_sep_line) { + } else if (in_status_line || in_sep_line) { + // Do nothing if on status line or vertical separator + // Handle double clicks otherwise } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (NORMAL | INSERT))) { if (is_click || !VIsual_active) { if (VIsual_active) { @@ -3066,6 +2299,7 @@ void end_visual_mode(void) may_clear_cmdline(); adjust_cursor_eol(); + may_trigger_modechanged(); } /* @@ -3092,6 +2326,14 @@ void reset_VIsual(void) } } +void restore_visual_mode(void) +{ + if (VIsual_mode_orig != NUL) { + curbuf->b_visual.vi_mode = VIsual_mode_orig; + VIsual_mode_orig = NUL; + } +} + // Check for a balloon-eval special item to include when searching for an // identifier. When "dir" is BACKWARD "ptr[-1]" must be valid! // Returns true if the character at "*ptr" should be included. @@ -3270,7 +2512,7 @@ static void prep_redo_cmd(cmdarg_T *cap) * Prepare for redo of any command. * Note that only the last argument can be a multi-byte char. */ -static void prep_redo(int regname, long num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5) +void prep_redo(int regname, long num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5) { ResetRedobuff(); if (regname != 0) { // yank from specified buffer @@ -3327,7 +2569,7 @@ static bool checkclearopq(oparg_T *oap) return true; } -static void clearop(oparg_T *oap) +void clearop(oparg_T *oap) { oap->op_type = OP_NOP; oap->regname = 0; @@ -3336,7 +2578,7 @@ static void clearop(oparg_T *oap) motion_force = NUL; } -static void clearopbeep(oparg_T *oap) +void clearopbeep(oparg_T *oap) { clearop(oap); beep_flush(); @@ -3366,7 +2608,7 @@ static void unshift_special(cmdarg_T *cap) /// If the mode is currently displayed clear the command line or update the /// command displayed. -static void may_clear_cmdline(void) +void may_clear_cmdline(void) { if (mode_displayed) { // unshow visual mode later @@ -3377,7 +2619,7 @@ static void may_clear_cmdline(void) } // Routines for displaying a partly typed command -#define SHOWCMD_BUFLEN SHOWCMD_COLS + 1 + 30 +#define SHOWCMD_BUFLEN (SHOWCMD_COLS + 1 + 30) static char_u showcmd_buf[SHOWCMD_BUFLEN]; static char_u old_showcmd_buf[SHOWCMD_BUFLEN]; // For push_showcmd() static bool showcmd_is_clear = true; @@ -3836,8 +3078,14 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock) if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0 || !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) { clearopbeep(oap); - } else if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { - foldOpenCursor(); + } else { + if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { + foldOpenCursor(); + } + // clear any search statistics + if (messaging() && !msg_silent && !shortmess(SHM_SEARCHCOUNT)) { + clear_cmdline = true; + } } } @@ -4027,7 +3275,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) int col_off1; // margin offset for first screen line int col_off2; // margin offset for wrapped screen line int width1; // text width for first screen line - int width2; // test width for wrapped screen line + int width2; // text width for wrapped screen line oap->motion_type = kMTCharWise; oap->inclusive = (curwin->w_curswant == MAXCOL); @@ -4151,6 +3399,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) virtcol -= vim_strsize(get_showbreak_value(curwin)); } + int c = utf_ptr2char(get_cursor_pos_ptr()); + if (dir == FORWARD && virtcol < curwin->w_curswant + && (curwin->w_curswant <= (colnr_T)width1) + && !vim_isprintc(c) && c > 255) { + oneright(); + } + if (virtcol > curwin->w_curswant && (curwin->w_curswant < (colnr_T)width1 ? (curwin->w_curswant > (colnr_T)width1 / 2) @@ -4297,7 +3552,6 @@ static void nv_zet(cmdarg_T *cap) bool undo = false; int l_p_siso = (int)get_sidescrolloff_value(curwin); - assert(l_p_siso <= INT_MAX); if (ascii_isdigit(nchar)) { /* @@ -4775,33 +4029,37 @@ dozet: /* * "Q" command. */ -static void nv_exmode(cmdarg_T *cap) +static void nv_regreplay(cmdarg_T *cap) { - /* - * Ignore 'Q' in Visual mode, just give a beep. - */ - if (VIsual_active) { - vim_beep(BO_EX); - } else if (!checkclearop(cap->oap)) { - do_exmode(); + if (checkclearop(cap->oap)) { + return; + } + + while (cap->count1-- && !got_int) { + if (do_execreg(reg_recorded, false, false, false) == false) { + clearopbeep(cap->oap); + break; + } + line_breakcheck(); } } -/// Handle a ":" command and <Cmd>. +/// Handle a ":" command and <Cmd> or Lua keymaps. static void nv_colon(cmdarg_T *cap) { int old_p_im; bool cmd_result; bool is_cmdkey = cap->cmdchar == K_COMMAND; + bool is_lua = cap->cmdchar == K_LUA; - if (VIsual_active && !is_cmdkey) { + if (VIsual_active && !is_cmdkey && !is_lua) { nv_operator(cap); } else { if (cap->oap->op_type != OP_NOP) { // Using ":" as a movement is charwise exclusive. cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; - } else if (cap->count0 && !is_cmdkey) { + } else if (cap->count0 && !is_cmdkey && !is_lua) { // translate "count:" into ":.,.+(count - 1)" stuffcharReadbuff('.'); if (cap->count0 > 1) { @@ -4817,9 +4075,13 @@ static void nv_colon(cmdarg_T *cap) old_p_im = p_im; - // get a command line and execute it - cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, - cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + if (is_lua) { + cmd_result = map_execute_lua(); + } else { + // get a command line and execute it + cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, + cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + } // If 'insertmode' changed, enter or exit Insert mode if (p_im != old_p_im) { @@ -4851,6 +4113,7 @@ static void nv_ctrlg(cmdarg_T *cap) { if (VIsual_active) { // toggle Selection/Visual mode VIsual_select = !VIsual_select; + may_trigger_modechanged(); showmode(); } else if (!checkclearop(cap->oap)) { // print full name if count given or :cd used @@ -4894,6 +4157,7 @@ static void nv_ctrlo(cmdarg_T *cap) { if (VIsual_active && VIsual_select) { VIsual_select = false; + may_trigger_modechanged(); showmode(); restart_VIsual_select = 2; // restart Select mode later } else { @@ -5173,11 +4437,7 @@ static void nv_ident(cmdarg_T *cap) // Start insert mode in terminal buffer restart_edit = 'i'; - add_map((char_u *)"<buffer> <esc> <Cmd>call jobstop(&channel)<CR>", TERM_FOCUS, true); - do_cmdline_cmd("autocmd TermClose <buffer> " - " if !v:event.status |" - " exec 'bdelete! ' .. expand('<abuf>') |" - " endif"); + add_map((char_u *)"<buffer> <esc> <Cmd>bdelete!<CR>", TERM_FOCUS, true); } } @@ -5212,8 +4472,13 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp) *pp = ml_get_pos(&VIsual); *lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1; } - // Correct the length to include the whole last character. - *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1); + if (**pp == NUL) { + *lenp = 0; + } + if (*lenp > 0) { + // Correct the length to include all bytes of the last character. + *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1); + } } reset_VIsual_and_resel(); return true; @@ -5747,9 +5012,7 @@ static void nv_brackets(cmdarg_T *cap) * identifier "]i" "[i" "]I" "[I" "]^I" "[^I" * define "]d" "[d" "]D" "[D" "]^D" "[^D" */ - if (vim_strchr((char_u *) - "iI\011dD\004", - cap->nchar) != NULL) { + if (vim_strchr((char_u *)"iI\011dD\004", cap->nchar) != NULL) { char_u *ptr; size_t len; @@ -6590,7 +5853,7 @@ static void nv_gomark(cmdarg_T *cap) } } -// Handle CTRL-O, CTRL-I, "g;", "g,", and "CTRL-Tab" commands. +/// Handle CTRL-O, CTRL-I, "g;", "g,", and "CTRL-Tab" commands. static void nv_pcmark(cmdarg_T *cap) { pos_T *pos; @@ -6599,7 +5862,9 @@ static void nv_pcmark(cmdarg_T *cap) if (!checkclearopq(cap->oap)) { if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) { - goto_tabpage_lastused(); + if (!goto_tabpage_lastused()) { + clearopbeep(cap->oap); + } return; } if (cap->cmdchar == 'g') { @@ -6680,6 +5945,7 @@ static void nv_visual(cmdarg_T *cap) // or char/line mode VIsual_mode = cap->cmdchar; showmode(); + may_trigger_modechanged(); } redraw_curbuf_later(INVERTED); // update the inversion } else { // start Visual mode @@ -6702,11 +5968,8 @@ static void nv_visual(cmdarg_T *cap) * was only one -- webb */ if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1) { - curwin->w_cursor.lnum += - resel_VIsual_line_count * cap->count0 - 1; - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - } + curwin->w_cursor.lnum += resel_VIsual_line_count * cap->count0 - 1; + check_cursor(); } VIsual_mode = resel_VIsual_mode; if (VIsual_mode == 'v') { @@ -6785,7 +6048,7 @@ static void n_start_visual_mode(int c) // Corner case: the 0 position in a tab may change when going into // virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting. // - if (c == Ctrl_V && (ve_flags & VE_BLOCK) && gchar_cursor() == TAB) { + if (c == Ctrl_V && (get_ve_flags() & VE_BLOCK) && gchar_cursor() == TAB) { validate_virtcol(); coladvance(curwin->w_virtcol); } @@ -6793,6 +6056,7 @@ static void n_start_visual_mode(int c) foldAdjustVisual(); + may_trigger_modechanged(); setmouse(); // Check for redraw after changing the state. conceal_check_cursor_line(); @@ -6924,6 +6188,7 @@ static void nv_g_cmd(cmdarg_T *cap) // start Select mode. if (cap->arg) { VIsual_select = true; + VIsual_select_reg = 0; } else { may_start_select('c'); } @@ -7050,20 +6315,17 @@ static void nv_g_cmd(cmdarg_T *cap) curwin->w_set_curswant = true; break; - case 'M': { - const char_u *const ptr = get_cursor_line_ptr(); - + case 'M': oap->motion_type = kMTCharWise; oap->inclusive = false; - i = (int)mb_string2cells_len(ptr, STRLEN(ptr)); + i = linetabsize(get_cursor_line_ptr()); if (cap->count0 > 0 && cap->count0 <= 100) { coladvance((colnr_T)(i * cap->count0 / 100)); } else { coladvance((colnr_T)(i / 2)); } curwin->w_set_curswant = true; - } - break; + break; case '_': /* "g_": to the last non-blank character in the line or <count> lines @@ -7366,9 +6628,10 @@ static void nv_g_cmd(cmdarg_T *cap) goto_tabpage(-(int)cap->count1); } break; + case TAB: - if (!checkclearop(oap)) { - goto_tabpage_lastused(); + if (!checkclearop(oap) && !goto_tabpage_lastused()) { + clearopbeep(oap); } break; @@ -7407,9 +6670,8 @@ static void n_opencmd(cmdarg_T *cap) (cap->cmdchar == 'o' ? 1 : 0)) ) && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, - has_format_option(FO_OPEN_COMS) - ? OPENLINE_DO_COM : 0, - 0)) { + has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0, + 0, NULL)) { if (win_cursorline_standout(curwin)) { // force redraw of cursorline curwin->w_valid &= ~VALID_CROW; @@ -7436,11 +6698,26 @@ static void nv_dot(cmdarg_T *cap) } } -/* - * CTRL-R: undo undo - */ -static void nv_redo(cmdarg_T *cap) +// CTRL-R: undo undo or specify register in select mode +static void nv_redo_or_register(cmdarg_T *cap) { + if (VIsual_select && VIsual_active) { + int reg; + // Get register name + no_mapping++; + reg = plain_vgetc(); + LANGMAP_ADJUST(reg, true); + no_mapping--; + + if (reg == '"') { + // the unnamed register is 0 + reg = 0; + } + + VIsual_select_reg = valid_yank_reg(reg, true) ? reg : 0; + return; + } + if (!checkclearopq(cap->oap)) { u_redo((int)cap->count1); curwin->w_set_curswant = true; @@ -7689,7 +6966,7 @@ static void adjust_cursor(oparg_T *oap) if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL && (!VIsual_active || *p_sel == 'o') && !virtual_active() - && (ve_flags & VE_ONEMORE) == 0) { + && (get_ve_flags() & VE_ONEMORE) == 0) { curwin->w_cursor.col--; // prevent cursor from moving on the trail byte mb_adjust_cursor(); @@ -7730,7 +7007,7 @@ static void adjust_for_sel(cmdarg_T *cap) * Should check VIsual_mode before calling this. * Returns true when backed up to the previous line. */ -static bool unadjust_for_sel(void) +bool unadjust_for_sel(void) { pos_T *pp; @@ -7761,6 +7038,7 @@ static void nv_select(cmdarg_T *cap) { if (VIsual_active) { VIsual_select = true; + VIsual_select_reg = 0; } else if (VIsual_reselect) { cap->nchar = 'v'; // fake "gv" command cap->arg = true; @@ -7895,7 +7173,7 @@ static void nv_esc(cmdarg_T *cap) void set_cursor_for_append_to_line(void) { curwin->w_set_curswant = true; - if (ve_flags == VE_ALL) { + if (get_ve_flags() == VE_ALL) { const int save_State = State; // Pretend Insert mode here to allow the cursor on the @@ -8213,9 +7491,9 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) // overwrites if the old contents is being put. was_visual = true; regname = cap->oap->regname; + bool save_unnamed = cap->cmdchar == 'P'; // '+' and '*' could be the same selection - bool clipoverwrite = (regname == '+' || regname == '*') - && (cb_flags & CB_UNNAMEDMASK); + bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK); if (regname == 0 || regname == '"' || clipoverwrite || ascii_isdigit(regname) || regname == '-') { // The delete might overwrite the register we want to put, save it first @@ -8228,6 +7506,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) // do_put(), which requires the visual selection to still be active. if (!VIsual_active || VIsual_mode == 'V' || regname != '.') { // Now delete the selected text. Avoid messages here. + yankreg_T *old_y_previous; + if (save_unnamed) { + old_y_previous = get_y_previous(); + } cap->cmdchar = 'd'; cap->nchar = NUL; cap->oap->regname = NUL; @@ -8237,6 +7519,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) empty = (curbuf->b_ml.ml_flags & ML_EMPTY); msg_silent--; + if (save_unnamed) { + set_y_previous(old_y_previous); + } + // delete PUT_LINE_BACKWARD; cap->oap->regname = regname; } @@ -8320,71 +7606,6 @@ static void nv_open(cmdarg_T *cap) } } -/// Calculate start/end virtual columns for operating in block mode. -/// -/// @param initial when true: adjust position for 'selectmode' -static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial) -{ - colnr_T start; - colnr_T end; - - if (VIsual_mode != Ctrl_V - || (!initial && oap->end.col < curwin->w_width_inner)) { - return; - } - - oap->motion_type = kMTBlockWise; - - // prevent from moving onto a trail byte - mark_mb_adjustpos(curwin->w_buffer, &oap->end); - - getvvcol(curwin, &(oap->start), &oap->start_vcol, NULL, &oap->end_vcol); - if (!redo_VIsual_busy) { - getvvcol(curwin, &(oap->end), &start, NULL, &end); - - if (start < oap->start_vcol) { - oap->start_vcol = start; - } - if (end > oap->end_vcol) { - if (initial && *p_sel == 'e' - && start >= 1 - && start - 1 >= oap->end_vcol) { - oap->end_vcol = start - 1; - } else { - oap->end_vcol = end; - } - } - } - - // if '$' was used, get oap->end_vcol from longest line - if (curwin->w_curswant == MAXCOL) { - curwin->w_cursor.col = MAXCOL; - oap->end_vcol = 0; - for (curwin->w_cursor.lnum = oap->start.lnum; - curwin->w_cursor.lnum <= oap->end.lnum; ++curwin->w_cursor.lnum) { - getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &end); - if (end > oap->end_vcol) { - oap->end_vcol = end; - } - } - } else if (redo_VIsual_busy) { - oap->end_vcol = oap->start_vcol + redo_VIsual_vcol - 1; - } - - // Correct oap->end.col and oap->start.col to be the - // upper-left and lower-right corner of the block area. - // - // (Actually, this does convert column positions into character - // positions) - curwin->w_cursor.lnum = oap->end.lnum; - coladvance(oap->end_vcol); - oap->end = curwin->w_cursor; - - curwin->w_cursor = oap->start; - coladvance(oap->start_vcol); - oap->start = curwin->w_cursor; -} - // Handle an arbitrary event in normal mode static void nv_event(cmdarg_T *cap) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index b4b9545daf..badc00fb39 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -27,7 +27,9 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/indent.h" +#include "nvim/indent_c.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/macros.h" @@ -36,7 +38,7 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" +#include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" @@ -133,10 +135,18 @@ static char opchars[][3] = { Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB }; -/* - * Translate a command name into an operator type. - * Must only be called with a valid operator name! - */ +yankreg_T *get_y_previous(void) +{ + return y_previous; +} + +void set_y_previous(yankreg_T *yreg) +{ + y_previous = yreg; +} + +/// Translate a command name into an operator type. +/// Must only be called with a valid operator name! int get_op_type(int char1, int char2) { int i; @@ -265,14 +275,14 @@ void op_shift(oparg_T *oap, int curs_top, int amount) msg_attr_keep((char *)IObuff, 0, true, false); } - /* - * Set "'[" and "']" marks. - */ - curbuf->b_op_start = oap->start; - curbuf->b_op_end.lnum = oap->end.lnum; - curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); - if (curbuf->b_op_end.col > 0) { - curbuf->b_op_end.col--; + if (!cmdmod.lockmarks) { + // Set "'[" and "']" marks. + curbuf->b_op_start = oap->start; + curbuf->b_op_end.lnum = oap->end.lnum; + curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); + if (curbuf->b_op_end.col > 0) { + curbuf->b_op_end.col--; + } } changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true); @@ -379,8 +389,8 @@ static void shift_block(oparg_T *oap, int amount) } } for (; ascii_iswhite(*bd.textstart);) { - // TODO: is passing bd.textstart for start of the line OK? - incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, (bd.start_vcol)); + // TODO(fmoralesc): is passing bd.textstart for start of the line OK? + incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, bd.start_vcol); total += incr; bd.start_vcol += incr; } @@ -558,21 +568,18 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def } if (spaces > 0) { - int off; - - // Avoid starting halfway through a multi-byte character. - if (b_insert) { - off = utf_head_off(oldp, oldp + offset + spaces); - } else { - off = (*mb_off_next)(oldp, oldp + offset); - offset += off; - } - spaces -= off; - count -= off; + // avoid copying part of a multi-byte character + offset -= utf_head_off(oldp, oldp + offset); + } + if (spaces < 0) { // can happen when the cursor was moved + spaces = 0; } assert(count >= 0); - newp = (char_u *)xmalloc(STRLEN(oldp) + s_len + (size_t)count + 1); + // Make sure the allocated size matches what is actually copied below. + newp = xmalloc(STRLEN(oldp) + (size_t)spaces + s_len + + (spaces > 0 && !bdp->is_short ? (size_t)p_ts - (size_t)spaces : 0) + + (size_t)count + 1); // copy up to shifted part memmove(newp, oldp, (size_t)offset); @@ -587,14 +594,19 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def offset += (int)s_len; int skipped = 0; - if (spaces && !bdp->is_short) { - // insert post-padding - memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces)); - // We're splitting a TAB, don't copy it. - oldp++; - // We allowed for that TAB, remember this now - count++; - skipped = 1; + if (spaces > 0 && !bdp->is_short) { + if (*oldp == TAB) { + // insert post-padding + memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces)); + // We're splitting a TAB, don't copy it. + oldp++; + // We allowed for that TAB, remember this now + count++; + skipped = 1; + } else { + // Not a TAB, no extra spaces + count = spaces; + } } if (spaces > 0) { @@ -692,9 +704,11 @@ void op_reindent(oparg_T *oap, Indenter how) "%" PRId64 " lines indented ", i), (int64_t)i); } - // set '[ and '] marks - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; + if (!cmdmod.lockmarks) { + // set '[ and '] marks + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + } } /* @@ -910,30 +924,45 @@ int do_record(int c) showmode(); regname = c; retval = OK; + apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf); } - } else { // stop recording - /* - * Get the recorded key hits. K_SPECIAL and CSI will be escaped, this - * needs to be removed again to put it in a register. exec_reg then - * adds the escaping back later. - */ + } else { // stop recording + save_v_event_T save_v_event; + // Set the v:event dictionary with information about the recording. + dict_T *dict = get_v_event(&save_v_event); + + // The recorded text contents. + p = get_recorded(); + if (p != NULL) { + // Remove escaping for K_SPECIAL in multi-byte chars. + vim_unescape_ks(p); + (void)tv_dict_add_str(dict, S_LEN("regcontents"), (const char *)p); + } + + // Name of requested register, or empty string for unnamed operation. + char buf[NUMBUFLEN+2]; + buf[0] = (char)regname; + buf[1] = NUL; + (void)tv_dict_add_str(dict, S_LEN("regname"), buf); + tv_dict_set_keys_readonly(dict); + + // Get the recorded key hits. K_SPECIAL will be escaped, this + // needs to be removed again to put it in a register. exec_reg then + // adds the escaping back later. + apply_autocmds(EVENT_RECORDINGLEAVE, NULL, NULL, false, curbuf); + restore_v_event(dict, &save_v_event); + reg_recorded = reg_recording; reg_recording = 0; if (ui_has(kUIMessages)) { showmode(); } else { msg(""); } - p = get_recorded(); if (p == NULL) { retval = FAIL; } else { - // Remove escaping for CSI and K_SPECIAL in multi-byte chars. - vim_unescape_csi(p); - - /* - * We don't want to change the default register here, so save and - * restore the current register name. - */ + // We don't want to change the default register here, so save and + // restore the current register name. old_y_previous = y_previous; retval = stuff_yank(regname, p); @@ -995,6 +1024,60 @@ static int stuff_yank(int regname, char_u *p) static int execreg_lastc = NUL; +/// When executing a register as a series of ex-commands, if the +/// line-continuation character is used for a line, then join it with one or +/// more previous lines. Note that lines are processed backwards starting from +/// the last line in the register. +/// +/// @param lines list of lines in the register +/// @param idx index of the line starting with \ or "\. Join this line with all the immediate +/// predecessor lines that start with a \ and the first line that doesn't start +/// with a \. Lines that start with a comment "\ character are ignored. +/// @returns the concatenated line. The index of the line that should be +/// processed next is returned in idx. +static char_u *execreg_line_continuation(char_u **lines, size_t *idx) +{ + size_t i = *idx; + assert(i > 0); + const size_t cmd_end = i; + + garray_T ga; + ga_init(&ga, (int)sizeof(char_u), 400); + + char_u *p; + + // search backwards to find the first line of this command. + // Any line not starting with \ or "\ is the start of the + // command. + while (--i > 0) { + p = skipwhite(lines[i]); + if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) { + break; + } + } + const size_t cmd_start = i; + + // join all the lines + ga_concat(&ga, (char *)lines[cmd_start]); + for (size_t j = cmd_start + 1; j <= cmd_end; j++) { + p = skipwhite(lines[j]); + if (*p == '\\') { + // Adjust the growsize to the current length to + // speed up concatenating many lines. + if (ga.ga_len > 400) { + ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); + } + ga_concat(&ga, (char *)(p + 1)); + } + } + ga_append(&ga, NUL); + char_u *str = vim_strsave(ga.ga_data); + ga_clear(&ga); + + *idx = i; + return str; +} + /// Execute a yank register: copy it into the stuff buffer /// /// @param colon insert ':' before each line @@ -1083,7 +1166,21 @@ int do_execreg(int regname, int colon, int addcr, int silent) return FAIL; } } - escaped = vim_strsave_escape_csi(reg->y_array[i]); + + // Handle line-continuation for :@<register> + char_u *str = reg->y_array[i]; + bool free_str = false; + if (colon && i > 0) { + p = skipwhite(str); + if (*p == '\\' || (p[0] == '"' && p[1] == '\\' && p[2] == ' ')) { + str = execreg_line_continuation(reg->y_array, &i); + free_str = true; + } + } + escaped = vim_strsave_escape_ks(str); + if (free_str) { + xfree(str); + } retval = ins_typebuf(escaped, remap, 0, true, silent); xfree(escaped); if (retval == FAIL) { @@ -1125,7 +1222,7 @@ static void put_reedit_in_typebuf(int silent) /// Insert register contents "s" into the typeahead buffer, so that it will be /// executed again. /// -/// @param esc when true then it is to be taken literally: Escape CSI +/// @param esc when true then it is to be taken literally: Escape K_SPECIAL /// characters and no remapping. /// @param colon add ':' before the line static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent) @@ -1140,7 +1237,7 @@ static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent) char_u *p; if (esc) { - p = vim_strsave_escape_csi(s); + p = vim_strsave_escape_ks(s); } else { p = s; } @@ -1419,6 +1516,11 @@ int op_delete(oparg_T *oap) return FAIL; } + if (VIsual_select && oap->is_VIsual) { + // Use the register given with CTRL_R, defaults to zero + oap->regname = VIsual_select_reg; + } + mb_adjust_opend(oap); /* @@ -1471,20 +1573,20 @@ int op_delete(oparg_T *oap) yankreg_T *reg = NULL; int did_yank = false; if (oap->regname != 0) { - // yank without message - did_yank = op_yank(oap, false, true); - if (!did_yank) { - // op_yank failed, don't do anything + // check for read-only register + if (!valid_yank_reg(oap->regname, true)) { + beep_flush(); return OK; } + reg = get_yank_register(oap->regname, YREG_YANK); // yank into specif'd reg + op_yank_reg(oap, false, reg, is_append_register(oap->regname)); // yank without message + did_yank = true; } - /* - * Put deleted text into register 1 and shift number registers if the - * delete contains a line break, or when a regname has been specified. - */ - if (oap->regname != 0 || oap->motion_type == kMTLineWise - || oap->line_count > 1 || oap->use_reg_one) { + // Put deleted text into register 1 and shift number registers if the + // delete contains a line break, or when using a specific operator (Vi + // compatible) + if (oap->motion_type == kMTLineWise || oap->line_count > 1 || oap->use_reg_one) { shift_delete_registers(is_append_register(oap->regname)); reg = &y_regs[1]; op_yank_reg(oap, false, reg, false); @@ -1715,13 +1817,15 @@ int op_delete(oparg_T *oap) msgmore(curbuf->b_ml.ml_line_count - old_lcount); setmarks: - if (oap->motion_type == kMTBlockWise) { - curbuf->b_op_end.lnum = oap->end.lnum; - curbuf->b_op_end.col = oap->start.col; - } else { - curbuf->b_op_end = oap->start; + if (!cmdmod.lockmarks) { + if (oap->motion_type == kMTBlockWise) { + curbuf->b_op_end.lnum = oap->end.lnum; + curbuf->b_op_end.col = oap->start.col; + } else { + curbuf->b_op_end = oap->start; + } + curbuf->b_op_start = oap->start; } - curbuf->b_op_start = oap->start; return OK; } @@ -1768,7 +1872,7 @@ static void replace_character(int c) /* * Replace a whole area with one character. */ -int op_replace(oparg_T *oap, int c) +static int op_replace(oparg_T *oap, int c) { int n, numc; int num_chars; @@ -1926,11 +2030,14 @@ int op_replace(oparg_T *oap, int c) while (ltoreq(curwin->w_cursor, oap->end)) { n = gchar_cursor(); if (n != NUL) { - if (utf_char2len(c) > 1 || utf_char2len(n) > 1) { + int new_byte_len = utf_char2len(c); + int old_byte_len = utfc_ptr2len(get_cursor_pos_ptr()); + + if (new_byte_len > 1 || old_byte_len > 1) { // This is slow, but it handles replacing a single-byte // with a multi-byte and the other way around. if (curwin->w_cursor.lnum == oap->end.lnum) { - oap->end.col += utf_char2len(c) - utf_char2len(n); + oap->end.col += new_byte_len - old_byte_len; } replace_character(c); } else { @@ -1986,9 +2093,11 @@ int op_replace(oparg_T *oap, int c) check_cursor(); changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0L, true); - // Set "'[" and "']" marks. - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; + if (!cmdmod.lockmarks) { + // Set "'[" and "']" marks. + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + } return OK; } @@ -2057,11 +2166,11 @@ void op_tilde(oparg_T *oap) redraw_curbuf_later(INVERTED); } - /* - * Set '[ and '] marks. - */ - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; + if (!cmdmod.lockmarks) { + // Set '[ and '] marks. + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + } if (oap->line_count > p_report) { smsg(NGETTEXT("%" PRId64 " line changed", @@ -2162,7 +2271,8 @@ void op_insert(oparg_T *oap, long count1) { long ins_len, pre_textlen = 0; char_u *firstline, *ins_text; - colnr_T ind_pre = 0; + colnr_T ind_pre_col = 0, ind_post_col; + int ind_pre_vcol = 0, ind_post_vcol = 0; struct block_def bd; int i; pos_T t1; @@ -2179,24 +2289,28 @@ void op_insert(oparg_T *oap, long count1) // doing block_prep(). When only "block" is used, virtual edit is // already disabled, but still need it when calling // coladvance_force(). + // coladvance_force() uses get_ve_flags() to get the 'virtualedit' + // state for the current window. To override that state, we need to + // set the window-local value of ve_flags rather than the global value. if (curwin->w_cursor.coladd > 0) { - unsigned old_ve_flags = ve_flags; + unsigned old_ve_flags = curwin->w_ve_flags; - ve_flags = VE_ALL; if (u_save_cursor() == FAIL) { return; } + curwin->w_ve_flags = VE_ALL; coladvance_force(oap->op_type == OP_APPEND ? oap->end_vcol + 1 : getviscol()); if (oap->op_type == OP_APPEND) { --curwin->w_cursor.col; } - ve_flags = old_ve_flags; + curwin->w_ve_flags = old_ve_flags; } // Get the info about the block before entering the text block_prep(oap, &bd, oap->start.lnum, true); // Get indent information - ind_pre = (colnr_T)getwhitecols_curline(); + ind_pre_col = (colnr_T)getwhitecols_curline(); + ind_pre_vcol = get_indent(); firstline = ml_get(oap->start.lnum) + bd.textcol; if (oap->op_type == OP_APPEND) { @@ -2238,6 +2352,7 @@ void op_insert(oparg_T *oap, long count1) } t1 = oap->start; + const pos_T start_insert = curwin->w_cursor; (void)edit(NUL, false, (linenr_T)count1); // When a tab was inserted, and the characters in front of the tab @@ -2261,33 +2376,29 @@ void op_insert(oparg_T *oap, long count1) // if indent kicked in, the firstline might have changed // but only do that, if the indent actually increased - const colnr_T ind_post = (colnr_T)getwhitecols_curline(); - if (curbuf->b_op_start.col > ind_pre && ind_post > ind_pre) { - bd.textcol += ind_post - ind_pre; - bd.start_vcol += ind_post - ind_pre; + ind_post_col = (colnr_T)getwhitecols_curline(); + if (curbuf->b_op_start.col > ind_pre_col && ind_post_col > ind_pre_col) { + bd.textcol += ind_post_col - ind_pre_col; + ind_post_vcol = get_indent(); + bd.start_vcol += ind_post_vcol - ind_pre_vcol; did_indent = true; } // The user may have moved the cursor before inserting something, try // to adjust the block for that. But only do it, if the difference // does not come from indent kicking in. - if (oap->start.lnum == curbuf->b_op_start_orig.lnum - && !bd.is_MAX - && !did_indent) { + if (oap->start.lnum == curbuf->b_op_start_orig.lnum && !bd.is_MAX && !did_indent) { + const int t = getviscol2(curbuf->b_op_start_orig.col, curbuf->b_op_start_orig.coladd); + if (oap->op_type == OP_INSERT && oap->start.col + oap->start.coladd != curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) { - int t = getviscol2(curbuf->b_op_start_orig.col, - curbuf->b_op_start_orig.coladd); oap->start.col = curbuf->b_op_start_orig.col; pre_textlen -= t - oap->start_vcol; oap->start_vcol = t; } else if (oap->op_type == OP_APPEND - && oap->end.col + oap->end.coladd - >= curbuf->b_op_start_orig.col - + curbuf->b_op_start_orig.coladd) { - int t = getviscol2(curbuf->b_op_start_orig.col, - curbuf->b_op_start_orig.coladd); + && oap->start.col + oap->start.coladd + >= curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) { oap->start.col = curbuf->b_op_start_orig.col; // reset pre_textlen to the value of OP_INSERT pre_textlen += bd.textlen; @@ -2297,12 +2408,26 @@ void op_insert(oparg_T *oap, long count1) } } - /* - * Spaces and tabs in the indent may have changed to other spaces and - * tabs. Get the starting column again and correct the length. - * Don't do this when "$" used, end-of-line will have changed. - */ + // Spaces and tabs in the indent may have changed to other spaces and + // tabs. Get the starting column again and correct the length. + // Don't do this when "$" used, end-of-line will have changed. + // + // if indent was added and the inserted text was after the indent, + // correct the selection for the new indent. + if (did_indent && bd.textcol - ind_post_col > 0) { + oap->start.col += ind_post_col - ind_pre_col; + oap->start_vcol += ind_post_vcol - ind_pre_vcol; + oap->end.col += ind_post_col - ind_pre_col; + oap->end_vcol += ind_post_vcol - ind_pre_vcol; + } block_prep(oap, &bd2, oap->start.lnum, true); + if (did_indent && bd.textcol - ind_post_col > 0) { + // undo for where "oap" is used below + oap->start.col -= ind_post_col - ind_pre_col; + oap->start_vcol -= ind_post_vcol - ind_pre_vcol; + oap->end.col -= ind_post_col - ind_pre_col; + oap->end_vcol -= ind_post_vcol - ind_pre_vcol; + } if (!bd.is_MAX || bd2.textlen < bd.textlen) { if (oap->op_type == OP_APPEND) { pre_textlen += bd2.textlen - bd.textlen; @@ -2321,15 +2446,27 @@ void op_insert(oparg_T *oap, long count1) firstline = ml_get(oap->start.lnum); const size_t len = STRLEN(firstline); colnr_T add = bd.textcol; + colnr_T offset = 0; // offset when cursor was moved in insert mode if (oap->op_type == OP_APPEND) { add += bd.textlen; + // account for pressing cursor in insert mode when '$' was used + if (bd.is_MAX && start_insert.lnum == Insstart.lnum && start_insert.col > Insstart.col) { + offset = start_insert.col - Insstart.col; + add -= offset; + if (oap->end_vcol > offset) { + oap->end_vcol -= offset + 1; + } else { + // moved outside of the visual block, what to do? + return; + } + } } if ((size_t)add > len) { firstline += len; // short line, point to the NUL } else { firstline += add; } - ins_len = (long)STRLEN(firstline) - pre_textlen; + ins_len = (long)STRLEN(firstline) - pre_textlen - offset; if (pre_textlen >= 0 && ins_len > 0) { ins_text = vim_strnsave(firstline, (size_t)ins_len); // block handled here @@ -2511,12 +2648,12 @@ void free_register(yankreg_T *reg) /// Yanks the text between "oap->start" and "oap->end" into a yank register. /// If we are to append (uppercase register), we first yank into a new yank /// register and then concatenate the old and the new one. +/// Do not call this from a delete operation. Use op_yank_reg() instead. /// /// @param oap operator arguments /// @param message show message when more than `&report` lines are yanked. -/// @param deleting whether the function was called from a delete operation. /// @returns whether the operation register was writable. -bool op_yank(oparg_T *oap, bool message, int deleting) +bool op_yank(oparg_T *oap, bool message) FUNC_ATTR_NONNULL_ALL { // check for read-only register @@ -2530,11 +2667,8 @@ bool op_yank(oparg_T *oap, bool message, int deleting) yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK); op_yank_reg(oap, message, reg, is_append_register(oap->regname)); - // op_delete will set_clipboard and do_autocmd - if (!deleting) { - set_clipboard(oap->regname, reg); - do_autocmd_textyankpost(oap, reg); - } + set_clipboard(oap->regname, reg); + do_autocmd_textyankpost(oap, reg); return true; } @@ -2702,9 +2836,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) curr->y_size = j; xfree(reg->y_array); } - if (curwin->w_p_rnu) { - redraw_later(curwin, SOME_VALID); // cursor moved to start - } + if (message) { // Display message about yank? if (yank_type == kMTCharWise && yanklines == 1) { yanklines = 0; @@ -2733,17 +2865,15 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } } - /* - * Set "'[" and "']" marks. - */ - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; - if (yank_type == kMTLineWise) { - curbuf->b_op_start.col = 0; - curbuf->b_op_end.col = MAXCOL; + if (!cmdmod.lockmarks) { + // Set "'[" and "']" marks. + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + if (yank_type == kMTLineWise) { + curbuf->b_op_start.col = 0; + curbuf->b_op_end.col = MAXCOL; + } } - - return; } // Copy a block range into a register. @@ -2768,7 +2898,7 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx, if (exclude_trailing_space) { int s = bd->textlen + bd->endspaces; - while (ascii_iswhite(*(bd->textstart + s - 1)) && s > 0) { + while (s > 0 && ascii_iswhite(*(bd->textstart + s - 1))) { s = s - utf_head_off(bd->textstart, bd->textstart + s - 1) - 1; pnew--; } @@ -2792,8 +2922,9 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) recursive = true; + save_v_event_T save_v_event; // Set the v:event dictionary with information about the yank. - dict_T *dict = get_vim_var_dict(VV_EVENT); + dict_T *dict = get_v_event(&save_v_event); // The yanked text contents. list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); @@ -2830,7 +2961,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) textlock++; apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf); textlock--; - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); recursive = false; } @@ -2872,6 +3003,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) char_u *insert_string = NULL; bool allocated = false; long cnt; + const pos_T orig_start = curbuf->b_op_start; + const pos_T orig_end = curbuf->b_op_end; + unsigned int cur_ve_flags = get_ve_flags(); if (flags & PUT_FIXINDENT) { orig_indent = get_indent(); @@ -2942,7 +3076,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) eol = (*(cursor_pos + utfc_ptr2len(cursor_pos)) == NUL); } - bool ve_allows = (ve_flags == VE_ALL || ve_flags == VE_ONEMORE); + bool ve_allows = (cur_ve_flags == VE_ALL || cur_ve_flags == VE_ONEMORE); bool eof = curbuf->b_ml.ml_line_count == curwin->w_cursor.lnum && one_past_line; if (ve_allows || !(eol || eof)) { @@ -3118,13 +3252,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) yanklen = (int)STRLEN(y_array[0]); - if (ve_flags == VE_ALL && y_type == kMTCharWise) { + if (cur_ve_flags == VE_ALL && y_type == kMTCharWise) { if (gchar_cursor() == TAB) { - /* Don't need to insert spaces when "p" on the last position of a - * tab or "P" on the first position. */ int viscol = getviscol(); + long ts = curbuf->b_p_ts; + // Don't need to insert spaces when "p" on the last position of a + // tab or "P" on the first position. if (dir == FORWARD - ? tabstop_padding(viscol, curbuf->b_p_ts, curbuf->b_p_vts_array) != 1 + ? tabstop_padding(viscol, ts, curbuf->b_p_vts_array) != 1 : curwin->w_cursor.coladd > 0) { coladvance_force(viscol); } else { @@ -3146,7 +3281,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) colnr_T endcol2 = 0; if (dir == FORWARD && c != NUL) { - if (ve_flags == VE_ALL) { + if (cur_ve_flags == VE_ALL) { getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2); } else { getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); @@ -3160,9 +3295,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } col += curwin->w_cursor.coladd; - if (ve_flags == VE_ALL - && (curwin->w_cursor.coladd > 0 - || endcol2 == curwin->w_cursor.col)) { + if (cur_ve_flags == VE_ALL + && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) { if (dir == FORWARD && c == NUL) { col++; } @@ -3244,18 +3378,28 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } - // insert the new text + // Insert the new text. + // First check for multiplication overflow. + if (yanklen + spaces != 0 + && count > ((INT_MAX - (bd.startspaces + bd.endspaces)) / (yanklen + spaces))) { + emsg(_(e_resulting_text_too_long)); + break; + } + totlen = (size_t)(count * (yanklen + spaces) + bd.startspaces + bd.endspaces); int addcount = (int)totlen + lines_appended; newp = (char_u *)xmalloc(totlen + oldlen + 1); + // copy part up to cursor to new line ptr = newp; memmove(ptr, oldp, (size_t)bd.textcol); ptr += bd.textcol; + // may insert some spaces before the new text memset(ptr, ' ', (size_t)bd.startspaces); ptr += bd.startspaces; + // insert the new text for (long j = 0; j < count; j++) { memmove(ptr, y_array[i], (size_t)yanklen); @@ -3269,9 +3413,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) addcount -= spaces; } } + // may insert some spaces after the new text memset(ptr, ' ', (size_t)bd.endspaces); ptr += bd.endspaces; + // move the text after the cursor to the end of the line. int columns = (int)oldlen - bd.textcol - delcount + 1; assert(columns >= 0); @@ -3340,6 +3486,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_type == kMTCharWise && y_size == 1) { linenr_T end_lnum = 0; // init for gcc linenr_T start_lnum = lnum; + int first_byte_off = 0; if (VIsual_active) { end_lnum = curbuf->b_visual.vi_end.lnum; @@ -3359,10 +3506,18 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } - do { + if (count == 0 || yanklen == 0) { + if (VIsual_active) { + lnum = end_lnum; + } + } else if (count > INT_MAX / yanklen) { + // multiplication overflow + emsg(_(e_resulting_text_too_long)); + } else { totlen = (size_t)(count * yanklen); - if (totlen > 0) { + do { oldp = ml_get(lnum); + oldlen = STRLEN(oldp); if (lnum > start_lnum) { pos_T pos = { .lnum = lnum, @@ -3373,11 +3528,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) col = MAXCOL; } } - if (VIsual_active && col > (int)STRLEN(oldp)) { + if (VIsual_active && col > (colnr_T)oldlen) { lnum++; continue; } - newp = (char_u *)xmalloc((size_t)(STRLEN(oldp) + totlen + 1)); + newp = (char_u *)xmalloc(totlen + oldlen + 1); memmove(newp, oldp, (size_t)col); ptr = newp + col; for (i = 0; i < (size_t)count; i++) { @@ -3386,6 +3541,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } STRMOVE(ptr, oldp + col); ml_replace(lnum, newp, false); + + // compute the byte offset for the last character + first_byte_off = utf_head_off(newp, ptr - 1); + // Place cursor on last putted char. if (lnum == curwin->w_cursor.lnum) { // make sure curwin->w_virtcol is updated @@ -3395,22 +3554,30 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) changed_bytes(lnum, col); extmark_splice_cols(curbuf, (int)lnum-1, col, 0, (int)totlen, kExtmarkUndo); - } - if (VIsual_active) { - lnum++; - } - } while (VIsual_active && lnum <= end_lnum); + if (VIsual_active) { + lnum++; + } + } while (VIsual_active && lnum <= end_lnum); - if (VIsual_active) { // reset lnum to the last visual line - lnum--; + if (VIsual_active) { // reset lnum to the last visual line + lnum--; + } } + // put '] at the first byte of the last character curbuf->b_op_end = curwin->w_cursor; + curbuf->b_op_end.col -= first_byte_off; + // For "CTRL-O p" in Insert mode, put cursor after last char if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND))) { curwin->w_cursor.col++; + } else { + curwin->w_cursor.col -= first_byte_off; } } else { + linenr_T new_lnum = new_cursor.lnum; + size_t len; + // Insert at least one line. When y_type is kMTCharWise, break the first // line in two. for (cnt = 1; cnt <= count; cnt++) { @@ -3427,6 +3594,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) STRCAT(newp, ptr); // insert second line ml_append(lnum, newp, (colnr_T)0, false); + new_lnum++; xfree(newp); oldp = ml_get(lnum); @@ -3442,10 +3610,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } for (; i < y_size; i++) { - if ((y_type != kMTCharWise || i < y_size - 1) - && ml_append(lnum, y_array[i], (colnr_T)0, false) - == FAIL) { - goto error; + if ((y_type != kMTCharWise || i < y_size - 1)) { + if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) { + goto error; + } + new_lnum++; } lnum++; ++nr_lines; @@ -3495,6 +3664,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) extmark_splice(curbuf, (int)new_cursor.lnum-1, col + 1, 0, 0, 0, (int)y_size+1, 0, totsize+2, kExtmarkUndo); } + + if (cnt == 1) { + new_lnum = lnum; + } } error: @@ -3520,12 +3693,14 @@ error: curbuf->b_op_start.lnum, nr_lines, true); } - // put '] mark at last inserted character - curbuf->b_op_end.lnum = lnum; - // correct length for change in indent - col = (colnr_T)STRLEN(y_array[y_size - 1]) - lendiff; + // Put the '] mark on the first byte of the last inserted character. + // Correct the length for change in indent. + curbuf->b_op_end.lnum = new_lnum; + len = STRLEN(y_array[y_size - 1]); + col = (colnr_T)len - lendiff; if (col > 1) { - curbuf->b_op_end.col = col - 1; + curbuf->b_op_end.col = col - 1 - utf_head_off(y_array[y_size - 1], + y_array[y_size - 1] + len - 1); } else { curbuf->b_op_end.col = 0; } @@ -3544,8 +3719,12 @@ error: } curwin->w_cursor.col = 0; } else { - curwin->w_cursor.lnum = lnum; + curwin->w_cursor.lnum = new_lnum; curwin->w_cursor.col = col; + curbuf->b_op_end = curwin->w_cursor; + if (col > 1) { + curbuf->b_op_end.col = col - 1; + } } } else if (y_type == kMTLineWise) { // put cursor on first non-blank in first inserted line @@ -3564,6 +3743,10 @@ error: curwin->w_set_curswant = TRUE; end: + if (cmdmod.lockmarks) { + curbuf->b_op_start = orig_start; + curbuf->b_op_end = orig_end; + } if (allocated) { xfree(insert_string); } @@ -3583,14 +3766,16 @@ end: */ void adjust_cursor_eol(void) { + unsigned int cur_ve_flags = get_ve_flags(); + if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL - && (ve_flags & VE_ONEMORE) == 0 + && (cur_ve_flags & VE_ONEMORE) == 0 && !(restart_edit || (State & INSERT))) { // Put the cursor on the last character in the line. dec_cursor(); - if (ve_flags == VE_ALL) { + if (cur_ve_flags == VE_ALL) { colnr_T scol, ecol; // Coladd is set to the width of the last character. @@ -3644,7 +3829,7 @@ void ex_display(exarg_T *eap) int name; char_u *arg = eap->arg; int clen; - char_u type[2]; + int type; if (arg != NULL && *arg == NUL) { arg = NULL; @@ -3657,11 +3842,11 @@ void ex_display(exarg_T *eap) name = get_register_name(i); switch (get_reg_type(name, NULL)) { case kMTLineWise: - type[0] = 'l'; break; + type = 'l'; break; case kMTCharWise: - type[0] = 'c'; break; + type = 'c'; break; default: - type[0] = 'b'; break; + type = 'b'; break; } if (arg != NULL && vim_strchr(arg, name) == NULL) { @@ -3688,88 +3873,87 @@ void ex_display(exarg_T *eap) } if (yb->y_array != NULL) { - msg_putchar('\n'); - msg_puts(" "); - msg_putchar(type[0]); - msg_puts(" "); - msg_putchar('"'); - msg_putchar(name); - msg_puts(" "); - - int n = Columns - 11; - for (size_t j = 0; j < yb->y_size && n > 1; j++) { - if (j) { - msg_puts_attr("^J", attr); - n -= 2; + bool do_show = false; + + for (size_t j = 0; !do_show && j < yb->y_size; j++) { + do_show = !message_filtered(yb->y_array[j]); + } + + if (do_show || yb->y_size == 0) { + msg_putchar('\n'); + msg_puts(" "); + msg_putchar(type); + msg_puts(" "); + msg_putchar('"'); + msg_putchar(name); + msg_puts(" "); + + int n = Columns - 11; + for (size_t j = 0; j < yb->y_size && n > 1; j++) { + if (j) { + msg_puts_attr("^J", attr); + n -= 2; + } + for (p = yb->y_array[j]; *p != NUL && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 + clen = utfc_ptr2len(p); + msg_outtrans_len(p, clen); + p += clen - 1; + } } - for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 NOLINT(whitespace/line_length) - clen = utfc_ptr2len(p); - msg_outtrans_len(p, clen); - p += clen - 1; + if (n > 1 && yb->y_type == kMTLineWise) { + msg_puts_attr("^J", attr); } + ui_flush(); // show one line at a time } - if (n > 1 && yb->y_type == kMTLineWise) { - msg_puts_attr("^J", attr); - } - ui_flush(); // show one line at a time + os_breakcheck(); } - os_breakcheck(); } - /* - * display last inserted text - */ + // display last inserted text if ((p = get_last_insert()) != NULL - && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int) { + && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int + && !message_filtered(p)) { msg_puts("\n c \". "); dis_msg(p, true); } - /* - * display last command line - */ + // display last command line if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL) - && !got_int) { + && !got_int && !message_filtered(last_cmdline)) { msg_puts("\n c \": "); dis_msg(last_cmdline, false); } - /* - * display current file name - */ + // display current file name if (curbuf->b_fname != NULL - && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) { + && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int + && !message_filtered(curbuf->b_fname)) { msg_puts("\n c \"% "); dis_msg(curbuf->b_fname, false); } - /* - * display alternate file name - */ + // display alternate file name if ((arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) { char_u *fname; linenr_T dummy; - if (buflist_name_nr(0, &fname, &dummy) != FAIL) { + if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) { msg_puts("\n c \"# "); dis_msg(fname, false); } } - /* - * display last search pattern - */ + // display last search pattern if (last_search_pat() != NULL - && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int) { + && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int + && !message_filtered(last_search_pat())) { msg_puts("\n c \"/ "); dis_msg(last_search_pat(), false); } - /* - * display last used expression - */ + // display last used expression if (expr_line != NULL && (arg == NULL || vim_strchr(arg, '=') != NULL) - && !got_int) { + && !got_int && !message_filtered(expr_line)) { msg_puts("\n c \"= "); dis_msg(expr_line, false); } @@ -3908,7 +4092,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions // and setup the array of space strings lengths for (t = 0; t < (linenr_T)count; t++) { curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t)); - if (t == 0 && setmark) { + if (t == 0 && setmark && !cmdmod.lockmarks) { // Set the '[ mark. curwin->w_buffer->b_op_start.lnum = curwin->w_cursor.lnum; curwin->w_buffer->b_op_start.col = (colnr_T)STRLEN(curr); @@ -4029,7 +4213,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions ml_replace(curwin->w_cursor.lnum, newp, false); - if (setmark) { + if (setmark && !cmdmod.lockmarks) { // Set the '] mark. curwin->w_buffer->b_op_end.lnum = curwin->w_cursor.lnum; curwin->w_buffer->b_op_end.col = sumsize; @@ -4093,7 +4277,7 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in * If first leader has 'f' flag, the lines can be joined only if the * second line does not have a leader. * If first leader has 'e' flag, the lines can never be joined. - * If fist leader has 's' flag, the lines can only be joined if there is + * If first leader has 's' flag, the lines can only be joined if there is * some text after it and the second line has the 'm' flag. */ if (leader1_flags != NULL) { @@ -4148,7 +4332,7 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in /// Implementation of the format operator 'gq'. /// /// @param keep_cursor keep cursor on same text char -void op_format(oparg_T *oap, int keep_cursor) +static void op_format(oparg_T *oap, int keep_cursor) { long old_line_count = curbuf->b_ml.ml_line_count; @@ -4167,8 +4351,10 @@ void op_format(oparg_T *oap, int keep_cursor) redraw_curbuf_later(INVERTED); } - // Set '[ mark at the start of the formatted area - curbuf->b_op_start = oap->start; + if (!cmdmod.lockmarks) { + // Set '[ mark at the start of the formatted area + curbuf->b_op_start = oap->start; + } // For "gw" remember the cursor position and put it back below (adjusted // for joined and split lines). @@ -4190,8 +4376,10 @@ void op_format(oparg_T *oap, int keep_cursor) old_line_count = curbuf->b_ml.ml_line_count - old_line_count; msgmore(old_line_count); - // put '] mark on the end of the formatted area - curbuf->b_op_end = curwin->w_cursor; + if (!cmdmod.lockmarks) { + // put '] mark on the end of the formatted area + curbuf->b_op_end = curwin->w_cursor; + } if (keep_cursor) { curwin->w_cursor = saved_cursor; @@ -4216,7 +4404,7 @@ void op_format(oparg_T *oap, int keep_cursor) /* * Implementation of the format operator 'gq' for when using 'formatexpr'. */ -void op_formatexpr(oparg_T *oap) +static void op_formatexpr(oparg_T *oap) { if (oap->is_VIsual) { // When there is no change: need to remove the Visual selection @@ -4278,7 +4466,7 @@ void format_lines(linenr_T line_count, int avoid_fex) 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 + char_u *next_leader_flags = NULL; // flags for leader of next line bool advance = true; int second_indent = -1; // indent for second line (comment aware) bool first_par_line = true; @@ -4395,7 +4583,14 @@ void format_lines(linenr_T line_count, int avoid_fex) leader_len, leader_flags, next_leader_len, next_leader_flags)) { - is_end_par = true; + // Special case: If the next line starts with a line comment + // and this line has a line comment after some text, the + // paragraph doesn't really end. + if (next_leader_flags == NULL + || STRNCMP(next_leader_flags, "://", 3) != 0 + || check_linecomment(get_cursor_line_ptr()) == MAXCOL) { + is_end_par = true; + } } /* @@ -4789,7 +4984,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) // Set '[ mark if something changed. Keep the last end // position from do_addsub(). - if (change_cnt > 0) { + if (change_cnt > 0 && !cmdmod.lockmarks) { curbuf->b_op_start = startpos; } @@ -5143,11 +5338,13 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } } - // set the '[ and '] marks - curbuf->b_op_start = startpos; - curbuf->b_op_end = endpos; - if (curbuf->b_op_end.col > 0) { - curbuf->b_op_end.col--; + if (!cmdmod.lockmarks) { + // set the '[ and '] marks + curbuf->b_op_start = startpos; + curbuf->b_op_end = endpos; + if (curbuf->b_op_end.col > 0) { + curbuf->b_op_end.col--; + } } theend: @@ -5578,7 +5775,9 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str // When appending, copy the previous line and free it after. size_t extra = append ? STRLEN(pp[--lnum]) : 0; char_u *s = xmallocz(line_len + extra); - memcpy(s, pp[lnum], extra); + if (extra > 0) { + memcpy(s, pp[lnum], extra); + } memcpy(s + extra, start, line_len); size_t s_len = extra + line_len; @@ -5907,6 +6106,797 @@ void cursor_pos_info(dict_T *dict) } } +// Handle indent and format operators and visual mode ":". +static void op_colon(oparg_T *oap) +{ + stuffcharReadbuff(':'); + if (oap->is_VIsual) { + stuffReadbuff("'<,'>"); + } else { + // Make the range look nice, so it can be repeated. + if (oap->start.lnum == curwin->w_cursor.lnum) { + stuffcharReadbuff('.'); + } else { + stuffnumReadbuff((long)oap->start.lnum); + } + if (oap->end.lnum != oap->start.lnum) { + stuffcharReadbuff(','); + if (oap->end.lnum == curwin->w_cursor.lnum) { + stuffcharReadbuff('.'); + } else if (oap->end.lnum == curbuf->b_ml.ml_line_count) { + stuffcharReadbuff('$'); + } else if (oap->start.lnum == curwin->w_cursor.lnum) { + stuffReadbuff(".+"); + stuffnumReadbuff(oap->line_count - 1); + } else { + stuffnumReadbuff((long)oap->end.lnum); + } + } + } + if (oap->op_type != OP_COLON) { + stuffReadbuff("!"); + } + if (oap->op_type == OP_INDENT) { + stuffReadbuff((const char *)get_equalprg()); + stuffReadbuff("\n"); + } else if (oap->op_type == OP_FORMAT) { + if (*curbuf->b_p_fp != NUL) { + stuffReadbuff((const char *)curbuf->b_p_fp); + } else if (*p_fp != NUL) { + stuffReadbuff((const char *)p_fp); + } else { + stuffReadbuff("fmt"); + } + stuffReadbuff("\n']"); + } + + // do_cmdline() does the rest +} + +// Handle the "g@" operator: call 'operatorfunc'. +static void op_function(const oparg_T *oap) + FUNC_ATTR_NONNULL_ALL +{ + const TriState save_virtual_op = virtual_op; + const bool save_finish_op = finish_op; + const pos_T orig_start = curbuf->b_op_start; + const pos_T orig_end = curbuf->b_op_end; + + if (*p_opfunc == NUL) { + emsg(_("E774: 'operatorfunc' is empty")); + } else { + // Set '[ and '] marks to text to be operated on. + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + if (oap->motion_type != kMTLineWise && !oap->inclusive) { + // Exclude the end position. + decl(&curbuf->b_op_end); + } + + typval_T argv[2]; + argv[0].v_type = VAR_STRING; + argv[1].v_type = VAR_UNKNOWN; + argv[0].vval.v_string = + (char_u *)(((const char *const[]) { + [kMTBlockWise] = "block", + [kMTLineWise] = "line", + [kMTCharWise] = "char", + })[oap->motion_type]); + + // Reset virtual_op so that 'virtualedit' can be changed in the + // function. + virtual_op = kNone; + + // Reset finish_op so that mode() returns the right value. + finish_op = false; + + (void)call_func_retnr(p_opfunc, 1, argv); + + virtual_op = save_virtual_op; + finish_op = save_finish_op; + if (cmdmod.lockmarks) { + curbuf->b_op_start = orig_start; + curbuf->b_op_end = orig_end; + } + } +} + +/// Calculate start/end virtual columns for operating in block mode. +/// +/// @param initial when true: adjust position for 'selectmode' +static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial) +{ + colnr_T start; + colnr_T end; + + if (VIsual_mode != Ctrl_V + || (!initial && oap->end.col < curwin->w_width_inner)) { + return; + } + + oap->motion_type = kMTBlockWise; + + // prevent from moving onto a trail byte + mark_mb_adjustpos(curwin->w_buffer, &oap->end); + + getvvcol(curwin, &(oap->start), &oap->start_vcol, NULL, &oap->end_vcol); + if (!redo_VIsual_busy) { + getvvcol(curwin, &(oap->end), &start, NULL, &end); + + if (start < oap->start_vcol) { + oap->start_vcol = start; + } + if (end > oap->end_vcol) { + if (initial && *p_sel == 'e' + && start >= 1 + && start - 1 >= oap->end_vcol) { + oap->end_vcol = start - 1; + } else { + oap->end_vcol = end; + } + } + } + + // if '$' was used, get oap->end_vcol from longest line + if (curwin->w_curswant == MAXCOL) { + curwin->w_cursor.col = MAXCOL; + oap->end_vcol = 0; + for (curwin->w_cursor.lnum = oap->start.lnum; + curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) { + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &end); + if (end > oap->end_vcol) { + oap->end_vcol = end; + } + } + } else if (redo_VIsual_busy) { + oap->end_vcol = oap->start_vcol + redo_VIsual_vcol - 1; + } + + // Correct oap->end.col and oap->start.col to be the + // upper-left and lower-right corner of the block area. + // + // (Actually, this does convert column positions into character + // positions) + curwin->w_cursor.lnum = oap->end.lnum; + coladvance(oap->end_vcol); + oap->end = curwin->w_cursor; + + curwin->w_cursor = oap->start; + coladvance(oap->start_vcol); + oap->start = curwin->w_cursor; +} + +// Handle an operator after Visual mode or when the movement is finished. +// "gui_yank" is true when yanking text for the clipboard. +void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) +{ + oparg_T *oap = cap->oap; + pos_T old_cursor; + bool empty_region_error; + int restart_edit_save; + int lbr_saved = curwin->w_p_lbr; + + + // The visual area is remembered for redo + static int redo_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V + static linenr_T redo_VIsual_line_count; // number of lines + static colnr_T redo_VIsual_vcol; // number of cols or end column + static long redo_VIsual_count; // count for Visual operator + static int redo_VIsual_arg; // extra argument + bool include_line_break = false; + + old_cursor = curwin->w_cursor; + + // If an operation is pending, handle it... + if ((finish_op + || VIsual_active) + && oap->op_type != OP_NOP) { + // Yank can be redone when 'y' is in 'cpoptions', but not when yanking + // for the clipboard. + const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank; + + // Avoid a problem with unwanted linebreaks in block mode + if (curwin->w_p_lbr) { + curwin->w_valid &= ~VALID_VIRTCOL; + } + curwin->w_p_lbr = false; + oap->is_VIsual = VIsual_active; + if (oap->motion_force == 'V') { + oap->motion_type = kMTLineWise; + } else if (oap->motion_force == 'v') { + // If the motion was linewise, "inclusive" will not have been set. + // Use "exclusive" to be consistent. Makes "dvj" work nice. + if (oap->motion_type == kMTLineWise) { + oap->inclusive = false; + } else if (oap->motion_type == kMTCharWise) { + // If the motion already was charwise, toggle "inclusive" + oap->inclusive = !oap->inclusive; + } + oap->motion_type = kMTCharWise; + } else if (oap->motion_force == Ctrl_V) { + // Change line- or charwise motion into Visual block mode. + if (!VIsual_active) { + VIsual_active = true; + VIsual = oap->start; + } + VIsual_mode = Ctrl_V; + VIsual_select = false; + VIsual_reselect = false; + } + + // Only redo yank when 'y' flag is in 'cpoptions'. + // Never redo "zf" (define fold). + if ((redo_yank || oap->op_type != OP_YANK) + && ((!VIsual_active || oap->motion_force) + // Also redo Operator-pending Visual mode mappings. + || ((cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) + && oap->op_type != OP_COLON)) + && cap->cmdchar != 'D' + && oap->op_type != OP_FOLD + && oap->op_type != OP_FOLDOPEN + && oap->op_type != OP_FOLDOPENREC + && oap->op_type != OP_FOLDCLOSE + && oap->op_type != OP_FOLDCLOSEREC + && oap->op_type != OP_FOLDDEL + && oap->op_type != OP_FOLDDELREC) { + prep_redo(oap->regname, cap->count0, + get_op_char(oap->op_type), get_extra_op_char(oap->op_type), + oap->motion_force, cap->cmdchar, cap->nchar); + if (cap->cmdchar == '/' || cap->cmdchar == '?') { // was a search + // If 'cpoptions' does not contain 'r', insert the search + // pattern to really repeat the same command. + if (vim_strchr(p_cpo, CPO_REDO) == NULL) { + AppendToRedobuffLit(cap->searchbuf, -1); + } + AppendToRedobuff(NL_STR); + } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { + // do_cmdline() has stored the first typed line in + // "repeat_cmdline". When several lines are typed repeating + // won't be possible. + if (repeat_cmdline == NULL) { + ResetRedobuff(); + } else { + AppendToRedobuffLit(repeat_cmdline, -1); + AppendToRedobuff(NL_STR); + XFREE_CLEAR(repeat_cmdline); + } + } + } + + if (redo_VIsual_busy) { + // Redo of an operation on a Visual area. Use the same size from + // redo_VIsual_line_count and redo_VIsual_vcol. + oap->start = curwin->w_cursor; + curwin->w_cursor.lnum += redo_VIsual_line_count - 1; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } + VIsual_mode = redo_VIsual_mode; + if (redo_VIsual_vcol == MAXCOL || VIsual_mode == 'v') { + if (VIsual_mode == 'v') { + if (redo_VIsual_line_count <= 1) { + validate_virtcol(); + curwin->w_curswant = + curwin->w_virtcol + redo_VIsual_vcol - 1; + } else { + curwin->w_curswant = redo_VIsual_vcol; + } + } else { + curwin->w_curswant = MAXCOL; + } + coladvance(curwin->w_curswant); + } + cap->count0 = redo_VIsual_count; + cap->count1 = (cap->count0 == 0 ? 1 : cap->count0); + } else if (VIsual_active) { + if (!gui_yank) { + // Save the current VIsual area for '< and '> marks, and "gv" + curbuf->b_visual.vi_start = VIsual; + curbuf->b_visual.vi_end = curwin->w_cursor; + curbuf->b_visual.vi_mode = VIsual_mode; + restore_visual_mode(); + curbuf->b_visual.vi_curswant = curwin->w_curswant; + curbuf->b_visual_mode_eval = VIsual_mode; + } + + // In Select mode, a linewise selection is operated upon like a + // charwise selection. + // Special case: gH<Del> deletes the last line. + if (VIsual_select && VIsual_mode == 'V' + && cap->oap->op_type != OP_DELETE) { + if (lt(VIsual, curwin->w_cursor)) { + VIsual.col = 0; + curwin->w_cursor.col = + (colnr_T)STRLEN(ml_get(curwin->w_cursor.lnum)); + } else { + curwin->w_cursor.col = 0; + VIsual.col = (colnr_T)STRLEN(ml_get(VIsual.lnum)); + } + VIsual_mode = 'v'; + } else if (VIsual_mode == 'v') { + // If 'selection' is "exclusive", backup one character for + // charwise selections. + include_line_break = + unadjust_for_sel(); + } + + oap->start = VIsual; + if (VIsual_mode == 'V') { + oap->start.col = 0; + oap->start.coladd = 0; + } + } + + // Set oap->start to the first position of the operated text, oap->end + // to the end of the operated text. w_cursor is equal to oap->start. + if (lt(oap->start, curwin->w_cursor)) { + // Include folded lines completely. + if (!VIsual_active) { + if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL)) { + oap->start.col = 0; + } + if ((curwin->w_cursor.col > 0 + || oap->inclusive + || oap->motion_type == kMTLineWise) + && hasFolding(curwin->w_cursor.lnum, NULL, + &curwin->w_cursor.lnum)) { + curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + } + } + oap->end = curwin->w_cursor; + curwin->w_cursor = oap->start; + + // w_virtcol may have been updated; if the cursor goes back to its + // previous position w_virtcol becomes invalid and isn't updated + // automatically. + curwin->w_valid &= ~VALID_VIRTCOL; + } else { + // Include folded lines completely. + if (!VIsual_active && oap->motion_type == kMTLineWise) { + if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, + NULL)) { + curwin->w_cursor.col = 0; + } + if (hasFolding(oap->start.lnum, NULL, &oap->start.lnum)) { + oap->start.col = (colnr_T)STRLEN(ml_get(oap->start.lnum)); + } + } + oap->end = oap->start; + oap->start = curwin->w_cursor; + } + + // Just in case lines were deleted that make the position invalid. + check_pos(curwin->w_buffer, &oap->end); + oap->line_count = oap->end.lnum - oap->start.lnum + 1; + + // Set "virtual_op" before resetting VIsual_active. + virtual_op = virtual_active(); + + if (VIsual_active || redo_VIsual_busy) { + get_op_vcol(oap, redo_VIsual_vcol, true); + + if (!redo_VIsual_busy && !gui_yank) { + // Prepare to reselect and redo Visual: this is based on the + // size of the Visual text + resel_VIsual_mode = VIsual_mode; + if (curwin->w_curswant == MAXCOL) { + resel_VIsual_vcol = MAXCOL; + } else { + if (VIsual_mode != Ctrl_V) { + getvvcol(curwin, &(oap->end), + NULL, NULL, &oap->end_vcol); + } + if (VIsual_mode == Ctrl_V || oap->line_count <= 1) { + if (VIsual_mode != Ctrl_V) { + getvvcol(curwin, &(oap->start), + &oap->start_vcol, NULL, NULL); + } + resel_VIsual_vcol = oap->end_vcol - oap->start_vcol + 1; + } else { + resel_VIsual_vcol = oap->end_vcol; + } + } + resel_VIsual_line_count = oap->line_count; + } + + // can't redo yank (unless 'y' is in 'cpoptions') and ":" + if ((redo_yank || oap->op_type != OP_YANK) + && oap->op_type != OP_COLON + && oap->op_type != OP_FOLD + && oap->op_type != OP_FOLDOPEN + && oap->op_type != OP_FOLDOPENREC + && oap->op_type != OP_FOLDCLOSE + && oap->op_type != OP_FOLDCLOSEREC + && oap->op_type != OP_FOLDDEL + && oap->op_type != OP_FOLDDELREC + && oap->motion_force == NUL) { + // Prepare for redoing. Only use the nchar field for "r", + // otherwise it might be the second char of the operator. + if (cap->cmdchar == 'g' && (cap->nchar == 'n' + || cap->nchar == 'N')) { + prep_redo(oap->regname, cap->count0, + get_op_char(oap->op_type), get_extra_op_char(oap->op_type), + oap->motion_force, cap->cmdchar, cap->nchar); + } else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND) { + int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL; + + // reverse what nv_replace() did + if (nchar == REPLACE_CR_NCHAR) { + nchar = CAR; + } else if (nchar == REPLACE_NL_NCHAR) { + nchar = NL; + } + prep_redo(oap->regname, 0L, NUL, 'v', get_op_char(oap->op_type), + get_extra_op_char(oap->op_type), nchar); + } + if (!redo_VIsual_busy) { + redo_VIsual_mode = resel_VIsual_mode; + redo_VIsual_vcol = resel_VIsual_vcol; + redo_VIsual_line_count = resel_VIsual_line_count; + redo_VIsual_count = cap->count0; + redo_VIsual_arg = cap->arg; + } + } + + // oap->inclusive defaults to true. + // If oap->end is on a NUL (empty line) oap->inclusive becomes + // false. This makes "d}P" and "v}dP" work the same. + if (oap->motion_force == NUL || oap->motion_type == kMTLineWise) { + oap->inclusive = true; + } + if (VIsual_mode == 'V') { + oap->motion_type = kMTLineWise; + } else if (VIsual_mode == 'v') { + oap->motion_type = kMTCharWise; + if (*ml_get_pos(&(oap->end)) == NUL + && (include_line_break || !virtual_op)) { + oap->inclusive = false; + // Try to include the newline, unless it's an operator + // that works on lines only. + if (*p_sel != 'o' + && !op_on_lines(oap->op_type) + && oap->end.lnum < curbuf->b_ml.ml_line_count) { + oap->end.lnum++; + oap->end.col = 0; + oap->end.coladd = 0; + oap->line_count++; + } + } + } + + redo_VIsual_busy = false; + + // Switch Visual off now, so screen updating does + // not show inverted text when the screen is redrawn. + // With OP_YANK and sometimes with OP_COLON and OP_FILTER there is + // no screen redraw, so it is done here to remove the inverted + // part. + if (!gui_yank) { + VIsual_active = false; + setmouse(); + mouse_dragging = 0; + may_clear_cmdline(); + if ((oap->op_type == OP_YANK + || oap->op_type == OP_COLON + || oap->op_type == OP_FUNCTION + || oap->op_type == OP_FILTER) + && oap->motion_force == NUL) { + // Make sure redrawing is correct. + curwin->w_p_lbr = lbr_saved; + redraw_curbuf_later(INVERTED); + } + } + } + + // Include the trailing byte of a multi-byte char. + if (oap->inclusive) { + const int l = utfc_ptr2len(ml_get_pos(&oap->end)); + if (l > 1) { + oap->end.col += l - 1; + } + } + curwin->w_set_curswant = true; + + // oap->empty is set when start and end are the same. The inclusive + // flag affects this too, unless yanking and the end is on a NUL. + oap->empty = (oap->motion_type != kMTLineWise + && (!oap->inclusive + || (oap->op_type == OP_YANK + && gchar_pos(&oap->end) == NUL)) + && equalpos(oap->start, oap->end) + && !(virtual_op && oap->start.coladd != oap->end.coladd)); + // For delete, change and yank, it's an error to operate on an + // empty region, when 'E' included in 'cpoptions' (Vi compatible). + empty_region_error = (oap->empty + && vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL); + + // Force a redraw when operating on an empty Visual region, when + // 'modifiable is off or creating a fold. + if (oap->is_VIsual && (oap->empty || !MODIFIABLE(curbuf) + || oap->op_type == OP_FOLD)) { + curwin->w_p_lbr = lbr_saved; + redraw_curbuf_later(INVERTED); + } + + // If the end of an operator is in column one while oap->motion_type + // is kMTCharWise and oap->inclusive is false, we put op_end after the last + // character in the previous line. If op_start is on or before the + // first non-blank in the line, the operator becomes linewise + // (strange, but that's the way vi does it). + if (oap->motion_type == kMTCharWise + && oap->inclusive == false + && !(cap->retval & CA_NO_ADJ_OP_END) + && oap->end.col == 0 + && (!oap->is_VIsual || *p_sel == 'o') + && oap->line_count > 1) { + oap->end_adjusted = true; // remember that we did this + oap->line_count--; + oap->end.lnum--; + if (inindent(0)) { + oap->motion_type = kMTLineWise; + } else { + oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); + if (oap->end.col) { + oap->end.col--; + oap->inclusive = true; + } + } + } else { + oap->end_adjusted = false; + } + + switch (oap->op_type) { + case OP_LSHIFT: + case OP_RSHIFT: + op_shift(oap, true, + oap->is_VIsual ? (int)cap->count1 : + 1); + auto_format(false, true); + break; + + case OP_JOIN_NS: + case OP_JOIN: + if (oap->line_count < 2) { + oap->line_count = 2; + } + if (curwin->w_cursor.lnum + oap->line_count - 1 > + curbuf->b_ml.ml_line_count) { + beep_flush(); + } else { + do_join((size_t)oap->line_count, oap->op_type == OP_JOIN, + true, true, true); + auto_format(false, true); + } + break; + + case OP_DELETE: + VIsual_reselect = false; // don't reselect now + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + (void)op_delete(oap); + // save cursor line for undo if it wasn't saved yet + if (oap->motion_type == kMTLineWise + && has_format_option(FO_AUTO) + && u_save_cursor() == OK) { + auto_format(false, true); + } + } + break; + + case OP_YANK: + if (empty_region_error) { + if (!gui_yank) { + vim_beep(BO_OPER); + CancelRedo(); + } + } else { + curwin->w_p_lbr = lbr_saved; + oap->excl_tr_ws = cap->cmdchar == 'z'; + (void)op_yank(oap, !gui_yank); + } + check_cursor_col(); + break; + + case OP_CHANGE: + VIsual_reselect = false; // don't reselect now + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + // This is a new edit command, not a restart. Need to + // remember it to make 'insertmode' work with mappings for + // Visual mode. But do this only once and not when typed and + // 'insertmode' isn't set. + if (p_im || !KeyTyped) { + restart_edit_save = restart_edit; + } else { + restart_edit_save = 0; + } + restart_edit = 0; + + // Restore linebreak, so that when the user edits it looks as before. + curwin->w_p_lbr = lbr_saved; + + // Reset finish_op now, don't want it set inside edit(). + finish_op = false; + if (op_change(oap)) { // will call edit() + cap->retval |= CA_COMMAND_BUSY; + } + if (restart_edit == 0) { + restart_edit = restart_edit_save; + } + } + break; + + case OP_FILTER: + if (vim_strchr(p_cpo, CPO_FILTER) != NULL) { + AppendToRedobuff("!\r"); // Use any last used !cmd. + } else { + bangredo = true; // do_bang() will put cmd in redo buffer. + } + FALLTHROUGH; + + case OP_INDENT: + case OP_COLON: + + // If 'equalprg' is empty, do the indenting internally. + if (oap->op_type == OP_INDENT && *get_equalprg() == NUL) { + if (curbuf->b_p_lisp) { + op_reindent(oap, get_lisp_indent); + break; + } + op_reindent(oap, + *curbuf->b_p_inde != NUL ? get_expr_indent : + get_c_indent); + break; + } + + op_colon(oap); + break; + + case OP_TILDE: + case OP_UPPER: + case OP_LOWER: + case OP_ROT13: + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + op_tilde(oap); + } + check_cursor_col(); + break; + + case OP_FORMAT: + if (*curbuf->b_p_fex != NUL) { + op_formatexpr(oap); // use expression + } else { + if (*p_fp != NUL || *curbuf->b_p_fp != NUL) { + op_colon(oap); // use external command + } else { + op_format(oap, false); // use internal function + } + } + break; + + case OP_FORMAT2: + op_format(oap, true); // use internal function + break; + + case OP_FUNCTION: + // Restore linebreak, so that when the user edits it looks as + // before. + curwin->w_p_lbr = lbr_saved; + op_function(oap); // call 'operatorfunc' + break; + + case OP_INSERT: + case OP_APPEND: + VIsual_reselect = false; // don't reselect now + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + // This is a new edit command, not a restart. Need to + // remember it to make 'insertmode' work with mappings for + // Visual mode. But do this only once. + restart_edit_save = restart_edit; + restart_edit = 0; + + // Restore linebreak, so that when the user edits it looks as before. + curwin->w_p_lbr = lbr_saved; + + op_insert(oap, cap->count1); + + // Reset linebreak, so that formatting works correctly. + curwin->w_p_lbr = false; + + // TODO(brammool): when inserting in several lines, should format all + // the lines. + auto_format(false, true); + + if (restart_edit == 0) { + restart_edit = restart_edit_save; + } else { + cap->retval |= CA_COMMAND_BUSY; + } + } + break; + + case OP_REPLACE: + VIsual_reselect = false; // don't reselect now + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + // Restore linebreak, so that when the user edits it looks as before. + curwin->w_p_lbr = lbr_saved; + + op_replace(oap, cap->nchar); + } + break; + + case OP_FOLD: + VIsual_reselect = false; // don't reselect now + 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, 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: + case OP_FOLDDELREC: + VIsual_reselect = false; // don't reselect now + deleteFold(curwin, oap->start.lnum, oap->end.lnum, + oap->op_type == OP_FOLDDELREC, oap->is_VIsual); + break; + + case OP_NR_ADD: + case OP_NR_SUB: + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + VIsual_active = true; + curwin->w_p_lbr = lbr_saved; + op_addsub(oap, cap->count1, redo_VIsual_arg); + VIsual_active = false; + } + check_cursor_col(); + break; + default: + clearopbeep(oap); + } + virtual_op = kNone; + if (!gui_yank) { + // if 'sol' not set, go back to old column for some commands + if (!p_sol && oap->motion_type == kMTLineWise && !oap->end_adjusted + && (oap->op_type == OP_LSHIFT || oap->op_type == OP_RSHIFT + || oap->op_type == OP_DELETE)) { + curwin->w_p_lbr = false; + coladvance(curwin->w_curswant = old_col); + } + } else { + curwin->w_cursor = old_cursor; + } + clearop(oap); + motion_force = NUL; + } + curwin->w_p_lbr = lbr_saved; +} + /// Check if the default register (used in an unnamed paste) should be a /// clipboard register. This happens when `clipboard=unnamed[plus]` is set /// and a provider is available. @@ -6388,7 +7378,6 @@ const yankreg_T *op_reg_get(const char name) /// /// @return true on success, false on failure. bool op_reg_set_previous(const char name) - FUNC_ATTR_WARN_UNUSED_RESULT { int i = op_reg_index(name); if (i == -1) { diff --git a/src/nvim/option.c b/src/nvim/option.c index 44b7d98e88..f6037fc20a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -47,6 +47,7 @@ #include "nvim/getchar.h" #include "nvim/hardcopy.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/indent_c.h" #include "nvim/keymap.h" #include "nvim/macros.h" @@ -55,7 +56,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -133,6 +133,7 @@ static int p_cin; static char_u *p_cink; static char_u *p_cino; static char_u *p_cinw; +static char_u *p_cinsd; static char_u *p_com; static char_u *p_cms; static char_u *p_cpt; @@ -262,6 +263,7 @@ typedef struct vimoption { #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ "i:IncSearch,l:Search,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow,N:CursorLineNr," \ + "G:CursorLineSign,O:CursorLineFold" \ "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \ "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \ "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar," \ @@ -281,7 +283,7 @@ typedef struct vimoption { # include "options.generated.h" #endif -#define PARAM_COUNT ARRAY_SIZE(options) +#define OPTION_COUNT ARRAY_SIZE(options) static char *(p_ambw_values[]) = { "single", "double", NULL }; static char *(p_bg_values[]) = { "light", "dark", NULL }; @@ -931,6 +933,21 @@ void set_title_defaults(void) } } +void ex_set(exarg_T *eap) +{ + int flags = 0; + + if (eap->cmdidx == CMD_setlocal) { + flags = OPT_LOCAL; + } else if (eap->cmdidx == CMD_setglobal) { + flags = OPT_GLOBAL; + } + if (eap->forceit) { + flags |= OPT_ONECOLUMN; + } + (void)do_set(eap->arg, flags); +} + /// Parse 'arg' for option settings. /// /// 'arg' may be IObuff, but only when no errors can be present and option @@ -1301,7 +1318,11 @@ int do_set(char_u *arg, int opt_flags) char_u *oldval = NULL; // previous value if *varp char_u *newval; char_u *origval = NULL; + char_u *origval_l = NULL; + char_u *origval_g = NULL; char *saved_origval = NULL; + char *saved_origval_l = NULL; + char *saved_origval_g = NULL; char *saved_newval = NULL; unsigned newlen; int comma; @@ -1319,10 +1340,21 @@ int do_set(char_u *arg, int opt_flags) // new value is valid. oldval = *(char_u **)varp; + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + origval_l = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); + origval_g = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + + // A global-local string option might have an empty + // option as value to indicate that the global + // value should be used. + if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == empty_option) { + origval_l = origval_g; + } + } + // When setting the local value of a global // option, the old value may be the global value. - if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags - & OPT_LOCAL)) { + if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) { origval = *(char_u **)get_varp(&options[opt_idx]); } else { origval = oldval; @@ -1330,7 +1362,7 @@ int do_set(char_u *arg, int opt_flags) if (nextchar == '&') { // set to default val newval = options[opt_idx].def_val; - // expand environment variables and ~ (since the + // expand environment variables and ~ since the // default value was already expanded, only // required when an environment variable was set // later @@ -1388,6 +1420,12 @@ int do_set(char_u *arg, int opt_flags) if (origval == oldval) { origval = *(char_u **)varp; } + if (origval_l == oldval) { + origval_l = *(char_u **)varp; + } + if (origval_g == oldval) { + origval_g = *(char_u **)varp; + } oldval = *(char_u **)varp; } /* @@ -1596,6 +1634,8 @@ int do_set(char_u *arg, int opt_flags) // origval may be freed by // did_set_string_option(), make a copy. saved_origval = (origval != NULL) ? xstrdup((char *)origval) : 0; + saved_origval_l = (origval_l != NULL) ? xstrdup((char *)origval_l) : 0; + saved_origval_g = (origval_g != NULL) ? xstrdup((char *)origval_g) : 0; // newval (and varp) may become invalid if the // buffer is closed by autocommands. @@ -1630,8 +1670,8 @@ int do_set(char_u *arg, int opt_flags) if (errmsg == NULL) { if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_origval, - saved_newval); + trigger_optionsset_string(opt_idx, opt_flags, saved_origval, saved_origval_l, + saved_origval_g, saved_newval); } if (options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -1639,6 +1679,8 @@ int do_set(char_u *arg, int opt_flags) } } xfree(saved_origval); + xfree(saved_origval_l); + xfree(saved_origval_g); xfree(saved_newval); // If error detected, print the error message. @@ -1741,7 +1783,7 @@ static char *illegal_char(char *errbuf, size_t errbuflen, int c) if (errbuf == NULL) { return ""; } - vim_snprintf((char *)errbuf, errbuflen, _("E539: Illegal character <%s>"), + vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"), (char *)transchar(c)); return errbuf; } @@ -1944,10 +1986,9 @@ static void didset_options(void) (void)did_set_spell_option(true); // set cedit_key (void)check_cedit(); - briopt_check(curwin); // initialize the table for 'breakat'. fill_breakat_flags(); - fill_culopt_flags(NULL, curwin); + didset_window_options(curwin); } // More side effects of setting options. @@ -1968,9 +2009,9 @@ static void didset_options2(void) // Parse default for 'wildmode'. check_opt_wim(); xfree(curbuf->b_p_vsts_array); - tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array); + (void)tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array); xfree(curbuf->b_p_vts_array); - tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array); + (void)tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array); } /// Check for string options that are NULL (normally only termcap options). @@ -2020,6 +2061,7 @@ void check_buf_options(buf_T *buf) parse_cino(buf); check_string_option(&buf->b_p_ft); check_string_option(&buf->b_p_cinw); + check_string_option(&buf->b_p_cinsd); check_string_option(&buf->b_p_cpt); check_string_option(&buf->b_p_cfu); check_string_option(&buf->b_p_ofu); @@ -2233,13 +2275,23 @@ static char *set_string_option(const int opt_idx, const char *const value, const ? OPT_GLOBAL : OPT_LOCAL) : opt_flags)); char *const oldval = *varp; + char *oldval_l = NULL; + char *oldval_g = NULL; + + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + oldval_l = *(char **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); + oldval_g = *(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + } + *varp = s; char *const saved_oldval = xstrdup(oldval); + char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0; + char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0; char *const saved_newval = xstrdup(s); int value_checked = false; - char *const r = did_set_string_option(opt_idx, (char_u **)varp, (int)true, + char *const r = did_set_string_option(opt_idx, (char_u **)varp, true, (char_u *)oldval, NULL, 0, opt_flags, &value_checked); if (r == NULL) { @@ -2249,7 +2301,8 @@ static char *set_string_option(const int opt_idx, const char *const value, const // call autocommand after handling side effects if (r == NULL) { if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_newval); + trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, + saved_newval); } if (options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -2257,6 +2310,8 @@ static char *set_string_option(const int opt_idx, const char *const value, const } } xfree(saved_oldval); + xfree(saved_oldval_l); + xfree(saved_oldval_g); xfree(saved_newval); return r; @@ -2650,24 +2705,38 @@ ambw_end: } s = skip_to_option_part(s); } - } else if (varp == &p_lcs) { // 'listchars' + } else if (varp == &p_lcs) { // global 'listchars' errmsg = set_chars_option(curwin, varp, false); - if (!errmsg) { + if (errmsg == NULL) { + // The current window is set to use the global 'listchars' value. + // So clear the window-local value. + if (!(opt_flags & OPT_GLOBAL)) { + clear_string_option(&curwin->w_p_lcs); + } FOR_ALL_TAB_WINDOWS(tp, wp) { - set_chars_option(wp, &wp->w_p_lcs, true); + // If no error was returned above, we don't expect an error + // here, so ignore the return value. + (void)set_chars_option(wp, &wp->w_p_lcs, true); } + redraw_all_later(NOT_VALID); } - redraw_all_later(NOT_VALID); } else if (varp == &curwin->w_p_lcs) { // local 'listchars' errmsg = set_chars_option(curwin, varp, true); - } else if (varp == &p_fcs) { // 'fillchars' + } else if (varp == &p_fcs) { // global 'fillchars' errmsg = set_chars_option(curwin, varp, false); - if (!errmsg) { + if (errmsg == NULL) { + // The current window is set to use the global 'fillchars' value. + // So clear the window-local value. + if (!(opt_flags & OPT_GLOBAL)) { + clear_string_option(&curwin->w_p_fcs); + } FOR_ALL_TAB_WINDOWS(tp, wp) { - set_chars_option(wp, &wp->w_p_fcs, true); + // If no error was returned above, we don't expect an error + // here, so ignore the return value. + (void)set_chars_option(wp, &wp->w_p_fcs, true); } + redraw_all_later(NOT_VALID); } - redraw_all_later(NOT_VALID); } else if (varp == &curwin->w_p_fcs) { // local 'fillchars' errmsg = set_chars_option(curwin, varp, true); } else if (varp == &p_cedit) { // 'cedit' @@ -2710,7 +2779,7 @@ ambw_end: if (!ascii_isdigit(*(s - 1))) { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E526: Missing number after <%s>"), transchar_byte(*(s - 1))); errmsg = errbuf; @@ -2844,7 +2913,7 @@ ambw_end: || check_opt_strings(curbuf->b_p_bt, p_buftype_values, false) != OK) { errmsg = e_invarg; } else { - if (curwin->w_status_height) { + if (curwin->w_status_height || global_stl_height()) { curwin->w_redr_status = true; redraw_later(curwin, VALID); } @@ -2901,7 +2970,7 @@ ambw_end: } } else { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E535: Illegal character after <%c>"), *--s); errmsg = errbuf; @@ -2939,7 +3008,7 @@ ambw_end: } } 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) { + if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { errmsg = e_invarg; } } else if (varp == &p_pt) { @@ -3032,14 +3101,27 @@ ambw_end: if (foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); } - } else if (varp == &p_ve) { // 'virtualedit' - if (opt_strings_flags(p_ve, p_ve_values, &ve_flags, true) != OK) { - errmsg = e_invarg; - } else if (STRCMP(p_ve, oldval) != 0) { - // Recompute cursor position in case the new 've' setting - // changes something. - validate_virtcol(); - coladvance(curwin->w_virtcol); + } else if (gvarp == &p_ve) { // 'virtualedit' + char_u *ve = p_ve; + unsigned int *flags = &ve_flags; + + if (opt_flags & OPT_LOCAL) { + ve = curwin->w_p_ve; + flags = &curwin->w_ve_flags; + } + + if ((opt_flags & OPT_LOCAL) && *ve == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else { + if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { + errmsg = e_invarg; + } else if (STRCMP(p_ve, oldval) != 0) { + // Recompute cursor position in case the new 've' setting + // changes something. + validate_virtcol(); + coladvance(curwin->w_virtcol); + } } } else if (varp == &p_csqf) { if (p_csqf != NULL) { @@ -3098,10 +3180,7 @@ ambw_end: char_u *cp; if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - if (curbuf->b_p_vsts_array) { - xfree(curbuf->b_p_vsts_array); - curbuf->b_p_vsts_array = 0; - } + XFREE_CLEAR(curbuf->b_p_vsts_array); } else { for (cp = *varp; *cp; cp++) { if (ascii_isdigit(*cp)) { @@ -3126,10 +3205,7 @@ ambw_end: char_u *cp; if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - if (curbuf->b_p_vts_array) { - xfree(curbuf->b_p_vts_array); - curbuf->b_p_vts_array = NULL; - } + XFREE_CLEAR(curbuf->b_p_vts_array); } else { for (cp = *varp; *cp; cp++) { if (ascii_isdigit(*cp)) { @@ -3318,6 +3394,9 @@ static int int_cmp(const void *a, const void *b) /// @return OK when the value is valid, FAIL otherwise int check_signcolumn(char_u *val) { + if (*val == NUL) { + return FAIL; + } // check for basic match if (check_opt_strings(val, p_scl_values, false) == OK) { return OK; @@ -3477,16 +3556,22 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) struct chars_tab *tab; struct chars_tab fcs_tab[] = { - { &wp->w_p_fcs_chars.stl, "stl", ' ' }, - { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, - { &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │ - { &wp->w_p_fcs_chars.fold, "fold", 183 }, // · - { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, - { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, - { &wp->w_p_fcs_chars.foldsep, "foldsep", 9474 }, // │ - { &wp->w_p_fcs_chars.diff, "diff", '-' }, - { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, - { &wp->w_p_fcs_chars.eob, "eob", '~' }, + { &wp->w_p_fcs_chars.stl, "stl", ' ' }, + { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, + { &wp->w_p_fcs_chars.horiz, "horiz", 9472 }, // ─ + { &wp->w_p_fcs_chars.horizup, "horizup", 9524 }, // â”´ + { &wp->w_p_fcs_chars.horizdown, "horizdown", 9516 }, // ┬ + { &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │ + { &wp->w_p_fcs_chars.vertleft, "vertleft", 9508 }, // ┤ + { &wp->w_p_fcs_chars.vertright, "vertright", 9500 }, // ├ + { &wp->w_p_fcs_chars.verthoriz, "verthoriz", 9532 }, // ┼ + { &wp->w_p_fcs_chars.fold, "fold", 183 }, // · + { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, + { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, + { &wp->w_p_fcs_chars.foldsep, "foldsep", 9474 }, // │ + { &wp->w_p_fcs_chars.diff, "diff", '-' }, + { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, + { &wp->w_p_fcs_chars.eob, "eob", '~' }, }; struct chars_tab lcs_tab[] = { { &wp->w_p_lcs_chars.eol, "eol", NUL }, @@ -3513,15 +3598,17 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) varp = &p_fcs; } if (*p_ambw == 'd') { - // XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is - // forbidden (TUI limitation?). Set old defaults. - fcs_tab[2].def = '|'; - fcs_tab[6].def = '|'; - fcs_tab[3].def = '-'; - } else { - fcs_tab[2].def = 9474; // │ - fcs_tab[6].def = 9474; // │ - fcs_tab[3].def = 183; // · + // XXX: If ambiwidth=double then some characters take 2 columns, + // which is forbidden (TUI limitation?). Set old defaults. + fcs_tab[2].def = '-'; + fcs_tab[3].def = '-'; + fcs_tab[4].def = '-'; + fcs_tab[5].def = '|'; + fcs_tab[6].def = '|'; + fcs_tab[7].def = '|'; + fcs_tab[8].def = '+'; + fcs_tab[9].def = '-'; + fcs_tab[12].def = '|'; } } @@ -3558,7 +3645,7 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) c2 = c3 = 0; s = p + len + 1; c1 = get_encoded_char_adv(&s); - if (c1 == 0 || utf_char2cells(c1) > 1) { + if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { @@ -3566,12 +3653,12 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) return e_invarg; } c2 = get_encoded_char_adv(&s); - if (c2 == 0 || utf_char2cells(c2) > 1) { + if (c2 == 0 || char2cells(c2) > 1) { return e_invarg; } if (!(*s == ',' || *s == NUL)) { c3 = get_encoded_char_adv(&s); - if (c3 == 0 || utf_char2cells(c3) > 1) { + if (c3 == 0 || char2cells(c3) > 1) { return e_invarg; } } @@ -3605,7 +3692,7 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) multispace_len = 0; while (*s != NUL && *s != ',') { c1 = get_encoded_char_adv(&s); - if (c1 == 0 || utf_char2cells(c1) > 1) { + if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } multispace_len++; @@ -3769,13 +3856,13 @@ static bool parse_winhl_opt(win_T *wp) size_t nlen = (size_t)(colon-p); char *hi = colon+1; char *commap = xstrchrnul(hi, ','); - int len = (int)(commap-hi); + size_t len = (size_t)(commap-hi); int hl_id = len ? syn_check_group(hi, len) : -1; if (strncmp("Normal", p, nlen) == 0) { w_hl_id_normal = hl_id; } else { - for (hlf = 0; hlf < (int)HLF_COUNT; hlf++) { + for (hlf = 0; hlf < HLF_COUNT; hlf++) { if (strlen(hlf_names[hlf]) == nlen && strncmp(hlf_names[hlf], p, nlen) == 0) { w_hl_ids[hlf] = hl_id; @@ -3802,6 +3889,7 @@ static void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) { int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; int indir = (int)options[opt_idx].indir; + nlua_set_sctx(&script_ctx); const LastSet last_set = { .script_ctx = { script_ctx.sc_sid, @@ -3837,6 +3925,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va const int opt_flags) { int old_value = *(int *)varp; + int old_global_value = 0; // Disallow changing some options from secure mode if ((secure || sandbox != 0) @@ -3844,6 +3933,13 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va return (char *)e_secure; } + // Save the global value before changing anything. This is needed as for + // a global-only option setting the "local value" in fact sets the global + // value (since there is only one value). + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + old_global_value = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + } + *(int *)varp = value; // set the new value // Remember where the option was set. set_option_sctx_idx(opt_idx, opt_flags, current_sctx); @@ -3868,11 +3964,8 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } else if ((int *)varp == &p_lnr) { // 'langnoremap' -> !'langremap' p_lrm = !p_lnr; - } else if ((int *)varp == &curwin->w_p_cul && !value && old_value) { - // 'cursorline' - reset_cursorline(); - // 'undofile' } else if ((int *)varp == &curbuf->b_p_udf || (int *)varp == &p_udf) { + // 'undofile' // Only take action when the option was set. When reset we do not // delete the undo file, the option may be set again without making // any changes in between. @@ -4069,7 +4162,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } } - // Arabic requires a utf-8 encoding, inform the user if its not + // Arabic requires a utf-8 encoding, inform the user if it's not // set. if (STRCMP(p_enc, "utf-8") != 0) { static char *w_arabic = N_("W17: Arabic requires UTF-8, do ':set encoding=utf-8'"); @@ -4120,20 +4213,35 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va // Don't do this while starting up or recursively. if (!starting && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { char buf_old[2]; + char buf_old_global[2]; char buf_new[2]; char buf_type[7]; - vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%d", - old_value ? true: false); - vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%d", - value ? true: false); + vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%d", old_value ? true : false); + vim_snprintf(buf_old_global, ARRAY_SIZE(buf_old_global), "%d", old_global_value ? true : false); + vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%d", value ? true : false); vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", (opt_flags & OPT_LOCAL) ? "local" : "global"); set_vim_var_string(VV_OPTION_NEW, buf_new, -1); set_vim_var_string(VV_OPTION_OLD, buf_old, -1); set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, - NULL, false, NULL); + if (opt_flags & OPT_LOCAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + if (opt_flags & OPT_GLOBAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); + } + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_vim_var_string(VV_OPTION_COMMAND, "set", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); + } + if (opt_flags & OPT_MODELINE) { + set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + apply_autocmds(EVENT_OPTIONSET, (char_u *)options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); } @@ -4167,7 +4275,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, { char *errmsg = NULL; long old_value = *(long *)varp; - long old_Rows = Rows; // remember old Rows + long old_global_value = 0; // only used when setting a local and global option + long old_Rows = Rows; // remember old Rows long *pp = (long *)varp; // Disallow changing some options from secure mode. @@ -4176,6 +4285,13 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, return e_secure; } + // Save the global value before changing anything. This is needed as for + // a global-only option setting the "local value" in fact sets the global + // value (since there is only one value). + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + old_global_value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + } + // Many number options assume their value is in the signed int range. if (value < INT_MIN || value > INT_MAX) { return e_invarg; @@ -4235,6 +4351,12 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } else if (value > 10000) { errmsg = e_invarg; } + } else if (pp == &p_pyx) { + if (value == 0) { + value = 3; + } else if (value != 3) { + errmsg = e_invarg; + } } else if (pp == &p_re) { if (value < 0 || value > 2) { errmsg = e_invarg; @@ -4300,6 +4422,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { if (value < 1) { errmsg = e_positive; + } else if (value > TABSTOP_MAX) { + errmsg = e_invarg; } } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { if (value < 0) { @@ -4313,7 +4437,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // Don't change the value and return early if validation failed. if (errmsg != NULL) { - return (char *)errmsg; + return errmsg; } *pp = value; @@ -4359,6 +4483,20 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // 'winminwidth' win_setminwidth(); } else if (pp == &p_ls) { + // When switching to global statusline, decrease topframe height + // Also clear the cmdline to remove the ruler if there is one + if (value == 3 && old_value != 3) { + frame_new_height(topframe, topframe->fr_height - STATUS_HEIGHT, false, false); + (void)win_comp_pos(); + clear_cmdline = true; + } + // When switching from global statusline, increase height of topframe by STATUS_HEIGHT + // in order to to re-add the space that was previously taken by the global statusline + if (old_value == 3 && value != 3) { + frame_new_height(topframe, topframe->fr_height + STATUS_HEIGHT, false, false); + (void)win_comp_pos(); + } + last_status(false); // (re)set last window status line. } else if (pp == &p_stal) { // (re)set tab page line @@ -4409,10 +4547,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, if (pum_drawn()) { pum_redraw(); } - } else if (pp == &p_pyx) { - if (p_pyx != 0 && p_pyx != 2 && p_pyx != 3) { - errmsg = e_invarg; - } } else if (pp == &p_ul || pp == &curbuf->b_p_ul) { // sync undo before 'undolevels' changes // use the old value, otherwise u_sync() may not work properly @@ -4441,7 +4575,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // Check the (new) bounds for Rows and Columns here. if (p_lines < min_rows() && full_screen) { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E593: Need at least %d lines"), min_rows()); errmsg = errbuf; } @@ -4449,7 +4583,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } if (p_columns < MIN_COLUMNS && full_screen) { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E594: Need at least %d columns"), MIN_COLUMNS); errmsg = errbuf; } @@ -4520,19 +4654,36 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // Don't do this while starting up, failure or recursively. if (!starting && errmsg == NULL && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { char buf_old[NUMBUFLEN]; + char buf_old_global[NUMBUFLEN]; char buf_new[NUMBUFLEN]; char buf_type[7]; vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%ld", old_value); + vim_snprintf(buf_old_global, ARRAY_SIZE(buf_old_global), "%ld", old_global_value); vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%ld", value); vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", (opt_flags & OPT_LOCAL) ? "local" : "global"); set_vim_var_string(VV_OPTION_NEW, buf_new, -1); set_vim_var_string(VV_OPTION_OLD, buf_old, -1); set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, - NULL, false, NULL); + if (opt_flags & OPT_LOCAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + if (opt_flags & OPT_GLOBAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); + } + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_vim_var_string(VV_OPTION_COMMAND, "set", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); + } + if (opt_flags & OPT_MODELINE) { + set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + apply_autocmds(EVENT_OPTIONSET, (char_u *)options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); } @@ -4548,10 +4699,18 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } check_redraw(options[opt_idx].flags); - return (char *)errmsg; + return errmsg; } -static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *newval) +/// Trigger the OptionSet autocommand. +/// "opt_idx" is the index of the option being set. +/// "opt_flags" can be OPT_LOCAL etc. +/// "oldval" the old value +/// "oldval_l" the old local value (only non-NULL if global and local value are set) +/// "oldval_g" the old global value (only non-NULL if global and local value are set) +/// "newval" the new value +static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, + char *oldval_g, char *newval) { // Don't do this recursively. if (oldval != NULL @@ -4564,8 +4723,24 @@ static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, set_vim_var_string(VV_OPTION_OLD, oldval, -1); set_vim_var_string(VV_OPTION_NEW, newval, -1); set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, NULL, false, NULL); + if (opt_flags & OPT_LOCAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); + } + if (opt_flags & OPT_GLOBAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1); + } + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_vim_var_string(VV_OPTION_COMMAND, "set", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1); + } + if (opt_flags & OPT_MODELINE) { + set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); + } + apply_autocmds(EVENT_OPTIONSET, (char_u *)options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); } } @@ -4757,7 +4932,8 @@ static int findoption(const char *const arg) /// @param stringval NULL when only checking existence /// /// @returns: -/// Number or Toggle option: 1, *numval gets value. +/// Toggle option: 2, *numval gets value. +/// Number option: 1, *numval gets value. /// String option: 0, *stringval gets allocated string. /// Hidden Number or Toggle option: -1. /// hidden String option: -2. @@ -4790,16 +4966,18 @@ int get_option_value(const char *name, long *numval, char_u **stringval, int opt } if (options[opt_idx].flags & P_NUM) { *numval = *(long *)varp; + return 1; + } + + // Special case: 'modified' is b_changed, but we also want to consider + // it set when 'ff' or 'fenc' changed. + if ((int *)varp == &curbuf->b_changed) { + *numval = curbufIsChanged(); } else { - // Special case: 'modified' is b_changed, but we also want to consider - // it set when 'ff' or 'fenc' changed. - if ((int *)varp == &curbuf->b_changed) { - *numval = curbufIsChanged(); - } else { - *numval = (long)*(int *)varp; // NOLINT(whitespace/cast) - } + *numval = (long)*(int *)varp; // NOLINT(whitespace/cast) } - return 1; + + return 2; } // Returns the option attributes and its value. Unlike the above function it @@ -4895,7 +5073,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o // only getting a pointer, no need to use aucmd_prepbuf() curbuf = (buf_T *)from; curwin->w_buffer = curbuf; - varp = get_varp(p); + varp = get_varp_scope(p, OPT_LOCAL); curbuf = save_curbuf; curwin->w_buffer = curbuf; } @@ -4903,7 +5081,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o win_T *save_curwin = curwin; curwin = (win_T *)from; curbuf = curwin->w_buffer; - varp = get_varp(p); + varp = get_varp_scope(p, OPT_LOCAL); curwin = save_curwin; curbuf = curwin->w_buffer; } @@ -4932,6 +5110,9 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o /// @param[in] number New value for the number or boolean option. /// @param[in] string New value for string option. /// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both). +/// If OPT_CLEAR is set, the value of the option +/// is cleared (the exact semantics of this depend +/// on the option). /// /// @return NULL on success, error message on error. char *set_option_value(const char *const name, const long number, const char *const string, @@ -4957,34 +5138,47 @@ char *set_option_value(const char *const name, const long number, const char *co } if (flags & P_STRING) { const char *s = string; - if (s == NULL) { + if (s == NULL || opt_flags & OPT_CLEAR) { s = ""; } return set_string_option(opt_idx, s, opt_flags); - } else { - varp = get_varp_scope(&(options[opt_idx]), opt_flags); - if (varp != NULL) { // hidden option is not changed - if (number == 0 && string != NULL) { - int idx; - - // Either we are given a string or we are setting option - // to zero. - for (idx = 0; string[idx] == '0'; idx++) {} - if (string[idx] != NUL || idx == 0) { - // There's another character after zeros or the string - // is empty. In both cases, we are trying to set a - // num option using a string. - semsg(_("E521: Number required: &%s = '%s'"), - name, string); - return NULL; // do nothing as we hit an error - } + } + + varp = get_varp_scope(&(options[opt_idx]), opt_flags); + if (varp != NULL) { // hidden option is not changed + if (number == 0 && string != NULL) { + int idx; + + // Either we are given a string or we are setting option + // to zero. + for (idx = 0; string[idx] == '0'; idx++) {} + if (string[idx] != NUL || idx == 0) { + // There's another character after zeros or the string + // is empty. In both cases, we are trying to set a + // num option using a string. + semsg(_("E521: Number required: &%s = '%s'"), + name, string); + return NULL; // do nothing as we hit an error } - if (flags & P_NUM) { - return set_num_option(opt_idx, varp, number, NULL, 0, opt_flags); + } + long numval = number; + if (opt_flags & OPT_CLEAR) { + if ((int *)varp == &curbuf->b_p_ar) { + numval = -1; + } else if ((long *)varp == &curbuf->b_p_ul) { + numval = NO_LOCAL_UNDOLEVEL; + } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) { + numval = -1; } else { - return set_bool_option(opt_idx, varp, (int)number, opt_flags); + char *s = NULL; + (void)get_option_value(name, &numval, (char_u **)&s, OPT_GLOBAL); } } + if (flags & P_NUM) { + return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags); + } else { + return set_bool_option(opt_idx, varp, (int)numval, opt_flags); + } } } return NULL; @@ -5038,7 +5232,7 @@ static void showoptions(int all, int opt_flags) #define INC 20 #define GAP 3 - vimoption_T **items = xmalloc(sizeof(vimoption_T *) * PARAM_COUNT); + vimoption_T **items = xmalloc(sizeof(vimoption_T *) * OPTION_COUNT); // Highlight title if (opt_flags & OPT_GLOBAL) { @@ -5052,6 +5246,7 @@ static void showoptions(int all, int opt_flags) // Do the loop two times: // 1. display the short items // 2. display the long items (only strings and numbers) + // When "opt_flags" has OPT_ONECOLUMN do everything in run 2. for (run = 1; run <= 2 && !got_int; run++) { // collect the items in items[] item_count = 0; @@ -5062,7 +5257,7 @@ static void showoptions(int all, int opt_flags) } varp = NULL; - if (opt_flags != 0) { + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) { if (p->indir != PV_NONE) { varp = get_varp_scope(p, opt_flags); } @@ -5071,8 +5266,10 @@ static void showoptions(int all, int opt_flags) } if (varp != NULL && (all == 1 || (all == 0 && !optval_default(p, varp)))) { - if (p->flags & P_BOOL) { - len = 1; // a toggle option fits always + if (opt_flags & OPT_ONECOLUMN) { + len = Columns; + } else if (p->flags & P_BOOL) { + len = 1; // a toggle option fits always } else { option_value2string(p, opt_flags); len = (int)STRLEN(p->fullname) + vim_strsize(NameBuff) + 1; @@ -5092,7 +5289,7 @@ static void showoptions(int all, int opt_flags) && Columns + GAP >= INT_MIN + 3 && (Columns + GAP - 3) / INC >= INT_MIN && (Columns + GAP - 3) / INC <= INT_MAX); - cols = (int)((Columns + GAP - 3) / INC); + cols = (Columns + GAP - 3) / INC; if (cols == 0) { cols = 1; } @@ -5471,7 +5668,7 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) void comp_col(void) { - int last_has_status = (p_ls == 2 || (p_ls == 1 && !ONE_WINDOW)); + int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW)); sc_col = 0; ru_col = 0; @@ -5489,13 +5686,11 @@ void comp_col(void) } } assert(sc_col >= 0 - && INT_MIN + sc_col <= Columns - && Columns - sc_col <= INT_MAX); - sc_col = (int)(Columns - sc_col); + && INT_MIN + sc_col <= Columns); + sc_col = Columns - sc_col; assert(ru_col >= 0 - && INT_MIN + ru_col <= Columns - && Columns - ru_col <= INT_MAX); - ru_col = (int)(Columns - ru_col); + && INT_MIN + ru_col <= Columns); + ru_col = Columns - ru_col; if (sc_col <= 0) { // screen too narrow, will become a mess sc_col = 1; } @@ -5602,6 +5797,10 @@ void unset_global_local_option(char *name, void *from) set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); redraw_later((win_T *)from, NOT_VALID); break; + case PV_VE: + clear_string_option(&((win_T *)from)->w_p_ve); + ((win_T *)from)->w_ve_flags = 0; + break; } } @@ -5668,6 +5867,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) return (char_u *)&(curwin->w_p_fcs); case PV_LCS: return (char_u *)&(curwin->w_p_lcs); + case PV_VE: + return (char_u *)&(curwin->w_p_ve); } return NULL; // "cannot happen" } @@ -5762,6 +5963,9 @@ static char_u *get_varp(vimoption_T *p) case PV_LCS: return *curwin->w_p_lcs != NUL ? (char_u *)&(curwin->w_p_lcs) : p->var; + case PV_VE: + return *curwin->w_p_ve != NUL + ? (char_u *)&curwin->w_p_ve : p->var; case PV_ARAB: return (char_u *)&(curwin->w_p_arab); @@ -5856,6 +6060,8 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curbuf->b_p_cink); case PV_CINO: return (char_u *)&(curbuf->b_p_cino); + case PV_CINSD: + return (char_u *)&(curbuf->b_p_cinsd); case PV_CINW: return (char_u *)&(curbuf->b_p_cinw); case PV_COM: @@ -5990,6 +6196,7 @@ void win_copy_options(win_T *wp_from, win_T *wp_to) { copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt); copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt); + didset_window_options(wp_to); } /// Copy the options from one winopt_T to another. @@ -6002,6 +6209,8 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_list = from->wo_list; to->wo_nu = from->wo_nu; to->wo_rnu = from->wo_rnu; + to->wo_ve = vim_strsave(from->wo_ve); + to->wo_ve_flags = from->wo_ve_flags; to->wo_nuw = from->wo_nuw; to->wo_rl = from->wo_rl; to->wo_rlc = vim_strsave(from->wo_rlc); @@ -6046,6 +6255,9 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_fcs = vim_strsave(from->wo_fcs); to->wo_lcs = vim_strsave(from->wo_lcs); to->wo_winbl = from->wo_winbl; + + // Copy the script context so that we know were the value was last set. + memmove(to->wo_script_ctx, from->wo_script_ctx, sizeof(to->wo_script_ctx)); check_winopt(to); // don't want NULL pointers } @@ -6078,6 +6290,7 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_winhl); check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_lcs); + check_string_option(&wop->wo_ve); } /// Free the allocated memory inside a winopt_T. @@ -6102,6 +6315,7 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_winhl); clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_lcs); + clear_string_option(&wop->wo_ve); } void didset_window_options(win_T *wp) @@ -6116,11 +6330,30 @@ void didset_window_options(win_T *wp) wp->w_grid_alloc.blending = wp->w_p_winbl > 0; } +/// Index into the options table for a buffer-local option enum. +static int buf_opt_idx[BV_COUNT]; +#define COPY_OPT_SCTX(buf, bv) buf->b_p_script_ctx[bv] = options[buf_opt_idx[bv]].last_set + +/// Initialize buf_opt_idx[] if not done already. +static void init_buf_opt_idx(void) +{ + static int did_init_buf_opt_idx = false; + + if (did_init_buf_opt_idx) { + return; + } + did_init_buf_opt_idx = true; + for (int i = 0; options[i].fullname != NULL; i++) { + if (options[i].indir & PV_BUF) { + buf_opt_idx[options[i].indir & PV_MASK] = i; + } + } +} /// Copy global option values to local options for one buffer. /// Used when creating a new buffer and sometimes when entering a buffer. /// flags: -/// BCO_ENTER We will enter the buf buffer. +/// BCO_ENTER We will enter the buffer "buf". /// BCO_ALWAYS Always copy the options, but only set b_p_initialized when /// appropriate. /// BCO_NOHELP Don't copy the values to a help buffer. @@ -6156,11 +6389,12 @@ void buf_copy_options(buf_T *buf, int flags) } if (should_copy || (flags & BCO_ALWAYS)) { - /* Don't copy the options specific to a help buffer when - * BCO_NOHELP is given or the options were initialized already - * (jumping back to a help file with CTRL-T or CTRL-O) */ - dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) - || buf->b_p_initialized; + memset(buf->b_p_script_ctx, 0, sizeof(buf->b_p_script_ctx)); + init_buf_opt_idx(); + // Don't copy the options specific to a help buffer when + // BCO_NOHELP is given or the options were initialized already + // (jumping back to a help file with CTRL-T or CTRL-O) + dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized; if (dont_do_help) { // don't free b_p_isk save_p_isk = buf->b_p_isk; buf->b_p_isk = NULL; @@ -6192,80 +6426,131 @@ void buf_copy_options(buf_T *buf, int flags) } buf->b_p_ai = p_ai; + COPY_OPT_SCTX(buf, BV_AI); buf->b_p_ai_nopaste = p_ai_nopaste; buf->b_p_sw = p_sw; + COPY_OPT_SCTX(buf, BV_SW); buf->b_p_scbk = p_scbk; + COPY_OPT_SCTX(buf, BV_SCBK); buf->b_p_tw = p_tw; + COPY_OPT_SCTX(buf, BV_TW); buf->b_p_tw_nopaste = p_tw_nopaste; buf->b_p_tw_nobin = p_tw_nobin; buf->b_p_wm = p_wm; + COPY_OPT_SCTX(buf, BV_WM); buf->b_p_wm_nopaste = p_wm_nopaste; buf->b_p_wm_nobin = p_wm_nobin; buf->b_p_bin = p_bin; + COPY_OPT_SCTX(buf, BV_BIN); buf->b_p_bomb = p_bomb; + COPY_OPT_SCTX(buf, BV_BOMB); buf->b_p_et = p_et; + COPY_OPT_SCTX(buf, BV_ET); buf->b_p_fixeol = p_fixeol; + COPY_OPT_SCTX(buf, BV_FIXEOL); buf->b_p_et_nobin = p_et_nobin; buf->b_p_et_nopaste = p_et_nopaste; buf->b_p_ml = p_ml; + COPY_OPT_SCTX(buf, BV_ML); buf->b_p_ml_nobin = p_ml_nobin; buf->b_p_inf = p_inf; - buf->b_p_swf = cmdmod.noswapfile ? false : p_swf; + COPY_OPT_SCTX(buf, BV_INF); + if (cmdmod.noswapfile) { + buf->b_p_swf = false; + } else { + buf->b_p_swf = p_swf; + COPY_OPT_SCTX(buf, BV_SWF); + } buf->b_p_cpt = vim_strsave(p_cpt); + COPY_OPT_SCTX(buf, BV_CPT); #ifdef BACKSLASH_IN_FILENAME buf->b_p_csl = vim_strsave(p_csl); + COPY_OPT_SCTX(buf, BV_CSL); #endif buf->b_p_cfu = vim_strsave(p_cfu); + COPY_OPT_SCTX(buf, BV_CFU); buf->b_p_ofu = vim_strsave(p_ofu); + COPY_OPT_SCTX(buf, BV_OFU); buf->b_p_tfu = vim_strsave(p_tfu); + COPY_OPT_SCTX(buf, BV_TFU); buf->b_p_sts = p_sts; + COPY_OPT_SCTX(buf, BV_STS); buf->b_p_sts_nopaste = p_sts_nopaste; buf->b_p_vsts = vim_strsave(p_vsts); + COPY_OPT_SCTX(buf, BV_VSTS); if (p_vsts && p_vsts != empty_option) { - tabstop_set(p_vsts, &buf->b_p_vsts_array); + (void)tabstop_set(p_vsts, &buf->b_p_vsts_array); } else { - buf->b_p_vsts_array = 0; + buf->b_p_vsts_array = NULL; } buf->b_p_vsts_nopaste = p_vsts_nopaste ? vim_strsave(p_vsts_nopaste) : NULL; buf->b_p_com = vim_strsave(p_com); + COPY_OPT_SCTX(buf, BV_COM); buf->b_p_cms = vim_strsave(p_cms); + COPY_OPT_SCTX(buf, BV_CMS); buf->b_p_fo = vim_strsave(p_fo); + COPY_OPT_SCTX(buf, BV_FO); buf->b_p_flp = vim_strsave(p_flp); + COPY_OPT_SCTX(buf, BV_FLP); buf->b_p_nf = vim_strsave(p_nf); + COPY_OPT_SCTX(buf, BV_NF); buf->b_p_mps = vim_strsave(p_mps); + COPY_OPT_SCTX(buf, BV_MPS); buf->b_p_si = p_si; + COPY_OPT_SCTX(buf, BV_SI); buf->b_p_channel = 0; buf->b_p_ci = p_ci; + COPY_OPT_SCTX(buf, BV_CI); buf->b_p_cin = p_cin; + COPY_OPT_SCTX(buf, BV_CIN); buf->b_p_cink = vim_strsave(p_cink); + COPY_OPT_SCTX(buf, BV_CINK); buf->b_p_cino = vim_strsave(p_cino); + COPY_OPT_SCTX(buf, BV_CINO); + buf->b_p_cinsd = vim_strsave(p_cinsd); + COPY_OPT_SCTX(buf, BV_CINSD); // Don't copy 'filetype', it must be detected buf->b_p_ft = empty_option; buf->b_p_pi = p_pi; + COPY_OPT_SCTX(buf, BV_PI); buf->b_p_cinw = vim_strsave(p_cinw); + COPY_OPT_SCTX(buf, BV_CINW); buf->b_p_lisp = p_lisp; + COPY_OPT_SCTX(buf, BV_LISP); // Don't copy 'syntax', it must be set buf->b_p_syn = empty_option; buf->b_p_smc = p_smc; + COPY_OPT_SCTX(buf, BV_SMC); buf->b_s.b_syn_isk = empty_option; buf->b_s.b_p_spc = vim_strsave(p_spc); + COPY_OPT_SCTX(buf, BV_SPC); (void)compile_cap_prog(&buf->b_s); buf->b_s.b_p_spf = vim_strsave(p_spf); + COPY_OPT_SCTX(buf, BV_SPF); buf->b_s.b_p_spl = vim_strsave(p_spl); + COPY_OPT_SCTX(buf, BV_SPL); buf->b_s.b_p_spo = vim_strsave(p_spo); + COPY_OPT_SCTX(buf, BV_SPO); buf->b_p_inde = vim_strsave(p_inde); + COPY_OPT_SCTX(buf, BV_INDE); buf->b_p_indk = vim_strsave(p_indk); + COPY_OPT_SCTX(buf, BV_INDK); buf->b_p_fp = empty_option; buf->b_p_fex = vim_strsave(p_fex); + COPY_OPT_SCTX(buf, BV_FEX); buf->b_p_sua = vim_strsave(p_sua); + COPY_OPT_SCTX(buf, BV_SUA); buf->b_p_keymap = vim_strsave(p_keymap); + COPY_OPT_SCTX(buf, BV_KMAP); buf->b_kmap_state |= KEYMAP_INIT; // This isn't really an option, but copying the langmap and IME // state from the current buffer is better than resetting it. buf->b_p_iminsert = p_iminsert; + COPY_OPT_SCTX(buf, BV_IMI); buf->b_p_imsearch = p_imsearch; + COPY_OPT_SCTX(buf, BV_IMS); // options that are normally global but also have a local value // are not copied, start using the global value @@ -6285,11 +6570,14 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_def = empty_option; buf->b_p_inc = empty_option; buf->b_p_inex = vim_strsave(p_inex); + COPY_OPT_SCTX(buf, BV_INEX); buf->b_p_dict = empty_option; buf->b_p_tsr = empty_option; buf->b_p_tsrfu = empty_option; buf->b_p_qe = vim_strsave(p_qe); + COPY_OPT_SCTX(buf, BV_QE); buf->b_p_udf = p_udf; + COPY_OPT_SCTX(buf, BV_UDF); buf->b_p_lw = empty_option; buf->b_p_menc = empty_option; @@ -6302,17 +6590,20 @@ void buf_copy_options(buf_T *buf, int flags) if (dont_do_help) { buf->b_p_isk = save_p_isk; if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { - tabstop_set(p_vts, &buf->b_p_vts_array); + (void)tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; } } else { buf->b_p_isk = vim_strsave(p_isk); + COPY_OPT_SCTX(buf, BV_ISK); did_isk = true; buf->b_p_ts = p_ts; + COPY_OPT_SCTX(buf, BV_TS); buf->b_p_vts = vim_strsave(p_vts); + COPY_OPT_SCTX(buf, BV_VTS); if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { - tabstop_set(p_vts, &buf->b_p_vts_array); + (void)tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; } @@ -6321,6 +6612,7 @@ void buf_copy_options(buf_T *buf, int flags) clear_string_option(&buf->b_p_bt); } buf->b_p_ma = p_ma; + COPY_OPT_SCTX(buf, BV_MA); } } @@ -6961,10 +7253,7 @@ static void paste_option_changed(void) free_string_option(buf->b_p_vsts); } buf->b_p_vsts = empty_option; - if (buf->b_p_vsts_array) { - xfree(buf->b_p_vsts_array); - } - buf->b_p_vsts_array = 0; + XFREE_CLEAR(buf->b_p_vsts_array); } // set global options @@ -7001,13 +7290,11 @@ static void paste_option_changed(void) buf->b_p_vsts = buf->b_p_vsts_nopaste ? vim_strsave(buf->b_p_vsts_nopaste) : empty_option; - if (buf->b_p_vsts_array) { - xfree(buf->b_p_vsts_array); - } + xfree(buf->b_p_vsts_array); if (buf->b_p_vsts && buf->b_p_vsts != empty_option) { - tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); + (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); } else { - buf->b_p_vsts_array = 0; + buf->b_p_vsts_array = NULL; } } @@ -7323,6 +7610,7 @@ int check_ff_value(char_u *p) // Set the integer values corresponding to the string setting of 'vartabstop'. // "array" will be set, caller must free it if needed. +// Return false for an error. bool tabstop_set(char_u *var, long **array) { long valcount = 1; @@ -7342,7 +7630,7 @@ bool tabstop_set(char_u *var, long **array) if (cp != end) { emsg(_(e_positive)); } else { - emsg(_(e_invarg)); + semsg(_(e_invarg2), cp); } return false; } @@ -7355,7 +7643,7 @@ bool tabstop_set(char_u *var, long **array) valcount++; continue; } - emsg(_(e_invarg)); + semsg(_(e_invarg2), var); return false; } @@ -7364,7 +7652,15 @@ bool tabstop_set(char_u *var, long **array) t = 1; for (cp = var; *cp != NUL;) { - (*array)[t++] = atoi((char *)cp); + int n = atoi((char *)cp); + + // Catch negative values, overflow and ridiculous big values. + if (n <= 0 || n > TABSTOP_MAX) { + semsg(_(e_invarg2), cp); + XFREE_CLEAR(*array); + return false; + } + (*array)[t++] = n; while (*cp != NUL && *cp != ',') { cp++; } @@ -7669,6 +7965,12 @@ unsigned int get_bkc_value(buf_T *buf) return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags; } +/// Get the local or global value of the 'virtualedit' flags. +unsigned int get_ve_flags(void) +{ + return (curwin->w_ve_flags ? curwin->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU); +} + /// Get the local or global value of 'showbreak'. /// /// @param win If not NULL, the window to get the local option from; global @@ -7771,7 +8073,7 @@ void set_fileformat(int eol_style, int opt_flags) } // This may cause the buffer to become (un)modified. - check_status(curbuf); + redraw_buf_status_later(curbuf); redraw_tabline = true; need_maketitle = true; // Set window title later. } @@ -7848,7 +8150,6 @@ int win_signcol_count(win_T *wp) /// Return the number of requested sign columns, based on user / configuration. int win_signcol_configured(win_T *wp, int *is_fixed) { - int minimum = 0, maximum = 1, needed_signcols; const char *scl = (const char *)wp->w_p_scl; if (is_fixed) { @@ -7861,7 +8162,6 @@ int win_signcol_configured(win_T *wp, int *is_fixed) && (wp->w_p_nu || wp->w_p_rnu)))) { return 0; } - needed_signcols = buf_signcols(wp->w_buffer); // yes or yes if (!strncmp(scl, "yes:", 4)) { @@ -7877,6 +8177,8 @@ int win_signcol_configured(win_T *wp, int *is_fixed) *is_fixed = 0; } + int minimum = 0, maximum = 1; + if (!strncmp(scl, "auto:", 5)) { // Variable depending on a configuration maximum = scl[5] - '0'; @@ -7887,6 +8189,7 @@ int win_signcol_configured(win_T *wp, int *is_fixed) } } + int needed_signcols = buf_signcols(wp->w_buffer, maximum); int ret = MAX(minimum, MIN(maximum, needed_signcols)); assert(ret <= SIGN_SHOW_MAX); return ret; diff --git a/src/nvim/option.h b/src/nvim/option.h index 452494172f..9321dd5454 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -13,15 +13,16 @@ /// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global /// values, get local value. typedef enum { - OPT_FREE = 1, ///< Free old value if it was allocated. - OPT_GLOBAL = 2, ///< Use global value. - OPT_LOCAL = 4, ///< Use local value. - OPT_MODELINE = 8, ///< Option in modeline. - OPT_WINONLY = 16, ///< Only set window-local options. - OPT_NOWIN = 32, ///< Don’t set window-local options. - OPT_ONECOLUMN = 64, ///< list options one per line - OPT_NO_REDRAW = 128, ///< ignore redraw flags on option - OPT_SKIPRTP = 256, ///< "skiprtp" in 'sessionoptions' + OPT_FREE = 0x01, ///< Free old value if it was allocated. + OPT_GLOBAL = 0x02, ///< Use global value. + OPT_LOCAL = 0x04, ///< Use local value. + OPT_MODELINE = 0x08, ///< Option in modeline. + OPT_WINONLY = 0x10, ///< Only set window-local options. + OPT_NOWIN = 0x20, ///< Don’t set window-local options. + OPT_ONECOLUMN = 0x40, ///< list options one per line + OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option + OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions' + OPT_CLEAR = 0x200, ///< Clear local value of an option. } OptionFlags; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 19cb33a354..bf71b63cc8 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -57,7 +57,7 @@ #define ENC_DFLT "utf-8" // end-of-line style -#define EOL_UNKNOWN -1 // not defined yet +#define EOL_UNKNOWN (-1) // not defined yet #define EOL_UNIX 0 // NL #define EOL_DOS 1 // CR NL #define EOL_MAC 2 // CR @@ -523,11 +523,11 @@ EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' -EXTERN long p_mle; // 'modelineexpr' +EXTERN int 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 int p_mousef; // 'mousefocus' EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' EXTERN char_u *p_opfunc; // 'operatorfunc' @@ -705,12 +705,14 @@ EXTERN int p_vb; ///< 'visualbell' EXTERN char_u *p_ve; ///< 'virtualedit' EXTERN unsigned ve_flags; #ifdef IN_OPTION_C -static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", NULL }; +static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; #endif -#define VE_BLOCK 5 // includes "all" -#define VE_INSERT 6 // includes "all" -#define VE_ALL 4 -#define VE_ONEMORE 8 +#define VE_BLOCK 5U // includes "all" +#define VE_INSERT 6U // includes "all" +#define VE_ALL 4U +#define VE_ONEMORE 8U +#define VE_NONE 16U // "none" +#define VE_NONEU 32U // "NONE" EXTERN long p_verbose; // 'verbose' #ifdef IN_OPTION_C char_u *p_vfile = (char_u *)""; // used before options are initialized @@ -743,6 +745,7 @@ EXTERN int p_write; // 'write' EXTERN int p_wa; // 'writeany' EXTERN int p_wb; // 'writebackup' EXTERN long p_wd; // 'writedelay' +EXTERN int p_cdh; // 'cdhome' EXTERN int p_force_on; ///< options that cannot be turned off. EXTERN int p_force_off; ///< options that cannot be turned on. @@ -770,6 +773,7 @@ enum { BV_CINK, BV_CINO, BV_CINW, + BV_CINSD, BV_CM, BV_CMS, BV_COM, @@ -868,6 +872,7 @@ enum { WV_LBR, WV_NU, WV_RNU, + WV_VE, WV_NUW, WV_PVW, WV_RL, @@ -895,10 +900,12 @@ enum { }; // Value for b_p_ul indicating the global value must be used. -#define NO_LOCAL_UNDOLEVEL -123456 +#define NO_LOCAL_UNDOLEVEL (-123456) #define SB_MAX 100000 // Maximum 'scrollback' value. +#define TABSTOP_MAX 9999 + /// Stores an identifier of a script or channel that last set an option. typedef struct { sctx_T script_ctx; /// script context where the option was last set diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 71208dfc68..313cace4b2 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -275,6 +275,14 @@ return { defaults={if_true="internal,keepascii"} }, { + full_name='cdhome', abbreviation='cdh', + short_desc=N_(":cd without argument goes to the home directory"), + type='bool', scope={'global'}, + secure=true, + varname='p_cdh', + defaults={if_true=false} + }, + { full_name='cdpath', abbreviation='cd', short_desc=N_("list of directories searched with \":cd\""), type='string', list='comma', scope={'global'}, @@ -343,6 +351,15 @@ return { defaults={if_true="if,else,while,do,for,switch"} }, { + full_name='cinscopedecls', abbreviation='cinsd', + short_desc=N_("words that are recognized by 'cino-g'"), + type='string', list='onecomma', scope={'buffer'}, + deny_duplicates=true, + alloced=true, + varname='p_cinsd', + defaults={if_true="public,protected,private"} + }, + { full_name='clipboard', abbreviation='cb', short_desc=N_("use the clipboard as the unnamed register"), type='string', list='onecomma', scope={'global'}, @@ -403,7 +420,7 @@ return { }, { full_name='compatible', abbreviation='cp', - short_desc=N_("No description"), + short_desc=N_("No description"), type='bool', scope={'global'}, redraw={'all_windows'}, varname='p_force_off', @@ -657,14 +674,14 @@ return { }, { full_name='edcompatible', abbreviation='ed', - short_desc=N_("No description"), + short_desc=N_("No description"), type='bool', scope={'global'}, varname='p_force_off', defaults={if_true=false} }, { full_name='emoji', abbreviation='emo', - short_desc=N_("No description"), + short_desc=N_("No description"), type='bool', scope={'global'}, redraw={'all_windows', 'ui_option'}, varname='p_emoji', @@ -1176,7 +1193,7 @@ return { }, { full_name='inccommand', abbreviation='icm', - short_desc=N_("Live preview of substitution"), + short_desc=N_("Live preview of substitution"), type='string', scope={'global'}, redraw={'all_windows'}, varname='p_icm', @@ -1838,7 +1855,7 @@ return { type='number', scope={'global'}, secure=true, varname='p_pyx', - defaults={if_true=0} + defaults={if_true=3} }, { full_name='quickfixtextfunc', abbreviation='qftf', @@ -2491,7 +2508,7 @@ return { }, { full_name='termencoding', abbreviation='tenc', - short_desc=N_("Terminal encodig"), + short_desc=N_("Terminal encoding"), type='string', scope={'global'}, defaults={if_true=""} }, @@ -2614,7 +2631,7 @@ return { }, { full_name='ttyfast', abbreviation='tf', - short_desc=N_("No description"), + short_desc=N_("No description"), type='bool', scope={'global'}, no_mkrc=true, varname='p_force_on', @@ -2728,7 +2745,7 @@ return { { full_name='virtualedit', abbreviation='ve', short_desc=N_("when to use virtual editing"), - type='string', list='onecomma', scope={'global'}, + type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, redraw={'curswant'}, varname='p_ve', diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index e9f44d2775..b738d36234 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -720,7 +720,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo && dst[-1] != ':' #endif && vim_ispathsep(*tail)) { - ++tail; + tail++; } dst += c; src = tail; @@ -738,7 +738,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo at_start = false; if (src[0] == '\\' && src[1] != NUL) { *dst++ = *src++; - --dstlen; + dstlen--; } else if ((src[0] == ' ' || src[0] == ',') && !one) { at_start = true; } @@ -1111,10 +1111,9 @@ size_t home_replace(const buf_T *const buf, const char_u *src, char_u *const dst *dst_p++ = '~'; } - // If it's just the home directory, add "/". - if (!vim_ispathsep(src[0]) && --dstlen > 0) { - *dst_p++ = '/'; - } + // Do not add directory separator into dst, because dst is + // expected to just return the directory name without the + // directory separator '/'. break; } if (p == homedir_env_mod) { diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index e9963516fc..daf974ee74 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -21,7 +21,6 @@ #include "nvim/assert.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" @@ -41,8 +40,10 @@ bool did_try_to_free = false; \ uv_call_start: {} \ uv_fs_t req; \ + fs_loop_lock(); \ ret = func(&fs_loop, &req, __VA_ARGS__); \ uv_fs_req_cleanup(&req); \ + fs_loop_unlock(); \ if (ret == UV_ENOMEM && !did_try_to_free) { \ try_to_free_memory(); \ did_try_to_free = true; \ @@ -53,12 +54,27 @@ uv_call_start: {} \ // Many fs functions from libuv return that value on success. static const int kLibuvSuccess = 0; static uv_loop_t fs_loop; +static uv_mutex_t fs_loop_mutex; // Initialize the fs module void fs_init(void) { uv_loop_init(&fs_loop); + uv_mutex_init_recursive(&fs_loop_mutex); +} + +/// TODO(bfredl): some of these operations should +/// be possible to do the private libuv loop of the +/// thread, instead of contending the global fs loop +void fs_loop_lock(void) +{ + uv_mutex_lock(&fs_loop_mutex); +} + +void fs_loop_unlock(void) +{ + uv_mutex_unlock(&fs_loop_mutex); } @@ -99,9 +115,12 @@ bool os_isrealdir(const char *name) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; + fs_loop_lock(); if (uv_fs_lstat(&fs_loop, &request, name, NULL) != kLibuvSuccess) { + fs_loop_unlock(); return false; } + fs_loop_unlock(); if (S_ISLNK(request.statbuf.st_mode)) { return false; } else { @@ -739,7 +758,9 @@ static int os_stat(const char *name, uv_stat_t *statbuf) return UV_EINVAL; } uv_fs_t request; + fs_loop_lock(); int result = uv_fs_stat(&fs_loop, &request, name, NULL); + fs_loop_unlock(); if (result == kLibuvSuccess) { *statbuf = request.statbuf; } @@ -936,9 +957,11 @@ int os_mkdtemp(const char *template, char *path) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; + fs_loop_lock(); int result = uv_fs_mkdtemp(&fs_loop, &request, template, NULL); + fs_loop_unlock(); if (result == kLibuvSuccess) { - STRNCPY(path, request.path, TEMP_FILE_PATH_MAXLEN); + xstrlcpy(path, request.path, TEMP_FILE_PATH_MAXLEN); } uv_fs_req_cleanup(&request); return result; @@ -963,7 +986,9 @@ int os_rmdir(const char *path) bool os_scandir(Directory *dir, const char *path) FUNC_ATTR_NONNULL_ALL { + fs_loop_lock(); int r = uv_fs_scandir(&fs_loop, &dir->request, path, 0, NULL); + fs_loop_unlock(); if (r < 0) { os_closedir(dir); } @@ -1024,7 +1049,9 @@ bool os_fileinfo_link(const char *path, FileInfo *file_info) return false; } uv_fs_t request; + fs_loop_lock(); bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess; + fs_loop_unlock(); if (ok) { file_info->stat = request.statbuf; } @@ -1042,6 +1069,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) { uv_fs_t request; memset(file_info, 0, sizeof(*file_info)); + fs_loop_lock(); bool ok = uv_fs_fstat(&fs_loop, &request, file_descriptor, @@ -1050,6 +1078,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) file_info->stat = request.statbuf; } uv_fs_req_cleanup(&request); + fs_loop_unlock(); return ok; } @@ -1166,6 +1195,7 @@ char *os_realpath(const char *name, char *buf) FUNC_ATTR_NONNULL_ARG(1) { uv_fs_t request; + fs_loop_lock(); int result = uv_fs_realpath(&fs_loop, &request, name, NULL); if (result == kLibuvSuccess) { if (buf == NULL) { @@ -1174,6 +1204,7 @@ char *os_realpath(const char *name, char *buf) xstrlcpy(buf, request.ptr, MAXPATHL + 1); } uv_fs_req_cleanup(&request); + fs_loop_unlock(); return result == kLibuvSuccess ? buf : NULL; } diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 5b231f205b..745b888b5e 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -17,7 +17,6 @@ #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/input.h" #include "nvim/state.h" @@ -183,6 +182,40 @@ void os_breakcheck(void) updating_screen = save_us; } +#define BREAKCHECK_SKIP 1000 +static int breakcheck_count = 0; + +/// Check for CTRL-C pressed, but only once in a while. +/// +/// Should be used instead of os_breakcheck() for functions that check for +/// each line in the file. Calling os_breakcheck() each time takes too much +/// time, because it will use system calls to check for input. +void line_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP) { + breakcheck_count = 0; + os_breakcheck(); + } +} + +/// Like line_breakcheck() but check 10 times less often. +void fast_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP * 10) { + breakcheck_count = 0; + os_breakcheck(); + } +} + +/// Like line_breakcheck() but check 100 times less often. +void veryfast_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP * 100) { + breakcheck_count = 0; + os_breakcheck(); + } +} + /// Test whether a file descriptor refers to a terminal. /// @@ -201,9 +234,9 @@ size_t input_enqueue(String keys) while (rbuffer_space(input_buffer) >= 19 && ptr < end) { // A "<x>" form occupies at least 1 characters, and produces up // to 19 characters (1 + 5 * 3 for the char and 3 for a modifier). - // In the case of K_SPECIAL(0x80) or CSI(0x9B), 3 bytes are escaped and - // needed, but since the keys are UTF-8, so the first byte cannot be - // K_SPECIAL(0x80) or CSI(0x9B). + // In the case of K_SPECIAL(0x80), 3 bytes are escaped and needed, + // but since the keys are UTF-8, so the first byte cannot be + // K_SPECIAL(0x80). uint8_t buf[19] = { 0 }; unsigned int new_size = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true, @@ -230,12 +263,8 @@ size_t input_enqueue(String keys) continue; } - // copy the character, escaping CSI and K_SPECIAL - if ((uint8_t)*ptr == CSI) { - rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1); - rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_EXTRA }, 1); - rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_CSI }, 1); - } else if ((uint8_t)*ptr == K_SPECIAL) { + // copy the character, escaping K_SPECIAL + if ((uint8_t)(*ptr) == K_SPECIAL) { rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1); rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_SPECIAL }, 1); rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_FILLER }, 1); @@ -259,8 +288,13 @@ static uint8_t check_multiclick(int code, int grid, int row, int col) static int orig_mouse_row = 0; static uint64_t orig_mouse_time = 0; // time of previous mouse click - if (code == KE_LEFTRELEASE || code == KE_RIGHTRELEASE - || code == KE_MIDDLERELEASE) { + if (code == KE_LEFTRELEASE + || code == KE_RIGHTRELEASE + || code == KE_MIDDLERELEASE + || code == KE_MOUSEDOWN + || code == KE_MOUSEUP + || code == KE_MOUSELEFT + || code == KE_MOUSERIGHT) { return 0; } uint64_t mouse_time = os_hrtime(); // time of current mouse click (ns) @@ -493,6 +527,7 @@ static bool input_ready(MultiQueue *events) // Exit because of an input read error. static void read_error_exit(void) + FUNC_ATTR_NORETURN { if (silent_mode) { // Normal way to exit for "nvim -es". getout(0); diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h index 8049b3b80e..a4361859ec 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -13,6 +13,10 @@ # include "nvim/os/unix_defs.h" #endif +#if !defined(NAME_MAX) && defined(_XOPEN_NAME_MAX) +# define NAME_MAX _XOPEN_NAME_MAX +#endif + #define BASENAMELEN (NAME_MAX - 5) // Use the system path length if it makes sense. diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 24ecf5c24f..4a49c0b162 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -15,6 +15,12 @@ # include <libutil.h> #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include <util.h> +#elif defined(__sun) +# include <fcntl.h> +# include <signal.h> +# include <sys/stream.h> +# include <sys/syscall.h> +# include <unistd.h> #else # include <pty.h> #endif @@ -38,6 +44,117 @@ # include "os/pty_process_unix.c.generated.h" #endif +#if defined(__sun) && !defined(HAVE_FORKPTY) + +// this header defines STR, just as nvim.h, but it is defined as ('S'<<8), +// to avoid #undef STR, #undef STR, #define STR ('S'<<8) just delay the +// inclusion of the header even though it gets include out of order. +# include <sys/stropts.h> + +static int openpty(int *amaster, int *aslave, char *name, struct termios *termp, + struct winsize *winp) +{ + int slave = -1; + int master = open("/dev/ptmx", O_RDWR); + if (master == -1) { + goto error; + } + + // grantpt will invoke a setuid program to change permissions + // and might fail if SIGCHLD handler is set, temporarily reset + // while running + void (*sig_saved)(int) = signal(SIGCHLD, SIG_DFL); + int res = grantpt(master); + signal(SIGCHLD, sig_saved); + + if (res == -1 || unlockpt(master) == -1) { + goto error; + } + + char *slave_name = ptsname(master); + if (slave_name == NULL) { + goto error; + } + + slave = open(slave_name, O_RDWR|O_NOCTTY); + if (slave == -1) { + goto error; + } + + // ptem emulates a terminal when used on a pseudo terminal driver, + // must be pushed before ldterm + ioctl(slave, I_PUSH, "ptem"); + // ldterm provides most of the termio terminal interface + ioctl(slave, I_PUSH, "ldterm"); + // ttcompat compatibility with older terminal ioctls + ioctl(slave, I_PUSH, "ttcompat"); + + if (termp) { + tcsetattr(slave, TCSAFLUSH, termp); + } + if (winp) { + ioctl(slave, TIOCSWINSZ, winp); + } + + *amaster = master; + *aslave = slave; + // ignoring name, not passed and size is unknown in the API + + return 0; + +error: + if (slave != -1) { + close(slave); + } + if (master != -1) { + close(master); + } + return -1; +} + +static int login_tty(int fd) +{ + setsid(); + if (ioctl(fd, TIOCSCTTY, NULL) == -1) { + return -1; + } + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) { + close(fd); + } + + return 0; +} + +static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp) +{ + int master, slave; + if (openpty(&master, &slave, name, termp, winp) == -1) { + return -1; + } + + pid_t pid = fork(); + switch (pid) { + case -1: + close(master); + close(slave); + return -1; + case 0: + close(master); + login_tty(slave); + return 0; + default: + close(slave); + *amaster = master; + return pid; + } +} + +#endif + /// termios saved at startup (for TUI) or initialized by pty_process_spawn(). static struct termios termios_default; @@ -198,7 +315,9 @@ static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL termios->c_cflag = CS8|CREAD; termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK; - cfsetspeed(termios, 38400); + // not using cfsetspeed, not available on all platforms + cfsetispeed(termios, 38400); + cfsetospeed(termios, 38400); #ifdef IUTF8 termios->c_iflag |= IUTF8; diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index f78f3e66f5..4fb9e30a96 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -58,8 +58,7 @@ int pty_process_spawn(PtyProcess *ptyproc) if (os_has_conpty_working()) { if ((conpty_object = - os_conpty_init(&in_name, &out_name, - ptyproc->width, ptyproc->height)) != NULL) { + os_conpty_init(&in_name, &out_name, ptyproc->width, ptyproc->height)) != NULL) { ptyproc->type = kConpty; } } @@ -281,7 +280,7 @@ static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) PtyProcess *ptyproc = wait_eof_timer->data; Process *proc = (Process *)ptyproc; - if (proc->out.closed || !uv_is_readable(proc->out.uvstream)) { + if (proc->out.closed || proc->out.did_eof || !uv_is_readable(proc->out.uvstream)) { uv_timer_stop(&ptyproc->wait_eof_timer); pty_process_finish2(ptyproc); } @@ -308,7 +307,7 @@ static void pty_process_finish2(PtyProcess *ptyproc) /// Build the command line to pass to CreateProcessW. /// /// @param[in] argv Array with string arguments. -/// @param[out] cmd_line Location where saved builded cmd line. +/// @param[out] cmd_line Location where saved built cmd line. /// /// @returns zero on success, or error code of MultiByteToWideChar function. /// diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 6ef0aa1091..5680cdbe42 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -9,6 +9,7 @@ #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/eval.h" #include "nvim/event/libuv_process.h" #include "nvim/event/loop.h" #include "nvim/event/rstream.h" @@ -20,13 +21,13 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option_defs.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" #include "nvim/path.h" #include "nvim/screen.h" #include "nvim/strings.h" +#include "nvim/tag.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -681,6 +682,116 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) return exitcode; } +/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. +/// Invalidates cached tags. +/// +/// @return shell command exit code +int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) +{ + int retval; + proftime_T wait_time; + + if (p_verbose > 3) { + verbose_enter(); + smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd); + msg_putchar('\n'); + verbose_leave(); + } + + if (do_profiling == PROF_YES) { + prof_child_enter(&wait_time); + } + + if (*p_sh == NUL) { + emsg(_(e_shellempty)); + retval = -1; + } else { + // The external command may update a tags file, clear cached tags. + tag_freematch(); + + retval = os_call_shell(cmd, opts, extra_shell_arg); + } + + set_vim_var_nr(VV_SHELL_ERROR, (varnumber_T)retval); + if (do_profiling == PROF_YES) { + prof_child_exit(&wait_time); + } + + return retval; +} + +/// Get the stdout of an external command. +/// If "ret_len" is NULL replace NUL characters with NL. When "ret_len" is not +/// NULL store the length there. +/// +/// @param cmd command to execute +/// @param infile optional input file name +/// @param flags can be kShellOptSilent or 0 +/// @param ret_len length of the stdout +/// +/// @return an allocated string, or NULL for error. +char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret_len) +{ + char_u *buffer = NULL; + + if (check_secure()) { + return NULL; + } + + // get a name for the temp file + char_u *tempname = vim_tempname(); + if (tempname == NULL) { + emsg(_(e_notmp)); + return NULL; + } + + // Add the redirection stuff + char_u *command = make_filter_cmd(cmd, infile, tempname); + + // Call the shell to execute the command (errors are ignored). + // Don't check timestamps here. + no_check_timestamps++; + call_shell(command, kShellOptDoOut | kShellOptExpand | flags, NULL); + no_check_timestamps--; + + xfree(command); + + // read the names from the file into memory + FILE *fd = os_fopen((char *)tempname, READBIN); + + if (fd == NULL) { + semsg(_(e_notopen), tempname); + goto done; + } + + fseek(fd, 0L, SEEK_END); + size_t len = (size_t)ftell(fd); // get size of temp file + fseek(fd, 0L, SEEK_SET); + + buffer = xmalloc(len + 1); + size_t i = fread((char *)buffer, 1, len, fd); + fclose(fd); + os_remove((char *)tempname); + if (i != len) { + semsg(_(e_notread), tempname); + XFREE_CLEAR(buffer); + } else if (ret_len == NULL) { + // Change NUL into SOH, otherwise the string is truncated. + for (i = 0; i < len; i++) { + if (buffer[i] == NUL) { + buffer[i] = 1; + } + } + + buffer[len] = NUL; // make sure the buffer is terminated + } else { + *ret_len = len; + } + +done: + xfree(tempname); + return buffer; +} /// os_system - synchronously execute a command in the shell /// /// example: @@ -1118,7 +1229,7 @@ static void read_input(DynamicBuffer *buf) dynamic_buffer_ensure(buf, buf->len + 1); buf->data[buf->len++] = NL; } - ++lnum; + lnum++; if (lnum > curbuf->b_op_end.lnum) { break; } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 0d125ec964..79e042b8a5 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -18,11 +18,10 @@ #include "nvim/main.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/os/signal.h" #include "nvim/vim.h" -static SignalWatcher spipe, shup, squit, sterm, susr1; +static SignalWatcher spipe, shup, squit, sterm, susr1, swinch; #ifdef SIGPWR static SignalWatcher spwr; #endif @@ -55,6 +54,9 @@ void signal_init(void) #ifdef SIGUSR1 signal_watcher_init(&main_loop, &susr1, NULL); #endif +#ifdef SIGWINCH + signal_watcher_init(&main_loop, &swinch, NULL); +#endif signal_start(); } @@ -71,6 +73,9 @@ void signal_teardown(void) #ifdef SIGUSR1 signal_watcher_close(&susr1, NULL); #endif +#ifdef SIGWINCH + signal_watcher_close(&swinch, NULL); +#endif } void signal_start(void) @@ -89,6 +94,9 @@ void signal_start(void) #ifdef SIGUSR1 signal_watcher_start(&susr1, on_signal, SIGUSR1); #endif +#ifdef SIGWINCH + signal_watcher_start(&swinch, on_signal, SIGWINCH); +#endif } void signal_stop(void) @@ -107,6 +115,9 @@ void signal_stop(void) #ifdef SIGUSR1 signal_watcher_stop(&susr1); #endif +#ifdef SIGWINCH + signal_watcher_stop(&swinch); +#endif } void signal_reject_deadly(void) @@ -142,6 +153,10 @@ static char *signal_name(int signum) case SIGUSR1: return "SIGUSR1"; #endif +#ifdef SIGWINCH + case SIGWINCH: + return "SIGWINCH"; +#endif default: return "Unknown"; } @@ -153,6 +168,7 @@ static char *signal_name(int signum) // NOTE: Avoid unsafe functions, such as allocating memory, they can result in // a deadlock. static void deadly_signal(int signum) + FUNC_ATTR_NORETURN { // Set the v:dying variable. set_vim_var_nr(VV_DYING, 1); @@ -198,6 +214,12 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) curbuf); break; #endif +#ifdef SIGWINCH + case SIGWINCH: + apply_autocmds(EVENT_SIGNAL, (char_u *)"SIGWINCH", curbuf->b_fname, true, + curbuf); + break; +#endif default: ELOG("invalid signal: %d", signum); break; diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index 9952e2b387..e0ce3fec31 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -18,6 +18,9 @@ # include <lm.h> #endif +// All user names (for ~user completion as done by shell). +static garray_T ga_users = GA_EMPTY_INIT_VALUE; + // Add a user name to the list of users in garray_T *users. // Do nothing if user name is NULL or empty. static void add_user(garray_T *users, char *user, bool need_copy) @@ -157,3 +160,60 @@ char *os_get_user_directory(const char *name) return NULL; } + +#if defined(EXITFREE) + +void free_users(void) +{ + ga_clear_strings(&ga_users); +} + +#endif + +/// Find all user names for user completion. +/// +/// Done only once and then cached. +static void init_users(void) +{ + static int lazy_init_done = false; + + if (lazy_init_done) { + return; + } + + lazy_init_done = true; + + os_get_usernames(&ga_users); +} + +/// Given to ExpandGeneric() to obtain an user names. +char_u *get_users(expand_T *xp, int idx) +{ + init_users(); + if (idx < ga_users.ga_len) { + return ((char_u **)ga_users.ga_data)[idx]; + } + return NULL; +} + +/// Check whether name matches a user name. +/// +/// @return 0 if name does not match any user name. +/// 1 if name partially matches the beginning of a user name. +/// 2 is name fully matches a user name. +int match_user(char_u *name) +{ + int n = (int)STRLEN(name); + int result = 0; + + init_users(); + for (int i = 0; i < ga_users.ga_len; i++) { + if (STRCMP(((char_u **)ga_users.ga_data)[i], name) == 0) { + return 2; // full match + } + if (STRNCMP(((char_u **)ga_users.ga_data)[i], name, n) == 0) { + result = 1; // partial match + } + } + return result; +} diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index efef77be7b..1ae86d6bbe 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -36,7 +36,7 @@ // Windows defines a RGB macro that produces 0x00bbggrr color values for use // with GDI. Our macro is different, and we don't use GDI. // Duplicated from macros.h to avoid include-order sensitivity. -#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) +#define RGB_(r, g, b) (((r) << 16) | ((g) << 8) | (b)) #ifdef _MSC_VER # ifndef inline diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 9396a5896a..1398dba0e4 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -20,7 +20,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/os/input.h" diff --git a/src/nvim/path.c b/src/nvim/path.c index 19c820e4dd..75624778e3 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -18,7 +18,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" @@ -270,16 +269,17 @@ int vim_ispathlistsep(int c) #endif } -/* - * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" - * It's done in-place. - */ -char_u *shorten_dir(char_u *str) +/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" +/// "trim_len" specifies how many characters to keep for each directory. +/// Must be 1 or more. +/// It's done in-place. +void shorten_dir_len(char_u *str, int trim_len) { char_u *tail = path_tail(str); char_u *d = str; bool skip = false; - for (char_u *s = str;; ++s) { + int dirchunk_len = 0; + for (char_u *s = str;; s++) { if (s >= tail) { // copy the whole tail *d++ = *s; if (*s == NUL) { @@ -288,10 +288,16 @@ char_u *shorten_dir(char_u *str) } else if (vim_ispathsep(*s)) { // copy '/' and next char *d++ = *s; skip = false; + dirchunk_len = 0; } else if (!skip) { *d++ = *s; // copy next char if (*s != '~' && *s != '.') { // and leading "~" and "." - skip = true; + dirchunk_len++; // only count word chars for the size + // keep copying chars until we have our preferred length (or + // until the above if/else branches move us along) + if (dirchunk_len >= trim_len) { + skip = true; + } } int l = utfc_ptr2len(s); while (--l > 0) { @@ -299,7 +305,13 @@ char_u *shorten_dir(char_u *str) } } } - return str; +} + +/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" +/// It's done in-place. +void shorten_dir(char_u *str) +{ + shorten_dir_len(str, 1); } /* @@ -630,8 +642,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, } else if (path_end >= path + wildoff && (vim_strchr((char_u *)"*?[{~$", *path_end) != NULL #ifndef WIN32 - || (!p_fic && (flags & EW_ICASE) - && isalpha(utf_ptr2char(path_end))) + || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char(path_end))) #endif )) { e = p; @@ -1337,6 +1348,17 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***fil return ((flags & EW_EMPTYOK) || ga.ga_data != NULL) ? OK : FAIL; } +/// Free the list of files returned by expand_wildcards() or other expansion functions. +void FreeWild(int count, char_u **files) +{ + if (count <= 0 || files == NULL) { + return; + } + while (count--) { + xfree(files[count]); + } + xfree(files); +} /* * Return TRUE if we can expand this backtick thing here. @@ -1498,7 +1520,7 @@ void simplify_filename(char_u *filename) p = filename; #ifdef BACKSLASH_IN_FILENAME - if (p[1] == ':') { // skip "x:" + if (p[0] != NUL && p[1] == ':') { // skip "x:" p += 2; } #endif @@ -1672,6 +1694,10 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, char_u *file_name; char_u *tofree = NULL; + if (len == 0) { + return NULL; + } + if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { tofree = (char_u *)eval_includeexpr((char *)ptr, len); if (tofree != NULL) { @@ -1733,14 +1759,32 @@ int path_is_url(const char *p) return 0; } -/// Check if "fname" starts with "name://". Return URL_SLASH if it does. +/// Check if "fname" starts with "name://" or "name:\\". /// /// @param fname is the filename to test -/// @return URL_BACKSLASH for "name:\\", zero otherwise. +/// @return URL_SLASH for "name://", URL_BACKSLASH for "name:\\", zero otherwise. int path_with_url(const char *fname) { const char *p; - for (p = fname; isalpha(*p); p++) {} + + // We accept alphabetic characters and a dash in scheme part. + // RFC 3986 allows for more, but it increases the risk of matching + // non-URL text. + + // first character must be alpha + if (!isalpha(*fname)) { + return 0; + } + + // check body: alpha or dash + for (p = fname + 1; (isalpha(*p) || (*p == '-')); p++) {} + + // check last char is not a dash + if (p[-1] == '-') { + return 0; + } + + // "://" or ":\\" must follow return path_is_url(p); } @@ -2245,11 +2289,17 @@ int path_full_dir_name(char *directory, char *buffer, size_t len) } if (os_chdir(directory) != SUCCESS) { - // Do not return immediately since we may be in the wrong directory. - retval = FAIL; - } - - if (retval == FAIL || os_dirname((char_u *)buffer, len) == FAIL) { + // Path does not exist (yet). For a full path fail, + // will use the path as-is. For a relative path use + // the current directory and append the file name. + if (path_is_absolute((const char_u *)directory)) { + // Do not return immediately since we may be in the wrong directory. + retval = FAIL; + } else { + xstrlcpy(buffer, old_dir, len); + append_path(buffer, directory, len); + } + } else if (os_dirname((char_u *)buffer, len) == FAIL) { // Do not return immediately since we are in the wrong directory. retval = FAIL; } @@ -2351,9 +2401,11 @@ static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int fo int path_is_absolute(const char_u *fname) { #ifdef WIN32 + if (*fname == NUL) { + return false; + } // A name like "d:/foo" and "//server/share" is absolute - return ((isalpha(fname[0]) && fname[1] == ':' - && vim_ispathsep_nocolon(fname[2])) + return ((isalpha(fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2])) || (vim_ispathsep_nocolon(fname[0]) && fname[0] == fname[1])); #else // UNIX: This just checks if the file name starts with '/' or '~'. diff --git a/src/nvim/plines.c b/src/nvim/plines.c index a061f76f34..a572f747df 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -83,7 +83,7 @@ int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight) return 1; } - // A folded lines is handled just like an empty line. + // Folded lines are handled just like an empty line. if (lineFolded(wp, lnum)) { return 1; } diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po index 5dda7c59f5..9633bec9f2 100644 --- a/src/nvim/po/ja.euc-jp.po +++ b/src/nvim/po/ja.euc-jp.po @@ -1644,15 +1644,6 @@ msgstr " [w]" msgid " written" msgstr " ½ñ¹þ¤ß" -msgid "E205: Patchmode: can't save original file" -msgstr "E205: patchmode: ¸¶ËÜ¥Õ¥¡¥¤¥ë¤òÊݸ¤Ç¤¤Þ¤»¤ó" - -msgid "E206: patchmode: can't touch empty original file" -msgstr "E206: patchmode: ¶õ¤Î¸¶ËÜ¥Õ¥¡¥¤¥ë¤òtouch¤Ç¤¤Þ¤»¤ó" - -msgid "E207: Can't delete backup file" -msgstr "E207: ¥Ð¥Ã¥¯¥¢¥Ã¥×¥Õ¥¡¥¤¥ë¤ò¾Ã¤»¤Þ¤»¤ó" - msgid "" "\n" "WARNING: Original file may be lost or damaged\n" diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po index a169bd3589..c363c00fa6 100644 --- a/src/nvim/po/ja.po +++ b/src/nvim/po/ja.po @@ -1644,15 +1644,6 @@ msgstr " [w]" msgid " written" msgstr " 書込ã¿" -msgid "E205: Patchmode: can't save original file" -msgstr "E205: patchmode: 原本ファイルをä¿å˜ã§ãã¾ã›ã‚“" - -msgid "E206: patchmode: can't touch empty original file" -msgstr "E206: patchmode: 空ã®åŽŸæœ¬ãƒ•ã‚¡ã‚¤ãƒ«ã‚’touchã§ãã¾ã›ã‚“" - -msgid "E207: Can't delete backup file" -msgstr "E207: ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ファイルを消ã›ã¾ã›ã‚“" - msgid "" "\n" "WARNING: Original file may be lost or damaged\n" diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po index 3a96ece2fb..5d3e51b7e2 100644 --- a/src/nvim/po/ru.po +++ b/src/nvim/po/ru.po @@ -959,7 +959,6 @@ msgstr "E129: ТребуетÑÑ Ð¸Ð¼Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸" #, c-format msgid "E128: Function name must start with a capital or \"s:\": %s" msgstr "E128: Ð˜Ð¼Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¸ должно начинатьÑÑ Ñ Ð·Ð°Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹ буквы или \"s:\": %s" -"двоеточие: %s" #: ../eval.c:17833 #, c-format @@ -1578,7 +1577,7 @@ msgstr "E494: ИÑпользуйте w или w>>" #: ../ex_docmd.c:3454 msgid "E319: The command is not available in this version" -msgstr "E319: Извините, Ñта команда недоÑтупна в данной верÑии" +msgstr "E319: Ðта команда недоÑтупна в данной верÑии" #: ../ex_docmd.c:3752 msgid "E172: Only one file name allowed" @@ -2055,7 +2054,7 @@ msgstr "E201: Ðвтокоманды *ReadPre не должны изменÑть #: ../fileio.c:672 msgid "Nvim: Reading from stdin...\n" -msgstr "Vim: Чтение из Ñтандартного потока ввода stdin...\n" +msgstr "Nvim: Чтение из Ñтандартного потока ввода stdin...\n" #. Re-opening the original file failed! #: ../fileio.c:909 @@ -2427,7 +2426,7 @@ msgstr "--Удалено--" #: ../fileio.c:5732 #, c-format msgid "auto-removing autocommand: %s <buffer=%d>" -msgstr "авто-удаление автокоманды: %s <буффер=%d>" +msgstr "авто-удаление автокоманды: %s <буфер=%d>" #. the group doesn't exist #: ../fileio.c:5772 @@ -2666,11 +2665,11 @@ msgstr "E17: \"%s\" ÑвлÑетÑÑ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³Ð¾Ð¼" #: ../globals.h:1020 #, fuzzy msgid "E900: Invalid job id" -msgstr "E49: ÐедопуÑтимый размер прокрутки" +msgstr "E900: ÐедопуÑтимый идентификатор заданиÑ" #: ../globals.h:1021 msgid "E901: Job table is full" -msgstr "" +msgstr "E901: Таблица заданий переполнена" #: ../globals.h:1024 #, c-format @@ -2707,9 +2706,7 @@ msgstr "E477: ! не допуÑкаетÑÑ" #: ../globals.h:1035 msgid "E25: Nvim does not have a built-in GUI" -msgstr "" -"E25: ВозможноÑть иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð³Ñ€Ð°Ñ„Ð¸Ñ‡ÐµÑкого интерфейÑа выключена при " -"компилÑции" +msgstr "E25: У Nvim нет вÑтроенного графичеÑкого интерфейÑа" #: ../globals.h:1036 #, c-format @@ -3351,10 +3348,10 @@ msgstr "E282: Ðевозможно выполнить чтение из \"%s\"" #: ../main.c:2149 msgid "" "\n" -"More info with: \"vim -h\"\n" +"More info with: \"" msgstr "" "\n" -"Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ: \"vim -h\"\n" +"Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ: \"" #: ../main.c:2178 msgid "[file ..] edit specified file(s)" @@ -4413,7 +4410,7 @@ msgstr "E663: Ð’ конце ÑпиÑка изменений" #: ../normal.c:7053 msgid "Type :quit<Enter> to exit Nvim" -msgstr "Введите :quit<Enter> Ð´Ð»Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð° из Vim" +msgstr "Введите :quit<Enter> Ð´Ð»Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð° из Nvim" #: ../ops.c:248 #, c-format @@ -4521,6 +4518,7 @@ msgid "" "lines" msgstr "" "E883: шаблон поиÑка и региÑтр Ð²Ñ‹Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð½Ðµ могут Ñодержать двух или более " +"Ñтрок" #: ../ops.c:5089 #, c-format @@ -6137,7 +6135,7 @@ msgstr "Vim: Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð²Ð²Ð¾Ð´Ð°, выход...\n" #: ../undo.c:379 #, fuzzy msgid "E881: Line count changed unexpectedly" -msgstr "E834: Ðеожиданно изменилÑÑ Ñчётчик Ñтрок" +msgstr "E881: Ðеожиданно изменилÑÑ Ñчётчик Ñтрок" #: ../undo.c:627 #, c-format diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index f0ae154648..7f0fe6a197 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: vim 7.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-01-18 17:46+0200\n" +"POT-Creation-Date: 2022-04-13 10:28+0300\n" "PO-Revision-Date: 2020-08-23 20:19+0300\n" "Last-Translator: Ðнатолій Сахнік <sakhnik@gmail.com>\n" "Language-Team: Ukrainian\n" @@ -40,18 +40,6 @@ msgstr "E936: Ðе вдалоÑÑ Ð·Ð½Ð¸Ñ‰Ð¸Ñ‚Ð¸ цю групу" msgid "W19: Deleting augroup that is still in use" msgstr "W19: Ð—Ð½Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð°Ð²Ñ‚Ð¾Ð³Ñ€ÑƒÐ¿Ð¸, Ñка вÑе ще викориÑтовуєтьÑÑ" -#, c-format -msgid "E215: Illegal character after *: %s" -msgstr "E215: Ðедозволений Ñимвол піÑÐ»Ñ *: %s" - -#, c-format -msgid "E216: No such event: %s" -msgstr "E216: Ðемає такої події: %s" - -#, c-format -msgid "E216: No such group or event: %s" -msgstr "E216: Ðемає такої групи чи події: %s" - msgid "" "\n" "--- Autocommands ---" @@ -84,6 +72,18 @@ msgstr "ВиконуєтьÑÑ %s" msgid "autocommand %s" msgstr "автокоманда %s" +#, c-format +msgid "E215: Illegal character after *: %s" +msgstr "E215: Ðедозволений Ñимвол піÑÐ»Ñ *: %s" + +#, c-format +msgid "E216: No such event: %s" +msgstr "E216: Ðемає такої події: %s" + +#, c-format +msgid "E216: No such group or event: %s" +msgstr "E216: Ðемає такої групи чи події: %s" + msgid "[Location List]" msgstr "[СпиÑок міÑць]" @@ -111,27 +111,6 @@ msgstr "E516: Жоден з буферів не знищено" msgid "E517: No buffers were wiped out" msgstr "E517: Жоден з буферів не витерто" -msgid "1 buffer unloaded" -msgstr "Вивантажено один буфер" - -#, c-format -msgid "%d buffers unloaded" -msgstr "Вивантажено %d буфери(ів)" - -msgid "1 buffer deleted" -msgstr "Знищено один буфер" - -#, c-format -msgid "%d buffers deleted" -msgstr "Знищено %d буфери(ів)" - -msgid "1 buffer wiped out" -msgstr "Витерто один буфер" - -#, c-format -msgid "%d buffers wiped out" -msgstr "Витерто %d буфери(ів)" - msgid "E90: Cannot unload last buffer" msgstr "E90: Ðе можу вивантажити оÑтанній буфер" @@ -148,14 +127,14 @@ msgid "E88: Cannot go before first buffer" msgstr "E88: Це вже найперший буфер" #, c-format -msgid "E89: %s will be killed (add ! to override)" -msgstr "E89: «%s» буде вбито (! щоб не зважати)" - -#, c-format msgid "" "E89: No write since last change for buffer %<PRId64> (add ! to override)" msgstr "E89: Буфер %<PRId64> має зміни (! щоб не зважати)" +#, c-format +msgid "E89: %s will be killed (add ! to override)" +msgstr "E89: «%s» буде вбито (! щоб не зважати)" + msgid "E948: Job still running (add ! to end the job)" msgstr "E948: Задача вÑе ще виконуєтьÑÑ (! щоб закінчити)" @@ -206,14 +185,6 @@ msgid "[readonly]" msgstr "[лише читати]" #, c-format -msgid "1 line --%d%%--" -msgstr "один Ñ€Ñдок --%d%%--" - -#, c-format -msgid "%<PRId64> lines --%d%%--" -msgstr "%<PRId64> Ñ€Ñдки(ів) --%d%%--" - -#, c-format msgid "line %<PRId64> of %<PRId64> --%d%%-- col " msgstr "Ñ€Ñдок %<PRId64> з %<PRId64> --%d%%-- колонка " @@ -277,6 +248,51 @@ msgstr "E548: Потрібна цифра" msgid "E549: Illegal percentage" msgstr "E549: Ðеправильний відÑоток" +msgid "Entering Debug mode. Type \"cont\" to continue." +msgstr "Режим налагодженнÑ. Щоб продовжити введіть «cont»." + +#, c-format +msgid "Oldval = \"%s\"" +msgstr "Oldval = «%s»" + +#, c-format +msgid "Newval = \"%s\"" +msgstr "Newval = «%s»" + +#, c-format +msgid "line %<PRId64>: %s" +msgstr "Ñ€Ñдок %<PRId64>: %s" + +#, c-format +msgid "cmd: %s" +msgstr "команда: %s" + +msgid "frame is zero" +msgstr "кадр нульовий" + +#, c-format +msgid "frame at highest level: %d" +msgstr "кадр на найвищому рівні: %d" + +#, c-format +msgid "Breakpoint in \"%s%s\" line %<PRId64>" +msgstr "Точка зупинки в «%s%s» Ñ€Ñдок %<PRId64>" + +#, c-format +msgid "E161: Breakpoint not found: %s" +msgstr "E161: Точку зупинки не знайдено: %s" + +msgid "No breakpoints defined" +msgstr "Ðе визначено жодної точки зупинки" + +#, c-format +msgid "%3d %s %s line %<PRId64>" +msgstr "%3d %s %s Ñ€Ñдок %<PRId64>" + +#, c-format +msgid "%3d expr %s" +msgstr "%3d вираз %s" + #, c-format msgid "E96: Cannot diff more than %<PRId64> buffers" msgstr "E96: Ðе можна порівнювати понад %<PRId64> буфери(ів)" @@ -325,6 +341,19 @@ msgstr "E103: Буфер «%s» не в режимі порівнÑннÑ" msgid "E787: Buffer changed unexpectedly" msgstr "E787: Буфер неÑподівано змінивÑÑ" +#, c-format +msgid "E1214: Digraph must be just two characters: %s" +msgstr "E1214: Диграф має бути з двох Ñимволів: %s" + +#, c-format +msgid "E1215: Digraph must be one character: %s" +msgstr "E1215: Диграф має бути одним Ñимволом: %s" + +msgid "" +"E1216: digraph_setlist() argument must be a list of lists with two items" +msgstr "" +"E1216: аргумент digraph_setlist() має бути ÑпиÑком ÑпиÑків з двох елементів" + msgid "E104: Escape not allowed in digraph" msgstr "E104: У диграфах не може міÑтитиÑÑ escape" @@ -528,14 +557,21 @@ msgstr "E461: ÐеприпуÑтима назва змінної: %s" msgid "E995: Cannot modify existing variable" msgstr "E995: Ðеможливо змінити наÑвну змінну" -msgid "E957: Invalid window number" -msgstr "E957: Ðекоректний номер вікна" +msgid "E274: No white space allowed before parenthesis" +msgstr "E274: Перед дужками не має бути пробілу" #, c-format msgid "E940: Cannot lock or unlock variable %s" msgstr "E940: Ðеможливо заблокувати чи розблокувати змінну %s" #, c-format +msgid "E80: Error while writing: %s" +msgstr "E80: Помилка під Ñ‡Ð°Ñ Ð·Ð°Ð¿Ð¸Ñу: %s" + +msgid "E1098: String, List or Blob required" +msgstr "E1098: Потрібен String, List чи Blob" + +#, c-format msgid "E734: Wrong variable type for %s=" msgstr "E734: Ðеправильний тип змінної Ð´Ð»Ñ %s=" @@ -564,8 +600,8 @@ msgstr "E687: Цілей менше, ніж елементів ÑпиÑку" msgid "E688: More targets than List items" msgstr "E688: Цілей більше, ніж елементів ÑпиÑку" -msgid "Double ; in list of variables" -msgstr "Друга ; у ÑпиÑку змінних" +msgid "E452: Double ; in list of variables" +msgstr "E452: Друга ; у ÑпиÑку змінних" #, c-format msgid "E738: Can't list variables for %s" @@ -584,8 +620,8 @@ msgstr "E996: Ðеможливо заблокувати регіÑтр" msgid "E121: Undefined variable: %.*s" msgstr "E121: Ðевизначена змінна: %.*s" -msgid "E689: Can only index a List or Dictionary" -msgstr "E689: ІндекÑний доÑтуп може бути тільки до ÑпиÑку чи Ñловника" +msgid "E689: Can only index a List, Dictionary or Blob" +msgstr "E689: ІндекÑний доÑтуп може бути тільки до List, Dictionary чи Blob" msgid "E708: [:] must come last" msgstr "E708: [:] має бути оÑтанньою" @@ -593,8 +629,11 @@ msgstr "E708: [:] має бути оÑтанньою" msgid "E713: Cannot use empty key after ." msgstr "E713: Ðеможливо вжити порожній ключ піÑÐ»Ñ ." -msgid "E709: [:] requires a List value" -msgstr "E709: [:] вимагає ÑпиÑок" +msgid "E709: [:] requires a List or Blob value" +msgstr "E709: [:] вимагає List чи Blob" + +msgid "E972: Blob value does not have the right number of bytes" +msgstr "E972: неправильна кількіÑть байтів у значенні Blob" msgid "E996: Cannot lock a range" msgstr "E996: Ðеможливо заблокувати діапазон" @@ -618,27 +657,18 @@ msgstr "E108: Змінної немає: «%s»" msgid "E109: Missing ':' after '?'" msgstr "E109: Бракує ':' піÑÐ»Ñ '?'" -msgid "E691: Can only compare List with List" -msgstr "E691: СпиÑок можна порівнÑти тільки зі ÑпиÑком" - -msgid "E692: Invalid operation for List" -msgstr "E692: Ðекоректна Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð´ ÑпиÑком" - -msgid "E735: Can only compare Dictionary with Dictionary" -msgstr "E735: Словник можна порівнÑти тільки із Ñловником" - -msgid "E736: Invalid operation for Dictionary" -msgstr "E736: Ðекоректна Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð´ Ñловником" - -msgid "E694: Invalid operation for Funcrefs" -msgstr "E694: Ðекоректна Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð´ функцією" - msgid "E804: Cannot use '%' with Float" msgstr "E804: Ðе можна виконати '%' над Float" +msgid "E973: Blob literal should have an even number of hex characters" +msgstr "E973: Ð—Ð°Ð¿Ð¸Ñ Blob повинен мати парну кількіÑть шіÑтнадцÑткових Ñимволів" + msgid "E110: Missing ')'" msgstr "E110: Пропущено ')'" +msgid "E260: Missing name after ->" +msgstr "E260: ПіÑÐ»Ñ -> бракує імені" + msgid "E695: Cannot index a Funcref" msgstr "E695: Ð¤ÑƒÐ½ÐºÑ†Ñ–Ñ Ð½Ðµ має індекÑації" @@ -716,10 +746,6 @@ msgid "E921: Invalid callback argument" msgstr "E921: Ðекоректний аргумент функції зворотнього виклику" #, c-format -msgid "E80: Error while writing: %s" -msgstr "E80: Помилка під Ñ‡Ð°Ñ Ð·Ð°Ð¿Ð¸Ñу: %s" - -#, c-format msgid "E963: setting %s to value with wrong type" msgstr "E963: вÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ %s до Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð· неправильним типом" @@ -759,6 +785,24 @@ msgstr "E5009: Ðекоректна $VIMRUNTIME: %s" msgid "E5009: Invalid 'runtimepath'" msgstr "E5009: Ðекоректний 'runtimepath'" +msgid "E977: Can only compare Blob with Blob" +msgstr "E977: Блоб можна порівнÑти тільки із блобом" + +msgid "E691: Can only compare List with List" +msgstr "E691: СпиÑок можна порівнÑти тільки зі ÑпиÑком" + +msgid "E692: Invalid operation for List" +msgstr "E692: Ðекоректна Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð´ ÑпиÑком" + +msgid "E735: Can only compare Dictionary with Dictionary" +msgstr "E735: Словник можна порівнÑти тільки із Ñловником" + +msgid "E736: Invalid operation for Dictionary" +msgstr "E736: Ðекоректна Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð´ Ñловником" + +msgid "E694: Invalid operation for Funcrefs" +msgstr "E694: Ðекоректна Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð´ функцією" + #, c-format msgid "E474: Expected comma before list item: %s" msgstr "E474: ОчікуєтьÑÑ ÐºÐ¾Ð¼Ð° перед елементом ÑпиÑка: %s" @@ -1014,8 +1058,15 @@ 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 "E899: Argument of %s must be a List or Blob" +msgstr "E899: Ðргумент у %s має бути ÑпиÑком чи блобом" + +msgid "E957: Invalid window number" +msgstr "E957: Ðекоректний номер вікна" + +#, c-format +msgid "E998: Reduce of an empty %s with no initial value" +msgstr "E998: Ð¡ÐºÐ¾Ñ€Ð¾Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ€Ð¾Ð¶Ð½ÑŒÐ¾Ð³Ð¾ %s без початкового значеннÑ" #, c-format msgid "Error converting the call result: %s" @@ -1082,14 +1133,6 @@ 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" @@ -1127,14 +1170,6 @@ 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»" @@ -1168,6 +1203,9 @@ msgstr "E935: неправильний номер групи ÑпівпадінРmsgid "Can only call this function in an unmodified buffer" msgstr "Цю функцію можна викликати тільки у незміненому буфері" +msgid "writefile() first argument must be a List or a Blob" +msgstr "перший аргумент writefile() має бути List або Blob" + #, c-format msgid "E5060: Unknown flag: %s" msgstr "E5060: Ðевідомий прапорець: %s" @@ -1228,6 +1266,9 @@ msgstr "E745: ОчікуєтьÑÑ Number чи String, трапивÑÑ List" msgid "E728: Expected a Number or a String, Dictionary found" msgstr "E728: ОчікуєтьÑÑ Number чи String, трапивÑÑ Dictionary" +msgid "E974: Expected a Number or a String, Blob found" +msgstr "E974: ОчікуєтьÑÑ Number чи String, трапивÑÑ Blob" + msgid "E5299: Expected a Number or a String, Boolean found" msgstr "E5299: ОчікуєтьÑÑ Number чи String, трапивÑÑ Boolean" @@ -1243,6 +1284,9 @@ msgstr "E728: Dictionary вжито Ñк Number" msgid "E805: Using a Float as a Number" msgstr "E805: Float вжито Ñк Number" +msgid "E974: Using a Blob as a Number" +msgstr "E974: Blob вжито Ñк Number" + msgid "E685: using an invalid value as a Number" msgstr "E685: некоректне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð¶Ð¸Ñ‚Ð¾ Ñк Number" @@ -1252,6 +1296,9 @@ msgstr "E730: List вжито Ñк String" msgid "E731: using Dictionary as a String" msgstr "E731: Dictionary вжито Ñк String" +msgid "E976: using Blob as a String" +msgstr "E976: Blob вжито Ñк String" + msgid "E908: using an invalid value as a String" msgstr "E908: некоректне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð²Ð¶Ð¸Ñ‚Ð¾ Ñк String" @@ -1273,6 +1320,9 @@ msgstr "E362: ВикориÑтано логічне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñк Float" msgid "E907: Using a special value as a Float" msgstr "E907: ВикориÑтано Ñпеціальне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñк Float" +msgid "E975: Using a Blob as a Float" +msgstr "E975: Blob вжито Ñк Float" + msgid "E808: Number or Float required" msgstr "E808: Треба вказати Number чи Float" @@ -1298,6 +1348,9 @@ msgstr "E125: Ðедозволений аргумент: %s" msgid "E853: Duplicate argument name: %s" msgstr "E853: Ðазва аргументу повторюєтьÑÑ: %s" +msgid "E989: Non-default argument follows default argument" +msgstr "E989: Ðргумент без домовленого Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð¿Ñ–ÑÐ»Ñ Ð°Ñ€Ð³ÑƒÐ¼ÐµÐ½Ñ‚Ñƒ з домовленим значеннÑм" + #, c-format msgid "E740: Too many arguments for function %s" msgstr "E740: Забагато аргументів Ð´Ð»Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ— %s" @@ -1337,6 +1390,10 @@ msgid "E117: Unknown function: %s" msgstr "E117: Ðевідома функціÑ: %s" #, c-format +msgid "E276: Cannot use function as a method: %s" +msgstr "E276: Ðе можна вжити функцію Ñк метод: %s" + +#, c-format msgid "E933: Function was deleted: %s" msgstr "E933: Функцію було видалено: %s" @@ -1408,10 +1465,6 @@ 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 має бути вузол:порт" @@ -1448,13 +1501,6 @@ msgstr "> %d, шіÑÑ‚ %08x, Ð²Ñ–Ñ %o" msgid "E134: Cannot move a range of lines into itself" msgstr "E134: Ðеможливо переміÑтити діапазон Ñ€Ñдків Ñам у Ñебе" -msgid "1 line moved" -msgstr "Переміщено один Ñ€Ñдок" - -#, c-format -msgid "%<PRId64> lines moved" -msgstr "Переміщено %<PRId64> Ñ€Ñдки(ів)" - #, c-format msgid "E482: Can't create file %s" msgstr "E482: Ðе вдалоÑÑ Ñтворити файл %s" @@ -1533,27 +1579,6 @@ msgstr "Замінити на %s (y/n/a/q/l/^E/^Y)?" msgid "(Interrupted) " msgstr "(Перервано) " -msgid "1 match" -msgstr "Один збіг" - -msgid "1 substitution" -msgstr "Одна заміна" - -#, c-format -msgid "%<PRId64> matches" -msgstr "%<PRId64> збіги(ів)" - -#, c-format -msgid "%<PRId64> substitutions" -msgstr "%<PRId64> замін(и)" - -msgid " on 1 line" -msgstr " в одному Ñ€Ñдку" - -#, c-format -msgid " on %<PRId64> lines" -msgstr " в %<PRId64> Ñ€Ñдках" - msgid "E147: Cannot do :global recursive with a range" msgstr "E147: :global не можна рекурÑивно з діапазоном" @@ -1610,39 +1635,6 @@ msgstr "E150: Ðе Ñ” каталогом: %s" msgid "No old files" msgstr "Жодного Ñтарого файлу" -msgid "Entering Debug mode. Type \"cont\" to continue." -msgstr "Режим налагодженнÑ. Щоб продовжити введіть «cont»." - -#, c-format -msgid "line %<PRId64>: %s" -msgstr "Ñ€Ñдок %<PRId64>: %s" - -#, c-format -msgid "cmd: %s" -msgstr "команда: %s" - -msgid "frame is zero" -msgstr "кадр нульовий" - -#, c-format -msgid "frame at highest level: %d" -msgstr "кадр на найвищому рівні: %d" - -#, c-format -msgid "Breakpoint in \"%s%s\" line %<PRId64>" -msgstr "Точка зупинки в «%s%s» Ñ€Ñдок %<PRId64>" - -#, c-format -msgid "E161: Breakpoint not found: %s" -msgstr "E161: Точку зупинки не знайдено: %s" - -msgid "No breakpoints defined" -msgstr "Ðе визначено жодної точки зупинки" - -#, c-format -msgid "%3d %s %s line %<PRId64>" -msgstr "%3d %s %s Ñ€Ñдок %<PRId64>" - msgid "E750: First use \":profile start {fname}\"" msgstr "E750: Спочатку зробіть «:profile start {файл}»" @@ -1683,10 +1675,6 @@ msgid "E666: compiler not supported: %s" msgstr "E666: КомпілÑтор не підтримуєтьÑÑ: %s" #, c-format -msgid ":source error parsing command %s" -msgstr ":source помилка розбору команди %s" - -#, c-format msgid "Cannot source a directory: \"%s\"" msgstr "Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ‡Ð¸Ñ‚Ð°Ñ‚Ð¸ каталог: «%s»" @@ -1725,6 +1713,9 @@ msgstr "змінна оточеннÑ" msgid "error handler" msgstr "обробник помилки" +msgid "changed window size" +msgstr "змінено розмір вікна" + msgid "Lua" msgstr "Lua" @@ -1735,6 +1726,10 @@ msgstr "Клієнт API (канал «%<PRIu64>»)" msgid "anonymous :source" msgstr "анонімний :source" +#, c-format +msgid "anonymous :source (script id %d)" +msgstr "анонімний :source (ід. Ñкрипта %d)" + msgid "W15: Warning: Wrong line separator, ^M may be missing" msgstr "W15: ЗаÑтереженнÑ: Ðеправильний роздільник Ñ€Ñдків, можливо, бракує ^M" @@ -1752,6 +1747,14 @@ msgstr "Мова (%s): «%s»" msgid "E197: Cannot set language to \"%s\"" msgstr "E197: Ðе вдалоÑÑ Ð²Ñтановити мову «%s»" +#, c-format +msgid "E184: No such user-defined command: %s" +msgstr "E184: Команду кориÑтувача не знайдено: %s" + +#, c-format +msgid "E1237: No such user-defined command in current buffer: %s" +msgstr "E1237: Ðемає такої команди кориÑтувача у цьому буфері: %s" + msgid "Entering Ex mode. Type \"visual\" to go to Normal mode." msgstr "Режим Ex. Ð”Ð»Ñ Ð¿Ð¾Ð²ÐµÑ€Ð½ÐµÐ½Ð½Ñ Ð´Ð¾ нормального режиму виконайте «visual»" @@ -1796,7 +1799,8 @@ msgstr "E494: Спробуйте w або w>>" msgid "" "INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX" msgstr "" -"Ð’ÐУТРІШÐЄ: Ðе можна вживати EX_DFLALL з ADDR_NONE, ADDR_UNSIGNED чи ADDR_QUICKFIX" +"Ð’ÐУТРІШÐЄ: Ðе можна вживати EX_DFLALL з ADDR_NONE, ADDR_UNSIGNED чи " +"ADDR_QUICKFIX" msgid "E943: Command table needs to be updated, run 'make'" msgstr "E943: Потрібно поновити таблицю команд, запуÑтіть 'make'" @@ -1804,20 +1808,6 @@ msgstr "E943: Потрібно поновити таблицю команд, зРmsgid "E319: The command is not available in this version" msgstr "E319: Вибачте, цієї команди немає у цій верÑÑ–Ñ—" -msgid "1 more file to edit. Quit anyway?" -msgstr "ЗалишилоÑÑ Ð²Ñ–Ð´Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ñ‚Ð¸ ще один файл. Ð’Ñе одно вийти?" - -#, c-format -msgid "%d more files to edit. Quit anyway?" -msgstr "Ще Ñ” %d не редагованих файлів. Ð’Ñе одно вийти?" - -msgid "E173: 1 more file to edit" -msgstr "E173: ЗалишилоÑÑ Ð²Ñ–Ð´Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ñ‚Ð¸ ще один файл" - -#, c-format -msgid "E173: %<PRId64> more files to edit" -msgstr "E173: ЗалишилоÑÑ %<PRId64> не редагованих файлів" - #, c-format msgid "E174: Command already exists: add ! to replace it: %s" msgstr "E174: Команда вже Ñ–Ñнує, ! щоб замінити Ñ—Ñ—: %s" @@ -1854,6 +1844,9 @@ msgstr "E179: Ð´Ð»Ñ -addr потрібний аргумент" msgid "E181: Invalid attribute: %s" msgstr "E181: Ðеправильний атрибут: %s" +msgid "E1208: -complete used without -nargs" +msgstr "E1208: -complete вжито без without -nargs" + msgid "E182: Invalid command name" msgstr "E182: Ðеправильна назва команди" @@ -1865,10 +1858,6 @@ msgstr "" "E841: Зарезервована назва, не можна викориÑтати Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувацької команди" #, c-format -msgid "E184: No such user-defined command: %s" -msgstr "E184: Команду кориÑтувача не знайдено: %s" - -#, c-format msgid "E180: Invalid address type value: %s" msgstr "E180: Ðеправильне Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ‚Ð¸Ð¿Ñƒ адреÑи: %s" @@ -1949,7 +1938,7 @@ msgstr "E842: немає номера Ñ€Ñдка, щоб викориÑтати msgid "E961: no line number to use for \"<sflnum>\"" msgstr "E961: немає номера Ñ€Ñдка, щоб викориÑтати з «<sflnum>»" -#, c-format +#, no-c-format msgid "E499: Empty file name for '%' or '#', only works with \":p:h\"" msgstr "E499: Ðазва файлу Ð´Ð»Ñ '%' чи '#' порожнÑ, працює лише з «:p:h»" @@ -2106,6 +2095,9 @@ msgstr " тип файлу\n" msgid "'history' option is zero" msgstr "ÐžÐ¿Ñ†Ñ–Ñ 'history' порожнÑ" +msgid "[Command Line]" +msgstr "[Ð Ñдок Команд]" + msgid "E199: Active window or buffer deleted" msgstr "E199: Ðктивне вікно або буфер було знищено" @@ -2228,6 +2220,10 @@ msgstr "Ðе придатний Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу" msgid "is read-only (add ! to override)" msgstr "лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ (! щоб не зважати)" +#, c-format +msgid "E303: Unable to create directory \"%s\" for backup file: %s" +msgstr "E303: Ðе вдалоÑÑ Ñтворити каталог «%s» Ð´Ð»Ñ Ñ€ÐµÐ·ÐµÑ€Ð²Ð½Ð¾Ð³Ð¾ файлу: %s" + msgid "E506: Can't write to backup file (add ! to override)" msgstr "E506: Ðе вдалоÑÑ Ð·Ð°Ð¿Ð¸Ñати резервний файл (! щоб не зважати)" @@ -2322,20 +2318,6 @@ msgstr "[формат unix]" msgid "[unix]" msgstr "[unix]" -msgid "1 line, " -msgstr "один Ñ€Ñдок, " - -#, c-format -msgid "%<PRId64> lines, " -msgstr "%<PRId64> Ñ€Ñдків, " - -msgid "1 character" -msgstr "один Ñимвол" - -#, c-format -msgid "%<PRId64> characters" -msgstr "%<PRId64> Ñимволів" - msgid "[Incomplete last line]" msgstr "[Ðеповний оÑтанній Ñ€Ñдок]" @@ -2399,10 +2381,12 @@ msgstr "ЗаÑтереженнÑ" msgid "" "&OK\n" -"&Load File" +"&Load File\n" +"Load File &and Options" msgstr "" -"&O:Гаразд\n" -"&L:Завантажити" +"[&O]Гаразд\n" +"[&L]Завантажити файл\n" +"[&a]Завантажити файл Ñ– опції" #, c-format msgid "E462: Could not prepare for reloading \"%s\"" @@ -2550,6 +2534,9 @@ msgstr "E476: Ðекоректна команда" msgid "E17: \"%s\" is a directory" msgstr "E17: «%s» — це каталог" +msgid "E756: Spell checking is not possible" +msgstr "E756: Перевірка орфографії неможлива" + msgid "E900: Invalid channel id" msgstr "E900: Ðекоректний канал" @@ -2718,7 +2705,14 @@ msgid "E928: String required" msgstr "E928: Потрібен String" msgid "E715: Dictionary required" -msgstr "E715: Потрібен Ñловник" +msgstr "E715: Потрібен Dictionary" + +#, c-format +msgid "E979: Blob index out of range: %<PRId64>" +msgstr "E979: Ð†Ð½Ð´ÐµÐºÑ Blob поза межами: %<PRId64>" + +msgid "E978: Invalid operation for Blob" +msgstr "E978: Ðекоректна Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð´ Blob" #, c-format msgid "E118: Too many arguments for function: %s" @@ -2731,10 +2725,17 @@ msgstr "E716: Ðемає такого ключа у Ñловнику: «%s»" msgid "E714: List required" msgstr "E714: Потрібен ÑпиÑок" +msgid "E897: List or Blob required" +msgstr "E897: Потрібен List або Blob" + #, c-format msgid "E712: Argument of %s must be a List or Dictionary" msgstr "E712: Ðргумент у %s має бути ÑпиÑком чи Ñловником" +#, c-format +msgid "E896: Argument of %s must be a List, Dictionary or Blob" +msgstr "E896: Ðргумент у %s має бути List, Dictionary чи Blob" + msgid "E47: Error while reading errorfile" msgstr "E47: Помилка Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ помилок" @@ -2802,6 +2803,10 @@ msgstr "E939: Потрібна додана кількіÑть" msgid "E81: Using <SID> not in a script context" msgstr "E81: <SID> викориÑтовуєтьÑÑ Ð½Ðµ в контекÑті Ñкрипту" +#, c-format +msgid "E107: Missing parentheses: %s" +msgstr "E107: Пропущено дужки: %s" + msgid "E363: pattern uses more memory than 'maxmempattern'" msgstr "E363: Зразок викориÑтовує більше, ніж 'maxmempattern', пам'Ñті" @@ -2832,6 +2837,13 @@ msgstr "E919: Каталог не знайдено у '%s': «%s»" msgid "E952: Autocommand caused recursive behavior" msgstr "E952: Ðвтокоманди призвели до рекурÑÑ–Ñ—" +msgid "E813: Cannot close autocmd window" +msgstr "E813: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸ вікно autocmd" + +#, c-format +msgid "E686: Argument of %s must be a List" +msgstr "E686: Ðргумент у %s має бути ÑпиÑком" + msgid "E519: Option not supported" msgstr "E519: ÐžÐ¿Ñ†Ñ–Ñ Ð½Ðµ підтримуєтьÑÑ" @@ -2869,6 +2881,21 @@ msgstr "E5601: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸ вікно, Ð·Ð°Ð»Ð¸ÑˆÐ¸Ð»Ð¾Ñ msgid "E5602: Cannot exchange or rotate float" msgstr "E5602: Ðе можна обмінÑти чи покрутити плавуче вікно" +msgid "E1142: Non-empty string required" +msgstr "E1142: Потрібен непорожній String" + +msgid "E1155: Cannot define autocommands for ALL events" +msgstr "E1155: Ðе можу визначити автокоманди Ð´Ð»Ñ Ð£Ð¡Ð†Ð¥ подій" + +msgid "E1240: Resulting text too long" +msgstr "E1240: ТекÑÑ‚ результату задовгий" + +msgid "E1247: Line number out of range" +msgstr "E1247: Ðомер Ñ€Ñдка вийшов поза межами" + +msgid "E1249: Highlight group name too long" +msgstr "E1249: Ðазва групи підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð´Ð¾Ð²Ð³Ð°" + msgid "search hit TOP, continuing at BOTTOM" msgstr "Пошук дійшов до ПОЧÐТКУ, продовжуєтьÑÑ Ð· КІÐЦЯ" @@ -2975,6 +3002,60 @@ msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð´Ñ€ÑƒÐºÑƒ відіÑлано." msgid "E424: Too many different highlighting attributes in use" msgstr "E424: ВикориÑтано забагато різних атрибутів кольору" +#, c-format +msgid "E411: highlight group not found: %s" +msgstr "E411: Групу підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ðµ знайдено: %s" + +#, c-format +msgid "E412: Not enough arguments: \":highlight link %s\"" +msgstr "E412: ÐедоÑтатньо аргументів: «:highlight link %s»" + +#, c-format +msgid "E413: Too many arguments: \":highlight link %s\"" +msgstr "E413: Забагато аргументів: «:highlight link %s»" + +msgid "E414: group has settings, highlight link ignored" +msgstr "E414: Грума має settings, highlight link проігноровано" + +#, c-format +msgid "E415: unexpected equal sign: %s" +msgstr "E415: ÐеÑподіваний знак рівноÑті: %s" + +#, c-format +msgid "E416: missing equal sign: %s" +msgstr "E416: Пропущено знак рівноÑті: %s" + +#, c-format +msgid "E417: missing argument: %s" +msgstr "E417: Пропущено аргумент: %s" + +#, c-format +msgid "E418: Illegal value: %s" +msgstr "E418: Ðеправильне значеннÑ: %s" + +msgid "E419: FG color unknown" +msgstr "E419: Ðевідомий колір текÑту" + +msgid "E420: BG color unknown" +msgstr "E420: Ðевідомий колір фону" + +#, c-format +msgid "E421: Color name or number not recognized: %s" +msgstr "E421: Ðерозпізнана назва або номер кольору: %s" + +#, c-format +msgid "E423: Illegal argument: %s" +msgstr "E423: Ðеправильний аргумент: %s" + +msgid "E669: Unprintable character in group name" +msgstr "E669: Ðедруковний Ñимвол у назві групи" + +msgid "W18: Invalid character in group name" +msgstr "W18: Ðекоректний Ñимвол у назві групи" + +msgid "E849: Too many highlight and syntax groups" +msgstr "E849: Забагато груп підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ñ– ÑинтакÑиÑу" + msgid "Add a new database" msgstr "Додати нову базу даних" @@ -3129,6 +3210,12 @@ msgstr "Жодного з'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· cscope\n" msgid " # pid database name prepend path\n" msgstr " # pid назва бази даних шлÑÑ…\n" +msgid "Type number and <Enter> or click with the mouse (q or empty cancels): " +msgstr "Ðаберіть чиÑло й <Enter> чи клацніть мишкою (q чи порожнє ÑкаÑовує): " + +msgid "Type number and <Enter> (q or empty cancels): " +msgstr "Ðаберіть чиÑло й <Enter> (q чи порожнє ÑкаÑовує): " + #, c-format msgid "E1502: Lua failed to grow stack to %i" msgstr "E1502: Lua не вдалоÑÑ Ð·Ð±Ñ–Ð»ÑŒÑˆÐ¸Ñ‚Ð¸ Ñтек до %i" @@ -3151,20 +3238,11 @@ msgstr "E5102: Lua не вдалоÑÑ Ð·Ð±Ñ–Ð»ÑŒÑˆÐ¸Ñ‚Ð¸ Ñтек до %i" 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 inspect module: %.*s" -msgstr "E5106: Помилка ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¼Ð¾Ð´ÑƒÐ»Ñ inspect: %.*s" - -#, c-format -msgid "E5106: Error while creating vim module: %.*s" -msgstr "E5106: Помилка ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¼Ð¾Ð´ÑƒÐ»Ñ vim: %.*s" +msgid "E970: Failed to initialize lua interpreter\n" +msgstr "E970: Ðе вдалоÑÑ Ñ–Ð½Ñ–Ñ†Ñ–Ð°Ð»Ñ–Ð·ÑƒÐ²Ð°Ñ‚Ð¸ інтерпретатор lua\n" -msgid "E970: Failed to initialize lua interpreter" -msgstr "E970: Ðе вдалоÑÑ Ñ–Ð½Ñ–Ñ†Ñ–Ð°Ð»Ñ–Ð·ÑƒÐ²Ð°Ñ‚Ð¸ інтерпретатор lua" +msgid "E970: Failed to initialize builtin lua modules\n" +msgstr "E970: Ðе вдалоÑÑ Ñ–Ð½Ñ–Ñ†Ñ–Ð°Ð»Ñ–Ð·ÑƒÐ²Ð°Ñ‚Ð¸ інтерпретатор lua\n" #, c-format msgid "E5114: Error while converting print argument #%i: %.*s" @@ -3179,6 +3257,10 @@ msgid "E5116: Error while calling debug string: %.*s" msgstr "E5116: Помилка виклику налагодженнÑ: %.*s" #, c-format +msgid "E5108: Error executing Lua function: %.*s" +msgstr "E5108: Помилка Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ— lua: %.*s" + +#, c-format msgid "E5107: Error loading lua %.*s" msgstr "E5107: Помилка Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ lua %.*s" @@ -3214,8 +3296,16 @@ 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 "Error executing vim._expand_pat: %.*s" +msgstr "Помилка Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ vim._expand_pat: %.*s" + +#, c-format +msgid "Error executing vim.on_key Lua callback: %.*s" +msgstr "Помилка Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð¾Ð±Ñ€Ð¾Ð±Ð½Ð¸ÐºÐ° Lua vim.on_key: %.*s" + +#, c-format +msgid "Error executing Lua callback: %.*s" +msgstr "Помилка Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð¾Ð±Ñ€Ð¾Ð±Ð½Ð¸ÐºÐ° Lua: %.*s" msgid "Argument missing after" msgstr "Пропущено аргумент піÑлÑ" @@ -3254,8 +3344,8 @@ msgid "pre-vimrc command line" msgstr "команди перед vimrc" #, c-format -msgid "Conflicting configs: \"%s\" \"%s\"" -msgstr "Суперечливі конфігурації: «%s» «%s»" +msgid "E5422: Conflicting configs: \"%s\" \"%s\"" +msgstr "E5422: Суперечливі конфігурації: «%s» «%s»" #, c-format msgid "E282: Cannot read from \"%s\"" @@ -3387,6 +3477,12 @@ msgstr " --listen <адреÑа> ОбÑлуговувати RPC API за ц msgid " --noplugin Don't load plugins\n" msgstr " --noplugin Ðе завантажувати доповненнÑ\n" +msgid " --remote[-subcommand] Execute commands remotely on a server\n" +msgstr " --remote[-subcommand] Виконати команди на віддаленому Ñервері\n" + +msgid " --server <address> Specify RPC server to send commands to\n" +msgstr " --server <адреÑа> Задати Ñервер RPC, куди Ñлати команди\n" + msgid " --startuptime <file> Write startup timing messages to <file>\n" msgstr " --startuptime <файл> ЗапиÑати профіль запуÑку до <файлу>\n" @@ -3425,6 +3521,46 @@ msgstr "" "\n" "змінити Ñ€Ñд. Ñтовп. текÑÑ‚" +#, c-format +msgid "E799: Invalid ID: %<PRId64> (must be greater than or equal to 1)" +msgstr "E799: Ðеправильний ID: %<PRId64> (має бути не меншим 1)" + +#, c-format +msgid "E801: ID already taken: %<PRId64>" +msgstr "E801: ID вже зайнÑто: %<PRId64>" + +#, c-format +msgid "E5030: Empty list at position %d" +msgstr "E5030: Порожній ÑпиÑок у позиції %d" + +#, c-format +msgid "E5031: List or number required at position %d" +msgstr "E5031: ОчікуєтьÑÑ ÑпиÑок чи чиÑло у позиції %d" + +#, c-format +msgid "E802: Invalid ID: %<PRId64> (must be greater than or equal to 1)" +msgstr "E802: Ðеправильний ID: %<PRId64> (має бути не меншим 1)" + +#, c-format +msgid "E803: ID not found: %<PRId64>" +msgstr "E803: ID не знайдено: %<PRId64>" + +#, 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 "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>" + msgid "E293: block was not locked" msgstr "E293: Блок не було зафікÑовано" @@ -3910,6 +4046,9 @@ msgstr "Перервано: " msgid "Press ENTER or type command to continue" msgstr "ÐатиÑніть ENTER або введіть команду Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð²Ð¶ÐµÐ½Ð½Ñ" +msgid " (Interrupted)" +msgstr " (Перервано)" + #, c-format msgid "%s line %<PRId64>" msgstr "%s Ñ€Ñдок %<PRId64>" @@ -3952,38 +4091,9 @@ msgstr "" "&D:Жодного\n" "&C:СкаÑувати" -msgid "Type number and <Enter> or click with mouse (empty cancels): " -msgstr "Ðаберіть чиÑло й <Enter> чи клацніть мишкою (порожнє ÑкаÑовує): " - -msgid "Type number and <Enter> (empty cancels): " -msgstr "Ðаберіть чиÑло й <Enter> (порожнє ÑкаÑовує): " - -msgid "1 more line" -msgstr "додано один Ñ€Ñдок" - -msgid "1 line less" -msgstr "знищено один Ñ€Ñдок" - -#, c-format -msgid "%<PRId64> more lines" -msgstr "додано Ñ€Ñдків: %<PRId64>" - -#, c-format -msgid "%<PRId64> fewer lines" -msgstr "знищено Ñ€Ñдків: %<PRId64>" - -msgid " (Interrupted)" -msgstr " (Перервано)" - -msgid "Beep!" -msgstr "Дзень!" - msgid "E349: No identifier under cursor" msgstr "E349: Ðемає ідентифікатора над курÑором" -msgid "E774: 'operatorfunc' is empty" -msgstr "E774: 'operatorfunc' порожнÑ" - msgid "E348: No string under cursor" msgstr "E348: Ðемає Ñ€Ñдка на курÑорі" @@ -4007,63 +4117,17 @@ msgid "Type :qa and press <Enter> to exit Nvim" msgstr "Введіть :qa Ñ– натиÑніÑть <Enter> щоб вийти з Nvim" #, c-format -msgid "1 line %sed 1 time" -msgstr "Один Ñ€Ñдок %s-но" - -#, c-format -msgid "1 line %sed %d times" -msgstr "Один Ñ€Ñдок %s-но %d разів" - -#, c-format -msgid "%<PRId64> lines %sed 1 time" -msgstr "%<PRId64> Ñ€Ñдків %s-но" - -#, c-format -msgid "%<PRId64> lines %sed %d times" -msgstr "%<PRId64> Ñ€Ñдків %s-но %d разів" - -#, c-format msgid "%<PRId64> lines to indent... " msgstr "ЗалишилоÑÑ Ð²Ð¸Ñ€Ñ–Ð²Ð½Ñти %<PRId64> Ñ€Ñдків..." -msgid "1 line indented " -msgstr "ВирівнÑно один Ñ€Ñдок" - -#, c-format -msgid "%<PRId64> lines indented " -msgstr "ВирівнÑно Ñ€Ñдків: %<PRId64>" - msgid "E748: No previously used register" msgstr "E748: РегіÑтри перед цим не вживалиÑÑŒ" -msgid "1 line changed" -msgstr "Один Ñ€Ñдок змінено" - -#, c-format -msgid "%<PRId64> lines changed" -msgstr "Змінено Ñ€Ñдків: %<PRId64>" - #, c-format msgid " into \"%c" msgstr " у \"%c" #, c-format -msgid "block of 1 line yanked%s" -msgstr "Запам'Ñтав блок з одного Ñ€Ñдка%s" - -#, c-format -msgid "1 line yanked%s" -msgstr "Запам'Ñтав один Ñ€Ñдок%s" - -#, c-format -msgid "block of %<PRId64> lines yanked%s" -msgstr "Запам'Ñтав блок із %<PRId64> Ñ€Ñдків%s" - -#, c-format -msgid "%<PRId64> lines yanked%s" -msgstr "Запам'Ñтав Ñ€Ñдків: %<PRId64>%s" - -#, c-format msgid "E353: Nothing in register %s" msgstr "E353: У регіÑтрі %s нічого немає" @@ -4120,6 +4184,9 @@ msgstr "" msgid "(+%<PRId64> for BOM)" msgstr "(+%<PRId64> Ð´Ð»Ñ BOM)" +msgid "E774: 'operatorfunc' is empty" +msgstr "E774: 'operatorfunc' порожнÑ" + msgid "E518: Unknown option" msgstr "E518: Ðевідома опціÑ" @@ -4168,8 +4235,8 @@ msgstr "E527: Бракує коми" msgid "E528: Must specify a ' value" msgstr "E528: Потрібно вказати Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ '" -msgid "E595: contains unprintable or wide character" -msgstr "E595: МіÑтить недруковні або розширені Ñимволи" +msgid "E595: 'showbreak' contains unprintable or wide character" +msgstr "E595: 'showbreak' міÑтить недруковні або розширені Ñимволи" #, c-format msgid "E535: Illegal character after <%c>" @@ -4399,67 +4466,18 @@ 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: Зразок занадто довгий" - -msgid "E50: Too many \\z(" -msgstr "E50: Забагато \\z(" - #, c-format -msgid "E51: Too many %s(" -msgstr "E51: Забагато %s(" - -msgid "E52: Unmatched \\z(" -msgstr "E52: Ðемає пари \\z(" +msgid "E1204: No Number allowed after .: '\\%%%c'" +msgstr "E1204: Number не можна піÑÐ»Ñ .: '\\%%%c'" #, c-format -msgid "E59: invalid character after %s@" -msgstr "E59: Ðедозволений Ñимвол піÑÐ»Ñ %s@" - -#, c-format -msgid "E60: Too many complex %s{...}s" -msgstr "E60: Забагато Ñкладних %s{...}" - -#, c-format -msgid "E61: Nested %s*" -msgstr "E61: Вкладені %s*" - -#, c-format -msgid "E62: Nested %s%c" -msgstr "E62: Вкладені %s%c" - -msgid "E63: invalid use of \\_" -msgstr "E63: Ðекоректно вжито \\_" - -#, c-format -msgid "E64: %s%c follows nothing" -msgstr "E64: ПіÑÐ»Ñ %s%c нічого немає" - -msgid "E68: Invalid character after \\z" -msgstr "E68: Ðеправильний Ñимвол піÑÐ»Ñ \\z" - -#, c-format -msgid "E678: Invalid character after %s%%[dxouU]" -msgstr "E678: Ðедозволений Ñимвол піÑÐ»Ñ %s%%[dxouU]" - -#, c-format -msgid "E71: Invalid character after %s%%" -msgstr "E71: Ðедозволений Ñимвол піÑÐ»Ñ %s%%" +msgid "E554: Syntax error in %s{...}" +msgstr "E554: СинтакÑична помилка в %s{...}" #, c-format msgid "E888: (NFA regexp) cannot repeat %s" msgstr "E888: (NFA regexp) неможливо повторити %s" -#, c-format -msgid "E554: Syntax error in %s{...}" -msgstr "E554: СинтакÑична помилка в %s{...}" - -msgid "External submatches:\n" -msgstr "Зовнішні під-збіги:\n" - msgid "" "E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " "used " @@ -4482,6 +4500,14 @@ msgstr "Пошук «%s»" msgid "not found in '%s': \"%s\"" msgstr "не знайдено в '%s': «%s»" +#, c-format +msgid "Searching for \"%s\" in runtime path" +msgstr "Пошук «%s» в шлÑху виконаннÑ" + +#, c-format +msgid "not found in runtime path: \"%s\"" +msgstr "не знайдено в шлÑху виконаннÑ: «%s»" + msgid " TERMINAL" msgstr " ТЕРМІÐÐЛ" @@ -4850,9 +4876,6 @@ msgstr " (не підтримуєтьÑÑ)" msgid "E759: Format error in spell file" msgstr "E759: Помилка формату у файлі орфографії" -msgid "E756: Spell checking is not enabled" -msgstr "E756: Перевірка орфографії не дозволена" - #, c-format msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\"" msgstr "" @@ -5193,6 +5216,9 @@ msgstr "E765: 'spellfile' не міÑтить %<PRId64> елементів" msgid "Word '%.*s' removed from %s" msgstr "Слово '%.*s' знищено з %s" +msgid "Seek error in spellfile" +msgstr "Помилка зміни позиції у файлі орфографії" + #, c-format msgid "Word '%.*s' added to %s" msgstr "Слово '%.*s' додано до %s" @@ -5222,36 +5248,6 @@ msgstr "Ð”Ð»Ñ Ð±ÑƒÑ„ÐµÑ€Ð° не визначено елементів ÑинтРmsgid "'redrawtime' exceeded, syntax highlighting disabled" msgstr "'redrawtime' перевищено, підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ ÑинтакÑиÑу вимкнено" -msgid "syntax conceal on" -msgstr "ÑинтакÑичне Ð¿Ñ€Ð¸Ñ…Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ ÑƒÐ²Ñ–Ð¼Ðº" - -msgid "syntax conceal off" -msgstr "ÑинтакÑичне Ð¿Ñ€Ð¸Ñ…Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ð¼Ðº" - -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 "ÑинтакÑÐ¸Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ñти вÑюди" - -msgid "syntax spell notoplevel" -msgstr "ÑинтакÑÐ¸Ñ Ð½Ðµ перевірÑти" - -msgid "syntax spell default" -msgstr "ÑинтакÑÐ¸Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÐ¾Ð²Ð¾" - -msgid "syntax iskeyword " -msgstr "ÑинтакÑÐ¸Ñ iskeyword " - msgid "syntax iskeyword not set" msgstr "не вÑтановлено ÑинтакÑÐ¸Ñ iskeyword" @@ -5400,63 +5396,6 @@ msgid "" msgstr "" " ВСЬОГО К-ТЬ СПІВП. ÐÐЙПОВІЛ. СЕРЕДÐ. ÐÐЗВРШÐБЛОÐ" -msgid "E679: recursive loop loading syncolor.vim" -msgstr "E679: РекурÑивний цикл Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ syncolor.vim" - -#, c-format -msgid "E411: highlight group not found: %s" -msgstr "E411: Групу підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ðµ знайдено: %s" - -#, c-format -msgid "E412: Not enough arguments: \":highlight link %s\"" -msgstr "E412: ÐедоÑтатньо аргументів: «:highlight link %s»" - -#, c-format -msgid "E413: Too many arguments: \":highlight link %s\"" -msgstr "E413: Забагато аргументів: «:highlight link %s»" - -msgid "E414: group has settings, highlight link ignored" -msgstr "E414: Грума має settings, highlight link проігноровано" - -#, c-format -msgid "E415: unexpected equal sign: %s" -msgstr "E415: ÐеÑподіваний знак рівноÑті: %s" - -#, c-format -msgid "E416: missing equal sign: %s" -msgstr "E416: Пропущено знак рівноÑті: %s" - -#, c-format -msgid "E417: missing argument: %s" -msgstr "E417: Пропущено аргумент: %s" - -#, c-format -msgid "E418: Illegal value: %s" -msgstr "E418: Ðеправильне значеннÑ: %s" - -msgid "E419: FG color unknown" -msgstr "E419: Ðевідомий колір текÑту" - -msgid "E420: BG color unknown" -msgstr "E420: Ðевідомий колір фону" - -#, c-format -msgid "E421: Color name or number not recognized: %s" -msgstr "E421: Ðерозпізнана назва або номер кольору: %s" - -#, c-format -msgid "E423: Illegal argument: %s" -msgstr "E423: Ðеправильний аргумент: %s" - -msgid "E669: Unprintable character in group name" -msgstr "E669: Ðедруковний Ñимвол у назві групи" - -msgid "W18: Invalid character in group name" -msgstr "W18: Ðекоректний Ñимвол у назві групи" - -msgid "E849: Too many highlight and syntax groups" -msgstr "E849: Забагато груп підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ñ– ÑинтакÑиÑу" - msgid "E555: at bottom of tag stack" msgstr "E555: Кінець Ñтеку міток" @@ -5542,6 +5481,9 @@ msgstr "E435: Ðе вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ мітку, тільки прип msgid "Duplicate field name: %s" msgstr "Ðазва Ð¿Ð¾Ð»Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÑŽÑ”Ñ‚ÑŒÑÑ: %s" +msgid "Beep!" +msgstr "Дзень!" + msgid "E881: Line count changed unexpectedly" msgstr "E881: КількіÑть Ñ€Ñдків неÑподівано змінилаÑÑ" @@ -5911,9 +5853,6 @@ msgstr "E443: Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñтити вікно, заважРmsgid "E444: Cannot close last window" msgstr "E444: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸ оÑтаннє вікно" -msgid "E813: Cannot close autocmd window" -msgstr "E813: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸ вікно autocmd" - msgid "E814: Cannot close window, only autocmd window would remain" msgstr "E814: Ðе вдалоÑÑ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸ вікно, залишилоÑÑ Ð± тільки вікно autocmd" @@ -5923,26 +5862,4 @@ msgstr "E445: У іншому вікні Ñ” зміни" msgid "E446: No file name under cursor" msgstr "E446: Ðемає назви файлу над курÑором" -#, c-format -msgid "E799: Invalid ID: %<PRId64> (must be greater than or equal to 1)" -msgstr "E799: Ðеправильний ID: %<PRId64> (має бути не меншим 1)" - -#, c-format -msgid "E801: ID already taken: %<PRId64>" -msgstr "E801: ID вже зайнÑто: %<PRId64>" - -#, c-format -msgid "E5030: Empty list at position %d" -msgstr "E5030: Порожній ÑпиÑок у позиції %d" -#, c-format -msgid "E5031: List or number required at position %d" -msgstr "E5031: ОчікуєтьÑÑ ÑпиÑок чи чиÑло у позиції %d" - -#, c-format -msgid "E802: Invalid ID: %<PRId64> (must be greater than or equal to 1)" -msgstr "E802: Ðеправильний ID: %<PRId64> (має бути не меншим 1)" - -#, c-format -msgid "E803: ID not found: %<PRId64>" -msgstr "E803: ID не знайдено: %<PRId64>" diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 606c03f838..e354a589a5 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -289,9 +289,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_rl) { pum_width = pum_col - pum_scrollbar + 1; } else { - assert(Columns - pum_col - pum_scrollbar >= INT_MIN - && Columns - pum_col - pum_scrollbar <= INT_MAX); - pum_width = (int)(Columns - pum_col - pum_scrollbar); + assert(Columns - pum_col - pum_scrollbar >= 0); + pum_width = Columns - pum_col - pum_scrollbar; } if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1) @@ -352,12 +351,11 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // not enough room, will use what we have if (pum_rl) { assert(Columns - 1 >= INT_MIN); - pum_col = (int)(Columns - 1); + pum_col = Columns - 1; } else { pum_col = 0; } - assert(Columns - 1 >= INT_MIN); - pum_width = (int)(Columns - 1); + pum_width = Columns - 1; } else { if (max_width > p_pw) { // truncate @@ -367,9 +365,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_rl) { pum_col = max_width - 1; } else { - assert(Columns - max_width >= INT_MIN - && Columns - max_width <= INT_MAX); - pum_col = (int)(Columns - max_width); + assert(Columns - max_width >= 0); + pum_col = Columns - max_width; } pum_width = max_width - pum_scrollbar; } @@ -386,7 +383,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i void pum_redraw(void) { int row = 0; - int col; + int grid_col; int attr_norm = win_hl_attr(curwin, HLF_PNI); int attr_select = win_hl_attr(curwin, HLF_PSI); int attr_scroll = win_hl_attr(curwin, HLF_PSB); @@ -479,7 +476,7 @@ void pum_redraw(void) // Display each entry, use two spaces for a Tab. // Do this 3 times: For the main text, kind and extra info - col = col_off; + grid_col = col_off; totwidth = 0; for (round = 1; round <= 3; ++round) { @@ -537,24 +534,15 @@ void pum_redraw(void) } } grid_puts_len(&pum_grid, rt, (int)STRLEN(rt), row, - col - size + 1, attr); + grid_col - size + 1, attr); xfree(rt_start); xfree(st); - col -= width; + grid_col -= width; } else { - int size = (int)STRLEN(st); - int cells = (int)mb_string2cells(st); - - // only draw the text that fits - while (size > 0 && col + cells > pum_width + pum_col) { - size--; - size -= utf_head_off(st, st + size); - cells -= utf_ptr2cells(st + size); - } - - grid_puts_len(&pum_grid, st, size, row, col, attr); + // use grid_puts_len() to truncate the text + grid_puts(&pum_grid, st, row, grid_col, attr); xfree(st); - col += width; + grid_col += width; } if (*p != TAB) { @@ -563,12 +551,12 @@ void pum_redraw(void) // Display two spaces for a Tab. if (pum_rl) { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col - 1, + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col - 1, attr); - col -= 2; + grid_col -= 2; } else { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col, attr); - col += 2; + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col, attr); + grid_col += 2; } totwidth += 2; // start text at next char @@ -599,21 +587,21 @@ void pum_redraw(void) if (pum_rl) { grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1, - col + 1, ' ', ' ', attr); - col = col_off - pum_base_width - n + 1; + grid_col + 1, ' ', ' ', attr); + grid_col = col_off - pum_base_width - n + 1; } else { - grid_fill(&pum_grid, row, row + 1, col, + grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_base_width + n, ' ', ' ', attr); - col = col_off + pum_base_width + n; + grid_col = col_off + pum_base_width + n; } totwidth = pum_base_width + n; } if (pum_rl) { - grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, col + 1, + grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, grid_col + 1, ' ', ' ', attr); } else { - grid_fill(&pum_grid, row, row + 1, col, col_off + pum_width, ' ', ' ', + grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_width, ' ', ' ', attr); } diff --git a/src/nvim/pos.h b/src/nvim/pos.h index d17e27906e..c60b008696 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -1,10 +1,8 @@ #ifndef NVIM_POS_H #define NVIM_POS_H -// for INT_MAX, LONG_MAX et al. -#include <limits.h> - -typedef long linenr_T; // line number type +/// Line number type +typedef long linenr_T; /// Format used to print values which have linenr_T type #define PRIdLINENR "ld" @@ -15,29 +13,29 @@ typedef int colnr_T; /// Maximal (invalid) line number enum { MAXLNUM = 0x7fffffff, }; + /// Maximal column number -enum { MAXCOL = INT_MAX, }; -// Minimum line number +/// MAXCOL used to be INT_MAX, but with 64 bit ints that results in running +/// out of memory when trying to allocate a very long line. +enum { MAXCOL = 0x7fffffff, }; + +/// Minimum line number enum { MINLNUM = 1, }; -// minimum column number + +/// Minimum column number enum { MINCOL = 1, }; -/* - * position in file or buffer - */ +/// position in file or buffer typedef struct { - linenr_T lnum; // line number - colnr_T col; // column number + linenr_T lnum; ///< line number + colnr_T col; ///< column number colnr_T coladd; } pos_T; - -/* - * Same, but without coladd. - */ +/// position in file or buffer, but without coladd typedef struct { - linenr_T lnum; // line number - colnr_T col; // column number + linenr_T lnum; ///< line number + colnr_T col; ///< column number } lpos_T; #endif // NVIM_POS_H diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 6a192d148f..4ad5e40fee 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -22,12 +22,12 @@ #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/highlight_group.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -40,7 +40,6 @@ #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -76,6 +75,7 @@ struct qfline_S { // There is a stack of error lists. #define LISTCOUNT 10 #define INVALID_QFIDX (-1) +#define INVALID_QFBUFNR (0) /// Quickfix list type. typedef enum @@ -127,12 +127,13 @@ struct qf_info_S { int qf_curlist; // current error list qf_list_T qf_lists[LISTCOUNT]; qfltype_T qfl_type; // type of list + int qf_bufnr; // quickfix window buffer number }; static qf_info_T ql_info; // global quickfix list static unsigned last_qf_id = 0; // Last Used quickfix list id -#define FMT_PATTERNS 11 // maximum number of % recognized +#define FMT_PATTERNS 13 // maximum number of % recognized // Structure used to hold the info of one part of 'errorformat' typedef struct efm_S efm_T; @@ -210,6 +211,17 @@ typedef struct { bool valid; } qffields_T; +/// :vimgrep command arguments +typedef struct vgr_args_S { + long tomatch; ///< maximum number of matches to find + char_u *spat; ///< search pattern + int flags; ///< search modifier + char_u **fnames; ///< list of files to search + int fcount; ///< number of files + regmmatch_T regmatch; ///< compiled search pattern + char_u *qf_title; ///< quickfix list title +} vgr_args_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "quickfix.c.generated.h" #endif @@ -217,28 +229,28 @@ typedef struct { static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); // Quickfix window check helper macro -#define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL) +#define IS_QF_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref == NULL) // Location list window check helper macro -#define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) +#define IS_LL_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref != NULL) // Quickfix and location list stack check helper macros -#define IS_QF_STACK(qi) (qi->qfl_type == QFLT_QUICKFIX) -#define IS_LL_STACK(qi) (qi->qfl_type == QFLT_LOCATION) -#define IS_QF_LIST(qfl) (qfl->qfl_type == QFLT_QUICKFIX) -#define IS_LL_LIST(qfl) (qfl->qfl_type == QFLT_LOCATION) +#define IS_QF_STACK(qi) ((qi)->qfl_type == QFLT_QUICKFIX) +#define IS_LL_STACK(qi) ((qi)->qfl_type == QFLT_LOCATION) +#define IS_QF_LIST(qfl) ((qfl)->qfl_type == QFLT_QUICKFIX) +#define IS_LL_LIST(qfl) ((qfl)->qfl_type == QFLT_LOCATION) // // Return location list for window 'wp' // For location list window, return the referenced location list // -#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist) +#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? (wp)->w_llist_ref : (wp)->w_llist) // Macro to loop through all the items in a quickfix list // Quickfix item index starts from 1, so i below starts at 1 #define FOR_ALL_QFL_ITEMS(qfl, qfp, i) \ - for (i = 1, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \ - !got_int && i <= qfl->qf_count && qfp != NULL; \ - i++, qfp = qfp->qf_next) + for ((i) = 1, (qfp) = (qfl)->qf_start; /* NOLINT(readability/braces) */ \ + !got_int && (i) <= (qfl)->qf_count && (qfp) != NULL; \ + (i)++, (qfp) = (qfp)->qf_next) // Looking up a buffer can be slow if there are many. Remember the last one @@ -322,22 +334,27 @@ int qf_init(win_T *wp, const char_u *restrict efile, char_u *restrict errorforma // Maximum number of bytes allowed per line while reading an errorfile. static const size_t LINE_MAXLEN = 4096; +/// Patterns used. Keep in sync with qf_parse_fmt[]. static struct fmtpattern { char_u convchar; char *pattern; } fmt_pat[FMT_PATTERNS] = { - { 'f', ".\\+" }, // only used when at end - { 'n', "\\d\\+" }, - { 'l', "\\d\\+" }, - { 'c', "\\d\\+" }, - { 't', "." }, - { 'm', ".\\+" }, - { 'r', ".*" }, - { 'p', "[- .]*"}, // NOLINT(whitespace/tab) - { 'v', "\\d\\+" }, - { 's', ".\\+" }, - { 'o', ".\\+" } + { 'f', ".\\+" }, // only used when at end + { 'n', "\\d\\+" }, // 1 + { 'l', "\\d\\+" }, // 2 + { 'e', "\\d\\+" }, // 3 + { 'c', "\\d\\+" }, // 4 + { 'k', "\\d\\+" }, // 5 + { 't', "." }, // 6 +#define FMT_PATTERN_M 7 + { 'm', ".\\+" }, // 7 +#define FMT_PATTERN_R 8 + { 'r', ".*" }, // 8 + { 'p', "[- \t.]*" }, // 9 + { 'v', "\\d\\+" }, // 10 + { 's', ".\\+" }, // 11 + { 'o', ".\\+" } // 12 }; /// Convert an errorformat pattern to a regular expression pattern. @@ -353,9 +370,9 @@ static char_u *efmpat_to_regpat(const char_u *efmpat, char_u *regpat, efm_T *efm semsg(_("E372: Too many %%%c in format string"), *efmpat); return NULL; } - if ((idx && idx < 6 + if ((idx && idx < FMT_PATTERN_R && vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL) - || (idx == 6 + || (idx == FMT_PATTERN_R && vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) { semsg(_("E373: Unexpected %%%c in format string"), *efmpat); return NULL; @@ -658,7 +675,8 @@ static int qf_get_next_str_line(qfstate_T *state) state->linebuf = IObuff; state->linelen = len; } - STRLCPY(state->linebuf, p_str, state->linelen + 1); + memcpy(state->linebuf, p_str, state->linelen); + state->linebuf[state->linelen] = '\0'; // Increment using len in order to discard the rest of the line if it // exceeds LINE_MAXLEN. @@ -1277,7 +1295,7 @@ static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } -/// Parse the match for line number (%l') pattern in regmatch. +/// Parse the match for line number ('%l') pattern in regmatch. /// Return the matched value in "fields->lnum". static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields) { @@ -1288,6 +1306,17 @@ static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } +/// Parse the match for end line number ('%e') pattern in regmatch. +/// Return the matched value in "fields->end_lnum". +static int qf_parse_fmt_e(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->end_lnum = atol((char *)rmp->startp[midx]); + return QF_OK; +} + /// Parse the match for column number ('%c') pattern in regmatch. /// Return the matched value in "fields->col". static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields) @@ -1299,6 +1328,17 @@ static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } +/// Parse the match for end line number ('%e') pattern in regmatch. +/// Return the matched value in "fields->end_lnum". +static int qf_parse_fmt_k(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->end_col = (int)atol((char *)rmp->startp[midx]); + return QF_OK; +} + /// Parse the match for error type ('%t') pattern in regmatch. /// Return the matched value in "fields->type". static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) @@ -1431,14 +1471,17 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields) /// 'errorformat' format pattern parser functions. /// The '%f' and '%r' formats are parsed differently from other formats. /// See qf_parse_match() for details. +/// Keep in sync with fmt_pat[]. static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = { - NULL, + NULL, // %f qf_parse_fmt_n, qf_parse_fmt_l, + qf_parse_fmt_e, qf_parse_fmt_c, + qf_parse_fmt_k, qf_parse_fmt_t, qf_parse_fmt_m, - NULL, + NULL, // %r qf_parse_fmt_p, qf_parse_fmt_v, qf_parse_fmt_s, @@ -1474,13 +1517,13 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, regma midx = (int)fmt_ptr->addr[i]; if (i == 0 && midx > 0) { // %f status = qf_parse_fmt_f(regmatch, midx, fields, idx); - } else if (i == 5) { + } else if (i == FMT_PATTERN_M) { if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+ qf_parse_fmt_plus(linebuf, linelen, fields); } else if (midx > 0) { // %m status = qf_parse_fmt_m(regmatch, midx, fields); } - } else if (i == 6 && midx > 0) { // %r + } else if (i == FMT_PATTERN_R && midx > 0) { // %r status = qf_parse_fmt_r(regmatch, midx, tail); } else if (midx > 0) { // others status = (qf_parse_fmt[i])(regmatch, midx, fields); @@ -1625,10 +1668,16 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields) if (!qfprev->qf_lnum) { qfprev->qf_lnum = fields->lnum; } + if (!qfprev->qf_end_lnum) { + qfprev->qf_end_lnum = fields->end_lnum; + } if (!qfprev->qf_col) { qfprev->qf_col = fields->col; qfprev->qf_viscol = fields->use_viscol; } + if (!qfprev->qf_end_col) { + qfprev->qf_end_col = fields->end_col; + } if (!qfprev->qf_fnum) { qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory, *fields->namebuf || qfl->qf_directory @@ -1656,6 +1705,28 @@ static void locstack_queue_delreq(qf_info_T *qi) qf_delq_head = q; } +/// Return the global quickfix stack window buffer number. +int qf_stack_get_bufnr(void) +{ + return ql_info.qf_bufnr; +} + +/// Wipe the quickfix window buffer (if present) for the specified +/// quickfix/location list. +static void wipe_qf_buffer(qf_info_T *qi) + FUNC_ATTR_NONNULL_ALL +{ + if (qi->qf_bufnr != INVALID_QFBUFNR) { + buf_T *const qfbuf = buflist_findnr(qi->qf_bufnr); + if (qfbuf != NULL && qfbuf->b_nwindows == 0) { + // If the quickfix buffer is not loaded in any window, then + // wipe the buffer. + close_buffer(NULL, qfbuf, DOBUF_WIPE, false, false); + qi->qf_bufnr = INVALID_QFBUFNR; + } + } +} + /// Free a location list stack static void ll_free_all(qf_info_T **pqi) { @@ -1668,19 +1739,23 @@ static void ll_free_all(qf_info_T **pqi) } *pqi = NULL; // Remove reference to this list + // If the location list is still in use, then queue the delete request + // to be processed later. + if (quickfix_busy > 0) { + locstack_queue_delreq(qi); + return; + } + qi->qf_refcount--; if (qi->qf_refcount < 1) { // No references to this location list. - // If the location list is still in use, then queue the delete request - // to be processed later. - if (quickfix_busy > 0) { - locstack_queue_delreq(qi); - } else { - for (i = 0; i < qi->qf_listcount; i++) { - qf_free(qf_get_list(qi, i)); - } - xfree(qi); + // If the quickfix window buffer is loaded, then wipe it + wipe_qf_buffer(qi); + + for (i = 0; i < qi->qf_listcount; i++) { + qf_free(qf_get_list(qi, i)); } + xfree(qi); } } @@ -1838,6 +1913,7 @@ static qf_info_T *qf_alloc_stack(qfltype_T qfltype) qf_info_T *qi = xcalloc(1, sizeof(qf_info_T)); qi->qf_refcount++; qi->qfl_type = qfltype; + qi->qf_bufnr = INVALID_QFBUFNR; return qi; } @@ -2399,7 +2475,7 @@ static qfline_T *qf_get_entry(qf_list_T *qfl, int errornr, int dir, int *new_qfi return qf_ptr; } -// Find a window displaying a Vim help file. +// Find a window displaying a Vim help file in the current tab page. static win_T *qf_find_help_win(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -2473,8 +2549,9 @@ static int jump_to_help_window(qf_info_T *qi, bool newwin, int *opened_window) return OK; } -// Find a non-quickfix window using the given location list. -// Returns NULL if a matching window is not found. +/// Find a non-quickfix window using the given location list stack in the +/// current tabpage. +/// Returns NULL if a matching window is not found. static win_T *qf_find_win_with_loclist(const qf_info_T *ll) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -2486,7 +2563,7 @@ static win_T *qf_find_win_with_loclist(const qf_info_T *ll) return NULL; } -// Find a window containing a normal buffer +/// Find a window containing a normal buffer in the current tab page. static win_T *qf_find_win_with_normal_buf(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -2542,7 +2619,7 @@ static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, qf_info_T *ll_ win_T *win = use_win; if (win == NULL) { - // Find the window showing the selected file + // Find the window showing the selected file in the current tab page. FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { if (win2->w_buffer->b_fnum == qf_fnum) { win = win2; @@ -2674,7 +2751,7 @@ static int qf_jump_to_usable_window(int qf_fnum, bool newwin, 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, +static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int prev_winid, int *opened_window) { qf_list_T *qfl = qf_get_curlist(qi); @@ -2693,7 +2770,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win } else { retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, ECMD_HIDE + ECMD_SET_HELP, - oldwin == curwin ? curwin : NULL); + prev_winid == curwin->handle ? curwin : NULL); } } else { retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, @@ -2701,10 +2778,14 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win } // 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) { + win_T *wp = win_id2wp(prev_winid); + + if (wp == NULL && curwin->w_llist != qi) { + emsg(_("E924: Current window was closed")); + *opened_window = false; + return NOTDONE; + } } if (qfl_type == QFLT_QUICKFIX && !qflist_valid(NULL, save_qfid)) { @@ -2712,8 +2793,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win return NOTDONE; } - if (old_qf_curlist != qi->qf_curlist - || old_changetick != qfl->qf_changedtick + if (old_qf_curlist != qi->qf_curlist // -V560 + || old_changetick != qfl->qf_changedtick // -V560 || !is_qf_entry_present(qfl, qf_ptr)) { if (qfl_type == QFLT_QUICKFIX) { emsg(_(e_current_quickfix_list_was_changed)); @@ -2814,7 +2895,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int } } if (old_qf_curlist != qi->qf_curlist - || old_changetick != qfl->qf_changedtick + || old_changetick != qfl->qf_changedtick // -V560 || !is_qf_entry_present(qfl, qf_ptr)) { if (qfl_type == QFLT_QUICKFIX) { emsg(_(e_current_quickfix_list_was_changed)); @@ -2859,7 +2940,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int /// 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) + int prev_winid, int *opened_window, int openfold, int print_message) { buf_T *old_curbuf; linenr_T old_lnum; @@ -2871,7 +2952,7 @@ static int qf_jump_to_buffer(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, int old_lnum = curwin->w_cursor.lnum; if (qf_ptr->qf_fnum != 0) { - retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin, + retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, prev_winid, opened_window); if (retval != OK) { return retval; @@ -2920,8 +3001,8 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo int old_qf_index; char_u *old_swb = p_swb; unsigned old_swb_flags = swb_flags; + int prev_winid; int opened_window = false; - win_T *oldwin = curwin; int print_message = true; const bool old_KeyTyped = KeyTyped; // getting file may reset it int retval = OK; @@ -2935,6 +3016,8 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo return; } + incr_quickfix_busy(); + qfl = qf_get_curlist(qi); qf_ptr = qfl->qf_ptr; @@ -2957,6 +3040,8 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo print_message = false; } + prev_winid = curwin->handle; + retval = qf_jump_open_window(qi, qf_ptr, newwin, &opened_window); if (retval == FAIL) { goto failed; @@ -2965,7 +3050,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo goto theend; } - retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, oldwin, + retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, prev_winid, &opened_window, old_KeyTyped, print_message); if (retval == NOTDONE) { // Quickfix/location list is freed by an autocmd @@ -2975,7 +3060,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo if (retval != OK) { if (opened_window) { - win_close(curwin, true); // Close opened window + win_close(curwin, true, false); // Close opened window } if (qf_ptr != NULL && qf_ptr->qf_fnum != 0) { // Couldn't open file, so put index back where it was. This could @@ -2990,12 +3075,13 @@ theend: qfl->qf_ptr = qf_ptr; qfl->qf_index = qf_index; } - if (p_swb != old_swb && p_swb == empty_option && opened_window) { + if (p_swb != old_swb && p_swb == empty_option) { // Restore old 'switchbuf' value, but not when an autocommand or // modeline has changed the value. p_swb = old_swb; swb_flags = old_swb_flags; } + decr_quickfix_busy(); } @@ -3530,7 +3616,7 @@ void ex_cclose(exarg_T *eap) // Find existing quickfix window and close it. win = qf_find_win(qi); if (win != NULL) { - win_close(win, false); + win_close(win, false, false); } } @@ -3550,7 +3636,7 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, bool vertsp win_setwidth(sz); } } else if (sz != win->w_height - && (win->w_height + win->w_status_height + tabline_height() + && (win->w_height + win->w_hsep_height + win->w_status_height + tabline_height() < cmdline_row)) { win_setheight(sz); } @@ -3565,7 +3651,7 @@ 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); + set_option_value("bh", 0L, "hide", OPT_LOCAL); RESET_BINDING(curwin); curwin->w_p_diff = false; set_option_value("fdm", 0L, "manual", OPT_LOCAL); @@ -3599,22 +3685,13 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height) if (win_split(height, flags) == FAIL) { return FAIL; // not enough room for window } - - // User autocommands may have invalidated the previous window after calling - // win_split, so add a check to ensure that the win is still here - if (IS_LL_STACK(qi) && !win_valid(win)) { - // close the window that was supposed to be for the loclist - win_close(curwin, false); - return FAIL; - } - RESET_BINDING(curwin); if (IS_LL_STACK(qi)) { // For the location list window, create a reference to the - // location list from the window 'win'. - curwin->w_llist_ref = win->w_llist; - win->w_llist->qf_refcount++; + // location list stack from the window 'win'. + curwin->w_llist_ref = qi; + qi->qf_refcount++; } if (oldwin != curwin) { @@ -3631,6 +3708,8 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height) if (do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE + ECMD_NOWINENTER, oldwin) == FAIL) { return FAIL; } + // save the number of the new buffer + qi->qf_bufnr = curbuf->b_fnum; } // Set the options for the quickfix buffer/window (if not already done) @@ -3809,8 +3888,8 @@ static int is_qf_win(const win_T *win, const qf_info_T *qi) return false; } -/// Find a window displaying the quickfix/location stack 'qi' -/// Only searches in the current tabpage. +/// Find a window displaying the quickfix/location stack 'qi' in the current tab +/// page. static win_T *qf_find_win(const qf_info_T *qi) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -3823,11 +3902,20 @@ static win_T *qf_find_win(const qf_info_T *qi) return NULL; } -// Find a quickfix buffer. -// Searches in windows opened in all the tabs. +/// Find a quickfix buffer. +/// Searches in windows opened in all the tab pages. static buf_T *qf_find_buf(qf_info_T *qi) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + if (qi->qf_bufnr != INVALID_QFBUFNR) { + buf_T *const qfbuf = buflist_findnr(qi->qf_bufnr); + if (qfbuf != NULL) { + return qfbuf; + } + // buffer is no longer present + qi->qf_bufnr = INVALID_QFBUFNR; + } + FOR_ALL_TAB_WINDOWS(tp, win) { if (is_qf_win(win, qi)) { return win->w_buffer; @@ -3947,7 +4035,7 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli int len; buf_T *errbuf; - // If the 'quickfixtextfunc' function returned an non-empty custom string + // If the 'quickfixtextfunc' function returned a non-empty custom string // for this entry, then use it. if (qftf_str != NULL && *qftf_str != NUL) { STRLCPY(IObuff, qftf_str, IOSIZE); @@ -4849,11 +4937,12 @@ static qfline_T *qf_find_closest_entry(qf_list_T *qfl, int bnr, const pos_T *pos /// Get the nth quickfix entry below the specified entry. Searches forward in /// the list. If linewise is true, then treat multiple entries on a single line /// as one. -static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, int *errornr) +static void qf_get_nth_below_entry(qfline_T *entry_arg, linenr_T n, bool linewise, int *errornr) FUNC_ATTR_NONNULL_ALL { + qfline_T *entry = entry_arg; + while (n-- > 0 && !got_int) { - // qfline_T *first_entry = entry; int first_errornr = *errornr; if (linewise) { @@ -4864,9 +4953,6 @@ static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, i if (entry->qf_next == NULL || entry->qf_next->qf_fnum != entry->qf_fnum) { if (linewise) { - // If multiple entries are on the same line, then use the first - // entry - // entry = first_entry; *errornr = first_errornr; } break; @@ -5194,49 +5280,93 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char_u *ti /// 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_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) +static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, char_u *spat, + regmmatch_T *regmatch, long *tomatch, int duplicate_name, int flags) + FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6) { bool found_match = false; 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(qfl, - NULL, // dir - fname, - NULL, - duplicate_name ? 0 : buf->b_fnum, - ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, - false), - regmatch->startpos[0].lnum + lnum, - regmatch->endpos[0].lnum + lnum, - regmatch->startpos[0].col + 1, - regmatch->endpos[0].col + 1, - false, // vis_col - NULL, // search pattern - 0, // nr - 0, // type - true) // valid - == QF_FAIL) { - got_int = true; - break; - } - found_match = true; - if (--*tomatch == 0) { - break; - } - if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) { - break; + if (!(flags & VGR_FUZZY)) { + // Regular expression match + 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(qfl, + NULL, // dir + fname, + NULL, + duplicate_name ? 0 : buf->b_fnum, + ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, false), + regmatch->startpos[0].lnum + lnum, + regmatch->endpos[0].lnum + lnum, + regmatch->startpos[0].col + 1, + regmatch->endpos[0].col + 1, + false, // vis_col + NULL, // search pattern + 0, // nr + 0, // type + true) // valid + == QF_FAIL) { + got_int = true; + break; + } + found_match = true; + if (--*tomatch == 0) { + break; + } + if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) { + break; + } + col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col); + if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) { + break; + } } - col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col); - if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) { - break; + } else { + const size_t pat_len = STRLEN(spat); + char_u *const str = ml_get_buf(buf, lnum, false); + int score; + uint32_t matches[MAX_FUZZY_MATCHES]; + const size_t sz = sizeof(matches) / sizeof(matches[0]); + + // Fuzzy string match + while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz) > 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(qfl, + NULL, // dir + fname, + NULL, + duplicate_name ? 0 : buf->b_fnum, + str, + lnum, + 0, + (colnr_T)matches[0] + col + 1, + 0, + false, // vis_col + NULL, // search pattern + 0, // nr + 0, // type + true) // valid + == QF_FAIL) { + got_int = true; + break; + } + found_match = true; + if (--*tomatch == 0) { + break; + } + if ((flags & VGR_GLOBAL) == 0) { + break; + } + col = (colnr_T)matches[pat_len - 1] + col + 1; + if (col > (colnr_T)STRLEN(str)) { + break; + } } } line_breakcheck(); @@ -5249,7 +5379,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, regmma } /// Jump to the first match and update the directory. -static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy, +static void vgr_jump_to_match(qf_info_T *qi, int forceit, bool *redraw_for_dummy, buf_T *first_match_buf, char_u *target_dir) { buf_T *buf = curbuf; @@ -5284,104 +5414,71 @@ static bool existing_swapfile(const buf_T *buf) return false; } -// ":vimgrep {pattern} file(s)" -// ":vimgrepadd {pattern} file(s)" -// ":lvimgrep {pattern} file(s)" -// ":lvimgrepadd {pattern} file(s)" -void ex_vimgrep(exarg_T *eap) +/// Process :vimgrep command arguments. The command syntax is: +/// +/// :{count}vimgrep /{pattern}/[g][j] {file} ... +static int vgr_process_args(exarg_T *eap, vgr_args_T *args) { - regmmatch_T regmatch; - int fcount; - char_u **fnames; - char_u *fname; - char_u *s; - char_u *p; - int fi; - qf_list_T *qfl; - win_T *wp = NULL; - buf_T *buf; - int duplicate_name = FALSE; - int using_dummy; - int redraw_for_dummy = FALSE; - int found_match; - buf_T *first_match_buf = NULL; - time_t seconds = 0; - aco_save_T aco; - int flags = 0; - long tomatch; - char_u *dirname_start = NULL; - char_u *dirname_now = NULL; - char_u *target_dir = NULL; - char_u *au_name = NULL; + memset(args, 0, sizeof(*args)); - au_name = vgr_get_auname(eap->cmdidx); - if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, - curbuf->b_fname, true, curbuf)) { - if (aborting()) { - return; - } - } - - qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); + args->regmatch.regprog = NULL; + args->qf_title = vim_strsave(qf_cmdtitle(*eap->cmdlinep)); if (eap->addr_count > 0) { - tomatch = eap->line2; + args->tomatch = eap->line2; } else { - tomatch = MAXLNUM; + args->tomatch = MAXLNUM; } // Get the search pattern: either white-separated or enclosed in // - regmatch.regprog = NULL; - char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep)); - p = skip_vimgrep_pat(eap->arg, &s, &flags); + char_u *p = skip_vimgrep_pat(eap->arg, &args->spat, &args->flags); if (p == NULL) { emsg(_(e_invalpat)); - goto theend; + return FAIL; } - vgr_init_regmatch(®match, s); - if (regmatch.regprog == NULL) { - goto theend; + vgr_init_regmatch(&args->regmatch, args->spat); + if (args->regmatch.regprog == NULL) { + return FAIL; } p = skipwhite(p); if (*p == NUL) { emsg(_("E683: File name missing or invalid pattern")); - goto theend; - } - - if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd - && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd) - || qf_stack_empty(qi)) { - // make place for a new list - qf_new_list(qi, title); + return FAIL; } // Parse the list of arguments, wildcards have already been expanded. - if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) { - goto theend; + if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL) { + return FAIL; } - if (fcount == 0) { + if (args->fcount == 0) { emsg(_(e_nomatch)); - goto theend; + return FAIL; } - dirname_start = xmalloc(MAXPATHL); - dirname_now = xmalloc(MAXPATHL); + return OK; +} + +/// Search for a pattern in a list of files and populate the quickfix list with +/// the matches. +static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, bool *redraw_for_dummy, + buf_T **first_match_buf, char_u **target_dir) +{ + int status = FAIL; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; + bool duplicate_name = false; + + char_u *dirname_start = xmalloc(MAXPATHL); + char_u *dirname_now = xmalloc(MAXPATHL); // Remember the current directory, because a BufRead autocommand that does // ":lcd %:p:h" changes the meaning of short path names. os_dirname(dirname_start, MAXPATHL); - incr_quickfix_busy(); - - // Remember the current quickfix list identifier, so that we can check for - // autocommands changing the current quickfix list. - unsigned save_qfid = qf_get_curlist(qi)->qf_id; - - seconds = (time_t)0; - for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { - fname = path_try_shorten_fname(fnames[fi]); + time_t seconds = (time_t)0; + for (int fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; fi++) { + char_u *fname = path_try_shorten_fname(cmd_args->fnames[fi]); if (time(NULL) > seconds) { // Display the file name every second or so, show the user we are // working on it. @@ -5389,13 +5486,13 @@ void ex_vimgrep(exarg_T *eap) vgr_display_fname(fname); } - buf = buflist_findname_exp(fnames[fi]); + buf_T *buf = buflist_findname_exp(cmd_args->fnames[fi]); + bool using_dummy; if (buf == NULL || buf->b_ml.ml_mfp == NULL) { // Remember that a buffer with this name already exists. duplicate_name = (buf != NULL); - using_dummy = TRUE; - redraw_for_dummy = TRUE; - + using_dummy = true; + *redraw_for_dummy = true; buf = vgr_load_dummy_buf(fname, dirname_start, dirname_now); } else { // Use existing, loaded buffer. @@ -5404,11 +5501,10 @@ void ex_vimgrep(exarg_T *eap) // Check whether the quickfix list is still valid. When loading a // buffer above, autocommands might have changed the quickfix list. - if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) { - FreeWild(fcount, fnames); - decr_quickfix_busy(); + if (!vgr_qflist_valid(wp, qi, save_qfid, cmd_args->qf_title)) { goto theend; } + save_qfid = qf_get_curlist(qi)->qf_id; if (buf == NULL) { @@ -5418,13 +5514,18 @@ 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(qf_get_curlist(qi), - fname, buf, ®match, &tomatch, - duplicate_name, flags); + bool found_match = vgr_match_buflines(qf_get_curlist(qi), + fname, + buf, + cmd_args->spat, + &cmd_args->regmatch, + &cmd_args->tomatch, + duplicate_name, + cmd_args->flags); if (using_dummy) { - if (found_match && first_match_buf == NULL) { - first_match_buf = buf; + if (found_match && *first_match_buf == NULL) { + *first_match_buf = buf; } if (duplicate_name) { // Never keep a dummy buffer if there is another buffer @@ -5444,8 +5545,8 @@ 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 + || (cmd_args->flags & VGR_NOJUMP) || existing_swapfile(buf)) { unload_dummy_buffer(buf, dirname_start); // Keeping the buffer, remove the dummy flag. @@ -5460,16 +5561,17 @@ void ex_vimgrep(exarg_T *eap) // If the buffer is still loaded we need to use the // directory we jumped to below. - if (buf == first_match_buf - && target_dir == NULL + if (buf == *first_match_buf + && *target_dir == NULL && STRCMP(dirname_start, dirname_now) != 0) { - target_dir = vim_strsave(dirname_now); + *target_dir = vim_strsave(dirname_now); } // The buffer is still loaded, the Filetype autocommands // need to be done now, in that buffer. And the modelines // need to be done (again). But not the window-local // options! + aco_save_T aco; aucmd_prepbuf(&aco, buf); apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf); do_modelines(OPT_NOWIN); @@ -5479,9 +5581,58 @@ void ex_vimgrep(exarg_T *eap) } } - FreeWild(fcount, fnames); + status = OK; - qfl = qf_get_curlist(qi); +theend: + xfree(dirname_now); + xfree(dirname_start); + return status; +} + +/// ":vimgrep {pattern} file(s)" +/// ":vimgrepadd {pattern} file(s)" +/// ":lvimgrep {pattern} file(s)" +/// ":lvimgrepadd {pattern} file(s)" +void ex_vimgrep(exarg_T *eap) +{ + char_u *au_name = vgr_get_auname(eap->cmdidx); + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, + curbuf->b_fname, true, curbuf)) { + if (aborting()) { + return; + } + } + + win_T *wp = NULL; + qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); + char_u *target_dir = NULL; + vgr_args_T args; + if (vgr_process_args(eap, &args) == FAIL) { + goto theend; + } + + if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd + && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd) + || qf_stack_empty(qi)) { + // make place for a new list + qf_new_list(qi, args.qf_title); + } + + incr_quickfix_busy(); + + bool redraw_for_dummy = false; + buf_T *first_match_buf = NULL; + int status = vgr_process_files(wp, qi, &args, &redraw_for_dummy, &first_match_buf, &target_dir); + + if (status != OK) { + FreeWild(args.fcount, args.fnames); + decr_quickfix_busy(); + goto theend; + } + + FreeWild(args.fcount, args.fnames); + + qf_list_T *qfl = qf_get_curlist(qi); qfl->qf_nonevalid = false; qfl->qf_ptr = qfl->qf_start; qfl->qf_index = 1; @@ -5489,26 +5640,28 @@ void ex_vimgrep(exarg_T *eap) qf_update_buffer(qi, NULL); + // Remember the current quickfix list identifier, so that we can check for + // autocommands changing the current quickfix list. + unsigned save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); } // The QuickFixCmdPost autocmd may free the quickfix list. Check the list // is still valid. - if (!qflist_valid(wp, save_qfid) - || qf_restore_list(qi, save_qfid) == FAIL) { + if (!qflist_valid(wp, save_qfid) || qf_restore_list(qi, save_qfid) == FAIL) { decr_quickfix_busy(); goto theend; } // Jump to first match. if (!qf_list_empty(qf_get_curlist(qi))) { - if ((flags & VGR_NOJUMP) == 0) { - vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, - target_dir); + if ((args.flags & VGR_NOJUMP) == 0) { + vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir); } } else { - semsg(_(e_nomatch2), s); + semsg(_(e_nomatch2), args.spat); } decr_quickfix_busy(); @@ -5520,11 +5673,9 @@ void ex_vimgrep(exarg_T *eap) } theend: - xfree(title); - xfree(dirname_now); - xfree(dirname_start); + xfree(args.qf_title); xfree(target_dir); - vim_regfree(regmatch.regprog); + vim_regfree(args.regmatch.regprog); } // Restore current working directory to "dirname_start" if they differ, taking @@ -5656,7 +5807,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start) if (firstwin->w_next != NULL) { for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) { if (wp->w_buffer == buf) { - if (win_close(wp, false) == OK) { + if (win_close(wp, false, false) == OK) { did_one = true; } break; @@ -5692,7 +5843,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start) static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) { if (curbuf != buf) { // safety check - close_buffer(NULL, buf, DOBUF_UNLOAD, false); + close_buffer(NULL, buf, DOBUF_UNLOAD, false, true); // When autocommands/'autochdir' option changed directory: go back. restore_start_dir(dirname_start); @@ -5869,6 +6020,21 @@ static int qf_winid(qf_info_T *qi) return 0; } +/// Returns the number of the buffer displayed in the quickfix/location list +/// window. If there is no buffer associated with the list or the buffer is +/// wiped out, then returns 0. +static int qf_getprop_qfbufnr(const qf_info_T *qi, dict_T *retdict) + FUNC_ATTR_NONNULL_ARG(2) +{ + int bufnum = 0; + + if (qi != NULL && buflist_findnr(qi->qf_bufnr) != NULL) { + bufnum = qi->qf_bufnr; + } + + return tv_dict_add_nr(retdict, S_LEN("qfbufnr"), bufnum); +} + /// Convert the keys in 'what' to quickfix list property flags. static int qf_getprop_keys2flags(const dict_T *what, bool loclist) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT @@ -5912,6 +6078,9 @@ static int qf_getprop_keys2flags(const dict_T *what, bool loclist) if (loclist && tv_dict_find(what, S_LEN("filewinid")) != NULL) { flags |= QF_GETLIST_FILEWINID; } + if (tv_dict_find(what, S_LEN("qfbufnr")) != NULL) { + flags |= QF_GETLIST_QFBUFNR; + } if (tv_dict_find(what, S_LEN("quickfixtextfunc")) != NULL) { flags |= QF_GETLIST_QFTF; } @@ -6003,6 +6172,9 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, int locstack, dict_T *r if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) { status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0); } + if ((status == OK) && (flags & QF_GETLIST_QFBUFNR)) { + status = qf_getprop_qfbufnr(qi, retdict); + } if ((status == OK) && (flags & QF_GETLIST_QFTF)) { status = tv_dict_add_str(retdict, S_LEN("quickfixtextfunc"), ""); } @@ -6172,6 +6344,9 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) { status = qf_getprop_filewinid(wp, qi, retdict); } + if ((status == OK) && (flags & QF_GETLIST_QFBUFNR)) { + status = qf_getprop_qfbufnr(qi, retdict); + } if ((status == OK) && (flags & QF_GETLIST_QFTF)) { status = qf_getprop_qftf(qfl, retdict); } @@ -6560,20 +6735,6 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, char return retval; } -/// Find the non-location list window with the specified location list stack in -/// the current tabpage. -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)) { - return wp; - } - } - - return NULL; -} - // Free the entire quickfix/location list stack. // If the quickfix/location list window is open, then clear it. static void qf_free_stack(win_T *wp, qf_info_T *qi) @@ -6588,12 +6749,10 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) qf_update_buffer(qi, NULL); } - win_T *llwin = NULL; - win_T *orig_wp = wp; if (wp != NULL && IS_LL_WINDOW(wp)) { // If in the location list window, then use the non-location list // window with this location list (if present) - llwin = find_win_with_ll(qi); + win_T *const llwin = qf_find_win_with_loclist(qi); if (llwin != NULL) { wp = llwin; } @@ -6604,16 +6763,17 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) // quickfix list qi->qf_curlist = 0; qi->qf_listcount = 0; - } else if (IS_LL_WINDOW(orig_wp)) { + } else if (qfwin != NULL) { // If the location list window is open, then create a new empty location // list qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION); + new_ll->qf_bufnr = qfwin->w_buffer->b_fnum; // first free the list reference in the location list window - ll_free_all(&orig_wp->w_llist_ref); + ll_free_all(&qfwin->w_llist_ref); - orig_wp->w_llist_ref = new_ll; - if (llwin != NULL) { + qfwin->w_llist_ref = new_ll; + if (wp != qfwin) { win_set_loclist(wp, new_ll); } } diff --git a/src/nvim/quickfix.h b/src/nvim/quickfix.h index f5178e332a..0da43e436c 100644 --- a/src/nvim/quickfix.h +++ b/src/nvim/quickfix.h @@ -7,6 +7,7 @@ // flags for skip_vimgrep_pat() #define VGR_GLOBAL 1 #define VGR_NOJUMP 2 +#define VGR_FUZZY 4 #ifdef INCLUDE_GENERATED_DECLARATIONS # include "quickfix.h.generated.h" diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h index cc690050ab..e86765a4ad 100644 --- a/src/nvim/rbuffer.h +++ b/src/nvim/rbuffer.h @@ -38,17 +38,15 @@ // create infinite loops #define RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) \ for (size_t rcnt = 0, _r = 1; _r; _r = 0) /* NOLINT(readability/braces) */ \ - for ( /* NOLINT(readability/braces) */ \ - char *rptr = rbuffer_read_ptr(buf, &rcnt); \ - buf->size; \ - rptr = rbuffer_read_ptr(buf, &rcnt)) + for (char *rptr = rbuffer_read_ptr(buf, &rcnt); /* NOLINT(readability/braces) */ \ + buf->size; \ + rptr = rbuffer_read_ptr(buf, &rcnt)) #define RBUFFER_UNTIL_FULL(buf, wptr, wcnt) \ for (size_t wcnt = 0, _r = 1; _r; _r = 0) /* NOLINT(readability/braces) */ \ - for ( /* NOLINT(readability/braces) */ \ - char *wptr = rbuffer_write_ptr(buf, &wcnt); \ - rbuffer_space(buf); \ - wptr = rbuffer_write_ptr(buf, &wcnt)) + for (char *wptr = rbuffer_write_ptr(buf, &wcnt); /* NOLINT(readability/braces) */ \ + rbuffer_space(buf); \ + wptr = rbuffer_write_ptr(buf, &wcnt)) // Iteration diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index bec3bc9648..b61c9a2cf5 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -5,41 +5,6 @@ /* * Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub() - * - * NOTICE: - * - * This is NOT the original regular expression code as written by Henry - * Spencer. This code has been modified specifically for use with the VIM - * editor, and should not be used separately from Vim. If you want a good - * regular expression library, get the original code. The copyright notice - * that follows is from the original. - * - * END NOTICE - * - * Copyright (c) 1986 by University of Toronto. - * Written by Henry Spencer. Not derived from licensed software. - * - * Permission is granted to anyone to use this software for any - * purpose on any computer system, and to redistribute it freely, - * subject to the following restrictions: - * - * 1. The author is not responsible for the consequences of use of - * this software, no matter how awful, even if they arise - * from defects in it. - * - * 2. The origin of this software must not be misrepresented, either - * by explicit claim or by omission. - * - * 3. Altered versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * Beware that some of this code is subtly aware of the way operator - * precedence is structured in regular expressions. Serious changes in - * regular-expression syntax might require a total rethink. - * - * Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert - * Webb, Ciaran McCreesh and Bram Moolenaar. - * Named character class support added by Walter Briscoe (1998 Jul 01) */ // By default: do not create debugging logs or files related to regular @@ -64,211 +29,21 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" +#include "nvim/os/input.h" #include "nvim/plines.h" #include "nvim/garray.h" #include "nvim/strings.h" #ifdef REGEXP_DEBUG -/* show/save debugging data when BT engine is used */ +// show/save debugging data when BT engine is used # define BT_REGEXP_DUMP -/* save the debugging data to a file instead of displaying it */ +// save the debugging data to a file instead of displaying it # define BT_REGEXP_LOG # define BT_REGEXP_DEBUG_LOG # define BT_REGEXP_DEBUG_LOG_NAME "bt_regexp_debug.log" #endif /* - * The "internal use only" fields in regexp_defs.h are present to pass info from - * compile to execute that permits the execute phase to run lots faster on - * simple cases. They are: - * - * regstart char that must begin a match; NUL if none obvious; Can be a - * multi-byte character. - * reganch is the match anchored (at beginning-of-line only)? - * regmust string (pointer into program) that match must include, or NULL - * regmlen length of regmust string - * regflags RF_ values or'ed together - * - * Regstart and reganch permit very fast decisions on suitable starting points - * for a match, cutting down the work a lot. Regmust permits fast rejection - * of lines that cannot possibly match. The regmust tests are costly enough - * that vim_regcomp() supplies a regmust only if the r.e. contains something - * potentially expensive (at present, the only such thing detected is * or + - * at the start of the r.e., which can involve a lot of backup). Regmlen is - * supplied because the test in vim_regexec() needs it and vim_regcomp() is - * computing it anyway. - */ - -/* - * Structure for regexp "program". This is essentially a linear encoding - * of a nondeterministic finite-state machine (aka syntax charts or - * "railroad normal form" in parsing technology). Each node is an opcode - * plus a "next" pointer, possibly plus an operand. "Next" pointers of - * all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next" - * pointer with a BRANCH on both ends of it is connecting two alternatives. - * (Here we have one of the subtle syntax dependencies: an individual BRANCH - * (as opposed to a collection of them) is never concatenated with anything - * because of operator precedence). The "next" pointer of a BRACES_COMPLEX - * node points to the node after the stuff to be repeated. - * The operand of some types of node is a literal string; for others, it is a - * node leading into a sub-FSM. In particular, the operand of a BRANCH node - * is the first node of the branch. - * (NB this is *not* a tree structure: the tail of the branch connects to the - * thing following the set of BRANCHes.) - * - * pattern is coded like: - * - * +-----------------+ - * | V - * <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END - * | ^ | ^ - * +------+ +----------+ - * - * - * +------------------+ - * V | - * <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END - * | | ^ ^ - * | +---------------+ | - * +---------------------------------------------+ - * - * - * +----------------------+ - * V | - * <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END - * | | ^ ^ - * | +-----------+ | - * +--------------------------------------------------+ - * - * - * +-------------------------+ - * V | - * <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END - * | | ^ - * | +----------------+ - * +-----------------------------------------------+ - * - * - * <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END - * | | ^ ^ - * | +----------------+ | - * +--------------------------------+ - * - * +---------+ - * | V - * \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END - * | | | | ^ ^ - * | | | +-----+ | - * | | +----------------+ | - * | +---------------------------+ | - * +------------------------------------------------------+ - * - * They all start with a BRANCH for "\|" alternatives, even when there is only - * one alternative. - */ - -/* - * The opcodes are: - */ - -/* definition number opnd? meaning */ -#define END 0 /* End of program or NOMATCH operand. */ -#define BOL 1 /* Match "" at beginning of line. */ -#define EOL 2 /* Match "" at end of line. */ -#define BRANCH 3 /* node Match this alternative, or the - * next... */ -#define BACK 4 /* Match "", "next" ptr points backward. */ -#define EXACTLY 5 /* str Match this string. */ -#define NOTHING 6 /* Match empty string. */ -#define STAR 7 /* node Match this (simple) thing 0 or more - * times. */ -#define PLUS 8 /* node Match this (simple) thing 1 or more - * times. */ -#define MATCH 9 /* node match the operand zero-width */ -#define NOMATCH 10 /* node check for no match with operand */ -#define BEHIND 11 /* node look behind for a match with operand */ -#define NOBEHIND 12 /* node look behind for no match with operand */ -#define SUBPAT 13 /* node match the operand here */ -#define BRACE_SIMPLE 14 /* node Match this (simple) thing between m and - * n times (\{m,n\}). */ -#define BOW 15 /* Match "" after [^a-zA-Z0-9_] */ -#define EOW 16 /* Match "" at [^a-zA-Z0-9_] */ -#define BRACE_LIMITS 17 /* nr nr define the min & max for BRACE_SIMPLE - * and BRACE_COMPLEX. */ -#define NEWL 18 /* Match line-break */ -#define BHPOS 19 /* End position for BEHIND or NOBEHIND */ - - -/* character classes: 20-48 normal, 50-78 include a line-break */ -#define ADD_NL 30 -#define FIRST_NL ANY + ADD_NL -#define ANY 20 /* Match any one character. */ -#define ANYOF 21 /* str Match any character in this string. */ -#define ANYBUT 22 /* str Match any character not in this - * string. */ -#define IDENT 23 /* Match identifier char */ -#define SIDENT 24 /* Match identifier char but no digit */ -#define KWORD 25 /* Match keyword char */ -#define SKWORD 26 /* Match word char but no digit */ -#define FNAME 27 /* Match file name char */ -#define SFNAME 28 /* Match file name char but no digit */ -#define PRINT 29 /* Match printable char */ -#define SPRINT 30 /* Match printable char but no digit */ -#define WHITE 31 /* Match whitespace char */ -#define NWHITE 32 /* Match non-whitespace char */ -#define DIGIT 33 /* Match digit char */ -#define NDIGIT 34 /* Match non-digit char */ -#define HEX 35 /* Match hex char */ -#define NHEX 36 /* Match non-hex char */ -#define OCTAL 37 /* Match octal char */ -#define NOCTAL 38 /* Match non-octal char */ -#define WORD 39 /* Match word char */ -#define NWORD 40 /* Match non-word char */ -#define HEAD 41 /* Match head char */ -#define NHEAD 42 /* Match non-head char */ -#define ALPHA 43 /* Match alpha char */ -#define NALPHA 44 /* Match non-alpha char */ -#define LOWER 45 /* Match lowercase char */ -#define NLOWER 46 /* Match non-lowercase char */ -#define UPPER 47 /* Match uppercase char */ -#define NUPPER 48 /* Match non-uppercase char */ -#define LAST_NL NUPPER + ADD_NL -// -V:WITH_NL:560 -#define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL) - -#define MOPEN 80 // -89 Mark this point in input as start of - // \( … \) subexpr. MOPEN + 0 marks start of - // match. -#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks - // end of match. -#define BACKREF 100 // -109 node Match same string again \1-\9. - -# define ZOPEN 110 // -119 Mark this point in input as start of - // \z( … \) subexpr. -# define ZCLOSE 120 // -129 Analogous to ZOPEN. -# define ZREF 130 // -139 node Match external submatch \z1-\z9 - -#define BRACE_COMPLEX 140 /* -149 node Match nodes between m & n times */ - -#define NOPEN 150 // Mark this point in input as start of - // \%( subexpr. -#define NCLOSE 151 // Analogous to NOPEN. - -#define MULTIBYTECODE 200 /* mbc Match one multi-byte character */ -#define RE_BOF 201 /* Match "" at beginning of file. */ -#define RE_EOF 202 /* Match "" at end of file. */ -#define CURSOR 203 /* Match location of cursor. */ - -#define RE_LNUM 204 /* nr cmp Match line number */ -#define RE_COL 205 /* nr cmp Match column number */ -#define RE_VCOL 206 /* nr cmp Match virtual column number */ - -#define RE_MARK 207 /* mark cmp Match mark position */ -#define RE_VISUAL 208 /* Match Visual area */ -#define RE_COMPOSING 209 // any composing characters - -/* * Magic characters have a special meaning, they don't match literally. * Magic characters are negative. This separates them from literal characters * (possibly multi-byte). Only ASCII characters can be Magic. @@ -285,107 +60,6 @@ */ typedef void (*(*fptr_T)(int *, int))(void); -typedef struct { - char_u *regparse; - int prevchr_len; - int curchr; - int prevchr; - int prevprevchr; - int nextchr; - int at_start; - int prev_at_start; - int regnpar; -} parse_state_T; - -/* - * Structure used to save the current input state, when it needs to be - * restored after trying a match. Used by reg_save() and reg_restore(). - * Also stores the length of "backpos". - */ -typedef struct { - union { - 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; - -/* struct to save start/end pointer/position in for \(\) */ -typedef struct { - union { - char_u *ptr; - lpos_T pos; - } se_u; -} save_se_T; - -/* used for BEHIND and NOBEHIND matching */ -typedef struct regbehind_S { - regsave_T save_after; - regsave_T save_behind; - int save_need_clear_subexpr; - save_se_T save_start[NSUBEXP]; - save_se_T save_end[NSUBEXP]; -} regbehind_T; - -/* Values for rs_state in regitem_T. */ -typedef enum regstate_E { - RS_NOPEN = 0 /* NOPEN and NCLOSE */ - , RS_MOPEN /* MOPEN + [0-9] */ - , RS_MCLOSE /* MCLOSE + [0-9] */ - , RS_ZOPEN /* ZOPEN + [0-9] */ - , RS_ZCLOSE /* ZCLOSE + [0-9] */ - , RS_BRANCH /* BRANCH */ - , RS_BRCPLX_MORE /* BRACE_COMPLEX and trying one more match */ - , RS_BRCPLX_LONG /* BRACE_COMPLEX and trying longest match */ - , RS_BRCPLX_SHORT /* BRACE_COMPLEX and trying shortest match */ - , RS_NOMATCH /* NOMATCH */ - , RS_BEHIND1 /* BEHIND / NOBEHIND matching rest */ - , RS_BEHIND2 /* BEHIND / NOBEHIND matching behind part */ - , RS_STAR_LONG /* STAR/PLUS/BRACE_SIMPLE longest match */ - , RS_STAR_SHORT /* STAR/PLUS/BRACE_SIMPLE shortest match */ -} regstate_T; - -/* - * When there are alternatives a regstate_T is put on the regstack to remember - * what we are doing. - * Before it may be another type of item, depending on rs_state, to remember - * more things. - */ -typedef struct regitem_S { - regstate_T rs_state; // what we are doing, one of RS_ above - uint16_t rs_no; // submatch nr or BEHIND/NOBEHIND - char_u *rs_scan; // current node in program - union { - save_se_T sesave; - regsave_T regsave; - } rs_un; ///< room for saving rex.input -} regitem_T; - - -/* used for STAR, PLUS and BRACE_SIMPLE matching */ -typedef struct regstar_S { - int nextb; /* next byte */ - int nextb_ic; /* next byte reverse case */ - long count; - long minval; - long maxval; -} regstar_T; - -/* used to store input position when a BACK was encountered, so that we now if - * we made any progress since the last time. */ -typedef struct backpos_S { - char_u *bp_scan; /* "scan" where BACK was encountered */ - regsave_T bp_pos; /* last input position */ -} backpos_T; - -typedef struct { - int a, b, c; -} decomp_T; - - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "regexp.c.generated.h" -#endif static int no_Magic(int x) { if (is_Magic(x)) @@ -400,66 +74,13 @@ static int toggle_Magic(int x) return Magic(x); } -/* - * The first byte of the regexp internal "program" is actually this magic - * number; the start node begins in the second byte. It's used to catch the - * most severe mutilation of the program by the caller. - */ - +// The first byte of the BT regexp internal "program" is actually this magic +// number; the start node begins in the second byte. It's used to catch the +// most severe mutilation of the program by the caller. #define REGMAGIC 0234 -/* - * Opcode notes: - * - * BRANCH The set of branches constituting a single choice are hooked - * together with their "next" pointers, since precedence prevents - * anything being concatenated to any individual branch. The - * "next" pointer of the last BRANCH in a choice points to the - * thing following the whole choice. This is also where the - * final "next" pointer of each individual branch points; each - * branch starts with the operand node of a BRANCH node. - * - * BACK Normal "next" pointers all implicitly point forward; BACK - * exists to make loop structures possible. - * - * STAR,PLUS '=', and complex '*' and '+', are implemented as circular - * BRANCH structures using BACK. Simple cases (one character - * per match) are implemented with STAR and PLUS for speed - * and to minimize recursive plunges. - * - * BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX - * node, and defines the min and max limits to be used for that - * node. - * - * MOPEN,MCLOSE ...are numbered at compile time. - * ZOPEN,ZCLOSE ...ditto - */ - -/* - * A node is one char of opcode followed by two chars of "next" pointer. - * "Next" pointers are stored as two 8-bit bytes, high order first. The - * value is a positive offset from the opcode of the node containing it. - * An operand, if any, simply follows the node. (Note that much of the - * code generation knows about this implicit relationship.) - * - * Using two bytes for the "next" pointer is vast overkill for most things, - * but allows patterns to get big without disasters. - */ -#define OP(p) ((int)*(p)) -#define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377)) -#define OPERAND(p) ((p) + 3) -/* Obtain an operand that was stored as four bytes, MSB first. */ -#define OPERAND_MIN(p) (((long)(p)[3] << 24) + ((long)(p)[4] << 16) \ - + ((long)(p)[5] << 8) + (long)(p)[6]) -/* Obtain a second operand stored as four bytes. */ -#define OPERAND_MAX(p) OPERAND_MIN((p) + 4) -/* Obtain a second single-byte operand stored after a four bytes operand. */ -#define OPERAND_CMP(p) (p)[7] - -/* - * Utility definitions. - */ -#define UCHARAT(p) ((int)*(char_u *)(p)) +// Utility definitions. +#define UCHARAT(p) ((int)(*(char_u *)(p))) // Used for an error (down from) vim_regcomp(): give the error message, set // rc_did_emsg and return NULL @@ -468,6 +89,8 @@ static int toggle_Magic(int x) #define EMSG_RET_FAIL(m) return (emsg(m), rc_did_emsg = true, FAIL) #define EMSG2_RET_NULL(m, c) \ return (semsg((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) +#define EMSG3_RET_NULL(m, c, a) \ + return (semsg((const char *)(m), (c) ? "" : "\\", (a)), rc_did_emsg = true, (void *)NULL) #define EMSG2_RET_FAIL(m, c) \ return (semsg((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ @@ -475,14 +98,6 @@ static int toggle_Magic(int x) #define MAX_LIMIT (32767L << 16L) - -#ifdef BT_REGEXP_DUMP -static void regdump(char_u *, bt_regprog_T *); -#endif -#ifdef REGEXP_DEBUG -static char_u *regprop(char_u *); -#endif - static char_u e_missingbracket[] = N_("E769: Missing ] after %s["); static char_u e_reverse_range[] = N_("E944: Reverse range in character class"); static char_u e_large_class[] = N_("E945: Range too large in character class"); @@ -492,17 +107,25 @@ static char_u e_unmatchedpar[] = N_("E55: Unmatched %s)"); 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"); +static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); +static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); +static char_u e_regexp_number_after_dot_pos_search[] + = N_("E1204: No Number allowed after .: '\\%%%c'"); #define NOT_MULTI 0 #define MULTI_ONE 1 #define MULTI_MULT 2 -/* - * Return NOT_MULTI if c is not a "multi" operator. - * Return MULTI_ONE if c is a single "multi" operator. - * Return MULTI_MULT if c is a multi "multi" operator. - */ + +// return values for regmatch() +#define RA_FAIL 1 // something failed, abort +#define RA_CONT 2 // continue in inner loop +#define RA_BREAK 3 // break inner loop +#define RA_MATCH 4 // successful match +#define RA_NOMATCH 5 // didn't match + +/// Return NOT_MULTI if c is not a "multi" operator. +/// Return MULTI_ONE if c is a single "multi" operator. +/// Return MULTI_MULT if c is a multi "multi" operator. static int re_multi_type(int c) { if (c == Magic('@') || c == Magic('=') || c == Magic('?')) @@ -512,22 +135,6 @@ static int re_multi_type(int c) return NOT_MULTI; } -/* - * Flags to be passed up and down. - */ -#define HASWIDTH 0x1 /* Known never to match null string. */ -#define SIMPLE 0x2 /* Simple enough to be STAR/PLUS operand. */ -#define SPSTART 0x4 /* Starts with * or +. */ -#define HASNL 0x8 /* Contains some \n. */ -#define HASLOOKBH 0x10 /* Contains "\@<=" or "\@<!". */ -#define WORST 0 /* Worst case. */ - -/* - * When regcode is set to this value, code is not emitted and size is computed - * instead. - */ -#define JUST_CALC_SIZE ((char_u *) -1) - static char_u *reg_prev_sub = NULL; /* @@ -672,48 +279,38 @@ static void init_class_tab(void) done = true; } -# define ri_digit(c) (c < 0x100 && (class_tab[c] & RI_DIGIT)) -# define ri_hex(c) (c < 0x100 && (class_tab[c] & RI_HEX)) -# define ri_octal(c) (c < 0x100 && (class_tab[c] & RI_OCTAL)) -# define ri_word(c) (c < 0x100 && (class_tab[c] & RI_WORD)) -# define ri_head(c) (c < 0x100 && (class_tab[c] & RI_HEAD)) -# define ri_alpha(c) (c < 0x100 && (class_tab[c] & RI_ALPHA)) -# define ri_lower(c) (c < 0x100 && (class_tab[c] & RI_LOWER)) -# define ri_upper(c) (c < 0x100 && (class_tab[c] & RI_UPPER)) -# define ri_white(c) (c < 0x100 && (class_tab[c] & RI_WHITE)) - -/* flags for regflags */ -#define RF_ICASE 1 /* ignore case */ -#define RF_NOICASE 2 /* don't ignore case */ -#define RF_HASNL 4 /* can match a NL */ -#define RF_ICOMBINE 8 /* ignore combining characters */ -#define RF_LOOKBH 16 /* uses "\@<=" or "\@<!" */ +# define ri_digit(c) ((c) < 0x100 && (class_tab[c] & RI_DIGIT)) +# define ri_hex(c) ((c) < 0x100 && (class_tab[c] & RI_HEX)) +# define ri_octal(c) ((c) < 0x100 && (class_tab[c] & RI_OCTAL)) +# define ri_word(c) ((c) < 0x100 && (class_tab[c] & RI_WORD)) +# define ri_head(c) ((c) < 0x100 && (class_tab[c] & RI_HEAD)) +# define ri_alpha(c) ((c) < 0x100 && (class_tab[c] & RI_ALPHA)) +# define ri_lower(c) ((c) < 0x100 && (class_tab[c] & RI_LOWER)) +# define ri_upper(c) ((c) < 0x100 && (class_tab[c] & RI_UPPER)) +# define ri_white(c) ((c) < 0x100 && (class_tab[c] & RI_WHITE)) + +// flags for regflags +#define RF_ICASE 1 // ignore case +#define RF_NOICASE 2 // don't ignore case +#define RF_HASNL 4 // can match a NL +#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 bool wants_nfa; ///< regex should use NFA engine 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 */ -#define MAGIC_OFF 2 /* "\M" or 'magic' off */ -#define MAGIC_ON 3 /* "\m" or 'magic' */ -#define MAGIC_ALL 4 /* "\v" very magic */ +static unsigned regflags; ///< RF_ flags for prog +static int had_eol; ///< true when EOL found by vim_regcomp() + +static int reg_magic; // magicness of the pattern: +#define MAGIC_NONE 1 // "\V" very unmagic +#define MAGIC_OFF 2 // "\M" or 'magic' off +#define MAGIC_ON 3 // "\m" or 'magic' +#define MAGIC_ALL 4 // "\v" very magic static int reg_string; // matching with a string instead of a buffer // line @@ -723,22 +320,22 @@ static int reg_strict; // "[abc" is illegal * META contains all characters that may be magic, except '^' and '$'. */ -/* META[] is used often enough to justify turning it into a table. */ +// META[] is used often enough to justify turning it into a table. static char_u META_flags[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* % & ( ) * + . */ - 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, - /* 1 2 3 4 5 6 7 8 9 < = > ? */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, - /* @ A C D F H I K L M O */ - 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, - /* P S U V W X Z [ _ */ - 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, - /* a c d f h i k l m n o */ - 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, - /* p s u v w x z { | ~ */ - 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// % & ( ) * + . + 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, +// 1 2 3 4 5 6 7 8 9 < = > ? + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, +// @ A C D F H I K L M O + 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, +// P S U V W X Z [ _ + 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, +// a c d f h i k l m n o + 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, +// p s u v w x z { | ~ + 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1 }; static int curchr; // currently parsed character @@ -746,24 +343,36 @@ static int curchr; // currently parsed character // start, eg in /[ ^I]^ the pattern was never found even if it existed, // because ^ was taken to be magic -- webb static int prevchr; -static int prevprevchr; /* previous-previous character */ -static int nextchr; /* used for ungetchr() */ +static int prevprevchr; // previous-previous character +static int nextchr; // used for ungetchr() -/* arguments for reg() */ -#define REG_NOPAREN 0 /* toplevel reg() */ -#define REG_PAREN 1 /* \(\) */ -#define REG_ZPAREN 2 /* \z(\) */ -#define REG_NPAREN 3 /* \%(\) */ +// arguments for reg() +#define REG_NOPAREN 0 // toplevel reg() +#define REG_PAREN 1 // \(\) +#define REG_ZPAREN 2 // \z(\) +#define REG_NPAREN 3 // \%(\) + +typedef struct { + char_u *regparse; + int prevchr_len; + int curchr; + int prevchr; + int prevprevchr; + int nextchr; + int at_start; + int prev_at_start; + int regnpar; +} parse_state_T; -/* - * Forward declarations for vim_regcomp()'s friends. - */ -# define REGMBC(x) regmbc(x); -# define CASEMBC(x) case x: static regengine_T bt_regengine; static regengine_T nfa_regengine; +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "regexp.c.generated.h" +#endif + + // Return true if compiled regular expression "prog" can match a line break. int re_multiline(const regprog_T *prog) FUNC_ATTR_NONNULL_ALL @@ -795,312 +404,6 @@ static int get_equi_class(char_u **pp) /* - * Produce the bytes for equivalence class "c". - * Currently only handles latin1, latin9 and utf-8. - * NOTE: When changing this function, also change nfa_emit_equi_class() - */ -static void reg_equi_class(int c) -{ - { - switch (c) { - // Do not use '\300' style, it results in a negative number. - case 'A': case 0xc0: case 0xc1: case 0xc2: - case 0xc3: case 0xc4: case 0xc5: - CASEMBC(0x100) CASEMBC(0x102) CASEMBC(0x104) CASEMBC(0x1cd) - CASEMBC(0x1de) CASEMBC(0x1e0) CASEMBC(0x1ea2) - regmbc('A'); regmbc(0xc0); regmbc(0xc1); - regmbc(0xc2); regmbc(0xc3); regmbc(0xc4); - regmbc(0xc5); - REGMBC(0x100) REGMBC(0x102) REGMBC(0x104) - REGMBC(0x1cd) REGMBC(0x1de) REGMBC(0x1e0) - REGMBC(0x1ea2) - return; - case 'B': CASEMBC(0x1e02) CASEMBC(0x1e06) - regmbc('B'); REGMBC(0x1e02) REGMBC(0x1e06) - return; - case 'C': case 0xc7: - CASEMBC(0x106) CASEMBC(0x108) CASEMBC(0x10a) CASEMBC(0x10c) - regmbc('C'); regmbc(0xc7); - REGMBC(0x106) REGMBC(0x108) REGMBC(0x10a) - REGMBC(0x10c) - return; - case 'D': CASEMBC(0x10e) CASEMBC(0x110) CASEMBC(0x1e0a) - CASEMBC(0x1e0e) CASEMBC(0x1e10) - regmbc('D'); REGMBC(0x10e) REGMBC(0x110) - REGMBC(0x1e0a) REGMBC(0x1e0e) REGMBC(0x1e10) - return; - case 'E': case 0xc8: case 0xc9: case 0xca: case 0xcb: - CASEMBC(0x112) CASEMBC(0x114) CASEMBC(0x116) CASEMBC(0x118) - CASEMBC(0x11a) CASEMBC(0x1eba) CASEMBC(0x1ebc) - regmbc('E'); regmbc(0xc8); regmbc(0xc9); - regmbc(0xca); regmbc(0xcb); - REGMBC(0x112) REGMBC(0x114) REGMBC(0x116) - REGMBC(0x118) REGMBC(0x11a) REGMBC(0x1eba) - REGMBC(0x1ebc) - return; - case 'F': CASEMBC(0x1e1e) - regmbc('F'); REGMBC(0x1e1e) - return; - case 'G': CASEMBC(0x11c) CASEMBC(0x11e) CASEMBC(0x120) - CASEMBC(0x122) CASEMBC(0x1e4) CASEMBC(0x1e6) CASEMBC(0x1f4) - CASEMBC(0x1e20) - regmbc('G'); REGMBC(0x11c) REGMBC(0x11e) - REGMBC(0x120) REGMBC(0x122) REGMBC(0x1e4) - REGMBC(0x1e6) REGMBC(0x1f4) REGMBC(0x1e20) - return; - case 'H': CASEMBC(0x124) CASEMBC(0x126) CASEMBC(0x1e22) - CASEMBC(0x1e26) CASEMBC(0x1e28) - regmbc('H'); REGMBC(0x124) REGMBC(0x126) - REGMBC(0x1e22) REGMBC(0x1e26) REGMBC(0x1e28) - return; - case 'I': case 0xcc: case 0xcd: case 0xce: case 0xcf: - CASEMBC(0x128) CASEMBC(0x12a) CASEMBC(0x12c) CASEMBC(0x12e) - CASEMBC(0x130) CASEMBC(0x1cf) CASEMBC(0x1ec8) - regmbc('I'); regmbc(0xcc); regmbc(0xcd); - regmbc(0xce); regmbc(0xcf); - REGMBC(0x128) REGMBC(0x12a) REGMBC(0x12c) - REGMBC(0x12e) REGMBC(0x130) REGMBC(0x1cf) - REGMBC(0x1ec8) - return; - case 'J': CASEMBC(0x134) - regmbc('J'); REGMBC(0x134) - return; - case 'K': CASEMBC(0x136) CASEMBC(0x1e8) CASEMBC(0x1e30) - CASEMBC(0x1e34) - regmbc('K'); REGMBC(0x136) REGMBC(0x1e8) - REGMBC(0x1e30) REGMBC(0x1e34) - return; - case 'L': CASEMBC(0x139) CASEMBC(0x13b) CASEMBC(0x13d) - CASEMBC(0x13f) CASEMBC(0x141) CASEMBC(0x1e3a) - regmbc('L'); REGMBC(0x139) REGMBC(0x13b) - REGMBC(0x13d) REGMBC(0x13f) REGMBC(0x141) - REGMBC(0x1e3a) - return; - case 'M': CASEMBC(0x1e3e) CASEMBC(0x1e40) - regmbc('M'); REGMBC(0x1e3e) REGMBC(0x1e40) - return; - case 'N': case 0xd1: - CASEMBC(0x143) CASEMBC(0x145) CASEMBC(0x147) CASEMBC(0x1e44) - CASEMBC(0x1e48) - regmbc('N'); regmbc(0xd1); - REGMBC(0x143) REGMBC(0x145) REGMBC(0x147) - REGMBC(0x1e44) REGMBC(0x1e48) - return; - case 'O': case 0xd2: case 0xd3: case 0xd4: case 0xd5: - case 0xd6: case 0xd8: - CASEMBC(0x14c) CASEMBC(0x14e) CASEMBC(0x150) CASEMBC(0x1a0) - CASEMBC(0x1d1) CASEMBC(0x1ea) CASEMBC(0x1ec) CASEMBC(0x1ece) - regmbc('O'); regmbc(0xd2); regmbc(0xd3); - regmbc(0xd4); regmbc(0xd5); regmbc(0xd6); - regmbc(0xd8); - REGMBC(0x14c) REGMBC(0x14e) REGMBC(0x150) - REGMBC(0x1a0) REGMBC(0x1d1) REGMBC(0x1ea) - REGMBC(0x1ec) REGMBC(0x1ece) - return; - case 'P': case 0x1e54: case 0x1e56: - regmbc('P'); REGMBC(0x1e54) REGMBC(0x1e56) - return; - case 'R': CASEMBC(0x154) CASEMBC(0x156) CASEMBC(0x158) - CASEMBC(0x1e58) CASEMBC(0x1e5e) - regmbc('R'); REGMBC(0x154) REGMBC(0x156) REGMBC(0x158) - REGMBC(0x1e58) REGMBC(0x1e5e) - return; - case 'S': CASEMBC(0x15a) CASEMBC(0x15c) CASEMBC(0x15e) - CASEMBC(0x160) CASEMBC(0x1e60) - regmbc('S'); REGMBC(0x15a) REGMBC(0x15c) - REGMBC(0x15e) REGMBC(0x160) REGMBC(0x1e60) - return; - case 'T': CASEMBC(0x162) CASEMBC(0x164) CASEMBC(0x166) - CASEMBC(0x1e6a) CASEMBC(0x1e6e) - regmbc('T'); REGMBC(0x162) REGMBC(0x164) - REGMBC(0x166) REGMBC(0x1e6a) REGMBC(0x1e6e) - return; - case 'U': case 0xd9: case 0xda: case 0xdb: case 0xdc: - CASEMBC(0x168) CASEMBC(0x16a) CASEMBC(0x16c) CASEMBC(0x16e) - CASEMBC(0x170) CASEMBC(0x172) CASEMBC(0x1af) CASEMBC(0x1d3) - CASEMBC(0x1ee6) - regmbc('U'); regmbc(0xd9); regmbc(0xda); - regmbc(0xdb); regmbc(0xdc); - REGMBC(0x168) REGMBC(0x16a) REGMBC(0x16c) - REGMBC(0x16e) REGMBC(0x170) REGMBC(0x172) - REGMBC(0x1af) REGMBC(0x1d3) REGMBC(0x1ee6) - return; - case 'V': CASEMBC(0x1e7c) - regmbc('V'); REGMBC(0x1e7c) - return; - case 'W': CASEMBC(0x174) CASEMBC(0x1e80) CASEMBC(0x1e82) - CASEMBC(0x1e84) CASEMBC(0x1e86) - regmbc('W'); REGMBC(0x174) REGMBC(0x1e80) - REGMBC(0x1e82) REGMBC(0x1e84) REGMBC(0x1e86) - return; - case 'X': CASEMBC(0x1e8a) CASEMBC(0x1e8c) - regmbc('X'); REGMBC(0x1e8a) REGMBC(0x1e8c) - return; - case 'Y': case 0xdd: - CASEMBC(0x176) CASEMBC(0x178) CASEMBC(0x1e8e) CASEMBC(0x1ef2) - CASEMBC(0x1ef6) CASEMBC(0x1ef8) - regmbc('Y'); regmbc(0xdd); - REGMBC(0x176) REGMBC(0x178) REGMBC(0x1e8e) - REGMBC(0x1ef2) REGMBC(0x1ef6) REGMBC(0x1ef8) - return; - case 'Z': CASEMBC(0x179) CASEMBC(0x17b) CASEMBC(0x17d) - CASEMBC(0x1b5) CASEMBC(0x1e90) CASEMBC(0x1e94) - regmbc('Z'); REGMBC(0x179) REGMBC(0x17b) - REGMBC(0x17d) REGMBC(0x1b5) REGMBC(0x1e90) - REGMBC(0x1e94) - return; - case 'a': case 0xe0: case 0xe1: case 0xe2: - case 0xe3: case 0xe4: case 0xe5: - CASEMBC(0x101) CASEMBC(0x103) CASEMBC(0x105) CASEMBC(0x1ce) - CASEMBC(0x1df) CASEMBC(0x1e1) CASEMBC(0x1ea3) - regmbc('a'); regmbc(0xe0); regmbc(0xe1); - regmbc(0xe2); regmbc(0xe3); regmbc(0xe4); - regmbc(0xe5); - REGMBC(0x101) REGMBC(0x103) REGMBC(0x105) - REGMBC(0x1ce) REGMBC(0x1df) REGMBC(0x1e1) - REGMBC(0x1ea3) - return; - case 'b': CASEMBC(0x1e03) CASEMBC(0x1e07) - regmbc('b'); REGMBC(0x1e03) REGMBC(0x1e07) - return; - case 'c': case 0xe7: - CASEMBC(0x107) CASEMBC(0x109) CASEMBC(0x10b) CASEMBC(0x10d) - regmbc('c'); regmbc(0xe7); - REGMBC(0x107) REGMBC(0x109) REGMBC(0x10b) - REGMBC(0x10d) - return; - case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1e0b) - CASEMBC(0x1e0f) CASEMBC(0x1e11) - regmbc('d'); REGMBC(0x10f) REGMBC(0x111) - REGMBC(0x1e0b) REGMBC(0x1e0f) REGMBC(0x1e11) - return; - case 'e': case 0xe8: case 0xe9: case 0xea: case 0xeb: - CASEMBC(0x113) CASEMBC(0x115) CASEMBC(0x117) CASEMBC(0x119) - CASEMBC(0x11b) CASEMBC(0x1ebb) CASEMBC(0x1ebd) - regmbc('e'); regmbc(0xe8); regmbc(0xe9); - regmbc(0xea); regmbc(0xeb); - REGMBC(0x113) REGMBC(0x115) REGMBC(0x117) - REGMBC(0x119) REGMBC(0x11b) REGMBC(0x1ebb) - REGMBC(0x1ebd) - return; - case 'f': CASEMBC(0x1e1f) - regmbc('f'); REGMBC(0x1e1f) - return; - case 'g': CASEMBC(0x11d) CASEMBC(0x11f) CASEMBC(0x121) - CASEMBC(0x123) CASEMBC(0x1e5) CASEMBC(0x1e7) CASEMBC(0x1f5) - CASEMBC(0x1e21) - regmbc('g'); REGMBC(0x11d) REGMBC(0x11f) - REGMBC(0x121) REGMBC(0x123) REGMBC(0x1e5) - REGMBC(0x1e7) REGMBC(0x1f5) REGMBC(0x1e21) - return; - case 'h': CASEMBC(0x125) CASEMBC(0x127) CASEMBC(0x1e23) - CASEMBC(0x1e27) CASEMBC(0x1e29) CASEMBC(0x1e96) - regmbc('h'); REGMBC(0x125) REGMBC(0x127) - REGMBC(0x1e23) REGMBC(0x1e27) REGMBC(0x1e29) - REGMBC(0x1e96) - return; - case 'i': case 0xec: case 0xed: case 0xee: case 0xef: - CASEMBC(0x129) CASEMBC(0x12b) CASEMBC(0x12d) CASEMBC(0x12f) - CASEMBC(0x1d0) CASEMBC(0x1ec9) - regmbc('i'); regmbc(0xec); regmbc(0xed); - regmbc(0xee); regmbc(0xef); - REGMBC(0x129) REGMBC(0x12b) REGMBC(0x12d) - REGMBC(0x12f) REGMBC(0x1d0) REGMBC(0x1ec9) - return; - case 'j': CASEMBC(0x135) CASEMBC(0x1f0) - regmbc('j'); REGMBC(0x135) REGMBC(0x1f0) - return; - case 'k': CASEMBC(0x137) CASEMBC(0x1e9) CASEMBC(0x1e31) - CASEMBC(0x1e35) - regmbc('k'); REGMBC(0x137) REGMBC(0x1e9) - REGMBC(0x1e31) REGMBC(0x1e35) - return; - case 'l': CASEMBC(0x13a) CASEMBC(0x13c) CASEMBC(0x13e) - CASEMBC(0x140) CASEMBC(0x142) CASEMBC(0x1e3b) - regmbc('l'); REGMBC(0x13a) REGMBC(0x13c) - REGMBC(0x13e) REGMBC(0x140) REGMBC(0x142) - REGMBC(0x1e3b) - return; - case 'm': CASEMBC(0x1e3f) CASEMBC(0x1e41) - regmbc('m'); REGMBC(0x1e3f) REGMBC(0x1e41) - return; - case 'n': case 0xf1: - CASEMBC(0x144) CASEMBC(0x146) CASEMBC(0x148) CASEMBC(0x149) - CASEMBC(0x1e45) CASEMBC(0x1e49) - regmbc('n'); regmbc(0xf1); - REGMBC(0x144) REGMBC(0x146) REGMBC(0x148) - REGMBC(0x149) REGMBC(0x1e45) REGMBC(0x1e49) - return; - case 'o': case 0xf2: case 0xf3: case 0xf4: case 0xf5: - case 0xf6: case 0xf8: - CASEMBC(0x14d) CASEMBC(0x14f) CASEMBC(0x151) CASEMBC(0x1a1) - CASEMBC(0x1d2) CASEMBC(0x1eb) CASEMBC(0x1ed) CASEMBC(0x1ecf) - regmbc('o'); regmbc(0xf2); regmbc(0xf3); - regmbc(0xf4); regmbc(0xf5); regmbc(0xf6); - regmbc(0xf8); - REGMBC(0x14d) REGMBC(0x14f) REGMBC(0x151) - REGMBC(0x1a1) REGMBC(0x1d2) REGMBC(0x1eb) - REGMBC(0x1ed) REGMBC(0x1ecf) - return; - case 'p': CASEMBC(0x1e55) CASEMBC(0x1e57) - regmbc('p'); REGMBC(0x1e55) REGMBC(0x1e57) - return; - case 'r': CASEMBC(0x155) CASEMBC(0x157) CASEMBC(0x159) - CASEMBC(0x1e59) CASEMBC(0x1e5f) - regmbc('r'); REGMBC(0x155) REGMBC(0x157) REGMBC(0x159) - REGMBC(0x1e59) REGMBC(0x1e5f) - return; - case 's': CASEMBC(0x15b) CASEMBC(0x15d) CASEMBC(0x15f) - CASEMBC(0x161) CASEMBC(0x1e61) - regmbc('s'); REGMBC(0x15b) REGMBC(0x15d) - REGMBC(0x15f) REGMBC(0x161) REGMBC(0x1e61) - return; - case 't': CASEMBC(0x163) CASEMBC(0x165) CASEMBC(0x167) - CASEMBC(0x1e6b) CASEMBC(0x1e6f) CASEMBC(0x1e97) - regmbc('t'); REGMBC(0x163) REGMBC(0x165) REGMBC(0x167) - REGMBC(0x1e6b) REGMBC(0x1e6f) REGMBC(0x1e97) - return; - case 'u': case 0xf9: case 0xfa: case 0xfb: case 0xfc: - CASEMBC(0x169) CASEMBC(0x16b) CASEMBC(0x16d) CASEMBC(0x16f) - CASEMBC(0x171) CASEMBC(0x173) CASEMBC(0x1b0) CASEMBC(0x1d4) - CASEMBC(0x1ee7) - regmbc('u'); regmbc(0xf9); regmbc(0xfa); - regmbc(0xfb); regmbc(0xfc); - REGMBC(0x169) REGMBC(0x16b) REGMBC(0x16d) - REGMBC(0x16f) REGMBC(0x171) REGMBC(0x173) - REGMBC(0x1b0) REGMBC(0x1d4) REGMBC(0x1ee7) - return; - case 'v': CASEMBC(0x1e7d) - regmbc('v'); REGMBC(0x1e7d) - return; - case 'w': CASEMBC(0x175) CASEMBC(0x1e81) CASEMBC(0x1e83) - CASEMBC(0x1e85) CASEMBC(0x1e87) CASEMBC(0x1e98) - regmbc('w'); REGMBC(0x175) REGMBC(0x1e81) - REGMBC(0x1e83) REGMBC(0x1e85) REGMBC(0x1e87) - REGMBC(0x1e98) - return; - case 'x': CASEMBC(0x1e8b) CASEMBC(0x1e8d) - regmbc('x'); REGMBC(0x1e8b) REGMBC(0x1e8d) - return; - case 'y': case 0xfd: case 0xff: - CASEMBC(0x177) CASEMBC(0x1e8f) CASEMBC(0x1e99) - CASEMBC(0x1ef3) CASEMBC(0x1ef7) CASEMBC(0x1ef9) - regmbc('y'); regmbc(0xfd); regmbc(0xff); - REGMBC(0x177) REGMBC(0x1e8f) REGMBC(0x1e99) - REGMBC(0x1ef3) REGMBC(0x1ef7) REGMBC(0x1ef9) - return; - case 'z': CASEMBC(0x17a) CASEMBC(0x17c) CASEMBC(0x17e) - CASEMBC(0x1b6) CASEMBC(0x1e91) CASEMBC(0x1e95) - regmbc('z'); REGMBC(0x17a) REGMBC(0x17c) - REGMBC(0x17e) REGMBC(0x1b6) REGMBC(0x1e91) - REGMBC(0x1e95) - return; - } - } - regmbc(c); -} - -/* * Check for a collating element "[.a.]". "pp" points to the '['. * Returns a character. Zero means that no item was recognized. Otherwise * "pp" is advanced to after the item. @@ -1123,7 +426,7 @@ static int get_coll_element(char_u **pp) return 0; } -static int reg_cpo_lit; /* 'cpoptions' contains 'l' flag */ +static int reg_cpo_lit; // 'cpoptions' contains 'l' flag static void get_cpo_flags(void) { @@ -1139,10 +442,12 @@ static char_u *skip_anyof(char_u *p) { int l; - if (*p == '^') /* Complement of range. */ - ++p; - if (*p == ']' || *p == '-') - ++p; + if (*p == '^') { // Complement of range. + p++; + } + if (*p == ']' || *p == '-') { + p++; + } while (*p != NUL && *p != ']') { if ((l = utfc_ptr2len(p)) > 1) { p += l; @@ -1202,1549 +507,32 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) break; } else if (p[0] == '\\' && p[1] != NUL) { if (dirc == '?' && newp != NULL && p[1] == '?') { - /* change "\?" to "?", make a copy first. */ + // change "\?" to "?", make a copy first. if (*newp == NULL) { *newp = vim_strsave(startp); p = *newp + (p - startp); } STRMOVE(p, p + 1); - } else - ++p; /* skip next character */ - if (*p == 'v') + } else { + p++; // skip next character + } + if (*p == 'v') { mymagic = MAGIC_ALL; - else if (*p == 'V') + } else if (*p == 'V') { mymagic = MAGIC_NONE; - } - } - return p; -} - -/// 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? -static int seen_endbrace(int refnum) -{ - if (!had_endbrace[refnum]) { - char_u *p; - - // Trick: check if "@<=" or "@<!" follows, in which case - // the \1 can appear before the referenced match. - for (p = regparse; *p != NUL; p++) { - if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) { - break; - } - } - - if (*p == NUL) { - emsg(_("E65: Illegal back reference")); - rc_did_emsg = true; - return false; - } - } - return true; -} - -/* - * bt_regcomp() - compile a regular expression into internal code for the - * traditional back track matcher. - * Returns the program in allocated space. Returns NULL for an error. - * - * We can't allocate space until we know how big the compiled form will be, - * but we can't compile it (and thus know how big it is) until we've got a - * place to put the code. So we cheat: we compile it twice, once with code - * generation turned off and size counting turned on, and once "for real". - * This also means that we don't allocate space until we are sure that the - * thing really will compile successfully, and we never have to move the - * code and thus invalidate pointers into it. (Note that it has to be in - * one piece because free() must be able to free it all.) - * - * Whether upper/lower case is to be ignored is decided when executing the - * program, it does not matter here. - * - * Beware that the optimization-preparation code in here knows about some - * of the structure of the compiled regexp. - * "re_flags": RE_MAGIC and/or RE_STRING. - */ -static regprog_T *bt_regcomp(char_u *expr, int re_flags) -{ - char_u *scan; - char_u *longest; - int len; - int flags; - - if (expr == NULL) { - IEMSG_RET_NULL(_(e_null)); - } - - init_class_tab(); - - /* - * First pass: determine size, legality. - */ - regcomp_start(expr, re_flags); - regcode = JUST_CALC_SIZE; - regc(REGMAGIC); - if (reg(REG_NOPAREN, &flags) == NULL) - return NULL; - - /* Allocate space. */ - bt_regprog_T *r = xmalloc(sizeof(bt_regprog_T) + regsize); - r->re_in_use = false; - - /* - * Second pass: emit code. - */ - regcomp_start(expr, re_flags); - regcode = r->program; - regc(REGMAGIC); - if (reg(REG_NOPAREN, &flags) == NULL || reg_toolong) { - xfree(r); - if (reg_toolong) - EMSG_RET_NULL(_("E339: Pattern too long")); - return NULL; - } - - /* Dig out information for optimizations. */ - r->regstart = NUL; /* Worst-case defaults. */ - r->reganch = 0; - r->regmust = NULL; - r->regmlen = 0; - r->regflags = regflags; - if (flags & HASNL) - r->regflags |= RF_HASNL; - if (flags & HASLOOKBH) - r->regflags |= RF_LOOKBH; - /* Remember whether this pattern has any \z specials in it. */ - r->reghasz = re_has_z; - scan = r->program + 1; /* First BRANCH. */ - if (OP(regnext(scan)) == END) { /* Only one top-level choice. */ - scan = OPERAND(scan); - - /* Starting-point info. */ - if (OP(scan) == BOL || OP(scan) == RE_BOF) { - r->reganch++; - scan = regnext(scan); - } - - if (OP(scan) == EXACTLY) { - r->regstart = utf_ptr2char(OPERAND(scan)); - } else if (OP(scan) == BOW - || OP(scan) == EOW - || OP(scan) == NOTHING - || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN - || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) { - char_u *regnext_scan = regnext(scan); - if (OP(regnext_scan) == EXACTLY) { - r->regstart = utf_ptr2char(OPERAND(regnext_scan)); } } - - /* - * If there's something expensive in the r.e., find the longest - * literal string that must appear and make it the regmust. Resolve - * ties in favor of later strings, since the regstart check works - * with the beginning of the r.e. and avoiding duplication - * strengthens checking. Not a strong reason, but sufficient in the - * absence of others. - */ - /* - * When the r.e. starts with BOW, it is faster to look for a regmust - * first. Used a lot for "#" and "*" commands. (Added by mool). - */ - if ((flags & SPSTART || OP(scan) == BOW || OP(scan) == EOW) - && !(flags & HASNL)) { - longest = NULL; - len = 0; - for (; scan != NULL; scan = regnext(scan)) - if (OP(scan) == EXACTLY && STRLEN(OPERAND(scan)) >= (size_t)len) { - longest = OPERAND(scan); - len = (int)STRLEN(OPERAND(scan)); - } - r->regmust = longest; - r->regmlen = len; - } } -#ifdef BT_REGEXP_DUMP - regdump(expr, r); -#endif - r->engine = &bt_regengine; - return (regprog_T *)r; -} - -/* - * Free a compiled regexp program, returned by bt_regcomp(). - */ -static void bt_regfree(regprog_T *prog) -{ - xfree(prog); -} - -/* - * Setup to parse the regexp. Used once to get the length and once to do it. - */ -static void -regcomp_start ( - char_u *expr, - int re_flags /* see vim_regcomp() */ -) -{ - initchr(expr); - if (re_flags & RE_MAGIC) - reg_magic = MAGIC_ON; - else - reg_magic = MAGIC_OFF; - reg_string = (re_flags & RE_STRING); - reg_strict = (re_flags & RE_STRICT); - get_cpo_flags(); - - num_complex_braces = 0; - regnpar = 1; - memset(had_endbrace, 0, sizeof(had_endbrace)); - regnzpar = 1; - re_has_z = 0; - regsize = 0L; - reg_toolong = false; - regflags = 0; - had_eol = false; + return p; } -/* - * Check if during the previous call to vim_regcomp the EOL item "$" has been - * found. This is messy, but it works fine. - */ -int vim_regcomp_had_eol(void) -{ - return had_eol; -} // variables used for parsing +static int prevchr_len; // byte length of previous char static int at_start; // True when on the first character static int prev_at_start; // True when on the second character /* - * Parse regular expression, i.e. main body or parenthesized thing. - * - * Caller must absorb opening parenthesis. - * - * Combining parenthesis handling with the base level of regular expression - * is a trifle forced, but the need to tie the tails of the branches to what - * follows makes it hard to avoid. - */ -static char_u * -reg ( - int paren, /* REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN */ - int *flagp -) -{ - char_u *ret; - char_u *br; - char_u *ender; - int parno = 0; - int flags; - - *flagp = HASWIDTH; /* Tentatively. */ - - if (paren == REG_ZPAREN) { - /* Make a ZOPEN node. */ - if (regnzpar >= NSUBEXP) - EMSG_RET_NULL(_("E50: Too many \\z(")); - parno = regnzpar; - regnzpar++; - ret = regnode(ZOPEN + parno); - } else if (paren == REG_PAREN) { - /* Make a MOPEN node. */ - if (regnpar >= NSUBEXP) - EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL); - parno = regnpar; - ++regnpar; - ret = regnode(MOPEN + parno); - } else if (paren == REG_NPAREN) { - /* Make a NOPEN node. */ - ret = regnode(NOPEN); - } else - ret = NULL; - - /* Pick up the branches, linking them together. */ - br = regbranch(&flags); - if (br == NULL) - return NULL; - if (ret != NULL) - regtail(ret, br); /* [MZ]OPEN -> first. */ - else - ret = br; - /* If one of the branches can be zero-width, the whole thing can. - * If one of the branches has * at start or matches a line-break, the - * whole thing can. */ - if (!(flags & HASWIDTH)) - *flagp &= ~HASWIDTH; - *flagp |= flags & (SPSTART | HASNL | HASLOOKBH); - while (peekchr() == Magic('|')) { - skipchr(); - br = regbranch(&flags); - if (br == NULL || reg_toolong) - return NULL; - regtail(ret, br); /* BRANCH -> BRANCH. */ - if (!(flags & HASWIDTH)) - *flagp &= ~HASWIDTH; - *flagp |= flags & (SPSTART | HASNL | HASLOOKBH); - } - - /* Make a closing node, and hook it on the end. */ - ender = regnode( - paren == REG_ZPAREN ? ZCLOSE + parno : - paren == REG_PAREN ? MCLOSE + parno : - paren == REG_NPAREN ? NCLOSE : END); - regtail(ret, ender); - - /* Hook the tails of the branches to the closing node. */ - for (br = ret; br != NULL; br = regnext(br)) - regoptail(br, ender); - - /* Check for proper termination. */ - if (paren != REG_NOPAREN && getchr() != Magic(')')) { - if (paren == REG_ZPAREN) - EMSG_RET_NULL(_("E52: Unmatched \\z(")); - else if (paren == REG_NPAREN) - EMSG2_RET_NULL(_(e_unmatchedpp), reg_magic == MAGIC_ALL); - else - EMSG2_RET_NULL(_(e_unmatchedp), reg_magic == MAGIC_ALL); - } else if (paren == REG_NOPAREN && peekchr() != NUL) { - if (curchr == Magic(')')) - EMSG2_RET_NULL(_(e_unmatchedpar), reg_magic == MAGIC_ALL); - else - 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 - } - return ret; -} - -/* - * Parse one alternative of an | operator. - * Implements the & operator. - */ -static char_u *regbranch(int *flagp) -{ - char_u *ret; - char_u *chain = NULL; - char_u *latest; - int flags; - - *flagp = WORST | HASNL; /* Tentatively. */ - - ret = regnode(BRANCH); - for (;; ) { - latest = regconcat(&flags); - if (latest == NULL) - return NULL; - /* If one of the branches has width, the whole thing has. If one of - * the branches anchors at start-of-line, the whole thing does. - * If one of the branches uses look-behind, the whole thing does. */ - *flagp |= flags & (HASWIDTH | SPSTART | HASLOOKBH); - /* If one of the branches doesn't match a line-break, the whole thing - * doesn't. */ - *flagp &= ~HASNL | (flags & HASNL); - if (chain != NULL) - regtail(chain, latest); - if (peekchr() != Magic('&')) - break; - skipchr(); - regtail(latest, regnode(END)); /* operand ends */ - if (reg_toolong) - break; - reginsert(MATCH, latest); - chain = latest; - } - - return ret; -} - -/* - * Parse one alternative of an | or & operator. - * Implements the concatenation operator. - */ -static char_u *regconcat(int *flagp) -{ - char_u *first = NULL; - char_u *chain = NULL; - char_u *latest; - int flags; - int cont = true; - - *flagp = WORST; /* Tentatively. */ - - while (cont) { - switch (peekchr()) { - case NUL: - case Magic('|'): - case Magic('&'): - case Magic(')'): - cont = false; - break; - case Magic('Z'): - regflags |= RF_ICOMBINE; - skipchr_keepstart(); - break; - case Magic('c'): - regflags |= RF_ICASE; - skipchr_keepstart(); - break; - case Magic('C'): - regflags |= RF_NOICASE; - skipchr_keepstart(); - break; - case Magic('v'): - reg_magic = MAGIC_ALL; - skipchr_keepstart(); - curchr = -1; - break; - case Magic('m'): - reg_magic = MAGIC_ON; - skipchr_keepstart(); - curchr = -1; - break; - case Magic('M'): - reg_magic = MAGIC_OFF; - skipchr_keepstart(); - curchr = -1; - break; - case Magic('V'): - reg_magic = MAGIC_NONE; - skipchr_keepstart(); - curchr = -1; - break; - default: - latest = regpiece(&flags); - if (latest == NULL || reg_toolong) - return NULL; - *flagp |= flags & (HASWIDTH | HASNL | HASLOOKBH); - if (chain == NULL) /* First piece. */ - *flagp |= flags & SPSTART; - else - regtail(chain, latest); - chain = latest; - if (first == NULL) - first = latest; - break; - } - } - if (first == NULL) /* Loop ran zero times. */ - first = regnode(NOTHING); - return first; -} - -/* - * Parse something followed by possible [*+=]. - * - * Note that the branching code sequences used for = and the general cases - * of * and + are somewhat optimized: they use the same NOTHING node as - * both the endmarker for their branch list and the body of the last branch. - * It might seem that this node could be dispensed with entirely, but the - * endmarker role is not redundant. - */ -static char_u *regpiece(int *flagp) -{ - char_u *ret; - int op; - char_u *next; - int flags; - long minval; - long maxval; - - ret = regatom(&flags); - if (ret == NULL) - return NULL; - - op = peekchr(); - if (re_multi_type(op) == NOT_MULTI) { - *flagp = flags; - return ret; - } - /* default flags */ - *flagp = (WORST | SPSTART | (flags & (HASNL | HASLOOKBH))); - - skipchr(); - switch (op) { - case Magic('*'): - if (flags & SIMPLE) - reginsert(STAR, ret); - else { - /* Emit x* as (x&|), where & means "self". */ - reginsert(BRANCH, ret); /* Either x */ - regoptail(ret, regnode(BACK)); /* and loop */ - regoptail(ret, ret); /* back */ - regtail(ret, regnode(BRANCH)); /* or */ - regtail(ret, regnode(NOTHING)); /* null. */ - } - break; - - case Magic('+'): - if (flags & SIMPLE) - reginsert(PLUS, ret); - else { - /* Emit x+ as x(&|), where & means "self". */ - next = regnode(BRANCH); /* Either */ - regtail(ret, next); - regtail(regnode(BACK), ret); /* loop back */ - regtail(next, regnode(BRANCH)); /* or */ - regtail(ret, regnode(NOTHING)); /* null. */ - } - *flagp = (WORST | HASWIDTH | (flags & (HASNL | HASLOOKBH))); - break; - - case Magic('@'): - { - int lop = END; - int64_t nr = getdecchrs(); - - switch (no_Magic(getchr())) { - case '=': lop = MATCH; break; /* \@= */ - case '!': lop = NOMATCH; break; /* \@! */ - case '>': lop = SUBPAT; break; /* \@> */ - case '<': switch (no_Magic(getchr())) { - case '=': lop = BEHIND; break; /* \@<= */ - case '!': lop = NOBEHIND; break; /* \@<! */ - } - } - if (lop == END) - EMSG2_RET_NULL(_("E59: invalid character after %s@"), - reg_magic == MAGIC_ALL); - /* Look behind must match with behind_pos. */ - if (lop == BEHIND || lop == NOBEHIND) { - regtail(ret, regnode(BHPOS)); - *flagp |= HASLOOKBH; - } - regtail(ret, regnode(END)); /* operand ends */ - if (lop == BEHIND || lop == NOBEHIND) { - if (nr < 0) - nr = 0; /* no limit is same as zero limit */ - reginsert_nr(lop, (uint32_t)nr, ret); - } else - reginsert(lop, ret); - break; - } - - case Magic('?'): - case Magic('='): - /* Emit x= as (x|) */ - reginsert(BRANCH, ret); /* Either x */ - regtail(ret, regnode(BRANCH)); /* or */ - next = regnode(NOTHING); /* null. */ - regtail(ret, next); - regoptail(ret, next); - break; - - case Magic('{'): - if (!read_limits(&minval, &maxval)) - return NULL; - if (flags & SIMPLE) { - reginsert(BRACE_SIMPLE, ret); - reginsert_limits(BRACE_LIMITS, minval, maxval, ret); - } else { - if (num_complex_braces >= 10) - EMSG2_RET_NULL(_("E60: Too many complex %s{...}s"), - reg_magic == MAGIC_ALL); - reginsert(BRACE_COMPLEX + num_complex_braces, ret); - regoptail(ret, regnode(BACK)); - regoptail(ret, ret); - reginsert_limits(BRACE_LIMITS, minval, maxval, ret); - ++num_complex_braces; - } - if (minval > 0 && maxval > 0) - *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH))); - break; - } - if (re_multi_type(peekchr()) != NOT_MULTI) { - /* Can't have a multi follow a multi. */ - if (peekchr() == Magic('*')) - sprintf((char *)IObuff, _("E61: Nested %s*"), - reg_magic >= MAGIC_ON ? "" : "\\"); - else - sprintf((char *)IObuff, _("E62: Nested %s%c"), - reg_magic == MAGIC_ALL ? "" : "\\", no_Magic(peekchr())); - EMSG_RET_NULL((char *)IObuff); - } - - return ret; -} - -/* When making changes to classchars also change nfa_classcodes. */ -static char_u *classchars = (char_u *)".iIkKfFpPsSdDxXoOwWhHaAlLuU"; -static int classcodes[] = { - ANY, IDENT, SIDENT, KWORD, SKWORD, - FNAME, SFNAME, PRINT, SPRINT, - WHITE, NWHITE, DIGIT, NDIGIT, - HEX, NHEX, OCTAL, NOCTAL, - WORD, NWORD, HEAD, NHEAD, - ALPHA, NALPHA, LOWER, NLOWER, - UPPER, NUPPER -}; - -/* - * Parse the lowest level. - * - * Optimization: gobbles an entire sequence of ordinary characters so that - * it can turn them into a single node, which is smaller to store and - * faster to run. Don't do this when one_exactly is set. - */ -static char_u *regatom(int *flagp) -{ - char_u *ret; - int flags; - int c; - char_u *p; - int extra = 0; - int save_prev_at_start = prev_at_start; - - *flagp = WORST; /* Tentatively. */ - - c = getchr(); - switch (c) { - case Magic('^'): - ret = regnode(BOL); - break; - - case Magic('$'): - ret = regnode(EOL); - had_eol = true; - break; - - case Magic('<'): - ret = regnode(BOW); - break; - - case Magic('>'): - ret = regnode(EOW); - break; - - case Magic('_'): - c = no_Magic(getchr()); - if (c == '^') { /* "\_^" is start-of-line */ - ret = regnode(BOL); - break; - } - if (c == '$') { /* "\_$" is end-of-line */ - ret = regnode(EOL); - had_eol = true; - break; - } - - extra = ADD_NL; - *flagp |= HASNL; - - /* "\_[" is character range plus newline */ - if (c == '[') - goto collection; - - // "\_x" is character class plus newline - FALLTHROUGH; - - /* - * Character classes. - */ - case Magic('.'): - case Magic('i'): - case Magic('I'): - case Magic('k'): - case Magic('K'): - case Magic('f'): - case Magic('F'): - case Magic('p'): - case Magic('P'): - case Magic('s'): - case Magic('S'): - case Magic('d'): - case Magic('D'): - case Magic('x'): - case Magic('X'): - case Magic('o'): - case Magic('O'): - case Magic('w'): - case Magic('W'): - case Magic('h'): - case Magic('H'): - case Magic('a'): - case Magic('A'): - case Magic('l'): - case Magic('L'): - case Magic('u'): - case Magic('U'): - p = vim_strchr(classchars, no_Magic(c)); - if (p == NULL) - EMSG_RET_NULL(_("E63: invalid use of \\_")); - /* When '.' is followed by a composing char ignore the dot, so that - * the composing char is matched here. */ - if (c == Magic('.') && utf_iscomposing(peekchr())) { - c = getchr(); - goto do_multibyte; - } - ret = regnode(classcodes[p - classchars] + extra); - *flagp |= HASWIDTH | SIMPLE; - break; - - case Magic('n'): - if (reg_string) { - /* In a string "\n" matches a newline character. */ - ret = regnode(EXACTLY); - regc(NL); - regc(NUL); - *flagp |= HASWIDTH | SIMPLE; - } else { - /* In buffer text "\n" matches the end of a line. */ - ret = regnode(NEWL); - *flagp |= HASWIDTH | HASNL; - } - break; - - case Magic('('): - if (one_exactly) - EMSG_ONE_RET_NULL; - ret = reg(REG_PAREN, &flags); - if (ret == NULL) - return NULL; - *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH); - break; - - case NUL: - case Magic('|'): - case Magic('&'): - case Magic(')'): - if (one_exactly) - EMSG_ONE_RET_NULL; - IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier. - // NOTREACHED - - case Magic('='): - case Magic('?'): - case Magic('+'): - case Magic('@'): - case Magic('{'): - case Magic('*'): - c = no_Magic(c); - sprintf((char *)IObuff, _("E64: %s%c follows nothing"), - (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL) - ? "" : "\\", c); - EMSG_RET_NULL((char *)IObuff); - /* NOTREACHED */ - - case Magic('~'): /* previous substitute pattern */ - if (reg_prev_sub != NULL) { - char_u *lp; - - ret = regnode(EXACTLY); - lp = reg_prev_sub; - while (*lp != NUL) - regc(*lp++); - regc(NUL); - if (*reg_prev_sub != NUL) { - *flagp |= HASWIDTH; - if ((lp - reg_prev_sub) == 1) - *flagp |= SIMPLE; - } - } else - EMSG_RET_NULL(_(e_nopresub)); - break; - - case Magic('1'): - case Magic('2'): - case Magic('3'): - case Magic('4'): - case Magic('5'): - case Magic('6'): - case Magic('7'): - case Magic('8'): - case Magic('9'): - { - int refnum; - - refnum = c - Magic('0'); - if (!seen_endbrace(refnum)) { - return NULL; - } - ret = regnode(BACKREF + refnum); - } - break; - - case Magic('z'): - { - c = no_Magic(getchr()); - switch (c) { - case '(': if ((reg_do_extmatch & REX_SET) == 0) - EMSG_RET_NULL(_(e_z_not_allowed)); - if (one_exactly) - EMSG_ONE_RET_NULL; - ret = reg(REG_ZPAREN, &flags); - if (ret == NULL) - return NULL; - *flagp |= flags & (HASWIDTH|SPSTART|HASNL|HASLOOKBH); - re_has_z = REX_SET; - break; - - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': if ((reg_do_extmatch & REX_USE) == 0) - EMSG_RET_NULL(_(e_z1_not_allowed)); - ret = regnode(ZREF + c - '0'); - re_has_z = REX_USE; - break; - - case 's': ret = regnode(MOPEN + 0); - if (!re_mult_next("\\zs")) { - return NULL; - } - break; - - case 'e': ret = regnode(MCLOSE + 0); - if (!re_mult_next("\\ze")) { - return NULL; - } - break; - - default: EMSG_RET_NULL(_("E68: Invalid character after \\z")); - } - } - break; - - case Magic('%'): - { - c = no_Magic(getchr()); - switch (c) { - /* () without a back reference */ - case '(': - if (one_exactly) - EMSG_ONE_RET_NULL; - ret = reg(REG_NPAREN, &flags); - if (ret == NULL) - return NULL; - *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH); - break; - - /* Catch \%^ and \%$ regardless of where they appear in the - * pattern -- regardless of whether or not it makes sense. */ - case '^': - ret = regnode(RE_BOF); - break; - - case '$': - ret = regnode(RE_EOF); - break; - - case '#': - ret = regnode(CURSOR); - break; - - case 'V': - ret = regnode(RE_VISUAL); - break; - - case 'C': - ret = regnode(RE_COMPOSING); - break; - - /* \%[abc]: Emit as a list of branches, all ending at the last - * branch which matches nothing. */ - case '[': - if (one_exactly) /* doesn't nest */ - EMSG_ONE_RET_NULL; - { - char_u *lastbranch; - char_u *lastnode = NULL; - char_u *br; - - ret = NULL; - while ((c = getchr()) != ']') { - if (c == NUL) - EMSG2_RET_NULL(_(e_missing_sb), - reg_magic == MAGIC_ALL); - br = regnode(BRANCH); - if (ret == NULL) { - ret = br; - } else { - regtail(lastnode, br); - if (reg_toolong) { - return NULL; - } - } - - ungetchr(); - one_exactly = true; - lastnode = regatom(flagp); - one_exactly = false; - if (lastnode == NULL) { - return NULL; - } - } - if (ret == NULL) - EMSG2_RET_NULL(_(e_empty_sb), - reg_magic == MAGIC_ALL); - lastbranch = regnode(BRANCH); - br = regnode(NOTHING); - if (ret != JUST_CALC_SIZE) { - regtail(lastnode, br); - regtail(lastbranch, br); - /* connect all branches to the NOTHING - * branch at the end */ - for (br = ret; br != lastnode; ) { - if (OP(br) == BRANCH) { - regtail(br, lastbranch); - if (reg_toolong) { - return NULL; - } - br = OPERAND(br); - } else - br = regnext(br); - } - } - *flagp &= ~(HASWIDTH | SIMPLE); - break; - } - - case 'd': /* %d123 decimal */ - case 'o': /* %o123 octal */ - case 'x': /* %xab hex 2 */ - case 'u': /* %uabcd hex 4 */ - case 'U': /* %U1234abcd hex 8 */ - { - int64_t i; - - switch (c) { - case 'd': i = getdecchrs(); break; - case 'o': i = getoctchrs(); break; - case 'x': i = gethexchrs(2); break; - case 'u': i = gethexchrs(4); break; - case 'U': i = gethexchrs(8); break; - default: i = -1; break; - } - - if (i < 0 || i > INT_MAX) { - EMSG2_RET_NULL(_("E678: Invalid character after %s%%[dxouU]"), - reg_magic == MAGIC_ALL); - } - if (use_multibytecode(i)) { - ret = regnode(MULTIBYTECODE); - } else { - ret = regnode(EXACTLY); - } - if (i == 0) { - regc(0x0a); - } else { - regmbc(i); - } - regc(NUL); - *flagp |= HASWIDTH; - break; - } - - default: - if (ascii_isdigit(c) || c == '<' || c == '>' - || c == '\'') { - uint32_t n = 0; - int cmp; - - cmp = c; - if (cmp == '<' || cmp == '>') - c = getchr(); - while (ascii_isdigit(c)) { - n = n * 10 + (uint32_t)(c - '0'); - c = getchr(); - } - if (c == '\'' && n == 0) { - /* "\%'m", "\%<'m" and "\%>'m": Mark */ - c = getchr(); - ret = regnode(RE_MARK); - if (ret == JUST_CALC_SIZE) - regsize += 2; - else { - *regcode++ = c; - *regcode++ = cmp; - } - break; - } else if (c == 'l' || c == 'c' || c == 'v') { - if (c == 'l') { - ret = regnode(RE_LNUM); - if (save_prev_at_start) { - at_start = true; - } - } else if (c == 'c') { - ret = regnode(RE_COL); - } else { - ret = regnode(RE_VCOL); - } - if (ret == JUST_CALC_SIZE) { - regsize += 5; - } else { - // put the number and the optional - // comparator after the opcode - regcode = re_put_uint32(regcode, n); - *regcode++ = cmp; - } - break; - } - } - - EMSG2_RET_NULL(_("E71: Invalid character after %s%%"), - reg_magic == MAGIC_ALL); - } - } - break; - - case Magic('['): -collection: - { - char_u *lp; - - /* - * If there is no matching ']', we assume the '[' is a normal - * character. This makes 'incsearch' and ":help [" work. - */ - lp = skip_anyof(regparse); - if (*lp == ']') { /* there is a matching ']' */ - int startc = -1; /* > 0 when next '-' is a range */ - int endc; - - /* - * In a character class, different parsing rules apply. - * Not even \ is special anymore, nothing is. - */ - if (*regparse == '^') { /* Complement of range. */ - ret = regnode(ANYBUT + extra); - regparse++; - } else - ret = regnode(ANYOF + extra); - - /* At the start ']' and '-' mean the literal character. */ - if (*regparse == ']' || *regparse == '-') { - startc = *regparse; - regc(*regparse++); - } - - while (*regparse != NUL && *regparse != ']') { - if (*regparse == '-') { - ++regparse; - /* The '-' is not used for a range at the end and - * after or before a '\n'. */ - if (*regparse == ']' || *regparse == NUL - || startc == -1 - || (regparse[0] == '\\' && regparse[1] == 'n')) { - regc('-'); - startc = '-'; /* [--x] is a range */ - } else { - /* Also accept "a-[.z.]" */ - endc = 0; - if (*regparse == '[') - endc = get_coll_element(®parse); - if (endc == 0) { - endc = mb_ptr2char_adv((const char_u **)®parse); - } - - /* Handle \o40, \x20 and \u20AC style sequences */ - if (endc == '\\' && !reg_cpo_lit) - endc = coll_get_char(); - - if (startc > endc) { - EMSG_RET_NULL(_(e_reverse_range)); - } - if (utf_char2len(startc) > 1 - || utf_char2len(endc) > 1) { - // Limit to a range of 256 chars - if (endc > startc + 256) { - EMSG_RET_NULL(_(e_large_class)); - } - while (++startc <= endc) { - regmbc(startc); - } - } else { - while (++startc <= endc) - regc(startc); - } - startc = -1; - } - } - /* - * Only "\]", "\^", "\]" and "\\" are special in Vi. Vim - * accepts "\t", "\e", etc., but only when the 'l' flag in - * 'cpoptions' is not included. - */ - else if (*regparse == '\\' - && (vim_strchr(REGEXP_INRANGE, regparse[1]) != NULL - || (!reg_cpo_lit - && vim_strchr(REGEXP_ABBR, - regparse[1]) != NULL))) { - regparse++; - if (*regparse == 'n') { - /* '\n' in range: also match NL */ - if (ret != JUST_CALC_SIZE) { - /* Using \n inside [^] does not change what - * matches. "[^\n]" is the same as ".". */ - if (*ret == ANYOF) { - *ret = ANYOF + ADD_NL; - *flagp |= HASNL; - } - /* else: must have had a \n already */ - } - regparse++; - startc = -1; - } else if (*regparse == 'd' - || *regparse == 'o' - || *regparse == 'x' - || *regparse == 'u' - || *regparse == 'U') { - startc = coll_get_char(); - if (startc == 0) - regc(0x0a); - else - regmbc(startc); - } else { - startc = backslash_trans(*regparse++); - regc(startc); - } - } else if (*regparse == '[') { - int c_class; - int cu; - - c_class = get_char_class(®parse); - startc = -1; - /* Characters assumed to be 8 bits! */ - switch (c_class) { - case CLASS_NONE: - c_class = get_equi_class(®parse); - if (c_class != 0) { - /* produce equivalence class */ - reg_equi_class(c_class); - } else if ((c_class = - get_coll_element(®parse)) != 0) { - /* produce a collating element */ - regmbc(c_class); - } else { - /* literal '[', allow [[-x] as a range */ - startc = *regparse++; - regc(startc); - } - break; - case CLASS_ALNUM: - for (cu = 1; cu < 128; cu++) { - if (isalnum(cu)) { - regmbc(cu); - } - } - break; - case CLASS_ALPHA: - for (cu = 1; cu < 128; cu++) { - if (isalpha(cu)) { - regmbc(cu); - } - } - break; - case CLASS_BLANK: - regc(' '); - regc('\t'); - break; - case CLASS_CNTRL: - for (cu = 1; cu <= 127; cu++) { - if (iscntrl(cu)) { - regmbc(cu); - } - } - break; - case CLASS_DIGIT: - for (cu = 1; cu <= 127; cu++) { - if (ascii_isdigit(cu)) { - regmbc(cu); - } - } - break; - case CLASS_GRAPH: - for (cu = 1; cu <= 127; cu++) { - if (isgraph(cu)) { - regmbc(cu); - } - } - break; - case CLASS_LOWER: - for (cu = 1; cu <= 255; cu++) { - if (mb_islower(cu) && cu != 170 && cu != 186) { - regmbc(cu); - } - } - break; - case CLASS_PRINT: - for (cu = 1; cu <= 255; cu++) { - if (vim_isprintc(cu)) { - regmbc(cu); - } - } - break; - case CLASS_PUNCT: - for (cu = 1; cu < 128; cu++) { - if (ispunct(cu)) { - regmbc(cu); - } - } - break; - case CLASS_SPACE: - for (cu = 9; cu <= 13; cu++) - regc(cu); - regc(' '); - break; - case CLASS_UPPER: - for (cu = 1; cu <= 255; cu++) { - if (mb_isupper(cu)) { - regmbc(cu); - } - } - break; - case CLASS_XDIGIT: - for (cu = 1; cu <= 255; cu++) { - if (ascii_isxdigit(cu)) { - regmbc(cu); - } - } - break; - case CLASS_TAB: - regc('\t'); - break; - case CLASS_RETURN: - regc('\r'); - break; - case CLASS_BACKSPACE: - regc('\b'); - break; - 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 - // following composing characters. - startc = utf_ptr2char(regparse); - int len = utfc_ptr2len(regparse); - if (utf_char2len(startc) != len) { - // composing chars - startc = -1; - } - while (--len >= 0) { - regc(*regparse++); - } - } - } - regc(NUL); - prevchr_len = 1; /* last char was the ']' */ - if (*regparse != ']') - EMSG_RET_NULL(_(e_toomsbra)); /* Cannot happen? */ - skipchr(); /* let's be friends with the lexer again */ - *flagp |= HASWIDTH | SIMPLE; - break; - } else if (reg_strict) - EMSG2_RET_NULL(_(e_missingbracket), reg_magic > MAGIC_OFF); - } - FALLTHROUGH; - - default: - { - int len; - - /* A multi-byte character is handled as a separate atom if it's - * before a multi and when it's a composing char. */ - if (use_multibytecode(c)) { -do_multibyte: - ret = regnode(MULTIBYTECODE); - regmbc(c); - *flagp |= HASWIDTH | SIMPLE; - break; - } - - ret = regnode(EXACTLY); - - /* - * Append characters as long as: - * - there is no following multi, we then need the character in - * front of it as a single character operand - * - not running into a Magic character - * - "one_exactly" is not set - * But always emit at least one character. Might be a Multi, - * e.g., a "[" without matching "]". - */ - for (len = 0; c != NUL && (len == 0 - || (re_multi_type(peekchr()) == NOT_MULTI - && !one_exactly - && !is_Magic(c))); ++len) { - c = no_Magic(c); - { - regmbc(c); - { - int l; - - /* Need to get composing character too. */ - for (;; ) { - l = utf_ptr2len(regparse); - if (!utf_composinglike(regparse, regparse + l)) { - break; - } - regmbc(utf_ptr2char(regparse)); - skipchr(); - } - } - } - c = getchr(); - } - ungetchr(); - - regc(NUL); - *flagp |= HASWIDTH; - if (len == 1) - *flagp |= SIMPLE; - } - break; - } - - return ret; -} - -/// Used in a place where no * or \+ can follow. -static bool re_mult_next(char *what) -{ - if (re_multi_type(peekchr()) == MULTI_MULT) { - EMSG2_RET_FAIL(_("E888: (NFA regexp) cannot repeat %s"), what); - } - return true; -} - -// Return true if MULTIBYTECODE should be used instead of EXACTLY for -// character "c". -static bool use_multibytecode(int c) -{ - return utf_char2len(c) > 1 - && (re_multi_type(peekchr()) != NOT_MULTI - || utf_iscomposing(c)); -} - -/* - * Emit a node. - * Return pointer to generated code. - */ -static char_u *regnode(int op) -{ - char_u *ret; - - ret = regcode; - if (ret == JUST_CALC_SIZE) - regsize += 3; - else { - *regcode++ = op; - *regcode++ = NUL; /* Null "next" pointer. */ - *regcode++ = NUL; - } - return ret; -} - -/* - * Emit (if appropriate) a byte of code - */ -static void regc(int b) -{ - if (regcode == JUST_CALC_SIZE) - regsize++; - else - *regcode++ = b; -} - -/* - * Emit (if appropriate) a multi-byte character of code - */ -static void regmbc(int c) -{ - if (regcode == JUST_CALC_SIZE) { - regsize += utf_char2len(c); - } else { - regcode += utf_char2bytes(c, regcode); - } -} - -/* - * Insert an operator in front of already-emitted operand - * - * Means relocating the operand. - */ -static void reginsert(int op, char_u *opnd) -{ - char_u *src; - char_u *dst; - char_u *place; - - if (regcode == JUST_CALC_SIZE) { - regsize += 3; - return; - } - src = regcode; - regcode += 3; - dst = regcode; - while (src > opnd) - *--dst = *--src; - - place = opnd; /* Op node, where operand used to be. */ - *place++ = op; - *place++ = NUL; - *place = NUL; -} - -/* - * Insert an operator in front of already-emitted operand. - * Add a number to the operator. - */ -static void reginsert_nr(int op, long val, char_u *opnd) -{ - char_u *src; - char_u *dst; - char_u *place; - - if (regcode == JUST_CALC_SIZE) { - regsize += 7; - return; - } - src = regcode; - regcode += 7; - dst = regcode; - while (src > opnd) - *--dst = *--src; - - place = opnd; /* Op node, where operand used to be. */ - *place++ = op; - *place++ = NUL; - *place++ = NUL; - assert(val >= 0 && (uintmax_t)val <= UINT32_MAX); - re_put_uint32(place, (uint32_t)val); -} - -/* - * Insert an operator in front of already-emitted operand. - * The operator has the given limit values as operands. Also set next pointer. - * - * Means relocating the operand. - */ -static void reginsert_limits(int op, long minval, long maxval, char_u *opnd) -{ - char_u *src; - char_u *dst; - char_u *place; - - if (regcode == JUST_CALC_SIZE) { - regsize += 11; - return; - } - src = regcode; - regcode += 11; - dst = regcode; - while (src > opnd) - *--dst = *--src; - - place = opnd; /* Op node, where operand used to be. */ - *place++ = op; - *place++ = NUL; - *place++ = NUL; - assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX); - place = re_put_uint32(place, (uint32_t)minval); - assert(maxval >= 0 && (uintmax_t)maxval <= UINT32_MAX); - place = re_put_uint32(place, (uint32_t)maxval); - regtail(opnd, place); -} - -/* - * Write a four bytes number at "p" and return pointer to the next char. - */ -static char_u *re_put_uint32(char_u *p, uint32_t val) -{ - *p++ = (char_u) ((val >> 24) & 0377); - *p++ = (char_u) ((val >> 16) & 0377); - *p++ = (char_u) ((val >> 8) & 0377); - *p++ = (char_u) (val & 0377); - return p; -} - -// Set the next-pointer at the end of a node chain. -static void regtail(char_u *p, char_u *val) -{ - int offset; - - if (p == JUST_CALC_SIZE) { - return; - } - - // Find last node. - char_u *scan = p; - for (;; ) { - char_u *temp = regnext(scan); - if (temp == NULL) { - break; - } - scan = temp; - } - - if (OP(scan) == BACK) { - offset = (int)(scan - val); - } 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); - } -} - -/* - * Like regtail, on item after a BRANCH; nop if none. - */ -static void regoptail(char_u *p, char_u *val) -{ - /* When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless" */ - if (p == NULL || p == JUST_CALC_SIZE - || (OP(p) != BRANCH - && (OP(p) < BRACE_COMPLEX || OP(p) > BRACE_COMPLEX + 9))) - return; - regtail(OPERAND(p), val); -} - -/* - * Functions for getting characters from the regexp input. - */ - -/* * Start parsing at "str". */ static void initchr(char_u *str) @@ -2805,9 +593,10 @@ static int peekchr(void) case '.': case '[': case '~': - /* magic when 'magic' is on */ - if (reg_magic >= MAGIC_ON) + // magic when 'magic' is on + if (reg_magic >= MAGIC_ON) { curchr = Magic(curchr); + } break; case '(': case ')': @@ -2822,18 +611,19 @@ static int peekchr(void) case '|': case '<': case '>': - case '#': /* future ext. */ - case '"': /* future ext. */ - case '\'': /* future ext. */ - case ',': /* future ext. */ - case '-': /* future ext. */ - case ':': /* future ext. */ - case ';': /* future ext. */ - case '`': /* future ext. */ - case '/': /* Can't be used in / command */ - /* magic only after "\v" */ - if (reg_magic == MAGIC_ALL) + case '#': // future ext. + case '"': // future ext. + case '\'': // future ext. + case ',': // future ext. + case '-': // future ext. + case ':': // future ext. + case ';': // future ext. + case '`': // future ext. + case '/': // Can't be used in / command + // magic only after "\v" + if (reg_magic == MAGIC_ALL) { curchr = Magic(curchr); + } break; case '*': // * is not magic as the very first character, eg "?*ptr", when @@ -2900,18 +690,14 @@ static int peekchr(void) { int c = regparse[1]; - if (c == NUL) - curchr = '\\'; /* trailing '\' */ - else if ( - c <= '~' && META_flags[c] - ) { - /* - * META contains everything that may be magic sometimes, - * except ^ and $ ("\^" and "\$" are only magic after - * "\V"). We now fetch the next character and toggle its - * magicness. Therefore, \ is so meta-magic that it is - * not in META. - */ + if (c == NUL) { + curchr = '\\'; // trailing '\' + } else if (c <= '~' && META_flags[c]) { + // META contains everything that may be magic sometimes, + // except ^ and $ ("\^" and "\$" are only magic after + // "\V"). We now fetch the next character and toggle its + // magicness. Therefore, \ is so meta-magic that it is + // not in META. curchr = -1; prev_at_start = at_start; at_start = false; // be able to say "/\*ptr" @@ -2950,11 +736,12 @@ static int peekchr(void) */ static void skipchr(void) { - /* peekchr() eats a backslash, do the same here */ - if (*regparse == '\\') + // peekchr() eats a backslash, do the same here + if (*regparse == '\\') { prevchr_len = 1; - else + } else { prevchr_len = 0; + } if (regparse[prevchr_len] != NUL) { // Exclude composing chars that utfc_ptr2len does include. prevchr_len += utf_ptr2len(regparse + prevchr_len); @@ -2964,7 +751,7 @@ static void skipchr(void) at_start = false; prevprevchr = prevchr; prevchr = curchr; - curchr = nextchr; /* use previously unget char, or -1 */ + curchr = nextchr; // use previously unget char, or -1 nextchr = -1; } @@ -3057,8 +844,8 @@ static int64_t getdecchrs(void) break; nr *= 10; nr += c - '0'; - ++regparse; - curchr = -1; /* no longer valid */ + regparse++; + curchr = -1; // no longer valid } if (i == 0) @@ -3094,29 +881,6 @@ static int64_t getoctchrs(void) return nr; } -/* - * Get a number after a backslash that is inside []. - * When nothing is recognized return a backslash. - */ -static int coll_get_char(void) -{ - int64_t nr = -1; - - switch (*regparse++) { - case 'd': nr = getdecchrs(); break; - case 'o': nr = getoctchrs(); break; - case 'x': nr = gethexchrs(2); break; - case 'u': nr = gethexchrs(4); break; - case 'U': nr = gethexchrs(8); break; - } - if (nr < 0 || nr > INT_MAX) { - // If getting the number fails be backwards compatible: the character - // is a backslash. - regparse--; - nr = '\\'; - } - return nr; -} /* * read_limits - Read two integers to be taken as a minimum and maximum. @@ -3152,9 +916,7 @@ static int read_limits(long *minval, long *maxval) regparse++; // Allow either \{...} or \{...\} } if (*regparse != '}') { - sprintf((char *)IObuff, _("E554: Syntax error in %s{...}"), - reg_magic == MAGIC_ALL ? "" : "\\"); - EMSG_RET_FAIL((char *)IObuff); + EMSG2_RET_FAIL(_("E554: Syntax error in %s{...}"), reg_magic == MAGIC_ALL); } /* @@ -3166,7 +928,7 @@ static int read_limits(long *minval, long *maxval) *minval = *maxval; *maxval = tmp; } - skipchr(); /* let's be friends with the lexer again */ + skipchr(); // let's be friends with the lexer again return OK; } @@ -3178,22 +940,6 @@ static int read_limits(long *minval, long *maxval) * Global work variables for vim_regexec(). */ -/* 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)) - -/* After a failed match restore the sub-expressions. */ -#define restore_se(savep, posp, pp) { \ - if (REG_MULTI) \ - *(posp) = (savep)->se_u.pos; \ - else \ - *(pp) = (savep)->se_u.ptr; } - - -#ifdef REGEXP_DEBUG -int regnarrate = 0; -#endif - // Sometimes need to save a copy of a line. Since alloc()/free() is very // slow, we keep one allocated piece of memory and only re-allocate it when // it's too small. It's freed in bt_regexec_both() when finished. @@ -3231,7 +977,7 @@ typedef struct { // 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" + char_u *input; ///< current input, points into "line" int need_clear_subexpr; ///< subexpressions still need to be cleared int need_clear_zsubexpr; ///< extmatch subexpressions still need to be @@ -3269,41 +1015,6 @@ typedef struct { static regexec_T rex; static bool rex_in_use = false; -/* - * "regstack" and "backpos" are used by regmatch(). They are kept over calls - * to avoid invoking malloc() and free() often. - * "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T - * or regbehind_T. - * "backpos_T" is a table with backpos_T for BACK - */ -static garray_T regstack = GA_EMPTY_INIT_VALUE; -static garray_T backpos = GA_EMPTY_INIT_VALUE; - -/* - * Both for regstack and backpos tables we use the following strategy of - * allocation (to reduce malloc/free calls): - * - Initial size is fairly small. - * - When needed, the tables are grown bigger (8 times at first, double after - * that). - * - After executing the match we free the memory only if the array has grown. - * Thus the memory is kept allocated when it's at the initial size. - * This makes it fast while not keeping a lot of memory allocated. - * A three times speed increase was observed when using many simple patterns. - */ -#define REGSTACK_INITIAL 2048 -#define BACKPOS_INITIAL 64 - -#if defined(EXITFREE) -void free_regexp_stuff(void) -{ - ga_clear(®stack); - ga_clear(&backpos); - xfree(reg_tofree); - xfree(reg_prev_sub); -} - -#endif - // Return true if character 'c' is included in 'iskeyword' option for // "reg_buf" buffer. static bool reg_iswordc(int c) @@ -3328,312 +1039,15 @@ static char_u *reg_getline(linenr_T lnum) return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum, false); } -static regsave_T behind_pos; - -static char_u *reg_startzp[NSUBEXP]; /* Workspace to mark beginning */ -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 */ +static char_u *reg_startzp[NSUBEXP]; // Workspace to mark beginning +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. #define REG_MULTI (rex.reg_match == NULL) /* - * Match a regexp against a string. - * "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). - * Uses curbuf for line count and 'iskeyword'. - * If "line_lbr" is true, consider a "\n" in "line" to be a line break. - * - * Returns 0 for failure, number of lines contained in the match otherwise. - */ -static int -bt_regexec_nl ( - regmatch_T *rmp, - char_u *line, /* string to match against */ - colnr_T col, /* column to start looking for match */ - bool line_lbr -) -{ - rex.reg_match = rmp; - rex.reg_mmatch = NULL; - rex.reg_maxline = 0; - rex.reg_line_lbr = line_lbr; - rex.reg_buf = curbuf; - rex.reg_win = NULL; - rex.reg_ic = rmp->rm_ic; - rex.reg_icombine = false; - rex.reg_maxcol = 0; - - long r = bt_regexec_both(line, col, NULL, NULL); - assert(r <= INT_MAX); - return (int)r; -} - -/// Wrapper around strchr which accounts for case-insensitive searches and -/// non-ASCII characters. -/// -/// This function is used a lot for simple searches, keep it fast! -/// -/// @param s string to search -/// @param c character to find in @a s -/// -/// @return NULL if no match, otherwise pointer to the position in @a s -static inline char_u *cstrchr(const char_u *const s, const int c) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL - FUNC_ATTR_ALWAYS_INLINE -{ - if (!rex.reg_ic) { - return vim_strchr(s, c); - } - - // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is - // expected to be highly optimized. - if (c > 0x80) { - const int folded_c = utf_fold(c); - for (const char_u *p = s; *p != NUL; p += utfc_ptr2len(p)) { - if (utf_fold(utf_ptr2char(p)) == folded_c) { - return (char_u *)p; - } - } - return NULL; - } - - int cc; - if (ASCII_ISUPPER(c)) { - cc = TOLOWER_ASC(c); - } else if (ASCII_ISLOWER(c)) { - cc = TOUPPER_ASC(c); - } else { - return vim_strchr(s, c); - } - - char tofind[] = { (char)c, (char)cc, NUL }; - return (char_u *)strpbrk((const char *)s, tofind); -} - -/// Matches a regexp against multiple lines. -/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). -/// Uses curbuf for line count and 'iskeyword'. -/// -/// @param win Window in which to search or NULL -/// @param buf Buffer in which to search -/// @param lnum Number of line to start looking for match -/// @param col Column to start looking for match -/// @param tm Timeout limit or NULL -/// -/// @return zero if there is no match and number of lines contained in the match -/// otherwise. -static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, - linenr_T lnum, colnr_T col, - proftime_T *tm, int *timed_out) -{ - rex.reg_match = NULL; - rex.reg_mmatch = rmp; - rex.reg_buf = buf; - rex.reg_win = win; - rex.reg_firstlnum = lnum; - rex.reg_maxline = rex.reg_buf->b_ml.ml_line_count - lnum; - rex.reg_line_lbr = false; - rex.reg_ic = rmp->rmm_ic; - rex.reg_icombine = false; - rex.reg_maxcol = rmp->rmm_maxcol; - - return bt_regexec_both(NULL, col, tm, timed_out); -} - -/// Match a regexp against a string ("line" points to the string) or multiple -/// lines (if "line" is NULL, use reg_getline()). -/// @return 0 for failure, or number of lines contained in the match. -static long bt_regexec_both(char_u *line, - colnr_T col, // column to start search - proftime_T *tm, // timeout limit or NULL - int *timed_out) // flag set on timeout or NULL -{ - bt_regprog_T *prog; - char_u *s; - long retval = 0L; - - /* Create "regstack" and "backpos" if they are not allocated yet. - * We allocate *_INITIAL amount of bytes first and then set the grow size - * to much bigger value to avoid many malloc calls in case of deep regular - * expressions. */ - if (regstack.ga_data == NULL) { - /* Use an item size of 1 byte, since we push different things - * onto the regstack. */ - ga_init(®stack, 1, REGSTACK_INITIAL); - ga_grow(®stack, REGSTACK_INITIAL); - ga_set_growsize(®stack, REGSTACK_INITIAL * 8); - } - - if (backpos.ga_data == NULL) { - ga_init(&backpos, sizeof(backpos_T), BACKPOS_INITIAL); - ga_grow(&backpos, BACKPOS_INITIAL); - ga_set_growsize(&backpos, BACKPOS_INITIAL * 8); - } - - if (REG_MULTI) { - prog = (bt_regprog_T *)rex.reg_mmatch->regprog; - line = reg_getline((linenr_T)0); - rex.reg_startpos = rex.reg_mmatch->startpos; - rex.reg_endpos = rex.reg_mmatch->endpos; - } else { - prog = (bt_regprog_T *)rex.reg_match->regprog; - rex.reg_startp = rex.reg_match->startp; - rex.reg_endp = rex.reg_match->endp; - } - - /* Be paranoid... */ - if (prog == NULL || line == NULL) { - iemsg(_(e_null)); - goto theend; - } - - /* Check validity of program. */ - if (prog_magic_wrong()) - goto theend; - - // If the start column is past the maximum column: no need to try. - if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { - goto theend; - } - - // If pattern contains "\c" or "\C": overrule value of rex.reg_ic - if (prog->regflags & RF_ICASE) { - rex.reg_ic = true; - } else if (prog->regflags & RF_NOICASE) { - rex.reg_ic = false; - } - - // If pattern contains "\Z" overrule value of rex.reg_icombine - if (prog->regflags & RF_ICOMBINE) { - rex.reg_icombine = true; - } - - /* If there is a "must appear" string, look for it. */ - if (prog->regmust != NULL) { - int c = utf_ptr2char(prog->regmust); - s = line + col; - - // This is used very often, esp. for ":global". Use two versions of - // the loop to avoid overhead of conditions. - if (!rex.reg_ic) { - while ((s = vim_strchr(s, c)) != NULL) { - if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { - break; // Found it. - } - MB_PTR_ADV(s); - } - } else { - while ((s = cstrchr(s, c)) != NULL) { - if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { - break; // Found it. - } - MB_PTR_ADV(s); - } - } - if (s == NULL) { // Not present. - goto theend; - } - } - - 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(rex.line + col); - if (prog->regstart == NUL - || prog->regstart == c - || (rex.reg_ic - && (utf_fold(prog->regstart) == utf_fold(c) - || (c < 255 && prog->regstart < 255 - && mb_tolower(prog->regstart) == mb_tolower(c))))) { - retval = regtry(prog, col, tm, timed_out); - } else { - retval = 0; - } - } else { - int tm_count = 0; - /* Messy cases: unanchored match. */ - while (!got_int) { - if (prog->regstart != NUL) { - // Skip until the char we know it must start with. - s = cstrchr(rex.line + col, prog->regstart); - if (s == NULL) { - retval = 0; - break; - } - col = (int)(s - rex.line); - } - - // Check for maximum column to try. - if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { - retval = 0; - break; - } - - retval = regtry(prog, col, tm, timed_out); - if (retval > 0) { - break; - } - - // 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 (rex.line[col] == NUL) { - break; - } - col += utfc_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)) { - if (timed_out != NULL) { - *timed_out = true; - } - break; - } - } - } - } - -theend: - /* Free "reg_tofree" when it's a bit big. - * Free regstack and backpos if they are bigger than their initial size. */ - if (reg_tofreelen > 400) { - XFREE_CLEAR(reg_tofree); - } - if (regstack.ga_maxlen > REGSTACK_INITIAL) - ga_clear(®stack); - if (backpos.ga_maxlen > BACKPOS_INITIAL) - ga_clear(&backpos); - - if (retval > 0) { - // Make sure the end is never before the start. Can happen when \zs - // and \ze are used. - if (REG_MULTI) { - const lpos_T *const start = &rex.reg_mmatch->startpos[0]; - const lpos_T *const end = &rex.reg_mmatch->endpos[0]; - - if (end->lnum < start->lnum - || (end->lnum == start->lnum && end->col < start->col)) { - rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0]; - } - } else { - if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) { - rex.reg_match->endp[0] = rex.reg_match->startp[0]; - } - } - } - - return retval; -} - - -/* * Create a new extmatch and mark it as referenced once. */ static reg_extmatch_T *make_extmatch(void) @@ -3669,75 +1083,6 @@ void unref_extmatch(reg_extmatch_T *em) } } -/// 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 -{ - 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; - } - - cleanup_subexpr(); - if (REG_MULTI) { - if (rex.reg_startpos[0].lnum < 0) { - rex.reg_startpos[0].lnum = 0; - rex.reg_startpos[0].col = col; - } - if (rex.reg_endpos[0].lnum < 0) { - rex.reg_endpos[0].lnum = rex.lnum; - rex.reg_endpos[0].col = (int)(rex.input - rex.line); - } else { - // Use line number of "\ze". - rex.lnum = rex.reg_endpos[0].lnum; - } - } else { - if (rex.reg_startp[0] == NULL) { - rex.reg_startp[0] = rex.line + col; - } - if (rex.reg_endp[0] == NULL) { - rex.reg_endp[0] = rex.input; - } - } - /* Package any found \z(...\) matches for export. Default is none. */ - unref_extmatch(re_extmatch_out); - re_extmatch_out = NULL; - - if (prog->reghasz == REX_SET) { - int i; - - cleanup_zsubexpr(); - re_extmatch_out = make_extmatch(); - for (i = 0; i < NSUBEXP; i++) { - if (REG_MULTI) { - /* Only accept single line matches. */ - if (reg_startzpos[i].lnum >= 0 - && reg_endzpos[i].lnum == reg_startzpos[i].lnum - && reg_endzpos[i].col >= reg_startzpos[i].col) { - re_extmatch_out->matches[i] = - vim_strnsave(reg_getline(reg_startzpos[i].lnum) - + reg_startzpos[i].col, - reg_endzpos[i].col - - reg_startzpos[i].col); - } - } else { - if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) - re_extmatch_out->matches[i] = - vim_strnsave(reg_startzp[i], reg_endzp[i] - reg_startzp[i]); - } - } - } - return 1 + rex.lnum; -} - - // Get class of previous character. static int reg_prev_class(void) { @@ -3793,8 +1138,8 @@ static bool reg_match_visual(void) return false; } + col = (colnr_T)(rex.input - rex.line); if (mode == 'v') { - 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; @@ -3809,8 +1154,12 @@ static bool reg_match_visual(void) if (top.col == MAXCOL || bot.col == MAXCOL || curswant == MAXCOL) { end = MAXCOL; } - unsigned int cols_u = win_linetabsize(wp, rex.line, - (colnr_T)(rex.input - rex.line)); + + // getvvcol() flushes rex.line, need to get it again + rex.line = reg_getline(rex.lnum); + rex.input = rex.line + col; + + unsigned int cols_u = win_linetabsize(wp, rex.line, col); assert(cols_u <= MAXCOL); colnr_T cols = (colnr_T)cols_u; if (cols < start || cols > end - (*p_sel == 'e')) { @@ -3820,1803 +1169,6 @@ static bool reg_match_visual(void) return true; } -#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input) - -/* - * The arguments from BRACE_LIMITS are stored here. They are actually local - * to regmatch(), but they are here to reduce the amount of stack space used - * (it can be called recursively many times). - */ -static long bl_minval; -static long bl_maxval; - -/// Main matching routine -/// -/// Conceptually the strategy is simple: Check to see whether the current node -/// matches, push an item onto the regstack and loop to see whether the rest -/// matches, and then act accordingly. In practice we make some effort to -/// avoid using the regstack, in particular by going through "ordinary" nodes -/// (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 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 bool regmatch( - char_u *scan, // Current node. - proftime_T *tm, // timeout limit or NULL - int *timed_out // flag set on timeout or NULL -) -{ - char_u *next; /* Next node. */ - int op; - int c; - regitem_T *rp; - int no; - int status; // one of the RA_ values: - int tm_count = 0; -#define RA_FAIL 1 // something failed, abort -#define RA_CONT 2 // continue in inner loop -#define RA_BREAK 3 // break inner loop -#define RA_MATCH 4 // successful match -#define RA_NOMATCH 5 // didn't match - - // Make "regstack" and "backpos" empty. They are allocated and freed in - // bt_regexec_both() to reduce malloc()/free() calls. - regstack.ga_len = 0; - backpos.ga_len = 0; - - /* - * Repeat until "regstack" is empty. - */ - for (;; ) { - /* Some patterns may take a long time to match, e.g., "\([a-z]\+\)\+Q". - * Allow interrupting them with CTRL-C. */ - fast_breakcheck(); - -#ifdef REGEXP_DEBUG - if (scan != NULL && regnarrate) { - mch_errmsg((char *)regprop(scan)); - mch_errmsg("(\n"); - } -#endif - - /* - * Repeat for items that can be matched sequentially, without using the - * regstack. - */ - for (;; ) { - if (got_int || scan == NULL) { - status = RA_FAIL; - break; - } - // Check for timeout once in a 100 times to avoid overhead. - if (tm != NULL && ++tm_count == 100) { - tm_count = 0; - if (profile_passed_limit(*tm)) { - if (timed_out != NULL) { - *timed_out = true; - } - status = RA_FAIL; - break; - } - } - status = RA_CONT; - -#ifdef REGEXP_DEBUG - if (regnarrate) { - mch_errmsg((char *)regprop(scan)); - mch_errmsg("...\n"); - if (re_extmatch_in != NULL) { - int i; - - mch_errmsg(_("External submatches:\n")); - for (i = 0; i < NSUBEXP; i++) { - mch_errmsg(" \""); - if (re_extmatch_in->matches[i] != NULL) - mch_errmsg((char *)re_extmatch_in->matches[i]); - mch_errmsg("\"\n"); - } - } - } -#endif - next = regnext(scan); - - op = OP(scan); - // Check for character class with NL added. - if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI - && *rex.input == NUL && rex.lnum <= rex.reg_maxline) { - reg_nextline(); - } 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(rex.input); - switch (op) { - case BOL: - if (rex.input != rex.line) { - status = RA_NOMATCH; - } - break; - - case EOL: - 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 (rex.lnum != 0 || rex.input != rex.line - || (REG_MULTI && rex.reg_firstlnum > 1)) { - status = RA_NOMATCH; - } - break; - - case RE_EOF: - if (rex.lnum != rex.reg_maxline || c != NUL) { - status = RA_NOMATCH; - } - break; - - case CURSOR: - // 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 - || (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; - - case RE_MARK: - /* Compare the mark position to the match position. */ - { - int mark = OPERAND(scan)[0]; - int cmp = OPERAND(scan)[1]; - pos_T *pos; - - 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 - status = RA_NOMATCH; - } else { - const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum - && pos->col == MAXCOL - ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum)) - : pos->col; - - if (pos->lnum == rex.lnum + rex.reg_firstlnum - ? (pos_col == (colnr_T)(rex.input - rex.line) - ? (cmp == '<' || cmp == '>') - : (pos_col < (colnr_T)(rex.input - rex.line) - ? cmp != '>' - : cmp != '<')) - : (pos->lnum < rex.lnum + rex.reg_firstlnum - ? cmp != '>' - : cmp != '<')) { - status = RA_NOMATCH; - } - } - } - break; - - case RE_VISUAL: - if (!reg_match_visual()) - status = RA_NOMATCH; - break; - - case RE_LNUM: - assert(rex.lnum + rex.reg_firstlnum >= 0 - && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX); - if (!REG_MULTI - || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) { - status = RA_NOMATCH; - } - break; - - case RE_COL: - 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, - rex.line, - (colnr_T)(rex.input - rex.line)) + 1, - scan)) { - status = RA_NOMATCH; - } - break; - - case BOW: // \<word; rex.input points to w - if (c == NUL) { // Can't match at end of line - status = RA_NOMATCH; - } else { - // Get class of current and previous char (if it exists). - 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. - } - } - break; - - case EOW: // word\>; rex.input points after d - if (rex.input == rex.line) { // Can't match at start of line - status = RA_NOMATCH; - } else { - int this_class, prev_class; - - // Get class of current and previous char (if it exists). - 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; - } - } - break; // Matched with EOW - - case ANY: - // ANY does not match new lines. - if (c == NUL) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case IDENT: - if (!vim_isIDc(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case SIDENT: - if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case KWORD: - if (!vim_iswordp_buf(rex.input, rex.reg_buf)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case SKWORD: - if (ascii_isdigit(*rex.input) - || !vim_iswordp_buf(rex.input, rex.reg_buf)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case FNAME: - if (!vim_isfilec(c)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case SFNAME: - if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case PRINT: - if (!vim_isprintc(utf_ptr2char(rex.input))) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case SPRINT: - if (ascii_isdigit(*rex.input) || !vim_isprintc(utf_ptr2char(rex.input))) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case WHITE: - if (!ascii_iswhite(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NWHITE: - if (c == NUL || ascii_iswhite(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case DIGIT: - if (!ri_digit(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NDIGIT: - if (c == NUL || ri_digit(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case HEX: - if (!ri_hex(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NHEX: - if (c == NUL || ri_hex(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case OCTAL: - if (!ri_octal(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NOCTAL: - if (c == NUL || ri_octal(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case WORD: - if (!ri_word(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NWORD: - if (c == NUL || ri_word(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case HEAD: - if (!ri_head(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NHEAD: - if (c == NUL || ri_head(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case ALPHA: - if (!ri_alpha(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NALPHA: - if (c == NUL || ri_alpha(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case LOWER: - if (!ri_lower(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NLOWER: - if (c == NUL || ri_lower(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case UPPER: - if (!ri_upper(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NUPPER: - if (c == NUL || ri_upper(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case EXACTLY: - { - int len; - char_u *opnd; - - opnd = OPERAND(scan); - // Inline the first byte, for speed. - if (*opnd != *rex.input - && (!rex.reg_ic)) { - status = RA_NOMATCH; - } else if (*opnd == NUL) { - // match empty string always works; happens when "~" is - // empty. - } else { - if (opnd[1] == NUL && !rex.reg_ic) { - len = 1; // matched a single byte above - } else { - // Need to match first byte again for multi-byte. - len = (int)STRLEN(opnd); - 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 - && utf_composinglike(rex.input, rex.input + len) - && !rex.reg_icombine - && OP(next) != RE_COMPOSING) { - // raaron: This code makes a composing character get - // ignored, which is the correct behavior (sometimes) - // for voweled Hebrew texts. - status = RA_NOMATCH; - } - if (status != RA_NOMATCH) { - rex.input += len; - } - } - } - break; - - case ANYOF: - case ANYBUT: - if (c == NUL) - status = RA_NOMATCH; - else if ((cstrchr(OPERAND(scan), c) == NULL) == (op == ANYOF)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case MULTIBYTECODE: - { - int i, len; - - const char_u *opnd = OPERAND(scan); - // Safety check (just in case 'encoding' was changed since - // compiling the program). - if ((len = utfc_ptr2len(opnd)) < 2) { - status = RA_NOMATCH; - break; - } - 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; 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(rex.input + i); - status = RA_MATCH; - break; - } - } - } else { - for (i = 0; i < len; i++) { - if (opnd[i] != rex.input[i]) { - status = RA_NOMATCH; - break; - } - } - } - rex.input += len; - } - break; - - case RE_COMPOSING: - { - // Skip composing characters. - while (utf_iscomposing(utf_ptr2char(rex.input))) { - MB_CPTR_ADV(rex.input); - } - } - break; - - case NOTHING: - break; - - case BACK: - { - int i; - - /* - * When we run into BACK we need to check if we don't keep - * looping without matching any input. The second and later - * times a BACK is encountered it fails if the input is still - * at the same position as the previous time. - * The positions are stored in "backpos" and found by the - * current value of "scan", the position in the RE program. - */ - backpos_T *bp = (backpos_T *)backpos.ga_data; - for (i = 0; i < backpos.ga_len; ++i) - if (bp[i].bp_scan == scan) - break; - if (i == backpos.ga_len) { - backpos_T *p = GA_APPEND_VIA_PTR(backpos_T, &backpos); - p->bp_scan = scan; - } else if (reg_save_equal(&bp[i].bp_pos)) - /* Still at same position as last time, fail. */ - status = RA_NOMATCH; - - assert(status != RA_FAIL); - if (status != RA_NOMATCH) { - reg_save(&bp[i].bp_pos, &backpos); - } - } - break; - - case MOPEN + 0: /* Match start: \zs */ - case MOPEN + 1: /* \( */ - case MOPEN + 2: - case MOPEN + 3: - case MOPEN + 4: - case MOPEN + 5: - case MOPEN + 6: - case MOPEN + 7: - case MOPEN + 8: - case MOPEN + 9: - { - no = op - MOPEN; - cleanup_subexpr(); - rp = regstack_push(RS_MOPEN, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - save_se(&rp->rs_un.sesave, &rex.reg_startpos[no], - &rex.reg_startp[no]); - // We simply continue and handle the result when done. - } - } - break; - - case NOPEN: /* \%( */ - case NCLOSE: /* \) after \%( */ - if (regstack_push(RS_NOPEN, scan) == NULL) - status = RA_FAIL; - /* We simply continue and handle the result when done. */ - break; - - case ZOPEN + 1: - case ZOPEN + 2: - case ZOPEN + 3: - case ZOPEN + 4: - case ZOPEN + 5: - case ZOPEN + 6: - case ZOPEN + 7: - case ZOPEN + 8: - case ZOPEN + 9: - { - no = op - ZOPEN; - cleanup_zsubexpr(); - rp = regstack_push(RS_ZOPEN, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - save_se(&rp->rs_un.sesave, ®_startzpos[no], - ®_startzp[no]); - /* We simply continue and handle the result when done. */ - } - } - break; - - case MCLOSE + 0: /* Match end: \ze */ - case MCLOSE + 1: /* \) */ - case MCLOSE + 2: - case MCLOSE + 3: - case MCLOSE + 4: - case MCLOSE + 5: - case MCLOSE + 6: - case MCLOSE + 7: - case MCLOSE + 8: - case MCLOSE + 9: - { - no = op - MCLOSE; - cleanup_subexpr(); - rp = regstack_push(RS_MCLOSE, scan); - if (rp == NULL) { - status = RA_FAIL; - } else { - rp->rs_no = no; - save_se(&rp->rs_un.sesave, &rex.reg_endpos[no], &rex.reg_endp[no]); - // We simply continue and handle the result when done. - } - } - break; - - case ZCLOSE + 1: /* \) after \z( */ - case ZCLOSE + 2: - case ZCLOSE + 3: - case ZCLOSE + 4: - case ZCLOSE + 5: - case ZCLOSE + 6: - case ZCLOSE + 7: - case ZCLOSE + 8: - case ZCLOSE + 9: - { - no = op - ZCLOSE; - cleanup_zsubexpr(); - rp = regstack_push(RS_ZCLOSE, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - save_se(&rp->rs_un.sesave, ®_endzpos[no], - ®_endzp[no]); - /* We simply continue and handle the result when done. */ - } - } - break; - - case BACKREF + 1: - case BACKREF + 2: - case BACKREF + 3: - case BACKREF + 4: - case BACKREF + 5: - case BACKREF + 6: - case BACKREF + 7: - case BACKREF + 8: - case BACKREF + 9: - { - int len; - - no = op - BACKREF; - cleanup_subexpr(); - if (!REG_MULTI) { // Single-line regexp - if (rex.reg_startp[no] == NULL || rex.reg_endp[no] == NULL) { - // Backref was not set: Match an empty string. - len = 0; - } 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], rex.input, &len) != 0) { - status = RA_NOMATCH; - } - } - } else { // Multi-line regexp - if (rex.reg_startpos[no].lnum < 0 || rex.reg_endpos[no].lnum < 0) { - // Backref was not set: Match an empty string. - len = 0; - } else { - 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(rex.line + rex.reg_startpos[no].col, - rex.input, &len) != 0) { - status = RA_NOMATCH; - } - } else { - // Messy situation: Need to compare between two lines. - int r = match_with_backref(rex.reg_startpos[no].lnum, - rex.reg_startpos[no].col, - rex.reg_endpos[no].lnum, - rex.reg_endpos[no].col, - &len); - if (r != RA_MATCH) { - status = r; - } - } - } - } - - // Matched the backref, skip over it. - rex.input += len; - } - break; - - case ZREF + 1: - case ZREF + 2: - case ZREF + 3: - case ZREF + 4: - case ZREF + 5: - case ZREF + 6: - case ZREF + 7: - case ZREF + 8: - case ZREF + 9: - { - cleanup_zsubexpr(); - no = op - ZREF; - if (re_extmatch_in != NULL - && re_extmatch_in->matches[no] != NULL) { - int len = (int)STRLEN(re_extmatch_in->matches[no]); - if (cstrncmp(re_extmatch_in->matches[no], rex.input, &len) != 0) { - status = RA_NOMATCH; - } else { - rex.input += len; - } - } else { - // Backref was not set: Match an empty string. - } - } - break; - - case BRANCH: - { - if (OP(next) != BRANCH) /* No choice. */ - next = OPERAND(scan); /* Avoid recursion. */ - else { - rp = regstack_push(RS_BRANCH, scan); - if (rp == NULL) - status = RA_FAIL; - else - status = RA_BREAK; /* rest is below */ - } - } - break; - - case BRACE_LIMITS: - { - if (OP(next) == BRACE_SIMPLE) { - bl_minval = OPERAND_MIN(scan); - bl_maxval = OPERAND_MAX(scan); - } else if (OP(next) >= BRACE_COMPLEX - && OP(next) < BRACE_COMPLEX + 10) { - no = OP(next) - BRACE_COMPLEX; - brace_min[no] = OPERAND_MIN(scan); - brace_max[no] = OPERAND_MAX(scan); - brace_count[no] = 0; - } else { - internal_error("BRACE_LIMITS"); - status = RA_FAIL; - } - } - break; - - case BRACE_COMPLEX + 0: - case BRACE_COMPLEX + 1: - case BRACE_COMPLEX + 2: - case BRACE_COMPLEX + 3: - case BRACE_COMPLEX + 4: - case BRACE_COMPLEX + 5: - case BRACE_COMPLEX + 6: - case BRACE_COMPLEX + 7: - case BRACE_COMPLEX + 8: - case BRACE_COMPLEX + 9: - { - no = op - BRACE_COMPLEX; - ++brace_count[no]; - - /* If not matched enough times yet, try one more */ - if (brace_count[no] <= (brace_min[no] <= brace_max[no] - ? brace_min[no] : brace_max[no])) { - rp = regstack_push(RS_BRCPLX_MORE, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - reg_save(&rp->rs_un.regsave, &backpos); - next = OPERAND(scan); - /* We continue and handle the result when done. */ - } - break; - } - - /* If matched enough times, may try matching some more */ - if (brace_min[no] <= brace_max[no]) { - /* Range is the normal way around, use longest match */ - if (brace_count[no] <= brace_max[no]) { - rp = regstack_push(RS_BRCPLX_LONG, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - reg_save(&rp->rs_un.regsave, &backpos); - next = OPERAND(scan); - /* We continue and handle the result when done. */ - } - } - } else { - /* Range is backwards, use shortest match first */ - if (brace_count[no] <= brace_min[no]) { - rp = regstack_push(RS_BRCPLX_SHORT, scan); - if (rp == NULL) - status = RA_FAIL; - else { - reg_save(&rp->rs_un.regsave, &backpos); - /* We continue and handle the result when done. */ - } - } - } - } - break; - - case BRACE_SIMPLE: - case STAR: - case PLUS: - { - regstar_T rst; - - /* - * Lookahead to avoid useless match attempts when we know - * what character comes next. - */ - if (OP(next) == EXACTLY) { - rst.nextb = *OPERAND(next); - if (rex.reg_ic) { - if (mb_isupper(rst.nextb)) { - rst.nextb_ic = mb_tolower(rst.nextb); - } else { - rst.nextb_ic = mb_toupper(rst.nextb); - } - } else { - rst.nextb_ic = rst.nextb; - } - } else { - rst.nextb = NUL; - rst.nextb_ic = NUL; - } - if (op != BRACE_SIMPLE) { - rst.minval = (op == STAR) ? 0 : 1; - rst.maxval = MAX_LIMIT; - } else { - rst.minval = bl_minval; - rst.maxval = bl_maxval; - } - - /* - * When maxval > minval, try matching as much as possible, up - * to maxval. When maxval < minval, try matching at least the - * minimal number (since the range is backwards, that's also - * maxval!). - */ - rst.count = regrepeat(OPERAND(scan), rst.maxval); - if (got_int) { - status = RA_FAIL; - break; - } - if (rst.minval <= rst.maxval - ? rst.count >= rst.minval : rst.count >= rst.maxval) { - /* It could match. Prepare for trying to match what - * follows. The code is below. Parameters are stored in - * a regstar_T on the regstack. */ - if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); - status = RA_FAIL; - } else { - ga_grow(®stack, sizeof(regstar_T)); - regstack.ga_len += sizeof(regstar_T); - rp = regstack_push(rst.minval <= rst.maxval - ? RS_STAR_LONG : RS_STAR_SHORT, scan); - if (rp == NULL) - status = RA_FAIL; - else { - *(((regstar_T *)rp) - 1) = rst; - status = RA_BREAK; /* skip the restore bits */ - } - } - } else - status = RA_NOMATCH; - - } - break; - - case NOMATCH: - case MATCH: - case SUBPAT: - rp = regstack_push(RS_NOMATCH, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = op; - reg_save(&rp->rs_un.regsave, &backpos); - next = OPERAND(scan); - /* We continue and handle the result when done. */ - } - break; - - case BEHIND: - case NOBEHIND: - /* Need a bit of room to store extra positions. */ - if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); - status = RA_FAIL; - } else { - ga_grow(®stack, sizeof(regbehind_T)); - regstack.ga_len += sizeof(regbehind_T); - rp = regstack_push(RS_BEHIND1, scan); - if (rp == NULL) - status = RA_FAIL; - else { - /* Need to save the subexpr to be able to restore them - * when there is a match but we don't use it. */ - save_subexpr(((regbehind_T *)rp) - 1); - - rp->rs_no = op; - reg_save(&rp->rs_un.regsave, &backpos); - /* First try if what follows matches. If it does then we - * check the behind match by looping. */ - } - } - break; - - case BHPOS: - if (REG_MULTI) { - 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 != rex.input) { - status = RA_NOMATCH; - } - break; - - case NEWL: - 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) { - ADVANCE_REGINPUT(); - } else { - reg_nextline(); - } - break; - - case END: - status = RA_MATCH; /* Success! */ - break; - - default: - iemsg(_(e_re_corr)); -#ifdef REGEXP_DEBUG - printf("Illegal op code %d\n", op); -#endif - status = RA_FAIL; - break; - } - } - - /* If we can't continue sequentially, break the inner loop. */ - if (status != RA_CONT) - break; - - /* Continue in inner loop, advance to next item. */ - scan = next; - - } /* end of inner loop */ - - /* - * If there is something on the regstack execute the code for the state. - * If the state is popped then loop and use the older state. - */ - while (!GA_EMPTY(®stack) && status != RA_FAIL) { - rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1; - switch (rp->rs_state) { - case RS_NOPEN: - /* Result is passed on as-is, simply pop the state. */ - regstack_pop(&scan); - break; - - case RS_MOPEN: - // Pop the state. Restore pointers when there is no match. - if (status == RA_NOMATCH) { - restore_se(&rp->rs_un.sesave, &rex.reg_startpos[rp->rs_no], - &rex.reg_startp[rp->rs_no]); - } - regstack_pop(&scan); - break; - - case RS_ZOPEN: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) - restore_se(&rp->rs_un.sesave, ®_startzpos[rp->rs_no], - ®_startzp[rp->rs_no]); - regstack_pop(&scan); - break; - - case RS_MCLOSE: - // Pop the state. Restore pointers when there is no match. - if (status == RA_NOMATCH) { - restore_se(&rp->rs_un.sesave, &rex.reg_endpos[rp->rs_no], - &rex.reg_endp[rp->rs_no]); - } - regstack_pop(&scan); - break; - - case RS_ZCLOSE: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) - restore_se(&rp->rs_un.sesave, ®_endzpos[rp->rs_no], - ®_endzp[rp->rs_no]); - regstack_pop(&scan); - break; - - case RS_BRANCH: - if (status == RA_MATCH) - /* this branch matched, use it */ - regstack_pop(&scan); - else { - if (status != RA_BREAK) { - /* After a non-matching branch: try next one. */ - reg_restore(&rp->rs_un.regsave, &backpos); - scan = rp->rs_scan; - } - if (scan == NULL || OP(scan) != BRANCH) { - /* no more branches, didn't find a match */ - status = RA_NOMATCH; - regstack_pop(&scan); - } else { - /* Prepare to try a branch. */ - rp->rs_scan = regnext(scan); - reg_save(&rp->rs_un.regsave, &backpos); - scan = OPERAND(scan); - } - } - break; - - case RS_BRCPLX_MORE: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) { - reg_restore(&rp->rs_un.regsave, &backpos); - --brace_count[rp->rs_no]; /* decrement match count */ - } - regstack_pop(&scan); - break; - - case RS_BRCPLX_LONG: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) { - /* There was no match, but we did find enough matches. */ - reg_restore(&rp->rs_un.regsave, &backpos); - --brace_count[rp->rs_no]; - /* continue with the items after "\{}" */ - status = RA_CONT; - } - regstack_pop(&scan); - if (status == RA_CONT) - scan = regnext(scan); - break; - - case RS_BRCPLX_SHORT: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) - /* There was no match, try to match one more item. */ - reg_restore(&rp->rs_un.regsave, &backpos); - regstack_pop(&scan); - if (status == RA_NOMATCH) { - scan = OPERAND(scan); - status = RA_CONT; - } - break; - - case RS_NOMATCH: - /* Pop the state. If the operand matches for NOMATCH or - * doesn't match for MATCH/SUBPAT, we fail. Otherwise backup, - * except for SUBPAT, and continue with the next item. */ - if (status == (rp->rs_no == NOMATCH ? RA_MATCH : RA_NOMATCH)) - status = RA_NOMATCH; - else { - status = RA_CONT; - if (rp->rs_no != SUBPAT) /* zero-width */ - reg_restore(&rp->rs_un.regsave, &backpos); - } - regstack_pop(&scan); - if (status == RA_CONT) - scan = regnext(scan); - break; - - case RS_BEHIND1: - if (status == RA_NOMATCH) { - regstack_pop(&scan); - regstack.ga_len -= sizeof(regbehind_T); - } else { - /* The stuff after BEHIND/NOBEHIND matches. Now try if - * the behind part does (not) match before the current - * position in the input. This must be done at every - * position in the input and checking if the match ends at - * the current position. */ - - /* save the position after the found match for next */ - reg_save(&(((regbehind_T *)rp) - 1)->save_after, &backpos); - - /* Start looking for a match with operand at the current - * position. Go back one character until we find the - * result, hitting the start of the line or the previous - * line (for multi-line matching). - * Set behind_pos to where the match should end, BHPOS - * will match it. Save the current value. */ - (((regbehind_T *)rp) - 1)->save_behind = behind_pos; - behind_pos = rp->rs_un.regsave; - - rp->rs_state = RS_BEHIND2; - - reg_restore(&rp->rs_un.regsave, &backpos); - scan = OPERAND(rp->rs_scan) + 4; - } - break; - - case RS_BEHIND2: - /* - * Looping for BEHIND / NOBEHIND match. - */ - if (status == RA_MATCH && reg_save_equal(&behind_pos)) { - /* found a match that ends where "next" started */ - behind_pos = (((regbehind_T *)rp) - 1)->save_behind; - if (rp->rs_no == BEHIND) - reg_restore(&(((regbehind_T *)rp) - 1)->save_after, - &backpos); - else { - /* But we didn't want a match. Need to restore the - * subexpr, because what follows matched, so they have - * been set. */ - status = RA_NOMATCH; - restore_subexpr(((regbehind_T *)rp) - 1); - } - regstack_pop(&scan); - regstack.ga_len -= sizeof(regbehind_T); - } else { - long limit; - - /* No match or a match that doesn't end where we want it: Go - * back one character. May go to previous line once. */ - no = OK; - limit = OPERAND_MIN(rp->rs_scan); - if (REG_MULTI) { - if (limit > 0 - && ((rp->rs_un.regsave.rs_u.pos.lnum - < behind_pos.rs_u.pos.lnum - ? (colnr_T)STRLEN(rex.line) - : behind_pos.rs_u.pos.col) - - rp->rs_un.regsave.rs_u.pos.col >= limit)) - no = FAIL; - else if (rp->rs_un.regsave.rs_u.pos.col == 0) { - if (rp->rs_un.regsave.rs_u.pos.lnum - < behind_pos.rs_u.pos.lnum - || reg_getline( - --rp->rs_un.regsave.rs_u.pos.lnum) - == NULL) - no = FAIL; - else { - reg_restore(&rp->rs_un.regsave, &backpos); - rp->rs_un.regsave.rs_u.pos.col = - (colnr_T)STRLEN(rex.line); - } - } else { - const char_u *const line = - reg_getline(rp->rs_un.regsave.rs_u.pos.lnum); - - rp->rs_un.regsave.rs_u.pos.col -= - utf_head_off(line, - line + rp->rs_un.regsave.rs_u.pos.col - 1) - + 1; - } - } else { - if (rp->rs_un.regsave.rs_u.ptr == rex.line) { - no = FAIL; - } else { - 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) { - no = FAIL; - } - } - } - if (no == OK) { - /* Advanced, prepare for finding match again. */ - reg_restore(&rp->rs_un.regsave, &backpos); - scan = OPERAND(rp->rs_scan) + 4; - if (status == RA_MATCH) { - /* We did match, so subexpr may have been changed, - * need to restore them for the next try. */ - status = RA_NOMATCH; - restore_subexpr(((regbehind_T *)rp) - 1); - } - } else { - /* Can't advance. For NOBEHIND that's a match. */ - behind_pos = (((regbehind_T *)rp) - 1)->save_behind; - if (rp->rs_no == NOBEHIND) { - reg_restore(&(((regbehind_T *)rp) - 1)->save_after, - &backpos); - status = RA_MATCH; - } else { - /* We do want a proper match. Need to restore the - * subexpr if we had a match, because they may have - * been set. */ - if (status == RA_MATCH) { - status = RA_NOMATCH; - restore_subexpr(((regbehind_T *)rp) - 1); - } - } - regstack_pop(&scan); - regstack.ga_len -= sizeof(regbehind_T); - } - } - break; - - case RS_STAR_LONG: - case RS_STAR_SHORT: - { - regstar_T *rst = ((regstar_T *)rp) - 1; - - if (status == RA_MATCH) { - regstack_pop(&scan); - regstack.ga_len -= sizeof(regstar_T); - break; - } - - /* Tried once already, restore input pointers. */ - if (status != RA_BREAK) - reg_restore(&rp->rs_un.regsave, &backpos); - - /* Repeat until we found a position where it could match. */ - for (;; ) { - if (status != RA_BREAK) { - /* Tried first position already, advance. */ - if (rp->rs_state == RS_STAR_LONG) { - /* Trying for longest match, but couldn't or - * didn't match -- back up one char. */ - if (--rst->count < rst->minval) - break; - if (rex.input == rex.line) { - // backup to last char of previous line - rex.lnum--; - rex.line = reg_getline(rex.lnum); - // Just in case regrepeat() didn't count right. - if (rex.line == NULL) { - break; - } - rex.input = rex.line + STRLEN(rex.line); - fast_breakcheck(); - } else { - MB_PTR_BACK(rex.line, rex.input); - } - } else { - /* Range is backwards, use shortest match first. - * Careful: maxval and minval are exchanged! - * Couldn't or didn't match: try advancing one - * char. */ - if (rst->count == rst->minval - || regrepeat(OPERAND(rp->rs_scan), 1L) == 0) - break; - ++rst->count; - } - if (got_int) - break; - } else - status = RA_NOMATCH; - - // 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; - break; - } - } - if (status != RA_CONT) { - /* Failed. */ - regstack_pop(&scan); - regstack.ga_len -= sizeof(regstar_T); - status = RA_NOMATCH; - } - } - break; - } - - /* If we want to continue the inner loop or didn't pop a state - * continue matching loop */ - if (status == RA_CONT || rp == (regitem_T *) - ((char *)regstack.ga_data + regstack.ga_len) - 1) - break; - } - - /* May need to continue with the inner loop, starting at "scan". */ - if (status == RA_CONT) - continue; - - /* - * If the regstack is empty or something failed we are done. - */ - if (GA_EMPTY(®stack) || status == RA_FAIL) { - if (scan == NULL) { - /* - * We get here only if there's trouble -- normally "case END" is - * the terminating point. - */ - iemsg(_(e_re_corr)); -#ifdef REGEXP_DEBUG - printf("Premature EOL\n"); -#endif - } - return status == RA_MATCH; - } - - } /* End of loop until the regstack is empty. */ - - /* NOTREACHED */ -} - -/* - * Push an item onto the regstack. - * Returns pointer to new item. Returns NULL when out of memory. - */ -static regitem_T *regstack_push(regstate_T state, char_u *scan) -{ - regitem_T *rp; - - if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); - return NULL; - } - ga_grow(®stack, sizeof(regitem_T)); - - rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len); - rp->rs_state = state; - rp->rs_scan = scan; - - regstack.ga_len += sizeof(regitem_T); - return rp; -} - -/* - * Pop an item from the regstack. - */ -static void regstack_pop(char_u **scan) -{ - regitem_T *rp; - - rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1; - *scan = rp->rs_scan; - - regstack.ga_len -= sizeof(regitem_T); -} - -/* - * regrepeat - repeatedly match something simple, return how many. - * Advances rex.input (and rex.lnum) to just after the matched chars. - */ -static int -regrepeat ( - char_u *p, - long maxcount /* maximum number of matches allowed */ -) -{ - long count = 0; - char_u *opnd; - int mask; - int testval = 0; - - char_u *scan = rex.input; // Make local copy of rex.input for speed. - opnd = OPERAND(p); - switch (OP(p)) { - case ANY: - case ANY + ADD_NL: - while (count < maxcount) { - /* Matching anything means we continue until end-of-line (or - * end-of-file for ANY + ADD_NL), only limited by maxcount. */ - while (*scan != NUL && count < maxcount) { - count++; - MB_PTR_ADV(scan); - } - 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 = rex.input; - if (got_int) { - break; - } - } - break; - - case IDENT: - case IDENT + ADD_NL: - testval = 1; - FALLTHROUGH; - case SIDENT: - case SIDENT + ADD_NL: - while (count < maxcount) { - if (vim_isIDc(utf_ptr2char(scan)) && (testval || !ascii_isdigit(*scan))) { - MB_PTR_ADV(scan); - } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - ++count; - } - break; - - case KWORD: - case KWORD + ADD_NL: - testval = 1; - FALLTHROUGH; - case SKWORD: - case SKWORD + ADD_NL: - while (count < maxcount) { - if (vim_iswordp_buf(scan, rex.reg_buf) - && (testval || !ascii_isdigit(*scan))) { - MB_PTR_ADV(scan); - } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - count++; - } - break; - - case FNAME: - case FNAME + ADD_NL: - testval = 1; - FALLTHROUGH; - case SFNAME: - case SFNAME + ADD_NL: - while (count < maxcount) { - if (vim_isfilec(utf_ptr2char(scan)) && (testval || !ascii_isdigit(*scan))) { - MB_PTR_ADV(scan); - } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - count++; - } - break; - - case PRINT: - case PRINT + ADD_NL: - testval = 1; - FALLTHROUGH; - case SPRINT: - case SPRINT + ADD_NL: - while (count < maxcount) { - if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (vim_isprintc(utf_ptr2char(scan)) == 1 - && (testval || !ascii_isdigit(*scan))) { - MB_PTR_ADV(scan); - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - count++; - } - break; - - case WHITE: - case WHITE + ADD_NL: - testval = mask = RI_WHITE; -do_class: - while (count < maxcount) { - int l; - if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if ((l = utfc_ptr2len(scan)) > 1) { - if (testval != 0) { - break; - } - scan += l; - } else if ((class_tab[*scan] & mask) == testval) { - scan++; - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - ++count; - } - break; - - case NWHITE: - case NWHITE + ADD_NL: - mask = RI_WHITE; - goto do_class; - case DIGIT: - case DIGIT + ADD_NL: - testval = mask = RI_DIGIT; - goto do_class; - case NDIGIT: - case NDIGIT + ADD_NL: - mask = RI_DIGIT; - goto do_class; - case HEX: - case HEX + ADD_NL: - testval = mask = RI_HEX; - goto do_class; - case NHEX: - case NHEX + ADD_NL: - mask = RI_HEX; - goto do_class; - case OCTAL: - case OCTAL + ADD_NL: - testval = mask = RI_OCTAL; - goto do_class; - case NOCTAL: - case NOCTAL + ADD_NL: - mask = RI_OCTAL; - goto do_class; - case WORD: - case WORD + ADD_NL: - testval = mask = RI_WORD; - goto do_class; - case NWORD: - case NWORD + ADD_NL: - mask = RI_WORD; - goto do_class; - case HEAD: - case HEAD + ADD_NL: - testval = mask = RI_HEAD; - goto do_class; - case NHEAD: - case NHEAD + ADD_NL: - mask = RI_HEAD; - goto do_class; - case ALPHA: - case ALPHA + ADD_NL: - testval = mask = RI_ALPHA; - goto do_class; - case NALPHA: - case NALPHA + ADD_NL: - mask = RI_ALPHA; - goto do_class; - case LOWER: - case LOWER + ADD_NL: - testval = mask = RI_LOWER; - goto do_class; - case NLOWER: - case NLOWER + ADD_NL: - mask = RI_LOWER; - goto do_class; - case UPPER: - case UPPER + ADD_NL: - testval = mask = RI_UPPER; - goto do_class; - case NUPPER: - case NUPPER + ADD_NL: - mask = RI_UPPER; - goto do_class; - - case EXACTLY: - { - int cu, cl; - - // This doesn't do a multi-byte character, because a MULTIBYTECODE - // would have been used for it. It does handle single-byte - // characters, such as latin1. - if (rex.reg_ic) { - cu = mb_toupper(*opnd); - cl = mb_tolower(*opnd); - while (count < maxcount && (*scan == cu || *scan == cl)) { - count++; - scan++; - } - } else { - cu = *opnd; - while (count < maxcount && *scan == cu) { - count++; - scan++; - } - } - break; - } - - case MULTIBYTECODE: - { - int i, len, cf = 0; - - /* Safety check (just in case 'encoding' was changed since - * compiling the program). */ - if ((len = utfc_ptr2len(opnd)) > 1) { - if (rex.reg_ic) { - cf = utf_fold(utf_ptr2char(opnd)); - } - while (count < maxcount && utfc_ptr2len(scan) >= len) { - for (i = 0; i < len; i++) { - if (opnd[i] != scan[i]) { - break; - } - } - if (i < len && (!rex.reg_ic - || utf_fold(utf_ptr2char(scan)) != cf)) { - break; - } - scan += len; - ++count; - } - } - } - break; - - case ANYOF: - case ANYOF + ADD_NL: - testval = 1; - FALLTHROUGH; - - case ANYBUT: - case ANYBUT + ADD_NL: - while (count < maxcount) { - int len; - if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else if ((len = utfc_ptr2len(scan)) > 1) { - if ((cstrchr(opnd, utf_ptr2char(scan)) == NULL) == testval) { - break; - } - scan += len; - } else { - if ((cstrchr(opnd, *scan) == NULL) == testval) - break; - ++scan; - } - ++count; - } - break; - - case NEWL: - while (count < maxcount - && ((*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) { - ADVANCE_REGINPUT(); - } else { - reg_nextline(); - } - scan = rex.input; - if (got_int) { - break; - } - } - break; - - default: // Oh dear. Called inappropriately. - iemsg(_(e_re_corr)); -#ifdef REGEXP_DEBUG - printf("Called regrepeat with op code %d\n", OP(p)); -#endif - break; - } - - rex.input = scan; - - return (int)count; -} - -/* - * regnext - dig the "next" pointer out of a node - * Returns NULL when calculating size, when there is no next item and when - * there is an error. - */ -static char_u *regnext(char_u *p) - FUNC_ATTR_NONNULL_ALL -{ - int offset; - - if (p == JUST_CALC_SIZE || reg_toolong) - return NULL; - - offset = NEXT(p); - if (offset == 0) - return NULL; - - if (OP(p) == BACK) - return p - offset; - else - return p + offset; -} - /* * Check the regexp program for its magic number. * Return true if it's wrong. @@ -5662,7 +1214,7 @@ static void cleanup_zsubexpr(void) { if (rex.need_clear_zsubexpr) { if (REG_MULTI) { - /* Use 0xff to set lnum to -1 */ + // Use 0xff to set lnum to -1 memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP); memset(reg_endzpos, 0xff, sizeof(lpos_T) * NSUBEXP); } else { @@ -5673,45 +1225,6 @@ static void cleanup_zsubexpr(void) } } -// 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 -{ - // 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 = 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]; - } else { - bp->save_start[i].se_u.ptr = rex.reg_startp[i]; - bp->save_end[i].se_u.ptr = rex.reg_endp[i]; - } - } - } -} - -// Restore the subexpr from "bp". -static void restore_subexpr(regbehind_T *bp) - FUNC_ATTR_NONNULL_ALL -{ - // 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; - } else { - rex.reg_startp[i] = bp->save_start[i].se_u.ptr; - rex.reg_endp[i] = bp->save_end[i].se_u.ptr; - } - } - } -} // Advance rex.lnum, rex.line and rex.input to the next line. static void reg_nextline(void) @@ -5721,81 +1234,6 @@ static void reg_nextline(void) fast_breakcheck(); } -// 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)(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. -static void reg_restore(regsave_T *save, garray_T *gap) - FUNC_ATTR_NONNULL_ALL -{ - if (REG_MULTI) { - 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); - } - 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 bool reg_save_equal(const regsave_T *save) - FUNC_ATTR_NONNULL_ALL -{ - 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; -} - -/* - * Tentatively set the sub-expression start to the current position (after - * calling regmatch() they will have changed). Need to save the existing - * values for when there is no match. - * Use se_save() to use pointer (save_se_multi()) or position (save_se_one()), - * depending on REG_MULTI. - */ -static void save_se_multi(save_se_T *savep, lpos_T *posp) -{ - savep->se_u.pos = *posp; - 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 = rex.input; -} - -/* - * Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL. - */ -static int re_num_cmp(uint32_t val, char_u *scan) -{ - uint32_t n = (uint32_t)OPERAND_MIN(scan); - - if (OPERAND_CMP(scan) == '>') - return val > n; - if (OPERAND_CMP(scan) == '<') - return val < n; - return val == n; -} /* * Check whether a backreference matches. @@ -5803,7 +1241,12 @@ static int re_num_cmp(uint32_t val, char_u *scan) * If "bytelen" is not NULL, it is set to the byte length of the match in the * last line. */ -static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T end_lnum, colnr_T end_col, int *bytelen) +static int match_with_backref( + linenr_T start_lnum, + colnr_T start_col, + linenr_T end_lnum, + colnr_T end_col, + int *bytelen) { linenr_T clnum = start_lnum; colnr_T ccol = start_col; @@ -5818,7 +1261,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e if (rex.line != reg_tofree) { len = (int)STRLEN(rex.line); if (reg_tofree == NULL || len >= (int)reg_tofreelen) { - len += 50; /* get some extra */ + len += 50; // get some extra xfree(reg_tofree); reg_tofree = xmalloc(len); reg_tofreelen = len; @@ -5828,7 +1271,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e rex.line = reg_tofree; } - /* Get the line to compare with. */ + // Get the line to compare with. p = reg_getline(clnum); assert(p); @@ -5850,7 +1293,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e return RA_NOMATCH; // text too short } - /* Advance to next line. */ + // Advance to next line. reg_nextline(); if (bytelen != NULL) *bytelen = 0; @@ -5865,520 +1308,72 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e return RA_MATCH; } -#ifdef BT_REGEXP_DUMP - -/* - * regdump - dump a regexp onto stdout in vaguely comprehensible form - */ -static void regdump(char_u *pattern, bt_regprog_T *r) -{ - char_u *s; - int op = EXACTLY; /* Arbitrary non-END op. */ - char_u *next; - char_u *end = NULL; - FILE *f; - -#ifdef BT_REGEXP_LOG - f = fopen("bt_regexp_log.log", "a"); -#else - f = stdout; -#endif - if (f == NULL) - return; - fprintf(f, "-------------------------------------\n\r\nregcomp(%s):\r\n", - pattern); - - s = r->program + 1; - /* - * Loop until we find the END that isn't before a referred next (an END - * can also appear in a NOMATCH operand). - */ - while (op != END || s <= end) { - op = OP(s); - fprintf(f, "%2d%s", (int)(s - r->program), regprop(s)); /* Where, what. */ - next = regnext(s); - if (next == NULL) /* Next ptr. */ - fprintf(f, "(0)"); - else - fprintf(f, "(%d)", (int)((s - r->program) + (next - s))); - if (end < next) - end = next; - if (op == BRACE_LIMITS) { - /* Two ints */ - fprintf(f, " minval %" PRId64 ", maxval %" PRId64, - (int64_t)OPERAND_MIN(s), (int64_t)OPERAND_MAX(s)); - s += 8; - } else if (op == BEHIND || op == NOBEHIND) { - /* one int */ - fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); - s += 4; - } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) { - // one int plus comparator - fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); - s += 5; - } - s += 3; - if (op == ANYOF || op == ANYOF + ADD_NL - || op == ANYBUT || op == ANYBUT + ADD_NL - || op == EXACTLY) { - /* Literal string, where present. */ - fprintf(f, "\nxxxxxxxxx\n"); - while (*s != NUL) - fprintf(f, "%c", *s++); - fprintf(f, "\nxxxxxxxxx\n"); - s++; - } - fprintf(f, "\r\n"); - } - - /* Header fields of interest. */ - if (r->regstart != NUL) - fprintf(f, "start `%s' 0x%x; ", r->regstart < 256 - ? (char *)transchar(r->regstart) - : "multibyte", r->regstart); - if (r->reganch) - fprintf(f, "anchored; "); - if (r->regmust != NULL) - fprintf(f, "must have \"%s\"", r->regmust); - fprintf(f, "\r\n"); - -#ifdef BT_REGEXP_LOG - fclose(f); -#endif -} -#endif /* BT_REGEXP_DUMP */ - -#ifdef REGEXP_DEBUG -/* - * regprop - printable representation of opcode - */ -static char_u *regprop(char_u *op) +/// Used in a place where no * or \+ can follow. +static bool re_mult_next(char *what) { - char *p; - static char buf[50]; - - STRCPY(buf, ":"); - - switch ((int) OP(op)) { - case BOL: - p = "BOL"; - break; - case EOL: - p = "EOL"; - break; - case RE_BOF: - p = "BOF"; - break; - case RE_EOF: - p = "EOF"; - break; - case CURSOR: - p = "CURSOR"; - break; - case RE_VISUAL: - p = "RE_VISUAL"; - break; - case RE_LNUM: - p = "RE_LNUM"; - break; - case RE_MARK: - p = "RE_MARK"; - break; - case RE_COL: - p = "RE_COL"; - break; - case RE_VCOL: - p = "RE_VCOL"; - break; - case BOW: - p = "BOW"; - break; - case EOW: - p = "EOW"; - break; - case ANY: - p = "ANY"; - break; - case ANY + ADD_NL: - p = "ANY+NL"; - break; - case ANYOF: - p = "ANYOF"; - break; - case ANYOF + ADD_NL: - p = "ANYOF+NL"; - break; - case ANYBUT: - p = "ANYBUT"; - break; - case ANYBUT + ADD_NL: - p = "ANYBUT+NL"; - break; - case IDENT: - p = "IDENT"; - break; - case IDENT + ADD_NL: - p = "IDENT+NL"; - break; - case SIDENT: - p = "SIDENT"; - break; - case SIDENT + ADD_NL: - p = "SIDENT+NL"; - break; - case KWORD: - p = "KWORD"; - break; - case KWORD + ADD_NL: - p = "KWORD+NL"; - break; - case SKWORD: - p = "SKWORD"; - break; - case SKWORD + ADD_NL: - p = "SKWORD+NL"; - break; - case FNAME: - p = "FNAME"; - break; - case FNAME + ADD_NL: - p = "FNAME+NL"; - break; - case SFNAME: - p = "SFNAME"; - break; - case SFNAME + ADD_NL: - p = "SFNAME+NL"; - break; - case PRINT: - p = "PRINT"; - break; - case PRINT + ADD_NL: - p = "PRINT+NL"; - break; - case SPRINT: - p = "SPRINT"; - break; - case SPRINT + ADD_NL: - p = "SPRINT+NL"; - break; - case WHITE: - p = "WHITE"; - break; - case WHITE + ADD_NL: - p = "WHITE+NL"; - break; - case NWHITE: - p = "NWHITE"; - break; - case NWHITE + ADD_NL: - p = "NWHITE+NL"; - break; - case DIGIT: - p = "DIGIT"; - break; - case DIGIT + ADD_NL: - p = "DIGIT+NL"; - break; - case NDIGIT: - p = "NDIGIT"; - break; - case NDIGIT + ADD_NL: - p = "NDIGIT+NL"; - break; - case HEX: - p = "HEX"; - break; - case HEX + ADD_NL: - p = "HEX+NL"; - break; - case NHEX: - p = "NHEX"; - break; - case NHEX + ADD_NL: - p = "NHEX+NL"; - break; - case OCTAL: - p = "OCTAL"; - break; - case OCTAL + ADD_NL: - p = "OCTAL+NL"; - break; - case NOCTAL: - p = "NOCTAL"; - break; - case NOCTAL + ADD_NL: - p = "NOCTAL+NL"; - break; - case WORD: - p = "WORD"; - break; - case WORD + ADD_NL: - p = "WORD+NL"; - break; - case NWORD: - p = "NWORD"; - break; - case NWORD + ADD_NL: - p = "NWORD+NL"; - break; - case HEAD: - p = "HEAD"; - break; - case HEAD + ADD_NL: - p = "HEAD+NL"; - break; - case NHEAD: - p = "NHEAD"; - break; - case NHEAD + ADD_NL: - p = "NHEAD+NL"; - break; - case ALPHA: - p = "ALPHA"; - break; - case ALPHA + ADD_NL: - p = "ALPHA+NL"; - break; - case NALPHA: - p = "NALPHA"; - break; - case NALPHA + ADD_NL: - p = "NALPHA+NL"; - break; - case LOWER: - p = "LOWER"; - break; - case LOWER + ADD_NL: - p = "LOWER+NL"; - break; - case NLOWER: - p = "NLOWER"; - break; - case NLOWER + ADD_NL: - p = "NLOWER+NL"; - break; - case UPPER: - p = "UPPER"; - break; - case UPPER + ADD_NL: - p = "UPPER+NL"; - break; - case NUPPER: - p = "NUPPER"; - break; - case NUPPER + ADD_NL: - p = "NUPPER+NL"; - break; - case BRANCH: - p = "BRANCH"; - break; - case EXACTLY: - p = "EXACTLY"; - break; - case NOTHING: - p = "NOTHING"; - break; - case BACK: - p = "BACK"; - break; - case END: - p = "END"; - break; - case MOPEN + 0: - p = "MATCH START"; - break; - case MOPEN + 1: - case MOPEN + 2: - case MOPEN + 3: - case MOPEN + 4: - case MOPEN + 5: - case MOPEN + 6: - case MOPEN + 7: - case MOPEN + 8: - case MOPEN + 9: - sprintf(buf + STRLEN(buf), "MOPEN%d", OP(op) - MOPEN); - p = NULL; - break; - case MCLOSE + 0: - p = "MATCH END"; - break; - case MCLOSE + 1: - case MCLOSE + 2: - case MCLOSE + 3: - case MCLOSE + 4: - case MCLOSE + 5: - case MCLOSE + 6: - case MCLOSE + 7: - case MCLOSE + 8: - case MCLOSE + 9: - sprintf(buf + STRLEN(buf), "MCLOSE%d", OP(op) - MCLOSE); - p = NULL; - break; - case BACKREF + 1: - case BACKREF + 2: - case BACKREF + 3: - case BACKREF + 4: - case BACKREF + 5: - case BACKREF + 6: - case BACKREF + 7: - case BACKREF + 8: - case BACKREF + 9: - sprintf(buf + STRLEN(buf), "BACKREF%d", OP(op) - BACKREF); - p = NULL; - break; - case NOPEN: - p = "NOPEN"; - break; - case NCLOSE: - p = "NCLOSE"; - break; - case ZOPEN + 1: - case ZOPEN + 2: - case ZOPEN + 3: - case ZOPEN + 4: - case ZOPEN + 5: - case ZOPEN + 6: - case ZOPEN + 7: - case ZOPEN + 8: - case ZOPEN + 9: - sprintf(buf + STRLEN(buf), "ZOPEN%d", OP(op) - ZOPEN); - p = NULL; - break; - case ZCLOSE + 1: - case ZCLOSE + 2: - case ZCLOSE + 3: - case ZCLOSE + 4: - case ZCLOSE + 5: - case ZCLOSE + 6: - case ZCLOSE + 7: - case ZCLOSE + 8: - case ZCLOSE + 9: - sprintf(buf + STRLEN(buf), "ZCLOSE%d", OP(op) - ZCLOSE); - p = NULL; - break; - case ZREF + 1: - case ZREF + 2: - case ZREF + 3: - case ZREF + 4: - case ZREF + 5: - case ZREF + 6: - case ZREF + 7: - case ZREF + 8: - case ZREF + 9: - sprintf(buf + STRLEN(buf), "ZREF%d", OP(op) - ZREF); - p = NULL; - break; - case STAR: - p = "STAR"; - break; - case PLUS: - p = "PLUS"; - break; - case NOMATCH: - p = "NOMATCH"; - break; - case MATCH: - p = "MATCH"; - break; - case BEHIND: - p = "BEHIND"; - break; - case NOBEHIND: - p = "NOBEHIND"; - break; - case SUBPAT: - p = "SUBPAT"; - break; - case BRACE_LIMITS: - p = "BRACE_LIMITS"; - break; - case BRACE_SIMPLE: - p = "BRACE_SIMPLE"; - break; - case BRACE_COMPLEX + 0: - case BRACE_COMPLEX + 1: - case BRACE_COMPLEX + 2: - case BRACE_COMPLEX + 3: - case BRACE_COMPLEX + 4: - case BRACE_COMPLEX + 5: - case BRACE_COMPLEX + 6: - case BRACE_COMPLEX + 7: - case BRACE_COMPLEX + 8: - case BRACE_COMPLEX + 9: - sprintf(buf + STRLEN(buf), "BRACE_COMPLEX%d", OP(op) - BRACE_COMPLEX); - p = NULL; - break; - case MULTIBYTECODE: - p = "MULTIBYTECODE"; - break; - case NEWL: - p = "NEWL"; - break; - default: - sprintf(buf + STRLEN(buf), "corrupt %d", OP(op)); - p = NULL; - break; + if (re_multi_type(peekchr()) == MULTI_MULT) { + semsg(_("E888: (NFA regexp) cannot repeat %s"), what); + rc_did_emsg = true; + return false; } - if (p != NULL) - STRCAT(buf, p); - return (char_u *)buf; + return true; } -#endif /* REGEXP_DEBUG */ - +typedef struct { + int a, b, c; +} decomp_T; -/* 0xfb20 - 0xfb4f */ +// 0xfb20 - 0xfb4f static decomp_T decomp_table[0xfb4f-0xfb20+1] = { - {0x5e2,0,0}, /* 0xfb20 alt ayin */ - {0x5d0,0,0}, /* 0xfb21 alt alef */ - {0x5d3,0,0}, /* 0xfb22 alt dalet */ - {0x5d4,0,0}, /* 0xfb23 alt he */ - {0x5db,0,0}, /* 0xfb24 alt kaf */ - {0x5dc,0,0}, /* 0xfb25 alt lamed */ - {0x5dd,0,0}, /* 0xfb26 alt mem-sofit */ - {0x5e8,0,0}, /* 0xfb27 alt resh */ - {0x5ea,0,0}, /* 0xfb28 alt tav */ - {'+', 0, 0}, /* 0xfb29 alt plus */ - {0x5e9, 0x5c1, 0}, /* 0xfb2a shin+shin-dot */ - {0x5e9, 0x5c2, 0}, /* 0xfb2b shin+sin-dot */ - {0x5e9, 0x5c1, 0x5bc}, /* 0xfb2c shin+shin-dot+dagesh */ - {0x5e9, 0x5c2, 0x5bc}, /* 0xfb2d shin+sin-dot+dagesh */ - {0x5d0, 0x5b7, 0}, /* 0xfb2e alef+patah */ - {0x5d0, 0x5b8, 0}, /* 0xfb2f alef+qamats */ - {0x5d0, 0x5b4, 0}, /* 0xfb30 alef+hiriq */ - {0x5d1, 0x5bc, 0}, /* 0xfb31 bet+dagesh */ - {0x5d2, 0x5bc, 0}, /* 0xfb32 gimel+dagesh */ - {0x5d3, 0x5bc, 0}, /* 0xfb33 dalet+dagesh */ - {0x5d4, 0x5bc, 0}, /* 0xfb34 he+dagesh */ - {0x5d5, 0x5bc, 0}, /* 0xfb35 vav+dagesh */ - {0x5d6, 0x5bc, 0}, /* 0xfb36 zayin+dagesh */ - {0xfb37, 0, 0}, /* 0xfb37 -- */ - {0x5d8, 0x5bc, 0}, /* 0xfb38 tet+dagesh */ - {0x5d9, 0x5bc, 0}, /* 0xfb39 yud+dagesh */ - {0x5da, 0x5bc, 0}, /* 0xfb3a kaf sofit+dagesh */ - {0x5db, 0x5bc, 0}, /* 0xfb3b kaf+dagesh */ - {0x5dc, 0x5bc, 0}, /* 0xfb3c lamed+dagesh */ - {0xfb3d, 0, 0}, /* 0xfb3d -- */ - {0x5de, 0x5bc, 0}, /* 0xfb3e mem+dagesh */ - {0xfb3f, 0, 0}, /* 0xfb3f -- */ - {0x5e0, 0x5bc, 0}, /* 0xfb40 nun+dagesh */ - {0x5e1, 0x5bc, 0}, /* 0xfb41 samech+dagesh */ - {0xfb42, 0, 0}, /* 0xfb42 -- */ - {0x5e3, 0x5bc, 0}, /* 0xfb43 pe sofit+dagesh */ - {0x5e4, 0x5bc,0}, /* 0xfb44 pe+dagesh */ - {0xfb45, 0, 0}, /* 0xfb45 -- */ - {0x5e6, 0x5bc, 0}, /* 0xfb46 tsadi+dagesh */ - {0x5e7, 0x5bc, 0}, /* 0xfb47 qof+dagesh */ - {0x5e8, 0x5bc, 0}, /* 0xfb48 resh+dagesh */ - {0x5e9, 0x5bc, 0}, /* 0xfb49 shin+dagesh */ - {0x5ea, 0x5bc, 0}, /* 0xfb4a tav+dagesh */ - {0x5d5, 0x5b9, 0}, /* 0xfb4b vav+holam */ - {0x5d1, 0x5bf, 0}, /* 0xfb4c bet+rafe */ - {0x5db, 0x5bf, 0}, /* 0xfb4d kaf+rafe */ - {0x5e4, 0x5bf, 0}, /* 0xfb4e pe+rafe */ - {0x5d0, 0x5dc, 0} /* 0xfb4f alef-lamed */ + { 0x5e2, 0, 0 }, // 0xfb20 alt ayin + { 0x5d0, 0, 0 }, // 0xfb21 alt alef + { 0x5d3, 0, 0 }, // 0xfb22 alt dalet + { 0x5d4, 0, 0 }, // 0xfb23 alt he + { 0x5db, 0, 0 }, // 0xfb24 alt kaf + { 0x5dc, 0, 0 }, // 0xfb25 alt lamed + { 0x5dd, 0, 0 }, // 0xfb26 alt mem-sofit + { 0x5e8, 0, 0 }, // 0xfb27 alt resh + { 0x5ea, 0, 0 }, // 0xfb28 alt tav + { '+', 0, 0 }, // 0xfb29 alt plus + { 0x5e9, 0x5c1, 0 }, // 0xfb2a shin+shin-dot + { 0x5e9, 0x5c2, 0 }, // 0xfb2b shin+sin-dot + { 0x5e9, 0x5c1, 0x5bc }, // 0xfb2c shin+shin-dot+dagesh + { 0x5e9, 0x5c2, 0x5bc }, // 0xfb2d shin+sin-dot+dagesh + { 0x5d0, 0x5b7, 0 }, // 0xfb2e alef+patah + { 0x5d0, 0x5b8, 0 }, // 0xfb2f alef+qamats + { 0x5d0, 0x5b4, 0 }, // 0xfb30 alef+hiriq + { 0x5d1, 0x5bc, 0 }, // 0xfb31 bet+dagesh + { 0x5d2, 0x5bc, 0 }, // 0xfb32 gimel+dagesh + { 0x5d3, 0x5bc, 0 }, // 0xfb33 dalet+dagesh + { 0x5d4, 0x5bc, 0 }, // 0xfb34 he+dagesh + { 0x5d5, 0x5bc, 0 }, // 0xfb35 vav+dagesh + { 0x5d6, 0x5bc, 0 }, // 0xfb36 zayin+dagesh + { 0xfb37, 0, 0 }, // 0xfb37 -- UNUSED + { 0x5d8, 0x5bc, 0 }, // 0xfb38 tet+dagesh + { 0x5d9, 0x5bc, 0 }, // 0xfb39 yud+dagesh + { 0x5da, 0x5bc, 0 }, // 0xfb3a kaf sofit+dagesh + { 0x5db, 0x5bc, 0 }, // 0xfb3b kaf+dagesh + { 0x5dc, 0x5bc, 0 }, // 0xfb3c lamed+dagesh + { 0xfb3d, 0, 0 }, // 0xfb3d -- UNUSED + { 0x5de, 0x5bc, 0 }, // 0xfb3e mem+dagesh + { 0xfb3f, 0, 0 }, // 0xfb3f -- UNUSED + { 0x5e0, 0x5bc, 0 }, // 0xfb40 nun+dagesh + { 0x5e1, 0x5bc, 0 }, // 0xfb41 samech+dagesh + { 0xfb42, 0, 0 }, // 0xfb42 -- UNUSED + { 0x5e3, 0x5bc, 0 }, // 0xfb43 pe sofit+dagesh + { 0x5e4, 0x5bc, 0 }, // 0xfb44 pe+dagesh + { 0xfb45, 0, 0 }, // 0xfb45 -- UNUSED + { 0x5e6, 0x5bc, 0 }, // 0xfb46 tsadi+dagesh + { 0x5e7, 0x5bc, 0 }, // 0xfb47 qof+dagesh + { 0x5e8, 0x5bc, 0 }, // 0xfb48 resh+dagesh + { 0x5e9, 0x5bc, 0 }, // 0xfb49 shin+dagesh + { 0x5ea, 0x5bc, 0 }, // 0xfb4a tav+dagesh + { 0x5d5, 0x5b9, 0 }, // 0xfb4b vav+holam + { 0x5d1, 0x5bf, 0 }, // 0xfb4c bet+rafe + { 0x5db, 0x5bf, 0 }, // 0xfb4d kaf+rafe + { 0x5e4, 0x5bf, 0 }, // 0xfb4e pe+rafe + { 0x5d0, 0x5dc, 0 } // 0xfb4f alef-lamed }; static void mb_decompose(int c, int *c1, int *c2, int *c3) @@ -6447,12 +1442,53 @@ static int cstrncmp(char_u *s1, char_u *s2, int *n) return result; } +/// Wrapper around strchr which accounts for case-insensitive searches and +/// non-ASCII characters. +/// +/// This function is used a lot for simple searches, keep it fast! +/// +/// @param s string to search +/// @param c character to find in @a s +/// +/// @return NULL if no match, otherwise pointer to the position in @a s +static inline char_u *cstrchr(const char_u *const s, const int c) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_ALWAYS_INLINE +{ + if (!rex.reg_ic) { + return vim_strchr(s, c); + } + + // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is + // expected to be highly optimized. + if (c > 0x80) { + const int folded_c = utf_fold(c); + for (const char_u *p = s; *p != NUL; p += utfc_ptr2len(p)) { + if (utf_fold(utf_ptr2char(p)) == folded_c) { + return (char_u *)p; + } + } + return NULL; + } + + int cc; + if (ASCII_ISUPPER(c)) { + cc = TOLOWER_ASC(c); + } else if (ASCII_ISLOWER(c)) { + cc = TOUPPER_ASC(c); + } else { + return vim_strchr(s, c); + } + + char tofind[] = { (char)c, (char)cc, NUL }; + return (char_u *)strpbrk((const char *)s, tofind); +} + //////////////////////////////////////////////////////////////// // regsub stuff // //////////////////////////////////////////////////////////////// -/* This stuff below really confuses cc on an SGI -- webb */ - +// This stuff below really confuses cc on an SGI -- webb static fptr_T do_upper(int *d, int c) @@ -6495,7 +1531,7 @@ static fptr_T do_Lower(int *d, int c) * * The tildes are parsed once before the first call to vim_regsub(). */ -char_u *regtilde(char_u *source, int magic) +char_u *regtilde(char_u *source, int magic, bool preview) { char_u *newsub = source; char_u *tmpsub; @@ -6506,13 +1542,13 @@ char_u *regtilde(char_u *source, int magic) for (p = newsub; *p; ++p) { if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic)) { if (reg_prev_sub != NULL) { - /* length = len(newsub) - 1 + len(prev_sub) + 1 */ + // length = len(newsub) - 1 + len(prev_sub) + 1 prevlen = (int)STRLEN(reg_prev_sub); tmpsub = xmalloc(STRLEN(newsub) + prevlen); - /* copy prefix */ - len = (int)(p - newsub); /* not including ~ */ + // copy prefix + len = (int)(p - newsub); // not including ~ memmove(tmpsub, newsub, (size_t)len); - /* interpret tilde */ + // interpret tilde memmove(tmpsub + len, reg_prev_sub, (size_t)prevlen); // copy postfix if (!magic) { @@ -6520,15 +1556,17 @@ char_u *regtilde(char_u *source, int magic) } STRCPY(tmpsub + len + prevlen, p + 1); - if (newsub != source) /* already allocated newsub */ + if (newsub != source) { // already allocated newsub xfree(newsub); + } newsub = tmpsub; p = newsub + len + prevlen; - } else if (magic) - STRMOVE(p, p + 1); /* remove '~' */ - else - STRMOVE(p, p + 2); /* remove '\~' */ - --p; + } else if (magic) { + STRMOVE(p, p + 1); // remove '~' + } else { + STRMOVE(p, p + 2); // remove '\~' + } + p--; } else { if (*p == '\\' && p[1]) { // skip escaped characters p++; @@ -6537,11 +1575,16 @@ char_u *regtilde(char_u *source, int magic) } } - xfree(reg_prev_sub); - if (newsub != source) /* newsub was allocated, just keep it */ - reg_prev_sub = newsub; - else /* no ~ found, need to save newsub */ - reg_prev_sub = vim_strsave(newsub); + // Only change reg_prev_sub when not previewing. + if (!preview) { + xfree(reg_prev_sub); + if (newsub != source) { // newsub was allocated, just keep it + reg_prev_sub = newsub; + } else { // no ~ found, need to save newsub + reg_prev_sub = vim_strsave(newsub); + } + } + return newsub; } @@ -6642,7 +1685,8 @@ int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, return result; } -int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int copy, int magic, int backslash) +int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, + int copy, int magic, int backslash) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; @@ -6680,8 +1724,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int no = -1; fptr_T func_all = (fptr_T)NULL; fptr_T func_one = (fptr_T)NULL; - linenr_T clnum = 0; /* init for GCC */ - int len = 0; /* init for GCC */ + linenr_T clnum = 0; // init for GCC + int len = 0; // init for GCC static char_u *eval_result = NULL; // We need to keep track of how many backslashes we escape, so that the byte @@ -6793,7 +1837,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } } if (had_backslash && backslash) { - /* Backslashes will be consumed, need to double them. */ + // Backslashes will be consumed, need to double them. s = vim_strsave_escaped(eval_result, (char_u *)"\\"); xfree(eval_result); eval_result = s; @@ -6833,9 +1877,9 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } } } - if (no < 0) { /* Ordinary character. */ + if (no < 0) { // Ordinary character. if (c == K_SPECIAL && src[0] != NUL && src[1] != NUL) { - /* Copy a special key as-is. */ + // Copy a special key as-is. if (copy) { *dst++ = c; *dst++ = *src++; @@ -6963,14 +2007,15 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } else { c = utf_ptr2char(s); - if (func_one != (fptr_T)NULL) - /* Turbo C complains without the typecast */ + if (func_one != (fptr_T)NULL) { + // Turbo C complains without the typecast func_one = (fptr_T)(func_one(&cc, c)); - else if (func_all != (fptr_T)NULL) - /* Turbo C complains without the typecast */ + } else if (func_all != (fptr_T)NULL) { + // Turbo C complains without the typecast func_all = (fptr_T)(func_all(&cc, c)); - else /* just copy */ + } else { // just copy cc = c; + } { int l; @@ -7165,6 +2210,12 @@ list_T *reg_submatch_list(int no) return list; } +// XXX Do not allow headers generator to catch definitions from regexp_nfa.c +#ifndef DO_NOT_DEFINE_EMPTY_ATTRIBUTES +# include "nvim/regexp_bt.c" +# include "nvim/regexp_nfa.c" +#endif + static regengine_T bt_regengine = { bt_regcomp, @@ -7174,12 +2225,6 @@ static regengine_T bt_regengine = (char_u *)"" }; - -// XXX Do not allow headers generator to catch definitions from regexp_nfa.c -#ifndef DO_NOT_DEFINE_EMPTY_ATTRIBUTES -# include "nvim/regexp_nfa.c" -#endif - static regengine_T nfa_regengine = { nfa_regcomp, @@ -7215,7 +2260,7 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) regexp_engine = p_re; - /* Check for prefix "\%#=", that sets the regexp engine */ + // Check for prefix "\%#=", that sets the regexp engine if (STRNCMP(expr, "\\%#=", 4) == 0) { int newengine = expr[4] - '0'; @@ -7263,9 +2308,10 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) if (f) { fprintf(f, "Syntax error in \"%s\"\n", expr); fclose(f); - } else + } else { semsg("(NFA) Could not open \"%s\" to write !!!", - BT_REGEXP_DEBUG_LOG_NAME); + BT_REGEXP_DEBUG_LOG_NAME); + } } #endif // If the NFA engine failed, try the backtracking engine. The NFA engine @@ -7299,6 +2345,18 @@ void vim_regfree(regprog_T *prog) prog->engine->regfree(prog); } + +#if defined(EXITFREE) +void free_regexp_stuff(void) +{ + ga_clear(®stack); + ga_clear(&backpos); + xfree(reg_tofree); + xfree(reg_prev_sub); +} + +#endif + static void report_re_switch(char_u *pat) { if (p_verbose > 0) { @@ -7321,8 +2379,7 @@ static void report_re_switch(char_u *pat) /// @param 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) +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; @@ -7379,8 +2436,7 @@ static bool 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. -bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, - colnr_T col) +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 }; bool r = vim_regexec_string(®match, line, col, false); @@ -7412,13 +2468,12 @@ bool vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) /// match otherwise. long vim_regexec_multi( regmmatch_T *rmp, - win_T *win, // window in which to search or NULL - buf_T *buf, // buffer in which to search - linenr_T lnum, // nr of line to start looking for match - colnr_T col, // column to start looking for match - proftime_T *tm, // timeout limit or NULL - int *timed_out // flag is set when timeout limit reached -) + win_T *win, // window in which to search or NULL + buf_T *buf, // buffer in which to search + linenr_T lnum, // nr of line to start looking for match + colnr_T col, // column to start looking for match + 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; @@ -7449,7 +2504,8 @@ long vim_regexec_multi( char_u *pat = vim_strsave(((nfa_regprog_T *)rmp->regprog)->pattern); p_re = BACKTRACKING_ENGINE; - vim_regfree(rmp->regprog); + regprog_T *prev_prog = rmp->regprog; + report_re_switch(pat); // checking for \z misuse was already done when compiling for NFA, // allow all here @@ -7457,7 +2513,13 @@ long vim_regexec_multi( rmp->regprog = vim_regcomp(pat, re_flags); reg_do_extmatch = 0; - if (rmp->regprog != NULL) { + if (rmp->regprog == NULL) { + // Somehow compiling the pattern failed now, put back the + // previous one to avoid "regprog" becoming NULL. + rmp->regprog = prev_prog; + } else { + vim_regfree(prev_prog); + rmp->regprog->re_in_use = true; result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out); diff --git a/src/nvim/regexp.h b/src/nvim/regexp.h index 9527afed58..6edbcf8a31 100644 --- a/src/nvim/regexp.h +++ b/src/nvim/regexp.h @@ -19,6 +19,7 @@ // regexp.c #ifdef INCLUDE_GENERATED_DECLARATIONS # include "regexp.h.generated.h" +# include "regexp_bt.h.generated.h" #endif #endif // NVIM_REGEXP_H diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c new file mode 100644 index 0000000000..7d0b9a7a77 --- /dev/null +++ b/src/nvim/regexp_bt.c @@ -0,0 +1,5151 @@ +// 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 + +// uncrustify:off + +/* + * + * Backtracking regular expression implementation. + * + * This file is included in "regexp.c". + * + * NOTICE: + * + * This is NOT the original regular expression code as written by Henry + * Spencer. This code has been modified specifically for use with the VIM + * editor, and should not be used separately from Vim. If you want a good + * regular expression library, get the original code. The copyright notice + * that follows is from the original. + * + * END NOTICE + * + * Copyright (c) 1986 by University of Toronto. + * Written by Henry Spencer. Not derived from licensed software. + * + * Permission is granted to anyone to use this software for any + * purpose on any computer system, and to redistribute it freely, + * subject to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of + * this software, no matter how awful, even if they arise + * from defects in it. + * + * 2. The origin of this software must not be misrepresented, either + * by explicit claim or by omission. + * + * 3. Altered versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * + * Beware that some of this code is subtly aware of the way operator + * precedence is structured in regular expressions. Serious changes in + * regular-expression syntax might require a total rethink. + * + * Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert + * Webb, Ciaran McCreesh and Bram Moolenaar. + * Named character class support added by Walter Briscoe (1998 Jul 01) + */ + + +/* + * The "internal use only" fields in regexp_defs.h are present to pass info from + * compile to execute that permits the execute phase to run lots faster on + * simple cases. They are: + * + * regstart char that must begin a match; NUL if none obvious; Can be a + * multi-byte character. + * reganch is the match anchored (at beginning-of-line only)? + * regmust string (pointer into program) that match must include, or NULL + * regmlen length of regmust string + * regflags RF_ values or'ed together + * + * Regstart and reganch permit very fast decisions on suitable starting points + * for a match, cutting down the work a lot. Regmust permits fast rejection + * of lines that cannot possibly match. The regmust tests are costly enough + * that vim_regcomp() supplies a regmust only if the r.e. contains something + * potentially expensive (at present, the only such thing detected is * or + + * at the start of the r.e., which can involve a lot of backup). Regmlen is + * supplied because the test in vim_regexec() needs it and vim_regcomp() is + * computing it anyway. + */ + +/* + * Structure for regexp "program". This is essentially a linear encoding + * of a nondeterministic finite-state machine (aka syntax charts or + * "railroad normal form" in parsing technology). Each node is an opcode + * plus a "next" pointer, possibly plus an operand. "Next" pointers of + * all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next" + * pointer with a BRANCH on both ends of it is connecting two alternatives. + * (Here we have one of the subtle syntax dependencies: an individual BRANCH + * (as opposed to a collection of them) is never concatenated with anything + * because of operator precedence). The "next" pointer of a BRACES_COMPLEX + * node points to the node after the stuff to be repeated. + * The operand of some types of node is a literal string; for others, it is a + * node leading into a sub-FSM. In particular, the operand of a BRANCH node + * is the first node of the branch. + * (NB this is *not* a tree structure: the tail of the branch connects to the + * thing following the set of BRANCHes.) + * + * pattern is coded like: + * + * +-----------------+ + * | V + * <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END + * | ^ | ^ + * +------+ +----------+ + * + * + * +------------------+ + * V | + * <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END + * | | ^ ^ + * | +---------------+ | + * +---------------------------------------------+ + * + * + * +----------------------+ + * V | + * <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END + * | | ^ ^ + * | +-----------+ | + * +--------------------------------------------------+ + * + * + * +-------------------------+ + * V | + * <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END + * | | ^ + * | +----------------+ + * +-----------------------------------------------+ + * + * + * <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END + * | | ^ ^ + * | +----------------+ | + * +--------------------------------+ + * + * +---------+ + * | V + * \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END + * | | | | ^ ^ + * | | | +-----+ | + * | | +----------------+ | + * | +---------------------------+ | + * +------------------------------------------------------+ + * + * They all start with a BRANCH for "\|" alternatives, even when there is only + * one alternative. + */ + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include "nvim/regexp.h" +#include "nvim/garray.h" + +/* + * The opcodes are: + */ + +// definition number opnd? meaning +#define END 0 // End of program or NOMATCH operand. +#define BOL 1 // Match "" at beginning of line. +#define EOL 2 // Match "" at end of line. +#define BRANCH 3 // node Match this alternative, or the + // next... +#define BACK 4 // Match "", "next" ptr points backward. +#define EXACTLY 5 // str Match this string. +#define NOTHING 6 // Match empty string. +#define STAR 7 // node Match this (simple) thing 0 or more + // times. +#define PLUS 8 // node Match this (simple) thing 1 or more + // times. +#define MATCH 9 // node match the operand zero-width +#define NOMATCH 10 // node check for no match with operand +#define BEHIND 11 // node look behind for a match with operand +#define NOBEHIND 12 // node look behind for no match with operand +#define SUBPAT 13 // node match the operand here +#define BRACE_SIMPLE 14 // node Match this (simple) thing between m and + // n times (\{m,n\}). +#define BOW 15 // Match "" after [^a-zA-Z0-9_] +#define EOW 16 // Match "" at [^a-zA-Z0-9_] +#define BRACE_LIMITS 17 // nr nr define the min & max for BRACE_SIMPLE + // and BRACE_COMPLEX. +#define NEWL 18 // Match line-break +#define BHPOS 19 // End position for BEHIND or NOBEHIND + + +// character classes: 20-48 normal, 50-78 include a line-break +#define ADD_NL 30 +#define FIRST_NL ANY + ADD_NL +#define ANY 20 // Match any one character. +#define ANYOF 21 // str Match any character in this string. +#define ANYBUT 22 // str Match any character not in this + // string. +#define IDENT 23 // Match identifier char +#define SIDENT 24 // Match identifier char but no digit +#define KWORD 25 // Match keyword char +#define SKWORD 26 // Match word char but no digit +#define FNAME 27 // Match file name char +#define SFNAME 28 // Match file name char but no digit +#define PRINT 29 // Match printable char +#define SPRINT 30 // Match printable char but no digit +#define WHITE 31 // Match whitespace char +#define NWHITE 32 // Match non-whitespace char +#define DIGIT 33 // Match digit char +#define NDIGIT 34 // Match non-digit char +#define HEX 35 // Match hex char +#define NHEX 36 // Match non-hex char +#define OCTAL 37 // Match octal char +#define NOCTAL 38 // Match non-octal char +#define WORD 39 // Match word char +#define NWORD 40 // Match non-word char +#define HEAD 41 // Match head char +#define NHEAD 42 // Match non-head char +#define ALPHA 43 // Match alpha char +#define NALPHA 44 // Match non-alpha char +#define LOWER 45 // Match lowercase char +#define NLOWER 46 // Match non-lowercase char +#define UPPER 47 // Match uppercase char +#define NUPPER 48 // Match non-uppercase char +#define LAST_NL NUPPER + ADD_NL +// -V:WITH_NL:560 +#define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL) + +#define MOPEN 80 // -89 Mark this point in input as start of + // \( … \) subexpr. MOPEN + 0 marks start of + // match. +#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks + // end of match. +#define BACKREF 100 // -109 node Match same string again \1-\9. + +# define ZOPEN 110 // -119 Mark this point in input as start of + // \z( … \) subexpr. +# define ZCLOSE 120 // -129 Analogous to ZOPEN. +# define ZREF 130 // -139 node Match external submatch \z1-\z9 + +#define BRACE_COMPLEX 140 // -149 node Match nodes between m & n times + +#define NOPEN 150 // Mark this point in input as start of + // \%( subexpr. +#define NCLOSE 151 // Analogous to NOPEN. + +#define MULTIBYTECODE 200 // mbc Match one multi-byte character +#define RE_BOF 201 // Match "" at beginning of file. +#define RE_EOF 202 // Match "" at end of file. +#define CURSOR 203 // Match location of cursor. + +#define RE_LNUM 204 // nr cmp Match line number +#define RE_COL 205 // nr cmp Match column number +#define RE_VCOL 206 // nr cmp Match virtual column number + +#define RE_MARK 207 // mark cmp Match mark position +#define RE_VISUAL 208 // Match Visual area +#define RE_COMPOSING 209 // any composing characters + +/* + * Flags to be passed up and down. + */ +#define HASWIDTH 0x1 // Known never to match null string. +#define SIMPLE 0x2 // Simple enough to be STAR/PLUS operand. +#define SPSTART 0x4 // Starts with * or +. +#define HASNL 0x8 // Contains some \n. +#define HASLOOKBH 0x10 // Contains "\@<=" or "\@<!". +#define WORST 0 // Worst case. + + +static int prevchr_len; ///< byte length of previous char +static int num_complex_braces; ///< Complex \{...} count +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 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 one_exactly = false; ///< only do one char for EXACTLY + +// When making changes to classchars also change nfa_classcodes. +static char_u *classchars = (char_u *)".iIkKfFpPsSdDxXoOwWhHaAlLuU"; +static int classcodes[] = { + ANY, IDENT, SIDENT, KWORD, SKWORD, + FNAME, SFNAME, PRINT, SPRINT, + WHITE, NWHITE, DIGIT, NDIGIT, + HEX, NHEX, OCTAL, NOCTAL, + WORD, NWORD, HEAD, NHEAD, + ALPHA, NALPHA, LOWER, NLOWER, + UPPER, NUPPER +}; + +/* + * When regcode is set to this value, code is not emitted and size is computed + * instead. + */ +#define JUST_CALC_SIZE ((char_u *) -1) + +// Values for rs_state in regitem_T. +typedef enum regstate_E { + RS_NOPEN = 0, // NOPEN and NCLOSE + RS_MOPEN, // MOPEN + [0-9] + RS_MCLOSE, // MCLOSE + [0-9] + RS_ZOPEN, // ZOPEN + [0-9] + RS_ZCLOSE, // ZCLOSE + [0-9] + RS_BRANCH, // BRANCH + RS_BRCPLX_MORE, // BRACE_COMPLEX and trying one more match + RS_BRCPLX_LONG, // BRACE_COMPLEX and trying longest match + RS_BRCPLX_SHORT, // BRACE_COMPLEX and trying shortest match + RS_NOMATCH, // NOMATCH + RS_BEHIND1, // BEHIND / NOBEHIND matching rest + RS_BEHIND2, // BEHIND / NOBEHIND matching behind part + RS_STAR_LONG, // STAR/PLUS/BRACE_SIMPLE longest match + RS_STAR_SHORT // STAR/PLUS/BRACE_SIMPLE shortest match +} regstate_T; + +/* + * Structure used to save the current input state, when it needs to be + * restored after trying a match. Used by reg_save() and reg_restore(). + * Also stores the length of "backpos". + */ +typedef struct +{ + union + { + 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; + +// struct to save start/end pointer/position in for \(\) +typedef struct +{ + union + { + char_u *ptr; + lpos_T pos; + } se_u; +} save_se_T; + +// used for BEHIND and NOBEHIND matching +typedef struct regbehind_S +{ + regsave_T save_after; + regsave_T save_behind; + int save_need_clear_subexpr; + save_se_T save_start[NSUBEXP]; + save_se_T save_end[NSUBEXP]; +} regbehind_T; + +/* + * When there are alternatives a regstate_T is put on the regstack to remember + * what we are doing. + * Before it may be another type of item, depending on rs_state, to remember + * more things. + */ +typedef struct regitem_S +{ + regstate_T rs_state; // what we are doing, one of RS_ above + short rs_no; // submatch nr or BEHIND/NOBEHIND + char_u *rs_scan; // current node in program + union + { + save_se_T sesave; + regsave_T regsave; + } rs_un; // room for saving rex.input +} regitem_T; + + +// used for STAR, PLUS and BRACE_SIMPLE matching +typedef struct regstar_S +{ + int nextb; // next byte + int nextb_ic; // next byte reverse case + long count; + long minval; + long maxval; +} regstar_T; + +// used to store input position when a BACK was encountered, so that we now if +// we made any progress since the last time. +typedef struct backpos_S +{ + char_u *bp_scan; // "scan" where BACK was encountered + regsave_T bp_pos; // last input position +} backpos_T; + +/* + * "regstack" and "backpos" are used by regmatch(). They are kept over calls + * to avoid invoking malloc() and free() often. + * "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T + * or regbehind_T. + * "backpos_T" is a table with backpos_T for BACK + */ +static garray_T regstack = GA_EMPTY_INIT_VALUE; +static garray_T backpos = GA_EMPTY_INIT_VALUE; + +static regsave_T behind_pos; + +/* + * Both for regstack and backpos tables we use the following strategy of + * allocation (to reduce malloc/free calls): + * - Initial size is fairly small. + * - When needed, the tables are grown bigger (8 times at first, double after + * that). + * - After executing the match we free the memory only if the array has grown. + * Thus the memory is kept allocated when it's at the initial size. + * This makes it fast while not keeping a lot of memory allocated. + * A three times speed increase was observed when using many simple patterns. + */ +#define REGSTACK_INITIAL 2048 +#define BACKPOS_INITIAL 64 + +/* + * Opcode notes: + * + * BRANCH The set of branches constituting a single choice are hooked + * together with their "next" pointers, since precedence prevents + * anything being concatenated to any individual branch. The + * "next" pointer of the last BRANCH in a choice points to the + * thing following the whole choice. This is also where the + * final "next" pointer of each individual branch points; each + * branch starts with the operand node of a BRANCH node. + * + * BACK Normal "next" pointers all implicitly point forward; BACK + * exists to make loop structures possible. + * + * STAR,PLUS '=', and complex '*' and '+', are implemented as circular + * BRANCH structures using BACK. Simple cases (one character + * per match) are implemented with STAR and PLUS for speed + * and to minimize recursive plunges. + * + * BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX + * node, and defines the min and max limits to be used for that + * node. + * + * MOPEN,MCLOSE ...are numbered at compile time. + * ZOPEN,ZCLOSE ...ditto + */ + +/* + * A node is one char of opcode followed by two chars of "next" pointer. + * "Next" pointers are stored as two 8-bit bytes, high order first. The + * value is a positive offset from the opcode of the node containing it. + * An operand, if any, simply follows the node. (Note that much of the + * code generation knows about this implicit relationship.) + * + * Using two bytes for the "next" pointer is vast overkill for most things, + * but allows patterns to get big without disasters. + */ +#define OP(p) ((int)(*(p))) +#define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377)) +#define OPERAND(p) ((p) + 3) +// Obtain an operand that was stored as four bytes, MSB first. +#define OPERAND_MIN(p) (((long)(p)[3] << 24) + ((long)(p)[4] << 16) \ + + ((long)(p)[5] << 8) + (long)(p)[6]) +// Obtain a second operand stored as four bytes. +#define OPERAND_MAX(p) OPERAND_MIN((p) + 4) +// Obtain a second single-byte operand stored after a four bytes operand. +#define OPERAND_CMP(p) (p)[7] + +static char_u *reg(int paren, int *flagp); + +#ifdef BT_REGEXP_DUMP +static void regdump(char_u *, bt_regprog_T *); +#endif + + +#ifdef REGEXP_DEBUG +static char_u *regprop(char_u *); + +static int regnarrate = 0; +#endif + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "regexp_bt.c.generated.h" +#endif + + +/* + * Setup to parse the regexp. Used once to get the length and once to do it. + */ +static void regcomp_start( + char_u *expr, + int re_flags) // see vim_regcomp() +{ + initchr(expr); + if (re_flags & RE_MAGIC) + reg_magic = MAGIC_ON; + else + reg_magic = MAGIC_OFF; + reg_string = (re_flags & RE_STRING); + reg_strict = (re_flags & RE_STRICT); + get_cpo_flags(); + + num_complex_braces = 0; + regnpar = 1; + memset(had_endbrace, 0, sizeof(had_endbrace)); + regnzpar = 1; + re_has_z = 0; + regsize = 0L; + reg_toolong = false; + regflags = 0; + had_eol = false; +} + +// Return true if MULTIBYTECODE should be used instead of EXACTLY for +// character "c". +static bool use_multibytecode(int c) +{ + return utf_char2len(c) > 1 + && (re_multi_type(peekchr()) != NOT_MULTI + || utf_iscomposing(c)); +} + + +/* + * Emit (if appropriate) a byte of code + */ +static void regc(int b) +{ + if (regcode == JUST_CALC_SIZE) + regsize++; + else + *regcode++ = b; +} + +/* + * Emit (if appropriate) a multi-byte character of code + */ +static void regmbc(int c) +{ + if (regcode == JUST_CALC_SIZE) { + regsize += utf_char2len(c); + } else { + regcode += utf_char2bytes(c, regcode); + } +} + + +/* + * Produce the bytes for equivalence class "c". + * Currently only handles latin1, latin9 and utf-8. + * NOTE: When changing this function, also change nfa_emit_equi_class() + */ +static void reg_equi_class(int c) +{ + { + switch (c) { + // Do not use '\300' style, it results in a negative number. + case 'A': case 0xc0: case 0xc1: case 0xc2: case 0xc3: case 0xc4: + case 0xc5: case 0x100: case 0x102: case 0x104: case 0x1cd: + case 0x1de: case 0x1e0: case 0x1fa: case 0x202: case 0x226: + case 0x23a: case 0x1e00: case 0x1ea0: case 0x1ea2: case 0x1ea4: + case 0x1ea6: case 0x1ea8: case 0x1eaa: case 0x1eac: case 0x1eae: + case 0x1eb0: case 0x1eb2: case 0x1eb4: case 0x1eb6: + regmbc('A'); regmbc(0xc0); regmbc(0xc1); regmbc(0xc2); + regmbc(0xc3); regmbc(0xc4); regmbc(0xc5); + regmbc(0x100); regmbc(0x102); regmbc(0x104); + regmbc(0x1cd); regmbc(0x1de); regmbc(0x1e0); + regmbc(0x1fa); regmbc(0x202); regmbc(0x226); + regmbc(0x23a); regmbc(0x1e00); regmbc(0x1ea0); + regmbc(0x1ea2); regmbc(0x1ea4); regmbc(0x1ea6); + regmbc(0x1ea8); regmbc(0x1eaa); regmbc(0x1eac); + regmbc(0x1eae); regmbc(0x1eb0); regmbc(0x1eb2); + regmbc(0x1eb4); regmbc(0x1eb6); + return; + case 'B': case 0x181: case 0x243: case 0x1e02: + case 0x1e04: case 0x1e06: + regmbc('B'); + regmbc(0x181); regmbc(0x243); regmbc(0x1e02); + regmbc(0x1e04); regmbc(0x1e06); + return; + case 'C': case 0xc7: + case 0x106: case 0x108: case 0x10a: case 0x10c: case 0x187: + case 0x23b: case 0x1e08: case 0xa792: + regmbc('C'); regmbc(0xc7); + regmbc(0x106); regmbc(0x108); regmbc(0x10a); + regmbc(0x10c); regmbc(0x187); regmbc(0x23b); + regmbc(0x1e08); regmbc(0xa792); + return; + case 'D': case 0x10e: case 0x110: case 0x18a: + case 0x1e0a: case 0x1e0c: case 0x1e0e: case 0x1e10: + case 0x1e12: + regmbc('D'); regmbc(0x10e); regmbc(0x110); + regmbc(0x18a); regmbc(0x1e0a); regmbc(0x1e0c); + regmbc(0x1e0e); regmbc(0x1e10); regmbc(0x1e12); + return; + case 'E': case 0xc8: case 0xc9: case 0xca: case 0xcb: + case 0x112: case 0x114: case 0x116: case 0x118: case 0x11a: + case 0x204: case 0x206: case 0x228: case 0x246: case 0x1e14: + case 0x1e16: case 0x1e18: case 0x1e1a: case 0x1e1c: + case 0x1eb8: case 0x1eba: case 0x1ebc: case 0x1ebe: + case 0x1ec0: case 0x1ec2: case 0x1ec4: case 0x1ec6: + regmbc('E'); regmbc(0xc8); regmbc(0xc9); + regmbc(0xca); regmbc(0xcb); regmbc(0x112); + regmbc(0x114); regmbc(0x116); regmbc(0x118); + regmbc(0x11a); regmbc(0x204); regmbc(0x206); + regmbc(0x228); regmbc(0x246); regmbc(0x1e14); + regmbc(0x1e16); regmbc(0x1e18); regmbc(0x1e1a); + regmbc(0x1e1c); regmbc(0x1eb8); regmbc(0x1eba); + regmbc(0x1ebc); regmbc(0x1ebe); regmbc(0x1ec0); + regmbc(0x1ec2); regmbc(0x1ec4); regmbc(0x1ec6); + return; + case 'F': case 0x191: case 0x1e1e: case 0xa798: + regmbc('F'); regmbc(0x191); regmbc(0x1e1e); + regmbc(0xa798); + return; + case 'G': case 0x11c: case 0x11e: case 0x120: + case 0x122: case 0x193: case 0x1e4: case 0x1e6: + case 0x1f4: case 0x1e20: case 0xa7a0: + regmbc('G'); regmbc(0x11c); regmbc(0x11e); + regmbc(0x120); regmbc(0x122); regmbc(0x193); + regmbc(0x1e4); regmbc(0x1e6); regmbc(0x1f4); + regmbc(0x1e20); regmbc(0xa7a0); + return; + case 'H': case 0x124: case 0x126: case 0x21e: + case 0x1e22: case 0x1e24: case 0x1e26: + case 0x1e28: case 0x1e2a: case 0x2c67: + regmbc('H'); regmbc(0x124); regmbc(0x126); + regmbc(0x21e); regmbc(0x1e22); regmbc(0x1e24); + regmbc(0x1e26); regmbc(0x1e28); regmbc(0x1e2a); + regmbc(0x2c67); + return; + case 'I': case 0xcc: case 0xcd: case 0xce: case 0xcf: + case 0x128: case 0x12a: case 0x12c: case 0x12e: + case 0x130: case 0x197: case 0x1cf: case 0x208: + case 0x20a: case 0x1e2c: case 0x1e2e: case 0x1ec8: + case 0x1eca: + regmbc('I'); regmbc(0xcc); regmbc(0xcd); + regmbc(0xce); regmbc(0xcf); regmbc(0x128); + regmbc(0x12a); regmbc(0x12c); regmbc(0x12e); + regmbc(0x130); regmbc(0x197); regmbc(0x1cf); + regmbc(0x208); regmbc(0x20a); regmbc(0x1e2c); + regmbc(0x1e2e); regmbc(0x1ec8); regmbc(0x1eca); + return; + case 'J': case 0x134: case 0x248: + regmbc('J'); regmbc(0x134); regmbc(0x248); + return; + case 'K': case 0x136: case 0x198: case 0x1e8: case 0x1e30: + case 0x1e32: case 0x1e34: case 0x2c69: case 0xa740: + regmbc('K'); regmbc(0x136); regmbc(0x198); + regmbc(0x1e8); regmbc(0x1e30); regmbc(0x1e32); + regmbc(0x1e34); regmbc(0x2c69); regmbc(0xa740); + return; + case 'L': case 0x139: case 0x13b: case 0x13d: case 0x13f: + case 0x141: case 0x23d: case 0x1e36: case 0x1e38: + case 0x1e3a: case 0x1e3c: case 0x2c60: + regmbc('L'); regmbc(0x139); regmbc(0x13b); + regmbc(0x13d); regmbc(0x13f); regmbc(0x141); + regmbc(0x23d); regmbc(0x1e36); regmbc(0x1e38); + regmbc(0x1e3a); regmbc(0x1e3c); regmbc(0x2c60); + return; + case 'M': case 0x1e3e: case 0x1e40: case 0x1e42: + regmbc('M'); regmbc(0x1e3e); regmbc(0x1e40); + regmbc(0x1e42); + return; + case 'N': case 0xd1: + case 0x143: case 0x145: case 0x147: case 0x1f8: + case 0x1e44: case 0x1e46: case 0x1e48: case 0x1e4a: + case 0xa7a4: + regmbc('N'); regmbc(0xd1); + regmbc(0x143); regmbc(0x145); regmbc(0x147); + regmbc(0x1f8); regmbc(0x1e44); regmbc(0x1e46); + regmbc(0x1e48); regmbc(0x1e4a); regmbc(0xa7a4); + return; + case 'O': case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: + case 0xd8: case 0x14c: case 0x14e: case 0x150: case 0x19f: + case 0x1a0: case 0x1d1: case 0x1ea: case 0x1ec: case 0x1fe: + case 0x20c: case 0x20e: case 0x22a: case 0x22c: case 0x22e: + case 0x230: case 0x1e4c: case 0x1e4e: case 0x1e50: case 0x1e52: + case 0x1ecc: case 0x1ece: case 0x1ed0: case 0x1ed2: case 0x1ed4: + case 0x1ed6: case 0x1ed8: case 0x1eda: case 0x1edc: case 0x1ede: + case 0x1ee0: case 0x1ee2: + regmbc('O'); regmbc(0xd2); regmbc(0xd3); regmbc(0xd4); + regmbc(0xd5); regmbc(0xd6); regmbc(0xd8); + regmbc(0x14c); regmbc(0x14e); regmbc(0x150); + regmbc(0x19f); regmbc(0x1a0); regmbc(0x1d1); + regmbc(0x1ea); regmbc(0x1ec); regmbc(0x1fe); + regmbc(0x20c); regmbc(0x20e); regmbc(0x22a); + regmbc(0x22c); regmbc(0x22e); regmbc(0x230); + regmbc(0x1e4c); regmbc(0x1e4e); regmbc(0x1e50); + regmbc(0x1e52); regmbc(0x1ecc); regmbc(0x1ece); + regmbc(0x1ed0); regmbc(0x1ed2); regmbc(0x1ed4); + regmbc(0x1ed6); regmbc(0x1ed8); regmbc(0x1eda); + regmbc(0x1edc); regmbc(0x1ede); regmbc(0x1ee0); + regmbc(0x1ee2); + return; + case 'P': case 0x1a4: case 0x1e54: case 0x1e56: case 0x2c63: + regmbc('P'); regmbc(0x1a4); regmbc(0x1e54); + regmbc(0x1e56); regmbc(0x2c63); + return; + case 'Q': case 0x24a: + regmbc('Q'); regmbc(0x24a); + return; + case 'R': case 0x154: case 0x156: case 0x158: case 0x210: + case 0x212: case 0x24c: case 0x1e58: case 0x1e5a: + case 0x1e5c: case 0x1e5e: case 0x2c64: case 0xa7a6: + regmbc('R'); regmbc(0x154); regmbc(0x156); + regmbc(0x210); regmbc(0x212); regmbc(0x158); + regmbc(0x24c); regmbc(0x1e58); regmbc(0x1e5a); + regmbc(0x1e5c); regmbc(0x1e5e); regmbc(0x2c64); + regmbc(0xa7a6); + return; + case 'S': case 0x15a: case 0x15c: case 0x15e: case 0x160: + case 0x218: case 0x1e60: case 0x1e62: case 0x1e64: + case 0x1e66: case 0x1e68: case 0x2c7e: case 0xa7a8: + regmbc('S'); regmbc(0x15a); regmbc(0x15c); + regmbc(0x15e); regmbc(0x160); regmbc(0x218); + regmbc(0x1e60); regmbc(0x1e62); regmbc(0x1e64); + regmbc(0x1e66); regmbc(0x1e68); regmbc(0x2c7e); + regmbc(0xa7a8); + return; + case 'T': case 0x162: case 0x164: case 0x166: case 0x1ac: + case 0x1ae: case 0x21a: case 0x23e: case 0x1e6a: case 0x1e6c: + case 0x1e6e: case 0x1e70: + regmbc('T'); regmbc(0x162); regmbc(0x164); + regmbc(0x166); regmbc(0x1ac); regmbc(0x23e); + regmbc(0x1ae); regmbc(0x21a); regmbc(0x1e6a); + regmbc(0x1e6c); regmbc(0x1e6e); regmbc(0x1e70); + return; + case 'U': case 0xd9: case 0xda: case 0xdb: case 0xdc: + case 0x168: case 0x16a: case 0x16c: case 0x16e: + case 0x170: case 0x172: case 0x1af: case 0x1d3: + case 0x1d5: case 0x1d7: case 0x1d9: case 0x1db: + case 0x214: case 0x216: case 0x244: case 0x1e72: + case 0x1e74: case 0x1e76: case 0x1e78: case 0x1e7a: + case 0x1ee4: case 0x1ee6: case 0x1ee8: case 0x1eea: + case 0x1eec: case 0x1eee: case 0x1ef0: + regmbc('U'); regmbc(0xd9); regmbc(0xda); + regmbc(0xdb); regmbc(0xdc); regmbc(0x168); + regmbc(0x16a); regmbc(0x16c); regmbc(0x16e); + regmbc(0x170); regmbc(0x172); regmbc(0x1af); + regmbc(0x1d3); regmbc(0x1d5); regmbc(0x1d7); + regmbc(0x1d9); regmbc(0x1db); regmbc(0x214); + regmbc(0x216); regmbc(0x244); regmbc(0x1e72); + regmbc(0x1e74); regmbc(0x1e76); regmbc(0x1e78); + regmbc(0x1e7a); regmbc(0x1ee4); regmbc(0x1ee6); + regmbc(0x1ee8); regmbc(0x1eea); regmbc(0x1eec); + regmbc(0x1eee); regmbc(0x1ef0); + return; + case 'V': case 0x1b2: case 0x1e7c: case 0x1e7e: + regmbc('V'); regmbc(0x1b2); regmbc(0x1e7c); + regmbc(0x1e7e); + return; + case 'W': case 0x174: case 0x1e80: case 0x1e82: + case 0x1e84: case 0x1e86: case 0x1e88: + regmbc('W'); regmbc(0x174); regmbc(0x1e80); + regmbc(0x1e82); regmbc(0x1e84); regmbc(0x1e86); + regmbc(0x1e88); + return; + case 'X': case 0x1e8a: case 0x1e8c: + regmbc('X'); regmbc(0x1e8a); regmbc(0x1e8c); + return; + case 'Y': case 0xdd: + case 0x176: case 0x178: case 0x1b3: case 0x232: case 0x24e: + case 0x1e8e: case 0x1ef2: case 0x1ef6: case 0x1ef4: case 0x1ef8: + regmbc('Y'); regmbc(0xdd); regmbc(0x176); + regmbc(0x178); regmbc(0x1b3); regmbc(0x232); + regmbc(0x24e); regmbc(0x1e8e); regmbc(0x1ef2); + regmbc(0x1ef4); regmbc(0x1ef6); regmbc(0x1ef8); + return; + case 'Z': case 0x179: case 0x17b: case 0x17d: case 0x1b5: + case 0x1e90: case 0x1e92: case 0x1e94: case 0x2c6b: + regmbc('Z'); regmbc(0x179); regmbc(0x17b); + regmbc(0x17d); regmbc(0x1b5); regmbc(0x1e90); + regmbc(0x1e92); regmbc(0x1e94); regmbc(0x2c6b); + return; + case 'a': case 0xe0: case 0xe1: case 0xe2: + case 0xe3: case 0xe4: case 0xe5: case 0x101: case 0x103: + case 0x105: case 0x1ce: case 0x1df: case 0x1e1: case 0x1fb: + case 0x201: case 0x203: case 0x227: case 0x1d8f: case 0x1e01: + case 0x1e9a: case 0x1ea1: case 0x1ea3: case 0x1ea5: + case 0x1ea7: case 0x1ea9: case 0x1eab: case 0x1ead: + case 0x1eaf: case 0x1eb1: case 0x1eb3: case 0x1eb5: + case 0x1eb7: case 0x2c65: + regmbc('a'); regmbc(0xe0); regmbc(0xe1); + regmbc(0xe2); regmbc(0xe3); regmbc(0xe4); + regmbc(0xe5); regmbc(0x101); regmbc(0x103); + regmbc(0x105); regmbc(0x1ce); regmbc(0x1df); + regmbc(0x1e1); regmbc(0x1fb); regmbc(0x201); + regmbc(0x203); regmbc(0x227); regmbc(0x1d8f); + regmbc(0x1e01); regmbc(0x1e9a); regmbc(0x1ea1); + regmbc(0x1ea3); regmbc(0x1ea5); regmbc(0x1ea7); + regmbc(0x1ea9); regmbc(0x1eab); regmbc(0x1ead); + regmbc(0x1eaf); regmbc(0x1eb1); regmbc(0x1eb3); + regmbc(0x1eb5); regmbc(0x1eb7); regmbc(0x2c65); + return; + case 'b': case 0x180: case 0x253: case 0x1d6c: case 0x1d80: + case 0x1e03: case 0x1e05: case 0x1e07: + regmbc('b'); + regmbc(0x180); regmbc(0x253); regmbc(0x1d6c); + regmbc(0x1d80); regmbc(0x1e03); regmbc(0x1e05); + regmbc(0x1e07); + return; + case 'c': case 0xe7: + case 0x107: case 0x109: case 0x10b: case 0x10d: case 0x188: + case 0x23c: case 0x1e09: case 0xa793: case 0xa794: + regmbc('c'); regmbc(0xe7); regmbc(0x107); + regmbc(0x109); regmbc(0x10b); regmbc(0x10d); + regmbc(0x188); regmbc(0x23c); regmbc(0x1e09); + regmbc(0xa793); regmbc(0xa794); + return; + case 'd': case 0x10f: case 0x111: case 0x257: case 0x1d6d: + case 0x1d81: case 0x1d91: case 0x1e0b: case 0x1e0d: + case 0x1e0f: case 0x1e11: case 0x1e13: + regmbc('d'); regmbc(0x10f); regmbc(0x111); + regmbc(0x257); regmbc(0x1d6d); regmbc(0x1d81); + regmbc(0x1d91); regmbc(0x1e0b); regmbc(0x1e0d); + regmbc(0x1e0f); regmbc(0x1e11); regmbc(0x1e13); + return; + case 'e': case 0xe8: case 0xe9: case 0xea: case 0xeb: + case 0x113: case 0x115: case 0x117: case 0x119: + case 0x11b: case 0x205: case 0x207: case 0x229: + case 0x247: case 0x1d92: case 0x1e15: case 0x1e17: + case 0x1e19: case 0x1e1b: case 0x1eb9: case 0x1ebb: + case 0x1e1d: case 0x1ebd: case 0x1ebf: case 0x1ec1: + case 0x1ec3: case 0x1ec5: case 0x1ec7: + regmbc('e'); regmbc(0xe8); regmbc(0xe9); + regmbc(0xea); regmbc(0xeb); regmbc(0x113); + regmbc(0x115); regmbc(0x117); regmbc(0x119); + regmbc(0x11b); regmbc(0x205); regmbc(0x207); + regmbc(0x229); regmbc(0x247); regmbc(0x1d92); + regmbc(0x1e15); regmbc(0x1e17); regmbc(0x1e19); + regmbc(0x1e1b); regmbc(0x1e1d); regmbc(0x1eb9); + regmbc(0x1ebb); regmbc(0x1ebd); regmbc(0x1ebf); + regmbc(0x1ec1); regmbc(0x1ec3); regmbc(0x1ec5); + regmbc(0x1ec7); + return; + case 'f': case 0x192: case 0x1d6e: case 0x1d82: + case 0x1e1f: case 0xa799: + regmbc('f'); regmbc(0x192); regmbc(0x1d6e); + regmbc(0x1d82); regmbc(0x1e1f); regmbc(0xa799); + return; + case 'g': case 0x11d: case 0x11f: case 0x121: case 0x123: + case 0x1e5: case 0x1e7: case 0x260: case 0x1f5: case 0x1d83: + case 0x1e21: case 0xa7a1: + regmbc('g'); regmbc(0x11d); regmbc(0x11f); + regmbc(0x121); regmbc(0x123); regmbc(0x1e5); + regmbc(0x1e7); regmbc(0x1f5); regmbc(0x260); + regmbc(0x1d83); regmbc(0x1e21); regmbc(0xa7a1); + return; + case 'h': case 0x125: case 0x127: case 0x21f: case 0x1e23: + case 0x1e25: case 0x1e27: case 0x1e29: case 0x1e2b: + case 0x1e96: case 0x2c68: case 0xa795: + regmbc('h'); regmbc(0x125); regmbc(0x127); + regmbc(0x21f); regmbc(0x1e23); regmbc(0x1e25); + regmbc(0x1e27); regmbc(0x1e29); regmbc(0x1e2b); + regmbc(0x1e96); regmbc(0x2c68); regmbc(0xa795); + return; + case 'i': case 0xec: case 0xed: case 0xee: case 0xef: + case 0x129: case 0x12b: case 0x12d: case 0x12f: + case 0x1d0: case 0x209: case 0x20b: case 0x268: + case 0x1d96: case 0x1e2d: case 0x1e2f: case 0x1ec9: + case 0x1ecb: + regmbc('i'); regmbc(0xec); regmbc(0xed); + regmbc(0xee); regmbc(0xef); regmbc(0x129); + regmbc(0x12b); regmbc(0x12d); regmbc(0x12f); + regmbc(0x1d0); regmbc(0x209); regmbc(0x20b); + regmbc(0x268); regmbc(0x1d96); regmbc(0x1e2d); + regmbc(0x1e2f); regmbc(0x1ec9); regmbc(0x1ecb); + return; + case 'j': case 0x135: case 0x1f0: case 0x249: + regmbc('j'); regmbc(0x135); regmbc(0x1f0); + regmbc(0x249); + return; + case 'k': case 0x137: case 0x199: case 0x1e9: + case 0x1d84: case 0x1e31: case 0x1e33: case 0x1e35: + case 0x2c6a: case 0xa741: + regmbc('k'); regmbc(0x137); regmbc(0x199); + regmbc(0x1e9); regmbc(0x1d84); regmbc(0x1e31); + regmbc(0x1e33); regmbc(0x1e35); regmbc(0x2c6a); + regmbc(0xa741); + return; + case 'l': case 0x13a: case 0x13c: case 0x13e: + case 0x140: case 0x142: case 0x19a: case 0x1e37: + case 0x1e39: case 0x1e3b: case 0x1e3d: case 0x2c61: + regmbc('l'); regmbc(0x13a); regmbc(0x13c); + regmbc(0x13e); regmbc(0x140); regmbc(0x142); + regmbc(0x19a); regmbc(0x1e37); regmbc(0x1e39); + regmbc(0x1e3b); regmbc(0x1e3d); regmbc(0x2c61); + return; + case 'm': case 0x1d6f: case 0x1e3f: case 0x1e41: case 0x1e43: + regmbc('m'); regmbc(0x1d6f); regmbc(0x1e3f); + regmbc(0x1e41); regmbc(0x1e43); + return; + case 'n': case 0xf1: case 0x144: case 0x146: case 0x148: + case 0x149: case 0x1f9: case 0x1d70: case 0x1d87: + case 0x1e45: case 0x1e47: case 0x1e49: case 0x1e4b: + case 0xa7a5: + regmbc('n'); regmbc(0xf1); regmbc(0x144); + regmbc(0x146); regmbc(0x148); regmbc(0x149); + regmbc(0x1f9); regmbc(0x1d70); regmbc(0x1d87); + regmbc(0x1e45); regmbc(0x1e47); regmbc(0x1e49); + regmbc(0x1e4b); regmbc(0xa7a5); + return; + case 'o': case 0xf2: case 0xf3: case 0xf4: case 0xf5: + case 0xf6: case 0xf8: case 0x14d: case 0x14f: case 0x151: + case 0x1a1: case 0x1d2: case 0x1eb: case 0x1ed: case 0x1ff: + case 0x20d: case 0x20f: case 0x22b: case 0x22d: case 0x22f: + case 0x231: case 0x275: case 0x1e4d: case 0x1e4f: + case 0x1e51: case 0x1e53: case 0x1ecd: case 0x1ecf: + case 0x1ed1: case 0x1ed3: case 0x1ed5: case 0x1ed7: + case 0x1ed9: case 0x1edb: case 0x1edd: case 0x1edf: + case 0x1ee1: case 0x1ee3: + regmbc('o'); regmbc(0xf2); regmbc(0xf3); + regmbc(0xf4); regmbc(0xf5); regmbc(0xf6); + regmbc(0xf8); regmbc(0x14d); regmbc(0x14f); + regmbc(0x151); regmbc(0x1a1); regmbc(0x1d2); + regmbc(0x1eb); regmbc(0x1ed); regmbc(0x1ff); + regmbc(0x20d); regmbc(0x20f); regmbc(0x22b); + regmbc(0x22d); regmbc(0x22f); regmbc(0x231); + regmbc(0x275); regmbc(0x1e4d); regmbc(0x1e4f); + regmbc(0x1e51); regmbc(0x1e53); regmbc(0x1ecd); + regmbc(0x1ecf); regmbc(0x1ed1); regmbc(0x1ed3); + regmbc(0x1ed5); regmbc(0x1ed7); regmbc(0x1ed9); + regmbc(0x1edb); regmbc(0x1edd); regmbc(0x1edf); + regmbc(0x1ee1); regmbc(0x1ee3); + return; + case 'p': case 0x1a5: case 0x1d71: case 0x1d88: case 0x1d7d: + case 0x1e55: case 0x1e57: + regmbc('p'); regmbc(0x1a5); regmbc(0x1d71); + regmbc(0x1d7d); regmbc(0x1d88); regmbc(0x1e55); + regmbc(0x1e57); + return; + case 'q': case 0x24b: case 0x2a0: + regmbc('q'); regmbc(0x24b); regmbc(0x2a0); + return; + case 'r': case 0x155: case 0x157: case 0x159: case 0x211: + case 0x213: case 0x24d: case 0x27d: case 0x1d72: case 0x1d73: + case 0x1d89: case 0x1e59: case 0x1e5b: case 0x1e5d: case 0x1e5f: + case 0xa7a7: + regmbc('r'); regmbc(0x155); regmbc(0x157); + regmbc(0x159); regmbc(0x211); regmbc(0x213); + regmbc(0x24d); regmbc(0x1d72); regmbc(0x1d73); + regmbc(0x1d89); regmbc(0x1e59); regmbc(0x27d); + regmbc(0x1e5b); regmbc(0x1e5d); regmbc(0x1e5f); + regmbc(0xa7a7); + return; + case 's': case 0x15b: case 0x15d: case 0x15f: case 0x161: + case 0x1e61: case 0x219: case 0x23f: case 0x1d74: case 0x1d8a: + case 0x1e63: case 0x1e65: case 0x1e67: case 0x1e69: case 0xa7a9: + regmbc('s'); regmbc(0x15b); regmbc(0x15d); + regmbc(0x15f); regmbc(0x161); regmbc(0x23f); + regmbc(0x219); regmbc(0x1d74); regmbc(0x1d8a); + regmbc(0x1e61); regmbc(0x1e63); regmbc(0x1e65); + regmbc(0x1e67); regmbc(0x1e69); regmbc(0xa7a9); + return; + case 't': case 0x163: case 0x165: case 0x167: case 0x1ab: + case 0x1ad: case 0x21b: case 0x288: case 0x1d75: case 0x1e6b: + case 0x1e6d: case 0x1e6f: case 0x1e71: case 0x1e97: case 0x2c66: + regmbc('t'); regmbc(0x163); regmbc(0x165); + regmbc(0x167); regmbc(0x1ab); regmbc(0x21b); + regmbc(0x1ad); regmbc(0x288); regmbc(0x1d75); + regmbc(0x1e6b); regmbc(0x1e6d); regmbc(0x1e6f); + regmbc(0x1e71); regmbc(0x1e97); regmbc(0x2c66); + return; + case 'u': case 0xf9: case 0xfa: case 0xfb: case 0xfc: + case 0x169: case 0x16b: case 0x16d: case 0x16f: + case 0x171: case 0x173: case 0x1b0: case 0x1d4: + case 0x1d6: case 0x1d8: case 0x1da: case 0x1dc: + case 0x215: case 0x217: case 0x289: case 0x1e73: + case 0x1d7e: case 0x1d99: case 0x1e75: case 0x1e77: + case 0x1e79: case 0x1e7b: case 0x1ee5: case 0x1ee7: + case 0x1ee9: case 0x1eeb: case 0x1eed: case 0x1eef: + case 0x1ef1: + regmbc('u'); regmbc(0xf9); regmbc(0xfa); + regmbc(0xfb); regmbc(0xfc); regmbc(0x169); + regmbc(0x16b); regmbc(0x16d); regmbc(0x16f); + regmbc(0x171); regmbc(0x173); regmbc(0x1d6); + regmbc(0x1d8); regmbc(0x1da); regmbc(0x1dc); + regmbc(0x215); regmbc(0x217); regmbc(0x1b0); + regmbc(0x1d4); regmbc(0x289); regmbc(0x1d7e); + regmbc(0x1d99); regmbc(0x1e73); regmbc(0x1e75); + regmbc(0x1e77); regmbc(0x1e79); regmbc(0x1e7b); + regmbc(0x1ee5); regmbc(0x1ee7); regmbc(0x1ee9); + regmbc(0x1eeb); regmbc(0x1eed); regmbc(0x1eef); + regmbc(0x1ef1); + return; + case 'v': case 0x28b: case 0x1d8c: case 0x1e7d: case 0x1e7f: + regmbc('v'); regmbc(0x28b); regmbc(0x1d8c); + regmbc(0x1e7d); regmbc(0x1e7f); + return; + case 'w': case 0x175: case 0x1e81: case 0x1e83: + case 0x1e85: case 0x1e87: case 0x1e89: case 0x1e98: + regmbc('w'); regmbc(0x175); regmbc(0x1e81); + regmbc(0x1e83); regmbc(0x1e85); regmbc(0x1e87); + regmbc(0x1e89); regmbc(0x1e98); + return; + case 'x': case 0x1e8b: case 0x1e8d: + regmbc('x'); regmbc(0x1e8b); regmbc(0x1e8d); + return; + case 'y': case 0xfd: case 0xff: case 0x177: case 0x1b4: + case 0x233: case 0x24f: case 0x1e8f: case 0x1e99: case 0x1ef3: + case 0x1ef5: case 0x1ef7: case 0x1ef9: + regmbc('y'); regmbc(0xfd); regmbc(0xff); + regmbc(0x177); regmbc(0x1b4); regmbc(0x233); + regmbc(0x24f); regmbc(0x1e8f); regmbc(0x1e99); + regmbc(0x1ef3); regmbc(0x1ef5); regmbc(0x1ef7); + regmbc(0x1ef9); + return; + case 'z': case 0x17a: case 0x17c: case 0x17e: case 0x1b6: + case 0x1d76: case 0x1d8e: case 0x1e91: case 0x1e93: + case 0x1e95: case 0x2c6c: + regmbc('z'); regmbc(0x17a); regmbc(0x17c); + regmbc(0x17e); regmbc(0x1b6); regmbc(0x1d76); + regmbc(0x1d8e); regmbc(0x1e91); regmbc(0x1e93); + regmbc(0x1e95); regmbc(0x2c6c); + return; + } + } + regmbc(c); +} + + + +/* + * Emit a node. + * Return pointer to generated code. + */ +static char_u *regnode(int op) +{ + char_u *ret; + + ret = regcode; + if (ret == JUST_CALC_SIZE) + regsize += 3; + else { + *regcode++ = op; + *regcode++ = NUL; // Null "next" pointer. + *regcode++ = NUL; + } + return ret; +} + +/* + * Write a four bytes number at "p" and return pointer to the next char. + */ +static char_u *re_put_uint32(char_u *p, uint32_t val) +{ + *p++ = (char_u)((val >> 24) & 0377); + *p++ = (char_u)((val >> 16) & 0377); + *p++ = (char_u)((val >> 8) & 0377); + *p++ = (char_u)(val & 0377); + return p; +} + +/* + * regnext - dig the "next" pointer out of a node + * Returns NULL when calculating size, when there is no next item and when + * there is an error. + */ +static char_u *regnext(char_u *p) + FUNC_ATTR_NONNULL_ALL +{ + int offset; + + if (p == JUST_CALC_SIZE || reg_toolong) + return NULL; + + offset = NEXT(p); + if (offset == 0) + return NULL; + + if (OP(p) == BACK) + return p - offset; + else + return p + offset; +} + +// Set the next-pointer at the end of a node chain. +static void regtail(char_u *p, char_u *val) +{ + int offset; + + if (p == JUST_CALC_SIZE) { + return; + } + + // Find last node. + char_u *scan = p; + for (;; ) { + char_u *temp = regnext(scan); + if (temp == NULL) { + break; + } + scan = temp; + } + + if (OP(scan) == BACK) { + offset = (int)(scan - val); + } 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); + } +} + +/* + * Like regtail, on item after a BRANCH; nop if none. + */ +static void regoptail(char_u *p, char_u *val) +{ + // When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless" + if (p == NULL || p == JUST_CALC_SIZE + || (OP(p) != BRANCH + && (OP(p) < BRACE_COMPLEX || OP(p) > BRACE_COMPLEX + 9))) + return; + regtail(OPERAND(p), val); +} + + +/* + * Insert an operator in front of already-emitted operand + * + * Means relocating the operand. + */ +static void reginsert(int op, char_u *opnd) +{ + char_u *src; + char_u *dst; + char_u *place; + + if (regcode == JUST_CALC_SIZE) { + regsize += 3; + return; + } + src = regcode; + regcode += 3; + dst = regcode; + while (src > opnd) + *--dst = *--src; + + place = opnd; // Op node, where operand used to be. + *place++ = op; + *place++ = NUL; + *place = NUL; +} + +/* + * Insert an operator in front of already-emitted operand. + * Add a number to the operator. + */ +static void reginsert_nr(int op, long val, char_u *opnd) +{ + char_u *src; + char_u *dst; + char_u *place; + + if (regcode == JUST_CALC_SIZE) { + regsize += 7; + return; + } + src = regcode; + regcode += 7; + dst = regcode; + while (src > opnd) + *--dst = *--src; + + place = opnd; // Op node, where operand used to be. + *place++ = op; + *place++ = NUL; + *place++ = NUL; + assert(val >= 0 && (uintmax_t)val <= UINT32_MAX); + re_put_uint32(place, (uint32_t)val); +} + +/* + * Insert an operator in front of already-emitted operand. + * The operator has the given limit values as operands. Also set next pointer. + * + * Means relocating the operand. + */ +static void reginsert_limits(int op, long minval, long maxval, char_u *opnd) +{ + char_u *src; + char_u *dst; + char_u *place; + + if (regcode == JUST_CALC_SIZE) { + regsize += 11; + return; + } + src = regcode; + regcode += 11; + dst = regcode; + while (src > opnd) + *--dst = *--src; + + place = opnd; // Op node, where operand used to be. + *place++ = op; + *place++ = NUL; + *place++ = NUL; + assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX); + place = re_put_uint32(place, (uint32_t)minval); + assert(maxval >= 0 && (uintmax_t)maxval <= UINT32_MAX); + place = re_put_uint32(place, (uint32_t)maxval); + regtail(opnd, place); +} + +/// 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? +static int seen_endbrace(int refnum) +{ + if (!had_endbrace[refnum]) { + char_u *p; + + // Trick: check if "@<=" or "@<!" follows, in which case + // the \1 can appear before the referenced match. + for (p = regparse; *p != NUL; p++) { + if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) { + break; + } + } + + if (*p == NUL) { + emsg(_("E65: Illegal back reference")); + rc_did_emsg = true; + return false; + } + } + return true; +} + +/* + * Parse the lowest level. + * + * Optimization: gobbles an entire sequence of ordinary characters so that + * it can turn them into a single node, which is smaller to store and + * faster to run. Don't do this when one_exactly is set. + */ +static char_u *regatom(int *flagp) +{ + char_u *ret; + int flags; + int c; + char_u *p; + int extra = 0; + int save_prev_at_start = prev_at_start; + + *flagp = WORST; // Tentatively. + + c = getchr(); + switch (c) { + case Magic('^'): + ret = regnode(BOL); + break; + + case Magic('$'): + ret = regnode(EOL); + had_eol = true; + break; + + case Magic('<'): + ret = regnode(BOW); + break; + + case Magic('>'): + ret = regnode(EOW); + break; + + case Magic('_'): + c = no_Magic(getchr()); + if (c == '^') { // "\_^" is start-of-line + ret = regnode(BOL); + break; + } + if (c == '$') { // "\_$" is end-of-line + ret = regnode(EOL); + had_eol = true; + break; + } + + extra = ADD_NL; + *flagp |= HASNL; + + // "\_[" is character range plus newline + if (c == '[') + goto collection; + + // "\_x" is character class plus newline + FALLTHROUGH; + + // Character classes. + case Magic('.'): + case Magic('i'): + case Magic('I'): + case Magic('k'): + case Magic('K'): + case Magic('f'): + case Magic('F'): + case Magic('p'): + case Magic('P'): + case Magic('s'): + case Magic('S'): + case Magic('d'): + case Magic('D'): + case Magic('x'): + case Magic('X'): + case Magic('o'): + case Magic('O'): + case Magic('w'): + case Magic('W'): + case Magic('h'): + case Magic('H'): + case Magic('a'): + case Magic('A'): + case Magic('l'): + case Magic('L'): + case Magic('u'): + case Magic('U'): + p = vim_strchr(classchars, no_Magic(c)); + if (p == NULL) + EMSG_RET_NULL(_("E63: invalid use of \\_")); + // When '.' is followed by a composing char ignore the dot, so that + // the composing char is matched here. + if (c == Magic('.') && utf_iscomposing(peekchr())) { + c = getchr(); + goto do_multibyte; + } + ret = regnode(classcodes[p - classchars] + extra); + *flagp |= HASWIDTH | SIMPLE; + break; + + case Magic('n'): + if (reg_string) { + // In a string "\n" matches a newline character. + ret = regnode(EXACTLY); + regc(NL); + regc(NUL); + *flagp |= HASWIDTH | SIMPLE; + } else { + // In buffer text "\n" matches the end of a line. + ret = regnode(NEWL); + *flagp |= HASWIDTH | HASNL; + } + break; + + case Magic('('): + if (one_exactly) + EMSG_ONE_RET_NULL; + ret = reg(REG_PAREN, &flags); + if (ret == NULL) + return NULL; + *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH); + break; + + case NUL: + case Magic('|'): + case Magic('&'): + case Magic(')'): + if (one_exactly) + EMSG_ONE_RET_NULL; + IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier. + // NOTREACHED + + case Magic('='): + case Magic('?'): + case Magic('+'): + case Magic('@'): + case Magic('{'): + case Magic('*'): + c = no_Magic(c); + EMSG3_RET_NULL(_("E64: %s%c follows nothing"), + (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL), c); + // NOTREACHED + + case Magic('~'): // previous substitute pattern + if (reg_prev_sub != NULL) { + char_u *lp; + + ret = regnode(EXACTLY); + lp = reg_prev_sub; + while (*lp != NUL) + regc(*lp++); + regc(NUL); + if (*reg_prev_sub != NUL) { + *flagp |= HASWIDTH; + if ((lp - reg_prev_sub) == 1) + *flagp |= SIMPLE; + } + } else + EMSG_RET_NULL(_(e_nopresub)); + break; + + case Magic('1'): + case Magic('2'): + case Magic('3'): + case Magic('4'): + case Magic('5'): + case Magic('6'): + case Magic('7'): + case Magic('8'): + case Magic('9'): + { + int refnum; + + refnum = c - Magic('0'); + if (!seen_endbrace(refnum)) { + return NULL; + } + ret = regnode(BACKREF + refnum); + } + break; + + case Magic('z'): + { + c = no_Magic(getchr()); + switch (c) { + case '(': if ((reg_do_extmatch & REX_SET) == 0) + EMSG_RET_NULL(_(e_z_not_allowed)); + if (one_exactly) + EMSG_ONE_RET_NULL; + ret = reg(REG_ZPAREN, &flags); + if (ret == NULL) + return NULL; + *flagp |= flags & (HASWIDTH|SPSTART|HASNL|HASLOOKBH); + re_has_z = REX_SET; + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': if ((reg_do_extmatch & REX_USE) == 0) + EMSG_RET_NULL(_(e_z1_not_allowed)); + ret = regnode(ZREF + c - '0'); + re_has_z = REX_USE; + break; + + case 's': ret = regnode(MOPEN + 0); + if (!re_mult_next("\\zs")) { + return NULL; + } + break; + + case 'e': ret = regnode(MCLOSE + 0); + if (!re_mult_next("\\ze")) { + return NULL; + } + break; + + default: EMSG_RET_NULL(_("E68: Invalid character after \\z")); + } + } + break; + + case Magic('%'): + { + c = no_Magic(getchr()); + switch (c) { + // () without a back reference + case '(': + if (one_exactly) + EMSG_ONE_RET_NULL; + ret = reg(REG_NPAREN, &flags); + if (ret == NULL) + return NULL; + *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH); + break; + + // Catch \%^ and \%$ regardless of where they appear in the + // pattern -- regardless of whether or not it makes sense. + case '^': + ret = regnode(RE_BOF); + break; + + case '$': + ret = regnode(RE_EOF); + break; + + case '#': + ret = regnode(CURSOR); + break; + + case 'V': + ret = regnode(RE_VISUAL); + break; + + case 'C': + ret = regnode(RE_COMPOSING); + break; + + // \%[abc]: Emit as a list of branches, all ending at the last + // branch which matches nothing. + case '[': + if (one_exactly) // doesn't nest + EMSG_ONE_RET_NULL; + { + char_u *lastbranch; + char_u *lastnode = NULL; + char_u *br; + + ret = NULL; + while ((c = getchr()) != ']') { + if (c == NUL) + EMSG2_RET_NULL(_(e_missing_sb), + reg_magic == MAGIC_ALL); + br = regnode(BRANCH); + if (ret == NULL) { + ret = br; + } else { + regtail(lastnode, br); + if (reg_toolong) { + return NULL; + } + } + + ungetchr(); + one_exactly = true; + lastnode = regatom(flagp); + one_exactly = false; + if (lastnode == NULL) { + return NULL; + } + } + if (ret == NULL) + EMSG2_RET_NULL(_(e_empty_sb), + reg_magic == MAGIC_ALL); + lastbranch = regnode(BRANCH); + br = regnode(NOTHING); + if (ret != JUST_CALC_SIZE) { + regtail(lastnode, br); + regtail(lastbranch, br); + // connect all branches to the NOTHING + // branch at the end + for (br = ret; br != lastnode; ) { + if (OP(br) == BRANCH) { + regtail(br, lastbranch); + if (reg_toolong) { + return NULL; + } + br = OPERAND(br); + } else + br = regnext(br); + } + } + *flagp &= ~(HASWIDTH | SIMPLE); + break; + } + + case 'd': // %d123 decimal + case 'o': // %o123 octal + case 'x': // %xab hex 2 + case 'u': // %uabcd hex 4 + case 'U': // %U1234abcd hex 8 + { + int64_t i; + + switch (c) { + case 'd': i = getdecchrs(); break; + case 'o': i = getoctchrs(); break; + case 'x': i = gethexchrs(2); break; + case 'u': i = gethexchrs(4); break; + case 'U': i = gethexchrs(8); break; + default: i = -1; break; + } + + if (i < 0 || i > INT_MAX) { + EMSG2_RET_NULL(_("E678: Invalid character after %s%%[dxouU]"), + reg_magic == MAGIC_ALL); + } + if (use_multibytecode(i)) { + ret = regnode(MULTIBYTECODE); + } else { + ret = regnode(EXACTLY); + } + if (i == 0) { + regc(0x0a); + } else { + regmbc(i); + } + regc(NUL); + *flagp |= HASWIDTH; + break; + } + + default: + if (ascii_isdigit(c) || c == '<' || c == '>' || c == '\'' || c == '.') { + uint32_t n = 0; + int cmp; + bool cur = false; + + cmp = c; + if (cmp == '<' || cmp == '>') { + c = getchr(); + } + if (no_Magic(c) == '.') { + cur = true; + c = getchr(); + } + while (ascii_isdigit(c)) { + n = n * 10 + (uint32_t)(c - '0'); + c = getchr(); + } + if (c == '\'' && n == 0) { + // "\%'m", "\%<'m" and "\%>'m": Mark + c = getchr(); + ret = regnode(RE_MARK); + if (ret == JUST_CALC_SIZE) + regsize += 2; + else { + *regcode++ = c; + *regcode++ = cmp; + } + break; + } else if (c == 'l' || c == 'c' || c == 'v') { + if (cur && n) { + semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + rc_did_emsg = true; + return NULL; + } + if (c == 'l') { + if (cur) { + n = curwin->w_cursor.lnum; + } + ret = regnode(RE_LNUM); + if (save_prev_at_start) { + at_start = true; + } + } else if (c == 'c') { + if (cur) { + n = curwin->w_cursor.col; + n++; + } + ret = regnode(RE_COL); + } else { + if (cur) { + colnr_T vcol = 0; + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); + n = ++vcol; + } + ret = regnode(RE_VCOL); + } + if (ret == JUST_CALC_SIZE) { + regsize += 5; + } else { + // put the number and the optional + // comparator after the opcode + regcode = re_put_uint32(regcode, n); + *regcode++ = cmp; + } + break; + } + } + + EMSG2_RET_NULL(_("E71: Invalid character after %s%%"), + reg_magic == MAGIC_ALL); + } + } + break; + + case Magic('['): +collection: + { + char_u *lp; + + // If there is no matching ']', we assume the '[' is a normal + // character. This makes 'incsearch' and ":help [" work. + lp = skip_anyof(regparse); + if (*lp == ']') { // there is a matching ']' + int startc = -1; // > 0 when next '-' is a range + int endc; + + // In a character class, different parsing rules apply. + // Not even \ is special anymore, nothing is. + if (*regparse == '^') { // Complement of range. + ret = regnode(ANYBUT + extra); + regparse++; + } else + ret = regnode(ANYOF + extra); + + // At the start ']' and '-' mean the literal character. + if (*regparse == ']' || *regparse == '-') { + startc = *regparse; + regc(*regparse++); + } + + while (*regparse != NUL && *regparse != ']') { + if (*regparse == '-') { + ++regparse; + // The '-' is not used for a range at the end and + // after or before a '\n'. + if (*regparse == ']' || *regparse == NUL + || startc == -1 + || (regparse[0] == '\\' && regparse[1] == 'n')) { + regc('-'); + startc = '-'; // [--x] is a range + } else { + // Also accept "a-[.z.]" + endc = 0; + if (*regparse == '[') + endc = get_coll_element(®parse); + if (endc == 0) { + endc = mb_ptr2char_adv((const char_u **)®parse); + } + + // Handle \o40, \x20 and \u20AC style sequences + if (endc == '\\' && !reg_cpo_lit) + endc = coll_get_char(); + + if (startc > endc) { + EMSG_RET_NULL(_(e_reverse_range)); + } + if (utf_char2len(startc) > 1 + || utf_char2len(endc) > 1) { + // Limit to a range of 256 chars + if (endc > startc + 256) { + EMSG_RET_NULL(_(e_large_class)); + } + while (++startc <= endc) { + regmbc(startc); + } + } else { + while (++startc <= endc) + regc(startc); + } + startc = -1; + } + } + // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim + // accepts "\t", "\e", etc., but only when the 'l' flag in + // 'cpoptions' is not included. + else if (*regparse == '\\' + && (vim_strchr(REGEXP_INRANGE, regparse[1]) != NULL + || (!reg_cpo_lit + && vim_strchr(REGEXP_ABBR, + regparse[1]) != NULL))) { + regparse++; + if (*regparse == 'n') { + // '\n' in range: also match NL + if (ret != JUST_CALC_SIZE) { + // Using \n inside [^] does not change what + // matches. "[^\n]" is the same as ".". + if (*ret == ANYOF) { + *ret = ANYOF + ADD_NL; + *flagp |= HASNL; + } + // else: must have had a \n already + } + regparse++; + startc = -1; + } else if (*regparse == 'd' + || *regparse == 'o' + || *regparse == 'x' + || *regparse == 'u' + || *regparse == 'U') { + startc = coll_get_char(); + if (startc == 0) + regc(0x0a); + else + regmbc(startc); + } else { + startc = backslash_trans(*regparse++); + regc(startc); + } + } else if (*regparse == '[') { + int c_class; + int cu; + + c_class = get_char_class(®parse); + startc = -1; + // Characters assumed to be 8 bits! + switch (c_class) { + case CLASS_NONE: + c_class = get_equi_class(®parse); + if (c_class != 0) { + // produce equivalence class + reg_equi_class(c_class); + } else if ((c_class = + get_coll_element(®parse)) != 0) { + // produce a collating element + regmbc(c_class); + } else { + // literal '[', allow [[-x] as a range + startc = *regparse++; + regc(startc); + } + break; + case CLASS_ALNUM: + for (cu = 1; cu < 128; cu++) { + if (isalnum(cu)) { + regmbc(cu); + } + } + break; + case CLASS_ALPHA: + for (cu = 1; cu < 128; cu++) { + if (isalpha(cu)) { + regmbc(cu); + } + } + break; + case CLASS_BLANK: + regc(' '); + regc('\t'); + break; + case CLASS_CNTRL: + for (cu = 1; cu <= 127; cu++) { + if (iscntrl(cu)) { + regmbc(cu); + } + } + break; + case CLASS_DIGIT: + for (cu = 1; cu <= 127; cu++) { + if (ascii_isdigit(cu)) { + regmbc(cu); + } + } + break; + case CLASS_GRAPH: + for (cu = 1; cu <= 127; cu++) { + if (isgraph(cu)) { + regmbc(cu); + } + } + break; + case CLASS_LOWER: + for (cu = 1; cu <= 255; cu++) { + if (mb_islower(cu) && cu != 170 && cu != 186) { + regmbc(cu); + } + } + break; + case CLASS_PRINT: + for (cu = 1; cu <= 255; cu++) { + if (vim_isprintc(cu)) { + regmbc(cu); + } + } + break; + case CLASS_PUNCT: + for (cu = 1; cu < 128; cu++) { + if (ispunct(cu)) { + regmbc(cu); + } + } + break; + case CLASS_SPACE: + for (cu = 9; cu <= 13; cu++) + regc(cu); + regc(' '); + break; + case CLASS_UPPER: + for (cu = 1; cu <= 255; cu++) { + if (mb_isupper(cu)) { + regmbc(cu); + } + } + break; + case CLASS_XDIGIT: + for (cu = 1; cu <= 255; cu++) { + if (ascii_isxdigit(cu)) { + regmbc(cu); + } + } + break; + case CLASS_TAB: + regc('\t'); + break; + case CLASS_RETURN: + regc('\r'); + break; + case CLASS_BACKSPACE: + regc('\b'); + break; + 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 + // following composing characters. + startc = utf_ptr2char(regparse); + int len = utfc_ptr2len(regparse); + if (utf_char2len(startc) != len) { + // composing chars + startc = -1; + } + while (--len >= 0) { + regc(*regparse++); + } + } + } + regc(NUL); + prevchr_len = 1; // last char was the ']' + if (*regparse != ']') + EMSG_RET_NULL(_(e_toomsbra)); // Cannot happen? + skipchr(); // let's be friends with the lexer again + *flagp |= HASWIDTH | SIMPLE; + break; + } else if (reg_strict) + EMSG2_RET_NULL(_(e_missingbracket), reg_magic > MAGIC_OFF); + } + FALLTHROUGH; + + default: + { + int len; + + // A multi-byte character is handled as a separate atom if it's + // before a multi and when it's a composing char. + if (use_multibytecode(c)) { +do_multibyte: + ret = regnode(MULTIBYTECODE); + regmbc(c); + *flagp |= HASWIDTH | SIMPLE; + break; + } + + ret = regnode(EXACTLY); + + // Append characters as long as: + // - there is no following multi, we then need the character in + // front of it as a single character operand + // - not running into a Magic character + // - "one_exactly" is not set + // But always emit at least one character. Might be a Multi, + // e.g., a "[" without matching "]". + for (len = 0; c != NUL && (len == 0 + || (re_multi_type(peekchr()) == NOT_MULTI + && !one_exactly + && !is_Magic(c))); ++len) { + c = no_Magic(c); + { + regmbc(c); + { + int l; + + // Need to get composing character too. + for (;; ) { + l = utf_ptr2len(regparse); + if (!utf_composinglike(regparse, regparse + l)) { + break; + } + regmbc(utf_ptr2char(regparse)); + skipchr(); + } + } + } + c = getchr(); + } + ungetchr(); + + regc(NUL); + *flagp |= HASWIDTH; + if (len == 1) + *flagp |= SIMPLE; + } + break; + } + + return ret; +} + +/* + * Parse something followed by possible [*+=]. + * + * Note that the branching code sequences used for = and the general cases + * of * and + are somewhat optimized: they use the same NOTHING node as + * both the endmarker for their branch list and the body of the last branch. + * It might seem that this node could be dispensed with entirely, but the + * endmarker role is not redundant. + */ +static char_u *regpiece(int *flagp) +{ + char_u *ret; + int op; + char_u *next; + int flags; + long minval; + long maxval; + + ret = regatom(&flags); + if (ret == NULL) + return NULL; + + op = peekchr(); + if (re_multi_type(op) == NOT_MULTI) { + *flagp = flags; + return ret; + } + // default flags + *flagp = (WORST | SPSTART | (flags & (HASNL | HASLOOKBH))); + + skipchr(); + switch (op) { + case Magic('*'): + if (flags & SIMPLE) + reginsert(STAR, ret); + else { + // Emit x* as (x&|), where & means "self". + reginsert(BRANCH, ret); // Either x + regoptail(ret, regnode(BACK)); // and loop + regoptail(ret, ret); // back + regtail(ret, regnode(BRANCH)); // or + regtail(ret, regnode(NOTHING)); // null. + } + break; + + case Magic('+'): + if (flags & SIMPLE) + reginsert(PLUS, ret); + else { + // Emit x+ as x(&|), where & means "self". + next = regnode(BRANCH); // Either + regtail(ret, next); + regtail(regnode(BACK), ret); // loop back + regtail(next, regnode(BRANCH)); // or + regtail(ret, regnode(NOTHING)); // null. + } + *flagp = (WORST | HASWIDTH | (flags & (HASNL | HASLOOKBH))); + break; + + case Magic('@'): + { + int lop = END; + int64_t nr = getdecchrs(); + + switch (no_Magic(getchr())) { + case '=': lop = MATCH; break; // \@= + case '!': lop = NOMATCH; break; // \@! + case '>': lop = SUBPAT; break; // \@> + case '<': switch (no_Magic(getchr())) { + case '=': lop = BEHIND; break; // \@<= + case '!': lop = NOBEHIND; break; // \@<! + } + } + if (lop == END) + EMSG2_RET_NULL(_("E59: invalid character after %s@"), + reg_magic == MAGIC_ALL); + // Look behind must match with behind_pos. + if (lop == BEHIND || lop == NOBEHIND) { + regtail(ret, regnode(BHPOS)); + *flagp |= HASLOOKBH; + } + regtail(ret, regnode(END)); // operand ends + if (lop == BEHIND || lop == NOBEHIND) { + if (nr < 0) + nr = 0; // no limit is same as zero limit + reginsert_nr(lop, (uint32_t)nr, ret); + } else + reginsert(lop, ret); + break; + } + + case Magic('?'): + case Magic('='): + // Emit x= as (x|) + reginsert(BRANCH, ret); // Either x + regtail(ret, regnode(BRANCH)); // or + next = regnode(NOTHING); // null. + regtail(ret, next); + regoptail(ret, next); + break; + + case Magic('{'): + if (!read_limits(&minval, &maxval)) + return NULL; + if (flags & SIMPLE) { + reginsert(BRACE_SIMPLE, ret); + reginsert_limits(BRACE_LIMITS, minval, maxval, ret); + } else { + if (num_complex_braces >= 10) + EMSG2_RET_NULL(_("E60: Too many complex %s{...}s"), + reg_magic == MAGIC_ALL); + reginsert(BRACE_COMPLEX + num_complex_braces, ret); + regoptail(ret, regnode(BACK)); + regoptail(ret, ret); + reginsert_limits(BRACE_LIMITS, minval, maxval, ret); + ++num_complex_braces; + } + if (minval > 0 && maxval > 0) + *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH))); + break; + } + if (re_multi_type(peekchr()) != NOT_MULTI) { + // Can't have a multi follow a multi. + if (peekchr() == Magic('*')) { + EMSG2_RET_NULL(_("E61: Nested %s*"), reg_magic >= MAGIC_ON); + } + EMSG3_RET_NULL(_("E62: Nested %s%c"), reg_magic == MAGIC_ALL, no_Magic(peekchr())); + } + + return ret; +} + +/* + * Parse one alternative of an | or & operator. + * Implements the concatenation operator. + */ +static char_u *regconcat(int *flagp) +{ + char_u *first = NULL; + char_u *chain = NULL; + char_u *latest; + int flags; + int cont = true; + + *flagp = WORST; // Tentatively. + + while (cont) { + switch (peekchr()) { + case NUL: + case Magic('|'): + case Magic('&'): + case Magic(')'): + cont = false; + break; + case Magic('Z'): + regflags |= RF_ICOMBINE; + skipchr_keepstart(); + break; + case Magic('c'): + regflags |= RF_ICASE; + skipchr_keepstart(); + break; + case Magic('C'): + regflags |= RF_NOICASE; + skipchr_keepstart(); + break; + case Magic('v'): + reg_magic = MAGIC_ALL; + skipchr_keepstart(); + curchr = -1; + break; + case Magic('m'): + reg_magic = MAGIC_ON; + skipchr_keepstart(); + curchr = -1; + break; + case Magic('M'): + reg_magic = MAGIC_OFF; + skipchr_keepstart(); + curchr = -1; + break; + case Magic('V'): + reg_magic = MAGIC_NONE; + skipchr_keepstart(); + curchr = -1; + break; + default: + latest = regpiece(&flags); + if (latest == NULL || reg_toolong) + return NULL; + *flagp |= flags & (HASWIDTH | HASNL | HASLOOKBH); + if (chain == NULL) // First piece. + *flagp |= flags & SPSTART; + else + regtail(chain, latest); + chain = latest; + if (first == NULL) + first = latest; + break; + } + } + if (first == NULL) // Loop ran zero times. + first = regnode(NOTHING); + return first; +} + +/* + * Parse one alternative of an | operator. + * Implements the & operator. + */ +static char_u *regbranch(int *flagp) +{ + char_u *ret; + char_u *chain = NULL; + char_u *latest; + int flags; + + *flagp = WORST | HASNL; // Tentatively. + + ret = regnode(BRANCH); + for (;; ) { + latest = regconcat(&flags); + if (latest == NULL) + return NULL; + // If one of the branches has width, the whole thing has. If one of + // the branches anchors at start-of-line, the whole thing does. + // If one of the branches uses look-behind, the whole thing does. + *flagp |= flags & (HASWIDTH | SPSTART | HASLOOKBH); + // If one of the branches doesn't match a line-break, the whole thing + // doesn't. + *flagp &= ~HASNL | (flags & HASNL); + if (chain != NULL) + regtail(chain, latest); + if (peekchr() != Magic('&')) + break; + skipchr(); + regtail(latest, regnode(END)); // operand ends + if (reg_toolong) + break; + reginsert(MATCH, latest); + chain = latest; + } + + return ret; +} + +/* + * Parse regular expression, i.e. main body or parenthesized thing. + * + * Caller must absorb opening parenthesis. + * + * Combining parenthesis handling with the base level of regular expression + * is a trifle forced, but the need to tie the tails of the branches to what + * follows makes it hard to avoid. + */ +static char_u *reg( + int paren, // REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN + int *flagp) +{ + char_u *ret; + char_u *br; + char_u *ender; + int parno = 0; + int flags; + + *flagp = HASWIDTH; // Tentatively. + + if (paren == REG_ZPAREN) { + // Make a ZOPEN node. + if (regnzpar >= NSUBEXP) + EMSG_RET_NULL(_("E50: Too many \\z(")); + parno = regnzpar; + regnzpar++; + ret = regnode(ZOPEN + parno); + } else if (paren == REG_PAREN) { + // Make a MOPEN node. + if (regnpar >= NSUBEXP) + EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL); + parno = regnpar; + ++regnpar; + ret = regnode(MOPEN + parno); + } else if (paren == REG_NPAREN) { + // Make a NOPEN node. + ret = regnode(NOPEN); + } else + ret = NULL; + + // Pick up the branches, linking them together. + br = regbranch(&flags); + if (br == NULL) + return NULL; + if (ret != NULL) + regtail(ret, br); // [MZ]OPEN -> first. + else + ret = br; + // If one of the branches can be zero-width, the whole thing can. + // If one of the branches has * at start or matches a line-break, the + // whole thing can. + if (!(flags & HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags & (SPSTART | HASNL | HASLOOKBH); + while (peekchr() == Magic('|')) { + skipchr(); + br = regbranch(&flags); + if (br == NULL || reg_toolong) + return NULL; + regtail(ret, br); // BRANCH -> BRANCH. + if (!(flags & HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags & (SPSTART | HASNL | HASLOOKBH); + } + + // Make a closing node, and hook it on the end. + ender = regnode( + paren == REG_ZPAREN ? ZCLOSE + parno : + paren == REG_PAREN ? MCLOSE + parno : + paren == REG_NPAREN ? NCLOSE : END); + regtail(ret, ender); + + // Hook the tails of the branches to the closing node. + for (br = ret; br != NULL; br = regnext(br)) + regoptail(br, ender); + + // Check for proper termination. + if (paren != REG_NOPAREN && getchr() != Magic(')')) { + if (paren == REG_ZPAREN) + EMSG_RET_NULL(_("E52: Unmatched \\z(")); + else if (paren == REG_NPAREN) + EMSG2_RET_NULL(_(e_unmatchedpp), reg_magic == MAGIC_ALL); + else + EMSG2_RET_NULL(_(e_unmatchedp), reg_magic == MAGIC_ALL); + } else if (paren == REG_NOPAREN && peekchr() != NUL) { + if (curchr == Magic(')')) + EMSG2_RET_NULL(_(e_unmatchedpar), reg_magic == MAGIC_ALL); + else + 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 + } + return ret; +} + + +/* + * bt_regcomp() - compile a regular expression into internal code for the + * traditional back track matcher. + * Returns the program in allocated space. Returns NULL for an error. + * + * We can't allocate space until we know how big the compiled form will be, + * but we can't compile it (and thus know how big it is) until we've got a + * place to put the code. So we cheat: we compile it twice, once with code + * generation turned off and size counting turned on, and once "for real". + * This also means that we don't allocate space until we are sure that the + * thing really will compile successfully, and we never have to move the + * code and thus invalidate pointers into it. (Note that it has to be in + * one piece because free() must be able to free it all.) + * + * Whether upper/lower case is to be ignored is decided when executing the + * program, it does not matter here. + * + * Beware that the optimization-preparation code in here knows about some + * of the structure of the compiled regexp. + * "re_flags": RE_MAGIC and/or RE_STRING. + */ +static regprog_T *bt_regcomp(char_u *expr, int re_flags) +{ + char_u *scan; + char_u *longest; + int len; + int flags; + + if (expr == NULL) { + IEMSG_RET_NULL(_(e_null)); + } + + init_class_tab(); + + // First pass: determine size, legality. + regcomp_start(expr, re_flags); + regcode = JUST_CALC_SIZE; + regc(REGMAGIC); + if (reg(REG_NOPAREN, &flags) == NULL) + return NULL; + + // Allocate space. + bt_regprog_T *r = xmalloc(sizeof(bt_regprog_T) + regsize); + r->re_in_use = false; + + // Second pass: emit code. + regcomp_start(expr, re_flags); + regcode = r->program; + regc(REGMAGIC); + if (reg(REG_NOPAREN, &flags) == NULL || reg_toolong) { + xfree(r); + if (reg_toolong) + EMSG_RET_NULL(_("E339: Pattern too long")); + return NULL; + } + + // Dig out information for optimizations. + r->regstart = NUL; // Worst-case defaults. + r->reganch = 0; + r->regmust = NULL; + r->regmlen = 0; + r->regflags = regflags; + if (flags & HASNL) + r->regflags |= RF_HASNL; + if (flags & HASLOOKBH) + r->regflags |= RF_LOOKBH; + // Remember whether this pattern has any \z specials in it. + r->reghasz = re_has_z; + scan = r->program + 1; // First BRANCH. + if (OP(regnext(scan)) == END) { // Only one top-level choice. + scan = OPERAND(scan); + + // Starting-point info. + if (OP(scan) == BOL || OP(scan) == RE_BOF) { + r->reganch++; + scan = regnext(scan); + } + + if (OP(scan) == EXACTLY) { + r->regstart = utf_ptr2char(OPERAND(scan)); + } else if (OP(scan) == BOW + || OP(scan) == EOW + || OP(scan) == NOTHING + || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN + || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) { + char_u *regnext_scan = regnext(scan); + if (OP(regnext_scan) == EXACTLY) { + r->regstart = utf_ptr2char(OPERAND(regnext_scan)); + } + } + + // If there's something expensive in the r.e., find the longest + // literal string that must appear and make it the regmust. Resolve + // ties in favor of later strings, since the regstart check works + // with the beginning of the r.e. and avoiding duplication + // strengthens checking. Not a strong reason, but sufficient in the + // absence of others. + + // When the r.e. starts with BOW, it is faster to look for a regmust + // first. Used a lot for "#" and "*" commands. (Added by mool). + if ((flags & SPSTART || OP(scan) == BOW || OP(scan) == EOW) + && !(flags & HASNL)) { + longest = NULL; + len = 0; + for (; scan != NULL; scan = regnext(scan)) + if (OP(scan) == EXACTLY && STRLEN(OPERAND(scan)) >= (size_t)len) { + longest = OPERAND(scan); + len = (int)STRLEN(OPERAND(scan)); + } + r->regmust = longest; + r->regmlen = len; + } + } +#ifdef BT_REGEXP_DUMP + regdump(expr, r); +#endif + r->engine = &bt_regengine; + return (regprog_T *)r; +} + +/* + * Check if during the previous call to vim_regcomp the EOL item "$" has been + * found. This is messy, but it works fine. + */ +int vim_regcomp_had_eol(void) +{ + return had_eol; +} + +/* + * Get a number after a backslash that is inside []. + * When nothing is recognized return a backslash. + */ +static int coll_get_char(void) +{ + int64_t nr = -1; + + switch (*regparse++) { + case 'd': nr = getdecchrs(); break; + case 'o': nr = getoctchrs(); break; + case 'x': nr = gethexchrs(2); break; + case 'u': nr = gethexchrs(4); break; + case 'U': nr = gethexchrs(8); break; + } + if (nr < 0 || nr > INT_MAX) { + // If getting the number fails be backwards compatible: the character + // is a backslash. + regparse--; + nr = '\\'; + } + return nr; +} + +/* + * Free a compiled regexp program, returned by bt_regcomp(). + */ +static void bt_regfree(regprog_T *prog) +{ + xfree(prog); +} + +#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input) + +/* + * The arguments from BRACE_LIMITS are stored here. They are actually local + * to regmatch(), but they are here to reduce the amount of stack space used + * (it can be called recursively many times). + */ +static long bl_minval; +static long bl_maxval; + +// 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)(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. +static void reg_restore(regsave_T *save, garray_T *gap) + FUNC_ATTR_NONNULL_ALL +{ + if (REG_MULTI) { + 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); + } + 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 bool reg_save_equal(const regsave_T *save) + FUNC_ATTR_NONNULL_ALL +{ + 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; +} + +// 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)) + +// After a failed match restore the sub-expressions. +#define restore_se(savep, posp, pp) { \ + if (REG_MULTI) \ + *(posp) = (savep)->se_u.pos; \ + else \ + *(pp) = (savep)->se_u.ptr; } + +/* + * Tentatively set the sub-expression start to the current position (after + * calling regmatch() they will have changed). Need to save the existing + * values for when there is no match. + * Use se_save() to use pointer (save_se_multi()) or position (save_se_one()), + * depending on REG_MULTI. + */ +static void save_se_multi(save_se_T *savep, lpos_T *posp) +{ + savep->se_u.pos = *posp; + 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 = rex.input; +} + +/* + * regrepeat - repeatedly match something simple, return how many. + * Advances rex.input (and rex.lnum) to just after the matched chars. + */ + static int +regrepeat( + char_u *p, + long maxcount) // maximum number of matches allowed +{ + long count = 0; + char_u *opnd; + int mask; + int testval = 0; + + char_u *scan = rex.input; // Make local copy of rex.input for speed. + opnd = OPERAND(p); + switch (OP(p)) { + case ANY: + case ANY + ADD_NL: + while (count < maxcount) { + // Matching anything means we continue until end-of-line (or + // end-of-file for ANY + ADD_NL), only limited by maxcount. + while (*scan != NUL && count < maxcount) { + count++; + MB_PTR_ADV(scan); + } + 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 = rex.input; + if (got_int) { + break; + } + } + break; + + case IDENT: + case IDENT + ADD_NL: + testval = 1; + FALLTHROUGH; + case SIDENT: + case SIDENT + ADD_NL: + while (count < maxcount) { + if (vim_isIDc(utf_ptr2char(scan)) && (testval || !ascii_isdigit(*scan))) { + MB_PTR_ADV(scan); + } else if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + ++count; + } + break; + + case KWORD: + case KWORD + ADD_NL: + testval = 1; + FALLTHROUGH; + case SKWORD: + case SKWORD + ADD_NL: + while (count < maxcount) { + if (vim_iswordp_buf(scan, rex.reg_buf) + && (testval || !ascii_isdigit(*scan))) { + MB_PTR_ADV(scan); + } else if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + count++; + } + break; + + case FNAME: + case FNAME + ADD_NL: + testval = 1; + FALLTHROUGH; + case SFNAME: + case SFNAME + ADD_NL: + while (count < maxcount) { + if (vim_isfilec(utf_ptr2char(scan)) && (testval || !ascii_isdigit(*scan))) { + MB_PTR_ADV(scan); + } else if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + count++; + } + break; + + case PRINT: + case PRINT + ADD_NL: + testval = 1; + FALLTHROUGH; + case SPRINT: + case SPRINT + ADD_NL: + while (count < maxcount) { + if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (vim_isprintc(utf_ptr2char(scan)) == 1 + && (testval || !ascii_isdigit(*scan))) { + MB_PTR_ADV(scan); + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + count++; + } + break; + + case WHITE: + case WHITE + ADD_NL: + testval = mask = RI_WHITE; +do_class: + while (count < maxcount) { + int l; + if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if ((l = utfc_ptr2len(scan)) > 1) { + if (testval != 0) { + break; + } + scan += l; + } else if ((class_tab[*scan] & mask) == testval) { + scan++; + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + ++count; + } + break; + + case NWHITE: + case NWHITE + ADD_NL: + mask = RI_WHITE; + goto do_class; + case DIGIT: + case DIGIT + ADD_NL: + testval = mask = RI_DIGIT; + goto do_class; + case NDIGIT: + case NDIGIT + ADD_NL: + mask = RI_DIGIT; + goto do_class; + case HEX: + case HEX + ADD_NL: + testval = mask = RI_HEX; + goto do_class; + case NHEX: + case NHEX + ADD_NL: + mask = RI_HEX; + goto do_class; + case OCTAL: + case OCTAL + ADD_NL: + testval = mask = RI_OCTAL; + goto do_class; + case NOCTAL: + case NOCTAL + ADD_NL: + mask = RI_OCTAL; + goto do_class; + case WORD: + case WORD + ADD_NL: + testval = mask = RI_WORD; + goto do_class; + case NWORD: + case NWORD + ADD_NL: + mask = RI_WORD; + goto do_class; + case HEAD: + case HEAD + ADD_NL: + testval = mask = RI_HEAD; + goto do_class; + case NHEAD: + case NHEAD + ADD_NL: + mask = RI_HEAD; + goto do_class; + case ALPHA: + case ALPHA + ADD_NL: + testval = mask = RI_ALPHA; + goto do_class; + case NALPHA: + case NALPHA + ADD_NL: + mask = RI_ALPHA; + goto do_class; + case LOWER: + case LOWER + ADD_NL: + testval = mask = RI_LOWER; + goto do_class; + case NLOWER: + case NLOWER + ADD_NL: + mask = RI_LOWER; + goto do_class; + case UPPER: + case UPPER + ADD_NL: + testval = mask = RI_UPPER; + goto do_class; + case NUPPER: + case NUPPER + ADD_NL: + mask = RI_UPPER; + goto do_class; + + case EXACTLY: + { + int cu, cl; + + // This doesn't do a multi-byte character, because a MULTIBYTECODE + // would have been used for it. It does handle single-byte + // characters, such as latin1. + if (rex.reg_ic) { + cu = mb_toupper(*opnd); + cl = mb_tolower(*opnd); + while (count < maxcount && (*scan == cu || *scan == cl)) { + count++; + scan++; + } + } else { + cu = *opnd; + while (count < maxcount && *scan == cu) { + count++; + scan++; + } + } + break; + } + + case MULTIBYTECODE: + { + int i, len, cf = 0; + + // Safety check (just in case 'encoding' was changed since + // compiling the program). + if ((len = utfc_ptr2len(opnd)) > 1) { + if (rex.reg_ic) { + cf = utf_fold(utf_ptr2char(opnd)); + } + while (count < maxcount && utfc_ptr2len(scan) >= len) { + for (i = 0; i < len; i++) { + if (opnd[i] != scan[i]) { + break; + } + } + if (i < len && (!rex.reg_ic + || utf_fold(utf_ptr2char(scan)) != cf)) { + break; + } + scan += len; + ++count; + } + } + } + break; + + case ANYOF: + case ANYOF + ADD_NL: + testval = 1; + FALLTHROUGH; + + case ANYBUT: + case ANYBUT + ADD_NL: + while (count < maxcount) { + int len; + if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else if ((len = utfc_ptr2len(scan)) > 1) { + if ((cstrchr(opnd, utf_ptr2char(scan)) == NULL) == testval) { + break; + } + scan += len; + } else { + if ((cstrchr(opnd, *scan) == NULL) == testval) + break; + ++scan; + } + ++count; + } + break; + + case NEWL: + while (count < maxcount + && ((*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) { + ADVANCE_REGINPUT(); + } else { + reg_nextline(); + } + scan = rex.input; + if (got_int) { + break; + } + } + break; + + default: // Oh dear. Called inappropriately. + iemsg(_(e_re_corr)); +#ifdef REGEXP_DEBUG + printf("Called regrepeat with op code %d\n", OP(p)); +#endif + break; + } + + rex.input = scan; + + return (int)count; +} + +/* + * Push an item onto the regstack. + * Returns pointer to new item. Returns NULL when out of memory. + */ +static regitem_T *regstack_push(regstate_T state, char_u *scan) +{ + regitem_T *rp; + + if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { + emsg(_(e_maxmempat)); + return NULL; + } + ga_grow(®stack, sizeof(regitem_T)); + + rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len); + rp->rs_state = state; + rp->rs_scan = scan; + + regstack.ga_len += sizeof(regitem_T); + return rp; +} + +/* + * Pop an item from the regstack. + */ +static void regstack_pop(char_u **scan) +{ + regitem_T *rp; + + rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1; + *scan = rp->rs_scan; + + regstack.ga_len -= sizeof(regitem_T); +} + +// 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 +{ + // 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 = 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]; + } else { + bp->save_start[i].se_u.ptr = rex.reg_startp[i]; + bp->save_end[i].se_u.ptr = rex.reg_endp[i]; + } + } + } +} + +// Restore the subexpr from "bp". +static void restore_subexpr(regbehind_T *bp) + FUNC_ATTR_NONNULL_ALL +{ + // 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; + } else { + rex.reg_startp[i] = bp->save_start[i].se_u.ptr; + rex.reg_endp[i] = bp->save_end[i].se_u.ptr; + } + } + } +} +/// Main matching routine +/// +/// Conceptually the strategy is simple: Check to see whether the current node +/// matches, push an item onto the regstack and loop to see whether the rest +/// matches, and then act accordingly. In practice we make some effort to +/// avoid using the regstack, in particular by going through "ordinary" nodes +/// (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 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 bool regmatch( + char_u *scan, // Current node. + proftime_T *tm, // timeout limit or NULL + int *timed_out) // flag set on timeout or NULL +{ + char_u *next; // Next node. + int op; + int c; + regitem_T *rp; + int no; + int status; // one of the RA_ values: + int tm_count = 0; + + // Make "regstack" and "backpos" empty. They are allocated and freed in + // bt_regexec_both() to reduce malloc()/free() calls. + regstack.ga_len = 0; + backpos.ga_len = 0; + + // Repeat until "regstack" is empty. + for (;; ) { + // Some patterns may take a long time to match, e.g., "\([a-z]\+\)\+Q". + // Allow interrupting them with CTRL-C. + fast_breakcheck(); + +#ifdef REGEXP_DEBUG + if (scan != NULL && regnarrate) { + mch_errmsg((char *)regprop(scan)); + mch_errmsg("(\n"); + } +#endif + + // Repeat for items that can be matched sequentially, without using the + // regstack. + for (;; ) { + if (got_int || scan == NULL) { + status = RA_FAIL; + break; + } + // Check for timeout once in a 100 times to avoid overhead. + if (tm != NULL && ++tm_count == 100) { + tm_count = 0; + if (profile_passed_limit(*tm)) { + if (timed_out != NULL) { + *timed_out = true; + } + status = RA_FAIL; + break; + } + } + status = RA_CONT; + +#ifdef REGEXP_DEBUG + if (regnarrate) { + mch_errmsg((char *)regprop(scan)); + mch_errmsg("...\n"); + if (re_extmatch_in != NULL) { + int i; + + mch_errmsg(_("External submatches:\n")); + for (i = 0; i < NSUBEXP; i++) { + mch_errmsg(" \""); + if (re_extmatch_in->matches[i] != NULL) + mch_errmsg((char *)re_extmatch_in->matches[i]); + mch_errmsg("\"\n"); + } + } + } +#endif + next = regnext(scan); + + op = OP(scan); + // Check for character class with NL added. + if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI + && *rex.input == NUL && rex.lnum <= rex.reg_maxline) { + reg_nextline(); + } 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(rex.input); + switch (op) { + case BOL: + if (rex.input != rex.line) { + status = RA_NOMATCH; + } + break; + + case EOL: + 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 (rex.lnum != 0 || rex.input != rex.line + || (REG_MULTI && rex.reg_firstlnum > 1)) { + status = RA_NOMATCH; + } + break; + + case RE_EOF: + if (rex.lnum != rex.reg_maxline || c != NUL) { + status = RA_NOMATCH; + } + break; + + case CURSOR: + // 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 + || (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; + + case RE_MARK: + // Compare the mark position to the match position. + { + int mark = OPERAND(scan)[0]; + int cmp = OPERAND(scan)[1]; + pos_T *pos; + size_t col = REG_MULTI ? rex.input - rex.line : 0; + + pos = getmark_buf(rex.reg_buf, mark, false); + + // Line may have been freed, get it again. + if (REG_MULTI) { + rex.line = reg_getline(rex.lnum); + rex.input = rex.line + col; + } + + if (pos == NULL // mark doesn't exist + || pos->lnum <= 0) { // mark isn't set in reg_buf + status = RA_NOMATCH; + } else { + const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum + && pos->col == MAXCOL + ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum)) + : pos->col; + + if (pos->lnum == rex.lnum + rex.reg_firstlnum + ? (pos_col == (colnr_T)(rex.input - rex.line) + ? (cmp == '<' || cmp == '>') + : (pos_col < (colnr_T)(rex.input - rex.line) + ? cmp != '>' + : cmp != '<')) + : (pos->lnum < rex.lnum + rex.reg_firstlnum + ? cmp != '>' + : cmp != '<')) { + status = RA_NOMATCH; + } + } + } + break; + + case RE_VISUAL: + if (!reg_match_visual()) + status = RA_NOMATCH; + break; + + case RE_LNUM: + assert(rex.lnum + rex.reg_firstlnum >= 0 + && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX); + if (!REG_MULTI + || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) { + status = RA_NOMATCH; + } + break; + + case RE_COL: + 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, + rex.line, + (colnr_T)(rex.input - rex.line)) + 1, + scan)) { + status = RA_NOMATCH; + } + break; + + case BOW: // \<word; rex.input points to w + if (c == NUL) { // Can't match at end of line + status = RA_NOMATCH; + } else { + // Get class of current and previous char (if it exists). + 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. + } + } + break; + + case EOW: // word\>; rex.input points after d + if (rex.input == rex.line) { // Can't match at start of line + status = RA_NOMATCH; + } else { + int this_class, prev_class; + + // Get class of current and previous char (if it exists). + 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; + } + } + break; // Matched with EOW + + case ANY: + // ANY does not match new lines. + if (c == NUL) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case IDENT: + if (!vim_isIDc(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case SIDENT: + if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case KWORD: + if (!vim_iswordp_buf(rex.input, rex.reg_buf)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case SKWORD: + if (ascii_isdigit(*rex.input) + || !vim_iswordp_buf(rex.input, rex.reg_buf)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case FNAME: + if (!vim_isfilec(c)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case SFNAME: + if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case PRINT: + if (!vim_isprintc(utf_ptr2char(rex.input))) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case SPRINT: + if (ascii_isdigit(*rex.input) || !vim_isprintc(utf_ptr2char(rex.input))) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case WHITE: + if (!ascii_iswhite(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NWHITE: + if (c == NUL || ascii_iswhite(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case DIGIT: + if (!ri_digit(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NDIGIT: + if (c == NUL || ri_digit(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case HEX: + if (!ri_hex(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NHEX: + if (c == NUL || ri_hex(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case OCTAL: + if (!ri_octal(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NOCTAL: + if (c == NUL || ri_octal(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case WORD: + if (!ri_word(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NWORD: + if (c == NUL || ri_word(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case HEAD: + if (!ri_head(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NHEAD: + if (c == NUL || ri_head(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case ALPHA: + if (!ri_alpha(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NALPHA: + if (c == NUL || ri_alpha(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case LOWER: + if (!ri_lower(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NLOWER: + if (c == NUL || ri_lower(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case UPPER: + if (!ri_upper(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NUPPER: + if (c == NUL || ri_upper(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case EXACTLY: + { + int len; + char_u *opnd; + + opnd = OPERAND(scan); + // Inline the first byte, for speed. + if (*opnd != *rex.input + && (!rex.reg_ic)) { + status = RA_NOMATCH; + } else if (*opnd == NUL) { + // match empty string always works; happens when "~" is + // empty. + } else { + if (opnd[1] == NUL && !rex.reg_ic) { + len = 1; // matched a single byte above + } else { + // Need to match first byte again for multi-byte. + len = (int)STRLEN(opnd); + 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 + && utf_composinglike(rex.input, rex.input + len) + && !rex.reg_icombine + && OP(next) != RE_COMPOSING) { + // raaron: This code makes a composing character get + // ignored, which is the correct behavior (sometimes) + // for voweled Hebrew texts. + status = RA_NOMATCH; + } + if (status != RA_NOMATCH) { + rex.input += len; + } + } + } + break; + + case ANYOF: + case ANYBUT: + if (c == NUL) + status = RA_NOMATCH; + else if ((cstrchr(OPERAND(scan), c) == NULL) == (op == ANYOF)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case MULTIBYTECODE: + { + int i, len; + + const char_u *opnd = OPERAND(scan); + // Safety check (just in case 'encoding' was changed since + // compiling the program). + if ((len = utfc_ptr2len(opnd)) < 2) { + status = RA_NOMATCH; + break; + } + 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; 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(rex.input + i); + status = RA_MATCH; + break; + } + } + } else { + for (i = 0; i < len; i++) { + if (opnd[i] != rex.input[i]) { + status = RA_NOMATCH; + break; + } + } + } + rex.input += len; + } + break; + + case RE_COMPOSING: + { + // Skip composing characters. + while (utf_iscomposing(utf_ptr2char(rex.input))) { + MB_CPTR_ADV(rex.input); + } + } + break; + + case NOTHING: + break; + + case BACK: + { + int i; + + // When we run into BACK we need to check if we don't keep + // looping without matching any input. The second and later + // times a BACK is encountered it fails if the input is still + // at the same position as the previous time. + // The positions are stored in "backpos" and found by the + // current value of "scan", the position in the RE program. + backpos_T *bp = (backpos_T *)backpos.ga_data; + for (i = 0; i < backpos.ga_len; ++i) + if (bp[i].bp_scan == scan) + break; + if (i == backpos.ga_len) { + backpos_T *p = GA_APPEND_VIA_PTR(backpos_T, &backpos); + p->bp_scan = scan; + } else if (reg_save_equal(&bp[i].bp_pos)) + // Still at same position as last time, fail. + status = RA_NOMATCH; + + assert(status != RA_FAIL); + if (status != RA_NOMATCH) { + reg_save(&bp[i].bp_pos, &backpos); + } + } + break; + + case MOPEN + 0: // Match start: \zs + case MOPEN + 1: // \( + case MOPEN + 2: + case MOPEN + 3: + case MOPEN + 4: + case MOPEN + 5: + case MOPEN + 6: + case MOPEN + 7: + case MOPEN + 8: + case MOPEN + 9: + { + no = op - MOPEN; + cleanup_subexpr(); + rp = regstack_push(RS_MOPEN, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + save_se(&rp->rs_un.sesave, &rex.reg_startpos[no], + &rex.reg_startp[no]); + // We simply continue and handle the result when done. + } + } + break; + + case NOPEN: // \%( + case NCLOSE: // \) after \%( + if (regstack_push(RS_NOPEN, scan) == NULL) + status = RA_FAIL; + // We simply continue and handle the result when done. + break; + + case ZOPEN + 1: + case ZOPEN + 2: + case ZOPEN + 3: + case ZOPEN + 4: + case ZOPEN + 5: + case ZOPEN + 6: + case ZOPEN + 7: + case ZOPEN + 8: + case ZOPEN + 9: + { + no = op - ZOPEN; + cleanup_zsubexpr(); + rp = regstack_push(RS_ZOPEN, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + save_se(&rp->rs_un.sesave, ®_startzpos[no], + ®_startzp[no]); + // We simply continue and handle the result when done. + } + } + break; + + case MCLOSE + 0: // Match end: \ze + case MCLOSE + 1: // \) + case MCLOSE + 2: + case MCLOSE + 3: + case MCLOSE + 4: + case MCLOSE + 5: + case MCLOSE + 6: + case MCLOSE + 7: + case MCLOSE + 8: + case MCLOSE + 9: + { + no = op - MCLOSE; + cleanup_subexpr(); + rp = regstack_push(RS_MCLOSE, scan); + if (rp == NULL) { + status = RA_FAIL; + } else { + rp->rs_no = no; + save_se(&rp->rs_un.sesave, &rex.reg_endpos[no], &rex.reg_endp[no]); + // We simply continue and handle the result when done. + } + } + break; + + case ZCLOSE + 1: // \) after \z( + case ZCLOSE + 2: + case ZCLOSE + 3: + case ZCLOSE + 4: + case ZCLOSE + 5: + case ZCLOSE + 6: + case ZCLOSE + 7: + case ZCLOSE + 8: + case ZCLOSE + 9: + { + no = op - ZCLOSE; + cleanup_zsubexpr(); + rp = regstack_push(RS_ZCLOSE, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + save_se(&rp->rs_un.sesave, ®_endzpos[no], + ®_endzp[no]); + // We simply continue and handle the result when done. + } + } + break; + + case BACKREF + 1: + case BACKREF + 2: + case BACKREF + 3: + case BACKREF + 4: + case BACKREF + 5: + case BACKREF + 6: + case BACKREF + 7: + case BACKREF + 8: + case BACKREF + 9: + { + int len; + + no = op - BACKREF; + cleanup_subexpr(); + if (!REG_MULTI) { // Single-line regexp + if (rex.reg_startp[no] == NULL || rex.reg_endp[no] == NULL) { + // Backref was not set: Match an empty string. + len = 0; + } 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], rex.input, &len) != 0) { + status = RA_NOMATCH; + } + } + } else { // Multi-line regexp + if (rex.reg_startpos[no].lnum < 0 || rex.reg_endpos[no].lnum < 0) { + // Backref was not set: Match an empty string. + len = 0; + } else { + 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(rex.line + rex.reg_startpos[no].col, + rex.input, &len) != 0) { + status = RA_NOMATCH; + } + } else { + // Messy situation: Need to compare between two lines. + int r = match_with_backref(rex.reg_startpos[no].lnum, + rex.reg_startpos[no].col, + rex.reg_endpos[no].lnum, + rex.reg_endpos[no].col, + &len); + if (r != RA_MATCH) { + status = r; + } + } + } + } + + // Matched the backref, skip over it. + rex.input += len; + } + break; + + case ZREF + 1: + case ZREF + 2: + case ZREF + 3: + case ZREF + 4: + case ZREF + 5: + case ZREF + 6: + case ZREF + 7: + case ZREF + 8: + case ZREF + 9: + { + cleanup_zsubexpr(); + no = op - ZREF; + if (re_extmatch_in != NULL + && re_extmatch_in->matches[no] != NULL) { + int len = (int)STRLEN(re_extmatch_in->matches[no]); + if (cstrncmp(re_extmatch_in->matches[no], rex.input, &len) != 0) { + status = RA_NOMATCH; + } else { + rex.input += len; + } + } else { + // Backref was not set: Match an empty string. + } + } + break; + + case BRANCH: + { + if (OP(next) != BRANCH) // No choice. + next = OPERAND(scan); // Avoid recursion. + else { + rp = regstack_push(RS_BRANCH, scan); + if (rp == NULL) + status = RA_FAIL; + else + status = RA_BREAK; // rest is below + } + } + break; + + case BRACE_LIMITS: + { + if (OP(next) == BRACE_SIMPLE) { + bl_minval = OPERAND_MIN(scan); + bl_maxval = OPERAND_MAX(scan); + } else if (OP(next) >= BRACE_COMPLEX + && OP(next) < BRACE_COMPLEX + 10) { + no = OP(next) - BRACE_COMPLEX; + brace_min[no] = OPERAND_MIN(scan); + brace_max[no] = OPERAND_MAX(scan); + brace_count[no] = 0; + } else { + internal_error("BRACE_LIMITS"); + status = RA_FAIL; + } + } + break; + + case BRACE_COMPLEX + 0: + case BRACE_COMPLEX + 1: + case BRACE_COMPLEX + 2: + case BRACE_COMPLEX + 3: + case BRACE_COMPLEX + 4: + case BRACE_COMPLEX + 5: + case BRACE_COMPLEX + 6: + case BRACE_COMPLEX + 7: + case BRACE_COMPLEX + 8: + case BRACE_COMPLEX + 9: + { + no = op - BRACE_COMPLEX; + ++brace_count[no]; + + // If not matched enough times yet, try one more + if (brace_count[no] <= (brace_min[no] <= brace_max[no] + ? brace_min[no] : brace_max[no])) { + rp = regstack_push(RS_BRCPLX_MORE, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + reg_save(&rp->rs_un.regsave, &backpos); + next = OPERAND(scan); + // We continue and handle the result when done. + } + break; + } + + // If matched enough times, may try matching some more + if (brace_min[no] <= brace_max[no]) { + // Range is the normal way around, use longest match + if (brace_count[no] <= brace_max[no]) { + rp = regstack_push(RS_BRCPLX_LONG, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + reg_save(&rp->rs_un.regsave, &backpos); + next = OPERAND(scan); + // We continue and handle the result when done. + } + } + } else { + // Range is backwards, use shortest match first + if (brace_count[no] <= brace_min[no]) { + rp = regstack_push(RS_BRCPLX_SHORT, scan); + if (rp == NULL) + status = RA_FAIL; + else { + reg_save(&rp->rs_un.regsave, &backpos); + // We continue and handle the result when done. + } + } + } + } + break; + + case BRACE_SIMPLE: + case STAR: + case PLUS: + { + regstar_T rst; + + // Lookahead to avoid useless match attempts when we know + // what character comes next. + if (OP(next) == EXACTLY) { + rst.nextb = *OPERAND(next); + if (rex.reg_ic) { + if (mb_isupper(rst.nextb)) { + rst.nextb_ic = mb_tolower(rst.nextb); + } else { + rst.nextb_ic = mb_toupper(rst.nextb); + } + } else { + rst.nextb_ic = rst.nextb; + } + } else { + rst.nextb = NUL; + rst.nextb_ic = NUL; + } + if (op != BRACE_SIMPLE) { + rst.minval = (op == STAR) ? 0 : 1; + rst.maxval = MAX_LIMIT; + } else { + rst.minval = bl_minval; + rst.maxval = bl_maxval; + } + + // When maxval > minval, try matching as much as possible, up + // to maxval. When maxval < minval, try matching at least the + // minimal number (since the range is backwards, that's also + // maxval!). + rst.count = regrepeat(OPERAND(scan), rst.maxval); + if (got_int) { + status = RA_FAIL; + break; + } + if (rst.minval <= rst.maxval + ? rst.count >= rst.minval : rst.count >= rst.maxval) { + // It could match. Prepare for trying to match what + // follows. The code is below. Parameters are stored in + // a regstar_T on the regstack. + if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { + emsg(_(e_maxmempat)); + status = RA_FAIL; + } else { + ga_grow(®stack, sizeof(regstar_T)); + regstack.ga_len += sizeof(regstar_T); + rp = regstack_push(rst.minval <= rst.maxval + ? RS_STAR_LONG : RS_STAR_SHORT, scan); + if (rp == NULL) + status = RA_FAIL; + else { + *(((regstar_T *)rp) - 1) = rst; + status = RA_BREAK; // skip the restore bits + } + } + } else + status = RA_NOMATCH; + + } + break; + + case NOMATCH: + case MATCH: + case SUBPAT: + rp = regstack_push(RS_NOMATCH, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = op; + reg_save(&rp->rs_un.regsave, &backpos); + next = OPERAND(scan); + // We continue and handle the result when done. + } + break; + + case BEHIND: + case NOBEHIND: + // Need a bit of room to store extra positions. + if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { + emsg(_(e_maxmempat)); + status = RA_FAIL; + } else { + ga_grow(®stack, sizeof(regbehind_T)); + regstack.ga_len += sizeof(regbehind_T); + rp = regstack_push(RS_BEHIND1, scan); + if (rp == NULL) + status = RA_FAIL; + else { + // Need to save the subexpr to be able to restore them + // when there is a match but we don't use it. + save_subexpr(((regbehind_T *)rp) - 1); + + rp->rs_no = op; + reg_save(&rp->rs_un.regsave, &backpos); + // First try if what follows matches. If it does then we + // check the behind match by looping. + } + } + break; + + case BHPOS: + if (REG_MULTI) { + 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 != rex.input) { + status = RA_NOMATCH; + } + break; + + case NEWL: + 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) { + ADVANCE_REGINPUT(); + } else { + reg_nextline(); + } + break; + + case END: + status = RA_MATCH; // Success! + break; + + default: + iemsg(_(e_re_corr)); +#ifdef REGEXP_DEBUG + printf("Illegal op code %d\n", op); +#endif + status = RA_FAIL; + break; + } + } + + // If we can't continue sequentially, break the inner loop. + if (status != RA_CONT) + break; + + // Continue in inner loop, advance to next item. + scan = next; + + } // end of inner loop + + // If there is something on the regstack execute the code for the state. + // If the state is popped then loop and use the older state. + while (!GA_EMPTY(®stack) && status != RA_FAIL) { + rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1; + switch (rp->rs_state) { + case RS_NOPEN: + // Result is passed on as-is, simply pop the state. + regstack_pop(&scan); + break; + + case RS_MOPEN: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + restore_se(&rp->rs_un.sesave, &rex.reg_startpos[rp->rs_no], + &rex.reg_startp[rp->rs_no]); + } + regstack_pop(&scan); + break; + + case RS_ZOPEN: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) + restore_se(&rp->rs_un.sesave, ®_startzpos[rp->rs_no], + ®_startzp[rp->rs_no]); + regstack_pop(&scan); + break; + + case RS_MCLOSE: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + restore_se(&rp->rs_un.sesave, &rex.reg_endpos[rp->rs_no], + &rex.reg_endp[rp->rs_no]); + } + regstack_pop(&scan); + break; + + case RS_ZCLOSE: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) + restore_se(&rp->rs_un.sesave, ®_endzpos[rp->rs_no], + ®_endzp[rp->rs_no]); + regstack_pop(&scan); + break; + + case RS_BRANCH: + if (status == RA_MATCH) + // this branch matched, use it + regstack_pop(&scan); + else { + if (status != RA_BREAK) { + // After a non-matching branch: try next one. + reg_restore(&rp->rs_un.regsave, &backpos); + scan = rp->rs_scan; + } + if (scan == NULL || OP(scan) != BRANCH) { + // no more branches, didn't find a match + status = RA_NOMATCH; + regstack_pop(&scan); + } else { + // Prepare to try a branch. + rp->rs_scan = regnext(scan); + reg_save(&rp->rs_un.regsave, &backpos); + scan = OPERAND(scan); + } + } + break; + + case RS_BRCPLX_MORE: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + reg_restore(&rp->rs_un.regsave, &backpos); + --brace_count[rp->rs_no]; // decrement match count + } + regstack_pop(&scan); + break; + + case RS_BRCPLX_LONG: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + // There was no match, but we did find enough matches. + reg_restore(&rp->rs_un.regsave, &backpos); + --brace_count[rp->rs_no]; + // continue with the items after "\{}" + status = RA_CONT; + } + regstack_pop(&scan); + if (status == RA_CONT) + scan = regnext(scan); + break; + + case RS_BRCPLX_SHORT: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) + // There was no match, try to match one more item. + reg_restore(&rp->rs_un.regsave, &backpos); + regstack_pop(&scan); + if (status == RA_NOMATCH) { + scan = OPERAND(scan); + status = RA_CONT; + } + break; + + case RS_NOMATCH: + // Pop the state. If the operand matches for NOMATCH or + // doesn't match for MATCH/SUBPAT, we fail. Otherwise backup, + // except for SUBPAT, and continue with the next item. + if (status == (rp->rs_no == NOMATCH ? RA_MATCH : RA_NOMATCH)) + status = RA_NOMATCH; + else { + status = RA_CONT; + if (rp->rs_no != SUBPAT) // zero-width + reg_restore(&rp->rs_un.regsave, &backpos); + } + regstack_pop(&scan); + if (status == RA_CONT) + scan = regnext(scan); + break; + + case RS_BEHIND1: + if (status == RA_NOMATCH) { + regstack_pop(&scan); + regstack.ga_len -= sizeof(regbehind_T); + } else { + // The stuff after BEHIND/NOBEHIND matches. Now try if + // the behind part does (not) match before the current + // position in the input. This must be done at every + // position in the input and checking if the match ends at + // the current position. + + // save the position after the found match for next + reg_save(&(((regbehind_T *)rp) - 1)->save_after, &backpos); + + // Start looking for a match with operand at the current + // position. Go back one character until we find the + // result, hitting the start of the line or the previous + // line (for multi-line matching). + // Set behind_pos to where the match should end, BHPOS + // will match it. Save the current value. + (((regbehind_T *)rp) - 1)->save_behind = behind_pos; + behind_pos = rp->rs_un.regsave; + + rp->rs_state = RS_BEHIND2; + + reg_restore(&rp->rs_un.regsave, &backpos); + scan = OPERAND(rp->rs_scan) + 4; + } + break; + + case RS_BEHIND2: + // Looping for BEHIND / NOBEHIND match. + if (status == RA_MATCH && reg_save_equal(&behind_pos)) { + // found a match that ends where "next" started + behind_pos = (((regbehind_T *)rp) - 1)->save_behind; + if (rp->rs_no == BEHIND) + reg_restore(&(((regbehind_T *)rp) - 1)->save_after, + &backpos); + else { + // But we didn't want a match. Need to restore the + // subexpr, because what follows matched, so they have + // been set. + status = RA_NOMATCH; + restore_subexpr(((regbehind_T *)rp) - 1); + } + regstack_pop(&scan); + regstack.ga_len -= sizeof(regbehind_T); + } else { + long limit; + + // No match or a match that doesn't end where we want it: Go + // back one character. May go to previous line once. + no = OK; + limit = OPERAND_MIN(rp->rs_scan); + if (REG_MULTI) { + if (limit > 0 + && ((rp->rs_un.regsave.rs_u.pos.lnum + < behind_pos.rs_u.pos.lnum + ? (colnr_T)STRLEN(rex.line) + : behind_pos.rs_u.pos.col) + - rp->rs_un.regsave.rs_u.pos.col >= limit)) + no = FAIL; + else if (rp->rs_un.regsave.rs_u.pos.col == 0) { + if (rp->rs_un.regsave.rs_u.pos.lnum + < behind_pos.rs_u.pos.lnum + || reg_getline( + --rp->rs_un.regsave.rs_u.pos.lnum) + == NULL) + no = FAIL; + else { + reg_restore(&rp->rs_un.regsave, &backpos); + rp->rs_un.regsave.rs_u.pos.col = + (colnr_T)STRLEN(rex.line); + } + } else { + const char_u *const line = + reg_getline(rp->rs_un.regsave.rs_u.pos.lnum); + + rp->rs_un.regsave.rs_u.pos.col -= + utf_head_off(line, + line + rp->rs_un.regsave.rs_u.pos.col - 1) + + 1; + } + } else { + if (rp->rs_un.regsave.rs_u.ptr == rex.line) { + no = FAIL; + } else { + MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr); + if (limit > 0 + && (behind_pos.rs_u.ptr - rp->rs_un.regsave.rs_u.ptr) > (ptrdiff_t)limit) { + no = FAIL; + } + } + } + if (no == OK) { + // Advanced, prepare for finding match again. + reg_restore(&rp->rs_un.regsave, &backpos); + scan = OPERAND(rp->rs_scan) + 4; + if (status == RA_MATCH) { + // We did match, so subexpr may have been changed, + // need to restore them for the next try. + status = RA_NOMATCH; + restore_subexpr(((regbehind_T *)rp) - 1); + } + } else { + // Can't advance. For NOBEHIND that's a match. + behind_pos = (((regbehind_T *)rp) - 1)->save_behind; + if (rp->rs_no == NOBEHIND) { + reg_restore(&(((regbehind_T *)rp) - 1)->save_after, + &backpos); + status = RA_MATCH; + } else { + // We do want a proper match. Need to restore the + // subexpr if we had a match, because they may have + // been set. + if (status == RA_MATCH) { + status = RA_NOMATCH; + restore_subexpr(((regbehind_T *)rp) - 1); + } + } + regstack_pop(&scan); + regstack.ga_len -= sizeof(regbehind_T); + } + } + break; + + case RS_STAR_LONG: + case RS_STAR_SHORT: + { + regstar_T *rst = ((regstar_T *)rp) - 1; + + if (status == RA_MATCH) { + regstack_pop(&scan); + regstack.ga_len -= sizeof(regstar_T); + break; + } + + // Tried once already, restore input pointers. + if (status != RA_BREAK) + reg_restore(&rp->rs_un.regsave, &backpos); + + // Repeat until we found a position where it could match. + for (;; ) { + if (status != RA_BREAK) { + // Tried first position already, advance. + if (rp->rs_state == RS_STAR_LONG) { + // Trying for longest match, but couldn't or + // didn't match -- back up one char. + if (--rst->count < rst->minval) + break; + if (rex.input == rex.line) { + // backup to last char of previous line + if (rex.lnum == 0) { + status = RA_NOMATCH; + break; + } + rex.lnum--; + rex.line = reg_getline(rex.lnum); + // Just in case regrepeat() didn't count right. + if (rex.line == NULL) { + break; + } + rex.input = rex.line + STRLEN(rex.line); + fast_breakcheck(); + } else { + MB_PTR_BACK(rex.line, rex.input); + } + } else { + // Range is backwards, use shortest match first. + // Careful: maxval and minval are exchanged! + // Couldn't or didn't match: try advancing one + // char. + if (rst->count == rst->minval + || regrepeat(OPERAND(rp->rs_scan), 1L) == 0) + break; + ++rst->count; + } + if (got_int) + break; + } else + status = RA_NOMATCH; + + // 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; + break; + } + } + if (status != RA_CONT) { + // Failed. + regstack_pop(&scan); + regstack.ga_len -= sizeof(regstar_T); + status = RA_NOMATCH; + } + } + break; + } + + // If we want to continue the inner loop or didn't pop a state + // continue matching loop + if (status == RA_CONT || rp == (regitem_T *) + ((char *)regstack.ga_data + regstack.ga_len) - 1) + break; + } + + // May need to continue with the inner loop, starting at "scan". + if (status == RA_CONT) + continue; + + // If the regstack is empty or something failed we are done. + if (GA_EMPTY(®stack) || status == RA_FAIL) { + if (scan == NULL) { + // We get here only if there's trouble -- normally "case END" is + // the terminating point. + iemsg(_(e_re_corr)); +#ifdef REGEXP_DEBUG + printf("Premature EOL\n"); +#endif + } + return status == RA_MATCH; + } + + } // End of loop until the regstack is empty. + + // NOTREACHED +} + +/// 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 +{ + 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; + } + + cleanup_subexpr(); + if (REG_MULTI) { + if (rex.reg_startpos[0].lnum < 0) { + rex.reg_startpos[0].lnum = 0; + rex.reg_startpos[0].col = col; + } + if (rex.reg_endpos[0].lnum < 0) { + rex.reg_endpos[0].lnum = rex.lnum; + rex.reg_endpos[0].col = (int)(rex.input - rex.line); + } else { + // Use line number of "\ze". + rex.lnum = rex.reg_endpos[0].lnum; + } + } else { + if (rex.reg_startp[0] == NULL) { + rex.reg_startp[0] = rex.line + col; + } + if (rex.reg_endp[0] == NULL) { + rex.reg_endp[0] = rex.input; + } + } + // Package any found \z(...\) matches for export. Default is none. + unref_extmatch(re_extmatch_out); + re_extmatch_out = NULL; + + if (prog->reghasz == REX_SET) { + int i; + + cleanup_zsubexpr(); + re_extmatch_out = make_extmatch(); + for (i = 0; i < NSUBEXP; i++) { + if (REG_MULTI) { + // Only accept single line matches. + if (reg_startzpos[i].lnum >= 0 + && reg_endzpos[i].lnum == reg_startzpos[i].lnum + && reg_endzpos[i].col >= reg_startzpos[i].col) { + re_extmatch_out->matches[i] = + vim_strnsave(reg_getline(reg_startzpos[i].lnum) + + reg_startzpos[i].col, + reg_endzpos[i].col + - reg_startzpos[i].col); + } + } else { + if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) + re_extmatch_out->matches[i] = + vim_strnsave(reg_startzp[i], reg_endzp[i] - reg_startzp[i]); + } + } + } + return 1 + rex.lnum; +} + +/// Match a regexp against a string ("line" points to the string) or multiple +/// lines (if "line" is NULL, use reg_getline()). +/// @return 0 for failure, or number of lines contained in the match. +static long bt_regexec_both(char_u *line, + colnr_T col, // column to start search + proftime_T *tm, // timeout limit or NULL + int *timed_out) // flag set on timeout or NULL +{ + bt_regprog_T *prog; + char_u *s; + long retval = 0L; + + // Create "regstack" and "backpos" if they are not allocated yet. + // We allocate *_INITIAL amount of bytes first and then set the grow size + // to much bigger value to avoid many malloc calls in case of deep regular + // expressions. + if (regstack.ga_data == NULL) { + // Use an item size of 1 byte, since we push different things + // onto the regstack. + ga_init(®stack, 1, REGSTACK_INITIAL); + ga_grow(®stack, REGSTACK_INITIAL); + ga_set_growsize(®stack, REGSTACK_INITIAL * 8); + } + + if (backpos.ga_data == NULL) { + ga_init(&backpos, sizeof(backpos_T), BACKPOS_INITIAL); + ga_grow(&backpos, BACKPOS_INITIAL); + ga_set_growsize(&backpos, BACKPOS_INITIAL * 8); + } + + if (REG_MULTI) { + prog = (bt_regprog_T *)rex.reg_mmatch->regprog; + line = reg_getline((linenr_T)0); + rex.reg_startpos = rex.reg_mmatch->startpos; + rex.reg_endpos = rex.reg_mmatch->endpos; + } else { + prog = (bt_regprog_T *)rex.reg_match->regprog; + rex.reg_startp = rex.reg_match->startp; + rex.reg_endp = rex.reg_match->endp; + } + + // Be paranoid... + if (prog == NULL || line == NULL) { + iemsg(_(e_null)); + goto theend; + } + + // Check validity of program. + if (prog_magic_wrong()) + goto theend; + + // If the start column is past the maximum column: no need to try. + if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { + goto theend; + } + + // If pattern contains "\c" or "\C": overrule value of rex.reg_ic + if (prog->regflags & RF_ICASE) { + rex.reg_ic = true; + } else if (prog->regflags & RF_NOICASE) { + rex.reg_ic = false; + } + + // If pattern contains "\Z" overrule value of rex.reg_icombine + if (prog->regflags & RF_ICOMBINE) { + rex.reg_icombine = true; + } + + // If there is a "must appear" string, look for it. + if (prog->regmust != NULL) { + int c = utf_ptr2char(prog->regmust); + s = line + col; + + // This is used very often, esp. for ":global". Use two versions of + // the loop to avoid overhead of conditions. + if (!rex.reg_ic) { + while ((s = vim_strchr(s, c)) != NULL) { + if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { + break; // Found it. + } + MB_PTR_ADV(s); + } + } else { + while ((s = cstrchr(s, c)) != NULL) { + if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { + break; // Found it. + } + MB_PTR_ADV(s); + } + } + if (s == NULL) { // Not present. + goto theend; + } + } + + 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(rex.line + col); + if (prog->regstart == NUL + || prog->regstart == c + || (rex.reg_ic + && (utf_fold(prog->regstart) == utf_fold(c) + || (c < 255 && prog->regstart < 255 + && mb_tolower(prog->regstart) == mb_tolower(c))))) { + retval = regtry(prog, col, tm, timed_out); + } else { + retval = 0; + } + } else { + int tm_count = 0; + // Messy cases: unanchored match. + while (!got_int) { + if (prog->regstart != NUL) { + // Skip until the char we know it must start with. + s = cstrchr(rex.line + col, prog->regstart); + if (s == NULL) { + retval = 0; + break; + } + col = (int)(s - rex.line); + } + + // Check for maximum column to try. + if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { + retval = 0; + break; + } + + retval = regtry(prog, col, tm, timed_out); + if (retval > 0) { + break; + } + + // 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 (rex.line[col] == NUL) { + break; + } + col += utfc_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)) { + if (timed_out != NULL) { + *timed_out = true; + } + break; + } + } + } + } + +theend: + // Free "reg_tofree" when it's a bit big. + // Free regstack and backpos if they are bigger than their initial size. + if (reg_tofreelen > 400) { + XFREE_CLEAR(reg_tofree); + } + if (regstack.ga_maxlen > REGSTACK_INITIAL) + ga_clear(®stack); + if (backpos.ga_maxlen > BACKPOS_INITIAL) + ga_clear(&backpos); + + if (retval > 0) { + // Make sure the end is never before the start. Can happen when \zs + // and \ze are used. + if (REG_MULTI) { + const lpos_T *const start = &rex.reg_mmatch->startpos[0]; + const lpos_T *const end = &rex.reg_mmatch->endpos[0]; + + if (end->lnum < start->lnum + || (end->lnum == start->lnum && end->col < start->col)) { + rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0]; + } + } else { + if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) { + rex.reg_match->endp[0] = rex.reg_match->startp[0]; + } + } + } + + return retval; +} + +/* + * Match a regexp against a string. + * "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). + * Uses curbuf for line count and 'iskeyword'. + * If "line_lbr" is true, consider a "\n" in "line" to be a line break. + * + * Returns 0 for failure, number of lines contained in the match otherwise. + */ +static int bt_regexec_nl( + regmatch_T *rmp, + char_u *line, // string to match against + colnr_T col, // column to start looking for match + bool line_lbr) +{ + rex.reg_match = rmp; + rex.reg_mmatch = NULL; + rex.reg_maxline = 0; + rex.reg_line_lbr = line_lbr; + rex.reg_buf = curbuf; + rex.reg_win = NULL; + rex.reg_ic = rmp->rm_ic; + rex.reg_icombine = false; + rex.reg_maxcol = 0; + + long r = bt_regexec_both(line, col, NULL, NULL); + assert(r <= INT_MAX); + return (int)r; +} + + +/// Matches a regexp against multiple lines. +/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). +/// Uses curbuf for line count and 'iskeyword'. +/// +/// @param win Window in which to search or NULL +/// @param buf Buffer in which to search +/// @param lnum Number of line to start looking for match +/// @param col Column to start looking for match +/// @param tm Timeout limit or NULL +/// +/// @return zero if there is no match and number of lines contained in the match +/// otherwise. +static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, + linenr_T lnum, colnr_T col, + proftime_T *tm, int *timed_out) +{ + rex.reg_match = NULL; + rex.reg_mmatch = rmp; + rex.reg_buf = buf; + rex.reg_win = win; + rex.reg_firstlnum = lnum; + rex.reg_maxline = rex.reg_buf->b_ml.ml_line_count - lnum; + rex.reg_line_lbr = false; + rex.reg_ic = rmp->rmm_ic; + rex.reg_icombine = false; + rex.reg_maxcol = rmp->rmm_maxcol; + + return bt_regexec_both(NULL, col, tm, timed_out); +} + +/* + * Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL. + */ +static int re_num_cmp(uint32_t val, char_u *scan) +{ + uint32_t n = (uint32_t)OPERAND_MIN(scan); + + if (OPERAND_CMP(scan) == '>') + return val > n; + if (OPERAND_CMP(scan) == '<') + return val < n; + return val == n; +} + +#ifdef BT_REGEXP_DUMP + +/* + * regdump - dump a regexp onto stdout in vaguely comprehensible form + */ +static void regdump(char_u *pattern, bt_regprog_T *r) +{ + char_u *s; + int op = EXACTLY; // Arbitrary non-END op. + char_u *next; + char_u *end = NULL; + FILE *f; + +#ifdef BT_REGEXP_LOG + f = fopen("bt_regexp_log.log", "a"); +#else + f = stdout; +#endif + if (f == NULL) + return; + fprintf(f, "-------------------------------------\n\r\nregcomp(%s):\r\n", + pattern); + + s = r->program + 1; + // Loop until we find the END that isn't before a referred next (an END + // can also appear in a NOMATCH operand). + while (op != END || s <= end) { + op = OP(s); + fprintf(f, "%2d%s", (int)(s - r->program), regprop(s)); // Where, what. + next = regnext(s); + if (next == NULL) // Next ptr. + fprintf(f, "(0)"); + else + fprintf(f, "(%d)", (int)((s - r->program) + (next - s))); + if (end < next) + end = next; + if (op == BRACE_LIMITS) { + // Two ints + fprintf(f, " minval %" PRId64 ", maxval %" PRId64, + (int64_t)OPERAND_MIN(s), (int64_t)OPERAND_MAX(s)); + s += 8; + } else if (op == BEHIND || op == NOBEHIND) { + // one int + fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); + s += 4; + } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) { + // one int plus comparator + fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); + s += 5; + } + s += 3; + if (op == ANYOF || op == ANYOF + ADD_NL + || op == ANYBUT || op == ANYBUT + ADD_NL + || op == EXACTLY) { + // Literal string, where present. + fprintf(f, "\nxxxxxxxxx\n"); + while (*s != NUL) + fprintf(f, "%c", *s++); + fprintf(f, "\nxxxxxxxxx\n"); + s++; + } + fprintf(f, "\r\n"); + } + + // Header fields of interest. + if (r->regstart != NUL) + fprintf(f, "start `%s' 0x%x; ", r->regstart < 256 + ? (char *)transchar(r->regstart) + : "multibyte", r->regstart); + if (r->reganch) + fprintf(f, "anchored; "); + if (r->regmust != NULL) + fprintf(f, "must have \"%s\"", r->regmust); + fprintf(f, "\r\n"); + +#ifdef BT_REGEXP_LOG + fclose(f); +#endif +} +#endif // BT_REGEXP_DUMP + +#ifdef REGEXP_DEBUG + +/* + * regprop - printable representation of opcode + */ +static char_u *regprop(char_u *op) +{ + char *p; + static char buf[50]; + + STRCPY(buf, ":"); + + switch ((int)OP(op)) { + case BOL: + p = "BOL"; + break; + case EOL: + p = "EOL"; + break; + case RE_BOF: + p = "BOF"; + break; + case RE_EOF: + p = "EOF"; + break; + case CURSOR: + p = "CURSOR"; + break; + case RE_VISUAL: + p = "RE_VISUAL"; + break; + case RE_LNUM: + p = "RE_LNUM"; + break; + case RE_MARK: + p = "RE_MARK"; + break; + case RE_COL: + p = "RE_COL"; + break; + case RE_VCOL: + p = "RE_VCOL"; + break; + case BOW: + p = "BOW"; + break; + case EOW: + p = "EOW"; + break; + case ANY: + p = "ANY"; + break; + case ANY + ADD_NL: + p = "ANY+NL"; + break; + case ANYOF: + p = "ANYOF"; + break; + case ANYOF + ADD_NL: + p = "ANYOF+NL"; + break; + case ANYBUT: + p = "ANYBUT"; + break; + case ANYBUT + ADD_NL: + p = "ANYBUT+NL"; + break; + case IDENT: + p = "IDENT"; + break; + case IDENT + ADD_NL: + p = "IDENT+NL"; + break; + case SIDENT: + p = "SIDENT"; + break; + case SIDENT + ADD_NL: + p = "SIDENT+NL"; + break; + case KWORD: + p = "KWORD"; + break; + case KWORD + ADD_NL: + p = "KWORD+NL"; + break; + case SKWORD: + p = "SKWORD"; + break; + case SKWORD + ADD_NL: + p = "SKWORD+NL"; + break; + case FNAME: + p = "FNAME"; + break; + case FNAME + ADD_NL: + p = "FNAME+NL"; + break; + case SFNAME: + p = "SFNAME"; + break; + case SFNAME + ADD_NL: + p = "SFNAME+NL"; + break; + case PRINT: + p = "PRINT"; + break; + case PRINT + ADD_NL: + p = "PRINT+NL"; + break; + case SPRINT: + p = "SPRINT"; + break; + case SPRINT + ADD_NL: + p = "SPRINT+NL"; + break; + case WHITE: + p = "WHITE"; + break; + case WHITE + ADD_NL: + p = "WHITE+NL"; + break; + case NWHITE: + p = "NWHITE"; + break; + case NWHITE + ADD_NL: + p = "NWHITE+NL"; + break; + case DIGIT: + p = "DIGIT"; + break; + case DIGIT + ADD_NL: + p = "DIGIT+NL"; + break; + case NDIGIT: + p = "NDIGIT"; + break; + case NDIGIT + ADD_NL: + p = "NDIGIT+NL"; + break; + case HEX: + p = "HEX"; + break; + case HEX + ADD_NL: + p = "HEX+NL"; + break; + case NHEX: + p = "NHEX"; + break; + case NHEX + ADD_NL: + p = "NHEX+NL"; + break; + case OCTAL: + p = "OCTAL"; + break; + case OCTAL + ADD_NL: + p = "OCTAL+NL"; + break; + case NOCTAL: + p = "NOCTAL"; + break; + case NOCTAL + ADD_NL: + p = "NOCTAL+NL"; + break; + case WORD: + p = "WORD"; + break; + case WORD + ADD_NL: + p = "WORD+NL"; + break; + case NWORD: + p = "NWORD"; + break; + case NWORD + ADD_NL: + p = "NWORD+NL"; + break; + case HEAD: + p = "HEAD"; + break; + case HEAD + ADD_NL: + p = "HEAD+NL"; + break; + case NHEAD: + p = "NHEAD"; + break; + case NHEAD + ADD_NL: + p = "NHEAD+NL"; + break; + case ALPHA: + p = "ALPHA"; + break; + case ALPHA + ADD_NL: + p = "ALPHA+NL"; + break; + case NALPHA: + p = "NALPHA"; + break; + case NALPHA + ADD_NL: + p = "NALPHA+NL"; + break; + case LOWER: + p = "LOWER"; + break; + case LOWER + ADD_NL: + p = "LOWER+NL"; + break; + case NLOWER: + p = "NLOWER"; + break; + case NLOWER + ADD_NL: + p = "NLOWER+NL"; + break; + case UPPER: + p = "UPPER"; + break; + case UPPER + ADD_NL: + p = "UPPER+NL"; + break; + case NUPPER: + p = "NUPPER"; + break; + case NUPPER + ADD_NL: + p = "NUPPER+NL"; + break; + case BRANCH: + p = "BRANCH"; + break; + case EXACTLY: + p = "EXACTLY"; + break; + case NOTHING: + p = "NOTHING"; + break; + case BACK: + p = "BACK"; + break; + case END: + p = "END"; + break; + case MOPEN + 0: + p = "MATCH START"; + break; + case MOPEN + 1: + case MOPEN + 2: + case MOPEN + 3: + case MOPEN + 4: + case MOPEN + 5: + case MOPEN + 6: + case MOPEN + 7: + case MOPEN + 8: + case MOPEN + 9: + sprintf(buf + STRLEN(buf), "MOPEN%d", OP(op) - MOPEN); + p = NULL; + break; + case MCLOSE + 0: + p = "MATCH END"; + break; + case MCLOSE + 1: + case MCLOSE + 2: + case MCLOSE + 3: + case MCLOSE + 4: + case MCLOSE + 5: + case MCLOSE + 6: + case MCLOSE + 7: + case MCLOSE + 8: + case MCLOSE + 9: + sprintf(buf + STRLEN(buf), "MCLOSE%d", OP(op) - MCLOSE); + p = NULL; + break; + case BACKREF + 1: + case BACKREF + 2: + case BACKREF + 3: + case BACKREF + 4: + case BACKREF + 5: + case BACKREF + 6: + case BACKREF + 7: + case BACKREF + 8: + case BACKREF + 9: + sprintf(buf + STRLEN(buf), "BACKREF%d", OP(op) - BACKREF); + p = NULL; + break; + case NOPEN: + p = "NOPEN"; + break; + case NCLOSE: + p = "NCLOSE"; + break; + case ZOPEN + 1: + case ZOPEN + 2: + case ZOPEN + 3: + case ZOPEN + 4: + case ZOPEN + 5: + case ZOPEN + 6: + case ZOPEN + 7: + case ZOPEN + 8: + case ZOPEN + 9: + sprintf(buf + STRLEN(buf), "ZOPEN%d", OP(op) - ZOPEN); + p = NULL; + break; + case ZCLOSE + 1: + case ZCLOSE + 2: + case ZCLOSE + 3: + case ZCLOSE + 4: + case ZCLOSE + 5: + case ZCLOSE + 6: + case ZCLOSE + 7: + case ZCLOSE + 8: + case ZCLOSE + 9: + sprintf(buf + STRLEN(buf), "ZCLOSE%d", OP(op) - ZCLOSE); + p = NULL; + break; + case ZREF + 1: + case ZREF + 2: + case ZREF + 3: + case ZREF + 4: + case ZREF + 5: + case ZREF + 6: + case ZREF + 7: + case ZREF + 8: + case ZREF + 9: + sprintf(buf + STRLEN(buf), "ZREF%d", OP(op) - ZREF); + p = NULL; + break; + case STAR: + p = "STAR"; + break; + case PLUS: + p = "PLUS"; + break; + case NOMATCH: + p = "NOMATCH"; + break; + case MATCH: + p = "MATCH"; + break; + case BEHIND: + p = "BEHIND"; + break; + case NOBEHIND: + p = "NOBEHIND"; + break; + case SUBPAT: + p = "SUBPAT"; + break; + case BRACE_LIMITS: + p = "BRACE_LIMITS"; + break; + case BRACE_SIMPLE: + p = "BRACE_SIMPLE"; + break; + case BRACE_COMPLEX + 0: + case BRACE_COMPLEX + 1: + case BRACE_COMPLEX + 2: + case BRACE_COMPLEX + 3: + case BRACE_COMPLEX + 4: + case BRACE_COMPLEX + 5: + case BRACE_COMPLEX + 6: + case BRACE_COMPLEX + 7: + case BRACE_COMPLEX + 8: + case BRACE_COMPLEX + 9: + sprintf(buf + STRLEN(buf), "BRACE_COMPLEX%d", OP(op) - BRACE_COMPLEX); + p = NULL; + break; + case MULTIBYTECODE: + p = "MULTIBYTECODE"; + break; + case NEWL: + p = "NEWL"; + break; + default: + sprintf(buf + STRLEN(buf), "corrupt %d", OP(op)); + p = NULL; + break; + } + if (p != NULL) + STRCAT(buf, p); + return (char_u *)buf; +} +#endif // REGEXP_DEBUG diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 1d112bd64a..913cfb2074 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -34,7 +34,7 @@ // In the NFA engine: how many states are allowed. #define NFA_MAX_STATES 100000 -#define NFA_TOO_EXPENSIVE -1 +#define NFA_TOO_EXPENSIVE (-1) // Which regexp engine to use? Needed for vim_regcomp(). // Must match with 'regexpengine'. diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 5df5cc5975..a8d6e9c40b 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -16,23 +16,22 @@ #include "nvim/ascii.h" #include "nvim/garray.h" - -/* - * Logging of NFA engine. - * - * The NFA engine can write four log files: - * - Error log: Contains NFA engine's fatal errors. - * - Dump log: Contains compiled NFA state machine's information. - * - Run log: Contains information of matching procedure. - * - Debug log: Contains detailed information of matching procedure. Can be - * disabled by undefining NFA_REGEXP_DEBUG_LOG. - * The first one can also be used without debug mode. - * The last three are enabled when compiled as debug mode and individually - * disabled by commenting them out. - * The log files can get quite big! - * Do disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in - * regexp.c - */ +#include "nvim/os/input.h" + +// Logging of NFA engine. +// +// The NFA engine can write four log files: +// - Error log: Contains NFA engine's fatal errors. +// - Dump log: Contains compiled NFA state machine's information. +// - Run log: Contains information of matching procedure. +// - Debug log: Contains detailed information of matching procedure. Can be +// disabled by undefining NFA_REGEXP_DEBUG_LOG. +// The first one can also be used without debug mode. +// The last three are enabled when compiled as debug mode and individually +// disabled by commenting them out. +// The log files can get quite big! +// To disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in +// regexp.c #ifdef REGEXP_DEBUG # define NFA_REGEXP_ERROR_LOG "nfa_regexp_error.log" # define NFA_REGEXP_DUMP_LOG "nfa_regexp_dump.log" @@ -712,7 +711,6 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) static void nfa_emit_equi_class(int c) { #define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT); -#define EMITMBC(c) EMIT(c); EMIT(NFA_CONCAT); { #define A_grave 0xc0 @@ -772,361 +770,518 @@ static void nfa_emit_equi_class(int c) #define y_diaeresis 0xff switch (c) { case 'A': case A_grave: case A_acute: case A_circumflex: - case A_virguilla: case A_diaeresis: case A_ring: - CASEMBC(0x100) CASEMBC(0x102) CASEMBC(0x104) - CASEMBC(0x1cd) CASEMBC(0x1de) CASEMBC(0x1e0) - CASEMBC(0x1ea2) - EMIT2('A'); EMIT2(A_grave); EMIT2(A_acute); - EMIT2(A_circumflex); EMIT2(A_virguilla); - EMIT2(A_diaeresis); EMIT2(A_ring); - EMITMBC(0x100) EMITMBC(0x102) EMITMBC(0x104) - EMITMBC(0x1cd) EMITMBC(0x1de) EMITMBC(0x1e0) - EMITMBC(0x1ea2) + case A_virguilla: case A_diaeresis: case A_ring: + case 0x100: case 0x102: case 0x104: case 0x1cd: + case 0x1de: case 0x1e0: case 0x1fa: case 0x200: + case 0x202: case 0x226: case 0x23a: case 0x1e00: + case 0x1ea0: case 0x1ea2: case 0x1ea4: case 0x1ea6: + case 0x1ea8: case 0x1eaa: case 0x1eac: case 0x1eae: + case 0x1eb0: case 0x1eb2: case 0x1eb4: case 0x1eb6: + EMIT2('A') EMIT2(A_grave) EMIT2(A_acute) + EMIT2(A_circumflex) EMIT2(A_virguilla) + EMIT2(A_diaeresis) EMIT2(A_ring) + EMIT2(0x100) EMIT2(0x102) EMIT2(0x104) + EMIT2(0x1cd) EMIT2(0x1de) EMIT2(0x1e0) + EMIT2(0x1fa) EMIT2(0x200) EMIT2(0x202) + EMIT2(0x226) EMIT2(0x23a) EMIT2(0x1e00) + EMIT2(0x1ea0) EMIT2(0x1ea2) EMIT2(0x1ea4) + EMIT2(0x1ea6) EMIT2(0x1ea8) EMIT2(0x1eaa) + EMIT2(0x1eac) EMIT2(0x1eae) EMIT2(0x1eb0) + EMIT2(0x1eb2) EMIT2(0x1eb6) EMIT2(0x1eb4) return; - case 'B': CASEMBC(0x1e02) CASEMBC(0x1e06) - EMIT2('B'); EMITMBC(0x1e02) EMITMBC(0x1e06) + case 'B': case 0x181: case 0x243: case 0x1e02: + case 0x1e04: case 0x1e06: + EMIT2('B') + EMIT2(0x181) EMIT2(0x243) EMIT2(0x1e02) + EMIT2(0x1e04) EMIT2(0x1e06) return; - case 'C': case C_cedilla: CASEMBC(0x106) CASEMBC(0x108) CASEMBC(0x10a) - CASEMBC(0x10c) - EMIT2('C'); EMIT2(C_cedilla); EMITMBC(0x106) EMITMBC(0x108) - EMITMBC(0x10a) EMITMBC(0x10c) + case 'C': case C_cedilla: case 0x106: case 0x108: + case 0x10a: case 0x10c: case 0x187: case 0x23b: + case 0x1e08: case 0xa792: + EMIT2('C') EMIT2(C_cedilla) + EMIT2(0x106) EMIT2(0x108) EMIT2(0x10a) + EMIT2(0x10c) EMIT2(0x187) EMIT2(0x23b) + EMIT2(0x1e08) EMIT2(0xa792) return; - case 'D': CASEMBC(0x10e) CASEMBC(0x110) CASEMBC(0x1e0a) - CASEMBC(0x1e0e) CASEMBC(0x1e10) - EMIT2('D'); EMITMBC(0x10e) EMITMBC(0x110) EMITMBC(0x1e0a) - EMITMBC(0x1e0e) EMITMBC(0x1e10) + case 'D': case 0x10e: case 0x110: case 0x18a: + case 0x1e0a: case 0x1e0c: case 0x1e0e: case 0x1e10: + case 0x1e12: + EMIT2('D') EMIT2(0x10e) EMIT2(0x110) EMIT2(0x18a) + EMIT2(0x1e0a) EMIT2(0x1e0c) EMIT2(0x1e0e) + EMIT2(0x1e10) EMIT2(0x1e12) return; case 'E': case E_grave: case E_acute: case E_circumflex: - case E_diaeresis: CASEMBC(0x112) CASEMBC(0x114) - CASEMBC(0x116) CASEMBC(0x118) CASEMBC(0x11a) - CASEMBC(0x1eba) CASEMBC(0x1ebc) - EMIT2('E'); EMIT2(E_grave); EMIT2(E_acute); - EMIT2(E_circumflex); EMIT2(E_diaeresis); - EMITMBC(0x112) EMITMBC(0x114) EMITMBC(0x116) - EMITMBC(0x118) EMITMBC(0x11a) EMITMBC(0x1eba) - EMITMBC(0x1ebc) + case E_diaeresis: case 0x112: case 0x114: case 0x116: + case 0x118: case 0x11a: case 0x204: case 0x206: + case 0x228: case 0x246: case 0x1e14: case 0x1e16: + case 0x1e18: case 0x1e1a: case 0x1e1c: case 0x1eb8: + case 0x1eba: case 0x1ebc: case 0x1ebe: case 0x1ec0: + case 0x1ec2: case 0x1ec4: case 0x1ec6: + EMIT2('E') EMIT2(E_grave) EMIT2(E_acute) + EMIT2(E_circumflex) EMIT2(E_diaeresis) + EMIT2(0x112) EMIT2(0x114) EMIT2(0x116) + EMIT2(0x118) EMIT2(0x11a) EMIT2(0x204) + EMIT2(0x206) EMIT2(0x228) EMIT2(0x246) + EMIT2(0x1e14) EMIT2(0x1e16) EMIT2(0x1e18) + EMIT2(0x1e1a) EMIT2(0x1e1c) EMIT2(0x1eb8) + EMIT2(0x1eba) EMIT2(0x1ebc) EMIT2(0x1ebe) + EMIT2(0x1ec0) EMIT2(0x1ec2) EMIT2(0x1ec4) + EMIT2(0x1ec6) return; - case 'F': CASEMBC(0x1e1e) - EMIT2('F'); EMITMBC(0x1e1e) + case 'F': case 0x191: case 0x1e1e: case 0xa798: + EMIT2('F') EMIT2(0x191) EMIT2(0x1e1e) EMIT2(0xa798) return; - case 'G': CASEMBC(0x11c) CASEMBC(0x11e) CASEMBC(0x120) - CASEMBC(0x122) CASEMBC(0x1e4) CASEMBC(0x1e6) - CASEMBC(0x1f4) CASEMBC(0x1e20) - EMIT2('G'); EMITMBC(0x11c) EMITMBC(0x11e) EMITMBC(0x120) - EMITMBC(0x122) EMITMBC(0x1e4) EMITMBC(0x1e6) - EMITMBC(0x1f4) EMITMBC(0x1e20) + case 'G': case 0x11c: case 0x11e: case 0x120: + case 0x122: case 0x193: case 0x1e4: case 0x1e6: + case 0x1f4: case 0x1e20: case 0xa7a0: + EMIT2('G') EMIT2(0x11c) EMIT2(0x11e) EMIT2(0x120) + EMIT2(0x122) EMIT2(0x193) EMIT2(0x1e4) + EMIT2(0x1e6) EMIT2(0x1f4) EMIT2(0x1e20) + EMIT2(0xa7a0) return; - case 'H': CASEMBC(0x124) CASEMBC(0x126) CASEMBC(0x1e22) - CASEMBC(0x1e26) CASEMBC(0x1e28) - EMIT2('H'); EMITMBC(0x124) EMITMBC(0x126) EMITMBC(0x1e22) - EMITMBC(0x1e26) EMITMBC(0x1e28) + case 'H': case 0x124: case 0x126: case 0x21e: + case 0x1e22: case 0x1e24: case 0x1e26: case 0x1e28: + case 0x1e2a: case 0x2c67: + EMIT2('H') EMIT2(0x124) EMIT2(0x126) EMIT2(0x21e) + EMIT2(0x1e22) EMIT2(0x1e24) EMIT2(0x1e26) + EMIT2(0x1e28) EMIT2(0x1e2a) EMIT2(0x2c67) return; case 'I': case I_grave: case I_acute: case I_circumflex: - case I_diaeresis: CASEMBC(0x128) CASEMBC(0x12a) - CASEMBC(0x12c) CASEMBC(0x12e) CASEMBC(0x130) - CASEMBC(0x1cf) CASEMBC(0x1ec8) - EMIT2('I'); EMIT2(I_grave); EMIT2(I_acute); - EMIT2(I_circumflex); EMIT2(I_diaeresis); - EMITMBC(0x128) EMITMBC(0x12a) - EMITMBC(0x12c) EMITMBC(0x12e) EMITMBC(0x130) - EMITMBC(0x1cf) EMITMBC(0x1ec8) + case I_diaeresis: case 0x128: case 0x12a: case 0x12c: + case 0x12e: case 0x130: case 0x197: case 0x1cf: + case 0x208: case 0x20a: case 0x1e2c: case 0x1e2e: + case 0x1ec8: case 0x1eca: + EMIT2('I') EMIT2(I_grave) EMIT2(I_acute) + EMIT2(I_circumflex) EMIT2(I_diaeresis) + EMIT2(0x128) EMIT2(0x12a) EMIT2(0x12c) + EMIT2(0x12e) EMIT2(0x130) EMIT2(0x197) + EMIT2(0x1cf) EMIT2(0x208) EMIT2(0x20a) + EMIT2(0x1e2c) EMIT2(0x1e2e) EMIT2(0x1ec8) + EMIT2(0x1eca) return; - case 'J': CASEMBC(0x134) - EMIT2('J'); EMITMBC(0x134) + case 'J': case 0x134: case 0x248: + EMIT2('J') EMIT2(0x134) EMIT2(0x248) return; - case 'K': CASEMBC(0x136) CASEMBC(0x1e8) CASEMBC(0x1e30) - CASEMBC(0x1e34) - EMIT2('K'); EMITMBC(0x136) EMITMBC(0x1e8) EMITMBC(0x1e30) - EMITMBC(0x1e34) + case 'K': case 0x136: case 0x198: case 0x1e8: case 0x1e30: + case 0x1e32: case 0x1e34: case 0x2c69: case 0xa740: + EMIT2('K') EMIT2(0x136) EMIT2(0x198) EMIT2(0x1e8) + EMIT2(0x1e30) EMIT2(0x1e32) EMIT2(0x1e34) + EMIT2(0x2c69) EMIT2(0xa740) return; - case 'L': CASEMBC(0x139) CASEMBC(0x13b) CASEMBC(0x13d) - CASEMBC(0x13f) CASEMBC(0x141) CASEMBC(0x1e3a) - EMIT2('L'); EMITMBC(0x139) EMITMBC(0x13b) EMITMBC(0x13d) - EMITMBC(0x13f) EMITMBC(0x141) EMITMBC(0x1e3a) + case 'L': case 0x139: case 0x13b: case 0x13d: + case 0x13f: case 0x141: case 0x23d: case 0x1e36: + case 0x1e38: case 0x1e3a: case 0x1e3c: case 0x2c60: + EMIT2('L') EMIT2(0x139) EMIT2(0x13b) + EMIT2(0x13d) EMIT2(0x13f) EMIT2(0x141) + EMIT2(0x23d) EMIT2(0x1e36) EMIT2(0x1e38) + EMIT2(0x1e3a) EMIT2(0x1e3c) EMIT2(0x2c60) return; - case 'M': CASEMBC(0x1e3e) CASEMBC(0x1e40) - EMIT2('M'); EMITMBC(0x1e3e) EMITMBC(0x1e40) + case 'M': case 0x1e3e: case 0x1e40: case 0x1e42: + EMIT2('M') EMIT2(0x1e3e) EMIT2(0x1e40) + EMIT2(0x1e42) return; - case 'N': case N_virguilla: CASEMBC(0x143) CASEMBC(0x145) - CASEMBC(0x147) CASEMBC(0x1e44) CASEMBC(0x1e48) - EMIT2('N'); EMIT2(N_virguilla); - EMITMBC(0x143) EMITMBC(0x145) - EMITMBC(0x147) EMITMBC(0x1e44) EMITMBC(0x1e48) + case 'N': case N_virguilla: + case 0x143: case 0x145: case 0x147: case 0x1f8: + case 0x1e44: case 0x1e46: case 0x1e48: case 0x1e4a: + case 0xa7a4: + EMIT2('N') EMIT2(N_virguilla) + EMIT2(0x143) EMIT2(0x145) EMIT2(0x147) + EMIT2(0x1f8) EMIT2(0x1e44) EMIT2(0x1e46) + EMIT2(0x1e48) EMIT2(0x1e4a) EMIT2(0xa7a4) return; case 'O': case O_grave: case O_acute: case O_circumflex: - case O_virguilla: case O_diaeresis: case O_slash: - CASEMBC(0x14c) CASEMBC(0x14e) CASEMBC(0x150) - CASEMBC(0x1a0) CASEMBC(0x1d1) CASEMBC(0x1ea) - CASEMBC(0x1ec) CASEMBC(0x1ece) - EMIT2('O'); EMIT2(O_grave); EMIT2(O_acute); - EMIT2(O_circumflex); EMIT2(O_virguilla); - EMIT2(O_diaeresis); EMIT2(O_slash); - EMITMBC(0x14c) EMITMBC(0x14e) EMITMBC(0x150) - EMITMBC(0x1a0) EMITMBC(0x1d1) EMITMBC(0x1ea) - EMITMBC(0x1ec) EMITMBC(0x1ece) + case O_virguilla: case O_diaeresis: case O_slash: + case 0x14c: case 0x14e: case 0x150: case 0x19f: + case 0x1a0: case 0x1d1: case 0x1ea: case 0x1ec: + case 0x1fe: case 0x20c: case 0x20e: case 0x22a: + case 0x22c: case 0x22e: case 0x230: case 0x1e4c: + case 0x1e4e: case 0x1e50: case 0x1e52: case 0x1ecc: + case 0x1ece: case 0x1ed0: case 0x1ed2: case 0x1ed4: + case 0x1ed6: case 0x1ed8: case 0x1eda: case 0x1edc: + case 0x1ede: case 0x1ee0: case 0x1ee2: + EMIT2('O') EMIT2(O_grave) EMIT2(O_acute) + EMIT2(O_circumflex) EMIT2(O_virguilla) + EMIT2(O_diaeresis) EMIT2(O_slash) + EMIT2(0x14c) EMIT2(0x14e) EMIT2(0x150) + EMIT2(0x19f) EMIT2(0x1a0) EMIT2(0x1d1) + EMIT2(0x1ea) EMIT2(0x1ec) EMIT2(0x1fe) + EMIT2(0x20c) EMIT2(0x20e) EMIT2(0x22a) + EMIT2(0x22c) EMIT2(0x22e) EMIT2(0x230) + EMIT2(0x1e4c) EMIT2(0x1e4e) EMIT2(0x1e50) + EMIT2(0x1e52) EMIT2(0x1ecc) EMIT2(0x1ece) + EMIT2(0x1ed0) EMIT2(0x1ed2) EMIT2(0x1ed4) + EMIT2(0x1ed6) EMIT2(0x1ed8) EMIT2(0x1eda) + EMIT2(0x1edc) EMIT2(0x1ede) EMIT2(0x1ee0) + EMIT2(0x1ee2) + return; + + case 'P': case 0x1a4: case 0x1e54: case 0x1e56: case 0x2c63: + EMIT2('P') EMIT2(0x1a4) EMIT2(0x1e54) EMIT2(0x1e56) + EMIT2(0x2c63) return; - case 'P': case 0x1e54: case 0x1e56: - EMIT2('P'); EMITMBC(0x1e54) EMITMBC(0x1e56) + case 'Q': case 0x24a: + EMIT2('Q') EMIT2(0x24a) return; - case 'R': CASEMBC(0x154) CASEMBC(0x156) CASEMBC(0x158) - CASEMBC(0x1e58) CASEMBC(0x1e5e) - EMIT2('R'); EMITMBC(0x154) EMITMBC(0x156) EMITMBC(0x158) - EMITMBC(0x1e58) EMITMBC(0x1e5e) + case 'R': case 0x154: case 0x156: case 0x158: case 0x210: + case 0x212: case 0x24c: case 0x1e58: case 0x1e5a: + case 0x1e5c: case 0x1e5e: case 0x2c64: case 0xa7a6: + EMIT2('R') EMIT2(0x154) EMIT2(0x156) EMIT2(0x158) + EMIT2(0x210) EMIT2(0x212) EMIT2(0x24c) EMIT2(0x1e58) + EMIT2(0x1e5a) EMIT2(0x1e5c) EMIT2(0x1e5e) EMIT2(0x2c64) + EMIT2(0xa7a6) return; - case 'S': CASEMBC(0x15a) CASEMBC(0x15c) CASEMBC(0x15e) - CASEMBC(0x160) CASEMBC(0x1e60) - EMIT2('S'); EMITMBC(0x15a) EMITMBC(0x15c) EMITMBC(0x15e) - EMITMBC(0x160) EMITMBC(0x1e60) + case 'S': case 0x15a: case 0x15c: case 0x15e: case 0x160: + case 0x218: case 0x1e60: case 0x1e62: case 0x1e64: + case 0x1e66: case 0x1e68: case 0x2c7e: case 0xa7a8: + EMIT2('S') EMIT2(0x15a) EMIT2(0x15c) EMIT2(0x15e) + EMIT2(0x160) EMIT2(0x218) EMIT2(0x1e60) EMIT2(0x1e62) + EMIT2(0x1e64) EMIT2(0x1e66) EMIT2(0x1e68) EMIT2(0x2c7e) + EMIT2(0xa7a8) return; - case 'T': CASEMBC(0x162) CASEMBC(0x164) CASEMBC(0x166) - CASEMBC(0x1e6a) CASEMBC(0x1e6e) - EMIT2('T'); EMITMBC(0x162) EMITMBC(0x164) EMITMBC(0x166) - EMITMBC(0x1e6a) EMITMBC(0x1e6e) + case 'T': case 0x162: case 0x164: case 0x166: case 0x1ac: + case 0x1ae: case 0x21a: case 0x23e: case 0x1e6a: case 0x1e6c: + case 0x1e6e: case 0x1e70: + EMIT2('T') EMIT2(0x162) EMIT2(0x164) EMIT2(0x166) + EMIT2(0x1ac) EMIT2(0x1ae) EMIT2(0x23e) EMIT2(0x21a) + EMIT2(0x1e6a) EMIT2(0x1e6c) EMIT2(0x1e6e) EMIT2(0x1e70) return; case 'U': case U_grave: case U_acute: case U_diaeresis: - case U_circumflex: CASEMBC(0x168) CASEMBC(0x16a) - CASEMBC(0x16c) CASEMBC(0x16e) CASEMBC(0x170) - CASEMBC(0x172) CASEMBC(0x1af) CASEMBC(0x1d3) - CASEMBC(0x1ee6) - EMIT2('U'); EMIT2(U_grave); EMIT2(U_acute); - EMIT2(U_diaeresis); EMIT2(U_circumflex); - EMITMBC(0x168) EMITMBC(0x16a) - EMITMBC(0x16c) EMITMBC(0x16e) EMITMBC(0x170) - EMITMBC(0x172) EMITMBC(0x1af) EMITMBC(0x1d3) - EMITMBC(0x1ee6) + case U_circumflex: case 0x168: case 0x16a: case 0x16c: + case 0x16e: case 0x170: case 0x172: case 0x1af: + case 0x1d3: case 0x1d5: case 0x1d7: case 0x1d9: + case 0x1db: case 0x214: case 0x216: case 0x244: + case 0x1e72: case 0x1e74: case 0x1e76: case 0x1e78: + case 0x1e7a: case 0x1ee4: case 0x1ee6: case 0x1ee8: + case 0x1eea: case 0x1eec: case 0x1eee: case 0x1ef0: + EMIT2('U') EMIT2(U_grave) EMIT2(U_acute) + EMIT2(U_diaeresis) EMIT2(U_circumflex) + EMIT2(0x168) EMIT2(0x16a) + EMIT2(0x16c) EMIT2(0x16e) EMIT2(0x170) + EMIT2(0x172) EMIT2(0x1af) EMIT2(0x1d3) + EMIT2(0x1d5) EMIT2(0x1d7) EMIT2(0x1d9) + EMIT2(0x1db) EMIT2(0x214) EMIT2(0x216) + EMIT2(0x244) EMIT2(0x1e72) EMIT2(0x1e74) + EMIT2(0x1e76) EMIT2(0x1e78) EMIT2(0x1e7a) + EMIT2(0x1ee4) EMIT2(0x1ee6) EMIT2(0x1ee8) + EMIT2(0x1eea) EMIT2(0x1eec) EMIT2(0x1eee) + EMIT2(0x1ef0) return; - case 'V': CASEMBC(0x1e7c) - EMIT2('V'); EMITMBC(0x1e7c) + case 'V': case 0x1b2: case 0x1e7c: case 0x1e7e: + EMIT2('V') EMIT2(0x1b2) EMIT2(0x1e7c) EMIT2(0x1e7e) return; - case 'W': CASEMBC(0x174) CASEMBC(0x1e80) CASEMBC(0x1e82) - CASEMBC(0x1e84) CASEMBC(0x1e86) - EMIT2('W'); EMITMBC(0x174) EMITMBC(0x1e80) EMITMBC(0x1e82) - EMITMBC(0x1e84) EMITMBC(0x1e86) + case 'W': case 0x174: case 0x1e80: case 0x1e82: case 0x1e84: + case 0x1e86: case 0x1e88: + EMIT2('W') EMIT2(0x174) EMIT2(0x1e80) EMIT2(0x1e82) + EMIT2(0x1e84) EMIT2(0x1e86) EMIT2(0x1e88) return; - case 'X': CASEMBC(0x1e8a) CASEMBC(0x1e8c) - EMIT2('X'); EMITMBC(0x1e8a) EMITMBC(0x1e8c) + case 'X': case 0x1e8a: case 0x1e8c: + EMIT2('X') EMIT2(0x1e8a) EMIT2(0x1e8c) return; - case 'Y': case Y_acute: CASEMBC(0x176) CASEMBC(0x178) - CASEMBC(0x1e8e) CASEMBC(0x1ef2) CASEMBC(0x1ef6) - CASEMBC(0x1ef8) - EMIT2('Y'); EMIT2(Y_acute); - EMITMBC(0x176) EMITMBC(0x178) - EMITMBC(0x1e8e) EMITMBC(0x1ef2) EMITMBC(0x1ef6) - EMITMBC(0x1ef8) + case 'Y': case Y_acute: case 0x176: case 0x178: + case 0x1b3: case 0x232: case 0x24e: case 0x1e8e: + case 0x1ef2: case 0x1ef4: case 0x1ef6: case 0x1ef8: + EMIT2('Y') EMIT2(Y_acute) + EMIT2(0x176) EMIT2(0x178) EMIT2(0x1b3) + EMIT2(0x232) EMIT2(0x24e) EMIT2(0x1e8e) + EMIT2(0x1ef2) EMIT2(0x1ef4) EMIT2(0x1ef6) + EMIT2(0x1ef8) return; - case 'Z': CASEMBC(0x179) CASEMBC(0x17b) CASEMBC(0x17d) - CASEMBC(0x1b5) CASEMBC(0x1e90) CASEMBC(0x1e94) - EMIT2('Z'); EMITMBC(0x179) EMITMBC(0x17b) EMITMBC(0x17d) - EMITMBC(0x1b5) EMITMBC(0x1e90) EMITMBC(0x1e94) + case 'Z': case 0x179: case 0x17b: case 0x17d: + case 0x1b5: case 0x1e90: case 0x1e92: case 0x1e94: + case 0x2c6b: + EMIT2('Z') EMIT2(0x179) EMIT2(0x17b) EMIT2(0x17d) + EMIT2(0x1b5) EMIT2(0x1e90) EMIT2(0x1e92) + EMIT2(0x1e94) EMIT2(0x2c6b) return; case 'a': case a_grave: case a_acute: case a_circumflex: - case a_virguilla: case a_diaeresis: case a_ring: - CASEMBC(0x101) CASEMBC(0x103) CASEMBC(0x105) - CASEMBC(0x1ce) CASEMBC(0x1df) CASEMBC(0x1e1) - CASEMBC(0x1ea3) - EMIT2('a'); EMIT2(a_grave); EMIT2(a_acute); - EMIT2(a_circumflex); EMIT2(a_virguilla); - EMIT2(a_diaeresis); EMIT2(a_ring); - EMITMBC(0x101) EMITMBC(0x103) EMITMBC(0x105) - EMITMBC(0x1ce) EMITMBC(0x1df) EMITMBC(0x1e1) - EMITMBC(0x1ea3) + case a_virguilla: case a_diaeresis: case a_ring: + case 0x101: case 0x103: case 0x105: case 0x1ce: + case 0x1df: case 0x1e1: case 0x1fb: case 0x201: + case 0x203: case 0x227: case 0x1d8f: case 0x1e01: + case 0x1e9a: case 0x1ea1: case 0x1ea3: case 0x1ea5: + case 0x1ea7: case 0x1ea9: case 0x1eab: case 0x1ead: + case 0x1eaf: case 0x1eb1: case 0x1eb3: case 0x1eb5: + case 0x1eb7: case 0x2c65: + EMIT2('a') EMIT2(a_grave) EMIT2(a_acute) + EMIT2(a_circumflex) EMIT2(a_virguilla) + EMIT2(a_diaeresis) EMIT2(a_ring) + EMIT2(0x101) EMIT2(0x103) EMIT2(0x105) + EMIT2(0x1ce) EMIT2(0x1df) EMIT2(0x1e1) + EMIT2(0x1fb) EMIT2(0x201) EMIT2(0x203) + EMIT2(0x227) EMIT2(0x1d8f) EMIT2(0x1e01) + EMIT2(0x1e9a) EMIT2(0x1ea1) EMIT2(0x1ea3) + EMIT2(0x1ea5) EMIT2(0x1ea7) EMIT2(0x1ea9) + EMIT2(0x1eab) EMIT2(0x1ead) EMIT2(0x1eaf) + EMIT2(0x1eb1) EMIT2(0x1eb3) EMIT2(0x1eb5) + EMIT2(0x1eb7) EMIT2(0x2c65) return; - case 'b': CASEMBC(0x1e03) CASEMBC(0x1e07) - EMIT2('b'); EMITMBC(0x1e03) EMITMBC(0x1e07) + case 'b': case 0x180: case 0x253: case 0x1d6c: case 0x1d80: + case 0x1e03: case 0x1e05: case 0x1e07: + EMIT2('b') EMIT2(0x180) EMIT2(0x253) EMIT2(0x1d6c) + EMIT2(0x1d80) EMIT2(0x1e03) EMIT2(0x1e05) EMIT2(0x1e07) return; - case 'c': case c_cedilla: CASEMBC(0x107) CASEMBC(0x109) - CASEMBC(0x10b) CASEMBC(0x10d) - EMIT2('c'); EMIT2(c_cedilla); - EMITMBC(0x107) EMITMBC(0x109) - EMITMBC(0x10b) EMITMBC(0x10d) + case 'c': case c_cedilla: case 0x107: case 0x109: case 0x10b: + case 0x10d: case 0x188: case 0x23c: case 0x1e09: case 0xa793: + case 0xa794: + EMIT2('c') EMIT2(c_cedilla) + EMIT2(0x107) EMIT2(0x109) EMIT2(0x10b) + EMIT2(0x10d) EMIT2(0x188) EMIT2(0x23c) + EMIT2(0x1e09) EMIT2(0xa793) EMIT2(0xa794) return; - case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1e0b) - CASEMBC(0x1e0f) CASEMBC(0x1e11) - EMIT2('d'); EMITMBC(0x10f) EMITMBC(0x111) EMITMBC(0x1e0b) - EMITMBC(0x1e0f) EMITMBC(0x1e11) + case 'd': case 0x10f: case 0x111: case 0x257: case 0x1d6d: + case 0x1d81: case 0x1d91: case 0x1e0b: case 0x1e0d: case 0x1e0f: + case 0x1e11: case 0x1e13: + EMIT2('d') EMIT2(0x10f) EMIT2(0x111) + EMIT2(0x257) EMIT2(0x1d6d) EMIT2(0x1d81) + EMIT2(0x1d91) EMIT2(0x1e0b) EMIT2(0x1e0d) + EMIT2(0x1e0f) EMIT2(0x1e11) EMIT2(0x1e13) return; case 'e': case e_grave: case e_acute: case e_circumflex: - case e_diaeresis: CASEMBC(0x113) CASEMBC(0x115) - CASEMBC(0x117) CASEMBC(0x119) CASEMBC(0x11b) - CASEMBC(0x1ebb) CASEMBC(0x1ebd) - EMIT2('e'); EMIT2(e_grave); EMIT2(e_acute); - EMIT2(e_circumflex); EMIT2(e_diaeresis); - EMITMBC(0x113) EMITMBC(0x115) - EMITMBC(0x117) EMITMBC(0x119) EMITMBC(0x11b) - EMITMBC(0x1ebb) EMITMBC(0x1ebd) + case e_diaeresis: case 0x113: case 0x115: case 0x117: + case 0x119: case 0x11b: case 0x205: case 0x207: + case 0x229: case 0x247: case 0x1d92: case 0x1e15: + case 0x1e17: case 0x1e19: case 0x1e1b: case 0x1e1d: + case 0x1eb9: case 0x1ebb: case 0x1ebd: case 0x1ebf: + case 0x1ec1: case 0x1ec3: case 0x1ec5: case 0x1ec7: + EMIT2('e') EMIT2(e_grave) EMIT2(e_acute) + EMIT2(e_circumflex) EMIT2(e_diaeresis) + EMIT2(0x113) EMIT2(0x115) + EMIT2(0x117) EMIT2(0x119) EMIT2(0x11b) + EMIT2(0x205) EMIT2(0x207) EMIT2(0x229) + EMIT2(0x247) EMIT2(0x1d92) EMIT2(0x1e15) + EMIT2(0x1e17) EMIT2(0x1e19) EMIT2(0x1e1b) + EMIT2(0x1e1d) EMIT2(0x1eb9) EMIT2(0x1ebb) + EMIT2(0x1ebd) EMIT2(0x1ebf) EMIT2(0x1ec1) + EMIT2(0x1ec3) EMIT2(0x1ec5) EMIT2(0x1ec7) return; - case 'f': CASEMBC(0x1e1f) - EMIT2('f'); EMITMBC(0x1e1f) + case 'f': case 0x192: case 0x1d6e: case 0x1d82: + case 0x1e1f: case 0xa799: + EMIT2('f') EMIT2(0x192) EMIT2(0x1d6e) EMIT2(0x1d82) + EMIT2(0x1e1f) EMIT2(0xa799) return; - case 'g': CASEMBC(0x11d) CASEMBC(0x11f) CASEMBC(0x121) - CASEMBC(0x123) CASEMBC(0x1e5) CASEMBC(0x1e7) - CASEMBC(0x1f5) CASEMBC(0x1e21) - EMIT2('g'); EMITMBC(0x11d) EMITMBC(0x11f) EMITMBC(0x121) - EMITMBC(0x123) EMITMBC(0x1e5) EMITMBC(0x1e7) - EMITMBC(0x1f5) EMITMBC(0x1e21) + case 'g': case 0x11d: case 0x11f: case 0x121: case 0x123: + case 0x1e5: case 0x1e7: case 0x1f5: case 0x260: case 0x1d83: + case 0x1e21: case 0xa7a1: + EMIT2('g') EMIT2(0x11d) EMIT2(0x11f) EMIT2(0x121) + EMIT2(0x123) EMIT2(0x1e5) EMIT2(0x1e7) + EMIT2(0x1f5) EMIT2(0x260) EMIT2(0x1d83) + EMIT2(0x1e21) EMIT2(0xa7a1) return; - case 'h': CASEMBC(0x125) CASEMBC(0x127) CASEMBC(0x1e23) - CASEMBC(0x1e27) CASEMBC(0x1e29) CASEMBC(0x1e96) - EMIT2('h'); EMITMBC(0x125) EMITMBC(0x127) EMITMBC(0x1e23) - EMITMBC(0x1e27) EMITMBC(0x1e29) EMITMBC(0x1e96) + case 'h': case 0x125: case 0x127: case 0x21f: case 0x1e23: + case 0x1e25: case 0x1e27: case 0x1e29: case 0x1e2b: + case 0x1e96: case 0x2c68: case 0xa795: + EMIT2('h') EMIT2(0x125) EMIT2(0x127) EMIT2(0x21f) + EMIT2(0x1e23) EMIT2(0x1e25) EMIT2(0x1e27) + EMIT2(0x1e29) EMIT2(0x1e2b) EMIT2(0x1e96) + EMIT2(0x2c68) EMIT2(0xa795) return; case 'i': case i_grave: case i_acute: case i_circumflex: - case i_diaeresis: CASEMBC(0x129) CASEMBC(0x12b) - CASEMBC(0x12d) CASEMBC(0x12f) CASEMBC(0x1d0) - CASEMBC(0x1ec9) - EMIT2('i'); EMIT2(i_grave); EMIT2(i_acute); - EMIT2(i_circumflex); EMIT2(i_diaeresis); - EMITMBC(0x129) EMITMBC(0x12b) - EMITMBC(0x12d) EMITMBC(0x12f) EMITMBC(0x1d0) - EMITMBC(0x1ec9) + case i_diaeresis: case 0x129: case 0x12b: case 0x12d: + case 0x12f: case 0x1d0: case 0x209: case 0x20b: + case 0x268: case 0x1d96: case 0x1e2d: case 0x1e2f: + case 0x1ec9: case 0x1ecb: + EMIT2('i') EMIT2(i_grave) EMIT2(i_acute) + EMIT2(i_circumflex) EMIT2(i_diaeresis) + EMIT2(0x129) EMIT2(0x12b) EMIT2(0x12d) + EMIT2(0x12f) EMIT2(0x1d0) EMIT2(0x209) + EMIT2(0x20b) EMIT2(0x268) EMIT2(0x1d96) + EMIT2(0x1e2d) EMIT2(0x1e2f) EMIT2(0x1ec9) + EMIT2(0x1ecb) EMIT2(0x1ecb) return; - case 'j': CASEMBC(0x135) CASEMBC(0x1f0) - EMIT2('j'); EMITMBC(0x135) EMITMBC(0x1f0) + case 'j': case 0x135: case 0x1f0: case 0x249: + EMIT2('j') EMIT2(0x135) EMIT2(0x1f0) EMIT2(0x249) return; - case 'k': CASEMBC(0x137) CASEMBC(0x1e9) CASEMBC(0x1e31) - CASEMBC(0x1e35) - EMIT2('k'); EMITMBC(0x137) EMITMBC(0x1e9) EMITMBC(0x1e31) - EMITMBC(0x1e35) + case 'k': case 0x137: case 0x199: case 0x1e9: case 0x1d84: + case 0x1e31: case 0x1e33: case 0x1e35: case 0x2c6a: case 0xa741: + EMIT2('k') EMIT2(0x137) EMIT2(0x199) EMIT2(0x1e9) + EMIT2(0x1d84) EMIT2(0x1e31) EMIT2(0x1e33) + EMIT2(0x1e35) EMIT2(0x2c6a) EMIT2(0xa741) return; - case 'l': CASEMBC(0x13a) CASEMBC(0x13c) CASEMBC(0x13e) - CASEMBC(0x140) CASEMBC(0x142) CASEMBC(0x1e3b) - EMIT2('l'); EMITMBC(0x13a) EMITMBC(0x13c) EMITMBC(0x13e) - EMITMBC(0x140) EMITMBC(0x142) EMITMBC(0x1e3b) + case 'l': case 0x13a: case 0x13c: case 0x13e: case 0x140: + case 0x142: case 0x19a: case 0x1e37: case 0x1e39: case 0x1e3b: + case 0x1e3d: case 0x2c61: + EMIT2('l') EMIT2(0x13a) EMIT2(0x13c) + EMIT2(0x13e) EMIT2(0x140) EMIT2(0x142) + EMIT2(0x19a) EMIT2(0x1e37) EMIT2(0x1e39) + EMIT2(0x1e3b) EMIT2(0x1e3d) EMIT2(0x2c61) return; - case 'm': CASEMBC(0x1e3f) CASEMBC(0x1e41) - EMIT2('m'); EMITMBC(0x1e3f) EMITMBC(0x1e41) + case 'm': case 0x1d6f: case 0x1e3f: case 0x1e41: case 0x1e43: + EMIT2('m') EMIT2(0x1d6f) EMIT2(0x1e3f) + EMIT2(0x1e41) EMIT2(0x1e43) return; - case 'n': case n_virguilla: CASEMBC(0x144) CASEMBC(0x146) - CASEMBC(0x148) CASEMBC(0x149) CASEMBC(0x1e45) - CASEMBC(0x1e49) - EMIT2('n'); EMIT2(n_virguilla); - EMITMBC(0x144) EMITMBC(0x146) - EMITMBC(0x148) EMITMBC(0x149) EMITMBC(0x1e45) - EMITMBC(0x1e49) + case 'n': case n_virguilla: case 0x144: case 0x146: case 0x148: + case 0x149: case 0x1f9: case 0x1d70: case 0x1d87: case 0x1e45: + case 0x1e47: case 0x1e49: case 0x1e4b: case 0xa7a5: + EMIT2('n') EMIT2(n_virguilla) + EMIT2(0x144) EMIT2(0x146) EMIT2(0x148) + EMIT2(0x149) EMIT2(0x1f9) EMIT2(0x1d70) + EMIT2(0x1d87) EMIT2(0x1e45) EMIT2(0x1e47) + EMIT2(0x1e49) EMIT2(0x1e4b) EMIT2(0xa7a5) return; case 'o': case o_grave: case o_acute: case o_circumflex: - case o_virguilla: case o_diaeresis: case o_slash: - CASEMBC(0x14d) CASEMBC(0x14f) CASEMBC(0x151) - CASEMBC(0x1a1) CASEMBC(0x1d2) CASEMBC(0x1eb) - CASEMBC(0x1ed) CASEMBC(0x1ecf) - EMIT2('o'); EMIT2(o_grave); EMIT2(o_acute); - EMIT2(o_circumflex); EMIT2(o_virguilla); - EMIT2(o_diaeresis); EMIT2(o_slash); - EMITMBC(0x14d) EMITMBC(0x14f) EMITMBC(0x151) - EMITMBC(0x1a1) EMITMBC(0x1d2) EMITMBC(0x1eb) - EMITMBC(0x1ed) EMITMBC(0x1ecf) + case o_virguilla: case o_diaeresis: case o_slash: + case 0x14d: case 0x14f: case 0x151: case 0x1a1: + case 0x1d2: case 0x1eb: case 0x1ed: case 0x1ff: + case 0x20d: case 0x20f: case 0x22b: case 0x22d: + case 0x22f: case 0x231: case 0x275: case 0x1e4d: + case 0x1e4f: case 0x1e51: case 0x1e53: case 0x1ecd: + case 0x1ecf: case 0x1ed1: case 0x1ed3: case 0x1ed5: + case 0x1ed7: case 0x1ed9: case 0x1edb: case 0x1edd: + case 0x1edf: case 0x1ee1: case 0x1ee3: + EMIT2('o') EMIT2(o_grave) EMIT2(o_acute) + EMIT2(o_circumflex) EMIT2(o_virguilla) + EMIT2(o_diaeresis) EMIT2(o_slash) + EMIT2(0x14d) EMIT2(0x14f) EMIT2(0x151) + EMIT2(0x1a1) EMIT2(0x1d2) EMIT2(0x1eb) + EMIT2(0x1ed) EMIT2(0x1ff) EMIT2(0x20d) + EMIT2(0x20f) EMIT2(0x22b) EMIT2(0x22d) + EMIT2(0x22f) EMIT2(0x231) EMIT2(0x275) + EMIT2(0x1e4d) EMIT2(0x1e4f) EMIT2(0x1e51) + EMIT2(0x1e53) EMIT2(0x1ecd) EMIT2(0x1ecf) + EMIT2(0x1ed1) EMIT2(0x1ed3) EMIT2(0x1ed5) + EMIT2(0x1ed7) EMIT2(0x1ed9) EMIT2(0x1edb) + EMIT2(0x1edd) EMIT2(0x1edf) EMIT2(0x1ee1) + EMIT2(0x1ee3) + return; + + case 'p': case 0x1a5: case 0x1d71: case 0x1d7d: case 0x1d88: + case 0x1e55: case 0x1e57: + EMIT2('p') EMIT2(0x1a5) EMIT2(0x1d71) EMIT2(0x1d7d) + EMIT2(0x1d88) EMIT2(0x1e55) EMIT2(0x1e57) return; - case 'p': CASEMBC(0x1e55) CASEMBC(0x1e57) - EMIT2('p'); EMITMBC(0x1e55) EMITMBC(0x1e57) + case 'q': case 0x24b: case 0x2a0: + EMIT2('q') EMIT2(0x24b) EMIT2(0x2a0) return; - case 'r': CASEMBC(0x155) CASEMBC(0x157) CASEMBC(0x159) - CASEMBC(0x1e59) CASEMBC(0x1e5f) - EMIT2('r'); EMITMBC(0x155) EMITMBC(0x157) EMITMBC(0x159) - EMITMBC(0x1e59) EMITMBC(0x1e5f) + case 'r': case 0x155: case 0x157: case 0x159: case 0x211: + case 0x213: case 0x24d: case 0x27d: case 0x1d72: case 0x1d73: + case 0x1d89: case 0x1e59: case 0x1e5b: case 0x1e5d: case 0x1e5f: + case 0xa7a7: + EMIT2('r') EMIT2(0x155) EMIT2(0x157) EMIT2(0x159) + EMIT2(0x211) EMIT2(0x213) EMIT2(0x24d) EMIT2(0x27d) + EMIT2(0x1d72) EMIT2(0x1d73) EMIT2(0x1d89) EMIT2(0x1e59) + EMIT2(0x1e5b) EMIT2(0x1e5d) EMIT2(0x1e5f) EMIT2(0xa7a7) return; - case 's': CASEMBC(0x15b) CASEMBC(0x15d) CASEMBC(0x15f) - CASEMBC(0x161) CASEMBC(0x1e61) - EMIT2('s'); EMITMBC(0x15b) EMITMBC(0x15d) EMITMBC(0x15f) - EMITMBC(0x161) EMITMBC(0x1e61) + case 's': case 0x15b: case 0x15d: case 0x15f: case 0x161: + case 0x219: case 0x23f: case 0x1d74: case 0x1d8a: case 0x1e61: + case 0x1e63: case 0x1e65: case 0x1e67: case 0x1e69: case 0xa7a9: + EMIT2('s') EMIT2(0x15b) EMIT2(0x15d) EMIT2(0x15f) + EMIT2(0x161) EMIT2(0x219) EMIT2(0x23f) EMIT2(0x1d74) + EMIT2(0x1d8a) EMIT2(0x1e61) EMIT2(0x1e63) EMIT2(0x1e65) + EMIT2(0x1e67) EMIT2(0x1e69) EMIT2(0xa7a9) return; - case 't': CASEMBC(0x163) CASEMBC(0x165) CASEMBC(0x167) - CASEMBC(0x1e6b) CASEMBC(0x1e6f) CASEMBC(0x1e97) - EMIT2('t'); EMITMBC(0x163) EMITMBC(0x165) EMITMBC(0x167) - EMITMBC(0x1e6b) EMITMBC(0x1e6f) EMITMBC(0x1e97) + case 't': case 0x163: case 0x165: case 0x167: case 0x1ab: + case 0x1ad: case 0x21b: case 0x288: case 0x1d75: case 0x1e6b: + case 0x1e6d: case 0x1e6f: case 0x1e71: case 0x1e97: case 0x2c66: + EMIT2('t') EMIT2(0x163) EMIT2(0x165) EMIT2(0x167) + EMIT2(0x1ab) EMIT2(0x1ad) EMIT2(0x21b) EMIT2(0x288) + EMIT2(0x1d75) EMIT2(0x1e6b) EMIT2(0x1e6d) EMIT2(0x1e6f) + EMIT2(0x1e71) EMIT2(0x1e97) EMIT2(0x2c66) return; case 'u': case u_grave: case u_acute: case u_circumflex: - case u_diaeresis: CASEMBC(0x169) CASEMBC(0x16b) - CASEMBC(0x16d) CASEMBC(0x16f) CASEMBC(0x171) - CASEMBC(0x173) CASEMBC(0x1b0) CASEMBC(0x1d4) - CASEMBC(0x1ee7) - EMIT2('u'); EMIT2(u_grave); EMIT2(u_acute); - EMIT2(u_circumflex); EMIT2(u_diaeresis); - EMITMBC(0x169) EMITMBC(0x16b) - EMITMBC(0x16d) EMITMBC(0x16f) EMITMBC(0x171) - EMITMBC(0x173) EMITMBC(0x1b0) EMITMBC(0x1d4) - EMITMBC(0x1ee7) + case u_diaeresis: case 0x169: case 0x16b: case 0x16d: + case 0x16f: case 0x171: case 0x173: case 0x1b0: case 0x1d4: + case 0x1d6: case 0x1d8: case 0x1da: case 0x1dc: case 0x215: + case 0x217: case 0x289: case 0x1d7e: case 0x1d99: case 0x1e73: + case 0x1e75: case 0x1e77: case 0x1e79: case 0x1e7b: + case 0x1ee5: case 0x1ee7: case 0x1ee9: case 0x1eeb: + case 0x1eed: case 0x1eef: case 0x1ef1: + EMIT2('u') EMIT2(u_grave) EMIT2(u_acute) + EMIT2(u_circumflex) EMIT2(u_diaeresis) + EMIT2(0x169) EMIT2(0x16b) + EMIT2(0x16d) EMIT2(0x16f) EMIT2(0x171) + EMIT2(0x173) EMIT2(0x1d6) EMIT2(0x1d8) + EMIT2(0x215) EMIT2(0x217) EMIT2(0x1b0) + EMIT2(0x1d4) EMIT2(0x1da) EMIT2(0x1dc) + EMIT2(0x289) EMIT2(0x1e73) EMIT2(0x1d7e) + EMIT2(0x1d99) EMIT2(0x1e75) EMIT2(0x1e77) + EMIT2(0x1e79) EMIT2(0x1e7b) EMIT2(0x1ee5) + EMIT2(0x1ee7) EMIT2(0x1ee9) EMIT2(0x1eeb) + EMIT2(0x1eed) EMIT2(0x1eef) EMIT2(0x1ef1) return; - case 'v': CASEMBC(0x1e7d) - EMIT2('v'); EMITMBC(0x1e7d) + case 'v': case 0x28b: case 0x1d8c: case 0x1e7d: case 0x1e7f: + EMIT2('v') EMIT2(0x28b) EMIT2(0x1d8c) EMIT2(0x1e7d) + EMIT2(0x1e7f) return; - case 'w': CASEMBC(0x175) CASEMBC(0x1e81) CASEMBC(0x1e83) - CASEMBC(0x1e85) CASEMBC(0x1e87) CASEMBC(0x1e98) - EMIT2('w'); EMITMBC(0x175) EMITMBC(0x1e81) EMITMBC(0x1e83) - EMITMBC(0x1e85) EMITMBC(0x1e87) EMITMBC(0x1e98) + case 'w': case 0x175: case 0x1e81: case 0x1e83: case 0x1e85: + case 0x1e87: case 0x1e89: case 0x1e98: + EMIT2('w') EMIT2(0x175) EMIT2(0x1e81) EMIT2(0x1e83) + EMIT2(0x1e85) EMIT2(0x1e87) EMIT2(0x1e89) EMIT2(0x1e98) return; - case 'x': CASEMBC(0x1e8b) CASEMBC(0x1e8d) - EMIT2('x'); EMITMBC(0x1e8b) EMITMBC(0x1e8d) + case 'x': case 0x1e8b: case 0x1e8d: + EMIT2('x') EMIT2(0x1e8b) EMIT2(0x1e8d) return; - case 'y': case y_acute: case y_diaeresis: CASEMBC(0x177) - CASEMBC(0x1e8f) CASEMBC(0x1e99) CASEMBC(0x1ef3) - CASEMBC(0x1ef7) CASEMBC(0x1ef9) - EMIT2('y'); EMIT2(y_acute); EMIT2(y_diaeresis); - EMITMBC(0x177) - EMITMBC(0x1e8f) EMITMBC(0x1e99) EMITMBC(0x1ef3) - EMITMBC(0x1ef7) EMITMBC(0x1ef9) + case 'y': case y_acute: case y_diaeresis: case 0x177: + case 0x1b4: case 0x233: case 0x24f: case 0x1e8f: + case 0x1e99: case 0x1ef3: case 0x1ef5: case 0x1ef7: + case 0x1ef9: + EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) + EMIT2(0x177) EMIT2(0x1b4) EMIT2(0x233) EMIT2(0x24f) + EMIT2(0x1e8f) EMIT2(0x1e99) EMIT2(0x1ef3) + EMIT2(0x1ef5) EMIT2(0x1ef7) EMIT2(0x1ef9) return; - case 'z': CASEMBC(0x17a) CASEMBC(0x17c) CASEMBC(0x17e) - CASEMBC(0x1b6) CASEMBC(0x1e91) CASEMBC(0x1e95) - EMIT2('z'); EMITMBC(0x17a) EMITMBC(0x17c) EMITMBC(0x17e) - EMITMBC(0x1b6) EMITMBC(0x1e91) EMITMBC(0x1e95) + case 'z': case 0x17a: case 0x17c: case 0x17e: case 0x1b6: + case 0x1d76: case 0x1d8e: case 0x1e91: case 0x1e93: + case 0x1e95: case 0x2c6c: + EMIT2('z') EMIT2(0x17a) EMIT2(0x17c) EMIT2(0x17e) + EMIT2(0x1b6) EMIT2(0x1d76) EMIT2(0x1d8e) EMIT2(0x1e91) + EMIT2(0x1e93) EMIT2(0x1e95) EMIT2(0x2c6c) return; - /* default: character itself */ + // default: character itself } } EMIT2(c); #undef EMIT2 -#undef EMITMBC } /* @@ -1484,10 +1639,20 @@ static int nfa_regatom(void) { int64_t n = 0; const int cmp = c; + bool cur = false; - if (c == '<' || c == '>') + if (c == '<' || c == '>') { + c = getchr(); + } + if (no_Magic(c) == '.') { + cur = true; c = getchr(); + } while (ascii_isdigit(c)) { + if (cur) { + semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + return FAIL; + } if (n > (INT32_MAX - (c - '0')) / 10) { // overflow. emsg(_(e_value_too_large)); @@ -1500,6 +1665,9 @@ static int nfa_regatom(void) int32_t limit = INT32_MAX; if (c == 'l') { + if (cur) { + n = curwin->w_cursor.lnum; + } // \%{n}l \%{n}<l \%{n}>l EMIT(cmp == '<' ? NFA_LNUM_LT : cmp == '>' ? NFA_LNUM_GT : NFA_LNUM); @@ -1507,10 +1675,19 @@ static int nfa_regatom(void) at_start = true; } } else if (c == 'c') { + if (cur) { + n = curwin->w_cursor.col; + n++; + } // \%{n}c \%{n}<c \%{n}>c EMIT(cmp == '<' ? NFA_COL_LT : cmp == '>' ? NFA_COL_GT : NFA_COL); } else { + if (cur) { + colnr_T vcol = 0; + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); + n = ++vcol; + } // \%{n}v \%{n}<v \%{n}>v EMIT(cmp == '<' ? NFA_VCOL_LT : cmp == '>' ? NFA_VCOL_GT : NFA_VCOL); @@ -2014,7 +2191,7 @@ static int nfa_regpiece(void) // will emit NFA_STAR. // Bail out if we can use the other engine, but only, when the // pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\} - // does not work with with characters > 8 bit with the BT engine) + // does not work with characters > 8 bit with the BT engine) if ((nfa_re_flags & RE_AUTO) && (maxval > 500 || maxval > minval + 200) && (maxval != MAX_LIMIT && minval < 200) @@ -2566,20 +2743,20 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) ga_concat(indent, (char_u *)"| "); else ga_concat(indent, (char_u *)" "); - ga_append(indent, '\0'); + ga_append(indent, NUL); nfa_print_state2(debugf, state->out, indent); /* replace last part of indent for state->out1 */ indent->ga_len -= 3; ga_concat(indent, (char_u *)" "); - ga_append(indent, '\0'); + ga_append(indent, NUL); nfa_print_state2(debugf, state->out1, indent); /* shrink indent */ indent->ga_len -= 3; - ga_append(indent, '\0'); + ga_append(indent, NUL); } /* @@ -4368,7 +4545,7 @@ static regsubs_T *addstate_here( // First add the state(s) at the end, so that we know how many there are. // Pass the listidx as offset (avoids adding another argument to - // addstate(). + // addstate()). regsubs_T *r = addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET); if (r == NULL) { return NULL; @@ -6072,7 +6249,16 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_MARK_GT: case NFA_MARK_LT: { - pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false); + pos_T *pos; + size_t col = REG_MULTI ? rex.input - rex.line : 0; + + pos = getmark_buf(rex.reg_buf, t->state->val, false); + + // Line may have been freed, get it again. + if (REG_MULTI) { + rex.line = reg_getline(rex.lnum); + rex.input = rex.line + col; + } // Compare the mark position to the match position, if the mark // exists and mark is set in reg_buf. diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index be365d4ab8..1102096b32 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -12,7 +12,6 @@ #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/lua/executor.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/runtime.h" @@ -25,6 +24,13 @@ static bool runtime_search_path_valid = false; static int *runtime_search_path_ref = NULL; static RuntimeSearchPath runtime_search_path; +static RuntimeSearchPath runtime_search_path_thread; +static uv_mutex_t runtime_search_path_mutex; + +void runtime_init(void) +{ + uv_mutex_init(&runtime_search_path_mutex); +} /// ":runtime [what] {name}" void ex_runtime(exarg_T *eap) @@ -147,7 +153,7 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, if (flags & DIP_ERR) { semsg(_(e_dirnotf), basepath, name); - } else if (p_verbose > 0) { + } else if (p_verbose > 1) { verbose_enter(); smsg(_("not found in '%s': \"%s\""), basepath, name); verbose_leave(); @@ -173,6 +179,17 @@ RuntimeSearchPath runtime_search_path_get_cached(int *ref) return runtime_search_path; } +RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src) +{ + RuntimeSearchPath dst = KV_INITIAL_VALUE; + for (size_t j = 0; j < kv_size(src); j++) { + SearchPathItem src_item = kv_A(src, j); + kv_push(dst, ((SearchPathItem){ xstrdup(src_item.path), src_item.after, src_item.has_lua })); + } + + return dst; +} + void runtime_search_path_unref(RuntimeSearchPath path, int *ref) FUNC_ATTR_NONNULL_ALL { @@ -269,7 +286,7 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void if (!did_one && name != NULL) { if (flags & DIP_ERR) { semsg(_(e_dirnotf), "runtime path", name); - } else if (p_verbose > 0) { + } else if (p_verbose > 1) { verbose_enter(); smsg(_("not found in runtime path: \"%s\""), name); verbose_leave(); @@ -303,15 +320,35 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all) { int ref; RuntimeSearchPath path = runtime_search_path_get_cached(&ref); - ArrayOf(String) rv = ARRAY_DICT_INIT; static char buf[MAXPATHL]; + ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, path, buf, sizeof buf); + + runtime_search_path_unref(path, &ref); + return rv; +} + +ArrayOf(String) runtime_get_named_thread(bool lua, Array pat, bool all) +{ + // TODO(bfredl): avoid contention between multiple worker threads? + uv_mutex_lock(&runtime_search_path_mutex); + static char buf[MAXPATHL]; + ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, runtime_search_path_thread, + buf, sizeof buf); + uv_mutex_unlock(&runtime_search_path_mutex); + return rv; +} + +ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all, + RuntimeSearchPath path, char *buf, size_t buf_len) +{ + ArrayOf(String) rv = ARRAY_DICT_INIT; for (size_t i = 0; i < kv_size(path); i++) { SearchPathItem *item = &kv_A(path, i); if (lua) { if (item->has_lua == kNone) { - size_t size = (size_t)snprintf(buf, sizeof buf, "%s/lua/", item->path); - item->has_lua = (size < sizeof buf && os_isdir((char_u *)buf)) ? kTrue : kFalse; + size_t size = (size_t)snprintf(buf, buf_len, "%s/lua/", item->path); + item->has_lua = (size < buf_len && os_isdir((char_u *)buf)); } if (item->has_lua == kFalse) { continue; @@ -321,9 +358,9 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all) for (size_t j = 0; j < pat.size; j++) { Object pat_item = pat.items[j]; if (pat_item.type == kObjectTypeString) { - size_t size = (size_t)snprintf(buf, sizeof buf, "%s/%s", + size_t size = (size_t)snprintf(buf, buf_len, "%s/%s", item->path, pat_item.data.string.data); - if (size < sizeof buf) { + if (size < buf_len) { if (os_file_is_readable(buf)) { ADD(rv, STRING_OBJ(cstr_to_string(buf))); if (!all) { @@ -334,9 +371,7 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all) } } } - done: - runtime_search_path_unref(path, &ref); return rv; } @@ -570,6 +605,10 @@ void runtime_search_path_validate(void) runtime_search_path = runtime_search_path_build(); runtime_search_path_valid = true; runtime_search_path_ref = NULL; // initially unowned + uv_mutex_lock(&runtime_search_path_mutex); + runtime_search_path_free(runtime_search_path_thread); + runtime_search_path_thread = copy_runtime_search_path(runtime_search_path); + uv_mutex_unlock(&runtime_search_path_mutex); } } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 2ce2be0bfd..296255ed8c 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -75,6 +75,7 @@ #include "nvim/cursor.h" #include "nvim/cursor_shape.h" #include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/eval.h" @@ -87,18 +88,19 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/lua/executor.h" #include "nvim/main.h" #include "nvim/mark.h" +#include "nvim/match.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -127,8 +129,6 @@ #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. // Per-cell attributes @@ -165,40 +165,9 @@ static bool resizing = false; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.c.generated.h" #endif -#define SEARCH_HL_PRIORITY 0 static char *provider_err = NULL; -static bool provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args, bool default_true) -{ - Error err = ERROR_INIT; - - textlock++; - provider_active = true; - Object ret = nlua_call_ref(ref, name, args, true, &err); - provider_active = false; - 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_err == NULL) { - static char errbuf[IOSIZE]; - snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg); - provider_err = xstrdup(errbuf); - } - } - - api_free_object(ret); - return false; -} - /// Redraw a window later, with update_screen(type). /// /// Set must_redraw only if not already set to a higher value. @@ -314,6 +283,32 @@ void update_curbuf(int type) update_screen(type); } +/// called when the status bars for the buffer 'buf' need to be updated +void redraw_buf_status_later(buf_T *buf) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && (wp->w_status_height || (wp == curwin && global_stl_height()))) { + wp->w_redr_status = true; + if (must_redraw < VALID) { + must_redraw = VALID; + } + } + } +} + +void redraw_win_signcol(win_T *wp) +{ + // 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. + int scwidth = wp->w_scwidth; + wp->w_scwidth = win_signcol_count(wp); + if (wp->w_scwidth != scwidth) { + changed_line_abv_curs_win(wp); + } +} + /// Redraw the parts of the screen that is marked for redraw. /// /// Most code shouldn't call this directly, rather use redraw_later() and @@ -323,6 +318,7 @@ void update_curbuf(int type) int update_screen(int type) { static bool did_intro = false; + bool is_stl_global = global_stl_height() > 0; // Don't do anything if the screen structures are (not yet) valid. // A VimResized autocmd can invoke redrawing in the middle of a resize, @@ -405,10 +401,13 @@ int update_screen(int type) if (W_ENDROW(wp) > valid) { wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID); } - if (W_ENDROW(wp) + wp->w_status_height > valid) { + if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { wp->w_redr_status = true; } } + if (is_stl_global && Rows - p_ch - 1 > valid) { + curwin->w_redr_status = true; + } } msg_grid_set_pos(Rows-p_ch, false); msg_grid_invalid = false; @@ -430,13 +429,15 @@ int update_screen(int type) wp->w_redr_type = REDRAW_TOP; } else { wp->w_redr_type = NOT_VALID; - if (W_ENDROW(wp) + wp->w_status_height - <= msg_scrolled) { - wp->w_redr_status = TRUE; + if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height <= msg_scrolled) { + wp->w_redr_status = true; } } } } + if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) { + curwin->w_redr_status = true; + } redraw_cmdline = true; redraw_tabline = true; } @@ -451,9 +452,11 @@ int update_screen(int type) // reset cmdline_row now (may have been changed temporarily) compute_cmdrow(); + bool hl_changed = false; // Check for changed highlighting if (need_highlight_changed) { highlight_changed(); + hl_changed = true; } if (type == CLEAR) { // first clear screen @@ -475,28 +478,8 @@ 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); - } - } + DecorProviders providers; + decor_providers_start(&providers, type, &provider_err); // "start" callback could have changed highlights for global elements if (win_check_ns_hl(NULL)) { @@ -554,7 +537,7 @@ int update_screen(int type) * buffer. Each buffer must only be done once. */ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - update_window_hl(wp, type >= NOT_VALID); + update_window_hl(wp, type >= NOT_VALID || hl_changed); buf_T *buf = wp->w_buffer; if (buf->b_mod_set) { @@ -565,14 +548,7 @@ int update_screen(int type) } 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); - } - } + decor_providers_invoke_buf(buf, &providers, &provider_err); buf->b_mod_tick_decor = display_tick; } } @@ -642,18 +618,7 @@ int update_screen(int type) } 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); - } - } + decor_providers_invoke_end(&providers, &provider_err); kvi_destroy(providers); @@ -702,15 +667,11 @@ void conceal_check_cursor_line(void) /// Whether cursorline is drawn in a special way /// -/// If true, both old and new cursorline will need -/// to be redrawn when moving cursor within windows. -/// TODO(bfredl): VIsual_active shouldn't be needed, but is used to fix a glitch -/// caused by scrolling. +/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows. bool win_cursorline_standout(const win_T *wp) FUNC_ATTR_NONNULL_ALL { - return wp->w_p_cul - || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp))); + return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); } /* @@ -740,7 +701,7 @@ bool win_cursorline_standout(const win_T *wp) * 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, Providers *providers) +static void win_update(win_T *wp, DecorProviders *providers) { buf_T *buf = wp->w_buffer; int type; @@ -776,12 +737,6 @@ static void win_update(win_T *wp, Providers *providers) 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. - buf_signcols(buf); - type = wp->w_redr_type; if (type >= NOT_VALID) { @@ -789,8 +744,11 @@ static void win_update(win_T *wp, Providers *providers) wp->w_lines_valid = 0; } - // Window is zero-height: nothing to draw. + // Window is zero-height: Only need to draw the separator if (wp->w_grid.Rows == 0) { + // draw the horizontal separator below this window + draw_hsep_win(wp); + draw_sep_connectors_win(wp); wp->w_redr_type = 0; return; } @@ -798,12 +756,15 @@ static void win_update(win_T *wp, Providers *providers) // Window is zero-width: Only need to draw the separator. if (wp->w_grid.Columns == 0) { // draw the vertical separator right of this window - draw_vsep_win(wp, 0); + draw_vsep_win(wp); + draw_sep_connectors_win(wp); wp->w_redr_type = 0; return; } - init_search_hl(wp); + redraw_win_signcol(wp); + + init_search_hl(wp, &search_hl); /* Force redraw when width of 'number' or 'relativenumber' column * changes. */ @@ -973,7 +934,7 @@ static void win_update(win_T *wp, Providers *providers) if (mod_top != 0 && wp->w_topline == mod_top && (!wp->w_lines[0].wl_valid - || wp->w_topline <= wp->w_lines[0].wl_lnum)) { + || 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 @@ -1201,19 +1162,40 @@ static void win_update(win_T *wp, Providers *providers) */ if (VIsual_mode == Ctrl_V) { colnr_T fromc, toc; - int save_ve_flags = ve_flags; + unsigned int save_ve_flags = curwin->w_ve_flags; if (curwin->w_p_lbr) { - ve_flags = VE_ALL; + curwin->w_ve_flags = VE_ALL; } getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); - ve_flags = save_ve_flags; toc++; + curwin->w_ve_flags = save_ve_flags; // Highlight to the end of the line, unless 'virtualedit' has // "block". - if (curwin->w_curswant == MAXCOL && !(ve_flags & VE_BLOCK)) { - toc = MAXCOL; + if (curwin->w_curswant == MAXCOL) { + if (get_ve_flags() & VE_BLOCK) { + pos_T pos; + int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; + + // Need to find the longest line. + toc = 0; + pos.coladd = 0; + for (pos.lnum = curwin->w_cursor.lnum; + cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum; + pos.lnum += cursor_above ? 1 : -1) { + colnr_T t; + + pos.col = STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false)); + getvvcol(wp, &pos, NULL, NULL, &t); + if (toc < t) { + toc = t; + } + } + toc++; + } else { + toc = MAXCOL; + } } if (fromc != wp->w_old_cursor_fcol @@ -1334,30 +1316,10 @@ static void win_update(win_T *wp, Providers *providers) decor_redraw_reset(buf, &decor_state); - 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); + DecorProviders line_providers; + decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + bool cursorline_standout = win_cursorline_standout(wp); for (;;) { /* stop updating when reached the end of the window (check for _past_ @@ -1403,8 +1365,8 @@ static void win_update(win_T *wp, Providers *providers) // if lines were inserted or deleted || (wp->w_match_head != NULL && buf->b_mod_xlines != 0))))) - || (wp->w_p_cul && (lnum == wp->w_cursor.lnum - || lnum == wp->w_last_cursorline))) { + || (cursorline_standout && lnum == wp->w_cursor.lnum) + || lnum == wp->w_last_cursorline) { if (lnum == mod_top) { top_to_mod = false; } @@ -1569,7 +1531,7 @@ static void win_update(win_T *wp, Providers *providers) // will draw "@ " lines below. row = wp->w_grid.Rows + 1; } else { - prepare_search_hl(wp, lnum); + prepare_search_hl(wp, &search_hl, lnum); // Let the syntax stuff know we skipped a few lines. if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum && syntax_present(wp)) { @@ -1581,17 +1543,17 @@ static void win_update(win_T *wp, Providers *providers) foldinfo.fi_lines ? srow : wp->w_grid.Rows, mod_top == 0, false, foldinfo, &line_providers); - 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; + if (foldinfo.fi_lines == 0) { + wp->w_lines[idx].wl_folded = false; + wp->w_lines[idx].wl_lastlnum = lnum; + did_update = DID_LINE; + syntax_last_parsed = lnum; + } else { foldinfo.fi_lines--; + wp->w_lines[idx].wl_folded = true; wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; + did_update = DID_FOLD; } - - syntax_last_parsed = lnum; } wp->w_lines[idx].wl_lnum = lnum; @@ -1611,9 +1573,9 @@ static void win_update(win_T *wp, Providers *providers) 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. + if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) { + // 'relativenumber' set and cursor moved vertically: The + // text doesn't need to be drawn, but the number column does. foldinfo_T info = fold_info(wp, lnum); (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true, info, &line_providers); @@ -1637,6 +1599,11 @@ static void win_update(win_T *wp, Providers *providers) * End of loop over all window lines. */ + // Now that the window has been redrawn with the old and new cursor line, + // update w_last_cursorline. + wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0; + + wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; if (idx > wp->w_lines_valid) { wp->w_lines_valid = idx; @@ -1672,16 +1639,18 @@ static void win_update(win_T *wp, Providers *providers) int scr_row = wp->w_grid.Rows - 1; // Last line isn't finished: Display "@@@" in the last screen line. - grid_puts_len(&wp->w_grid, (char_u *)"@@", 2, scr_row, 0, at_attr); + grid_puts_len(&wp->w_grid, (char_u *)"@@", MIN(wp->w_grid.Columns, 2), scr_row, 0, at_attr); grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.Columns, '@', ' ', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" + int start_col = wp->w_grid.Columns - 3; + // Last line isn't finished: Display "@@@" at the end. grid_fill(&wp->w_grid, wp->w_grid.Rows - 1, wp->w_grid.Rows, - wp->w_grid.Columns - 3, wp->w_grid.Columns, '@', '@', at_attr); + MAX(start_col, 0), wp->w_grid.Columns, '@', '@', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; } else { @@ -1692,7 +1661,7 @@ static void win_update(win_T *wp, Providers *providers) if (eof) { // we hit the end of the file wp->w_botline = buf->b_ml.ml_line_count + 1; j = win_get_fill(wp, wp->w_botline); - if (j > 0 && !wp->w_botfill) { + if (j > 0 && !wp->w_botfill && row < wp->w_grid.Rows) { // Display filler text below last line. win_line() will check // for ml_line_count+1 and only draw filler lines foldinfo_T info = FOLDINFO_INIT; @@ -1712,7 +1681,9 @@ static void win_update(win_T *wp, Providers *providers) kvi_destroy(line_providers); if (wp->w_redr_type >= REDRAW_TOP) { - draw_vsep_win(wp, 0); + draw_vsep_win(wp); + draw_hsep_win(wp); + draw_sep_connectors_win(wp); } syn_set_timeout(NULL); @@ -1811,7 +1782,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i win_hl_attr(wp, HLF_FC)); } // draw the sign column - int count = win_signcol_count(wp); + int count = wp->w_scwidth; if (count > 0) { n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row, endrow, win_hl_attr(wp, HLF_SC)); @@ -1946,7 +1917,7 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_ 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. + // fits and use numbers to indicate the depth. first_level = level - fdc - closed + 1; if (first_level < 1) { first_level = 1; @@ -1985,6 +1956,38 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_ return MAX(char_counter + (fdc-i), (size_t)fdc); } +static inline void provider_err_virt_text(linenr_T lnum, char *err) +{ + Decoration err_decor = DECORATION_INIT; + int hl_err = syn_check_group(S_LEN("ErrorMsg")); + kv_push(err_decor.virt_text, + ((VirtTextChunk){ .text = provider_err, + .hl_id = hl_err })); + err_decor.virt_text_width = mb_string2cells((char_u *)err); + decor_add_ephemeral(lnum-1, 0, lnum-1, 0, &err_decor); +} + +static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) +{ + 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 "; + } + } + + snprintf((char *)buf, buf_len, fmt, number_width(wp), num); +} + /// Display line "lnum" of window 'wp' on the screen. /// wp->w_virtcol needs to be valid. /// @@ -2000,7 +2003,7 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_ /// /// @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) + bool number_only, foldinfo_T foldinfo, DecorProviders *providers) { int c = 0; // init for GCC long vcol = 0; // virtual column (for tabs) @@ -2097,13 +2100,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc int line_attr_save; int line_attr_lowprio = 0; // low-priority attribute for the line int line_attr_lowprio_save; - matchitem_T *cur; // points to the match list - match_T *shl; // points to search_hl or a match - bool shl_flag; // flag to indicate whether search_hl - // has been processed or not - bool prevcol_hl_flag; // flag to indicate whether prevcol - // equals startcol of search_hl or one - // of the matches int prev_c = 0; // previous Arabic character int prev_c1 = 0; // first composing char for prev_c @@ -2125,13 +2121,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // draw_state: items that are drawn in sequence: #define WL_START 0 // nothing done yet -#define WL_CMDLINE WL_START + 1 // cmdline window column -#define WL_FOLD WL_CMDLINE + 1 // 'foldcolumn' -#define WL_SIGN WL_FOLD + 1 // column for signs -#define WL_NR WL_SIGN + 1 // line number -#define WL_BRI WL_NR + 1 // 'breakindent' -#define WL_SBR WL_BRI + 1 // 'showbreak' or 'diff' -#define WL_LINE WL_SBR + 1 // text in the line +#define WL_CMDLINE (WL_START + 1) // cmdline window column +#define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn' +#define WL_SIGN (WL_FOLD + 1) // column for signs +#define WL_NR (WL_SIGN + 1) // line number +#define WL_BRI (WL_NR + 1) // 'breakindent' +#define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff' +#define WL_LINE (WL_SBR + 1) // text in the line int draw_state = WL_START; // what to draw next int syntax_flags = 0; @@ -2169,7 +2165,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // To speed up the loop below, set extra_check when there is linebreak, // trailing white space and/or syntax processing to be done. extra_check = wp->w_p_lbr; - if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow) { + if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow + && !has_fold && !end_fill) { // Prepare for syntax highlighting in this line. When there is an // error, stop syntax highlighting. save_did_emsg = did_emsg; @@ -2186,37 +2183,14 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } } - has_decor = decor_redraw_line(wp->w_buffer, lnum-1, - &decor_state); + has_decor = decor_redraw_line(buf, 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); - 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; - } - - win_check_ns_hl(wp); - } - } + providers_invoke_line(wp, providers, lnum-1, &has_decor, &provider_err); if (provider_err) { - Decoration err_decor = DECORATION_INIT; - int hl_err = syn_check_group(S_LEN("ErrorMsg")); - kv_push(err_decor.virt_text, - ((VirtTextChunk){ .text = provider_err, - .hl_id = hl_err })); - err_decor.virt_text_width = mb_string2cells((char_u *)provider_err); - decor_add_ephemeral(lnum-1, 0, lnum-1, 0, &err_decor); - provider_err = NULL; + provider_err_virt_text(lnum, provider_err); has_decor = true; + provider_err = NULL; } if (has_decor) { @@ -2411,12 +2385,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } area_highlighting = true; } - // Update w_last_cursorline even if Visual mode is active. - wp->w_last_cursorline = wp->w_cursor.lnum; } memset(sattrs, 0, sizeof(sattrs)); num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs); + decor_redraw_signs(buf, lnum-1, &num_signs, sattrs); // If this line has a sign with line highlighting set line_attr. // TODO(bfredl, vigoux): this should not take priority over decoration! @@ -2623,69 +2596,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } } - /* - * Handle highlighting the last used search pattern and matches. - * Do this for both search_hl and the match list. - */ - cur = wp->w_match_head; - shl_flag = false; - while ((cur != NULL || !shl_flag) && !number_only - && !has_fold && !end_fill) { - if (!shl_flag) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; // -V595 - } - shl->startcol = MAXCOL; - shl->endcol = MAXCOL; - shl->attr_cur = 0; - shl->is_addpos = false; - v = (ptr - line); - if (cur != NULL) { - cur->pos.cur = 0; - } - next_search_hl(wp, shl, lnum, (colnr_T)v, - shl == &search_hl ? NULL : cur); - if (wp->w_s->b_syn_slow) { - has_syntax = false; - } - - // Need to get the line again, a multi-line regexp may have made it - // invalid. - line = ml_get_buf(wp->w_buffer, lnum, false); - ptr = line + v; - - if (shl->lnum != 0 && shl->lnum <= lnum) { - if (shl->lnum == lnum) { - shl->startcol = shl->rm.startpos[0].col; - } else { - shl->startcol = 0; - } - if (lnum == shl->lnum + shl->rm.endpos[0].lnum - - shl->rm.startpos[0].lnum) { - shl->endcol = shl->rm.endpos[0].col; - } else { - shl->endcol = MAXCOL; - } - // Highlight one character for an empty match. - if (shl->startcol == shl->endcol) { - if (line[shl->endcol] != NUL) { - shl->endcol += utfc_ptr2len(line + shl->endcol); - } else { - ++shl->endcol; - } - } - if ((long)shl->startcol < v) { // match at leftcol - shl->attr_cur = shl->attr; - search_attr = shl->attr; - search_attr_from_match = shl != &search_hl; - } - area_highlighting = true; - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } + if (!number_only && !has_fold && !end_fill) { + v = ptr - line; + area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v, + &line, &search_hl, &search_attr, + &search_attr_from_match); + ptr = line + v; // "line" may have been updated } unsigned off = 0; // Offset relative start of line @@ -2709,6 +2625,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // Repeat for the whole displayed line. for (;;) { int has_match_conc = 0; ///< match wants to conceal + int decor_conceal = 0; + bool did_decrement_ptr = false; // Skip this quickly when working on the text. @@ -2744,7 +2662,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc p_extra = p_extra_free; c_extra = NUL; c_final = NUL; - char_attr = win_hl_attr(wp, HLF_FC); + if (use_cursor_line_sign(wp, lnum)) { + char_attr = win_hl_attr(wp, HLF_CLF); + } else { + char_attr = win_hl_attr(wp, HLF_FC); + } } } @@ -2753,13 +2675,17 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc draw_state = WL_SIGN; /* Show the sign column when there are any signs in this * buffer or when using Netbeans. */ - int count = win_signcol_count(wp); - if (count > 0) { - get_sign_display_info(false, wp, sattrs, row, - startrow, filler_lines, filler_todo, count, + if (wp->w_scwidth > 0) { + get_sign_display_info(false, wp, lnum, sattrs, row, + startrow, filler_lines, filler_todo, &c_extra, &c_final, extra, sizeof(extra), - &p_extra, &n_extra, - &char_attr, &draw_state, &sign_idx); + &p_extra, &n_extra, &char_attr, sign_idx); + sign_idx++; + if (sign_idx < wp->w_scwidth) { + draw_state = WL_SIGN - 1; + } else { + sign_idx = 0; + } } } @@ -2774,34 +2700,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // in 'lnum', then display the sign instead of the line // number. if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' - && num_signs > 0) { - int count = win_signcol_count(wp); - get_sign_display_info(true, wp, sattrs, row, - startrow, filler_lines, filler_todo, count, + && num_signs > 0 && sign_get_attr(SIGN_TEXT, sattrs, 0, 1)) { + get_sign_display_info(true, wp, lnum, sattrs, row, + startrow, filler_lines, filler_todo, &c_extra, &c_final, extra, sizeof(extra), - &p_extra, &n_extra, - &char_attr, &draw_state, &sign_idx); + &p_extra, &n_extra, &char_attr, sign_idx); } else { + // Draw the line number (empty space after wrapping). 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 "; - } - } - - snprintf((char *)extra, sizeof(extra), - fmt, number_width(wp), num); + get_line_number_str(wp, lnum, (char_u *)extra, sizeof(extra)); if (wp->w_skipcol > 0) { for (p_extra = extra; *p_extra == ' '; p_extra++) { *p_extra = '-'; @@ -2819,41 +2726,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } p_extra = extra; c_extra = NUL; - c_final = NUL; } else { c_extra = ' '; - c_final = NUL; } + c_final = NUL; n_extra = number_width(wp) + 1; - char_attr = win_hl_attr(wp, HLF_N); - - if (wp->w_p_rnu && lnum < wp->w_cursor.lnum) { - // Use LineNrAbove - char_attr = win_hl_attr(wp, HLF_LNA); - } - if (wp->w_p_rnu && lnum > wp->w_cursor.lnum) { - // Use LineNrBelow - char_attr = win_hl_attr(wp, HLF_LNB); - } - - sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1); - if (num_sattr != NULL) { - // :sign defined with "numhl" highlight. - char_attr = num_sattr->sat_numhl; - } else if (wp->w_p_cul - && lnum == wp->w_cursor.lnum - && (wp->w_p_culopt_flags & CULOPT_NBR) - && (row == startrow - || wp->w_p_culopt_flags & CULOPT_LINE) - && filler_todo == 0) { - // When 'cursorline' is set highlight the line number of - // the current line differently. - // When 'cursorlineopt' has "screenline" only highlight - // the line number itself. - // TODO(vim): Can we use CursorLine instead of CursorLineNr - // when CursorLineNr isn't set? - char_attr = win_hl_attr(wp, HLF_CLN); - } + char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines, sattrs); } } } @@ -3078,115 +2956,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } if (!n_extra) { - /* - * Check for start/end of search pattern match. - * After end, check for start/end of next match. - * When another match, have to check for start again. - * Watch out for matching an empty string! - * Do this for 'search_hl' and the match list (ordered by - * priority). - */ + // Check for start/end of 'hlsearch' and other matches. + // After end, check for start/end of next match. + // When another match, have to check for start again. v = (ptr - line); - cur = wp->w_match_head; - shl_flag = false; - while (cur != NULL || !shl_flag) { - if (!shl_flag - && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; - } - if (cur != NULL) { - cur->pos.cur = 0; - } - bool pos_inprogress = true; // mark that a position match search is - // in progress - while (shl->rm.regprog != NULL - || (cur != NULL && pos_inprogress)) { - if (shl->startcol != MAXCOL - && v >= (long)shl->startcol - && v < (long)shl->endcol) { - int tmp_col = v + utfc_ptr2len(ptr); - - if (shl->endcol < tmp_col) { - shl->endcol = tmp_col; - } - shl->attr_cur = shl->attr; - // Match with the "Conceal" group results in hiding - // the match. - if (cur != NULL - && shl != &search_hl - && syn_name2id("Conceal") == cur->hlg_id) { - has_match_conc = v == (long)shl->startcol ? 2 : 1; - match_conc = cur->conceal_char; - } else { - has_match_conc = 0; - } - } else if (v == (long)shl->endcol) { - shl->attr_cur = 0; - - next_search_hl(wp, shl, lnum, (colnr_T)v, - shl == &search_hl ? NULL : cur); - pos_inprogress = !(cur == NULL || cur->pos.cur == 0); - - // Need to get the line again, a multi-line regexp - // may have made it invalid. - line = ml_get_buf(wp->w_buffer, lnum, false); - ptr = line + v; - - if (shl->lnum == lnum) { - shl->startcol = shl->rm.startpos[0].col; - if (shl->rm.endpos[0].lnum == 0) { - shl->endcol = shl->rm.endpos[0].col; - } else { - shl->endcol = MAXCOL; - } - - if (shl->startcol == shl->endcol) { - // highlight empty match, try again after it - shl->endcol += utfc_ptr2len(line + shl->endcol); - } - - // Loop to check if the match starts at the - // current position - continue; - } - } - break; - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } - } - - /* Use attributes from match with highest priority among - * 'search_hl' and the match list. */ - search_attr_from_match = false; - search_attr = search_hl.attr_cur; - cur = wp->w_match_head; - shl_flag = false; - while (cur != NULL || !shl_flag) { - if (!shl_flag - && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; - } - if (shl->attr_cur != 0) { - search_attr = shl->attr_cur; - search_attr_from_match = shl != &search_hl; - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } - } - // Only highlight one character after the last column. - if (*ptr == NUL - && (wp->w_p_list && lcs_eol_one == -1)) { - search_attr = 0; - } + search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &search_hl, &has_match_conc, + &match_conc, lcs_eol_one, &search_attr_from_match); + ptr = line + v; // "line" may have been changed // Do not allow a conceal over EOL otherwise EOL will be missed // and bad things happen. @@ -3447,6 +3223,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc did_emsg = save_did_emsg; } + if (wp->w_s->b_syn_slow) { + has_syntax = false; + } + // Need to get the line again, a multi-line regexp may // have made it invalid. line = ml_get_buf(wp->w_buffer, lnum, false); @@ -3569,6 +3349,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc char_attr = hl_combine_attr(extmark_attr, char_attr); } } + + decor_conceal = decor_state.conceal; + if (decor_conceal && decor_state.conceal_char) { + decor_conceal = 2; // really?? + } } // Found last space before word: check for line break. @@ -3702,10 +3487,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc tab_len += n_extra - tab_len; } - // if n_extra > 0, it gives the number of chars + // If n_extra > 0, it gives the number of chars // to use for a tab, else we need to calculate the width - // for a tab + // for a tab. int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2)); + if (wp->w_p_lcs_chars.tab3) { + len += utf_char2len(wp->w_p_lcs_chars.tab3); + } if (n_extra > 0) { len += n_extra - tab_len; } @@ -3722,13 +3510,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } int lcs = wp->w_p_lcs_chars.tab2; - // if tab3 is given, need to change the char - // for tab + // if tab3 is given, use it for the last char if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { lcs = wp->w_p_lcs_chars.tab3; } - utf_char2bytes(lcs, p); - p += utf_char2len(lcs); + p += utf_char2bytes(lcs, p); n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); } p_extra = p_extra_free; @@ -3753,7 +3539,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // Make sure, the highlighting for the tab char will be // correctly set further below (effectively reverts the - // FIX_FOR_BOGSUCOLS macro. + // FIX_FOR_BOGSUCOLS macro). if (n_extra == tab_len + vc_saved && wp->w_p_list && wp->w_p_lcs_chars.tab1) { tab_len += vc_saved; @@ -3871,19 +3657,25 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (wp->w_p_cole > 0 && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp)) - && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0) + && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0) && !(lnum_in_visual_area && vim_strchr(wp->w_p_cocu, 'v') == NULL)) { char_attr = conceal_attr; - if ((prev_syntax_id != syntax_seqnr || has_match_conc > 1) + if ((prev_syntax_id != syntax_seqnr || has_match_conc > 1 || decor_conceal > 1) && (syn_get_sub_char() != NUL || (has_match_conc && match_conc) + || (decor_conceal && decor_state.conceal_char) || wp->w_p_cole == 1) && wp->w_p_cole != 3) { // First time at this concealed item: display one // character. if (has_match_conc && match_conc) { c = match_conc; + } else if (decor_conceal && decor_state.conceal_char) { + c = decor_state.conceal_char; + if (decor_state.conceal_attr) { + char_attr = decor_state.conceal_attr; + } } else if (syn_get_sub_char() != NUL) { c = syn_get_sub_char(); } else if (wp->w_p_lcs_chars.conceal != NUL) { @@ -3988,30 +3780,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // At end of the text line or just after the last character. if (c == NUL && eol_hl_off == 0) { - long prevcol = (ptr - line) - 1; - - // we're not really at that column when skipping some text - if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) { - prevcol++; - } + // flag to indicate whether prevcol equals startcol of search_hl or + // one of the matches + bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &search_hl, + (long)(ptr - line) - 1); // Invert at least one char, used for Visual and empty line or // highlight match at end of line. If it's beyond the last // char on the screen, just overwrite that one (tricky!) Not // needed when a '$' was displayed for 'list'. - prevcol_hl_flag = false; - if (!search_hl.is_addpos && prevcol == (long)search_hl.startcol) { - prevcol_hl_flag = true; - } else { - cur = wp->w_match_head; - while (cur != NULL) { - if (!cur->hl.is_addpos && prevcol == (long)cur->hl.startcol) { - prevcol_hl_flag = true; - break; - } - cur = cur->next; - } - } if (wp->w_p_lcs_chars.eol == lcs_eol_one && ((area_attr != 0 && vcol == fromcol && (VIsual_mode != Ctrl_V @@ -4042,25 +3819,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (area_attr == 0 && !has_fold) { // Use attributes from match with highest priority among // 'search_hl' and the match list. - char_attr = search_hl.attr; - cur = wp->w_match_head; - shl_flag = false; - while (cur != NULL || !shl_flag) { - if (!shl_flag - && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; - } - if ((ptr - line) - 1 == (long)shl->startcol - && (shl == &search_hl || !shl->is_addpos)) { - char_attr = shl->attr; - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } - } + get_search_match_hl(wp, &search_hl, (long)(ptr - line), &char_attr); } int eol_attr = char_attr; @@ -4112,7 +3871,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (((wp->w_p_cuc && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off && (int)wp->w_virtcol < - grid->Columns * (row - startrow + 1) + v + (long)grid->Columns * (row - startrow + 1) + v && lnum != wp->w_cursor.lnum) || draw_color_col || line_attr_lowprio || line_attr || diff_hlf != (hlf_T)0 || has_virttext)) { @@ -4214,6 +3973,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // Show "extends" character from 'listchars' if beyond the line end and // 'list' is set. if (wp->w_p_lcs_chars.ext != NUL + && draw_state == WL_LINE && wp->w_p_list && !wp->w_p_wrap && filler_todo <= 0 @@ -4276,7 +4036,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // Store the character. // if (wp->w_p_rl && utf_char2cells(mb_c) > 1) { - // A double-wide character is: put first halve in left cell. + // A double-wide character is: put first half in left cell. off--; col--; } @@ -4409,7 +4169,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc */ if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns)) && foldinfo.fi_lines == 0 - && (*ptr != NUL + && (draw_state != WL_LINE + || *ptr != NUL || filler_todo > 0 || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && p_extra != at_end_str) @@ -4572,6 +4333,9 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, break; } } + if (!*s.p) { + continue; + } int attr; bool through = false; if (hl_mode == kHlModeCombine) { @@ -4615,18 +4379,73 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) } } +// Return true if CursorLineSign highlight is to be used. +static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR); +} + +/// Return true if CursorLineNr highlight is to be used for the number column. +/// +/// - 'cursorline' must be set +/// - lnum must be the cursor line +/// - 'cursorlineopt' has "number" +/// - don't highlight filler lines (when in diff mode) +/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number +/// itself on the first screenline of the wrapped line, otherwise highlight the number column of +/// all screenlines of the wrapped line. +static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR) + && (row == startrow + filler_lines + || (row > startrow + filler_lines + && (wp->w_p_culopt_flags & CULOPT_LINE))); +} + +static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines, + sign_attrs_T *sattrs) +{ + sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1); + if (num_sattr != NULL) { + // :sign defined with "numhl" highlight. + return num_sattr->sat_numhl; + } + + if (wp->w_p_rnu) { + if (lnum < wp->w_cursor.lnum) { + // Use LineNrAbove + return win_hl_attr(wp, HLF_LNA); + } + if (lnum > wp->w_cursor.lnum) { + // Use LineNrBelow + return win_hl_attr(wp, HLF_LNB); + } + } + + if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) { + // TODO(vim): Can we use CursorLine instead of CursorLineNr + // when CursorLineNr isn't set? + return win_hl_attr(wp, HLF_CLN); + } + + return win_hl_attr(wp, HLF_N); +} + // 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. // // @param count max number of signs // @param[out] n_extrap number of characters from pp_extra to display -// @param[in, out] sign_idxp Index of the displayed sign -static void get_sign_display_info(bool nrcol, win_T *wp, sign_attrs_T sattrs[], int row, - int startrow, int filler_lines, int filler_todo, int count, +// @param sign_idxp Index of the displayed sign +static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, sign_attrs_T sattrs[], + int row, int startrow, int filler_lines, int filler_todo, 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) + char_u **pp_extra, int *n_extrap, int *char_attrp, int sign_idx) { // Draw cells with the sign value or blank. *c_extrap = ' '; @@ -4634,12 +4453,16 @@ static void get_sign_display_info(bool nrcol, win_T *wp, sign_attrs_T sattrs[], if (nrcol) { *n_extrap = number_width(wp) + 1; } else { - *char_attrp = win_hl_attr(wp, HLF_SC); + if (use_cursor_line_sign(wp, lnum)) { + *char_attrp = win_hl_attr(wp, HLF_CLS); + } else { + *char_attrp = win_hl_attr(wp, HLF_SC); + } *n_extrap = win_signcol_width(wp); } if (row == startrow + filler_lines && filler_todo <= 0) { - sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, *sign_idxp, count); + sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, sign_idx, wp->w_scwidth); if (sattr != NULL) { *pp_extra = sattr->sat_text; if (*pp_extra != NULL) { @@ -4674,15 +4497,13 @@ static void get_sign_display_info(bool nrcol, win_T *wp, sign_attrs_T sattrs[], (*pp_extra)[*n_extrap] = NUL; } } - *char_attrp = sattr->sat_texthl; - } - } - (*sign_idxp)++; - if (*sign_idxp < count) { - *draw_statep = WL_SIGN - 1; - } else { - *sign_idxp = 0; + if (use_cursor_line_sign(wp, lnum) && sattr->sat_culhl > 0) { + *char_attrp = sattr->sat_culhl; + } else { + *char_attrp = sattr->sat_texthl; + } + } } } @@ -4799,9 +4620,9 @@ static void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, end_dirty = col + char_cells; // When writing a single-width character over a double-width // character and at the end of the redrawn text, need to clear out - // the right halve of the old character. - // Also required when writing the right halve of a double-width - // char over the left halve of an existing one + // the right half of the old character. + // Also required when writing the right half of a double-width + // char over the left half of an existing one if (col + char_cells == endcol && ((char_cells == 1 && grid_off2cells(grid, off_to, max_off_to) > 1) @@ -4899,10 +4720,15 @@ void rl_mirror(char_u *str) */ void status_redraw_all(void) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_status_height) { - wp->w_redr_status = true; - redraw_later(wp, VALID); + if (global_stl_height()) { + curwin->w_redr_status = true; + redraw_later(curwin, VALID); + } else { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_status_height) { + wp->w_redr_status = true; + redraw_later(wp, VALID); + } } } } @@ -4916,10 +4742,15 @@ void status_redraw_curbuf(void) /// Marks all status lines of the specified buffer for redraw. 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(wp, VALID); + if (global_stl_height() != 0 && curwin->w_buffer == buf) { + curwin->w_redr_status = true; + redraw_later(curwin, VALID); + } else { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_status_height != 0 && wp->w_buffer == buf) { + wp->w_redr_status = true; + redraw_later(wp, VALID); + } } } } @@ -4961,10 +4792,8 @@ void win_redraw_last_status(const frame_T *frp) } } -/* - * Draw the verticap separator right of window "wp" starting with line "row". - */ -static void draw_vsep_win(win_T *wp, int row) +/// Draw the vertical separator right of window "wp" +static void draw_vsep_win(win_T *wp) { int hl; int c; @@ -4972,15 +4801,97 @@ static void draw_vsep_win(win_T *wp, int row) if (wp->w_vsep_width) { // draw the vertical separator right of this window c = fillchar_vsep(wp, &hl); - grid_fill(&default_grid, wp->w_winrow + row, W_ENDROW(wp), + grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp), W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); } } +/// Draw the horizontal separator below window "wp" +static void draw_hsep_win(win_T *wp) +{ + int hl; + int c; -/* - * Get the length of an item as it will be shown in the status line. - */ + if (wp->w_hsep_height) { + // draw the horizontal separator below this window + c = fillchar_hsep(wp, &hl); + grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1, + wp->w_wincol, W_ENDCOL(wp), c, c, hl); + } +} + +/// Get the separator connector for specified window corner of window "wp" +static int get_corner_sep_connector(win_T *wp, WindowCorner corner) +{ + // It's impossible for windows to be connected neither vertically nor horizontally + // So if they're not vertically connected, assume they're horizontally connected + if (vsep_connected(wp, corner)) { + if (hsep_connected(wp, corner)) { + return wp->w_p_fcs_chars.verthoriz; + } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { + return wp->w_p_fcs_chars.vertright; + } else { + return wp->w_p_fcs_chars.vertleft; + } + } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { + return wp->w_p_fcs_chars.horizdown; + } else { + return wp->w_p_fcs_chars.horizup; + } +} + +/// Draw seperator connecting characters on the corners of window "wp" +static void draw_sep_connectors_win(win_T *wp) +{ + // Don't draw separator connectors unless global statusline is enabled and the window has + // either a horizontal or vertical separator + if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) { + return; + } + + int hl = win_hl_attr(wp, HLF_C); + + // Determine which edges of the screen the window is located on so we can avoid drawing separators + // on corners contained in those edges + bool win_at_top; + bool win_at_bottom = wp->w_hsep_height == 0; + bool win_at_left; + bool win_at_right = wp->w_vsep_width == 0; + frame_T *frp; + + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) { + break; + } + } + win_at_top = frp->fr_parent == NULL; + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { + break; + } + } + win_at_left = frp->fr_parent == NULL; + + // Draw the appropriate separator connector in every corner where drawing them is necessary + if (!(win_at_top || win_at_left)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT), + wp->w_winrow - 1, wp->w_wincol - 1, hl); + } + if (!(win_at_top || win_at_right)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT), + wp->w_winrow - 1, W_ENDCOL(wp), hl); + } + if (!(win_at_bottom || win_at_left)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), + W_ENDROW(wp), wp->w_wincol - 1, hl); + } + if (!(win_at_bottom || win_at_right)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), + W_ENDROW(wp), W_ENDCOL(wp), hl); + } +} + +/// Get the length of an item as it will be shown in the status line. static int status_match_len(expand_T *xp, char_u *s) { int len = 0; @@ -5181,7 +5092,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char_u **matches, in // Create status line if needed by setting 'laststatus' to 2. // Set 'winminheight' to zero to avoid that the window is // resized. - if (lastwin->w_status_height == 0) { + if (lastwin->w_status_height == 0 && global_stl_height() == 0) { save_p_ls = p_ls; save_p_wmh = p_wmh; p_ls = 2; @@ -5217,12 +5128,15 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char_u **matches, in static void win_redr_status(win_T *wp) { int row; + int col; char_u *p; int len; int fillchar; int attr; + int width; int this_ru_col; - static int busy = FALSE; + bool is_stl_global = global_stl_height() > 0; + static int busy = false; // May get here recursively when 'statusline' (indirectly) // invokes ":redrawstatus". Simply ignore the call then. @@ -5233,9 +5147,9 @@ static void win_redr_status(win_T *wp) } busy = true; - wp->w_redr_status = FALSE; - if (wp->w_status_height == 0) { - // no status line, can only be last window + wp->w_redr_status = false; + if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) { + // no status line, either global statusline is enabled or the window is a last window redraw_cmdline = true; } else if (!redrawing()) { // Don't redraw right now, do it later. Don't update status line when @@ -5246,6 +5160,7 @@ static void win_redr_status(win_T *wp) redraw_custom_statusline(wp); } else { fillchar = fillchar_status(&attr, wp); + width = is_stl_global ? Columns : wp->w_width; get_trans_bufname(wp->w_buffer); p = NameBuff; @@ -5274,9 +5189,9 @@ static void win_redr_status(win_T *wp) // len += (int)STRLEN(p + len); // dead assignment } - this_ru_col = ru_col - (Columns - wp->w_width); - if (this_ru_col < (wp->w_width + 1) / 2) { - this_ru_col = (wp->w_width + 1) / 2; + this_ru_col = ru_col - (Columns - width); + if (this_ru_col < (width + 1) / 2) { + this_ru_col = (width + 1) / 2; } if (this_ru_col <= 1) { p = (char_u *)"<"; // No room for file name! @@ -5301,10 +5216,11 @@ static void win_redr_status(win_T *wp) } } - row = W_ENDROW(wp); - grid_puts(&default_grid, p, row, wp->w_wincol, attr); - grid_fill(&default_grid, row, row + 1, len + wp->w_wincol, - this_ru_col + wp->w_wincol, fillchar, fillchar, attr); + row = is_stl_global ? (Rows - p_ch - 1) : W_ENDROW(wp); + col = is_stl_global ? 0 : wp->w_wincol; + grid_puts(&default_grid, p, row, col, attr); + grid_fill(&default_grid, row, row + 1, len + col, + this_ru_col + col, fillchar, fillchar, attr); if (get_keymap_str(wp, (char_u *)"<%s>", NameBuff, MAXPATHL) && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) { @@ -5383,6 +5299,76 @@ bool stl_connected(win_T *wp) return false; } +/// Check if horizontal separator of window "wp" at specified window corner is connected to the +/// horizontal separator of another window +/// Assumes global statusline is enabled +static bool hsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT); + int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) + ? wp->w_winrow - 1 : W_ENDROW(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_ROW && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) { + fr = fr->fr_next; + } + } + } + + return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win)); +} + +/// Check if vertical separator of window "wp" at specified window corner is connected to the +/// vertical separator of another window +static bool vsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT); + int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) + ? wp->w_wincol - 1 : W_ENDCOL(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_COL && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) { + fr = fr->fr_next; + } + } + } + + return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win)); +} /// Get the value to show for the language mappings, active 'keymap'. /// @@ -5449,6 +5435,7 @@ static void win_redr_custom(win_T *wp, bool draw_ruler) int use_sandbox = false; win_T *ewp; int p_crb_save; + bool is_stl_global = global_stl_height() > 0; ScreenGrid *grid = &default_grid; @@ -5470,9 +5457,9 @@ static void win_redr_custom(win_T *wp, bool draw_ruler) maxwidth = Columns; use_sandbox = was_set_insecurely(wp, "tabline", 0); } else { - row = W_ENDROW(wp); + row = is_stl_global ? (Rows - p_ch - 1) : W_ENDROW(wp); fillchar = fillchar_status(&attr, wp); - maxwidth = wp->w_width; + maxwidth = is_stl_global ? Columns : wp->w_width; if (draw_ruler) { stl = p_ruf; @@ -5490,12 +5477,12 @@ static void win_redr_custom(win_T *wp, bool draw_ruler) stl = p_ruf; } } - col = ru_col - (Columns - wp->w_width); - if (col < (wp->w_width + 1) / 2) { - col = (wp->w_width + 1) / 2; + col = ru_col - (Columns - maxwidth); + if (col < (maxwidth + 1) / 2) { + col = (maxwidth + 1) / 2; } - maxwidth = wp->w_width - col; - if (!wp->w_status_height) { + maxwidth = maxwidth - col; + if (!wp->w_status_height && !is_stl_global) { grid = &msg_grid_adj; row = Rows - 1; maxwidth--; // writing in last column may cause scrolling @@ -5513,7 +5500,7 @@ static void win_redr_custom(win_T *wp, bool draw_ruler) use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); } - col += wp->w_wincol; + col += is_stl_global ? 0 : wp->w_wincol; } if (maxwidth <= 0) { @@ -5852,8 +5839,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col } off = grid->line_offset[row] + col; - /* When drawing over the right halve of a double-wide char clear out the - * left halve. Only needed in a terminal. */ + // When drawing over the right half of a double-wide char clear out the + // left half. Only needed in a terminal. if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) { // redraw the previous cell, make it empty put_dirty_first = -1; @@ -5898,6 +5885,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col // Only 1 cell left, but character requires 2 cells: // display a '>' in the last column to avoid wrapping. */ c = '>'; + u8c = '>'; + u8cc[0] = 0; mbyte_cells = 1; } @@ -5913,9 +5902,9 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col if (need_redraw) { // When at the end of the text and overwriting a two-cell // character with a one-cell character, need to clear the next - // cell. Also when overwriting the left halve of a two-cell char - // with the right halve of a two-cell char. Do this only once - // (utf8_off2cells() may return 2 on the right halve). + // cell. Also when overwriting the left half of a two-cell char + // with the right half of a two-cell char. Do this only once + // (utf8_off2cells() may return 2 on the right half). if (clear_next_cell) { clear_next_cell = false; } else if ((len < 0 ? ptr[mbyte_blen] == NUL @@ -5928,6 +5917,13 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col clear_next_cell = true; } + // When at the start of the text and overwriting the right half of a + // two-cell character in the same grid, truncate that into a '>'. + if (ptr == text && col > 0 && grid->chars[off][0] == 0) { + grid->chars[off - 1][0] = '>'; + grid->chars[off - 1][1] = 0; + } + schar_copy(grid->chars[off], buf); grid->attrs[off] = attr; if (mbyte_cells == 2) { @@ -6007,280 +6003,9 @@ static void end_search_hl(void) } -/* - * Init for calling prepare_search_hl(). - */ -static void init_search_hl(win_T *wp) - FUNC_ATTR_NONNULL_ALL -{ - // Setup for match and 'hlsearch' highlighting. Disable any previous - // match - matchitem_T *cur = wp->w_match_head; - while (cur != NULL) { - cur->hl.rm = cur->match; - if (cur->hlg_id == 0) { - cur->hl.attr = 0; - } else { - cur->hl.attr = syn_id2attr(cur->hlg_id); - } - cur->hl.buf = wp->w_buffer; - cur->hl.lnum = 0; - cur->hl.first_lnum = 0; - // Set the time limit to 'redrawtime'. - cur->hl.tm = profile_setlimit(p_rdt); - cur = cur->next; - } - search_hl.buf = wp->w_buffer; - search_hl.lnum = 0; - search_hl.first_lnum = 0; - search_hl.attr = win_hl_attr(wp, HLF_L); - - // time limit is set at the toplevel, for all windows -} - -/* - * Advance to the match in window "wp" line "lnum" or past it. - */ -static void prepare_search_hl(win_T *wp, linenr_T lnum) - FUNC_ATTR_NONNULL_ALL -{ - matchitem_T *cur; // points to the match list - match_T *shl; // points to search_hl or a match - bool shl_flag; // flag to indicate whether search_hl - // has been processed or not - - // When using a multi-line pattern, start searching at the top - // of the window or just after a closed fold. - // Do this both for search_hl and the match list. - cur = wp->w_match_head; - shl_flag = false; - while (cur != NULL || shl_flag == false) { - if (shl_flag == false) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; // -V595 - } - if (shl->rm.regprog != NULL - && shl->lnum == 0 - && re_multiline(shl->rm.regprog)) { - if (shl->first_lnum == 0) { - for (shl->first_lnum = lnum; - shl->first_lnum > wp->w_topline; - shl->first_lnum--) { - if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) { - break; - } - } - } - if (cur != NULL) { - cur->pos.cur = 0; - } - bool pos_inprogress = true; // mark that a position match search is - // in progress - int n = 0; - while (shl->first_lnum < lnum && (shl->rm.regprog != NULL - || (cur != NULL && pos_inprogress))) { - next_search_hl(wp, shl, shl->first_lnum, (colnr_T)n, - shl == &search_hl ? NULL : cur); - pos_inprogress = !(cur == NULL || cur->pos.cur == 0); - if (shl->lnum != 0) { - shl->first_lnum = shl->lnum - + shl->rm.endpos[0].lnum - - shl->rm.startpos[0].lnum; - n = shl->rm.endpos[0].col; - } else { - ++shl->first_lnum; - n = 0; - } - } - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } - } -} - -/// Search for a next 'hlsearch' or match. -/// Uses shl->buf. -/// Sets shl->lnum and shl->rm contents. -/// Note: Assumes a previous match is always before "lnum", unless -/// shl->lnum is zero. -/// Careful: Any pointers for buffer lines will become invalid. -/// -/// @param shl points to search_hl or a match -/// @param mincol minimal column for a match -/// @param cur to retrieve match positions if any -static void next_search_hl(win_T *win, match_T *shl, linenr_T lnum, colnr_T mincol, - matchitem_T *cur) - FUNC_ATTR_NONNULL_ARG(2) -{ - linenr_T l; - colnr_T matchcol; - 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. - // 2. If the previous match includes "mincol", use it. - // 3. Continue after the previous match. - l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum; - if (lnum > l) { - shl->lnum = 0; - } else if (lnum < l || shl->rm.endpos[0].col > mincol) { - return; - } - } - - /* - * Repeat searching for a match until one is found that includes "mincol" - * or none is found in this line. - */ - called_emsg = FALSE; - for (;;) { - // Stop searching after passing the time limit. - if (profile_passed_limit(shl->tm)) { - shl->lnum = 0; // no match found in time - break; - } - // Three situations: - // 1. No useful previous match: search from start of line. - // 2. Not Vi compatible or empty match: continue at next character. - // Break the loop if this is beyond the end of the line. - // 3. Vi compatible searching: continue at end of previous match. - if (shl->lnum == 0) { - matchcol = 0; - } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL - || (shl->rm.endpos[0].lnum == 0 - && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { - char_u *ml; - - matchcol = shl->rm.startpos[0].col; - ml = ml_get_buf(shl->buf, lnum, false) + matchcol; - if (*ml == NUL) { - ++matchcol; - shl->lnum = 0; - break; - } - matchcol += utfc_ptr2len(ml); - } else { - matchcol = shl->rm.endpos[0].col; - } - - shl->lnum = lnum; - if (shl->rm.regprog != NULL) { - // Remember whether shl->rm is using a copy of the regprog in - // cur->match. - bool regprog_is_copy = (shl != &search_hl - && cur != NULL - && shl == &cur->hl - && cur->match.regprog == cur->hl.rm.regprog); - int timed_out = false; - - nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, - &(shl->tm), &timed_out); - // Copy the regprog, in case it got freed and recompiled. - if (regprog_is_copy) { - cur->match.regprog = cur->hl.rm.regprog; - } - if (called_emsg || got_int || timed_out) { - // Error while handling regexp: stop using this regexp. - if (shl == &search_hl) { - // don't free regprog in the match list, it's a copy - vim_regfree(shl->rm.regprog); - set_no_hlsearch(true); - } - shl->rm.regprog = NULL; - shl->lnum = 0; - got_int = FALSE; // avoid the "Type :quit to exit Vim" message - break; - } - } else if (cur != NULL) { - nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol); - } - if (nmatched == 0) { - shl->lnum = 0; // no match found - break; - } - if (shl->rm.startpos[0].lnum > 0 - || shl->rm.startpos[0].col >= mincol - || nmatched > 1 - || shl->rm.endpos[0].col > mincol) { - shl->lnum += shl->rm.startpos[0].lnum; - break; // useful match found - } - - // Restore called_emsg for assert_fails(). - called_emsg = save_called_emsg; - } -} - -/// @param shl points to a match. Fill on match. -/// @param posmatch match positions -/// @param mincol minimal column for a match -/// -/// @return one on match, otherwise return zero. -static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, colnr_T mincol) - FUNC_ATTR_NONNULL_ALL -{ - int i; - int found = -1; - - shl->lnum = 0; - for (i = posmatch->cur; i < MAXPOSMATCH; i++) { - llpos_T *pos = &posmatch->pos[i]; - - if (pos->lnum == 0) { - break; - } - if (pos->len == 0 && pos->col < mincol) { - continue; - } - if (pos->lnum == lnum) { - if (found >= 0) { - // if this match comes before the one at "found" then swap - // them - if (pos->col < posmatch->pos[found].col) { - llpos_T tmp = *pos; - - *pos = posmatch->pos[found]; - posmatch->pos[found] = tmp; - } - } else { - found = i; - } - } - } - posmatch->cur = 0; - if (found >= 0) { - colnr_T start = posmatch->pos[found].col == 0 - ? 0: posmatch->pos[found].col - 1; - colnr_T end = posmatch->pos[found].col == 0 - ? MAXCOL : start + posmatch->pos[found].len; - - shl->lnum = lnum; - shl->rm.startpos[0].lnum = 0; - shl->rm.startpos[0].col = start; - shl->rm.endpos[0].lnum = 0; - shl->rm.endpos[0].col = end; - shl->is_addpos = true; - posmatch->cur = found + 1; - return 1; - } - return 0; -} - - -/// Fill the grid from 'start_row' to 'end_row', from 'start_col' to 'end_col' -/// with character 'c1' in first column followed by 'c2' in the other columns. -/// Use attributes 'attr'. +/// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col" +/// to "end_col" (exclusive) with character "c1" in first column followed by +/// "c2" in the other columns. Use attributes "attr". void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int end_col, int c1, int c2, int attr) { @@ -6307,9 +6032,9 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int } for (int row = start_row; row < end_row; row++) { - // When drawing over the right halve of a double-wide char clear - // out the left halve. When drawing over the left halve of a - // double wide-char clear out the right halve. Only needed in a + // When drawing over the right half of a double-wide char clear + // out the left half. When drawing over the left half of a + // double wide-char clear out the right half. Only needed in a // terminal. if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0); @@ -6718,7 +6443,7 @@ void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid) void grid_invalidate(ScreenGrid *grid) { - (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T)); + (void)memset(grid->attrs, -1, sizeof(sattr_T) * grid->Rows * grid->Columns); } bool grid_invalid_row(ScreenGrid *grid, int row) @@ -6847,8 +6572,6 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, if (!grid->throttled) { ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0); } - - return; } /// delete lines on the screen and move lines up. @@ -6899,8 +6622,6 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, if (!grid->throttled) { ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0); } - - return; } @@ -6930,7 +6651,7 @@ int showmode(void) do_mode = ((p_smd && msg_silent == 0) && ((State & TERM_FOCUS) || (State & INSERT) - || restart_edit + || restart_edit != NUL || VIsual_active)); if (do_mode || reg_recording != 0) { // Don't show mode right now, when not redrawing or inside a mapping. @@ -7010,7 +6731,7 @@ int showmode(void) } msg_puts_attr(_(" INSERT"), attr); } else if (restart_edit == 'I' || restart_edit == 'i' - || restart_edit == 'a') { + || restart_edit == 'a' || restart_edit == 'A') { msg_puts_attr(_(" (insert)"), attr); } else if (restart_edit == 'R') { msg_puts_attr(_(" (replace)"), attr); @@ -7090,10 +6811,10 @@ int showmode(void) clear_showcmd(); } - // If the last window has no status line, the ruler is after the mode - // message and must be redrawn + // If the last window has no status line and global statusline is disabled, + // the ruler is after the mode message and must be redrawn win_T *last = lastwin_nofloating(); - if (redrawing() && last->w_status_height == 0) { + if (redrawing() && last->w_status_height == 0 && global_stl_height() == 0) { win_redr_ruler(last, true); } redraw_cmdline = false; @@ -7282,7 +7003,7 @@ void draw_tabline(void) if (room > 0) { // Get buffer name in NameBuff[] get_trans_bufname(cwp->w_buffer); - (void)shorten_dir(NameBuff); + shorten_dir(NameBuff); len = vim_strsize(NameBuff); p = NameBuff; while (len > room) { @@ -7408,16 +7129,22 @@ int fillchar_status(int *attr, win_T *wp) return '='; } -/* - * Get the character to use in a separator between vertically split windows. - * Get its attributes in "*attr". - */ +/// Get the character to use in a separator between vertically split windows. +/// Get its attributes in "*attr". static int fillchar_vsep(win_T *wp, int *attr) { *attr = win_hl_attr(wp, HLF_C); return wp->w_p_fcs_chars.vert; } +/// Get the character to use in a separator between horizontally split windows. +/// Get its attributes in "*attr". +static int fillchar_hsep(win_T *wp, int *attr) +{ + *attr = win_hl_attr(wp, HLF_C); + return wp->w_p_fcs_chars.horiz; +} + /* * Return TRUE if redrawing should currently be done. */ @@ -7443,7 +7170,8 @@ void showruler(bool always) if (!always && !redrawing()) { return; } - if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) { + if ((*p_stl != NUL || *curwin->w_p_stl != NUL) + && (curwin->w_status_height || global_stl_height())) { redraw_custom_statusline(curwin); } else { win_redr_ruler(curwin, always); @@ -7462,6 +7190,7 @@ void showruler(bool always) static void win_redr_ruler(win_T *wp, bool always) { + bool is_stl_global = global_stl_height() > 0; static bool did_show_ext_ruler = false; // If 'ruler' off or redrawing disabled, don't do anything @@ -7479,7 +7208,7 @@ static void win_redr_ruler(win_T *wp, bool always) // Don't draw the ruler while doing insert-completion, it might overwrite // the (long) mode message. - if (wp == lastwin && lastwin->w_status_height == 0) { + if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) { if (edit_submode != NULL) { return; } @@ -7534,6 +7263,12 @@ static void win_redr_ruler(win_T *wp, bool always) off = wp->w_wincol; width = wp->w_width; part_of_status = true; + } else if (is_stl_global) { + row = Rows - p_ch - 1; + fillchar = fillchar_status(&attr, wp); + off = 0; + width = Columns; + part_of_status = true; } else { row = Rows - 1; fillchar = ' '; @@ -7573,7 +7308,7 @@ static void win_redr_ruler(win_T *wp, bool always) int i = (int)STRLEN(buffer); get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); int o = i + vim_strsize(buffer + i + 1); - if (wp->w_status_height == 0) { // can't use last char of screen + if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen o++; } int this_ru_col = ru_col - (Columns - width); @@ -7885,4 +7620,3 @@ win_T *get_win_by_grid_handle(handle_T handle) } return NULL; } - diff --git a/src/nvim/screen.h b/src/nvim/screen.h index d704a6eb8a..5e86dacd25 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -8,12 +8,10 @@ #include "nvim/pos.h" #include "nvim/types.h" -/* - * flags for update_screen() - * The higher the value, the higher the priority - */ -#define VALID 10 /* buffer not changed, or changes marked - with b_mod_* */ +// flags for update_screen() +// The higher the value, the higher the priority +#define VALID 10 // buffer not changed, or changes marked + // with b_mod_* #define INVERTED 20 // redisplay inverted part that changed #define INVERTED_ALL 25 // redisplay whole inverted part #define REDRAW_TOP 30 // display first w_upd_rows screen lines @@ -21,6 +19,14 @@ #define NOT_VALID 40 // buffer needs complete redraw #define CLEAR 50 // screen messed up, clear it +/// corner value flags for hsep_connected and vsep_connected +typedef enum { + WC_TOP_LEFT = 0, + WC_TOP_RIGHT, + WC_BOTTOM_LEFT, + WC_BOTTOM_RIGHT +} WindowCorner; + /// By default, all widows are draw on a single rectangular grid, represented by /// this ScreenGrid instance. In multigrid mode each window will have its own /// grid, then this is only used for global screen elements that hasn't been @@ -59,8 +65,8 @@ extern StlClickDefinition *tab_page_click_defs; /// Size of the tab_page_click_defs array extern long tab_page_click_defs_size; -#define W_ENDCOL(wp) (wp->w_wincol + wp->w_width) -#define W_ENDROW(wp) (wp->w_winrow + wp->w_height) +#define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width) +#define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.h.generated.h" diff --git a/src/nvim/search.c b/src/nvim/search.c index f45ae102d2..c30e69391f 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -13,6 +13,7 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" @@ -25,17 +26,18 @@ #include "nvim/func_attr.h" #include "nvim/getchar.h" #include "nvim/indent.h" +#include "nvim/indent_c.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/regexp.h" @@ -1069,7 +1071,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, * Find out the direction of the search. */ if (dirc == 0) { - dirc = spats[0].off.dir; + dirc = (char_u)spats[0].off.dir; } else { spats[0].off.dir = dirc; set_vv_searchforward(); @@ -1353,6 +1355,10 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } retval = 1; // pattern found + if (sia && sia->sa_wrapped) { + apply_autocmds(EVENT_SEARCHWRAPPED, NULL, NULL, false, NULL); + } + /* * Add character and/or line offset */ @@ -1808,6 +1814,9 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) initc = NUL; } else if (initc != '#' && initc != NUL) { find_mps_values(&initc, &findc, &backwards, true); + if (dir) { + backwards = (dir == FORWARD) ? false : true; + } if (findc == NUL) { return NULL; } @@ -2305,12 +2314,9 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) return (pos_T *)NULL; // never found it } -/* - * Check if line[] contains a / / comment. - * Return MAXCOL if not, otherwise return the column. - * TODO: skip strings. - */ -static int check_linecomment(const char_u *line) +/// Check if line[] contains a / / comment. +/// @returns MAXCOL if not, otherwise return the column. +int check_linecomment(const char_u *line) { const char_u *p = line; // scan from start // skip Lispish one-line comments @@ -2330,7 +2336,8 @@ static int check_linecomment(const char_u *line) in_str = true; } } else if (!in_str && ((p - line) < 2 - || (*(p - 1) != '\\' && *(p - 2) != '#'))) { + || (*(p - 1) != '\\' && *(p - 2) != '#')) + && !is_pos_in_string(line, (colnr_T)(p - line))) { break; // found! } p++; @@ -2340,9 +2347,11 @@ static int check_linecomment(const char_u *line) } } else { while ((p = vim_strchr(p, '/')) != NULL) { - // accept a double /, unless it's preceded with * and followed by *, - // because * / / * is an end and start of a C comment - if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')) { + // Accept a double /, unless it's preceded with * and followed by *, + // because * / / * is an end and start of a C comment. + // Only accept the position if it is not inside a string. + if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*') + && !is_pos_in_string(line, (colnr_T)(p - line))) { break; } ++p; @@ -3427,12 +3436,22 @@ int current_block(oparg_T *oap, long count, int include, int what, int other) // user wants. save_cpo = p_cpo; p_cpo = (char_u *)(vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"); - while (count-- > 0) { - if ((pos = findmatch(NULL, what)) == NULL) { - break; + if ((pos = findmatch(NULL, what)) != NULL) { + while (count-- > 0) { + if ((pos = findmatch(NULL, what)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } else { + while (count-- > 0) { + if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos } - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos } p_cpo = save_cpo; @@ -4745,6 +4764,536 @@ the_end: restore_last_search_pattern(); } +/// Fuzzy string matching +/// +/// Ported from the lib_fts library authored by Forrest Smith. +/// https://github.com/forrestthewoods/lib_fts/tree/master/code +/// +/// The following blog describes the fuzzy matching algorithm: +/// https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/ +/// +/// Each matching string is assigned a score. The following factors are checked: +/// - Matched letter +/// - Unmatched letter +/// - Consecutively matched letters +/// - Proximity to start +/// - Letter following a separator (space, underscore) +/// - Uppercase letter following lowercase (aka CamelCase) +/// +/// Matched letters are good. Unmatched letters are bad. Matching near the start +/// is good. Matching the first letter in the middle of a phrase is good. +/// Matching the uppercase letters in camel case entries is good. +/// +/// The score assigned for each factor is explained below. +/// File paths are different from file names. File extensions may be ignorable. +/// Single words care about consecutive matches but not separators or camel +/// case. +/// Score starts at 100 +/// Matched letter: +0 points +/// Unmatched letter: -1 point +/// Consecutive match bonus: +15 points +/// First letter bonus: +15 points +/// Separator bonus: +30 points +/// Camel case bonus: +30 points +/// Unmatched leading letter: -5 points (max: -15) +/// +/// There is some nuance to this. Scores don’t have an intrinsic meaning. The +/// score range isn’t 0 to 100. It’s roughly [50, 150]. Longer words have a +/// lower minimum score due to unmatched letter penalty. Longer search patterns +/// have a higher maximum score due to match bonuses. +/// +/// Separator and camel case bonus is worth a LOT. Consecutive matches are worth +/// quite a bit. +/// +/// There is a penalty if you DON’T match the first three letters. Which +/// effectively rewards matching near the start. However there’s no difference +/// in matching between the middle and end. +/// +/// There is not an explicit bonus for an exact match. Unmatched letters receive +/// a penalty. So shorter strings and closer matches are worth more. +typedef struct { + int idx; ///< used for stable sort + listitem_T *item; + int score; + list_T *lmatchpos; +} fuzzyItem_T; + +/// bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that +/// matching a whole word is preferred. +#define SEQUENTIAL_BONUS 40 +/// bonus if match occurs after a path separator +#define PATH_SEPARATOR_BONUS 30 +/// bonus if match occurs after a word separator +#define WORD_SEPARATOR_BONUS 25 +/// bonus if match is uppercase and prev is lower +#define CAMEL_BONUS 30 +/// bonus if the first letter is matched +#define FIRST_LETTER_BONUS 15 +/// penalty applied for every letter in str before the first match +#define LEADING_LETTER_PENALTY (-5) +/// maximum penalty for leading letters +#define MAX_LEADING_LETTER_PENALTY (-15) +/// penalty for every letter that doesn't match +#define UNMATCHED_LETTER_PENALTY (-1) +/// penalty for gap in matching positions (-2 * k) +#define GAP_PENALTY (-2) +/// Score for a string that doesn't fuzzy match the pattern +#define SCORE_NONE (-9999) + +#define FUZZY_MATCH_RECURSION_LIMIT 10 + +/// Compute a score for a fuzzy matched string. The matching character locations +/// are in 'matches'. +static int fuzzy_match_compute_score(const char_u *const str, const int strSz, + const uint32_t *const matches, const int numMatches) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + assert(numMatches > 0); // suppress clang "result of operation is garbage" + // Initialize score + int score = 100; + + // Apply leading letter penalty + int penalty = LEADING_LETTER_PENALTY * matches[0]; + if (penalty < MAX_LEADING_LETTER_PENALTY) { + penalty = MAX_LEADING_LETTER_PENALTY; + } + score += penalty; + + // Apply unmatched penalty + const int unmatched = strSz - numMatches; + score += UNMATCHED_LETTER_PENALTY * unmatched; + + // Apply ordering bonuses + for (int i = 0; i < numMatches; i++) { + const uint32_t currIdx = matches[i]; + + if (i > 0) { + const uint32_t prevIdx = matches[i - 1]; + + // Sequential + if (currIdx == prevIdx + 1) { + score += SEQUENTIAL_BONUS; + } else { + score += GAP_PENALTY * (currIdx - prevIdx); + } + } + + // Check for bonuses based on neighbor character value + if (currIdx > 0) { + // Camel case + const char_u *p = str; + int neighbor; + + for (uint32_t sidx = 0; sidx < currIdx; sidx++) { + neighbor = utf_ptr2char(p); + MB_PTR_ADV(p); + } + const int curr = utf_ptr2char(p); + + if (mb_islower(neighbor) && mb_isupper(curr)) { + score += CAMEL_BONUS; + } + + // Bonus if the match follows a separator character + if (neighbor == '/' || neighbor == '\\') { + score += PATH_SEPARATOR_BONUS; + } else if (neighbor == ' ' || neighbor == '_') { + score += WORD_SEPARATOR_BONUS; + } + } else { + // First letter + score += FIRST_LETTER_BONUS; + } + } + return score; +} + +/// Perform a recursive search for fuzzy matching 'fuzpat' in 'str'. +/// @return the number of matching characters. +static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32_t strIdx, + int *const outScore, const char_u *const strBegin, + const int strLen, const uint32_t *const srcMatches, + uint32_t *const matches, const int maxMatches, int nextMatch, + int *const recursionCount) + FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5, 8, 11) FUNC_ATTR_WARN_UNUSED_RESULT +{ + // Recursion params + bool recursiveMatch = false; + uint32_t bestRecursiveMatches[MAX_FUZZY_MATCHES]; + int bestRecursiveScore = 0; + + // Count recursions + (*recursionCount)++; + if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT) { + return 0; + } + + // Detect end of strings + if (*fuzpat == NUL || *str == NUL) { + return 0; + } + + // Loop through fuzpat and str looking for a match + bool first_match = true; + while (*fuzpat != NUL && *str != NUL) { + const int c1 = utf_ptr2char(fuzpat); + const int c2 = utf_ptr2char(str); + + // Found match + if (mb_tolower(c1) == mb_tolower(c2)) { + // Supplied matches buffer was too short + if (nextMatch >= maxMatches) { + return 0; + } + + // "Copy-on-Write" srcMatches into matches + if (first_match && srcMatches != NULL) { + memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0])); + first_match = false; + } + + // Recursive call that "skips" this match + uint32_t recursiveMatches[MAX_FUZZY_MATCHES]; + int recursiveScore = 0; + const char_u *const next_char = str + utfc_ptr2len(str); + if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, strBegin, strLen, + matches, recursiveMatches, + sizeof(recursiveMatches) / sizeof(recursiveMatches[0]), nextMatch, + recursionCount)) { + // Pick best recursive score + if (!recursiveMatch || recursiveScore > bestRecursiveScore) { + memcpy(bestRecursiveMatches, recursiveMatches, + MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0])); + bestRecursiveScore = recursiveScore; + } + recursiveMatch = true; + } + + // Advance + matches[nextMatch++] = strIdx; + MB_PTR_ADV(fuzpat); + } + MB_PTR_ADV(str); + strIdx++; + } + + // Determine if full fuzpat was matched + const bool matched = *fuzpat == NUL; + + // Calculate score + if (matched) { + *outScore = fuzzy_match_compute_score(strBegin, strLen, matches, nextMatch); + } + + // Return best result + if (recursiveMatch && (!matched || bestRecursiveScore > *outScore)) { + // Recursive score is better than "this" + memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0])); + *outScore = bestRecursiveScore; + return nextMatch; + } else if (matched) { + return nextMatch; // "this" score is better than recursive + } + + return 0; // no match +} + +/// fuzzy_match() +/// +/// Performs exhaustive search via recursion to find all possible matches and +/// match with highest score. +/// Scores values have no intrinsic meaning. Possible score range is not +/// normalized and varies with pattern. +/// Recursion is limited internally (default=10) to prevent degenerate cases +/// (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"). +/// Uses char_u for match indices. Therefore patterns are limited to +/// MAX_FUZZY_MATCHES characters. +/// +/// @return true if 'pat_arg' matches 'str'. Also returns the match score in +/// 'outScore' and the matching character positions in 'matches'. +bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matchseq, + int *const outScore, uint32_t *const matches, const int maxMatches) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + const int len = mb_charlen(str); + bool complete = false; + int numMatches = 0; + + *outScore = 0; + + char_u *const save_pat = vim_strsave(pat_arg); + char_u *pat = save_pat; + char_u *p = pat; + + // Try matching each word in 'pat_arg' in 'str' + while (true) { + if (matchseq) { + complete = true; + } else { + // Extract one word from the pattern (separated by space) + p = skipwhite(p); + if (*p == NUL) { + break; + } + pat = p; + while (*p != NUL && !ascii_iswhite(utf_ptr2char(p))) { + MB_PTR_ADV(p); + } + if (*p == NUL) { // processed all the words + complete = true; + } + *p = NUL; + } + + int score = 0; + int recursionCount = 0; + const int matchCount + = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL, matches + numMatches, + maxMatches - numMatches, 0, &recursionCount); + if (matchCount == 0) { + numMatches = 0; + break; + } + + // Accumulate the match score and the number of matches + *outScore += score; + numMatches += matchCount; + + if (complete) { + break; + } + + // try matching the next word + p++; + } + + xfree(save_pat); + return numMatches != 0; +} + +/// Sort the fuzzy matches in the descending order of the match score. +/// For items with same score, retain the order using the index (stable sort) +static int fuzzy_match_item_compare(const void *const s1, const void *const s2) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + const int v1 = ((const fuzzyItem_T *)s1)->score; + const int v2 = ((const fuzzyItem_T *)s2)->score; + const int idx1 = ((const fuzzyItem_T *)s1)->idx; + const int idx2 = ((const fuzzyItem_T *)s2)->idx; + + return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1; +} + +/// Fuzzy search the string 'str' in a list of 'items' and return the matching +/// strings in 'fmatchlist'. +/// If 'matchseq' is true, then for multi-word search strings, match all the +/// words in sequence. +/// If 'items' is a list of strings, then search for 'str' in the list. +/// If 'items' is a list of dicts, then either use 'key' to lookup the string +/// for each item or use 'item_cb' Funcref function to get the string. +/// If 'retmatchpos' is true, then return a list of positions where 'str' +/// matches for each item. +static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bool matchseq, + const char_u *const key, Callback *const item_cb, + const bool retmatchpos, list_T *const fmatchlist) + FUNC_ATTR_NONNULL_ARG(2, 5, 7) +{ + const long len = tv_list_len(items); + if (len == 0) { + return; + } + + fuzzyItem_T *const ptrs = xcalloc(len, sizeof(fuzzyItem_T)); + long i = 0; + bool found_match = false; + uint32_t matches[MAX_FUZZY_MATCHES]; + + // For all the string items in items, get the fuzzy matching score + TV_LIST_ITER(items, li, { + ptrs[i].idx = i; + ptrs[i].item = li; + ptrs[i].score = SCORE_NONE; + char_u *itemstr = NULL; + typval_T rettv; + rettv.v_type = VAR_UNKNOWN; + const typval_T *const tv = TV_LIST_ITEM_TV(li); + if (tv->v_type == VAR_STRING) { // list of strings + itemstr = tv->vval.v_string; + } else if (tv->v_type == VAR_DICT && (key != NULL || item_cb->type != kCallbackNone)) { + // For a dict, either use the specified key to lookup the string or + // use the specified callback function to get the string. + if (key != NULL) { + itemstr = (char_u *)tv_dict_get_string(tv->vval.v_dict, (const char *)key, false); + } else { + typval_T argv[2]; + + // Invoke the supplied callback (if any) to get the dict item + tv->vval.v_dict->dv_refcount++; + argv[0].v_type = VAR_DICT; + argv[0].vval.v_dict = tv->vval.v_dict; + argv[1].v_type = VAR_UNKNOWN; + if (callback_call(item_cb, 1, argv, &rettv)) { + if (rettv.v_type == VAR_STRING) { + itemstr = rettv.vval.v_string; + } + } + tv_dict_unref(tv->vval.v_dict); + } + } + + int score; + if (itemstr != NULL && fuzzy_match(itemstr, str, matchseq, &score, matches, + sizeof(matches) / sizeof(matches[0]))) { + // Copy the list of matching positions in itemstr to a list, if + // 'retmatchpos' is set. + if (retmatchpos) { + ptrs[i].lmatchpos = tv_list_alloc(kListLenMayKnow); + int j = 0; + const char_u *p = str; + while (*p != NUL) { + if (!ascii_iswhite(utf_ptr2char(p))) { + tv_list_append_number(ptrs[i].lmatchpos, matches[j]); + j++; + } + MB_PTR_ADV(p); + } + } + ptrs[i].score = score; + found_match = true; + } + i++; + tv_clear(&rettv); + }); + + if (found_match) { + // Sort the list by the descending order of the match score + qsort(ptrs, len, sizeof(fuzzyItem_T), fuzzy_match_item_compare); + + // For matchfuzzy(), return a list of matched strings. + // ['str1', 'str2', 'str3'] + // For matchfuzzypos(), return a list with three items. + // The first item is a list of matched strings. The second item + // is a list of lists where each list item is a list of matched + // character positions. The third item is a list of matching scores. + // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]] + list_T *l; + if (retmatchpos) { + const listitem_T *const li = tv_list_find(fmatchlist, 0); + assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL); + l = TV_LIST_ITEM_TV(li)->vval.v_list; + } else { + l = fmatchlist; + } + + // Copy the matching strings with a valid score to the return list + for (i = 0; i < len; i++) { + if (ptrs[i].score == SCORE_NONE) { + break; + } + tv_list_append_tv(l, TV_LIST_ITEM_TV(ptrs[i].item)); + } + + // next copy the list of matching positions + if (retmatchpos) { + const listitem_T *li = tv_list_find(fmatchlist, -2); + assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL); + l = TV_LIST_ITEM_TV(li)->vval.v_list; + for (i = 0; i < len; i++) { + if (ptrs[i].score == SCORE_NONE) { + break; + } + tv_list_append_list(l, ptrs[i].lmatchpos); + } + + // copy the matching scores + li = tv_list_find(fmatchlist, -1); + assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL); + l = TV_LIST_ITEM_TV(li)->vval.v_list; + for (i = 0; i < len; i++) { + if (ptrs[i].score == SCORE_NONE) { + break; + } + tv_list_append_number(l, ptrs[i].score); + } + } + } + xfree(ptrs); +} + +/// Do fuzzy matching. Returns the list of matched strings in 'rettv'. +/// If 'retmatchpos' is true, also returns the matching character positions. +static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, + const bool retmatchpos) + FUNC_ATTR_NONNULL_ALL +{ + // validate and get the arguments + if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { + semsg(_(e_listarg), retmatchpos ? "matchfuzzypos()" : "matchfuzzy()"); + return; + } + if (argvars[1].v_type != VAR_STRING || argvars[1].vval.v_string == NULL) { + semsg(_(e_invarg2), tv_get_string(&argvars[1])); + return; + } + + Callback cb = CALLBACK_NONE; + const char_u *key = NULL; + bool matchseq = false; + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { + emsg(_(e_dictreq)); + return; + } + + // To search a dict, either a callback function or a key can be + // specified. + dict_T *const d = argvars[2].vval.v_dict; + const dictitem_T *const di = tv_dict_find(d, "key", -1); + if (di != NULL) { + if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL + || *di->di_tv.vval.v_string == NUL) { + semsg(_(e_invarg2), tv_get_string(&di->di_tv)); + return; + } + key = (const char_u *)tv_get_string(&di->di_tv); + } else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) { + semsg(_(e_invargval), "text_cb"); + return; + } + if (tv_dict_find(d, "matchseq", -1) != NULL) { + matchseq = true; + } + } + + // get the fuzzy matches + tv_list_alloc_ret(rettv, retmatchpos ? 3 : kListLenUnknown); + if (retmatchpos) { + // For matchfuzzypos(), a list with three items are returned. First + // item is a list of matching strings, the second item is a list of + // lists with matching positions within each string and the third item + // is the list of scores of the matches. + tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown)); + tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown)); + tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown)); + } + + fuzzy_match_in_list(argvars[0].vval.v_list, (char_u *)tv_get_string(&argvars[1]), matchseq, key, + &cb, retmatchpos, rettv->vval.v_list); + callback_free(&cb); +} + +/// "matchfuzzy()" function +void f_matchfuzzy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_fuzzymatch(argvars, rettv, false); +} + +/// "matchfuzzypos()" function +void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_fuzzymatch(argvars, rettv, true); +} + /// Find identifiers or defines in included files. /// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase. /// @@ -5140,7 +5689,7 @@ search_line: // we read a line, set "already" to check this "line" later // if depth >= 0 we'll increase files[depth].lnum far - // bellow -- Acevedo + // below -- Acevedo already = aux = p = skipwhite(line); p = find_word_start(p); p = find_word_end(p); @@ -5235,6 +5784,9 @@ search_line: if (depth == -1) { // match in current file if (l_g_do_tagpreview != 0) { + if (!win_valid(curwin_save)) { + break; + } if (!GETFILE_SUCCESS(getfile(curwin_save->w_buffer->b_fnum, NULL, NULL, true, lnum, false))) { break; // failed to jump to file diff --git a/src/nvim/search.h b/src/nvim/search.h index 15b8d41f39..53059cc1ea 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -55,6 +55,9 @@ #define SEARCH_STAT_DEF_MAX_COUNT 99 #define SEARCH_STAT_BUF_LEN 12 +/// Maximum number of characters that can be fuzzy matched +#define MAX_FUZZY_MATCHES 256 + /// Structure containing offset definition for the last search pattern /// /// @note Only offset for the last search pattern is used, not for the last diff --git a/src/nvim/sha256.c b/src/nvim/sha256.c index 9d6a2f2c41..2c6199bf8b 100644 --- a/src/nvim/sha256.c +++ b/src/nvim/sha256.c @@ -73,8 +73,8 @@ static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFE GET_UINT32(W[14], data, 56); GET_UINT32(W[15], data, 60); -#define SHR(x, n) ((x & 0xFFFFFFFF) >> n) -#define ROTR(x, n) (SHR(x, n) | (x << (32 - n))) +#define SHR(x, n) (((x) & 0xFFFFFFFF) >> (n)) +#define ROTR(x, n) (SHR(x, n) | ((x) << (32 - (n)))) #define S0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) #define S1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) @@ -82,17 +82,16 @@ static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFE #define S2(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) #define S3(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) -#define F0(x, y, z) ((x & y) | (z & (x | y))) -#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F0(x, y, z) (((x) & (y)) | ((z) & ((x) | (y)))) +#define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define R(t) \ - (W[t] = S1(W[t - 2]) + W[t - 7] + \ - S0(W[t - 15]) + W[t - 16]) + (W[t] = S1(W[(t) - 2]) + W[(t) - 7] + S0(W[(t) - 15]) + W[(t) - 16]) #define P(a, b, c, d, e, f, g, h, x, K) { \ - temp1 = h + S3(e) + F1(e, f, g) + K + x; \ + temp1 = (h) + S3(e) + F1(e, f, g) + (K) + (x); \ temp2 = S2(a) + F0(a, b, c); \ - d += temp1; h = temp1 + temp2; \ + (d) += temp1; (h) = temp1 + temp2; \ } A = ctx->state[0]; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index e75a244031..6c0add87d3 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -831,7 +831,7 @@ static int shada_read_file(const char *const file, const int flags) ShaDaReadDef sd_reader; const int of_ret = open_shada_file_for_reading(fname, &sd_reader); - if (p_verbose > 0) { + if (p_verbose > 1) { verbose_enter(); smsg(_("Reading ShaDa file \"%s\"%s%s%s%s"), fname, @@ -1238,7 +1238,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) // string is close to useless: you can only use it with :& or :~ and // that’s all because s//~ is not available until the first call to // regtilde. Vim was not calling this for some reason. - (void)(char *)regtilde((char_u *)cur_entry.data.sub_string.sub, p_magic); + (void)(char *)regtilde((char_u *)cur_entry.data.sub_string.sub, p_magic, false); // Do not free shada entry: its allocated memory was saved above. break; case kSDItemHistoryEntry: @@ -3036,7 +3036,7 @@ shada_write_file_nomerge: {} return FAIL; } - if (p_verbose > 0) { + if (p_verbose > 1) { verbose_enter(); smsg(_("Writing ShaDa file \"%s\""), fname); verbose_leave(); diff --git a/src/nvim/sign.c b/src/nvim/sign.c index d8eea7f942..a50b4a5a99 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -13,6 +13,7 @@ #include "nvim/edit.h" #include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/highlight_group.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/screen.h" @@ -31,6 +32,7 @@ struct sign { char_u *sn_text; // text used instead of pixmap int sn_line_hl; // highlight ID for line int sn_text_hl; // highlight ID for text + int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set int sn_num_hl; // highlight ID for line number }; @@ -80,7 +82,7 @@ static signgroup_T *sign_group_ref(const char_u *groupname) hi = hash_lookup(&sg_table, (char *)groupname, STRLEN(groupname), hash); if (HASHITEM_EMPTY(hi)) { // new group - group = xmalloc((unsigned)(sizeof(signgroup_T) + STRLEN(groupname))); + group = xmalloc(sizeof(signgroup_T) + STRLEN(groupname)); STRCPY(group->sg_name, groupname); group->sg_refcount = 1; @@ -117,7 +119,7 @@ static void sign_group_unref(char_u *groupname) /// @return true if 'sign' is in 'group'. /// A sign can either be in the global group (sign->group == NULL) /// or in a named group. If 'group' is '*', then the sign is part of the group. -bool sign_in_group(sign_entry_T *sign, const char_u *group) +static bool sign_in_group(sign_entry_T *sign, const char_u *group) { return ((group != NULL && STRCMP(group, "*") == 0) || (group == NULL && sign->se_group == NULL) @@ -126,7 +128,7 @@ bool sign_in_group(sign_entry_T *sign, const char_u *group) } /// Get the next free sign identifier in the specified group -int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) +static int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) { int id = 1; signgroup_T *group = NULL; @@ -195,7 +197,8 @@ static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int if (next != NULL) { next->se_prev = newsign; } - buf->b_signcols_valid = false; + + buf_signcols_add_check(buf, newsign); if (prev == NULL) { // When adding first sign need to redraw the windows to create the @@ -243,8 +246,21 @@ static void insert_sign_by_lnum_prio(buf_T *buf, sign_entry_T *prev, int id, con insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon); } +/// Lookup a sign by typenr. Returns NULL if sign is not found. +static sign_T *find_sign_by_typenr(int typenr) +{ + sign_T *sp; + + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (sp->sn_typenr == typenr) { + return sp; + } + } + return NULL; +} + /// Get the name of a sign by its typenr. -char_u *sign_typenr2name(int typenr) +static char_u *sign_typenr2name(int typenr) { sign_T *sp; @@ -257,7 +273,7 @@ char_u *sign_typenr2name(int typenr) } /// Return information about a sign in a Dict -dict_T *sign_get_info(sign_entry_T *sign) +static dict_T *sign_get_info(sign_entry_T *sign) { dict_T *d = tv_dict_alloc(); tv_dict_add_nr(d, S_LEN("id"), sign->se_id); @@ -355,8 +371,8 @@ static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign) /// @param lnum line number which gets the mark /// @param typenr typenr of sign we are adding /// @param has_text_or_icon sign has text or icon -void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T lnum, int typenr, - bool has_text_or_icon) +static void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T lnum, + int typenr, bool has_text_or_icon) { sign_entry_T *sign; // a sign in the signlist sign_entry_T *prev; // the previous sign @@ -402,7 +418,8 @@ void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T /// @param group sign group /// @param typenr typenr of sign we are adding /// @param prio sign priority -linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group, int typenr, int prio) +static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group, int typenr, + int prio) { sign_entry_T *sign; // a sign in the signlist @@ -453,19 +470,6 @@ sign_attrs_T *sign_get_attr(SignType type, sign_attrs_T sattrs[], int idx, int m return NULL; } -/// Lookup a sign by typenr. Returns NULL if sign is not found. -static sign_T *find_sign_by_typenr(int typenr) -{ - sign_T *sp; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_typenr == typenr) { - return sp; - } - } - return NULL; -} - /// Return the attributes of all the signs placed on line 'lnum' in buffer /// 'buf'. Used when refreshing the screen. Returns the number of signs. /// @param buf Buffer in which to search @@ -499,9 +503,14 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) if (sp->sn_line_hl != 0) { sattr.sat_linehl = syn_id2attr(sp->sn_line_hl); } + if (sp->sn_cul_hl != 0) { + sattr.sat_culhl = syn_id2attr(sp->sn_cul_hl); + } if (sp->sn_num_hl != 0) { sattr.sat_numhl = syn_id2attr(sp->sn_num_hl); } + // Store the priority so we can mesh in extmark signs later + sattr.sat_prio = sign->se_priority; } sattrs[nr_matches] = sattr; @@ -528,14 +537,13 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) /// /// @return the line number of the deleted sign. If multiple signs are deleted, /// then returns the line number of the last sign deleted. -linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) +static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) { sign_entry_T **lastp; // pointer to pointer to current sign sign_entry_T *sign; // a sign in a b_signlist sign_entry_T *next; // the next sign in a b_signlist linenr_T lnum; // line number whose sign was deleted - buf->b_signcols_valid = false; lastp = &buf->b_signlist; lnum = 0; for (sign = buf->b_signlist; sign != NULL; sign = next) { @@ -548,6 +556,7 @@ linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) next->se_prev = sign->se_prev; } lnum = sign->se_lnum; + buf_signcols_del_check(buf, lnum, lnum); if (sign->se_group != NULL) { sign_group_unref(sign->se_group->sg_name); } @@ -585,7 +594,7 @@ linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) /// @param buf buffer to store sign in /// @param id sign ID /// @param group sign group -int buf_findsign(buf_T *buf, int id, char_u *group) +static int buf_findsign(buf_T *buf, int id, char_u *group) { sign_entry_T *sign; // a sign in the signlist @@ -628,7 +637,7 @@ static sign_entry_T *buf_getsign_at_line(buf_T *buf, linenr_T lnum, char_u *grou /// @param buf buffer whose sign we are searching for /// @param lnum line number of sign /// @param groupname sign group name -int buf_findsign_id(buf_T *buf, linenr_T lnum, char_u *groupname) +static int buf_findsign_id(buf_T *buf, linenr_T lnum, char_u *groupname) { sign_entry_T *sign; // a sign in the signlist @@ -669,11 +678,11 @@ void buf_delete_signs(buf_T *buf, char_u *group) lastp = &sign->se_next; } } - buf->b_signcols_valid = false; + buf_signcols_del_check(buf, 1, MAXLNUM); } /// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers. -void sign_list_placed(buf_T *rbuf, char_u *sign_group) +static void sign_list_placed(buf_T *rbuf, char_u *sign_group) { buf_T *buf; sign_entry_T *sign; @@ -731,14 +740,19 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_a int is_fixed = 0; int signcol = win_signcol_configured(curwin, &is_fixed); - curbuf->b_signcols_valid = false; + bool delete = amount == MAXLNUM; + + if (delete) { + buf_signcols_del_check(curbuf, line1, line2); + } + lastp = &curbuf->b_signlist; for (sign = curbuf->b_signlist; sign != NULL; sign = next) { next = sign->se_next; new_lnum = sign->se_lnum; if (sign->se_lnum >= line1 && sign->se_lnum <= line2) { - if (amount != MAXLNUM) { + if (!delete) { new_lnum += amount; } else if (!is_fixed || signcol >= 2) { *lastp = next; @@ -900,8 +914,8 @@ static int sign_define_init_text(sign_T *sp, char_u *text) } /// Define a new sign or update an existing sign -int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text, char_u *texthl, - char *numhl) +static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text, + char_u *texthl, char_u *culhl, char *numhl) { sign_T *sp_prev; sign_T *sp; @@ -939,22 +953,42 @@ int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text } if (linehl != NULL) { - sp->sn_line_hl = syn_check_group((char *)linehl, (int)STRLEN(linehl)); + if (*linehl == NUL) { + sp->sn_line_hl = 0; + } else { + sp->sn_line_hl = syn_check_group((char *)linehl, STRLEN(linehl)); + } } if (texthl != NULL) { - sp->sn_text_hl = syn_check_group((char *)texthl, (int)STRLEN(texthl)); + if (*texthl == NUL) { + sp->sn_text_hl = 0; + } else { + sp->sn_text_hl = syn_check_group((char *)texthl, STRLEN(texthl)); + } + } + + if (culhl != NULL) { + if (*culhl == NUL) { + sp->sn_cul_hl = 0; + } else { + sp->sn_cul_hl = syn_check_group((char *)culhl, STRLEN(culhl)); + } } if (numhl != NULL) { - sp->sn_num_hl = syn_check_group(numhl, (int)STRLEN(numhl)); + if (*numhl == NUL) { + sp->sn_num_hl = 0; + } else { + sp->sn_num_hl = syn_check_group(numhl, STRLEN(numhl)); + } } return OK; } /// Free the sign specified by 'name'. -int sign_undefine_by_name(const char_u *name) +static int sign_undefine_by_name(const char_u *name) { sign_T *sp_prev; sign_T *sp; @@ -969,17 +1003,6 @@ 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) { @@ -993,10 +1016,20 @@ static void sign_list_by_name(char_u *name) } } +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; + } +} /// Place a sign at the specified file location or update a sign. -int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name, buf_T *buf, - linenr_T lnum, int prio) +static int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name, buf_T *buf, + linenr_T lnum, int prio) { sign_T *sp; @@ -1048,7 +1081,7 @@ int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name, } /// Unplace the specified sign -int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum) +static int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum) { if (buf->b_signlist == NULL) { // No signs in the buffer return OK; @@ -1092,7 +1125,7 @@ static void sign_unplace_at_cursor(char_u *groupname) } /// Jump to a sign. -linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf) +static linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf) { linenr_T lnum; @@ -1133,6 +1166,7 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) char_u *text = NULL; char_u *linehl = NULL; char_u *texthl = NULL; + char_u *culhl = NULL; char_u *numhl = NULL; int failed = false; @@ -1155,6 +1189,9 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) } else if (STRNCMP(arg, "texthl=", 7) == 0) { arg += 7; texthl = vim_strnsave(arg, (size_t)(p - arg)); + } else if (STRNCMP(arg, "culhl=", 6) == 0) { + arg += 6; + culhl = vim_strnsave(arg, (size_t)(p - arg)); } else if (STRNCMP(arg, "numhl=", 6) == 0) { arg += 6; numhl = vim_strnsave(arg, (size_t)(p - arg)); @@ -1166,13 +1203,14 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) } if (!failed) { - sign_define_by_name(sign_name, icon, linehl, text, texthl, (char *)numhl); + sign_define_by_name(sign_name, icon, linehl, text, texthl, culhl, (char *)numhl); } xfree(icon); xfree(text); xfree(linehl); xfree(texthl); + xfree(culhl); xfree(numhl); } @@ -1472,27 +1510,34 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict) if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("linehl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("linehl"), p); } if (sp->sn_text_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, false); if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("texthl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("texthl"), p); + } + if (sp->sn_cul_hl > 0) { + p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, false); + if (p == NULL) { + p = "NONE"; + } + tv_dict_add_str(retdict, S_LEN("culhl"), p); } if (sp->sn_num_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, false); if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("numhl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("numhl"), p); } } /// If 'name' is NULL, return a list of all the defined signs. /// Otherwise, return information about the specified sign. -void sign_getlist(const char_u *name, list_T *retlist) +static void sign_getlist(const char_u *name, list_T *retlist) { sign_T *sp = first_sign; dict_T *dict; @@ -1562,8 +1607,8 @@ static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const /// Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the /// sign placed at the line number. If 'lnum' is zero, return all the signs /// placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers. -void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char_u *sign_group, - list_T *retlist) +static void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char_u *sign_group, + list_T *retlist) { if (buf != NULL) { sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist); @@ -1609,6 +1654,16 @@ static void sign_list_defined(sign_T *sp) msg_puts(p); } } + if (sp->sn_cul_hl > 0) { + msg_puts(" culhl="); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_cul_hl - 1, false); + if (p == NULL) { + msg_puts("NONE"); + } else { + msg_puts(p); + } + } if (sp->sn_num_hl > 0) { msg_puts(" numhl="); const char *const p = get_highlight_name_ext(NULL, @@ -1694,7 +1749,7 @@ char_u *get_sign_name(expand_T *xp, int idx) case EXP_SUBCMD: return (char_u *)cmds[idx]; case EXP_DEFINE: { - char *define_arg[] = { "icon=", "linehl=", "text=", "texthl=", "numhl=", + char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=", NULL }; return (char_u *)define_arg[idx]; } @@ -1803,6 +1858,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) case SIGNCMD_DEFINE: if (STRNCMP(last, "texthl", 6) == 0 || STRNCMP(last, "linehl", 6) == 0 + || STRNCMP(last, "culhl", 5) == 0 || STRNCMP(last, "numhl", 5) == 0) { xp->xp_context = EXPAND_HIGHLIGHT; } else if (STRNCMP(last, "icon", 4) == 0) { @@ -1840,13 +1896,14 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) /// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on /// failure. -int sign_define_from_dict(const char *name_arg, dict_T *dict) +static int sign_define_from_dict(const char *name_arg, dict_T *dict) { char *name = NULL; char *icon = NULL; char *linehl = NULL; char *text = NULL; char *texthl = NULL; + char *culhl = NULL; char *numhl = NULL; int retval = -1; @@ -1866,11 +1923,12 @@ int sign_define_from_dict(const char *name_arg, dict_T *dict) linehl = tv_dict_get_string(dict, "linehl", true); text = tv_dict_get_string(dict, "text", true); texthl = tv_dict_get_string(dict, "texthl", true); + culhl = tv_dict_get_string(dict, "culhl", true); numhl = tv_dict_get_string(dict, "numhl", true); } if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, - (char_u *)text, (char_u *)texthl, numhl) + (char_u *)text, (char_u *)texthl, (char_u *)culhl, numhl) == OK) { retval = 0; } @@ -1881,6 +1939,7 @@ cleanup: xfree(linehl); xfree(text); xfree(texthl); + xfree(culhl); xfree(numhl); return retval; @@ -1888,7 +1947,7 @@ cleanup: /// Define multiple signs using attributes from list 'l' and store the return /// values in 'retlist'. -void sign_define_multiple(list_T *l, list_T *retlist) +static void sign_define_multiple(list_T *l, list_T *retlist) { int retval; @@ -1903,10 +1962,156 @@ void sign_define_multiple(list_T *l, list_T *retlist) }); } +/// "sign_define()" function +void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name; + + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { + // Define multiple signs + tv_list_alloc_ret(rettv, kListLenMayKnow); + + sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list); + return; + } + + // Define a single sign + rettv->vval.v_number = -1; + + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return; + } + + rettv->vval.v_number = sign_define_from_dict(name, + argvars[1].v_type == + VAR_DICT ? argvars[1].vval.v_dict : NULL); +} + +/// "sign_getdefined()" function +void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name = NULL; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + name = tv_get_string(&argvars[0]); + } + + sign_getlist((const char_u *)name, rettv->vval.v_list); +} + +/// "sign_getplaced()" function +void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *buf = NULL; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int sign_id = 0; + const char *group = NULL; + bool notanum = false; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + // get signs placed in the specified buffer + buf = get_buf_arg(&argvars[0]); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT + || ((dict = argvars[1].vval.v_dict) == NULL)) { + emsg(_(e_dictreq)); + return; + } + if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { + // get signs placed at this line + lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + return; + } + (void)lnum; + lnum = tv_get_lnum(&di->di_tv); + } + if ((di = tv_dict_find(dict, "id", -1)) != NULL) { + // get sign placed with this identifier + sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + return; + } + } + if ((di = tv_dict_find(dict, "group", -1)) != NULL) { + group = tv_get_string_chk(&di->di_tv); + if (group == NULL) { + return; + } + if (*group == '\0') { // empty string means global group + group = NULL; + } + } + } + } + + sign_get_placed(buf, lnum, sign_id, (const char_u *)group, + rettv->vval.v_list); +} + +/// "sign_jump()" function +void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + char *sign_group = NULL; + buf_T *buf; + bool notanum = false; + + rettv->vval.v_number = -1; + + // Sign identifier + sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); + if (notanum) { + return; + } + if (sign_id <= 0) { + emsg(_(e_invarg)); + return; + } + + // Sign group + const char *sign_group_chk = tv_get_string_chk(&argvars[1]); + if (sign_group_chk == NULL) { + return; + } + if (sign_group_chk[0] == '\0') { + sign_group = NULL; // global sign group + } else { + sign_group = xstrdup(sign_group_chk); + } + + // Buffer to place the sign + buf = get_buf_arg(&argvars[2]); + if (buf == NULL) { + goto cleanup; + } + + rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); + +cleanup: + xfree(sign_group); +} + /// Place a new sign using the values specified in dict 'dict'. Returns the sign /// identifier if successfully placed, otherwise returns 0. -int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv, typval_T *buf_tv, - dict_T *dict) +static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv, + typval_T *buf_tv, dict_T *dict) { int sign_id = 0; char_u *group = NULL; @@ -2018,8 +2223,50 @@ cleanup: return ret_sign_id; } +/// "sign_place()" function +void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict = NULL; + + rettv->vval.v_number = -1; + + if (argvars[4].v_type != VAR_UNKNOWN + && (argvars[4].v_type != VAR_DICT + || ((dict = argvars[4].vval.v_dict) == NULL))) { + emsg(_(e_dictreq)); + return; + } + + rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3], + dict); +} + +/// "sign_placelist()" function. Place multiple signs. +void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } + + // Process the List of sign attributes + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + sign_id = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { + emsg(_(e_dictreq)); + } + tv_list_append_number(rettv->vval.v_list, sign_id); + }); +} + /// Undefine multiple signs -void sign_undefine_multiple(list_T *l, list_T *retlist) +static void sign_undefine_multiple(list_T *l, list_T *retlist) { char_u *name; int retval; @@ -2034,9 +2281,41 @@ void sign_undefine_multiple(list_T *l, list_T *retlist) }); } +/// "sign_undefine()" function +void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name; + + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { + // Undefine multiple signs + tv_list_alloc_ret(rettv, kListLenMayKnow); + + sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list); + return; + } + + rettv->vval.v_number = -1; + + if (argvars[0].v_type == VAR_UNKNOWN) { + // Free all the signs + free_signs(); + rettv->vval.v_number = 0; + } else { + // Free only the specified sign + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; + } + + if (sign_undefine_by_name((const char_u *)name) == OK) { + rettv->vval.v_number = 0; + } + } +} + /// Unplace the sign with attributes specified in 'dict'. Returns 0 on success /// and -1 on failure. -int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict) +static int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict) { dictitem_T *di; int sign_id = 0; @@ -2092,3 +2371,48 @@ cleanup: return retval; } +/// "sign_unplace()" function +void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict = NULL; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type != VAR_STRING) { + emsg(_(e_invarg)); + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return; + } + dict = argvars[1].vval.v_dict; + } + + rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict); +} + +/// "sign_unplacelist()" function +void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int retval; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } + + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + retval = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { + emsg(_(e_dictreq)); + } + tv_list_append_number(rettv->vval.v_list, retval); + }); +} diff --git a/src/nvim/sign.h b/src/nvim/sign.h index 9044c2d0bb..6e75a4e62b 100644 --- a/src/nvim/sign.h +++ b/src/nvim/sign.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" +#include "nvim/eval/funcs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/sign_defs.h" diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index 46436b2c8e..6d28c1849a 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -38,7 +38,9 @@ typedef struct sign_attrs_S { char_u *sat_text; int sat_texthl; int sat_linehl; + int sat_culhl; int sat_numhl; + int sat_prio; // Used for inserting extmark signs } sign_attrs_T; #define SIGN_SHOW_MAX 9 diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 85d1e139bf..9fa594bc96 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -62,11 +62,11 @@ // Disadvantage: When "the" is typed as "hte" it sounds quite different ("@" // vs "ht") and goes down in the list. // Used when 'spellsuggest' is set to "best". -#define RESCORE(word_score, sound_score) ((3 * word_score + sound_score) / 4) +#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4) // Do the opposite: based on a maximum end score and a known sound score, // compute the maximum word score that can be used. -#define MAXSCORE(word_score, sound_score) ((4 * word_score - sound_score) / 3) +#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3) #include <assert.h> #include <inttypes.h> @@ -94,12 +94,12 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" +#include "nvim/input.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -122,7 +122,7 @@ #define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP) // Result values. Lower number is accepted over higher one. -#define SP_BANNED -1 +#define SP_BANNED (-1) #define SP_RARE 0 #define SP_OK 1 #define SP_LOCAL 2 @@ -176,7 +176,7 @@ typedef struct { #define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i]) // True if a word appears in the list of banned words. -#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&su->su_banned, word))) +#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word))) // Number of suggestions kept when cleaning up. We need to keep more than // what is displayed, because when rescore_suggestions() is called the score @@ -219,13 +219,13 @@ typedef struct { #define SCORE_THRES3 100 // word count threshold for COMMON3 // When trying changed soundfold words it becomes slow when trying more than -// two changes. With less then two changes it's slightly faster but we miss a +// two changes. With less than two changes it's slightly faster but we miss a // few good suggestions. In rare cases we need to try three of four changes. #define SCORE_SFMAX1 200 // maximum score for first try #define SCORE_SFMAX2 300 // maximum score for second try #define SCORE_SFMAX3 400 // maximum score for third try -#define SCORE_BIG SCORE_INS * 3 // big difference +#define SCORE_BIG (SCORE_INS * 3) // big difference #define SCORE_MAXMAX 999999 // accept any score #define SCORE_LIMITMAX 350 // for spell_edit_score_limit() @@ -1628,7 +1628,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att } // For spell checking: concatenate the start of the following line "line" into -// "buf", blanking-out special characters. Copy less then "maxlen" bytes. +// "buf", blanking-out special characters. Copy less than "maxlen" bytes. // Keep the blanks at the start of the next line, this is used in win_line() // to skip those bytes if the word was OK. void spell_cat_line(char_u *buf, char_u *line, int maxlen) @@ -3066,7 +3066,7 @@ void spell_suggest(int count) ml_replace(curwin->w_cursor.lnum, p, false); curwin->w_cursor.col = c; - changed_bytes(curwin->w_cursor.lnum, c); + inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen); } else { curwin->w_cursor = prev_cursor; } @@ -3690,7 +3690,7 @@ static void suggest_try_change(suginfo_T *su) // Check the maximum score, if we go over it we won't try this change. #define TRY_DEEPER(su, stack, depth, add) \ - (stack[depth].ts_score + (add) < su->su_maxscore) + ((depth) < MAXWLEN - 1 && (stack)[depth].ts_score + (add) < (su)->su_maxscore) // Try finding suggestions by adding/removing/swapping letters. // @@ -3794,6 +3794,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } } + // The loop may take an indefinite amount of time. Break out after five + // sectonds. TODO(vim): add an option for the time limit. + proftime_T time_limit = profile_setlimit(5000); + // Loop to find all suggestions. At each round we either: // - For the current state try one operation, advance "ts_curi", // increase "depth". @@ -3824,7 +3828,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // At end of a prefix or at start of prefixtree: check for // following word. - if (byts[arridx] == 0 || n == (int)STATE_NOPREFIX) { + if (depth < MAXWLEN - 1 && (byts[arridx] == 0 || n == STATE_NOPREFIX)) { // Set su->su_badflags to the caps type at this position. // Use the caps type until here for the prefix itself. n = nofold_len(fword, sp->ts_fidx, su->su_badptr); @@ -4082,7 +4086,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // char, e.g., "thes," -> "these". p = fword + sp->ts_fidx; MB_PTR_BACK(fword, p); - if (!spell_iswordp(p, curwin)) { + if (!spell_iswordp(p, curwin) && *preword != NUL) { p = preword + STRLEN(preword); MB_PTR_BACK(preword, p); if (spell_iswordp(p, curwin)) { @@ -4927,6 +4931,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (--breakcheckcount == 0) { os_breakcheck(); breakcheckcount = 1000; + if (profile_passed_limit(time_limit)) { + got_int = true; + } } } } @@ -5284,7 +5291,7 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * } static sftword_T dumsft; -#define HIKEY2SFT(p) ((sftword_T *)(p - (dumsft.sft_word - (char_u *)&dumsft))) +#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft))) #define HI2SFT(hi) HIKEY2SFT((hi)->hi_key) // Prepare for calling suggest_try_soundalike(). @@ -6106,7 +6113,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) for (; ((ws = smp[n].sm_lead_w)[0] & 0xff) == (c & 0xff) && ws[0] != NUL; ++n) { // Quickly skip entries that don't match the word. Most - // entries are less then three chars, optimize for that. + // entries are less than three chars, optimize for that. if (c != ws[0]) { continue; } @@ -7057,7 +7064,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) arridx[depth] = idxs[n]; curi[depth] = 1; - // Check if this characters matches with the pattern. + // Check if this character matches with the pattern. // If not skip the whole tree below it. // Always ignore case here, dump_word() will check // proper case later. This isn't exactly right when diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 220f51cd2f..12e44cb7ff 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -93,9 +93,9 @@ typedef int salfirst_T; // Values for SP_*ERROR are negative, positive values are used by // read_cnt_string(). -#define SP_TRUNCERROR -1 // spell file truncated error -#define SP_FORMERROR -2 // format error in spell file -#define SP_OTHERERROR -3 // other error while reading spell file +#define SP_TRUNCERROR (-1) // spell file truncated error +#define SP_FORMERROR (-2) // format error in spell file +#define SP_OTHERERROR (-3) // other error while reading spell file // Structure used to store words and other info for one language, loaded from // a .spl file. diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index ae4514dd30..3d3e6e728c 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -237,8 +237,8 @@ #include "nvim/fileio.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/regexp.h" @@ -1850,7 +1850,7 @@ static void spell_reload_one(char_u *fname, bool added_word) // In the postponed prefixes tree wn_flags is used to store the WFP_ flags, // but it must be negative to indicate the prefix tree to tree_add_word(). // Use a negative number with the lower 8 bits zero. -#define PFX_FLAGS -256 +#define PFX_FLAGS (-256) // flags for "condit" argument of store_aff_word() #define CONDIT_COMB 1 // affix must combine @@ -4395,7 +4395,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // // The table with character flags and the table for case folding. // This makes sure the same characters are recognized as word characters - // when generating an when using a spell file. + // when generating and when using a spell file. // Skip this for ASCII, the table may conflict with the one used for // 'encoding'. // Also skip this for an .add.spl file, the main spell file must contain @@ -4446,10 +4446,10 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) putc(SN_PREFCOND, fd); // <sectionID> putc(SNF_REQUIRED, fd); // <sectionflags> - size_t l = (size_t)write_spell_prefcond(NULL, &spin->si_prefcond); + size_t l = (size_t)write_spell_prefcond(NULL, &spin->si_prefcond, &fwv); put_bytes(fd, l, 4); // <sectionlen> - write_spell_prefcond(fd, &spin->si_prefcond); + write_spell_prefcond(fd, &spin->si_prefcond, &fwv); } // SN_REP: <repcount> <rep> ... @@ -5122,7 +5122,7 @@ static int sug_filltable(spellinfo_T *spin, wordnode_T *node, int startwordnr, g wordnr++; // Remove extra NUL entries, we no longer need them. We don't - // bother freeing the nodes, the won't be reused anyway. + // bother freeing the nodes, they won't be reused anyway. while (p->wn_sibling != NULL && p->wn_sibling->wn_byte == NUL) { p->wn_sibling = p->wn_sibling->wn_sibling; } @@ -5580,6 +5580,9 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo while (!vim_fgets(line, MAXWLEN * 2, fd)) { fpos = fpos_next; fpos_next = ftell(fd); + if (fpos_next < 0) { + break; // should never happen + } if (STRNCMP(word, line, len) == 0 && (line[len] == '/' || line[len] < ' ')) { // Found duplicate word. Remove it by writing a '#' at @@ -5654,7 +5657,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo // If the .add file is edited somewhere, reload it. if (buf != NULL) { - buf_reload(buf, buf->b_orig_mode); + buf_reload(buf, buf->b_orig_mode, false); } redraw_all_later(SOME_VALID); @@ -5793,7 +5796,7 @@ static int set_spell_finish(spelltab_T *new_st) // Write the table with prefix conditions to the .spl file. // When "fd" is NULL only count the length of what is written. -static int write_spell_prefcond(FILE *fd, garray_T *gap) +static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv) { assert(gap->ga_len >= 0); @@ -5801,8 +5804,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap) put_bytes(fd, (uintmax_t)gap->ga_len, 2); // <prefcondcnt> } size_t totlen = 2 + (size_t)gap->ga_len; // <prefcondcnt> and <condlen> bytes - size_t x = 1; // collect return value of fwrite() - for (int i = 0; i < gap->ga_len; ++i) { + for (int i = 0; i < gap->ga_len; i++) { // <prefcond> : <condlen> <condstr> char_u *p = ((char_u **)gap->ga_data)[i]; if (p != NULL) { @@ -5810,7 +5812,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap) if (fd != NULL) { assert(len <= INT_MAX); fputc((int)len, fd); - x &= fwrite(p, len, 1, fd); + *fwv &= fwrite(p, len, 1, fd); } totlen += len; } else if (fd != NULL) { diff --git a/src/nvim/state.c b/src/nvim/state.c index 4eb0073873..056828574f 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -4,14 +4,18 @@ #include <assert.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/edit.h" +#include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/getchar.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/main.h" +#include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/os/input.h" +#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -36,15 +40,27 @@ void state_enter(VimState *s) int key; getkey: - if (char_avail() || using_script() || input_available()) { - // Don't block for events if there's a character already available for - // processing. Characters can come from mappings, scripts and other - // sources, so this scenario is very common. + // Apply mappings first by calling vpeekc() directly. + // - If vpeekc() returns non-NUL, there is a character already available for processing, so + // don't block for events. vgetc() may still block, in case of an incomplete UTF-8 sequence. + // - If vpeekc() returns NUL, vgetc() will block, and there are three cases: + // - There is no input available. + // - All of available input maps to an empty string. + // - There is an incomplete mapping. + // A blocking wait for a character should only be done in the third case, which is the only + // case of the three where typebuf.tb_len > 0 after vpeekc() returns NUL. + if (vpeekc() != NUL || typebuf.tb_len > 0) { key = safe_vgetc(); } else if (!multiqueue_empty(main_loop.events)) { // Event was made available after the last multiqueue_process_events call key = K_EVENT; } else { + // Duplicate display updating logic in vgetorpeek() + if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0 + && must_redraw != 0 && !need_wait_return) { + update_screen(0); + setcursor(); // put cursor back where it belongs + } // Flush screen updates before blocking ui_flush(); // Call `os_inchar` directly to block for events or user input without @@ -52,12 +68,17 @@ getkey: // mapping engine. (void)os_inchar(NULL, 0, -1, 0, main_loop.events); // If an event was put into the queue, we send K_EVENT directly. - key = !multiqueue_empty(main_loop.events) - ? K_EVENT - : safe_vgetc(); + if (!multiqueue_empty(main_loop.events)) { + key = K_EVENT; + } else { + goto getkey; + } } if (key == K_EVENT) { + // An event handler may use the value of reg_executing. + // Clear it if it should be cleared when getting the next character. + check_end_reg_executing(true); may_sync_undo(); } @@ -105,15 +126,17 @@ void state_handle_k_event(void) /// Return true if in the current mode we need to use virtual. bool virtual_active(void) { + unsigned int cur_ve_flags = get_ve_flags(); + // While an operator is being executed we return "virtual_op", because // VIsual_active has already been reset, thus we can't check for "block" // being used. if (virtual_op != kNone) { return virtual_op; } - return ve_flags == VE_ALL - || ((ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V) - || ((ve_flags & VE_INSERT) && (State & INSERT)); + return cur_ve_flags == VE_ALL + || ((cur_ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V) + || ((cur_ve_flags & VE_INSERT) && (State & INSERT)); } /// VISUAL, SELECTMODE and OP_PENDING State are never set, they are equal to @@ -133,72 +156,105 @@ int get_real_state(void) return State; } -/// @returns[allocated] mode string -char *get_mode(void) +/// Returns the current mode as a string in "buf[MODE_MAX_LENGTH]", NUL +/// terminated. +/// The first character represents the major mode, the following ones the minor +/// ones. +void get_mode(char *buf) { - char *buf = xcalloc(4, sizeof(char)); + int i = 0; if (VIsual_active) { if (VIsual_select) { - buf[0] = (char)(VIsual_mode + 's' - 'v'); + buf[i++] = (char)(VIsual_mode + 's' - 'v'); } else { - buf[0] = (char)VIsual_mode; + buf[i++] = (char)VIsual_mode; if (restart_VIsual_select) { - buf[1] = 's'; + buf[i++] = 's'; } } } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE || State == CONFIRM) { - buf[0] = 'r'; + buf[i++] = 'r'; if (State == ASKMORE) { - buf[1] = 'm'; + buf[i++] = 'm'; } else if (State == CONFIRM) { - buf[1] = '?'; + buf[i++] = '?'; } } else if (State == EXTERNCMD) { - buf[0] = '!'; + buf[i++] = '!'; } else if (State & INSERT) { if (State & VREPLACE_FLAG) { - buf[0] = 'R'; - buf[1] = 'v'; + buf[i++] = 'R'; + buf[i++] = 'v'; if (ins_compl_active()) { - buf[2] = 'c'; + buf[i++] = 'c'; } else if (ctrl_x_mode_not_defined_yet()) { - buf[2] = 'x'; + buf[i++] = 'x'; } } else { if (State & REPLACE_FLAG) { - buf[0] = 'R'; + buf[i++] = 'R'; } else { - buf[0] = 'i'; + buf[i++] = 'i'; } if (ins_compl_active()) { - buf[1] = 'c'; + buf[i++] = 'c'; } else if (ctrl_x_mode_not_defined_yet()) { - buf[1] = 'x'; + buf[i++] = 'x'; } } - } else if (State & CMDLINE) { - buf[0] = 'c'; + } else if ((State & CMDLINE) || exmode_active) { + buf[i++] = 'c'; if (exmode_active) { - buf[1] = 'v'; + buf[i++] = 'v'; } } else if (State & TERM_FOCUS) { - buf[0] = 't'; + buf[i++] = 't'; } else { - buf[0] = 'n'; + buf[i++] = 'n'; if (finish_op) { - buf[1] = 'o'; + buf[i++] = 'o'; // to be able to detect force-linewise/blockwise/charwise operations - buf[2] = (char)motion_force; + buf[i++] = (char)motion_force; } else if (restart_edit == 'I' || restart_edit == 'R' || restart_edit == 'V') { - buf[1] = 'i'; - buf[2] = (char)restart_edit; + buf[i++] = 'i'; + buf[i++] = (char)restart_edit; } else if (curbuf->terminal) { - buf[1] = 't'; + buf[i++] = 't'; } } - return buf; + buf[i] = NUL; +} + +/// Fires a ModeChanged autocmd if appropriate. +void may_trigger_modechanged(void) +{ + if (!has_event(EVENT_MODECHANGED)) { + return; + } + + char curr_mode[MODE_MAX_LENGTH]; + char_u pattern_buf[2 * MODE_MAX_LENGTH]; + + get_mode(curr_mode); + if (STRCMP(curr_mode, last_mode) == 0) { + return; + } + + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + tv_dict_add_str(v_event, S_LEN("new_mode"), curr_mode); + tv_dict_add_str(v_event, S_LEN("old_mode"), last_mode); + tv_dict_set_keys_readonly(v_event); + + // concatenate modes in format "old_mode:new_mode" + vim_snprintf((char *)pattern_buf, sizeof(pattern_buf), "%s:%s", last_mode, curr_mode); + + apply_autocmds(EVENT_MODECHANGED, pattern_buf, NULL, false, curbuf); + STRCPY(last_mode, curr_mode); + + restore_v_event(v_event, &save_v_event); } diff --git a/src/nvim/strings.c b/src/nvim/strings.c index c58e052ae9..9d4c64e4b1 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -31,7 +31,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" @@ -355,7 +354,7 @@ char *strcase_save(const char *const orig, bool upper) int l = utf_ptr2len((const char_u *)p); if (c == 0) { // overlong sequence, use only the first byte - c = *p; + c = (char_u)(*p); l = 1; } int uc = upper ? mb_toupper(c) : mb_tolower(c); @@ -394,6 +393,18 @@ void del_trailing_spaces(char_u *ptr) } } +#if !defined(HAVE_STRNLEN) +size_t xstrnlen(const char *s, size_t n) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + const char *end = memchr(s, '\0', n); + if (end == NULL) { + return n; + } + return end - s; +} +#endif + #if (!defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP)) /* * Compare two strings, ignoring case, using current locale. @@ -990,22 +1001,20 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t - str_arg); } if (fmt_spec == 'S') { - if (min_field_width != 0) { - min_field_width += (strlen(str_arg) - - mb_string2cells((char_u *)str_arg)); - } - if (precision) { - char_u *p1; - size_t i = 0; - - for (p1 = (char_u *)str_arg; *p1; - p1 += utfc_ptr2len(p1)) { - i += (size_t)utf_ptr2cells(p1); - if (i > precision) { - break; - } + char_u *p1; + size_t i; + + for (i = 0, p1 = (char_u *)str_arg; *p1; p1 += utfc_ptr2len(p1)) { + size_t cell = (size_t)utf_ptr2cells(p1); + if (precision_specified && i + cell > precision) { + break; } - str_arg_l = (size_t)(p1 - (char_u *)str_arg); + i += cell; + } + + str_arg_l = (size_t)(p1 - (char_u *)str_arg); + if (min_field_width != 0) { + min_field_width += str_arg_l - i; } } break; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 13833c256e..d884ad704b 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -25,15 +25,17 @@ #include "nvim/garray.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/indent_c.h" #include "nvim/keymap.h" +#include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os_unix.h" @@ -50,56 +52,6 @@ static bool did_syntax_onoff = false; -/// Structure that stores information about a highlight group. -/// The ID of a highlight group is also called group ID. It is the index in -/// the highlight_ga array PLUS ONE. -typedef struct hl_group { - char_u *sg_name; ///< highlight group name - char *sg_name_u; ///< uppercase of sg_name - bool sg_cleared; ///< "hi clear" was used - int sg_attr; ///< Screen attr @see ATTR_ENTRY - int sg_link; ///< link to this highlight group ID - int sg_deflink; ///< default link; restored in highlight_clear() - int sg_set; ///< combination of flags in \ref SG_SET - sctx_T sg_deflink_sctx; ///< script where the default link was set - sctx_T sg_script_ctx; ///< script in which the group was last set - // for terminal UIs - int sg_cterm; ///< "cterm=" highlighting attr - ///< (combination of \ref HlAttrFlags) - int sg_cterm_fg; ///< terminal fg color number + 1 - int sg_cterm_bg; ///< terminal bg color number + 1 - bool sg_cterm_bold; ///< bold attr was set for light color - // for RGB UIs - int sg_gui; ///< "gui=" highlighting attributes - ///< (combination of \ref HlAttrFlags) - RgbValue sg_rgb_fg; ///< RGB foreground color - RgbValue sg_rgb_bg; ///< RGB background color - RgbValue sg_rgb_sp; ///< RGB special color - 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 -} HlGroup; - -/// \addtogroup SG_SET -/// @{ -#define SG_CTERM 2 // cterm has been set -#define SG_GUI 4 // gui has been set -#define SG_LINK 8 // link has been set -/// @} - -// builtin |highlight-groups| -static garray_T highlight_ga = GA_EMPTY_INIT_VALUE; -Map(cstr_t, int) highlight_unames = MAP_INIT; - -static inline struct hl_group *HL_TABLE(void) -{ - return ((struct hl_group *)((highlight_ga.ga_data))); -} - -#define MAX_HL_ID 20000 // maximum value for a highlight ID. - // different types of offsets that are possible #define SPO_MS_OFF 0 // match start offset #define SPO_ME_OFF 1 // match end offset @@ -110,20 +62,6 @@ static inline struct hl_group *HL_TABLE(void) #define SPO_LC_OFF 6 // leading context offset #define SPO_COUNT 7 -// Flags to indicate an additional string for highlight name completion. -static int include_none = 0; // when 1 include "nvim/None" -static int include_default = 0; // when 1 include "nvim/default" -static int include_link = 0; // when 2 include "nvim/link" and "clear" - -/// The "term", "cterm" and "gui" arguments can be any combination of the -/// following names, separated by commas (but no spaces!). -static char *(hl_name_table[]) = -{ "bold", "standout", "underline", "undercurl", - "italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" }; -static int hl_attr_table[] = -{ HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE, - HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 }; - static char e_illegal_arg[] = N_("E390: Illegal argument: %s"); // The patterns that are being searched for are stored in a syn_pattern. @@ -243,7 +181,7 @@ static char *(spo_name_tab[SPO_COUNT]) = #define SYN_ITEMS(buf) ((synpat_T *)((buf)->b_syn_patterns.ga_data)) -#define NONE_IDX -2 // value of sp_sync_idx for "NONE" +#define NONE_IDX (-2) // value of sp_sync_idx for "NONE" /* * Flags for b_syn_sync_flags: @@ -329,9 +267,9 @@ static int keepend_level = -1; static char msg_no_items[] = N_("No Syntax items defined for this buffer"); // value of si_idx for keywords -#define KEYWORD_IDX -1 +#define KEYWORD_IDX (-1) // valid of si_cont_list for containing all but contained groups -#define ID_LIST_ALL (int16_t *)-1 +#define ID_LIST_ALL ((int16_t *)-1) static int next_seqnr = 1; // value to use for si_seqnr @@ -2351,55 +2289,52 @@ static void check_state_ends(void) next_match_idx = 0; next_match_col = MAXCOL; break; - } else { - // handle next_list, unless at end of line and no "skipnl" or - // "skipempty" - current_next_list = cur_si->si_next_list; - current_next_flags = cur_si->si_flags; - if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)) - && syn_getcurline()[current_col] == NUL) { - current_next_list = NULL; - } + } - // When the ended item has "extend", another item with - // "keepend" now needs to check for its end. - had_extend = (cur_si->si_flags & HL_EXTEND); + // handle next_list, unless at end of line and no "skipnl" or + // "skipempty" + current_next_list = cur_si->si_next_list; + current_next_flags = cur_si->si_flags; + if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)) + && syn_getcurline()[current_col] == NUL) { + current_next_list = NULL; + } - pop_current_state(); + // When the ended item has "extend", another item with + // "keepend" now needs to check for its end. + had_extend = (cur_si->si_flags & HL_EXTEND); + pop_current_state(); + + if (GA_EMPTY(¤t_state)) { + break; + } + + if (had_extend && keepend_level >= 0) { + syn_update_ends(false); if (GA_EMPTY(¤t_state)) { break; } + } - if (had_extend && keepend_level >= 0) { - syn_update_ends(false); - if (GA_EMPTY(¤t_state)) { - break; - } - } - - cur_si = &CUR_STATE(current_state.ga_len - 1); + cur_si = &CUR_STATE(current_state.ga_len - 1); - /* - * Only for a region the search for the end continues after - * the end of the contained item. If the contained match - * included the end-of-line, break here, the region continues. - * Don't do this when: - * - "keepend" is used for the contained item - * - not at the end of the line (could be end="x$"me=e-1). - * - "excludenl" is used (HL_HAS_EOL won't be set) - */ - if (cur_si->si_idx >= 0 - && SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type - == SPTYPE_START - && !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND))) { - update_si_end(cur_si, (int)current_col, true); - check_keepend(); - if ((current_next_flags & HL_HAS_EOL) - && keepend_level < 0 - && syn_getcurline()[current_col] == NUL) { - break; - } + // Only for a region the search for the end continues after + // the end of the contained item. If the contained match + // included the end-of-line, break here, the region continues. + // Don't do this when: + // - "keepend" is used for the contained item + // - not at the end of the line (could be end="x$"me=e-1). + // - "excludenl" is used (HL_HAS_EOL won't be set) + if (cur_si->si_idx >= 0 + && SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type == SPTYPE_START + && !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND))) { + update_si_end(cur_si, (int)current_col, true); + check_keepend(); + if ((current_next_flags & HL_HAS_EOL) + && keepend_level < 0 + && syn_getcurline()[current_col] == NUL) { + break; } } } else { @@ -3110,9 +3045,9 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_conceal) { - msg(_("syntax conceal on")); + msg("syntax conceal on"); } else { - msg(_("syntax conceal off")); + msg("syntax conceal off"); } } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) { curwin->w_s->b_syn_conceal = true; @@ -3139,9 +3074,9 @@ static void syn_cmd_case(exarg_T *eap, int syncing) next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_ic) { - msg(_("syntax case ignore")); + msg("syntax case ignore"); } else { - msg(_("syntax case match")); + msg("syntax case match"); } } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) { curwin->w_s->b_syn_ic = false; @@ -3166,9 +3101,9 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing) if (*arg == NUL) { switch (curwin->w_s->b_syn_foldlevel) { case SYNFLD_START: - msg(_("syntax foldlevel start")); break; + msg("syntax foldlevel start"); break; case SYNFLD_MINIMUM: - msg(_("syntax foldlevel minimum")); break; + msg("syntax foldlevel minimum"); break; default: break; } @@ -3207,11 +3142,11 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_spell == SYNSPL_TOP) { - msg(_("syntax spell toplevel")); + msg("syntax spell toplevel"); } else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) { - msg(_("syntax spell notoplevel")); + msg("syntax spell notoplevel"); } else { - msg(_("syntax spell default")); + msg("syntax spell default"); } } else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { curwin->w_s->b_syn_spell = SYNSPL_TOP; @@ -3243,7 +3178,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) if (*arg == NUL) { msg_puts("\n"); if (curwin->w_s->b_syn_isk != empty_option) { - msg_puts(_("syntax iskeyword ")); + msg_puts("syntax iskeyword "); msg_outtrans(curwin->w_s->b_syn_isk); } else { msg_outtrans((char_u *)_("syntax iskeyword not set")); @@ -3606,7 +3541,7 @@ static void syn_cmd_list(exarg_T *eap, int syncing) /* * No argument: List all group IDs and all syntax clusters. */ - for (int id = 1; id <= highlight_ga.ga_len && !got_int; id++) { + for (int id = 1; id <= highlight_num_groups() && !got_int; id++) { syn_list_one(id, syncing, false); } for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id) { @@ -3764,8 +3699,8 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) } msg_putchar(' '); if (spp->sp_sync_idx >= 0) { - msg_outtrans(HL_TABLE()[SYN_ITEMS(curwin->w_s) - [spp->sp_sync_idx].sp_syn.id - 1].sg_name); + msg_outtrans(highlight_group_name(SYN_ITEMS(curwin->w_s) + [spp->sp_sync_idx].sp_syn.id - 1)); } else { msg_puts("NONE"); } @@ -3774,11 +3709,11 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) } // list the link, if there is one - if (HL_TABLE()[id - 1].sg_link && (did_header || link_only) && !got_int) { + if (highlight_link_id(id - 1) && (did_header || link_only) && !got_int) { (void)syn_list_header(did_header, 0, id, true); msg_puts_attr("links to", attr); msg_putchar(' '); - msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); + msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1)); } } @@ -3842,7 +3777,7 @@ static void put_id_list(const char *const name, const int16_t *const list, const msg_putchar('@'); msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name); } else { - msg_outtrans(HL_TABLE()[*p - 1].sg_name); + msg_outtrans(highlight_group_name(*p - 1)); } if (p[1]) { msg_putchar(','); @@ -3864,7 +3799,7 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const if (last_matchgroup == 0) { msg_outtrans((char_u *)"NONE"); } else { - msg_outtrans(HL_TABLE()[last_matchgroup - 1].sg_name); + msg_outtrans(highlight_group_name(last_matchgroup - 1)); } msg_putchar(' '); } @@ -4205,7 +4140,7 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha p = flagtab[fidx].name; int i; for (i = 0, len = 0; p[i] != NUL; i += 2, ++len) { - if (arg[len] != p[i] && arg[len] != p[i + 1]) { + if (arg[len] != (char_u)p[i] && arg[len] != (char_u)p[i + 1]) { break; } } @@ -5401,7 +5336,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis do { for (end = p; *end && !ascii_iswhite(*end) && *end != ','; end++) { } - char_u *const name = xmalloc((int)(end - p + 3)); // leave room for "^$" + char_u *const name = xmalloc(end - p + 3); // leave room for "^$" STRLCPY(name + 1, p, end - p + 1); if (STRCMP(name + 1, "ALLBUT") == 0 || STRCMP(name + 1, "ALL") == 0 @@ -5456,8 +5391,8 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis regmatch.rm_ic = TRUE; id = 0; - for (int i = highlight_ga.ga_len; --i >= 0;) { - if (vim_regexec(®match, HL_TABLE()[i].sg_name, (colnr_T)0)) { + for (int i = highlight_num_groups(); --i >= 0;) { + if (vim_regexec(®match, highlight_group_name(i), (colnr_T)0)) { if (round == 2) { // Got more items than expected; can happen // when adding items that match: @@ -6118,7 +6053,7 @@ static void syntime_report(void) msg_puts(profile_msg(p->average)); msg_puts(" "); msg_advance(50); - msg_outtrans(HL_TABLE()[p->id - 1].sg_name); + msg_outtrans(highlight_group_name(p->id - 1)); msg_puts(" "); msg_advance(69); @@ -6143,2617 +6078,3 @@ static void syntime_report(void) msg_puts("\n"); } } - -/************************************** -* Highlighting stuff * -**************************************/ - -// The default highlight groups. These are compiled-in for fast startup and -// they still work when the runtime files can't be found. -// -// When making changes here, also change runtime/colors/default.vim! - -static const char *highlight_init_both[] = { - "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", - "Cursor guibg=fg guifg=bg", - "lCursor guibg=fg guifg=bg", - "DiffText cterm=bold ctermbg=Red gui=bold guibg=Red", - "ErrorMsg ctermbg=DarkRed ctermfg=White guibg=Red guifg=White", - "IncSearch cterm=reverse gui=reverse", - "ModeMsg cterm=bold gui=bold", - "NonText ctermfg=Blue gui=bold guifg=Blue", - "Normal cterm=NONE gui=NONE", - "PmenuSbar ctermbg=Grey guibg=Grey", - "StatusLine cterm=reverse,bold gui=reverse,bold", - "StatusLineNC cterm=reverse gui=reverse", - "TabLineFill cterm=reverse gui=reverse", - "TabLineSel cterm=bold gui=bold", - "TermCursor cterm=reverse gui=reverse", - "VertSplit cterm=reverse gui=reverse", - "WildMenu ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black", - "default link EndOfBuffer NonText", - "default link LineNrAbove LineNr", - "default link LineNrBelow LineNr", - "default link QuickFixLine Search", - "default link Substitute Search", - "default link Whitespace NonText", - "default link MsgSeparator StatusLine", - "default link NormalFloat Pmenu", - "default link FloatBorder VertSplit", - "default FloatShadow blend=80 guibg=Black", - "default FloatShadowThrough blend=100 guibg=Black", - "RedrawDebugNormal cterm=reverse gui=reverse", - "RedrawDebugClear ctermbg=Yellow guibg=Yellow", - "RedrawDebugComposed ctermbg=Green guibg=Green", - "RedrawDebugRecompose ctermbg=Red guibg=Red", - "Error term=reverse cterm=NONE ctermfg=White ctermbg=Red gui=NONE guifg=White guibg=Red", - "Todo term=standout cterm=NONE ctermfg=Black ctermbg=Yellow gui=NONE guifg=Blue guibg=Yellow", - "default link String Constant", - "default link Character Constant", - "default link Number Constant", - "default link Boolean Constant", - "default link Float Number", - "default link Function Identifier", - "default link Conditional Statement", - "default link Repeat Statement", - "default link Label Statement", - "default link Operator Statement", - "default link Keyword Statement", - "default link Exception Statement", - "default link Include PreProc", - "default link Define PreProc", - "default link Macro PreProc", - "default link PreCondit PreProc", - "default link StorageClass Type", - "default link Structure Type", - "default link Typedef Type", - "default link Tag Special", - "default link SpecialChar Special", - "default link Delimiter Special", - "default link SpecialComment Special", - "default link Debug Special", - "default DiagnosticError ctermfg=1 guifg=Red", - "default DiagnosticWarn ctermfg=3 guifg=Orange", - "default DiagnosticInfo ctermfg=4 guifg=LightBlue", - "default DiagnosticHint ctermfg=7 guifg=LightGrey", - "default DiagnosticUnderlineError cterm=underline gui=underline guisp=Red", - "default DiagnosticUnderlineWarn cterm=underline gui=underline guisp=Orange", - "default DiagnosticUnderlineInfo cterm=underline gui=underline guisp=LightBlue", - "default DiagnosticUnderlineHint cterm=underline gui=underline guisp=LightGrey", - "default link DiagnosticVirtualTextError DiagnosticError", - "default link DiagnosticVirtualTextWarn DiagnosticWarn", - "default link DiagnosticVirtualTextInfo DiagnosticInfo", - "default link DiagnosticVirtualTextHint DiagnosticHint", - "default link DiagnosticFloatingError DiagnosticError", - "default link DiagnosticFloatingWarn DiagnosticWarn", - "default link DiagnosticFloatingInfo DiagnosticInfo", - "default link DiagnosticFloatingHint DiagnosticHint", - "default link DiagnosticSignError DiagnosticError", - "default link DiagnosticSignWarn DiagnosticWarn", - "default link DiagnosticSignInfo DiagnosticInfo", - "default link DiagnosticSignHint DiagnosticHint", - NULL -}; - -// Default colors only used with a light background. -static const char *highlight_init_light[] = { - "ColorColumn ctermbg=LightRed guibg=LightRed", - "CursorColumn ctermbg=LightGrey guibg=Grey90", - "CursorLine cterm=underline guibg=Grey90", - "CursorLineNr cterm=underline ctermfg=Brown gui=bold guifg=Brown", - "DiffAdd ctermbg=LightBlue guibg=LightBlue", - "DiffChange ctermbg=LightMagenta guibg=LightMagenta", - "DiffDelete ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan", - "Directory ctermfg=DarkBlue guifg=Blue", - "FoldColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue", - "Folded ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue", - "LineNr ctermfg=Brown guifg=Brown", - "MatchParen ctermbg=Cyan guibg=Cyan", - "MoreMsg ctermfg=DarkGreen gui=bold guifg=SeaGreen", - "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta", - "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey", - "PmenuThumb ctermbg=Black guibg=Black", - "Question ctermfg=DarkGreen gui=bold guifg=SeaGreen", - "Search ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE", - "SignColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue", - "SpecialKey ctermfg=DarkBlue guifg=Blue", - "SpellBad ctermbg=LightRed guisp=Red gui=undercurl", - "SpellCap ctermbg=LightBlue guisp=Blue gui=undercurl", - "SpellLocal ctermbg=Cyan guisp=DarkCyan gui=undercurl", - "SpellRare ctermbg=LightMagenta guisp=Magenta gui=undercurl", - "TabLine cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey", - "Title ctermfg=DarkMagenta gui=bold guifg=Magenta", - "Visual guibg=LightGrey", - "WarningMsg ctermfg=DarkRed guifg=Red", - "Comment term=bold cterm=NONE ctermfg=DarkBlue ctermbg=NONE gui=NONE guifg=Blue guibg=NONE", - "Constant term=underline cterm=NONE ctermfg=DarkRed ctermbg=NONE gui=NONE guifg=Magenta guibg=NONE", - "Special term=bold cterm=NONE ctermfg=DarkMagenta ctermbg=NONE gui=NONE guifg=#6a5acd guibg=NONE", - "Identifier term=underline cterm=NONE ctermfg=DarkCyan ctermbg=NONE gui=NONE guifg=DarkCyan guibg=NONE", - "Statement term=bold cterm=NONE ctermfg=Brown ctermbg=NONE gui=bold guifg=Brown guibg=NONE", - "PreProc term=underline cterm=NONE ctermfg=DarkMagenta ctermbg=NONE gui=NONE guifg=#6a0dad guibg=NONE", - "Type term=underline cterm=NONE ctermfg=DarkGreen ctermbg=NONE gui=bold guifg=SeaGreen guibg=NONE", - "Underlined term=underline cterm=underline ctermfg=DarkMagenta gui=underline guifg=SlateBlue", - "Ignore term=NONE cterm=NONE ctermfg=white ctermbg=NONE gui=NONE guifg=bg guibg=NONE", - NULL -}; - -// Default colors only used with a dark background. -static const char *highlight_init_dark[] = { - "ColorColumn ctermbg=DarkRed guibg=DarkRed", - "CursorColumn ctermbg=DarkGrey guibg=Grey40", - "CursorLine cterm=underline guibg=Grey40", - "CursorLineNr cterm=underline ctermfg=Yellow gui=bold guifg=Yellow", - "DiffAdd ctermbg=DarkBlue guibg=DarkBlue", - "DiffChange ctermbg=DarkMagenta guibg=DarkMagenta", - "DiffDelete ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan", - "Directory ctermfg=LightCyan guifg=Cyan", - "FoldColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan", - "Folded ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan", - "LineNr ctermfg=Yellow guifg=Yellow", - "MatchParen ctermbg=DarkCyan guibg=DarkCyan", - "MoreMsg ctermfg=LightGreen gui=bold guifg=SeaGreen", - "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta", - "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey", - "PmenuThumb ctermbg=White guibg=White", - "Question ctermfg=LightGreen gui=bold guifg=Green", - "Search ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black", - "SignColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan", - "SpecialKey ctermfg=LightBlue guifg=Cyan", - "SpellBad ctermbg=Red guisp=Red gui=undercurl", - "SpellCap ctermbg=Blue guisp=Blue gui=undercurl", - "SpellLocal ctermbg=Cyan guisp=Cyan gui=undercurl", - "SpellRare ctermbg=Magenta guisp=Magenta gui=undercurl", - "TabLine cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey", - "Title ctermfg=LightMagenta gui=bold guifg=Magenta", - "Visual guibg=DarkGrey", - "WarningMsg ctermfg=LightRed guifg=Red", - "Comment term=bold cterm=NONE ctermfg=Cyan ctermbg=NONE gui=NONE guifg=#80a0ff guibg=NONE", - "Constant term=underline cterm=NONE ctermfg=Magenta ctermbg=NONE gui=NONE guifg=#ffa0a0 guibg=NONE", - "Special term=bold cterm=NONE ctermfg=LightRed ctermbg=NONE gui=NONE guifg=Orange guibg=NONE", - "Identifier term=underline cterm=bold ctermfg=Cyan ctermbg=NONE gui=NONE guifg=#40ffff guibg=NONE", - "Statement term=bold cterm=NONE ctermfg=Yellow ctermbg=NONE gui=bold guifg=#ffff60 guibg=NONE", - "PreProc term=underline cterm=NONE ctermfg=LightBlue ctermbg=NONE gui=NONE guifg=#ff80ff guibg=NONE", - "Type term=underline cterm=NONE ctermfg=LightGreen ctermbg=NONE gui=bold guifg=#60ff60 guibg=NONE", - "Underlined term=underline cterm=underline ctermfg=LightBlue gui=underline guifg=#80a0ff", - "Ignore term=NONE cterm=NONE ctermfg=black ctermbg=NONE gui=NONE guifg=bg guibg=NONE", - NULL -}; - -const char *const highlight_init_cmdline[] = { - // XXX When modifying a list modify it in both valid and invalid halves. - // TODO(ZyX-I): merge valid and invalid groups via a macros. - - // NvimInternalError should appear only when highlighter has a bug. - "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", - - // Highlight groups (links) used by parser: - - "default link NvimAssignment Operator", - "default link NvimPlainAssignment NvimAssignment", - "default link NvimAugmentedAssignment NvimAssignment", - "default link NvimAssignmentWithAddition NvimAugmentedAssignment", - "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment", - "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment", - - "default link NvimOperator Operator", - - "default link NvimUnaryOperator NvimOperator", - "default link NvimUnaryPlus NvimUnaryOperator", - "default link NvimUnaryMinus NvimUnaryOperator", - "default link NvimNot NvimUnaryOperator", - - "default link NvimBinaryOperator NvimOperator", - "default link NvimComparison NvimBinaryOperator", - "default link NvimComparisonModifier NvimComparison", - "default link NvimBinaryPlus NvimBinaryOperator", - "default link NvimBinaryMinus NvimBinaryOperator", - "default link NvimConcat NvimBinaryOperator", - "default link NvimConcatOrSubscript NvimConcat", - "default link NvimOr NvimBinaryOperator", - "default link NvimAnd NvimBinaryOperator", - "default link NvimMultiplication NvimBinaryOperator", - "default link NvimDivision NvimBinaryOperator", - "default link NvimMod NvimBinaryOperator", - - "default link NvimTernary NvimOperator", - "default link NvimTernaryColon NvimTernary", - - "default link NvimParenthesis Delimiter", - "default link NvimLambda NvimParenthesis", - "default link NvimNestingParenthesis NvimParenthesis", - "default link NvimCallingParenthesis NvimParenthesis", - - "default link NvimSubscript NvimParenthesis", - "default link NvimSubscriptBracket NvimSubscript", - "default link NvimSubscriptColon NvimSubscript", - "default link NvimCurly NvimSubscript", - - "default link NvimContainer NvimParenthesis", - "default link NvimDict NvimContainer", - "default link NvimList NvimContainer", - - "default link NvimIdentifier Identifier", - "default link NvimIdentifierScope NvimIdentifier", - "default link NvimIdentifierScopeDelimiter NvimIdentifier", - "default link NvimIdentifierName NvimIdentifier", - "default link NvimIdentifierKey NvimIdentifier", - - "default link NvimColon Delimiter", - "default link NvimComma Delimiter", - "default link NvimArrow Delimiter", - - "default link NvimRegister SpecialChar", - "default link NvimNumber Number", - "default link NvimFloat NvimNumber", - "default link NvimNumberPrefix Type", - - "default link NvimOptionSigil Type", - "default link NvimOptionName NvimIdentifier", - "default link NvimOptionScope NvimIdentifierScope", - "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter", - - "default link NvimEnvironmentSigil NvimOptionSigil", - "default link NvimEnvironmentName NvimIdentifier", - - "default link NvimString String", - "default link NvimStringBody NvimString", - "default link NvimStringQuote NvimString", - "default link NvimStringSpecial SpecialChar", - - "default link NvimSingleQuote NvimStringQuote", - "default link NvimSingleQuotedBody NvimStringBody", - "default link NvimSingleQuotedQuote NvimStringSpecial", - - "default link NvimDoubleQuote NvimStringQuote", - "default link NvimDoubleQuotedBody NvimStringBody", - "default link NvimDoubleQuotedEscape NvimStringSpecial", - - "default link NvimFigureBrace NvimInternalError", - "default link NvimSingleQuotedUnknownEscape NvimInternalError", - - "default link NvimSpacing Normal", - - // NvimInvalid groups: - - "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError", - - "default link NvimInvalid Error", - - "default link NvimInvalidAssignment NvimInvalid", - "default link NvimInvalidPlainAssignment NvimInvalidAssignment", - "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment", - "default link NvimInvalidAssignmentWithAddition NvimInvalidAugmentedAssignment", - "default link NvimInvalidAssignmentWithSubtraction NvimInvalidAugmentedAssignment", - "default link NvimInvalidAssignmentWithConcatenation NvimInvalidAugmentedAssignment", - - "default link NvimInvalidOperator NvimInvalid", - - "default link NvimInvalidUnaryOperator NvimInvalidOperator", - "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator", - "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator", - "default link NvimInvalidNot NvimInvalidUnaryOperator", - - "default link NvimInvalidBinaryOperator NvimInvalidOperator", - "default link NvimInvalidComparison NvimInvalidBinaryOperator", - "default link NvimInvalidComparisonModifier NvimInvalidComparison", - "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator", - "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator", - "default link NvimInvalidConcat NvimInvalidBinaryOperator", - "default link NvimInvalidConcatOrSubscript NvimInvalidConcat", - "default link NvimInvalidOr NvimInvalidBinaryOperator", - "default link NvimInvalidAnd NvimInvalidBinaryOperator", - "default link NvimInvalidMultiplication NvimInvalidBinaryOperator", - "default link NvimInvalidDivision NvimInvalidBinaryOperator", - "default link NvimInvalidMod NvimInvalidBinaryOperator", - - "default link NvimInvalidTernary NvimInvalidOperator", - "default link NvimInvalidTernaryColon NvimInvalidTernary", - - "default link NvimInvalidDelimiter NvimInvalid", - - "default link NvimInvalidParenthesis NvimInvalidDelimiter", - "default link NvimInvalidLambda NvimInvalidParenthesis", - "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis", - "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis", - - "default link NvimInvalidSubscript NvimInvalidParenthesis", - "default link NvimInvalidSubscriptBracket NvimInvalidSubscript", - "default link NvimInvalidSubscriptColon NvimInvalidSubscript", - "default link NvimInvalidCurly NvimInvalidSubscript", - - "default link NvimInvalidContainer NvimInvalidParenthesis", - "default link NvimInvalidDict NvimInvalidContainer", - "default link NvimInvalidList NvimInvalidContainer", - - "default link NvimInvalidValue NvimInvalid", - - "default link NvimInvalidIdentifier NvimInvalidValue", - "default link NvimInvalidIdentifierScope NvimInvalidIdentifier", - "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier", - "default link NvimInvalidIdentifierName NvimInvalidIdentifier", - "default link NvimInvalidIdentifierKey NvimInvalidIdentifier", - - "default link NvimInvalidColon NvimInvalidDelimiter", - "default link NvimInvalidComma NvimInvalidDelimiter", - "default link NvimInvalidArrow NvimInvalidDelimiter", - - "default link NvimInvalidRegister NvimInvalidValue", - "default link NvimInvalidNumber NvimInvalidValue", - "default link NvimInvalidFloat NvimInvalidNumber", - "default link NvimInvalidNumberPrefix NvimInvalidNumber", - - "default link NvimInvalidOptionSigil NvimInvalidIdentifier", - "default link NvimInvalidOptionName NvimInvalidIdentifier", - "default link NvimInvalidOptionScope NvimInvalidIdentifierScope", - "default link NvimInvalidOptionScopeDelimiter " - "NvimInvalidIdentifierScopeDelimiter", - - "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil", - "default link NvimInvalidEnvironmentName NvimInvalidIdentifier", - - // Invalid string bodies and specials are still highlighted as valid ones to - // minimize the red area. - "default link NvimInvalidString NvimInvalidValue", - "default link NvimInvalidStringBody NvimStringBody", - "default link NvimInvalidStringQuote NvimInvalidString", - "default link NvimInvalidStringSpecial NvimStringSpecial", - - "default link NvimInvalidSingleQuote NvimInvalidStringQuote", - "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody", - "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial", - - "default link NvimInvalidDoubleQuote NvimInvalidStringQuote", - "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody", - "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial", - "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue", - - "default link NvimInvalidFigureBrace NvimInvalidDelimiter", - - "default link NvimInvalidSpacing ErrorMsg", - - // Not actually invalid, but we highlight user that he is doing something - // wrong. - "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue", - NULL, -}; - -/// Create default links for Nvim* highlight groups used for cmdline coloring -void syn_init_cmdline_highlight(bool reset, bool init) -{ - for (size_t i = 0; highlight_init_cmdline[i] != NULL; i++) { - do_highlight(highlight_init_cmdline[i], reset, init); - } -} - -/// Load colors from a file if "g:colors_name" is set, otherwise load builtin -/// colors -/// -/// @param both include groups where 'bg' doesn't matter -/// @param reset clear groups first -void init_highlight(bool both, bool reset) -{ - static int had_both = false; - - // Try finding the color scheme file. Used when a color file was loaded - // and 'background' or 't_Co' is changed. - char_u *p = get_var_value("g:colors_name"); - if (p != NULL) { - // Value of g:colors_name could be freed in load_colors() and make - // p invalid, so copy it. - char_u *copy_p = vim_strsave(p); - bool okay = load_colors(copy_p); - xfree(copy_p); - if (okay) { - return; - } - } - - /* - * Didn't use a color file, use the compiled-in colors. - */ - if (both) { - had_both = true; - const char *const *const pp = highlight_init_both; - for (size_t i = 0; pp[i] != NULL; i++) { - do_highlight(pp[i], reset, true); - } - } else if (!had_both) { - // Don't do anything before the call with both == true from main(). - // Not everything has been setup then, and that call will overrule - // everything anyway. - return; - } - - const char *const *const pp = ((*p_bg == 'l') - ? highlight_init_light - : highlight_init_dark); - for (size_t i = 0; pp[i] != NULL; i++) { - do_highlight(pp[i], reset, true); - } - - /* Reverse looks ugly, but grey may not work for 8 colors. Thus let it - * depend on the number of colors available. - * With 8 colors brown is equal to yellow, need to use black for Search fg - * to avoid Statement highlighted text disappears. - * Clear the attributes, needed when changing the t_Co value. */ - if (t_colors > 8) { - do_highlight((*p_bg == 'l' - ? "Visual cterm=NONE ctermbg=LightGrey" - : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); - } else { - do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); - if (*p_bg == 'l') { - do_highlight("Search ctermfg=black", false, true); - } - } - - syn_init_cmdline_highlight(false, false); -} - -/* - * Load color file "name". - * Return OK for success, FAIL for failure. - */ -int load_colors(char_u *name) -{ - char_u *buf; - int retval = FAIL; - static bool recursive = false; - - // When being called recursively, this is probably because setting - // 'background' caused the highlighting to be reloaded. This means it is - // working, thus we should return OK. - if (recursive) { - return OK; - } - - recursive = true; - size_t buflen = STRLEN(name) + 12; - buf = xmalloc(buflen); - apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf); - snprintf((char *)buf, buflen, "colors/%s.vim", name); - retval = source_runtime((char *)buf, DIP_START + DIP_OPT); - if (retval == FAIL) { - snprintf((char *)buf, buflen, "colors/%s.lua", name); - retval = source_runtime((char *)buf, DIP_START + DIP_OPT); - } - xfree(buf); - apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, false, curbuf); - - recursive = false; - - return retval; -} - -static char *(color_names[28]) = { - "Black", "DarkBlue", "DarkGreen", "DarkCyan", - "DarkRed", "DarkMagenta", "Brown", "DarkYellow", - "Gray", "Grey", "LightGray", "LightGrey", - "DarkGray", "DarkGrey", - "Blue", "LightBlue", "Green", "LightGreen", - "Cyan", "LightCyan", "Red", "LightRed", "Magenta", - "LightMagenta", "Yellow", "LightYellow", "White", "NONE" -}; -// indices: -// 0, 1, 2, 3, -// 4, 5, 6, 7, -// 8, 9, 10, 11, -// 12, 13, -// 14, 15, 16, 17, -// 18, 19, 20, 21, 22, -// 23, 24, 25, 26, 27 -static int color_numbers_16[28] = { 0, 1, 2, 3, - 4, 5, 6, 6, - 7, 7, 7, 7, - 8, 8, - 9, 9, 10, 10, - 11, 11, 12, 12, 13, - 13, 14, 14, 15, -1 }; -// for xterm with 88 colors... -static int color_numbers_88[28] = { 0, 4, 2, 6, - 1, 5, 32, 72, - 84, 84, 7, 7, - 82, 82, - 12, 43, 10, 61, - 14, 63, 9, 74, 13, - 75, 11, 78, 15, -1 }; -// for xterm with 256 colors... -static int color_numbers_256[28] = { 0, 4, 2, 6, - 1, 5, 130, 3, - 248, 248, 7, 7, - 242, 242, - 12, 81, 10, 121, - 14, 159, 9, 224, 13, - 225, 11, 229, 15, -1 }; -// for terminals with less than 16 colors... -static int color_numbers_8[28] = { 0, 4, 2, 6, - 1, 5, 3, 3, - 7, 7, 7, 7, - 0+8, 0+8, - 4+8, 4+8, 2+8, 2+8, - 6+8, 6+8, 1+8, 1+8, 5+8, - 5+8, 3+8, 3+8, 7+8, -1 }; - -// Lookup the "cterm" value to be used for color with index "idx" in -// color_names[]. -// "boldp" will be set to TRUE or FALSE for a foreground color when using 8 -// colors, otherwise it will be unchanged. -int lookup_color(const int idx, const bool foreground, TriState *const boldp) -{ - int color = color_numbers_16[idx]; - - // Use the _16 table to check if it's a valid color name. - if (color < 0) { - return -1; - } - - if (t_colors == 8) { - // t_Co is 8: use the 8 colors table - color = color_numbers_8[idx]; - if (foreground) { - // set/reset bold attribute to get light foreground - // colors (on some terminals, e.g. "linux") - if (color & 8) { - *boldp = kTrue; - } else { - *boldp = kFalse; - } - } - color &= 7; // truncate to 8 colors - } else if (t_colors == 16) { - color = color_numbers_8[idx]; - } else if (t_colors == 88) { - color = color_numbers_88[idx]; - } else if (t_colors >= 256) { - color = color_numbers_256[idx]; - } - return color; -} - - -/// Handle ":highlight" command -/// -/// When using ":highlight clear" this is called recursively for each group with -/// forceit and init being both true. -/// -/// @param[in] line Command arguments. -/// @param[in] forceit True when bang is given, allows to link group even if -/// it has its own settings. -/// @param[in] init True when initializing. -void do_highlight(const char *line, const bool forceit, const bool init) - FUNC_ATTR_NONNULL_ALL -{ - const char *name_end; - const char *linep; - const char *key_start; - const char *arg_start; - long i; - int off; - int len; - int attr; - int id; - int idx; - struct hl_group item_before; - bool did_change = false; - bool dodefault = false; - bool doclear = false; - bool dolink = false; - bool error = false; - int color; - bool is_normal_group = false; // "Normal" group - bool did_highlight_changed = false; - - // If no argument, list current highlighting. - if (ends_excmd((uint8_t)(*line))) { - for (i = 1; i <= highlight_ga.ga_len && !got_int; i++) { - // TODO(brammool): only call when the group has attributes set - highlight_list_one(i); - } - return; - } - - // Isolate the name. - name_end = (const char *)skiptowhite((const char_u *)line); - linep = (const char *)skipwhite((const char_u *)name_end); - - // Check for "default" argument. - if (strncmp(line, "default", name_end - line) == 0) { - dodefault = true; - line = linep; - name_end = (const char *)skiptowhite((const char_u *)line); - linep = (const char *)skipwhite((const char_u *)name_end); - } - - // Check for "clear" or "link" argument. - if (strncmp(line, "clear", name_end - line) == 0) { - doclear = true; - } else if (strncmp(line, "link", name_end - line) == 0) { - dolink = true; - } - - // ":highlight {group-name}": list highlighting for one group. - if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { - id = syn_name2id_len((const char_u *)line, (int)(name_end - line)); - if (id == 0) { - semsg(_("E411: highlight group not found: %s"), line); - } else { - highlight_list_one(id); - } - return; - } - - // Handle ":highlight link {from} {to}" command. - if (dolink) { - const char *from_start = linep; - const char *from_end; - const char *to_start; - const char *to_end; - int from_id; - int to_id; - struct hl_group *hlgroup = NULL; - - from_end = (const char *)skiptowhite((const char_u *)from_start); - to_start = (const char *)skipwhite((const char_u *)from_end); - to_end = (const char *)skiptowhite((const char_u *)to_start); - - if (ends_excmd((uint8_t)(*from_start)) - || ends_excmd((uint8_t)(*to_start))) { - semsg(_("E412: Not enough arguments: \":highlight link %s\""), - from_start); - return; - } - - if (!ends_excmd(*skipwhite((const char_u *)to_end))) { - semsg(_("E413: Too many arguments: \":highlight link %s\""), from_start); - return; - } - - from_id = syn_check_group(from_start, (int)(from_end - from_start)); - if (strncmp(to_start, "NONE", 4) == 0) { - to_id = 0; - } else { - to_id = syn_check_group(to_start, (int)(to_end - to_start)); - } - - if (from_id > 0) { - hlgroup = &HL_TABLE()[from_id - 1]; - if (dodefault && (forceit || hlgroup->sg_deflink == 0)) { - hlgroup->sg_deflink = to_id; - hlgroup->sg_deflink_sctx = current_sctx; - hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; - } - } - - if (from_id > 0 && (!init || hlgroup->sg_set == 0)) { - // Don't allow a link when there already is some highlighting - // for the group, unless '!' is used - if (to_id > 0 && !forceit && !init - && hl_has_settings(from_id - 1, dodefault)) { - if (sourcing_name == NULL && !dodefault) { - emsg(_("E414: group has settings, highlight link ignored")); - } - } else if (hlgroup->sg_link != to_id - || hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid - || hlgroup->sg_cleared) { - if (!init) { - hlgroup->sg_set |= SG_LINK; - } - hlgroup->sg_link = to_id; - hlgroup->sg_script_ctx = current_sctx; - hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; - hlgroup->sg_cleared = false; - redraw_all_later(SOME_VALID); - - // Only call highlight changed() once after multiple changes - need_highlight_changed = true; - } - } - - return; - } - - if (doclear) { - // ":highlight clear [group]" command. - line = linep; - if (ends_excmd((uint8_t)(*line))) { - do_unlet(S_LEN("colors_name"), true); - restore_cterm_colors(); - - // Clear all default highlight groups and load the defaults. - for (int j = 0; j < highlight_ga.ga_len; j++) { - highlight_clear(j); - } - init_highlight(true, true); - highlight_changed(); - redraw_all_later(NOT_VALID); - return; - } - name_end = (const char *)skiptowhite((const char_u *)line); - linep = (const char *)skipwhite((const char_u *)name_end); - } - - // Find the group name in the table. If it does not exist yet, add it. - id = syn_check_group(line, (int)(name_end - line)); - if (id == 0) { // Failed (out of memory). - return; - } - idx = id - 1; // Index is ID minus one. - - // Return if "default" was used and the group already has settings - if (dodefault && hl_has_settings(idx, true)) { - return; - } - - // Make a copy so we can check if any attribute actually changed - item_before = HL_TABLE()[idx]; - is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0); - - // Clear the highlighting for ":hi clear {group}" and ":hi clear". - if (doclear || (forceit && init)) { - highlight_clear(idx); - if (!doclear) { - HL_TABLE()[idx].sg_set = 0; - } - } - - char *key = NULL; - char *arg = NULL; - if (!doclear) { - while (!ends_excmd((uint8_t)(*linep))) { - key_start = linep; - if (*linep == '=') { - semsg(_("E415: unexpected equal sign: %s"), key_start); - error = true; - break; - } - - // Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg", - // "guibg" or "guisp"). - while (*linep && !ascii_iswhite(*linep) && *linep != '=') { - linep++; - } - xfree(key); - key = (char *)vim_strnsave_up((const char_u *)key_start, - linep - key_start); - linep = (const char *)skipwhite((const char_u *)linep); - - if (strcmp(key, "NONE") == 0) { - if (!init || HL_TABLE()[idx].sg_set == 0) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI; - } - highlight_clear(idx); - } - continue; - } - - // Check for the equal sign. - if (*linep != '=') { - semsg(_("E416: missing equal sign: %s"), key_start); - error = true; - break; - } - linep++; - - // Isolate the argument. - linep = (const char *)skipwhite((const char_u *)linep); - if (*linep == '\'') { // guifg='color name' - arg_start = ++linep; - linep = strchr(linep, '\''); - if (linep == NULL) { - semsg(_(e_invarg2), key_start); - error = true; - break; - } - } else { - arg_start = linep; - linep = (const char *)skiptowhite((const char_u *)linep); - } - if (linep == arg_start) { - semsg(_("E417: missing argument: %s"), key_start); - error = true; - break; - } - xfree(arg); - arg = xstrndup(arg_start, (size_t)(linep - arg_start)); - - if (*linep == '\'') { - linep++; - } - - // Store the argument. - if (strcmp(key, "TERM") == 0 - || strcmp(key, "CTERM") == 0 - || strcmp(key, "GUI") == 0) { - attr = 0; - off = 0; - while (arg[off] != NUL) { - for (i = ARRAY_SIZE(hl_attr_table); --i >= 0;) { - len = (int)STRLEN(hl_name_table[i]); - if (STRNICMP(arg + off, hl_name_table[i], len) == 0) { - attr |= hl_attr_table[i]; - off += len; - break; - } - } - if (i < 0) { - semsg(_("E418: Illegal value: %s"), arg); - error = true; - break; - } - if (arg[off] == ',') { // Another one follows. - off++; - } - } - if (error) { - break; - } - if (*key == 'C') { - if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_CTERM; - } - HL_TABLE()[idx].sg_cterm = attr; - HL_TABLE()[idx].sg_cterm_bold = false; - } - } else if (*key == 'G') { - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_GUI; - } - HL_TABLE()[idx].sg_gui = attr; - } - } - } else if (STRCMP(key, "FONT") == 0) { - // in non-GUI fonts are simply ignored - } else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) { - if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_CTERM; - } - - /* When setting the foreground color, and previously the "bold" - * flag was set for a light color, reset it now */ - if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold) { - HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; - HL_TABLE()[idx].sg_cterm_bold = false; - } - - if (ascii_isdigit(*arg)) { - color = atoi(arg); - } else if (STRICMP(arg, "fg") == 0) { - if (cterm_normal_fg_color) { - color = cterm_normal_fg_color - 1; - } else { - emsg(_("E419: FG color unknown")); - error = true; - break; - } - } else if (STRICMP(arg, "bg") == 0) { - if (cterm_normal_bg_color > 0) { - color = cterm_normal_bg_color - 1; - } else { - emsg(_("E420: BG color unknown")); - error = true; - break; - } - } else { - // Reduce calls to STRICMP a bit, it can be slow. - off = TOUPPER_ASC(*arg); - for (i = ARRAY_SIZE(color_names); --i >= 0;) { - if (off == color_names[i][0] - && STRICMP(arg + 1, color_names[i] + 1) == 0) { - break; - } - } - if (i < 0) { - semsg(_("E421: Color name or number not recognized: %s"), - key_start); - error = true; - break; - } - - TriState bold = kNone; - color = lookup_color(i, key[5] == 'F', &bold); - - // set/reset bold attribute to get light foreground - // colors (on some terminals, e.g. "linux") - if (bold == kTrue) { - HL_TABLE()[idx].sg_cterm |= HL_BOLD; - HL_TABLE()[idx].sg_cterm_bold = true; - } else if (bold == kFalse) { - HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; - } - } - // Add one to the argument, to avoid zero. Zero is used for - // "NONE", then "color" is -1. - if (key[5] == 'F') { - HL_TABLE()[idx].sg_cterm_fg = color + 1; - if (is_normal_group) { - cterm_normal_fg_color = color + 1; - } - } else { - HL_TABLE()[idx].sg_cterm_bg = color + 1; - if (is_normal_group) { - cterm_normal_bg_color = color + 1; - if (!ui_rgb_attached()) { - if (color >= 0) { - int dark = -1; - - if (t_colors < 16) { - dark = (color == 0 || color == 4); - } else if (color < 16) { - // Limit the heuristic to the standard 16 colors - dark = (color < 7 || color == 8); - } - // Set the 'background' option if the value is - // wrong. - if (dark != -1 - && dark != (*p_bg == 'd') - && !option_was_set("bg")) { - set_option_value("bg", 0L, (dark ? "dark" : "light"), 0); - reset_option_was_set("bg"); - } - } - } - } - } - } - } else if (strcmp(key, "GUIFG") == 0) { - char **namep = &HL_TABLE()[idx].sg_rgb_fg_name; - - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_GUI; - } - - if (*namep == NULL || STRCMP(*namep, arg) != 0) { - xfree(*namep); - if (strcmp(arg, "NONE") != 0) { - *namep = xstrdup(arg); - HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); - } else { - *namep = NULL; - HL_TABLE()[idx].sg_rgb_fg = -1; - } - did_change = true; - } - } - - if (is_normal_group) { - normal_fg = HL_TABLE()[idx].sg_rgb_fg; - } - } else if (STRCMP(key, "GUIBG") == 0) { - char **const namep = &HL_TABLE()[idx].sg_rgb_bg_name; - - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_GUI; - } - - if (*namep == NULL || STRCMP(*namep, arg) != 0) { - xfree(*namep); - if (STRCMP(arg, "NONE") != 0) { - *namep = xstrdup(arg); - HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); - } else { - *namep = NULL; - HL_TABLE()[idx].sg_rgb_bg = -1; - } - did_change = true; - } - } - - if (is_normal_group) { - normal_bg = HL_TABLE()[idx].sg_rgb_bg; - } - } else if (strcmp(key, "GUISP") == 0) { - char **const namep = &HL_TABLE()[idx].sg_rgb_sp_name; - - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_GUI; - } - - if (*namep == NULL || STRCMP(*namep, arg) != 0) { - xfree(*namep); - if (strcmp(arg, "NONE") != 0) { - *namep = xstrdup(arg); - HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); - } else { - *namep = NULL; - HL_TABLE()[idx].sg_rgb_sp = -1; - } - did_change = true; - } - } - - if (is_normal_group) { - normal_sp = HL_TABLE()[idx].sg_rgb_sp; - } - } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) { - // Ignored for now - } else if (strcmp(key, "BLEND") == 0) { - if (strcmp(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_blend = strtol(arg, NULL, 10); - } else { - HL_TABLE()[idx].sg_blend = -1; - } - } else { - semsg(_("E423: Illegal argument: %s"), key_start); - error = true; - break; - } - HL_TABLE()[idx].sg_cleared = false; - - // When highlighting has been given for a group, don't link it. - if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { - HL_TABLE()[idx].sg_link = 0; - } - - // Continue with next argument. - linep = (const char *)skipwhite((const char_u *)linep); - } - } - - // If there is an error, and it's a new entry, remove it from the table. - if (error && idx == highlight_ga.ga_len) { - syn_unadd_group(); - } else { - if (!error && is_normal_group) { - // Need to update all groups, because they might be using "bg" and/or - // "fg", which have been changed now. - highlight_attr_set_all(); - - if (!ui_has(kUILinegrid) && starting == 0) { - // Older UIs assume that we clear the screen after normal group is - // changed - ui_refresh(); - } else { - // TUI and newer UIs will repaint the screen themselves. NOT_VALID - // redraw below will still handle usages of guibg=fg etc. - ui_default_colors_set(); - } - did_highlight_changed = true; - redraw_all_later(NOT_VALID); - } else { - set_hl_attr(idx); - } - HL_TABLE()[idx].sg_script_ctx = current_sctx; - HL_TABLE()[idx].sg_script_ctx.sc_lnum += sourcing_lnum; - } - xfree(key); - xfree(arg); - - // Only call highlight_changed() once, after a sequence of highlight - // commands, and only if an attribute actually changed - if ((did_change - || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0) - && !did_highlight_changed) { - // Do not trigger a redraw when highlighting is changed while - // redrawing. This may happen when evaluating 'statusline' changes the - // StatusLine group. - if (!updating_screen) { - redraw_all_later(NOT_VALID); - } - need_highlight_changed = true; - } -} - -#if defined(EXITFREE) -void free_highlight(void) -{ - for (int i = 0; i < highlight_ga.ga_len; ++i) { - highlight_clear(i); - xfree(HL_TABLE()[i].sg_name); - xfree(HL_TABLE()[i].sg_name_u); - } - ga_clear(&highlight_ga); - map_destroy(cstr_t, int)(&highlight_unames); -} - -#endif - -/* - * Reset the cterm colors to what they were before Vim was started, if - * possible. Otherwise reset them to zero. - */ -void restore_cterm_colors(void) -{ - normal_fg = -1; - normal_bg = -1; - normal_sp = -1; - cterm_normal_fg_color = 0; - cterm_normal_bg_color = 0; -} - -/// @param check_link if true also check for an existing link. -/// -/// @return TRUE if highlight group "idx" has any settings. -static int hl_has_settings(int idx, bool check_link) -{ - return HL_TABLE()[idx].sg_cleared == 0 - && (HL_TABLE()[idx].sg_attr != 0 - || HL_TABLE()[idx].sg_cterm_fg != 0 - || HL_TABLE()[idx].sg_cterm_bg != 0 - || HL_TABLE()[idx].sg_rgb_fg_name != NULL - || HL_TABLE()[idx].sg_rgb_bg_name != NULL - || HL_TABLE()[idx].sg_rgb_sp_name != NULL - || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK))); -} - -/* - * Clear highlighting for one group. - */ -static void highlight_clear(int idx) -{ - HL_TABLE()[idx].sg_cleared = true; - - HL_TABLE()[idx].sg_attr = 0; - HL_TABLE()[idx].sg_cterm = 0; - HL_TABLE()[idx].sg_cterm_bold = false; - HL_TABLE()[idx].sg_cterm_fg = 0; - HL_TABLE()[idx].sg_cterm_bg = 0; - HL_TABLE()[idx].sg_gui = 0; - HL_TABLE()[idx].sg_rgb_fg = -1; - HL_TABLE()[idx].sg_rgb_bg = -1; - HL_TABLE()[idx].sg_rgb_sp = -1; - XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_fg_name); - XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name); - XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name); - HL_TABLE()[idx].sg_blend = -1; - // Restore default link and context if they exist. Otherwise clears. - HL_TABLE()[idx].sg_link = HL_TABLE()[idx].sg_deflink; - // Since we set the default link, set the location to where the default - // link was set. - HL_TABLE()[idx].sg_script_ctx = HL_TABLE()[idx].sg_deflink_sctx; -} - - -/// \addtogroup LIST_XXX -/// @{ -#define LIST_ATTR 1 -#define LIST_STRING 2 -#define LIST_INT 3 -/// @} - -static void highlight_list_one(const int id) -{ - struct hl_group *const sgp = &HL_TABLE()[id - 1]; // index is ID minus one - bool didh = false; - - if (message_filtered(sgp->sg_name)) { - return; - } - - didh = highlight_list_arg(id, didh, LIST_ATTR, - sgp->sg_cterm, NULL, "cterm"); - didh = highlight_list_arg(id, didh, LIST_INT, - sgp->sg_cterm_fg, NULL, "ctermfg"); - didh = highlight_list_arg(id, didh, LIST_INT, - sgp->sg_cterm_bg, NULL, "ctermbg"); - - didh = highlight_list_arg(id, didh, LIST_ATTR, - sgp->sg_gui, NULL, "gui"); - didh = highlight_list_arg(id, didh, LIST_STRING, - 0, sgp->sg_rgb_fg_name, "guifg"); - didh = highlight_list_arg(id, didh, LIST_STRING, - 0, sgp->sg_rgb_bg_name, "guibg"); - didh = highlight_list_arg(id, didh, LIST_STRING, - 0, sgp->sg_rgb_sp_name, "guisp"); - - didh = highlight_list_arg(id, didh, LIST_INT, - sgp->sg_blend+1, NULL, "blend"); - - if (sgp->sg_link && !got_int) { - (void)syn_list_header(didh, 0, id, true); - didh = true; - msg_puts_attr("links to", HL_ATTR(HLF_D)); - msg_putchar(' '); - msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); - } - - 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 -/// @param iarg integer argument used if \p type == LIST_INT -/// @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 *const sarg, - const char *const name) -{ - char buf[100]; - - if (got_int) { - return false; - } - if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) { - char *ts = buf; - if (type == LIST_INT) { - snprintf((char *)buf, sizeof(buf), "%d", iarg - 1); - } else if (type == LIST_STRING) { - ts = sarg; - } else { // type == LIST_ATTR - buf[0] = NUL; - for (int i = 0; hl_attr_table[i] != 0; i++) { - if (iarg & hl_attr_table[i]) { - if (buf[0] != NUL) { - xstrlcat(buf, ",", 100); - } - xstrlcat(buf, hl_name_table[i], 100); - iarg &= ~hl_attr_table[i]; // don't want "inverse" - } - } - } - - (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((char_u *)ts); - } - } - return didh; -} - -/// Check whether highlight group has attribute -/// -/// @param[in] id Highlight group to check. -/// @param[in] flag Attribute to check. -/// @param[in] modec 'g' for GUI, 'c' for term. -/// -/// @return "1" if highlight group has attribute, NULL otherwise. -const char *highlight_has_attr(const int id, const int flag, const int modec) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE -{ - int attr; - - if (id <= 0 || id > highlight_ga.ga_len) { - return NULL; - } - - if (modec == 'g') { - attr = HL_TABLE()[id - 1].sg_gui; - } else { - attr = HL_TABLE()[id - 1].sg_cterm; - } - - return (attr & flag) ? "1" : NULL; -} - -/// Return color name of the given highlight group -/// -/// @param[in] id Highlight group to work with. -/// @param[in] what What to return: one of "font", "fg", "bg", "sp", "fg#", -/// "bg#" or "sp#". -/// @param[in] modec 'g' for GUI, 'c' for cterm and 't' for term. -/// -/// @return color name, possibly in a static buffer. Buffer will be overwritten -/// on next highlight_color() call. May return NULL. -const char *highlight_color(const int id, const char *const what, const int modec) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL -{ - static char name[20]; - int n; - bool fg = false; - bool sp = false; - bool font = false; - - if (id <= 0 || id > highlight_ga.ga_len) { - return NULL; - } - - if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g') { - fg = true; - } else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o' - && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't') { - font = true; - } else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p') { - sp = true; - } else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g')) { - return NULL; - } - if (modec == 'g') { - if (what[2] == '#' && ui_rgb_attached()) { - if (fg) { - n = HL_TABLE()[id - 1].sg_rgb_fg; - } else if (sp) { - n = HL_TABLE()[id - 1].sg_rgb_sp; - } else { - n = HL_TABLE()[id - 1].sg_rgb_bg; - } - if (n < 0 || n > 0xffffff) { - return NULL; - } - snprintf(name, sizeof(name), "#%06x", n); - return name; - } - if (fg) { - return (const char *)HL_TABLE()[id - 1].sg_rgb_fg_name; - } - if (sp) { - return (const char *)HL_TABLE()[id - 1].sg_rgb_sp_name; - } - return (const char *)HL_TABLE()[id - 1].sg_rgb_bg_name; - } - if (font || sp) { - return NULL; - } - if (modec == 'c') { - if (fg) { - n = HL_TABLE()[id - 1].sg_cterm_fg - 1; - } else { - n = HL_TABLE()[id - 1].sg_cterm_bg - 1; - } - if (n < 0) { - return NULL; - } - snprintf(name, sizeof(name), "%d", n); - return name; - } - // term doesn't have color. - return NULL; -} - -/// Output the syntax list header. -/// -/// @param did_header did header already -/// @param outlen length of string that comes -/// @param id highlight group id -/// @param force_newline always start a new line -/// @return true when started a new line. -static bool syn_list_header(const bool did_header, const int outlen, const int id, - bool force_newline) -{ - int endcol = 19; - bool newline = true; - bool adjust = true; - - if (!did_header) { - msg_putchar('\n'); - if (got_int) { - return true; - } - msg_outtrans(HL_TABLE()[id - 1].sg_name); - endcol = 15; - } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) { - msg_putchar(' '); - adjust = false; - } else if (msg_col + outlen + 1 >= Columns || force_newline) { - msg_putchar('\n'); - if (got_int) { - return true; - } - } else { - if (msg_col >= endcol) { // wrap around is like starting a new line - newline = false; - } - } - - if (adjust) { - if (msg_col >= endcol) { - // output at least one space - endcol = msg_col + 1; - } - - msg_advance(endcol); - } - - // Show "xxx" with the attributes. - if (!did_header) { - msg_puts_attr("xxx", syn_id2attr(id)); - msg_putchar(' '); - } - - return newline; -} - -/// Set the attribute numbers for a highlight group. -/// Called after one of the attributes has changed. -/// @param idx corrected highlight index -static void set_hl_attr(int idx) -{ - HlAttrs at_en = HLATTRS_INIT; - struct hl_group *sgp = HL_TABLE() + idx; - - at_en.cterm_ae_attr = sgp->sg_cterm; - at_en.cterm_fg_color = sgp->sg_cterm_fg; - at_en.cterm_bg_color = sgp->sg_cterm_bg; - at_en.rgb_ae_attr = sgp->sg_gui; - // FIXME(tarruda): The "unset value" for rgb is -1, but since hlgroup is - // initialized with 0(by garray functions), check for sg_rgb_{f,b}g_name - // before setting attr_entry->{f,g}g_color to a other than -1 - at_en.rgb_fg_color = sgp->sg_rgb_fg_name ? sgp->sg_rgb_fg : -1; - at_en.rgb_bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1; - 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(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)) { - ui_mode_info_set(); - } -} - -int syn_name2id(const char *name) - FUNC_ATTR_NONNULL_ALL -{ - return syn_name2id_len((char_u *)name, STRLEN(name)); -} - -/// Lookup a highlight group name and return its ID. -/// -/// @param highlight name e.g. 'Cursor', 'Normal' -/// @return the highlight id, else 0 if \p name does not exist -int syn_name2id_len(const char_u *name, size_t len) - FUNC_ATTR_NONNULL_ALL -{ - char name_u[201]; - - if (len == 0 || len > 200) { - // ID names over 200 chars don't deserve to be found! - return 0; - } - - // Avoid using stricmp() too much, it's slow on some systems */ - // Avoid alloc()/free(), these are slow too. - memcpy(name_u, name, len); - name_u[len] = '\0'; - vim_strup((char_u *)name_u); - - // map_get(..., int) returns 0 when no key is present, which is - // the expected value for missing highlight group. - return map_get(cstr_t, int)(&highlight_unames, name_u); -} - -/// Lookup a highlight group name and return its attributes. -/// Return zero if not found. -int syn_name2attr(const char_u *name) - FUNC_ATTR_NONNULL_ALL -{ - int id = syn_name2id((char *)name); - - if (id != 0) { - return syn_id2attr(id); - } - return 0; -} - -/* - * Return TRUE if highlight group "name" exists. - */ -int highlight_exists(const char *name) -{ - return syn_name2id(name) > 0; -} - -/* - * Return the name of highlight group "id". - * When not a valid ID return an empty string. - */ -char_u *syn_id2name(int id) -{ - if (id <= 0 || id > highlight_ga.ga_len) { - return (char_u *)""; - } - return HL_TABLE()[id - 1].sg_name; -} - - -/// Find highlight group name in the table and return its ID. -/// If it doesn't exist yet, a new entry is created. -/// -/// @param pp Highlight group name -/// @param len length of \p pp -/// -/// @return 0 for failure else the id of the group -int syn_check_group(const char *name, int len) -{ - int id = syn_name2id_len((char_u *)name, len); - if (id == 0) { // doesn't exist yet - return syn_add_group(vim_strnsave((char_u *)name, len)); - } - return id; -} - -/// Add new highlight group and return its ID. -/// -/// @param name must be an allocated string, it will be consumed. -/// @return 0 for failure, else the allocated group id -/// @see syn_check_group syn_unadd_group -static int syn_add_group(char_u *name) -{ - char_u *p; - - // Check that the name is ASCII letters, digits and underscore. - for (p = name; *p != NUL; ++p) { - if (!vim_isprintc(*p)) { - emsg(_("E669: Unprintable character in group name")); - xfree(name); - return 0; - } else if (!ASCII_ISALNUM(*p) && *p != '_') { - /* This is an error, but since there previously was no check only - * give a warning. */ - msg_source(HL_ATTR(HLF_W)); - msg(_("W18: Invalid character in group name")); - break; - } - } - - /* - * First call for this growarray: init growing array. - */ - if (highlight_ga.ga_data == NULL) { - highlight_ga.ga_itemsize = sizeof(struct hl_group); - ga_set_growsize(&highlight_ga, 10); - } - - if (highlight_ga.ga_len >= MAX_HL_ID) { - emsg(_("E849: Too many highlight and syntax groups")); - xfree(name); - return 0; - } - - char *const name_up = (char *)vim_strsave_up(name); - - // Append another syntax_highlight entry. - struct hl_group *hlgp = GA_APPEND_VIA_PTR(struct hl_group, &highlight_ga); - memset(hlgp, 0, sizeof(*hlgp)); - hlgp->sg_name = name; - hlgp->sg_rgb_bg = -1; - hlgp->sg_rgb_fg = -1; - hlgp->sg_rgb_sp = -1; - hlgp->sg_blend = -1; - hlgp->sg_name_u = name_up; - - int id = highlight_ga.ga_len; // ID is index plus one - - map_put(cstr_t, int)(&highlight_unames, name_up, id); - - return id; -} - -/// When, just after calling syn_add_group(), an error is discovered, this -/// function deletes the new name. -static void syn_unadd_group(void) -{ - highlight_ga.ga_len--; - HlGroup *item = &HL_TABLE()[highlight_ga.ga_len]; - map_del(cstr_t, int)(&highlight_unames, item->sg_name_u); - xfree(item->sg_name); - xfree(item->sg_name_u); -} - - -/// Translate a group ID to highlight attributes. -/// @see syn_attr2entry -int syn_id2attr(int hl_id) -{ - hl_id = syn_get_final_id(hl_id); - struct hl_group *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one - - int attr = ns_get_hl(-1, hl_id, false, sgp->sg_set); - if (attr >= 0) { - return attr; - } - return sgp->sg_attr; -} - - -/* - * Translate a group ID to the final group ID (following links). - */ -int syn_get_final_id(int hl_id) -{ - int count; - - if (hl_id > highlight_ga.ga_len || hl_id < 1) { - return 0; // Can be called from eval!! - } - /* - * Follow links until there is no more. - * Look out for loops! Break after 100 links. - */ - for (count = 100; --count >= 0;) { - struct hl_group *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one - - // 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, sgp->sg_set); - if (check == 0) { - return hl_id; // how dare! it broke the link! - } else if (check > 0) { - hl_id = check; - continue; - } - - - if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { - break; - } - hl_id = sgp->sg_link; - } - - return hl_id; -} - -/// Refresh the color attributes of all highlight groups. -void highlight_attr_set_all(void) -{ - for (int idx = 0; idx < highlight_ga.ga_len; idx++) { - struct hl_group *sgp = &HL_TABLE()[idx]; - if (sgp->sg_rgb_bg_name != NULL) { - sgp->sg_rgb_bg = name_to_color(sgp->sg_rgb_bg_name); - } - if (sgp->sg_rgb_fg_name != NULL) { - sgp->sg_rgb_fg = name_to_color(sgp->sg_rgb_fg_name); - } - if (sgp->sg_rgb_sp_name != NULL) { - sgp->sg_rgb_sp = name_to_color(sgp->sg_rgb_sp_name); - } - set_hl_attr(idx); - } -} - -// Apply difference between User[1-9] and HLF_S to HLF_SNC. -static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i, int hlf, int *table) - FUNC_ATTR_NONNULL_ALL -{ - struct hl_group *const hlt = HL_TABLE(); - - if (id_alt == 0) { - memset(&hlt[hlcnt + i], 0, sizeof(struct hl_group)); - hlt[hlcnt + i].sg_cterm = highlight_attr[hlf]; - hlt[hlcnt + i].sg_gui = highlight_attr[hlf]; - } else { - memmove(&hlt[hlcnt + i], &hlt[id_alt - 1], sizeof(struct hl_group)); - } - hlt[hlcnt + i].sg_link = 0; - - hlt[hlcnt + i].sg_cterm ^= hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm; - if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg) { - hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg; - } - if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg) { - hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg; - } - hlt[hlcnt + i].sg_gui ^= hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui; - if (hlt[id - 1].sg_rgb_fg != hlt[id_S - 1].sg_rgb_fg) { - hlt[hlcnt + i].sg_rgb_fg = hlt[id - 1].sg_rgb_fg; - } - if (hlt[id - 1].sg_rgb_bg != hlt[id_S - 1].sg_rgb_bg) { - hlt[hlcnt + i].sg_rgb_bg = hlt[id - 1].sg_rgb_bg; - } - if (hlt[id - 1].sg_rgb_sp != hlt[id_S - 1].sg_rgb_sp) { - hlt[hlcnt + i].sg_rgb_sp = hlt[id - 1].sg_rgb_sp; - } - highlight_ga.ga_len = hlcnt + i + 1; - set_hl_attr(hlcnt + i); // At long last we can apply - table[i] = syn_id2attr(hlcnt + i + 1); -} - -/// Translate highlight groups into attributes in highlight_attr[] and set up -/// the user highlights User1..9. A set of corresponding highlights to use on -/// top of HLF_SNC is computed. Called only when nvim starts and upon first -/// screen redraw after any :highlight command. -void highlight_changed(void) -{ - int id; - char userhl[30]; // use 30 to avoid compiler warning - int id_S = -1; - int id_SNC = 0; - int hlcnt; - - need_highlight_changed = false; - - /// Translate builtin highlight groups into attributes for quick lookup. - for (int hlf = 0; hlf < HLF_COUNT; hlf++) { - id = syn_check_group(hlf_names[hlf], STRLEN(hlf_names[hlf])); - if (id == 0) { - abort(); - } - int final_id = syn_get_final_id(id); - if (hlf == HLF_SNC) { - id_SNC = final_id; - } else if (hlf == HLF_S) { - id_S = final_id; - } - - highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id, - hlf == HLF_INACTIVE); - - if (highlight_attr[hlf] != highlight_attr_last[hlf]) { - if (hlf == HLF_MSG) { - clear_cmdline = true; - } - ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]), - highlight_attr[hlf]); - highlight_attr_last[hlf] = highlight_attr[hlf]; - } - } - - // - // Setup the user highlights - // - // Temporarily utilize 10 more hl entries: - // 9 for User1-User9 combined with StatusLineNC - // 1 for StatusLine default - // Must to be in there simultaneously in case of table overflows in - // get_attr_entry() - ga_grow(&highlight_ga, 10); - hlcnt = highlight_ga.ga_len; - if (id_S == -1) { - // Make sure id_S is always valid to simplify code below. Use the last entry - memset(&HL_TABLE()[hlcnt + 9], 0, sizeof(struct hl_group)); - id_S = hlcnt + 10; - } - for (int i = 0; i < 9; i++) { - snprintf(userhl, sizeof(userhl), "User%d", i + 1); - id = syn_name2id(userhl); - if (id == 0) { - highlight_user[i] = 0; - highlight_stlnc[i] = 0; - } else { - highlight_user[i] = syn_id2attr(id); - combine_stl_hlt(id, id_S, id_SNC, hlcnt, i, HLF_SNC, highlight_stlnc); - } - } - highlight_ga.ga_len = hlcnt; -} - - -/* - * Handle command line completion for :highlight command. - */ -void set_context_in_highlight_cmd(expand_T *xp, const char *arg) -{ - // Default: expand group names. - xp->xp_context = EXPAND_HIGHLIGHT; - xp->xp_pattern = (char_u *)arg; - include_link = 2; - include_default = 1; - - // (part of) subcommand already typed - if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); - if (*p != NUL) { // Past "default" or group name. - include_default = 0; - if (strncmp("default", arg, p - arg) == 0) { - arg = (const char *)skipwhite((const char_u *)p); - xp->xp_pattern = (char_u *)arg; - p = (const char *)skiptowhite((const char_u *)arg); - } - if (*p != NUL) { // past group name - include_link = 0; - if (arg[1] == 'i' && arg[0] == 'N') { - highlight_list(); - } - if (strncmp("link", arg, p - arg) == 0 - || strncmp("clear", arg, p - arg) == 0) { - xp->xp_pattern = skipwhite((const char_u *)p); - p = (const char *)skiptowhite(xp->xp_pattern); - if (*p != NUL) { // Past first group name. - xp->xp_pattern = skipwhite((const char_u *)p); - p = (const char *)skiptowhite(xp->xp_pattern); - } - } - if (*p != NUL) { // Past group name(s). - xp->xp_context = EXPAND_NOTHING; - } - } - } - } -} - -/* - * List highlighting matches in a nice way. - */ -static void highlight_list(void) -{ - int i; - - for (i = 10; --i >= 0;) { - highlight_list_two(i, HL_ATTR(HLF_D)); - } - for (i = 40; --i >= 0;) { - highlight_list_two(99, 0); - } -} - -static void highlight_list_two(int cnt, int attr) -{ - msg_puts_attr(&("N \bI \b! \b"[cnt / 11]), attr); - msg_clr_eos(); - ui_flush(); - os_delay(cnt == 99 ? 40L : (long)cnt * 50L, false); -} - - -/// Function given to ExpandGeneric() to obtain the list of group names. -const char *get_highlight_name(expand_T *const xp, int idx) - FUNC_ATTR_WARN_UNUSED_RESULT -{ - return get_highlight_name_ext(xp, idx, true); -} - - -/// Obtain a highlight group name. -/// -/// @param skip_cleared if true don't return a cleared entry. -const char *get_highlight_name_ext(expand_T *xp, int idx, bool skip_cleared) - FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (idx < 0) { - return NULL; - } - - // Items are never removed from the table, skip the ones that were cleared. - if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared) { - return ""; - } - - if (idx == highlight_ga.ga_len && include_none != 0) { - return "none"; - } else if (idx == highlight_ga.ga_len + include_none - && include_default != 0) { - return "default"; - } else if (idx == highlight_ga.ga_len + include_none + include_default - && include_link != 0) { - return "link"; - } else if (idx == highlight_ga.ga_len + include_none + include_default + 1 - && include_link != 0) { - return "clear"; - } else if (idx >= highlight_ga.ga_len) { - return NULL; - } - return (const char *)HL_TABLE()[idx].sg_name; -} - -color_name_table_T color_name_table[] = { - // Colors from rgb.txt - { "AliceBlue", RGB_(0xf0, 0xf8, 0xff) }, - { "AntiqueWhite", RGB_(0xfa, 0xeb, 0xd7) }, - { "AntiqueWhite1", RGB_(0xff, 0xef, 0xdb) }, - { "AntiqueWhite2", RGB_(0xee, 0xdf, 0xcc) }, - { "AntiqueWhite3", RGB_(0xcd, 0xc0, 0xb0) }, - { "AntiqueWhite4", RGB_(0x8b, 0x83, 0x78) }, - { "Aqua", RGB_(0x00, 0xff, 0xff) }, - { "Aquamarine", RGB_(0x7f, 0xff, 0xd4) }, - { "Aquamarine1", RGB_(0x7f, 0xff, 0xd4) }, - { "Aquamarine2", RGB_(0x76, 0xee, 0xc6) }, - { "Aquamarine3", RGB_(0x66, 0xcd, 0xaa) }, - { "Aquamarine4", RGB_(0x45, 0x8b, 0x74) }, - { "Azure", RGB_(0xf0, 0xff, 0xff) }, - { "Azure1", RGB_(0xf0, 0xff, 0xff) }, - { "Azure2", RGB_(0xe0, 0xee, 0xee) }, - { "Azure3", RGB_(0xc1, 0xcd, 0xcd) }, - { "Azure4", RGB_(0x83, 0x8b, 0x8b) }, - { "Beige", RGB_(0xf5, 0xf5, 0xdc) }, - { "Bisque", RGB_(0xff, 0xe4, 0xc4) }, - { "Bisque1", RGB_(0xff, 0xe4, 0xc4) }, - { "Bisque2", RGB_(0xee, 0xd5, 0xb7) }, - { "Bisque3", RGB_(0xcd, 0xb7, 0x9e) }, - { "Bisque4", RGB_(0x8b, 0x7d, 0x6b) }, - { "Black", RGB_(0x00, 0x00, 0x00) }, - { "BlanchedAlmond", RGB_(0xff, 0xeb, 0xcd) }, - { "Blue", RGB_(0x00, 0x00, 0xff) }, - { "Blue1", RGB_(0x0, 0x0, 0xff) }, - { "Blue2", RGB_(0x0, 0x0, 0xee) }, - { "Blue3", RGB_(0x0, 0x0, 0xcd) }, - { "Blue4", RGB_(0x0, 0x0, 0x8b) }, - { "BlueViolet", RGB_(0x8a, 0x2b, 0xe2) }, - { "Brown", RGB_(0xa5, 0x2a, 0x2a) }, - { "Brown1", RGB_(0xff, 0x40, 0x40) }, - { "Brown2", RGB_(0xee, 0x3b, 0x3b) }, - { "Brown3", RGB_(0xcd, 0x33, 0x33) }, - { "Brown4", RGB_(0x8b, 0x23, 0x23) }, - { "BurlyWood", RGB_(0xde, 0xb8, 0x87) }, - { "Burlywood1", RGB_(0xff, 0xd3, 0x9b) }, - { "Burlywood2", RGB_(0xee, 0xc5, 0x91) }, - { "Burlywood3", RGB_(0xcd, 0xaa, 0x7d) }, - { "Burlywood4", RGB_(0x8b, 0x73, 0x55) }, - { "CadetBlue", RGB_(0x5f, 0x9e, 0xa0) }, - { "CadetBlue1", RGB_(0x98, 0xf5, 0xff) }, - { "CadetBlue2", RGB_(0x8e, 0xe5, 0xee) }, - { "CadetBlue3", RGB_(0x7a, 0xc5, 0xcd) }, - { "CadetBlue4", RGB_(0x53, 0x86, 0x8b) }, - { "ChartReuse", RGB_(0x7f, 0xff, 0x00) }, - { "Chartreuse1", RGB_(0x7f, 0xff, 0x0) }, - { "Chartreuse2", RGB_(0x76, 0xee, 0x0) }, - { "Chartreuse3", RGB_(0x66, 0xcd, 0x0) }, - { "Chartreuse4", RGB_(0x45, 0x8b, 0x0) }, - { "Chocolate", RGB_(0xd2, 0x69, 0x1e) }, - { "Chocolate1", RGB_(0xff, 0x7f, 0x24) }, - { "Chocolate2", RGB_(0xee, 0x76, 0x21) }, - { "Chocolate3", RGB_(0xcd, 0x66, 0x1d) }, - { "Chocolate4", RGB_(0x8b, 0x45, 0x13) }, - { "Coral", RGB_(0xff, 0x7f, 0x50) }, - { "Coral1", RGB_(0xff, 0x72, 0x56) }, - { "Coral2", RGB_(0xee, 0x6a, 0x50) }, - { "Coral3", RGB_(0xcd, 0x5b, 0x45) }, - { "Coral4", RGB_(0x8b, 0x3e, 0x2f) }, - { "CornFlowerBlue", RGB_(0x64, 0x95, 0xed) }, - { "Cornsilk", RGB_(0xff, 0xf8, 0xdc) }, - { "Cornsilk1", RGB_(0xff, 0xf8, 0xdc) }, - { "Cornsilk2", RGB_(0xee, 0xe8, 0xcd) }, - { "Cornsilk3", RGB_(0xcd, 0xc8, 0xb1) }, - { "Cornsilk4", RGB_(0x8b, 0x88, 0x78) }, - { "Crimson", RGB_(0xdc, 0x14, 0x3c) }, - { "Cyan", RGB_(0x00, 0xff, 0xff) }, - { "Cyan1", RGB_(0x0, 0xff, 0xff) }, - { "Cyan2", RGB_(0x0, 0xee, 0xee) }, - { "Cyan3", RGB_(0x0, 0xcd, 0xcd) }, - { "Cyan4", RGB_(0x0, 0x8b, 0x8b) }, - { "DarkBlue", RGB_(0x00, 0x00, 0x8b) }, - { "DarkCyan", RGB_(0x00, 0x8b, 0x8b) }, - { "DarkGoldenRod", RGB_(0xb8, 0x86, 0x0b) }, - { "DarkGoldenrod1", RGB_(0xff, 0xb9, 0xf) }, - { "DarkGoldenrod2", RGB_(0xee, 0xad, 0xe) }, - { "DarkGoldenrod3", RGB_(0xcd, 0x95, 0xc) }, - { "DarkGoldenrod4", RGB_(0x8b, 0x65, 0x8) }, - { "DarkGray", RGB_(0xa9, 0xa9, 0xa9) }, - { "DarkGreen", RGB_(0x00, 0x64, 0x00) }, - { "DarkGrey", RGB_(0xa9, 0xa9, 0xa9) }, - { "DarkKhaki", RGB_(0xbd, 0xb7, 0x6b) }, - { "DarkMagenta", RGB_(0x8b, 0x00, 0x8b) }, - { "DarkOliveGreen", RGB_(0x55, 0x6b, 0x2f) }, - { "DarkOliveGreen1", RGB_(0xca, 0xff, 0x70) }, - { "DarkOliveGreen2", RGB_(0xbc, 0xee, 0x68) }, - { "DarkOliveGreen3", RGB_(0xa2, 0xcd, 0x5a) }, - { "DarkOliveGreen4", RGB_(0x6e, 0x8b, 0x3d) }, - { "DarkOrange", RGB_(0xff, 0x8c, 0x00) }, - { "DarkOrange1", RGB_(0xff, 0x7f, 0x0) }, - { "DarkOrange2", RGB_(0xee, 0x76, 0x0) }, - { "DarkOrange3", RGB_(0xcd, 0x66, 0x0) }, - { "DarkOrange4", RGB_(0x8b, 0x45, 0x0) }, - { "DarkOrchid", RGB_(0x99, 0x32, 0xcc) }, - { "DarkOrchid1", RGB_(0xbf, 0x3e, 0xff) }, - { "DarkOrchid2", RGB_(0xb2, 0x3a, 0xee) }, - { "DarkOrchid3", RGB_(0x9a, 0x32, 0xcd) }, - { "DarkOrchid4", RGB_(0x68, 0x22, 0x8b) }, - { "DarkRed", RGB_(0x8b, 0x00, 0x00) }, - { "DarkSalmon", RGB_(0xe9, 0x96, 0x7a) }, - { "DarkSeaGreen", RGB_(0x8f, 0xbc, 0x8f) }, - { "DarkSeaGreen1", RGB_(0xc1, 0xff, 0xc1) }, - { "DarkSeaGreen2", RGB_(0xb4, 0xee, 0xb4) }, - { "DarkSeaGreen3", RGB_(0x9b, 0xcd, 0x9b) }, - { "DarkSeaGreen4", RGB_(0x69, 0x8b, 0x69) }, - { "DarkSlateBlue", RGB_(0x48, 0x3d, 0x8b) }, - { "DarkSlateGray", RGB_(0x2f, 0x4f, 0x4f) }, - { "DarkSlateGray1", RGB_(0x97, 0xff, 0xff) }, - { "DarkSlateGray2", RGB_(0x8d, 0xee, 0xee) }, - { "DarkSlateGray3", RGB_(0x79, 0xcd, 0xcd) }, - { "DarkSlateGray4", RGB_(0x52, 0x8b, 0x8b) }, - { "DarkSlateGrey", RGB_(0x2f, 0x4f, 0x4f) }, - { "DarkTurquoise", RGB_(0x00, 0xce, 0xd1) }, - { "DarkViolet", RGB_(0x94, 0x00, 0xd3) }, - { "DarkYellow", RGB_(0xbb, 0xbb, 0x00) }, - { "DeepPink", RGB_(0xff, 0x14, 0x93) }, - { "DeepPink1", RGB_(0xff, 0x14, 0x93) }, - { "DeepPink2", RGB_(0xee, 0x12, 0x89) }, - { "DeepPink3", RGB_(0xcd, 0x10, 0x76) }, - { "DeepPink4", RGB_(0x8b, 0xa, 0x50) }, - { "DeepSkyBlue", RGB_(0x00, 0xbf, 0xff) }, - { "DeepSkyBlue1", RGB_(0x0, 0xbf, 0xff) }, - { "DeepSkyBlue2", RGB_(0x0, 0xb2, 0xee) }, - { "DeepSkyBlue3", RGB_(0x0, 0x9a, 0xcd) }, - { "DeepSkyBlue4", RGB_(0x0, 0x68, 0x8b) }, - { "DimGray", RGB_(0x69, 0x69, 0x69) }, - { "DimGrey", RGB_(0x69, 0x69, 0x69) }, - { "DodgerBlue", RGB_(0x1e, 0x90, 0xff) }, - { "DodgerBlue1", RGB_(0x1e, 0x90, 0xff) }, - { "DodgerBlue2", RGB_(0x1c, 0x86, 0xee) }, - { "DodgerBlue3", RGB_(0x18, 0x74, 0xcd) }, - { "DodgerBlue4", RGB_(0x10, 0x4e, 0x8b) }, - { "Firebrick", RGB_(0xb2, 0x22, 0x22) }, - { "Firebrick1", RGB_(0xff, 0x30, 0x30) }, - { "Firebrick2", RGB_(0xee, 0x2c, 0x2c) }, - { "Firebrick3", RGB_(0xcd, 0x26, 0x26) }, - { "Firebrick4", RGB_(0x8b, 0x1a, 0x1a) }, - { "FloralWhite", RGB_(0xff, 0xfa, 0xf0) }, - { "ForestGreen", RGB_(0x22, 0x8b, 0x22) }, - { "Fuchsia", RGB_(0xff, 0x00, 0xff) }, - { "Gainsboro", RGB_(0xdc, 0xdc, 0xdc) }, - { "GhostWhite", RGB_(0xf8, 0xf8, 0xff) }, - { "Gold", RGB_(0xff, 0xd7, 0x00) }, - { "Gold1", RGB_(0xff, 0xd7, 0x0) }, - { "Gold2", RGB_(0xee, 0xc9, 0x0) }, - { "Gold3", RGB_(0xcd, 0xad, 0x0) }, - { "Gold4", RGB_(0x8b, 0x75, 0x0) }, - { "GoldenRod", RGB_(0xda, 0xa5, 0x20) }, - { "Goldenrod1", RGB_(0xff, 0xc1, 0x25) }, - { "Goldenrod2", RGB_(0xee, 0xb4, 0x22) }, - { "Goldenrod3", RGB_(0xcd, 0x9b, 0x1d) }, - { "Goldenrod4", RGB_(0x8b, 0x69, 0x14) }, - { "Gray", RGB_(0x80, 0x80, 0x80) }, - { "Gray0", RGB_(0x0, 0x0, 0x0) }, - { "Gray1", RGB_(0x3, 0x3, 0x3) }, - { "Gray10", RGB_(0x1a, 0x1a, 0x1a) }, - { "Gray100", RGB_(0xff, 0xff, 0xff) }, - { "Gray11", RGB_(0x1c, 0x1c, 0x1c) }, - { "Gray12", RGB_(0x1f, 0x1f, 0x1f) }, - { "Gray13", RGB_(0x21, 0x21, 0x21) }, - { "Gray14", RGB_(0x24, 0x24, 0x24) }, - { "Gray15", RGB_(0x26, 0x26, 0x26) }, - { "Gray16", RGB_(0x29, 0x29, 0x29) }, - { "Gray17", RGB_(0x2b, 0x2b, 0x2b) }, - { "Gray18", RGB_(0x2e, 0x2e, 0x2e) }, - { "Gray19", RGB_(0x30, 0x30, 0x30) }, - { "Gray2", RGB_(0x5, 0x5, 0x5) }, - { "Gray20", RGB_(0x33, 0x33, 0x33) }, - { "Gray21", RGB_(0x36, 0x36, 0x36) }, - { "Gray22", RGB_(0x38, 0x38, 0x38) }, - { "Gray23", RGB_(0x3b, 0x3b, 0x3b) }, - { "Gray24", RGB_(0x3d, 0x3d, 0x3d) }, - { "Gray25", RGB_(0x40, 0x40, 0x40) }, - { "Gray26", RGB_(0x42, 0x42, 0x42) }, - { "Gray27", RGB_(0x45, 0x45, 0x45) }, - { "Gray28", RGB_(0x47, 0x47, 0x47) }, - { "Gray29", RGB_(0x4a, 0x4a, 0x4a) }, - { "Gray3", RGB_(0x8, 0x8, 0x8) }, - { "Gray30", RGB_(0x4d, 0x4d, 0x4d) }, - { "Gray31", RGB_(0x4f, 0x4f, 0x4f) }, - { "Gray32", RGB_(0x52, 0x52, 0x52) }, - { "Gray33", RGB_(0x54, 0x54, 0x54) }, - { "Gray34", RGB_(0x57, 0x57, 0x57) }, - { "Gray35", RGB_(0x59, 0x59, 0x59) }, - { "Gray36", RGB_(0x5c, 0x5c, 0x5c) }, - { "Gray37", RGB_(0x5e, 0x5e, 0x5e) }, - { "Gray38", RGB_(0x61, 0x61, 0x61) }, - { "Gray39", RGB_(0x63, 0x63, 0x63) }, - { "Gray4", RGB_(0xa, 0xa, 0xa) }, - { "Gray40", RGB_(0x66, 0x66, 0x66) }, - { "Gray41", RGB_(0x69, 0x69, 0x69) }, - { "Gray42", RGB_(0x6b, 0x6b, 0x6b) }, - { "Gray43", RGB_(0x6e, 0x6e, 0x6e) }, - { "Gray44", RGB_(0x70, 0x70, 0x70) }, - { "Gray45", RGB_(0x73, 0x73, 0x73) }, - { "Gray46", RGB_(0x75, 0x75, 0x75) }, - { "Gray47", RGB_(0x78, 0x78, 0x78) }, - { "Gray48", RGB_(0x7a, 0x7a, 0x7a) }, - { "Gray49", RGB_(0x7d, 0x7d, 0x7d) }, - { "Gray5", RGB_(0xd, 0xd, 0xd) }, - { "Gray50", RGB_(0x7f, 0x7f, 0x7f) }, - { "Gray51", RGB_(0x82, 0x82, 0x82) }, - { "Gray52", RGB_(0x85, 0x85, 0x85) }, - { "Gray53", RGB_(0x87, 0x87, 0x87) }, - { "Gray54", RGB_(0x8a, 0x8a, 0x8a) }, - { "Gray55", RGB_(0x8c, 0x8c, 0x8c) }, - { "Gray56", RGB_(0x8f, 0x8f, 0x8f) }, - { "Gray57", RGB_(0x91, 0x91, 0x91) }, - { "Gray58", RGB_(0x94, 0x94, 0x94) }, - { "Gray59", RGB_(0x96, 0x96, 0x96) }, - { "Gray6", RGB_(0xf, 0xf, 0xf) }, - { "Gray60", RGB_(0x99, 0x99, 0x99) }, - { "Gray61", RGB_(0x9c, 0x9c, 0x9c) }, - { "Gray62", RGB_(0x9e, 0x9e, 0x9e) }, - { "Gray63", RGB_(0xa1, 0xa1, 0xa1) }, - { "Gray64", RGB_(0xa3, 0xa3, 0xa3) }, - { "Gray65", RGB_(0xa6, 0xa6, 0xa6) }, - { "Gray66", RGB_(0xa8, 0xa8, 0xa8) }, - { "Gray67", RGB_(0xab, 0xab, 0xab) }, - { "Gray68", RGB_(0xad, 0xad, 0xad) }, - { "Gray69", RGB_(0xb0, 0xb0, 0xb0) }, - { "Gray7", RGB_(0x12, 0x12, 0x12) }, - { "Gray70", RGB_(0xb3, 0xb3, 0xb3) }, - { "Gray71", RGB_(0xb5, 0xb5, 0xb5) }, - { "Gray72", RGB_(0xb8, 0xb8, 0xb8) }, - { "Gray73", RGB_(0xba, 0xba, 0xba) }, - { "Gray74", RGB_(0xbd, 0xbd, 0xbd) }, - { "Gray75", RGB_(0xbf, 0xbf, 0xbf) }, - { "Gray76", RGB_(0xc2, 0xc2, 0xc2) }, - { "Gray77", RGB_(0xc4, 0xc4, 0xc4) }, - { "Gray78", RGB_(0xc7, 0xc7, 0xc7) }, - { "Gray79", RGB_(0xc9, 0xc9, 0xc9) }, - { "Gray8", RGB_(0x14, 0x14, 0x14) }, - { "Gray80", RGB_(0xcc, 0xcc, 0xcc) }, - { "Gray81", RGB_(0xcf, 0xcf, 0xcf) }, - { "Gray82", RGB_(0xd1, 0xd1, 0xd1) }, - { "Gray83", RGB_(0xd4, 0xd4, 0xd4) }, - { "Gray84", RGB_(0xd6, 0xd6, 0xd6) }, - { "Gray85", RGB_(0xd9, 0xd9, 0xd9) }, - { "Gray86", RGB_(0xdb, 0xdb, 0xdb) }, - { "Gray87", RGB_(0xde, 0xde, 0xde) }, - { "Gray88", RGB_(0xe0, 0xe0, 0xe0) }, - { "Gray89", RGB_(0xe3, 0xe3, 0xe3) }, - { "Gray9", RGB_(0x17, 0x17, 0x17) }, - { "Gray90", RGB_(0xe5, 0xe5, 0xe5) }, - { "Gray91", RGB_(0xe8, 0xe8, 0xe8) }, - { "Gray92", RGB_(0xeb, 0xeb, 0xeb) }, - { "Gray93", RGB_(0xed, 0xed, 0xed) }, - { "Gray94", RGB_(0xf0, 0xf0, 0xf0) }, - { "Gray95", RGB_(0xf2, 0xf2, 0xf2) }, - { "Gray96", RGB_(0xf5, 0xf5, 0xf5) }, - { "Gray97", RGB_(0xf7, 0xf7, 0xf7) }, - { "Gray98", RGB_(0xfa, 0xfa, 0xfa) }, - { "Gray99", RGB_(0xfc, 0xfc, 0xfc) }, - { "Green", RGB_(0x00, 0x80, 0x00) }, - { "Green1", RGB_(0x0, 0xff, 0x0) }, - { "Green2", RGB_(0x0, 0xee, 0x0) }, - { "Green3", RGB_(0x0, 0xcd, 0x0) }, - { "Green4", RGB_(0x0, 0x8b, 0x0) }, - { "GreenYellow", RGB_(0xad, 0xff, 0x2f) }, - { "Grey", RGB_(0x80, 0x80, 0x80) }, - { "Grey0", RGB_(0x0, 0x0, 0x0) }, - { "Grey1", RGB_(0x3, 0x3, 0x3) }, - { "Grey10", RGB_(0x1a, 0x1a, 0x1a) }, - { "Grey100", RGB_(0xff, 0xff, 0xff) }, - { "Grey11", RGB_(0x1c, 0x1c, 0x1c) }, - { "Grey12", RGB_(0x1f, 0x1f, 0x1f) }, - { "Grey13", RGB_(0x21, 0x21, 0x21) }, - { "Grey14", RGB_(0x24, 0x24, 0x24) }, - { "Grey15", RGB_(0x26, 0x26, 0x26) }, - { "Grey16", RGB_(0x29, 0x29, 0x29) }, - { "Grey17", RGB_(0x2b, 0x2b, 0x2b) }, - { "Grey18", RGB_(0x2e, 0x2e, 0x2e) }, - { "Grey19", RGB_(0x30, 0x30, 0x30) }, - { "Grey2", RGB_(0x5, 0x5, 0x5) }, - { "Grey20", RGB_(0x33, 0x33, 0x33) }, - { "Grey21", RGB_(0x36, 0x36, 0x36) }, - { "Grey22", RGB_(0x38, 0x38, 0x38) }, - { "Grey23", RGB_(0x3b, 0x3b, 0x3b) }, - { "Grey24", RGB_(0x3d, 0x3d, 0x3d) }, - { "Grey25", RGB_(0x40, 0x40, 0x40) }, - { "Grey26", RGB_(0x42, 0x42, 0x42) }, - { "Grey27", RGB_(0x45, 0x45, 0x45) }, - { "Grey28", RGB_(0x47, 0x47, 0x47) }, - { "Grey29", RGB_(0x4a, 0x4a, 0x4a) }, - { "Grey3", RGB_(0x8, 0x8, 0x8) }, - { "Grey30", RGB_(0x4d, 0x4d, 0x4d) }, - { "Grey31", RGB_(0x4f, 0x4f, 0x4f) }, - { "Grey32", RGB_(0x52, 0x52, 0x52) }, - { "Grey33", RGB_(0x54, 0x54, 0x54) }, - { "Grey34", RGB_(0x57, 0x57, 0x57) }, - { "Grey35", RGB_(0x59, 0x59, 0x59) }, - { "Grey36", RGB_(0x5c, 0x5c, 0x5c) }, - { "Grey37", RGB_(0x5e, 0x5e, 0x5e) }, - { "Grey38", RGB_(0x61, 0x61, 0x61) }, - { "Grey39", RGB_(0x63, 0x63, 0x63) }, - { "Grey4", RGB_(0xa, 0xa, 0xa) }, - { "Grey40", RGB_(0x66, 0x66, 0x66) }, - { "Grey41", RGB_(0x69, 0x69, 0x69) }, - { "Grey42", RGB_(0x6b, 0x6b, 0x6b) }, - { "Grey43", RGB_(0x6e, 0x6e, 0x6e) }, - { "Grey44", RGB_(0x70, 0x70, 0x70) }, - { "Grey45", RGB_(0x73, 0x73, 0x73) }, - { "Grey46", RGB_(0x75, 0x75, 0x75) }, - { "Grey47", RGB_(0x78, 0x78, 0x78) }, - { "Grey48", RGB_(0x7a, 0x7a, 0x7a) }, - { "Grey49", RGB_(0x7d, 0x7d, 0x7d) }, - { "Grey5", RGB_(0xd, 0xd, 0xd) }, - { "Grey50", RGB_(0x7f, 0x7f, 0x7f) }, - { "Grey51", RGB_(0x82, 0x82, 0x82) }, - { "Grey52", RGB_(0x85, 0x85, 0x85) }, - { "Grey53", RGB_(0x87, 0x87, 0x87) }, - { "Grey54", RGB_(0x8a, 0x8a, 0x8a) }, - { "Grey55", RGB_(0x8c, 0x8c, 0x8c) }, - { "Grey56", RGB_(0x8f, 0x8f, 0x8f) }, - { "Grey57", RGB_(0x91, 0x91, 0x91) }, - { "Grey58", RGB_(0x94, 0x94, 0x94) }, - { "Grey59", RGB_(0x96, 0x96, 0x96) }, - { "Grey6", RGB_(0xf, 0xf, 0xf) }, - { "Grey60", RGB_(0x99, 0x99, 0x99) }, - { "Grey61", RGB_(0x9c, 0x9c, 0x9c) }, - { "Grey62", RGB_(0x9e, 0x9e, 0x9e) }, - { "Grey63", RGB_(0xa1, 0xa1, 0xa1) }, - { "Grey64", RGB_(0xa3, 0xa3, 0xa3) }, - { "Grey65", RGB_(0xa6, 0xa6, 0xa6) }, - { "Grey66", RGB_(0xa8, 0xa8, 0xa8) }, - { "Grey67", RGB_(0xab, 0xab, 0xab) }, - { "Grey68", RGB_(0xad, 0xad, 0xad) }, - { "Grey69", RGB_(0xb0, 0xb0, 0xb0) }, - { "Grey7", RGB_(0x12, 0x12, 0x12) }, - { "Grey70", RGB_(0xb3, 0xb3, 0xb3) }, - { "Grey71", RGB_(0xb5, 0xb5, 0xb5) }, - { "Grey72", RGB_(0xb8, 0xb8, 0xb8) }, - { "Grey73", RGB_(0xba, 0xba, 0xba) }, - { "Grey74", RGB_(0xbd, 0xbd, 0xbd) }, - { "Grey75", RGB_(0xbf, 0xbf, 0xbf) }, - { "Grey76", RGB_(0xc2, 0xc2, 0xc2) }, - { "Grey77", RGB_(0xc4, 0xc4, 0xc4) }, - { "Grey78", RGB_(0xc7, 0xc7, 0xc7) }, - { "Grey79", RGB_(0xc9, 0xc9, 0xc9) }, - { "Grey8", RGB_(0x14, 0x14, 0x14) }, - { "Grey80", RGB_(0xcc, 0xcc, 0xcc) }, - { "Grey81", RGB_(0xcf, 0xcf, 0xcf) }, - { "Grey82", RGB_(0xd1, 0xd1, 0xd1) }, - { "Grey83", RGB_(0xd4, 0xd4, 0xd4) }, - { "Grey84", RGB_(0xd6, 0xd6, 0xd6) }, - { "Grey85", RGB_(0xd9, 0xd9, 0xd9) }, - { "Grey86", RGB_(0xdb, 0xdb, 0xdb) }, - { "Grey87", RGB_(0xde, 0xde, 0xde) }, - { "Grey88", RGB_(0xe0, 0xe0, 0xe0) }, - { "Grey89", RGB_(0xe3, 0xe3, 0xe3) }, - { "Grey9", RGB_(0x17, 0x17, 0x17) }, - { "Grey90", RGB_(0xe5, 0xe5, 0xe5) }, - { "Grey91", RGB_(0xe8, 0xe8, 0xe8) }, - { "Grey92", RGB_(0xeb, 0xeb, 0xeb) }, - { "Grey93", RGB_(0xed, 0xed, 0xed) }, - { "Grey94", RGB_(0xf0, 0xf0, 0xf0) }, - { "Grey95", RGB_(0xf2, 0xf2, 0xf2) }, - { "Grey96", RGB_(0xf5, 0xf5, 0xf5) }, - { "Grey97", RGB_(0xf7, 0xf7, 0xf7) }, - { "Grey98", RGB_(0xfa, 0xfa, 0xfa) }, - { "Grey99", RGB_(0xfc, 0xfc, 0xfc) }, - { "Honeydew", RGB_(0xf0, 0xff, 0xf0) }, - { "Honeydew1", RGB_(0xf0, 0xff, 0xf0) }, - { "Honeydew2", RGB_(0xe0, 0xee, 0xe0) }, - { "Honeydew3", RGB_(0xc1, 0xcd, 0xc1) }, - { "Honeydew4", RGB_(0x83, 0x8b, 0x83) }, - { "HotPink", RGB_(0xff, 0x69, 0xb4) }, - { "HotPink1", RGB_(0xff, 0x6e, 0xb4) }, - { "HotPink2", RGB_(0xee, 0x6a, 0xa7) }, - { "HotPink3", RGB_(0xcd, 0x60, 0x90) }, - { "HotPink4", RGB_(0x8b, 0x3a, 0x62) }, - { "IndianRed", RGB_(0xcd, 0x5c, 0x5c) }, - { "IndianRed1", RGB_(0xff, 0x6a, 0x6a) }, - { "IndianRed2", RGB_(0xee, 0x63, 0x63) }, - { "IndianRed3", RGB_(0xcd, 0x55, 0x55) }, - { "IndianRed4", RGB_(0x8b, 0x3a, 0x3a) }, - { "Indigo", RGB_(0x4b, 0x00, 0x82) }, - { "Ivory", RGB_(0xff, 0xff, 0xf0) }, - { "Ivory1", RGB_(0xff, 0xff, 0xf0) }, - { "Ivory2", RGB_(0xee, 0xee, 0xe0) }, - { "Ivory3", RGB_(0xcd, 0xcd, 0xc1) }, - { "Ivory4", RGB_(0x8b, 0x8b, 0x83) }, - { "Khaki", RGB_(0xf0, 0xe6, 0x8c) }, - { "Khaki1", RGB_(0xff, 0xf6, 0x8f) }, - { "Khaki2", RGB_(0xee, 0xe6, 0x85) }, - { "Khaki3", RGB_(0xcd, 0xc6, 0x73) }, - { "Khaki4", RGB_(0x8b, 0x86, 0x4e) }, - { "Lavender", RGB_(0xe6, 0xe6, 0xfa) }, - { "LavenderBlush", RGB_(0xff, 0xf0, 0xf5) }, - { "LavenderBlush1", RGB_(0xff, 0xf0, 0xf5) }, - { "LavenderBlush2", RGB_(0xee, 0xe0, 0xe5) }, - { "LavenderBlush3", RGB_(0xcd, 0xc1, 0xc5) }, - { "LavenderBlush4", RGB_(0x8b, 0x83, 0x86) }, - { "LawnGreen", RGB_(0x7c, 0xfc, 0x00) }, - { "LemonChiffon", RGB_(0xff, 0xfa, 0xcd) }, - { "LemonChiffon1", RGB_(0xff, 0xfa, 0xcd) }, - { "LemonChiffon2", RGB_(0xee, 0xe9, 0xbf) }, - { "LemonChiffon3", RGB_(0xcd, 0xc9, 0xa5) }, - { "LemonChiffon4", RGB_(0x8b, 0x89, 0x70) }, - { "LightBlue", RGB_(0xad, 0xd8, 0xe6) }, - { "LightBlue1", RGB_(0xbf, 0xef, 0xff) }, - { "LightBlue2", RGB_(0xb2, 0xdf, 0xee) }, - { "LightBlue3", RGB_(0x9a, 0xc0, 0xcd) }, - { "LightBlue4", RGB_(0x68, 0x83, 0x8b) }, - { "LightCoral", RGB_(0xf0, 0x80, 0x80) }, - { "LightCyan", RGB_(0xe0, 0xff, 0xff) }, - { "LightCyan1", RGB_(0xe0, 0xff, 0xff) }, - { "LightCyan2", RGB_(0xd1, 0xee, 0xee) }, - { "LightCyan3", RGB_(0xb4, 0xcd, 0xcd) }, - { "LightCyan4", RGB_(0x7a, 0x8b, 0x8b) }, - { "LightGoldenrod", RGB_(0xee, 0xdd, 0x82) }, - { "LightGoldenrod1", RGB_(0xff, 0xec, 0x8b) }, - { "LightGoldenrod2", RGB_(0xee, 0xdc, 0x82) }, - { "LightGoldenrod3", RGB_(0xcd, 0xbe, 0x70) }, - { "LightGoldenrod4", RGB_(0x8b, 0x81, 0x4c) }, - { "LightGoldenRodYellow", RGB_(0xfa, 0xfa, 0xd2) }, - { "LightGray", RGB_(0xd3, 0xd3, 0xd3) }, - { "LightGreen", RGB_(0x90, 0xee, 0x90) }, - { "LightGrey", RGB_(0xd3, 0xd3, 0xd3) }, - { "LightMagenta", RGB_(0xff, 0xbb, 0xff) }, - { "LightPink", RGB_(0xff, 0xb6, 0xc1) }, - { "LightPink1", RGB_(0xff, 0xae, 0xb9) }, - { "LightPink2", RGB_(0xee, 0xa2, 0xad) }, - { "LightPink3", RGB_(0xcd, 0x8c, 0x95) }, - { "LightPink4", RGB_(0x8b, 0x5f, 0x65) }, - { "LightRed", RGB_(0xff, 0xbb, 0xbb) }, - { "LightSalmon", RGB_(0xff, 0xa0, 0x7a) }, - { "LightSalmon1", RGB_(0xff, 0xa0, 0x7a) }, - { "LightSalmon2", RGB_(0xee, 0x95, 0x72) }, - { "LightSalmon3", RGB_(0xcd, 0x81, 0x62) }, - { "LightSalmon4", RGB_(0x8b, 0x57, 0x42) }, - { "LightSeaGreen", RGB_(0x20, 0xb2, 0xaa) }, - { "LightSkyBlue", RGB_(0x87, 0xce, 0xfa) }, - { "LightSkyBlue1", RGB_(0xb0, 0xe2, 0xff) }, - { "LightSkyBlue2", RGB_(0xa4, 0xd3, 0xee) }, - { "LightSkyBlue3", RGB_(0x8d, 0xb6, 0xcd) }, - { "LightSkyBlue4", RGB_(0x60, 0x7b, 0x8b) }, - { "LightSlateBlue", RGB_(0x84, 0x70, 0xff) }, - { "LightSlateGray", RGB_(0x77, 0x88, 0x99) }, - { "LightSlateGrey", RGB_(0x77, 0x88, 0x99) }, - { "LightSteelBlue", RGB_(0xb0, 0xc4, 0xde) }, - { "LightSteelBlue1", RGB_(0xca, 0xe1, 0xff) }, - { "LightSteelBlue2", RGB_(0xbc, 0xd2, 0xee) }, - { "LightSteelBlue3", RGB_(0xa2, 0xb5, 0xcd) }, - { "LightSteelBlue4", RGB_(0x6e, 0x7b, 0x8b) }, - { "LightYellow", RGB_(0xff, 0xff, 0xe0) }, - { "LightYellow1", RGB_(0xff, 0xff, 0xe0) }, - { "LightYellow2", RGB_(0xee, 0xee, 0xd1) }, - { "LightYellow3", RGB_(0xcd, 0xcd, 0xb4) }, - { "LightYellow4", RGB_(0x8b, 0x8b, 0x7a) }, - { "Lime", RGB_(0x00, 0xff, 0x00) }, - { "LimeGreen", RGB_(0x32, 0xcd, 0x32) }, - { "Linen", RGB_(0xfa, 0xf0, 0xe6) }, - { "Magenta", RGB_(0xff, 0x00, 0xff) }, - { "Magenta1", RGB_(0xff, 0x0, 0xff) }, - { "Magenta2", RGB_(0xee, 0x0, 0xee) }, - { "Magenta3", RGB_(0xcd, 0x0, 0xcd) }, - { "Magenta4", RGB_(0x8b, 0x0, 0x8b) }, - { "Maroon", RGB_(0x80, 0x00, 0x00) }, - { "Maroon1", RGB_(0xff, 0x34, 0xb3) }, - { "Maroon2", RGB_(0xee, 0x30, 0xa7) }, - { "Maroon3", RGB_(0xcd, 0x29, 0x90) }, - { "Maroon4", RGB_(0x8b, 0x1c, 0x62) }, - { "MediumAquamarine", RGB_(0x66, 0xcd, 0xaa) }, - { "MediumBlue", RGB_(0x00, 0x00, 0xcd) }, - { "MediumOrchid", RGB_(0xba, 0x55, 0xd3) }, - { "MediumOrchid1", RGB_(0xe0, 0x66, 0xff) }, - { "MediumOrchid2", RGB_(0xd1, 0x5f, 0xee) }, - { "MediumOrchid3", RGB_(0xb4, 0x52, 0xcd) }, - { "MediumOrchid4", RGB_(0x7a, 0x37, 0x8b) }, - { "MediumPurple", RGB_(0x93, 0x70, 0xdb) }, - { "MediumPurple1", RGB_(0xab, 0x82, 0xff) }, - { "MediumPurple2", RGB_(0x9f, 0x79, 0xee) }, - { "MediumPurple3", RGB_(0x89, 0x68, 0xcd) }, - { "MediumPurple4", RGB_(0x5d, 0x47, 0x8b) }, - { "MediumSeaGreen", RGB_(0x3c, 0xb3, 0x71) }, - { "MediumSlateBlue", RGB_(0x7b, 0x68, 0xee) }, - { "MediumSpringGreen", RGB_(0x00, 0xfa, 0x9a) }, - { "MediumTurquoise", RGB_(0x48, 0xd1, 0xcc) }, - { "MediumVioletRed", RGB_(0xc7, 0x15, 0x85) }, - { "MidnightBlue", RGB_(0x19, 0x19, 0x70) }, - { "MintCream", RGB_(0xf5, 0xff, 0xfa) }, - { "MistyRose", RGB_(0xff, 0xe4, 0xe1) }, - { "MistyRose1", RGB_(0xff, 0xe4, 0xe1) }, - { "MistyRose2", RGB_(0xee, 0xd5, 0xd2) }, - { "MistyRose3", RGB_(0xcd, 0xb7, 0xb5) }, - { "MistyRose4", RGB_(0x8b, 0x7d, 0x7b) }, - { "Moccasin", RGB_(0xff, 0xe4, 0xb5) }, - { "NavajoWhite", RGB_(0xff, 0xde, 0xad) }, - { "NavajoWhite1", RGB_(0xff, 0xde, 0xad) }, - { "NavajoWhite2", RGB_(0xee, 0xcf, 0xa1) }, - { "NavajoWhite3", RGB_(0xcd, 0xb3, 0x8b) }, - { "NavajoWhite4", RGB_(0x8b, 0x79, 0x5e) }, - { "Navy", RGB_(0x00, 0x00, 0x80) }, - { "NavyBlue", RGB_(0x0, 0x0, 0x80) }, - { "OldLace", RGB_(0xfd, 0xf5, 0xe6) }, - { "Olive", RGB_(0x80, 0x80, 0x00) }, - { "OliveDrab", RGB_(0x6b, 0x8e, 0x23) }, - { "OliveDrab1", RGB_(0xc0, 0xff, 0x3e) }, - { "OliveDrab2", RGB_(0xb3, 0xee, 0x3a) }, - { "OliveDrab3", RGB_(0x9a, 0xcd, 0x32) }, - { "OliveDrab4", RGB_(0x69, 0x8b, 0x22) }, - { "Orange", RGB_(0xff, 0xa5, 0x00) }, - { "Orange1", RGB_(0xff, 0xa5, 0x0) }, - { "Orange2", RGB_(0xee, 0x9a, 0x0) }, - { "Orange3", RGB_(0xcd, 0x85, 0x0) }, - { "Orange4", RGB_(0x8b, 0x5a, 0x0) }, - { "OrangeRed", RGB_(0xff, 0x45, 0x00) }, - { "OrangeRed1", RGB_(0xff, 0x45, 0x0) }, - { "OrangeRed2", RGB_(0xee, 0x40, 0x0) }, - { "OrangeRed3", RGB_(0xcd, 0x37, 0x0) }, - { "OrangeRed4", RGB_(0x8b, 0x25, 0x0) }, - { "Orchid", RGB_(0xda, 0x70, 0xd6) }, - { "Orchid1", RGB_(0xff, 0x83, 0xfa) }, - { "Orchid2", RGB_(0xee, 0x7a, 0xe9) }, - { "Orchid3", RGB_(0xcd, 0x69, 0xc9) }, - { "Orchid4", RGB_(0x8b, 0x47, 0x89) }, - { "PaleGoldenRod", RGB_(0xee, 0xe8, 0xaa) }, - { "PaleGreen", RGB_(0x98, 0xfb, 0x98) }, - { "PaleGreen1", RGB_(0x9a, 0xff, 0x9a) }, - { "PaleGreen2", RGB_(0x90, 0xee, 0x90) }, - { "PaleGreen3", RGB_(0x7c, 0xcd, 0x7c) }, - { "PaleGreen4", RGB_(0x54, 0x8b, 0x54) }, - { "PaleTurquoise", RGB_(0xaf, 0xee, 0xee) }, - { "PaleTurquoise1", RGB_(0xbb, 0xff, 0xff) }, - { "PaleTurquoise2", RGB_(0xae, 0xee, 0xee) }, - { "PaleTurquoise3", RGB_(0x96, 0xcd, 0xcd) }, - { "PaleTurquoise4", RGB_(0x66, 0x8b, 0x8b) }, - { "PaleVioletRed", RGB_(0xdb, 0x70, 0x93) }, - { "PaleVioletRed1", RGB_(0xff, 0x82, 0xab) }, - { "PaleVioletRed2", RGB_(0xee, 0x79, 0x9f) }, - { "PaleVioletRed3", RGB_(0xcd, 0x68, 0x89) }, - { "PaleVioletRed4", RGB_(0x8b, 0x47, 0x5d) }, - { "PapayaWhip", RGB_(0xff, 0xef, 0xd5) }, - { "PeachPuff", RGB_(0xff, 0xda, 0xb9) }, - { "PeachPuff1", RGB_(0xff, 0xda, 0xb9) }, - { "PeachPuff2", RGB_(0xee, 0xcb, 0xad) }, - { "PeachPuff3", RGB_(0xcd, 0xaf, 0x95) }, - { "PeachPuff4", RGB_(0x8b, 0x77, 0x65) }, - { "Peru", RGB_(0xcd, 0x85, 0x3f) }, - { "Pink", RGB_(0xff, 0xc0, 0xcb) }, - { "Pink1", RGB_(0xff, 0xb5, 0xc5) }, - { "Pink2", RGB_(0xee, 0xa9, 0xb8) }, - { "Pink3", RGB_(0xcd, 0x91, 0x9e) }, - { "Pink4", RGB_(0x8b, 0x63, 0x6c) }, - { "Plum", RGB_(0xdd, 0xa0, 0xdd) }, - { "Plum1", RGB_(0xff, 0xbb, 0xff) }, - { "Plum2", RGB_(0xee, 0xae, 0xee) }, - { "Plum3", RGB_(0xcd, 0x96, 0xcd) }, - { "Plum4", RGB_(0x8b, 0x66, 0x8b) }, - { "PowderBlue", RGB_(0xb0, 0xe0, 0xe6) }, - { "Purple", RGB_(0x80, 0x00, 0x80) }, - { "Purple1", RGB_(0x9b, 0x30, 0xff) }, - { "Purple2", RGB_(0x91, 0x2c, 0xee) }, - { "Purple3", RGB_(0x7d, 0x26, 0xcd) }, - { "Purple4", RGB_(0x55, 0x1a, 0x8b) }, - { "RebeccaPurple", RGB_(0x66, 0x33, 0x99) }, - { "Red", RGB_(0xff, 0x00, 0x00) }, - { "Red1", RGB_(0xff, 0x0, 0x0) }, - { "Red2", RGB_(0xee, 0x0, 0x0) }, - { "Red3", RGB_(0xcd, 0x0, 0x0) }, - { "Red4", RGB_(0x8b, 0x0, 0x0) }, - { "RosyBrown", RGB_(0xbc, 0x8f, 0x8f) }, - { "RosyBrown1", RGB_(0xff, 0xc1, 0xc1) }, - { "RosyBrown2", RGB_(0xee, 0xb4, 0xb4) }, - { "RosyBrown3", RGB_(0xcd, 0x9b, 0x9b) }, - { "RosyBrown4", RGB_(0x8b, 0x69, 0x69) }, - { "RoyalBlue", RGB_(0x41, 0x69, 0xe1) }, - { "RoyalBlue1", RGB_(0x48, 0x76, 0xff) }, - { "RoyalBlue2", RGB_(0x43, 0x6e, 0xee) }, - { "RoyalBlue3", RGB_(0x3a, 0x5f, 0xcd) }, - { "RoyalBlue4", RGB_(0x27, 0x40, 0x8b) }, - { "SaddleBrown", RGB_(0x8b, 0x45, 0x13) }, - { "Salmon", RGB_(0xfa, 0x80, 0x72) }, - { "Salmon1", RGB_(0xff, 0x8c, 0x69) }, - { "Salmon2", RGB_(0xee, 0x82, 0x62) }, - { "Salmon3", RGB_(0xcd, 0x70, 0x54) }, - { "Salmon4", RGB_(0x8b, 0x4c, 0x39) }, - { "SandyBrown", RGB_(0xf4, 0xa4, 0x60) }, - { "SeaGreen", RGB_(0x2e, 0x8b, 0x57) }, - { "SeaGreen1", RGB_(0x54, 0xff, 0x9f) }, - { "SeaGreen2", RGB_(0x4e, 0xee, 0x94) }, - { "SeaGreen3", RGB_(0x43, 0xcd, 0x80) }, - { "SeaGreen4", RGB_(0x2e, 0x8b, 0x57) }, - { "SeaShell", RGB_(0xff, 0xf5, 0xee) }, - { "Seashell1", RGB_(0xff, 0xf5, 0xee) }, - { "Seashell2", RGB_(0xee, 0xe5, 0xde) }, - { "Seashell3", RGB_(0xcd, 0xc5, 0xbf) }, - { "Seashell4", RGB_(0x8b, 0x86, 0x82) }, - { "Sienna", RGB_(0xa0, 0x52, 0x2d) }, - { "Sienna1", RGB_(0xff, 0x82, 0x47) }, - { "Sienna2", RGB_(0xee, 0x79, 0x42) }, - { "Sienna3", RGB_(0xcd, 0x68, 0x39) }, - { "Sienna4", RGB_(0x8b, 0x47, 0x26) }, - { "Silver", RGB_(0xc0, 0xc0, 0xc0) }, - { "SkyBlue", RGB_(0x87, 0xce, 0xeb) }, - { "SkyBlue1", RGB_(0x87, 0xce, 0xff) }, - { "SkyBlue2", RGB_(0x7e, 0xc0, 0xee) }, - { "SkyBlue3", RGB_(0x6c, 0xa6, 0xcd) }, - { "SkyBlue4", RGB_(0x4a, 0x70, 0x8b) }, - { "SlateBlue", RGB_(0x6a, 0x5a, 0xcd) }, - { "SlateBlue1", RGB_(0x83, 0x6f, 0xff) }, - { "SlateBlue2", RGB_(0x7a, 0x67, 0xee) }, - { "SlateBlue3", RGB_(0x69, 0x59, 0xcd) }, - { "SlateBlue4", RGB_(0x47, 0x3c, 0x8b) }, - { "SlateGray", RGB_(0x70, 0x80, 0x90) }, - { "SlateGray1", RGB_(0xc6, 0xe2, 0xff) }, - { "SlateGray2", RGB_(0xb9, 0xd3, 0xee) }, - { "SlateGray3", RGB_(0x9f, 0xb6, 0xcd) }, - { "SlateGray4", RGB_(0x6c, 0x7b, 0x8b) }, - { "SlateGrey", RGB_(0x70, 0x80, 0x90) }, - { "Snow", RGB_(0xff, 0xfa, 0xfa) }, - { "Snow1", RGB_(0xff, 0xfa, 0xfa) }, - { "Snow2", RGB_(0xee, 0xe9, 0xe9) }, - { "Snow3", RGB_(0xcd, 0xc9, 0xc9) }, - { "Snow4", RGB_(0x8b, 0x89, 0x89) }, - { "SpringGreen", RGB_(0x00, 0xff, 0x7f) }, - { "SpringGreen1", RGB_(0x0, 0xff, 0x7f) }, - { "SpringGreen2", RGB_(0x0, 0xee, 0x76) }, - { "SpringGreen3", RGB_(0x0, 0xcd, 0x66) }, - { "SpringGreen4", RGB_(0x0, 0x8b, 0x45) }, - { "SteelBlue", RGB_(0x46, 0x82, 0xb4) }, - { "SteelBlue1", RGB_(0x63, 0xb8, 0xff) }, - { "SteelBlue2", RGB_(0x5c, 0xac, 0xee) }, - { "SteelBlue3", RGB_(0x4f, 0x94, 0xcd) }, - { "SteelBlue4", RGB_(0x36, 0x64, 0x8b) }, - { "Tan", RGB_(0xd2, 0xb4, 0x8c) }, - { "Tan1", RGB_(0xff, 0xa5, 0x4f) }, - { "Tan2", RGB_(0xee, 0x9a, 0x49) }, - { "Tan3", RGB_(0xcd, 0x85, 0x3f) }, - { "Tan4", RGB_(0x8b, 0x5a, 0x2b) }, - { "Teal", RGB_(0x00, 0x80, 0x80) }, - { "Thistle", RGB_(0xd8, 0xbf, 0xd8) }, - { "Thistle1", RGB_(0xff, 0xe1, 0xff) }, - { "Thistle2", RGB_(0xee, 0xd2, 0xee) }, - { "Thistle3", RGB_(0xcd, 0xb5, 0xcd) }, - { "Thistle4", RGB_(0x8b, 0x7b, 0x8b) }, - { "Tomato", RGB_(0xff, 0x63, 0x47) }, - { "Tomato1", RGB_(0xff, 0x63, 0x47) }, - { "Tomato2", RGB_(0xee, 0x5c, 0x42) }, - { "Tomato3", RGB_(0xcd, 0x4f, 0x39) }, - { "Tomato4", RGB_(0x8b, 0x36, 0x26) }, - { "Turquoise", RGB_(0x40, 0xe0, 0xd0) }, - { "Turquoise1", RGB_(0x0, 0xf5, 0xff) }, - { "Turquoise2", RGB_(0x0, 0xe5, 0xee) }, - { "Turquoise3", RGB_(0x0, 0xc5, 0xcd) }, - { "Turquoise4", RGB_(0x0, 0x86, 0x8b) }, - { "Violet", RGB_(0xee, 0x82, 0xee) }, - { "VioletRed", RGB_(0xd0, 0x20, 0x90) }, - { "VioletRed1", RGB_(0xff, 0x3e, 0x96) }, - { "VioletRed2", RGB_(0xee, 0x3a, 0x8c) }, - { "VioletRed3", RGB_(0xcd, 0x32, 0x78) }, - { "VioletRed4", RGB_(0x8b, 0x22, 0x52) }, - { "WebGray", RGB_(0x80, 0x80, 0x80) }, - { "WebGreen", RGB_(0x0, 0x80, 0x0) }, - { "WebGrey", RGB_(0x80, 0x80, 0x80) }, - { "WebMaroon", RGB_(0x80, 0x0, 0x0) }, - { "WebPurple", RGB_(0x80, 0x0, 0x80) }, - { "Wheat", RGB_(0xf5, 0xde, 0xb3) }, - { "Wheat1", RGB_(0xff, 0xe7, 0xba) }, - { "Wheat2", RGB_(0xee, 0xd8, 0xae) }, - { "Wheat3", RGB_(0xcd, 0xba, 0x96) }, - { "Wheat4", RGB_(0x8b, 0x7e, 0x66) }, - { "White", RGB_(0xff, 0xff, 0xff) }, - { "WhiteSmoke", RGB_(0xf5, 0xf5, 0xf5) }, - { "X11Gray", RGB_(0xbe, 0xbe, 0xbe) }, - { "X11Green", RGB_(0x0, 0xff, 0x0) }, - { "X11Grey", RGB_(0xbe, 0xbe, 0xbe) }, - { "X11Maroon", RGB_(0xb0, 0x30, 0x60) }, - { "X11Purple", RGB_(0xa0, 0x20, 0xf0) }, - { "Yellow", RGB_(0xff, 0xff, 0x00) }, - { "Yellow1", RGB_(0xff, 0xff, 0x0) }, - { "Yellow2", RGB_(0xee, 0xee, 0x0) }, - { "Yellow3", RGB_(0xcd, 0xcd, 0x0) }, - { "Yellow4", RGB_(0x8b, 0x8b, 0x0) }, - { "YellowGreen", RGB_(0x9a, 0xcd, 0x32) }, - { NULL, 0 }, -}; - - -/// Translate to RgbValue if \p name is an hex value (e.g. #XXXXXX), -/// else look into color_name_table to translate a color name to its -/// hex value -/// -/// @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 *name) -{ - if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) - && isxdigit(name[3]) && isxdigit(name[4]) && isxdigit(name[5]) - && isxdigit(name[6]) && name[7] == NUL) { - // rgb hex string - return strtol((char *)(name + 1), NULL, 16); - } else if (!STRICMP(name, "bg") || !STRICMP(name, "background")) { - return normal_bg; - } else if (!STRICMP(name, "fg") || !STRICMP(name, "foreground")) { - return normal_fg; - } - - for (int i = 0; color_name_table[i].name != NULL; i++) { - if (!STRICMP(name, color_name_table[i].name)) { - return color_name_table[i].color; - } - } - - return -1; -} - - -/************************************** -* End of Highlighting stuff * -**************************************/ diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index 15fc084a0a..0d890314c5 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -29,12 +29,6 @@ #define SYN_GROUP_STATIC(s) syn_check_group(S_LEN(s)) -typedef struct { - char *name; - RgbValue color; -} color_name_table_T; -extern color_name_table_T color_name_table[]; - /// Array of highlight definitions, used for unit testing extern const char *const highlight_init_cmdline[]; diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h index 526be905e9..c656f21181 100644 --- a/src/nvim/syntax_defs.h +++ b/src/nvim/syntax_defs.h @@ -7,7 +7,7 @@ #define SST_MAX_ENTRIES 1000 // maximal size for state stack array #define SST_FIX_STATES 7 // size of sst_stack[]. #define SST_DIST 16 // normal distance between entries -#define SST_INVALID (synstate_T *)-1 // invalid syn_state pointer +#define SST_INVALID ((synstate_T *)-1) // invalid syn_state pointer typedef struct syn_state synstate_T; @@ -21,9 +21,7 @@ struct sp_syn { int16_t *cont_in_list; // cont.in group IDs, if non-zero }; -/* - * Each keyword has one keyentry, which is linked in a hash list. - */ +// Each keyword has one keyentry, which is linked in a hash list. typedef struct keyentry keyentry_T; struct keyentry { @@ -35,9 +33,7 @@ struct keyentry { char_u keyword[1]; // actually longer }; -/* - * Struct used to store one state of the state stack. - */ +// Struct used to store one state of the state stack. typedef struct buf_state { int bs_idx; // index of pattern int bs_flags; // flags for pattern @@ -46,10 +42,8 @@ typedef struct buf_state { reg_extmatch_T *bs_extmatch; // external matches from start pattern } bufstate_T; -/* - * syn_state contains the syntax state stack for the start of one line. - * Used by b_sst_array[]. - */ +// syn_state contains the syntax state stack for the start of one line. +// Used by b_sst_array[]. struct syn_state { synstate_T *sst_next; // next entry in used or free list linenr_T sst_lnum; // line number for this state @@ -66,4 +60,4 @@ struct syn_state { // may have made the state invalid }; -#endif // NVIM_SYNTAX_DEFS_H +#endif // NVIM_SYNTAX_DEFS_H diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 1f4d3adc92..32d72218c8 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -25,11 +25,11 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/if_cscope.h" +#include "nvim/input.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -141,12 +141,12 @@ static int tfu_in_use = false; // disallow recursive call of tagfunc /// type == DT_LTAG: use location list for displaying tag matches /// type == DT_FREE: free cached matches /// -/// for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise +/// for cscope, returns true if we jumped to tag or aborted, false otherwise /// /// @param tag tag (pattern) to jump to /// @param forceit :ta with ! /// @param verbose print "tag not found" message -int do_tag(char_u *tag, int type, int count, int forceit, int verbose) +bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) { taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; @@ -163,7 +163,7 @@ int do_tag(char_u *tag, int type, int count, int forceit, int verbose) int error_cur_match = 0; int save_pos = false; fmark_T saved_fmark; - int jumped_to_tag = false; + bool jumped_to_tag = false; int new_num_matches; char_u **new_matches; int use_tagstack; @@ -2960,7 +2960,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) } else { RedrawingDisabled--; if (postponed_split) { // close the window - win_close(curwin, false); + win_close(curwin, false, false); postponed_split = 0; } } @@ -3408,7 +3408,7 @@ static void tagstack_push_items(win_T *wp, list_T *l) if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) { continue; } - if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) { + if (list2fpos(&di->di_tv, &mark, &fnum, NULL, false) != OK) { continue; } if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true)) diff --git a/src/nvim/tag.h b/src/nvim/tag.h index 64bacceb1b..902fe0c7ba 100644 --- a/src/nvim/tag.h +++ b/src/nvim/tag.h @@ -4,9 +4,7 @@ #include "nvim/ex_cmds_defs.h" #include "nvim/types.h" -/* - * Values for do_tag(). - */ +// Values for do_tag(). #define DT_TAG 1 // jump to newer position or same tag again #define DT_POP 2 // jump to older position #define DT_NEXT 3 // jump to next match of same tag @@ -20,9 +18,7 @@ #define DT_LTAG 11 // tag using location list #define DT_FREE 99 // free cached matches -// // flags for find_tags(). -// #define TAG_HELP 1 // only search for help tags #define TAG_NAMES 2 // only return name of tag #define TAG_REGEXP 4 // use tag pattern as regexp @@ -36,9 +32,7 @@ #define TAG_MANY 300 // When finding many tags (for completion), // find up to this many tags -/* - * Structure used for get_tagfname(). - */ +// Structure used for get_tagfname(). typedef struct { char_u *tn_tags; // value of 'tags' when starting char_u *tn_np; // current position in tn_tags diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 35c68fa1f6..fd870361c7 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -46,6 +46,7 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/change.h" +#include "nvim/cursor.h" #include "nvim/edit.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" @@ -54,6 +55,7 @@ #include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/keymap.h" #include "nvim/log.h" #include "nvim/macros.h" @@ -63,14 +65,12 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/screen.h" #include "nvim/state.h" -#include "nvim/syntax.h" #include "nvim/terminal.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -135,7 +135,6 @@ struct terminal { int row, col; bool visible; } cursor; - int pressed_button; // which mouse button is pressed bool pending_resize; // pending width/height bool color_set[16]; @@ -173,6 +172,11 @@ void terminal_teardown(void) pmap_init(ptr_t, &invalidated_terminals); } +static void term_output_callback(const char *s, size_t len, void *user_data) +{ + terminal_send((Terminal *)user_data, (char *)s, len); +} + // public API {{{ Terminal *terminal_open(buf_T *buf, TerminalOptions opts) @@ -196,6 +200,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts) vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv); vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL); vterm_screen_reset(rv->vts, 1); + vterm_output_set_callback(rv->vt, term_output_callback, rv); // force a initial refresh of the screen to ensure the buffer will always // have as many lines as screen rows when refresh_scrollback is called rv->invalid_start = 0; @@ -312,10 +317,14 @@ void terminal_close(Terminal *term, int status) term->opts.close_cb(term->opts.data); } } else if (!only_destroy) { - // This was called by channel_process_exit_cb() not in process_teardown(). + // Associated channel has been closed and the editor is not exiting. // Do not call the close callback now. Wait for the user to press a key. char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN]; - snprintf(msg, sizeof msg, "\r\n[Process exited %d]", status); + if (((Channel *)term->opts.data)->streamtype == kChannelStreamInternal) { + snprintf(msg, sizeof msg, "\r\n[Terminal closed]"); + } else { + snprintf(msg, sizeof msg, "\r\n[Process exited %d]", status); + } terminal_receive(term, msg, strlen(msg)); } @@ -324,10 +333,12 @@ void terminal_close(Terminal *term, int status) } if (buf && !is_autocmd_blocked()) { - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); tv_dict_add_nr(dict, S_LEN("status"), status); + tv_dict_set_keys_readonly(dict); apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, buf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); } } @@ -412,6 +423,7 @@ void terminal_enter(void) curwin->w_redr_status = true; // For mode() in statusline. #8323 ui_busy_start(); apply_autocmds(EVENT_TERMENTER, NULL, NULL, false, curbuf); + may_trigger_modechanged(); s->state.execute = terminal_execute; s->state.check = terminal_check; @@ -462,9 +474,7 @@ static void terminal_check_cursor(void) row_to_linenr(term, term->cursor.row)); // Nudge cursor when returning to normal-mode. int off = is_focused(term) ? 0 : (curwin->w_p_rl ? 1 : -1); - curwin->w_cursor.col = MAX(0, term->cursor.col + win_col_off(curwin) + off); - curwin->w_cursor.coladd = 0; - mb_check_adjust_col(curwin); + coladvance(MAX(0, term->cursor.col + off)); } // Function executed before each iteration of terminal mode. @@ -529,6 +539,10 @@ static int terminal_execute(VimState *state, int key) do_cmdline(NULL, getcmdkeycmd, NULL, 0); break; + case K_LUA: + map_execute_lua(); + break; + case Ctrl_N: if (s->got_bsl) { return 0; @@ -633,7 +647,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size) return; } vterm_keyboard_start_paste(curbuf->terminal->vt); - terminal_flush_output(curbuf->terminal); size_t buff_len = STRLEN(y_array[0]); char_u *buff = xmalloc(buff_len); for (int i = 0; i < count; i++) { // -V756 @@ -664,14 +677,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size) } xfree(buff); vterm_keyboard_end_paste(curbuf->terminal->vt); - terminal_flush_output(curbuf->terminal); -} - -void terminal_flush_output(Terminal *term) -{ - size_t len = vterm_output_read(term->vt, term->textbuf, - sizeof(term->textbuf)); - terminal_send(term, term->textbuf, len); } void terminal_send_key(Terminal *term, int c) @@ -690,8 +695,6 @@ void terminal_send_key(Terminal *term, int c) } else { vterm_keyboard_unichar(term->vt, (uint32_t)c, mod); } - - terminal_flush_output(term); } void terminal_receive(Terminal *term, char *data, size_t len) @@ -1203,27 +1206,70 @@ static VTermKey convert_key(int key, VTermModifier *statep) return VTERM_KEY_FUNCTION(36); case K_F37: return VTERM_KEY_FUNCTION(37); + case K_F38: + return VTERM_KEY_FUNCTION(38); + case K_F39: + return VTERM_KEY_FUNCTION(39); + case K_F40: + return VTERM_KEY_FUNCTION(40); + case K_F41: + return VTERM_KEY_FUNCTION(41); + case K_F42: + return VTERM_KEY_FUNCTION(42); + case K_F43: + return VTERM_KEY_FUNCTION(43); + case K_F44: + return VTERM_KEY_FUNCTION(44); + case K_F45: + return VTERM_KEY_FUNCTION(45); + case K_F46: + return VTERM_KEY_FUNCTION(46); + case K_F47: + return VTERM_KEY_FUNCTION(47); + case K_F48: + return VTERM_KEY_FUNCTION(48); + case K_F49: + return VTERM_KEY_FUNCTION(49); + case K_F50: + return VTERM_KEY_FUNCTION(50); + case K_F51: + return VTERM_KEY_FUNCTION(51); + case K_F52: + return VTERM_KEY_FUNCTION(52); + case K_F53: + return VTERM_KEY_FUNCTION(53); + case K_F54: + return VTERM_KEY_FUNCTION(54); + case K_F55: + return VTERM_KEY_FUNCTION(55); + case K_F56: + return VTERM_KEY_FUNCTION(56); + case K_F57: + return VTERM_KEY_FUNCTION(57); + case K_F58: + return VTERM_KEY_FUNCTION(58); + case K_F59: + return VTERM_KEY_FUNCTION(59); + case K_F60: + return VTERM_KEY_FUNCTION(60); + case K_F61: + return VTERM_KEY_FUNCTION(61); + case K_F62: + return VTERM_KEY_FUNCTION(62); + case K_F63: + return VTERM_KEY_FUNCTION(63); default: return VTERM_KEY_NONE; } } -static void mouse_action(Terminal *term, int button, int row, int col, bool drag, VTermModifier mod) +static void mouse_action(Terminal *term, int button, int row, int col, bool pressed, + VTermModifier mod) { - if (term->pressed_button && (term->pressed_button != button || !drag)) { - // release the previous button - vterm_mouse_button(term->vt, term->pressed_button, 0, mod); - term->pressed_button = 0; - } - - // move the mouse vterm_mouse_move(term->vt, row, col, mod); - - if (!term->pressed_button) { - // press the button if not already pressed - vterm_mouse_button(term->vt, button, 1, mod); - term->pressed_button = button; + if (button) { + vterm_mouse_button(term->vt, button, pressed, mod); } } @@ -1242,35 +1288,35 @@ static bool send_mouse_event(Terminal *term, int c) // event in the terminal window and mouse events was enabled by the // program. translate and forward the event int button; - bool drag = false; + bool pressed = false; switch (c) { case K_LEFTDRAG: - drag = true; FALLTHROUGH; case K_LEFTMOUSE: + pressed = true; FALLTHROUGH; + case K_LEFTRELEASE: button = 1; break; case K_MOUSEMOVE: - drag = true; button = 0; break; + button = 0; break; case K_MIDDLEDRAG: - drag = true; FALLTHROUGH; case K_MIDDLEMOUSE: + pressed = true; FALLTHROUGH; + case K_MIDDLERELEASE: button = 2; break; case K_RIGHTDRAG: - drag = true; FALLTHROUGH; case K_RIGHTMOUSE: + pressed = true; FALLTHROUGH; + case K_RIGHTRELEASE: button = 3; break; case K_MOUSEDOWN: - button = 4; break; + pressed = true; button = 4; break; case K_MOUSEUP: - button = 5; break; + pressed = true; button = 5; break; default: return false; } - mouse_action(term, button, row, col - offset, drag, 0); - size_t len = vterm_output_read(term->vt, term->textbuf, - sizeof(term->textbuf)); - terminal_send(term, term->textbuf, len); + mouse_action(term, button, row, col - offset, pressed, 0); return false; } @@ -1295,8 +1341,14 @@ static bool send_mouse_event(Terminal *term, int c) return mouse_win == curwin; } + // ignore left release action if it was not processed above + // to prevent leaving Terminal mode after entering to it using a mouse + if (c == K_LEFTRELEASE && mouse_win->w_buffer->terminal == term) { + return false; + } + end: - ins_char_typebuf(c); + ins_char_typebuf(c, mod_mask); return true; } @@ -1466,6 +1518,17 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) int width, height; vterm_get_size(term->vt, &height, &width); + // May still have pending scrollback after increase in terminal height if the + // scrollback wasn't refreshed in time; append these to the top of the buffer. + int row_offset = term->sb_pending; + while (term->sb_pending > 0 && buf->b_ml.ml_line_count < height) { + fetch_row(term, term->sb_pending - row_offset - 1, width); + ml_append(0, (uint8_t *)term->textbuf, 0, false); + appended_lines(0, 1); + term->sb_pending--; + } + + row_offset -= term->sb_pending; while (term->sb_pending > 0) { // This means that either the window height has decreased or the screen // became full and libvterm had to push all rows up. Convert the first @@ -1476,7 +1539,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) ml_delete(1, false); deleted_lines(1, 1); } - fetch_row(term, -term->sb_pending, width); + fetch_row(term, -term->sb_pending - row_offset, width); int buf_index = (int)buf->b_ml.ml_line_count - height; ml_append(buf_index, (uint8_t *)term->textbuf, 0, false); appended_lines(buf_index, 1); @@ -1506,6 +1569,13 @@ static void refresh_screen(Terminal *term, buf_T *buf) // Terminal height may have decreased before `invalid_end` reflects it. term->invalid_end = MIN(term->invalid_end, height); + // There are no invalid rows. + if (term->invalid_start >= term->invalid_end) { + term->invalid_start = INT_MAX; + term->invalid_end = -1; + return; + } + for (int r = term->invalid_start, linenr = row_to_linenr(term, r); r < term->invalid_end; r++, linenr++) { fetch_row(term, r, width); diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 14bab33a2f..8f97d959ce 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -12,9 +12,9 @@ endfunc " Command to check for the absence of a feature. command -nargs=1 CheckNotFeature call CheckNotFeature(<f-args>) func CheckNotFeature(name) - if !has(a:name, 1) - throw 'Checking for non-existent feature ' .. a:name - endif + " if !has(a:name, 1) + " throw 'Checking for non-existent feature ' .. a:name + " endif if has(a:name) throw 'Skipped: ' .. a:name .. ' feature present' endif @@ -63,6 +63,15 @@ func CheckUnix() endif endfunc +" Command to check for not running on a BSD system. +" TODO: using this checks should not be needed +command CheckNotBSD call CheckNotBSD() +func CheckNotBSD() + if has('bsd') + throw 'Skipped: does not work on BSD' + endif +endfunc + " Command to check that making screendumps is supported. " Caller must source screendump.vim command CheckScreendump call CheckScreendump() @@ -104,6 +113,14 @@ func CheckNotGui() endif endfunc +" Command to check that test is not running as root +command CheckNotRoot call CheckNotRoot() +func CheckNotRoot() + if IsRoot() + throw 'Skipped: cannot run test as root' + endif +endfunc + " Command to check that the current language is English command CheckEnglish call CheckEnglish() func CheckEnglish() @@ -120,6 +137,14 @@ func CheckNotMSWindows() endif endfunc +" Command to check for not running under ASAN +command CheckNotAsan call CheckNotAsan() +func CheckNotAsan() + if execute('version') =~# '-fsanitize=[a-z,]*\<address\>' + throw 'Skipped: does not work with ASAN' + endif +endfunc + " Command to check for satisfying any of the conditions. " e.g. CheckAnyOf Feature:bsd Feature:sun Linux command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>) diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh index 25cb8437b4..322265737a 100755 --- a/src/nvim/testdir/runnvim.sh +++ b/src/nvim/testdir/runnvim.sh @@ -38,7 +38,7 @@ main() {( -S runnvim.vim \ "$tlog" > "out-$tlog" 2> "err-$tlog" then - fail "$test_name" F "Nvim exited with non-zero code" + fail "$test_name" "Nvim exited with non-zero code" fi { echo "Stdout of :terminal runner" @@ -53,7 +53,7 @@ main() {( if test "$oldesttest" = 1 ; then if ! diff -q test.out "$test_name.ok" > /dev/null 2>&1 ; then if test -f test.out ; then - fail "$test_name" F "Oldest test .out file differs from .ok file" + fail "$test_name" "Oldest test .out file differs from .ok file" { echo "Diff between test.out and $test_name.ok" echo "$separator" @@ -65,9 +65,6 @@ main() {( fi fi fi - if test "$FAILED" = 1 ; then - ci_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name" - fi valgrind_check . if test -n "$LOG_DIR" ; then check_sanitizer "$LOG_DIR" @@ -78,9 +75,6 @@ main() {( fi rm -f "$tlog" if test "$FAILED" = 1 ; then - 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 # When Neovim crashed/aborted it might not have created messages. # test.log itself is used as an indicator to exit non-zero in the Makefile. diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 49993c03aa..b0d872e392 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -64,6 +64,9 @@ if has('reltime') let s:start_time = reltime() endif +" Always use forward slashes. +set shellslash + " Common with all tests on all systems. source setup.vim @@ -104,9 +107,6 @@ lang mess C " Nvim: append runtime from build dir, which contains the generated doc/tags. 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') @@ -197,7 +197,12 @@ func RunTheTest(test) " Close any extra tab pages and windows and make the current one not modified. while tabpagenr('$') > 1 + let winid = win_getid() quit! + if winid == win_getid() + echoerr 'Could not quit window' + break + endif endwhile while 1 diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index b3df8c63e6..15e3b31498 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -10,10 +10,11 @@ let s:did_load = 1 set backspace= set directory^=. set fillchars=vert:\|,fold:- +set fsync set laststatus=1 set listchars=eol:$ set joinspaces -set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd +set nohidden nosmarttab noautoindent noautoread complete-=i noruler noshowcmd set nrformats+=octal set shortmess-=F set sidescroll=0 diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index f456ff4250..c2809844ac 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -343,6 +343,15 @@ func RunVimPiped(before, after, arguments, pipecmd) return 1 endfunc +func IsRoot() + if !has('unix') + return v:false + elseif $USER == 'root' || system('id -un') =~ '\<root\>' + return v:true + endif + return v:false +endfunc + " Get all messages but drop the maintainer entry. func GetMessages() redir => result diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index cc767a9bcf..43a519bc84 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -3,54 +3,32 @@ source test_backup.vim source test_behave.vim -source test_cd.vim -source test_changedtick.vim source test_compiler.vim -source test_cursor_func.vim -source test_cursorline.vim source test_ex_equal.vim source test_ex_undo.vim source test_ex_z.vim source test_ex_mode.vim -source test_execute_func.vim +source test_expand.vim source test_expand_func.vim source test_feedkeys.vim -source test_filter_cmd.vim -source test_filter_map.vim -source test_findfile.vim -source test_float_func.vim -source test_functions.vim +source test_file_perm.vim +source test_fnamemodify.vim source test_ga.vim +source test_glob2regpat.vim source test_global.vim -source test_goto.vim -source test_join.vim -source test_jumps.vim -source test_fileformat.vim -source test_filetype.vim -source test_lambda.vim +source test_lispwords.vim source test_menu.vim -source test_messages.vim -source test_modeline.vim source test_move.vim -source test_partial.vim -source test_popup.vim source test_put.vim -source test_rename.vim +source test_reltime.vim source test_scroll_opt.vim +source test_searchpos.vim +source test_set.vim source test_shift.vim -source test_sort.vim source test_sha256.vim -source test_suspend.vim -source test_syn_attr.vim source test_tabline.vim -source test_tabpage.vim source test_tagcase.vim source test_tagfunc.vim -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_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim index 70f14320a6..77f5ede4c8 100644 --- a/src/nvim/testdir/test_alot_utf8.vim +++ b/src/nvim/testdir/test_alot_utf8.vim @@ -6,7 +6,6 @@ source test_charsearch_utf8.vim source test_expr_utf8.vim -source test_matchadd_conceal_utf8.vim source test_mksession_utf8.vim source test_regexp_utf8.vim source test_source_utf8.vim diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 52f243aaea..28c5948142 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -1,5 +1,55 @@ " Test that the methods used for testing work. +func Test_assert_false() + call assert_equal(0, assert_false(0)) + call assert_equal(0, assert_false(v:false)) + call assert_equal(0, v:false->assert_false()) + + call assert_equal(1, assert_false(123)) + call assert_match("Expected False but got 123", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, 123->assert_false()) + call assert_match("Expected False but got 123", v:errors[0]) + call remove(v:errors, 0) +endfunc + +func Test_assert_true() + call assert_equal(0, assert_true(1)) + call assert_equal(0, assert_true(123)) + call assert_equal(0, assert_true(v:true)) + call assert_equal(0, v:true->assert_true()) + + call assert_equal(1, assert_true(0)) + call assert_match("Expected True but got 0", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, 0->assert_true()) + call assert_match("Expected True but got 0", v:errors[0]) + call remove(v:errors, 0) +endfunc + +func Test_assert_equal() + let s = 'foo' + call assert_equal(0, assert_equal('foo', s)) + let n = 4 + call assert_equal(0, assert_equal(4, n)) + let l = [1, 2, 3] + call assert_equal(0, assert_equal([1, 2, 3], l)) + call assert_equal(v:_null_list, v:_null_list) + call assert_equal(v:_null_list, []) + call assert_equal([], v:_null_list) + + let s = 'foo' + call assert_equal(1, assert_equal('bar', s)) + call assert_match("Expected 'bar' but got 'foo'", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal('XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX') + call assert_match("Expected 'X\\\\\\[x occurs 21 times]X' but got 'X\\\\\\[y occurs 25 times]X'", v:errors[0]) + call remove(v:errors, 0) +endfunc + func Test_assert_equalfile() call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz')) call assert_match("E485: Can't read file abcabc", v:errors[0]) @@ -46,17 +96,129 @@ func Test_assert_equalfile() call delete('Xtwo') endfunc +func Test_assert_notequal() + let n = 4 + call assert_equal(0, assert_notequal('foo', n)) + let s = 'foo' + call assert_equal(0, assert_notequal([1, 2, 3], s)) + + call assert_equal(1, assert_notequal('foo', s)) + call assert_match("Expected not equal to 'foo'", v:errors[0]) + call remove(v:errors, 0) +endfunc + +func Test_assert_report() + call assert_equal(1, assert_report('something is wrong')) + call assert_match('something is wrong', v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, 'also wrong'->assert_report()) + call assert_match('also wrong', v:errors[0]) + call remove(v:errors, 0) +endfunc + +func Test_assert_exception() + try + nocommand + catch + call assert_equal(0, assert_exception('E492:')) + endtry + + try + nocommand + catch + try + " illegal argument, get NULL for error + call assert_equal(1, assert_exception([])) + catch + call assert_equal(0, assert_exception('E730:')) + endtry + endtry +endfunc + +func Test_wrong_error_type() + let save_verrors = v:errors + let v:['errors'] = {'foo': 3} + call assert_equal('yes', 'no') + let verrors = v:errors + let v:errors = save_verrors + call assert_equal(type([]), type(verrors)) +endfunc + +func Test_match() + call assert_equal(0, assert_match('^f.*b.*r$', 'foobar')) + + call assert_equal(1, assert_match('bar.*foo', 'foobar')) + call assert_match("Pattern 'bar.*foo' does not match 'foobar'", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_match('bar.*foo', 'foobar', 'wrong')) + call assert_match('wrong', v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, 'foobar'->assert_match('bar.*foo', 'wrong')) + call assert_match('wrong', v:errors[0]) + call remove(v:errors, 0) +endfunc + +func Test_notmatch() + call assert_equal(0, assert_notmatch('foo', 'bar')) + call assert_equal(0, assert_notmatch('^foobar$', 'foobars')) + + call assert_equal(1, assert_notmatch('foo', 'foobar')) + call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, 'foobar'->assert_notmatch('foo')) + call assert_match("Pattern 'foo' does match 'foobar'", v:errors[0]) + call remove(v:errors, 0) +endfunc + +func Test_assert_fail_fails() + call assert_equal(1, assert_fails('xxx', 'E12345')) + call assert_match("Expected 'E12345' but got 'E492:", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_fails('xxx', 'E9876', 'stupid')) + call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_fails('echo', '', 'echo command')) + call assert_match("command did not fail: echo command", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, 'echo'->assert_fails('', 'echo command')) + call assert_match("command did not fail: echo command", v:errors[0]) + call remove(v:errors, 0) +endfunc + func Test_assert_fails_in_try_block() try call assert_equal(0, assert_fails('throw "error"')) endtry endfunc +func Test_assert_beeps() + new + call assert_equal(0, assert_beeps('normal h')) + + call assert_equal(1, assert_beeps('normal 0')) + call assert_match("command did not beep: normal 0", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(0, 'normal h'->assert_beeps()) + call assert_equal(1, 'normal 0'->assert_beeps()) + call assert_match("command did not beep: normal 0", v:errors[0]) + call remove(v:errors, 0) + + bwipe +endfunc + func Test_assert_inrange() call assert_equal(0, assert_inrange(7, 7, 7)) call assert_equal(0, assert_inrange(5, 7, 5)) call assert_equal(0, assert_inrange(5, 7, 6)) call assert_equal(0, assert_inrange(5, 7, 7)) + call assert_equal(1, assert_inrange(5, 7, 4)) call assert_match("Expected range 5 - 7, but got 4", v:errors[0]) call remove(v:errors, 0) @@ -64,6 +226,12 @@ func Test_assert_inrange() call assert_match("Expected range 5 - 7, but got 8", v:errors[0]) call remove(v:errors, 0) + call assert_equal(0, 5->assert_inrange(5, 7)) + call assert_equal(0, 7->assert_inrange(5, 7)) + call assert_equal(1, 8->assert_inrange(5, 7)) + call assert_match("Expected range 5 - 7, but got 8", v:errors[0]) + call remove(v:errors, 0) + call assert_fails('call assert_inrange(1, 1)', 'E119:') if has('float') @@ -83,6 +251,12 @@ func Test_assert_inrange() endif endfunc +func Test_assert_with_msg() + call assert_equal('foo', 'bar', 'testing') + call assert_match("testing: Expected 'foo' but got 'bar'", v:errors[0]) + call remove(v:errors, 0) +endfunc + " Must be last. func Test_zz_quit_detected() " Verify that if a test function ends Vim the test script detects this. diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 0b76828dd7..4229095f9f 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -26,4 +26,107 @@ func Test_set_filename() call delete('samples/Xtest') endfunc +func Test_set_filename_other_window() + CheckFunction test_autochdir + let cwd = getcwd() + call test_autochdir() + call mkdir('Xa') + call mkdir('Xb') + call mkdir('Xc') + try + args Xa/aaa.txt Xb/bbb.txt + set acd + let winid = win_getid() + snext + call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', '')) + call win_execute(winid, 'file ' .. cwd .. '/Xc/ccc.txt') + call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', '')) + finally + set noacd + call chdir(cwd) + call delete('Xa', 'rf') + call delete('Xb', 'rf') + call delete('Xc', 'rf') + bwipe! aaa.txt + bwipe! bbb.txt + bwipe! ccc.txt + endtry +endfunc + +func Test_acd_win_execute() + CheckFunction test_autochdir + let cwd = getcwd() + set acd + call test_autochdir() + + call mkdir('Xfile') + let winid = win_getid() + new Xfile/file + call assert_match('testdir.Xfile$', getcwd()) + cd .. + call assert_match('testdir$', getcwd()) + call win_execute(winid, 'echo') + call assert_match('testdir$', getcwd()) + + bwipe! + set noacd + call chdir(cwd) + call delete('Xfile', 'rf') +endfunc + +func Test_verbose_pwd() + CheckFunction test_autochdir + let cwd = getcwd() + call test_autochdir() + + edit global.txt + call assert_match('\[global\].*testdir$', execute('verbose pwd')) + + call mkdir('Xautodir') + split Xautodir/local.txt + lcd Xautodir + call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd')) + + set acd + wincmd w + call assert_match('\[autochdir\].*testdir$', execute('verbose pwd')) + execute 'tcd' cwd + call assert_match('\[tabpage\].*testdir$', execute('verbose pwd')) + execute 'cd' cwd + call assert_match('\[global\].*testdir$', execute('verbose pwd')) + execute 'lcd' cwd + call assert_match('\[window\].*testdir$', execute('verbose pwd')) + edit + call assert_match('\[autochdir\].*testdir$', execute('verbose pwd')) + enew + wincmd w + call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd')) + wincmd w + call assert_match('\[window\].*testdir$', execute('verbose pwd')) + wincmd w + call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd')) + set noacd + call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd')) + wincmd w + call assert_match('\[window\].*testdir$', execute('verbose pwd')) + execute 'cd' cwd + call assert_match('\[global\].*testdir$', execute('verbose pwd')) + wincmd w + call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd')) + + bwipe! + call chdir(cwd) + call delete('Xautodir', 'rf') +endfunc + +func Test_multibyte() + " using an invalid character should not cause a crash + set wic + " Except on Windows, E472 is also thrown last, but v8.1.1183 isn't ported yet + " call assert_fails('tc û¦*', has('win32') ? 'E480:' : 'E344:') + call assert_fails('tc û¦*', has('win32') ? 'E480:' : 'E472:') + set nowic +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 0c8b8a45d9..228145ec4d 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -3,8 +3,9 @@ source shared.vim source check.vim source term_util.vim +source screendump.vim -func! s:cleanup_buffers() abort +func s:cleanup_buffers() abort for bnr in range(1, bufnr('$')) if bufloaded(bnr) && bufnr('%') != bnr execute 'bd! ' . bnr @@ -33,7 +34,7 @@ if has('timers') let g:triggered = 0 au CursorHoldI * let g:triggered += 1 set updatetime=20 - call timer_start(LoadAdjust(100), 'ExitInsertMode') + call timer_start(LoadAdjust(200), 'ExitInsertMode') call feedkeys('a', 'x!') call assert_equal(1, g:triggered) unlet g:triggered @@ -260,6 +261,84 @@ func Test_win_tab_autocmd() unlet g:record endfunc +func Test_WinScrolled() + CheckRunVimInTerminal + + let lines =<< trim END + set nowrap scrolloff=0 + for ii in range(1, 18) + call setline(ii, repeat(nr2char(96 + ii), ii * 2)) + endfor + let win_id = win_getid() + let g:matched = v:false + execute 'au WinScrolled' win_id 'let g:matched = v:true' + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + au WinScrolled * let g:amatch = str2nr(expand('<amatch>')) + au WinScrolled * let g:afile = str2nr(expand('<afile>')) + END + call writefile(lines, 'Xtest_winscrolled') + let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6}) + + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000) + + " Scroll left/right in Normal mode. + call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000) + + " Scroll up/down in Normal mode. + call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^4 ', term_getline(buf, 6))}, 1000) + + " Scroll up/down in Insert mode. + call term_sendkeys(buf, "Mi\<c-x>\<c-e>\<Esc>i\<c-x>\<c-y>\<Esc>") + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^6 ', term_getline(buf, 6))}, 1000) + + " Scroll the window horizontally to focus the last letter of the third line + " containing only six characters. Moving to the previous and shorter lines + " should trigger another autocommand as Vim has to make them visible. + call term_sendkeys(buf, "5zl2k") + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000) + + " Ensure the command was triggered for the specified window ID. + call term_sendkeys(buf, ":echo g:matched\<CR>") + call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) + + " Ensure the expansion of <amatch> and <afile> matches the window ID. + call term_sendkeys(buf, ":echo g:amatch == win_id && g:afile == win_id\<CR>") + call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) + + call StopVimInTerminal(buf) + call delete('Xtest_winscrolled') +endfunc + +func Test_WinScrolled_close_curwin() + CheckRunVimInTerminal + + let lines =<< trim END + set nowrap scrolloff=0 + call setline(1, ['aaa', 'bbb']) + vsplit + au WinScrolled * close + au VimLeave * call writefile(['123456'], 'Xtestout') + END + call writefile(lines, 'Xtest_winscrolled_close_curwin') + let buf = RunVimInTerminal('-S Xtest_winscrolled_close_curwin', {'rows': 6}) + + " This was using freed memory + call term_sendkeys(buf, "\<C-E>") + call TermWait(buf) + call StopVimInTerminal(buf) + + call assert_equal(['123456'], readfile('Xtestout')) + + call delete('Xtest_winscrolled_close_curwin') + call delete('Xtestout') +endfunc + func Test_WinClosed() " Test that the pattern is matched against the closed window's ID, and both " <amatch> and <afile> are set to it. @@ -299,6 +378,40 @@ func Test_WinClosed() unlet g:triggered endfunc +func Test_WinClosed_throws() + vnew + let bnr = bufnr() + call assert_equal(1, bufloaded(bnr)) + augroup test-WinClosed + autocmd WinClosed * throw 'foo' + augroup END + try + close + catch /.*/ + endtry + call assert_equal(0, bufloaded(bnr)) + + autocmd! test-WinClosed + augroup! test-WinClosed +endfunc + +func Test_WinClosed_throws_with_tabs() + tabnew + let bnr = bufnr() + call assert_equal(1, bufloaded(bnr)) + augroup test-WinClosed + autocmd WinClosed * throw 'foo' + augroup END + try + close + catch /.*/ + endtry + call assert_equal(0, bufloaded(bnr)) + + autocmd! test-WinClosed + augroup! test-WinClosed +endfunc + func s:AddAnAutocmd() augroup vimBarTest au BufReadCmd * echo 'hello' @@ -502,7 +615,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost() [CODE] call writefile(content, 'Xvimrc') - call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq') + call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq') let errors = join(readfile('Xerrors')) call assert_match('E814', errors) @@ -526,8 +639,7 @@ func Test_autocmd_blast_badd() call writefile(content, 'XblastBall') call system(GetVimCommand() .. ' --clean -S XblastBall') - " call assert_match('OK', readfile('Xerrors')->join()) - call assert_match('OK', join(readfile('Xerrors'))) + call assert_match('OK', readfile('Xerrors')->join()) call delete('XblastBall') call delete('Xerrors') @@ -563,7 +675,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost2() [CODE] call writefile(content, 'Xvimrc') - call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq') + call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq') let errors = join(readfile('Xerrors')) " This probably only ever matches on unix. call assert_notmatch('Caught deadly signal SEGV', errors) @@ -580,9 +692,10 @@ func Test_empty_doau() endfunc func s:AutoCommandOptionSet(match) + let template = "Option: <%s>, OldVal: <%s>, OldValLocal: <%s>, OldValGlobal: <%s>, NewVal: <%s>, Scope: <%s>, Command: <%s>\n" let item = remove(g:options, 0) - let expected = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", item[0], item[1], item[2], item[3]) - let actual = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", a:match, v:option_old, v:option_new, v:option_type) + let expected = printf(template, item[0], item[1], item[2], item[3], item[4], item[5], item[6]) + let actual = printf(template, a:match, v:option_old, v:option_oldlocal, v:option_oldglobal, v:option_new, v:option_type, v:option_command) let g:opt = [expected, actual] "call assert_equal(expected, actual) endfunc @@ -596,130 +709,593 @@ func Test_OptionSet() au OptionSet * :call s:AutoCommandOptionSet(expand("<amatch>")) " 1: Setting number option" - let g:options = [['number', 0, 1, 'global']] + let g:options = [['number', 0, 0, 0, 1, 'global', 'set']] set nu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 2: Setting local number option" - let g:options = [['number', 1, 0, 'local']] + let g:options = [['number', 1, 1, '', 0, 'local', 'setlocal']] setlocal nonu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 3: Setting global number option" - let g:options = [['number', 1, 0, 'global']] + let g:options = [['number', 1, '', 1, 0, 'global', 'setglobal']] setglobal nonu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 4: Setting local autoindent option" - let g:options = [['autoindent', 0, 1, 'local']] + let g:options = [['autoindent', 0, 0, '', 1, 'local', 'setlocal']] setlocal ai call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 5: Setting global autoindent option" - let g:options = [['autoindent', 0, 1, 'global']] + let g:options = [['autoindent', 0, '', 0, 1, 'global', 'setglobal']] setglobal ai call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 6: Setting global autoindent option" - let g:options = [['autoindent', 1, 0, 'global']] + let g:options = [['autoindent', 1, 1, 1, 0, 'global', 'set']] + set ai! + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 6a: Setting global autoindent option" + let g:options = [['autoindent', 1, 1, 0, 0, 'global', 'set']] + noa setlocal ai + noa setglobal noai set ai! call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " Should not print anything, use :noa " 7: don't trigger OptionSet" - let g:options = [['invalid', 1, 1, 'invalid']] + let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']] noa set nonu - call assert_equal([['invalid', 1, 1, 'invalid']], g:options) + call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options) call assert_equal(g:opt[0], g:opt[1]) " 8: Setting several global list and number option" - let g:options = [['list', 0, 1, 'global'], ['number', 0, 1, 'global']] + let g:options = [['list', 0, 0, 0, 1, 'global', 'set'], ['number', 0, 0, 0, 1, 'global', 'set']] set list nu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 9: don't trigger OptionSet" - let g:options = [['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']] + let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']] noa set nolist nonu - call assert_equal([['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']], g:options) + call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options) call assert_equal(g:opt[0], g:opt[1]) " 10: Setting global acd" - let g:options = [['autochdir', 0, 1, 'local']] + let g:options = [['autochdir', 0, 0, '', 1, 'local', 'setlocal']] setlocal acd call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 11: Setting global autoread (also sets local value)" - let g:options = [['autoread', 0, 1, 'global']] + let g:options = [['autoread', 0, 0, 0, 1, 'global', 'set']] set ar call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 12: Setting local autoread" - let g:options = [['autoread', 1, 1, 'local']] + let g:options = [['autoread', 1, 1, '', 1, 'local', 'setlocal']] setlocal ar call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 13: Setting global autoread" - let g:options = [['autoread', 1, 0, 'global']] + let g:options = [['autoread', 1, '', 1, 0, 'global', 'setglobal']] setglobal invar call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 14: Setting option backspace through :let" - let g:options = [['backspace', '', 'eol,indent,start', 'global']] + let g:options = [['backspace', '', '', '', 'eol,indent,start', 'global', 'set']] let &bs = "eol,indent,start" call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 15: Setting option backspace through setbufvar()" - let g:options = [['backup', 0, 1, 'local']] + let g:options = [['backup', 0, 0, '', 1, 'local', 'setlocal']] " try twice, first time, shouldn't trigger because option name is invalid, " second time, it should trigger - call assert_fails("call setbufvar(1, '&l:bk', 1)", "E355") + let bnum = bufnr('%') + call assert_fails("call setbufvar(bnum, '&l:bk', 1)", 'E355:') " should trigger, use correct option name - call setbufvar(1, '&backup', 1) + call setbufvar(bnum, '&backup', 1) call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 16: Setting number option using setwinvar" - let g:options = [['number', 0, 1, 'local']] + let g:options = [['number', 0, 0, '', 1, 'local', 'setlocal']] call setwinvar(0, '&number', 1) call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 17: Setting key option, shouldn't trigger" - let g:options = [['key', 'invalid', 'invalid1', 'invalid']] + let g:options = [['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']] setlocal key=blah setlocal key= - call assert_equal([['key', 'invalid', 'invalid1', 'invalid']], g:options) + call assert_equal([['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 18a: Setting string global option" + let oldval = &backupext + let g:options = [['backupext', oldval, oldval, oldval, 'foo', 'global', 'set']] + set backupext=foo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 18b: Resetting string global option" + let g:options = [['backupext', 'foo', 'foo', 'foo', oldval, 'global', 'set']] + set backupext& + call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) - " 18: Setting string option" + " 18c: Setting global string global option" + let g:options = [['backupext', oldval, '', oldval, 'bar', 'global', 'setglobal']] + setglobal backupext=bar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 18d: Setting local string global option" + " As this is a global option this sets the global value even though + " :setlocal is used! + noa set backupext& " Reset global and local value (without triggering autocmd) + let g:options = [['backupext', oldval, oldval, '', 'baz', 'local', 'setlocal']] + setlocal backupext=baz + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 18e: Setting again string global option" + noa setglobal backupext=ext_global " Reset global and local value (without triggering autocmd) + noa setlocal backupext=ext_local " Sets the global(!) value! + let g:options = [['backupext', 'ext_local', 'ext_local', 'ext_local', 'fuu', 'global', 'set']] + set backupext=fuu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 19a: Setting string global-local (to buffer) option" let oldval = &tags - let g:options = [['tags', oldval, 'tagpath', 'global']] + let g:options = [['tags', oldval, oldval, oldval, 'tagpath', 'global', 'set']] set tags=tagpath call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) - " 1l: Resetting string option" - let g:options = [['tags', 'tagpath', oldval, 'global']] + " 19b: Resetting string global-local (to buffer) option" + let g:options = [['tags', 'tagpath', 'tagpath', 'tagpath', oldval, 'global', 'set']] set tags& call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) + " 19c: Setting global string global-local (to buffer) option " + let g:options = [['tags', oldval, '', oldval, 'tagpath1', 'global', 'setglobal']] + setglobal tags=tagpath1 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 19d: Setting local string global-local (to buffer) option" + let g:options = [['tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal']] + setlocal tags=tagpath2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 19e: Setting again string global-local (to buffer) option" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + noa setglobal tags=tag_global " Reset global and local value (without triggering autocmd) + noa setlocal tags=tag_local + let g:options = [['tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set']] + set tags=tagpath + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 19f: Setting string global-local (to buffer) option to an empty string" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + noa set tags=tag_global " Reset global and local value (without triggering autocmd) + noa setlocal tags= " empty string + let g:options = [['tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set']] + set tags=tagpath + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 20a: Setting string local (to buffer) option" + let oldval = &spelllang + let g:options = [['spelllang', oldval, oldval, oldval, 'elvish,klingon', 'global', 'set']] + set spelllang=elvish,klingon + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 20b: Resetting string local (to buffer) option" + let g:options = [['spelllang', 'elvish,klingon', 'elvish,klingon', 'elvish,klingon', oldval, 'global', 'set']] + set spelllang& + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 20c: Setting global string local (to buffer) option" + let g:options = [['spelllang', oldval, '', oldval, 'elvish', 'global', 'setglobal']] + setglobal spelllang=elvish + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 20d: Setting local string local (to buffer) option" + noa set spelllang& " Reset global and local value (without triggering autocmd) + let g:options = [['spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal']] + setlocal spelllang=klingon + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 20e: Setting again string local (to buffer) option" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + noa setglobal spelllang=spellglobal " Reset global and local value (without triggering autocmd) + noa setlocal spelllang=spelllocal + let g:options = [['spelllang', 'spelllocal', 'spelllocal', 'spellglobal', 'foo', 'global', 'set']] + set spelllang=foo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 21a: Setting string global-local (to window) option" + let oldval = &statusline + let g:options = [['statusline', oldval, oldval, oldval, 'foo', 'global', 'set']] + set statusline=foo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 21b: Resetting string global-local (to window) option" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + let g:options = [['statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set']] + set statusline& + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 21c: Setting global string global-local (to window) option" + let g:options = [['statusline', oldval, '', oldval, 'bar', 'global', 'setglobal']] + setglobal statusline=bar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 21d: Setting local string global-local (to window) option" + noa set statusline& " Reset global and local value (without triggering autocmd) + let g:options = [['statusline', oldval, oldval, '', 'baz', 'local', 'setlocal']] + setlocal statusline=baz + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 21e: Setting again string global-local (to window) option" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + noa setglobal statusline=bar " Reset global and local value (without triggering autocmd) + noa setlocal statusline=baz + let g:options = [['statusline', 'bar', 'baz', 'bar', 'foo', 'global', 'set']] + set statusline=foo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 22a: Setting string local (to window) option" + let oldval = &foldignore + let g:options = [['foldignore', oldval, oldval, oldval, 'fo', 'global', 'set']] + set foldignore=fo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 22b: Resetting string local (to window) option" + let g:options = [['foldignore', 'fo', 'fo', 'fo', oldval, 'global', 'set']] + set foldignore& + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 22c: Setting global string local (to window) option" + let g:options = [['foldignore', oldval, '', oldval, 'bar', 'global', 'setglobal']] + setglobal foldignore=bar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 22d: Setting local string local (to window) option" + noa set foldignore& " Reset global and local value (without triggering autocmd) + let g:options = [['foldignore', oldval, oldval, '', 'baz', 'local', 'setlocal']] + setlocal foldignore=baz + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 22e: Setting again string local (to window) option" + noa setglobal foldignore=glob " Reset global and local value (without triggering autocmd) + noa setlocal foldignore=loc + let g:options = [['foldignore', 'loc', 'loc', 'glob', 'fo', 'global', 'set']] + set foldignore=fo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 23a: Setting global number global option" + noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd) + noa setlocal cmdheight=1 " Sets the global(!) value! + let g:options = [['cmdheight', '1', '', '1', '2', 'global', 'setglobal']] + setglobal cmdheight=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 23b: Setting local number global option" + noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd) + noa setlocal cmdheight=1 " Sets the global(!) value! + let g:options = [['cmdheight', '1', '1', '', '2', 'local', 'setlocal']] + setlocal cmdheight=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 23c: Setting again number global option" + noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd) + noa setlocal cmdheight=1 " Sets the global(!) value! + let g:options = [['cmdheight', '1', '1', '1', '2', 'global', 'set']] + set cmdheight=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 23d: Setting again number global option" + noa set cmdheight=8 " Reset global and local value (without triggering autocmd) + let g:options = [['cmdheight', '8', '8', '8', '2', 'global', 'set']] + set cmdheight=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 24a: Setting global number global-local (to buffer) option" + noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd) + noa setlocal undolevels=1 + let g:options = [['undolevels', '8', '', '8', '2', 'global', 'setglobal']] + setglobal undolevels=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 24b: Setting local number global-local (to buffer) option" + noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd) + noa setlocal undolevels=1 + let g:options = [['undolevels', '1', '1', '', '2', 'local', 'setlocal']] + setlocal undolevels=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 24c: Setting again number global-local (to buffer) option" + noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd) + noa setlocal undolevels=1 + let g:options = [['undolevels', '1', '1', '8', '2', 'global', 'set']] + set undolevels=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 24d: Setting again global number global-local (to buffer) option" + noa set undolevels=8 " Reset global and local value (without triggering autocmd) + let g:options = [['undolevels', '8', '8', '8', '2', 'global', 'set']] + set undolevels=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 25a: Setting global number local (to buffer) option" + noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd) + noa setlocal wrapmargin=1 + let g:options = [['wrapmargin', '8', '', '8', '2', 'global', 'setglobal']] + setglobal wrapmargin=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 25b: Setting local number local (to buffer) option" + noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd) + noa setlocal wrapmargin=1 + let g:options = [['wrapmargin', '1', '1', '', '2', 'local', 'setlocal']] + setlocal wrapmargin=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 25c: Setting again number local (to buffer) option" + noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd) + noa setlocal wrapmargin=1 + let g:options = [['wrapmargin', '1', '1', '8', '2', 'global', 'set']] + set wrapmargin=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 25d: Setting again global number local (to buffer) option" + noa set wrapmargin=8 " Reset global and local value (without triggering autocmd) + let g:options = [['wrapmargin', '8', '8', '8', '2', 'global', 'set']] + set wrapmargin=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 26: Setting number global-local (to window) option. + " Such option does currently not exist. + + + " 27a: Setting global number local (to window) option" + noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd) + noa setlocal foldcolumn=1 + let g:options = [['foldcolumn', '8', '', '8', '2', 'global', 'setglobal']] + setglobal foldcolumn=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 27b: Setting local number local (to window) option" + noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd) + noa setlocal foldcolumn=1 + let g:options = [['foldcolumn', '1', '1', '', '2', 'local', 'setlocal']] + setlocal foldcolumn=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 27c: Setting again number local (to window) option" + noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd) + noa setlocal foldcolumn=1 + let g:options = [['foldcolumn', '1', '1', '8', '2', 'global', 'set']] + set foldcolumn=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 27d: Setting again global number local (to window) option" + noa set foldcolumn=8 " Reset global and local value (without triggering autocmd) + let g:options = [['foldcolumn', '8', '8', '8', '2', 'global', 'set']] + set foldcolumn=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 28a: Setting global boolean global option" + noa setglobal nowrapscan " Reset global and local value (without triggering autocmd) + noa setlocal wrapscan " Sets the global(!) value! + let g:options = [['wrapscan', '1', '', '1', '0', 'global', 'setglobal']] + setglobal nowrapscan + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 28b: Setting local boolean global option" + noa setglobal nowrapscan " Reset global and local value (without triggering autocmd) + noa setlocal wrapscan " Sets the global(!) value! + let g:options = [['wrapscan', '1', '1', '', '0', 'local', 'setlocal']] + setlocal nowrapscan + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 28c: Setting again boolean global option" + noa setglobal nowrapscan " Reset global and local value (without triggering autocmd) + noa setlocal wrapscan " Sets the global(!) value! + let g:options = [['wrapscan', '1', '1', '1', '0', 'global', 'set']] + set nowrapscan + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 28d: Setting again global boolean global option" + noa set nowrapscan " Reset global and local value (without triggering autocmd) + let g:options = [['wrapscan', '0', '0', '0', '1', 'global', 'set']] + set wrapscan + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 29a: Setting global boolean global-local (to buffer) option" + noa setglobal noautoread " Reset global and local value (without triggering autocmd) + noa setlocal autoread + let g:options = [['autoread', '0', '', '0', '1', 'global', 'setglobal']] + setglobal autoread + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 29b: Setting local boolean global-local (to buffer) option" + noa setglobal noautoread " Reset global and local value (without triggering autocmd) + noa setlocal autoread + let g:options = [['autoread', '1', '1', '', '0', 'local', 'setlocal']] + setlocal noautoread + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 29c: Setting again boolean global-local (to buffer) option" + noa setglobal noautoread " Reset global and local value (without triggering autocmd) + noa setlocal autoread + let g:options = [['autoread', '1', '1', '0', '1', 'global', 'set']] + set autoread + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 29d: Setting again global boolean global-local (to buffer) option" + noa set noautoread " Reset global and local value (without triggering autocmd) + let g:options = [['autoread', '0', '0', '0', '1', 'global', 'set']] + set autoread + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 30a: Setting global boolean local (to buffer) option" + noa setglobal nocindent " Reset global and local value (without triggering autocmd) + noa setlocal cindent + let g:options = [['cindent', '0', '', '0', '1', 'global', 'setglobal']] + setglobal cindent + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 30b: Setting local boolean local (to buffer) option" + noa setglobal nocindent " Reset global and local value (without triggering autocmd) + noa setlocal cindent + let g:options = [['cindent', '1', '1', '', '0', 'local', 'setlocal']] + setlocal nocindent + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 30c: Setting again boolean local (to buffer) option" + noa setglobal nocindent " Reset global and local value (without triggering autocmd) + noa setlocal cindent + let g:options = [['cindent', '1', '1', '0', '1', 'global', 'set']] + set cindent + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 30d: Setting again global boolean local (to buffer) option" + noa set nocindent " Reset global and local value (without triggering autocmd) + let g:options = [['cindent', '0', '0', '0', '1', 'global', 'set']] + set cindent + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 31: Setting boolean global-local (to window) option + " Currently no such option exists. + + + " 32a: Setting global boolean local (to window) option" + noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd) + noa setlocal cursorcolumn + let g:options = [['cursorcolumn', '0', '', '0', '1', 'global', 'setglobal']] + setglobal cursorcolumn + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 32b: Setting local boolean local (to window) option" + noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd) + noa setlocal cursorcolumn + let g:options = [['cursorcolumn', '1', '1', '', '0', 'local', 'setlocal']] + setlocal nocursorcolumn + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 32c: Setting again boolean local (to window) option" + noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd) + noa setlocal cursorcolumn + let g:options = [['cursorcolumn', '1', '1', '0', '1', 'global', 'set']] + set cursorcolumn + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 32d: Setting again global boolean local (to window) option" + noa set nocursorcolumn " Reset global and local value (without triggering autocmd) + let g:options = [['cursorcolumn', '0', '0', '0', '1', 'global', 'set']] + set cursorcolumn + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 33: Test autocommands when an option value is converted internally. + noa set backspace=1 " Reset global and local value (without triggering autocmd) + let g:options = [['backspace', 'indent,eol', 'indent,eol', 'indent,eol', '2', 'global', 'set']] + set backspace=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " Cleanup au! OptionSet " set tags& - for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'tags'] + for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'backupext', 'tags', 'spelllang', 'statusline', 'foldignore', 'cmdheight', 'undolevels', 'wrapmargin', 'foldcolumn', 'wrapscan', 'autoread', 'cindent', 'cursorcolumn'] exe printf(":set %s&vim", opt) endfor call test_override('starting', 0) @@ -1043,7 +1619,7 @@ func Test_bufunload_all() call writefile(content, 'Xtest') call delete('Xout') - call system(v:progpath. ' -u NORC -i NONE -N -S Xtest') + call system(GetVimCommandClean() .. ' -N --headless -S Xtest') call assert_true(filereadable('Xout')) call delete('Xxx1') @@ -1364,6 +1940,14 @@ func Test_autocommand_all_events() call assert_fails('au * x bwipe', 'E1155:') endfunc +func Test_autocmd_user() + au User MyEvent let s:res = [expand("<afile>"), expand("<amatch>")] + doautocmd User MyEvent + call assert_equal(['MyEvent', 'MyEvent'], s:res) + au! User + unlet s:res +endfunc + function s:Before_test_dirchanged() augroup test_dirchanged autocmd! @@ -1387,14 +1971,16 @@ endfunc function Test_dirchanged_global() call s:Before_test_dirchanged() + autocmd test_dirchanged DirChangedPre global call add(s:li, expand("<amatch>") .. " pre cd " .. v:event.directory) autocmd test_dirchanged DirChanged global call add(s:li, "cd:") autocmd test_dirchanged DirChanged global call add(s:li, expand("<afile>")) call chdir(s:dir_foo) - call assert_equal(["cd:", s:dir_foo], s:li) + let expected = ["global pre cd " .. s:dir_foo, "cd:", s:dir_foo] + call assert_equal(expected, s:li) call chdir(s:dir_foo) - call assert_equal(["cd:", s:dir_foo], s:li) + call assert_equal(expected, s:li) exe 'lcd ' .. fnameescape(s:dir_bar) - call assert_equal(["cd:", s:dir_foo], s:li) + call assert_equal(expected, s:li) call s:After_test_dirchanged() endfunc @@ -1416,6 +2002,7 @@ function Test_dirchanged_auto() CheckOption autochdir call s:Before_test_dirchanged() call test_autochdir() + autocmd test_dirchanged DirChangedPre auto call add(s:li, "pre cd " .. v:event.directory) autocmd test_dirchanged DirChanged auto call add(s:li, "auto:") autocmd test_dirchanged DirChanged auto call add(s:li, expand("<afile>")) set acd @@ -1423,7 +2010,8 @@ function Test_dirchanged_auto() call assert_equal([], s:li) exe 'edit ' . s:dir_foo . '/Xfile' call assert_equal(s:dir_foo, getcwd()) - call assert_equal(["auto:", s:dir_foo], s:li) + let expected = ["pre cd " .. s:dir_foo, "auto:", s:dir_foo] + call assert_equal(expected, s:li) set noacd bwipe! call s:After_test_dirchanged() @@ -1868,6 +2456,19 @@ func Test_throw_in_BufWritePre() au! throwing endfunc +func Test_autocmd_in_try_block() + call mkdir('Xdir') + au BufEnter * let g:fname = expand('%') + try + edit Xdir/ + endtry + call assert_match('Xdir', g:fname) + + unlet g:fname + au! BufEnter + call delete('Xdir', 'rf') +endfunc + func Test_autocmd_CmdWinEnter() CheckRunVimInTerminal " There is not cmdwin switch, so @@ -1897,96 +2498,62 @@ func Test_autocmd_CmdWinEnter() call delete(filename) endfunc -func Test_FileChangedShell_reload() - if !has('unix') - return - endif - augroup testreload - au FileChangedShell Xchanged let g:reason = v:fcs_reason | let v:fcs_choice = 'reload' +func Test_autocmd_was_using_freed_memory() + pedit xx + n x + augroup winenter + au WinEnter * if winnr('$') > 2 | quit | endif augroup END - new Xchanged - call setline(1, 'reload this') - write - " Need to wait until the timestamp would change by at least a second. - sleep 2 - silent !echo 'extra line' >>Xchanged - checktime - call assert_equal('changed', g:reason) - call assert_equal(2, line('$')) - call assert_equal('extra line', getline(2)) - - " Only triggers once - let g:reason = '' - checktime - call assert_equal('', g:reason) - - " When deleted buffer is not reloaded - silent !rm Xchanged - let g:reason = '' - checktime - call assert_equal('deleted', g:reason) - call assert_equal(2, line('$')) - call assert_equal('extra line', getline(2)) - - " When recreated buffer is reloaded - call setline(1, 'buffer is changed') - silent !echo 'new line' >>Xchanged - let g:reason = '' - checktime - call assert_equal('conflict', g:reason) - call assert_equal(1, line('$')) - call assert_equal('new line', getline(1)) + " Nvim needs large 'winwidth' and 'nowinfixwidth' to crash + set winwidth=99999 nowinfixwidth + split - " Only mode changed - silent !chmod +x Xchanged - let g:reason = '' - checktime - call assert_equal('mode', g:reason) - call assert_equal(1, line('$')) - call assert_equal('new line', getline(1)) - - " Only time changed - sleep 2 - silent !touch Xchanged - let g:reason = '' - checktime - call assert_equal('time', g:reason) - call assert_equal(1, line('$')) - call assert_equal('new line', getline(1)) - - if has('persistent_undo') - " With an undo file the reload can be undone and a change before the - " reload. - set undofile - call setline(2, 'before write') - write - call setline(2, 'after write') - sleep 2 - silent !echo 'different line' >>Xchanged - let g:reason = '' - checktime - call assert_equal('conflict', g:reason) - call assert_equal(3, line('$')) - call assert_equal('before write', getline(2)) - call assert_equal('different line', getline(3)) - " undo the reload - undo - call assert_equal(2, line('$')) - call assert_equal('after write', getline(2)) - " undo the change before reload - undo - call assert_equal(2, line('$')) - call assert_equal('before write', getline(2)) + augroup winenter + au! WinEnter + augroup END + + set winwidth& winfixwidth& + bwipe xx + bwipe x + pclose +endfunc + +func Test_BufWrite_lockmarks() + edit! Xtest + call setline(1, ['a', 'b', 'c', 'd']) + + " :lockmarks preserves the marks + call SetChangeMarks(2, 3) + lockmarks write + call assert_equal([2, 3], [line("'["), line("']")]) + + " *WritePre autocmds get the correct line range, but lockmarks preserves the + " original values for the user + augroup lockmarks + au! + au BufWritePre,FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")]) + au FileWritePre * call assert_equal([3, 4], [line("'["), line("']")]) + augroup END + + lockmarks write + call assert_equal([2, 3], [line("'["), line("']")]) - set noundofile + if executable('cat') + lockmarks %!cat + call assert_equal([2, 3], [line("'["), line("']")]) endif + lockmarks 3,4write Xtest2 + call assert_equal([2, 3], [line("'["), line("']")]) - au! testreload - bwipe! - call delete('Xchanged') + au! lockmarks + augroup! lockmarks + call delete('Xtest') + call delete('Xtest2') endfunc +" FileChangedShell tested in test_filechanged.vim + func LogACmd() call add(g:logged, line('$')) endfunc @@ -2114,6 +2681,16 @@ func Test_close_autocmd_tab() %bwipe! endfunc +func Test_Visual_doautoall_redraw() + call setline(1, ['a', 'b']) + new + wincmd p + call feedkeys("G\<C-V>", 'txn') + autocmd User Explode ++once redraw + doautoall User Explode + %bwipe! +endfunc + func Test_autocmd_closes_window() au BufNew,BufWinLeave * e %e file yyy @@ -2125,6 +2702,19 @@ func Test_autocmd_closes_window() au! BufWinLeave endfunc +func Test_autocmd_quit_psearch() + sn aa bb + augroup aucmd_win_test + au! + au BufEnter,BufLeave,BufNew,WinEnter,WinLeave,WinNew * if winnr('$') > 1 | q | endif + augroup END + ps / + + augroup aucmd_win_test + au! + augroup END +endfunc + func Test_autocmd_closing_cmdwin() au BufWinLeave * nested q call assert_fails("norm 7q?\n", 'E855:') @@ -2134,4 +2724,39 @@ func Test_autocmd_closing_cmdwin() only endfunc +func Test_bufwipeout_changes_window() + " This should not crash, but we don't have any expectations about what + " happens, changing window in BufWipeout has unpredictable results. + tabedit + let g:window_id = win_getid() + topleft new + setlocal bufhidden=wipe + autocmd BufWipeout <buffer> call win_gotoid(g:window_id) + tabprevious + +tabclose + + unlet g:window_id + au! BufWipeout + %bwipe! +endfunc + +func Test_v_event_readonly() + autocmd CompleteChanged * let v:event.width = 0 + call assert_fails("normal! i\<C-X>\<C-V>", 'E46:') + au! CompleteChanged + + autocmd DirChangedPre * let v:event.directory = '' + call assert_fails('cd .', 'E46:') + au! DirChangedPre + + autocmd ModeChanged * let v:event.new_mode = '' + call assert_fails('normal! cc', 'E46:') + au! ModeChanged + + autocmd TextYankPost * let v:event.operator = '' + call assert_fails('normal! yy', 'E46:') + au! TextYankPost +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim index 20758b0c0a..af42b3857d 100644 --- a/src/nvim/testdir/test_blob.vim +++ b/src/nvim/testdir/test_blob.vim @@ -346,4 +346,12 @@ func Test_blob_sort() endif endfunc +" The following used to cause an out-of-bounds memory access +func Test_blob2string() + let v = '0z' .. repeat('01010101.', 444) + let v ..= '01' + exe 'let b = ' .. v + call assert_equal(v, string(b)) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_blockedit.vim b/src/nvim/testdir/test_blockedit.vim index 180524cd73..7b56b1554f 100644 --- a/src/nvim/testdir/test_blockedit.vim +++ b/src/nvim/testdir/test_blockedit.vim @@ -15,6 +15,58 @@ func Test_blockinsert_indent() bwipe! endfunc +func Test_blockinsert_autoindent() + new + let lines =<< trim END + var d = { + a: () => 0, + b: () => 0, + c: () => 0, + } + END + call setline(1, lines) + filetype plugin indent on + setlocal sw=2 et ft=vim + setlocal indentkeys+=: + exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>" + let expected =<< trim END + var d = { + a: (): asdf => 0, + b: (): asdf => 0, + c: (): asdf => 0, + } + END + call assert_equal(expected, getline(1, 5)) + + " insert on the next column should do exactly the same + :%dele + call setline(1, lines) + exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>" + call assert_equal(expected, getline(1, 5)) + + :%dele + call setline(1, lines) + setlocal sw=8 noet + exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>" + let expected =<< trim END + var d = { + a: (): asdf => 0, + b: (): asdf => 0, + c: (): asdf => 0, + } + END + call assert_equal(expected, getline(1, 5)) + + " insert on the next column should do exactly the same + :%dele + call setline(1, lines) + exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>" + call assert_equal(expected, getline(1, 5)) + + filetype off + bwipe! +endfunc + func Test_blockinsert_delete() new let _bs = &bs @@ -29,4 +81,52 @@ func Test_blockinsert_delete() bwipe! endfunc +func Test_blockappend_eol_cursor() + new + " Test 1 Move 1 char left + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "norm! gg$\<c-v>2jA\<left>x\<esc>" + call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$')) + " Test 2 Move 2 chars left + sil %d + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "norm! gg$\<c-v>2jA\<left>\<left>x\<esc>" + call assert_equal(['axaa', 'bxbb', 'cxcc'], getline(1, '$')) + " Test 3 Move 3 chars left (outside of the visual selection) + sil %d + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "norm! ggl$\<c-v>2jA\<left>\<left>\<left>x\<esc>" + call assert_equal(['xaaa', 'bbb', 'ccc'], getline(1, '$')) + bw! +endfunc + +func Test_blockappend_eol_cursor2() + new + " Test 1 Move 1 char left + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! gg\<c-v>$2jA\<left>x\<esc>" + call assert_equal(['aaaaxa', 'bbbx', 'ccccxc'], getline(1, '$')) + " Test 2 Move 2 chars left + sil %d + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! gg\<c-v>$2jA\<left>\<left>x\<esc>" + call assert_equal(['aaaxaa', 'bbbx', 'cccxcc'], getline(1, '$')) + " Test 3 Move 3 chars left (to the beginning of the visual selection) + sil %d + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! gg\<c-v>$2jA\<left>\<left>\<left>x\<esc>" + call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$')) + " Test 4 Move 3 chars left (outside of the visual selection) + sil %d + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>x\<esc>" + call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$')) + " Test 5 Move 4 chars left (outside of the visual selection) + sil %d + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>\<left>x\<esc>" + call assert_equal(['axaaaa', 'bxbb', 'cxcccc'], getline(1, '$')) + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index 277050876e..438edb0257 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -1,7 +1,7 @@ " Test for breakindent " " Note: if you get strange failures when adding new tests, it might be that -" while the test is run, the breakindent cacheing gets in its way. +" while the test is run, the breakindent caching gets in its way. " It helps to change the tabstop setting and force a redraw (e.g. see " Test_breakindent08()) if !exists('+breakindent') @@ -20,7 +20,7 @@ func s:screen_lines2(lnums, lnume, width) abort return ScreenLines([a:lnums, a:lnume], a:width) endfunc -func! s:compare_lines(expect, actual) +func s:compare_lines(expect, actual) call assert_equal(join(a:expect, "\n"), join(a:actual, "\n")) endfunc @@ -432,7 +432,7 @@ func Test_breakindent11_vartabs() call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4 vts=4') let text = getline(2) let width = strlen(text[1:]) + 2->indent() + strlen(&sbr) * 3 " text wraps 3 times - call assert_equal(width, strdisplaywidth(text)) + call assert_equal(width, text->strdisplaywidth()) call s:close_windows('set sbr= vts&') endfunc diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim index 40111fdf06..a31cdbb49a 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -1,5 +1,7 @@ " Tests for Vim buffer +source check.vim + func Test_buffer_error() new foo1 new foo2 @@ -30,4 +32,33 @@ func Test_balt() call assert_equal('OtherBuffer', bufname()) endfunc +" Test for buffer match URL(scheme) check +" scheme is alpha and inner hyphen only. +func Test_buffer_scheme() + CheckMSWindows + + set noshellslash + %bwipe! + let bufnames = [ + \ #{id: 'b0', name: 'test://xyz/foo/b0' , match: 1}, + \ #{id: 'b1', name: 'test+abc://xyz/foo/b1', match: 0}, + \ #{id: 'b2', name: 'test_abc://xyz/foo/b2', match: 0}, + \ #{id: 'b3', name: 'test-abc://xyz/foo/b3', match: 1}, + \ #{id: 'b4', name: '-test://xyz/foo/b4' , match: 0}, + \ #{id: 'b5', name: 'test-://xyz/foo/b5' , match: 0}, + \] + for buf in bufnames + new `=buf.name` + if buf.match + call assert_equal(buf.name, getbufinfo(buf.id)[0].name) + else + " slashes will have become backslashes + call assert_notequal(buf.name, getbufinfo(buf.id)[0].name) + endif + bwipe + endfor + + set shellslash& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index bb672cf0ec..a6eb93b4be 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -1,108 +1,113 @@ " Tests for the getbufinfo(), getwininfo() and gettabinfo() functions function Test_getbufwintabinfo() - edit Xtestfile1 - edit Xtestfile2 - let buflist = getbufinfo() - call assert_equal(2, len(buflist)) - call assert_match('Xtestfile1', buflist[0].name) - call assert_match('Xtestfile2', getbufinfo('Xtestfile2')[0].name) - call assert_equal([], getbufinfo(2016)) - edit Xtestfile1 - hide edit Xtestfile2 - hide enew - call assert_equal(3, len(getbufinfo({'bufloaded':1}))) - - set tabstop&vim - let b:editor = 'vim' + edit Xtestfile1 + edit Xtestfile2 + let buflist = getbufinfo() + call assert_equal(2, len(buflist)) + call assert_match('Xtestfile1', buflist[0].name) + call assert_match('Xtestfile2', getbufinfo('Xtestfile2')[0].name) + call assert_equal([], getbufinfo(2016)) + edit Xtestfile1 + hide edit Xtestfile2 + hide enew + call assert_equal(3, len(getbufinfo({'bufloaded':1}))) + + set tabstop&vim + let b:editor = 'vim' + let l = getbufinfo('%') + call assert_equal(bufnr('%'), l[0].bufnr) + call assert_equal('vim', l[0].variables.editor) + call assert_notequal(-1, index(l[0].windows, '%'->bufwinid())) + + let l = '%'->getbufinfo() + call assert_equal(bufnr('%'), l[0].bufnr) + + " Test for getbufinfo() with 'bufmodified' + call assert_equal(0, len(getbufinfo({'bufmodified' : 1}))) + call setbufline('Xtestfile1', 1, ["Line1"]) + let l = getbufinfo({'bufmodified' : 1}) + call assert_equal(1, len(l)) + call assert_equal(bufnr('Xtestfile1'), l[0].bufnr) + + if has('signs') + call append(0, ['Linux', 'Windows', 'Mac']) + sign define Mark text=>> texthl=Search + exe "sign place 2 line=3 name=Mark buffer=" . bufnr('%') let l = getbufinfo('%') - call assert_equal(bufnr('%'), l[0].bufnr) - call assert_equal('vim', l[0].variables.editor) - call assert_notequal(-1, index(l[0].windows, '%'->bufwinid())) - - " Test for getbufinfo() with 'bufmodified' - call assert_equal(0, len(getbufinfo({'bufmodified' : 1}))) - call setbufline('Xtestfile1', 1, ["Line1"]) - let l = getbufinfo({'bufmodified' : 1}) - call assert_equal(1, len(l)) - call assert_equal(bufnr('Xtestfile1'), l[0].bufnr) - - if has('signs') - call append(0, ['Linux', 'Windows', 'Mac']) - sign define Mark text=>> texthl=Search - exe "sign place 2 line=3 name=Mark buffer=" . bufnr('%') - let l = getbufinfo('%') - call assert_equal(2, l[0].signs[0].id) - call assert_equal(3, l[0].signs[0].lnum) - call assert_equal('Mark', l[0].signs[0].name) - sign unplace * - sign undefine Mark - enew! - endif - - only - let w1_id = win_getid() - new - let w2_id = win_getid() - tabnew | let w3_id = win_getid() - new | let w4_id = win_getid() - vert new | let w5_id = win_getid() - call setwinvar(0, 'signal', 'green') - tabfirst - let winlist = getwininfo() - call assert_equal(5, len(winlist)) - call assert_equal(winwidth(1), winlist[0].width) - call assert_equal(1, winlist[0].wincol) - " tabline adds one row in terminal, not in GUI - let tablineheight = winlist[0].winrow == 2 ? 1 : 0 - call assert_equal(tablineheight + 1, winlist[0].winrow) - - call assert_equal(winbufnr(2), winlist[1].bufnr) - call assert_equal(winheight(2), winlist[1].height) - call assert_equal(1, winlist[1].wincol) - call assert_equal(tablineheight + winheight(1) + 2, winlist[1].winrow) - - call assert_equal(1, winlist[2].winnr) - call assert_equal(tablineheight + 1, winlist[2].winrow) - call assert_equal(1, winlist[2].wincol) - - call assert_equal(winlist[2].width + 2, winlist[3].wincol) - call assert_equal(1, winlist[4].wincol) - - call assert_equal(1, winlist[0].tabnr) - call assert_equal(1, winlist[1].tabnr) - call assert_equal(2, winlist[2].tabnr) - call assert_equal(2, winlist[3].tabnr) - call assert_equal(2, winlist[4].tabnr) - - call assert_equal('green', winlist[2].variables.signal) - call assert_equal(w4_id, winlist[3].winid) - let winfo = w5_id->getwininfo()[0] - call assert_equal(2, winfo.tabnr) - call assert_equal([], getwininfo(3)) - - call settabvar(1, 'space', 'build') - let tablist = gettabinfo() - call assert_equal(2, len(tablist)) - call assert_equal(3, len(tablist[1].windows)) - call assert_equal(2, tablist[1].tabnr) - call assert_equal('build', tablist[0].variables.space) - call assert_equal(w2_id, tablist[0].windows[0]) - call assert_equal([], 3->gettabinfo()) - - tabonly | only - - lexpr '' - lopen - copen - let winlist = getwininfo() - call assert_false(winlist[0].quickfix) - call assert_false(winlist[0].loclist) - call assert_true(winlist[1].quickfix) - call assert_true(winlist[1].loclist) - call assert_true(winlist[2].quickfix) - call assert_false(winlist[2].loclist) - wincmd t | only + call assert_equal(2, l[0].signs[0].id) + call assert_equal(3, l[0].signs[0].lnum) + call assert_equal('Mark', l[0].signs[0].name) + sign unplace * + sign undefine Mark + enew! + endif + + only + let w1_id = win_getid() + setl foldcolumn=3 + new + let w2_id = win_getid() + tabnew | let w3_id = win_getid() + new | let w4_id = win_getid() + vert new | let w5_id = win_getid() + eval 'green'->setwinvar(0, 'signal') + tabfirst + let winlist = getwininfo() + call assert_equal(5, len(winlist)) + call assert_equal(winwidth(1), winlist[0].width) + call assert_equal(1, winlist[0].wincol) + " tabline adds one row in terminal, not in GUI + let tablineheight = winlist[0].winrow == 2 ? 1 : 0 + call assert_equal(tablineheight + 1, winlist[0].winrow) + + call assert_equal(winbufnr(2), winlist[1].bufnr) + call assert_equal(winheight(2), winlist[1].height) + call assert_equal(1, winlist[1].wincol) + call assert_equal(3, winlist[1].textoff) " foldcolumn + call assert_equal(tablineheight + winheight(1) + 2, winlist[1].winrow) + + call assert_equal(1, winlist[2].winnr) + call assert_equal(tablineheight + 1, winlist[2].winrow) + call assert_equal(1, winlist[2].wincol) + + call assert_equal(winlist[2].width + 2, winlist[3].wincol) + call assert_equal(1, winlist[4].wincol) + + call assert_equal(1, winlist[0].tabnr) + call assert_equal(1, winlist[1].tabnr) + call assert_equal(2, winlist[2].tabnr) + call assert_equal(2, winlist[3].tabnr) + call assert_equal(2, winlist[4].tabnr) + + call assert_equal('green', winlist[2].variables.signal) + call assert_equal(w4_id, winlist[3].winid) + let winfo = w5_id->getwininfo()[0] + call assert_equal(2, winfo.tabnr) + call assert_equal([], getwininfo(3)) + + call settabvar(1, 'space', 'build') + let tablist = gettabinfo() + call assert_equal(2, len(tablist)) + call assert_equal(3, len(tablist[1].windows)) + call assert_equal(2, tablist[1].tabnr) + call assert_equal('build', tablist[0].variables.space) + call assert_equal(w2_id, tablist[0].windows[0]) + call assert_equal([], 3->gettabinfo()) + + tabonly | only + + lexpr '' + lopen + copen + let winlist = getwininfo() + call assert_false(winlist[0].quickfix) + call assert_false(winlist[0].loclist) + call assert_true(winlist[1].quickfix) + call assert_true(winlist[1].loclist) + call assert_true(winlist[2].quickfix) + call assert_false(winlist[2].loclist) + wincmd t | only endfunction function Test_get_buf_options() diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index 0bba321ee2..c364babd65 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -44,6 +44,15 @@ func Test_cd_minus() cd - call assert_equal(path, getcwd()) + " Test for :cd - after a failed :cd + " v8.2.1183 is not ported yet + " call assert_fails('cd /nonexistent', 'E344:') + call assert_fails('cd /nonexistent', 'E472:') + call assert_equal(path, getcwd()) + cd - + call assert_equal(path_dotdot, getcwd()) + cd - + " Test for :cd - without a previous directory let lines =<< trim [SCRIPT] call assert_fails('cd -', 'E186:') @@ -101,7 +110,7 @@ func Test_chdir_func() call assert_match('^\[global\] .*/Xdir$', trim(execute('verbose pwd'))) call chdir('..') call assert_equal('y', fnamemodify(getcwd(1, 2), ':t')) - call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) + call assert_equal('z', fnamemodify(3->getcwd(2), ':t')) tabnext | wincmd t call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd'))) call chdir('..') @@ -215,3 +224,42 @@ func Test_cd_from_non_existing_dir() cd - call assert_equal(saveddir, getcwd()) endfunc + +func Test_cd_unknown_dir() + call mkdir('Xa') + cd Xa + call writefile(['text'], 'Xb.txt') + edit Xa/Xb.txt + let first_buf = bufnr() + cd .. + edit + call assert_equal(first_buf, bufnr()) + edit Xa/Xb.txt + call assert_notequal(first_buf, bufnr()) + + bwipe! + exe "bwipe! " .. first_buf + call delete('Xa', 'rf') +endfunc + +func Test_getcwd_actual_dir() + CheckFunction test_autochdir + let startdir = getcwd() + call mkdir('Xactual') + call test_autochdir() + set autochdir + edit Xactual/file.txt + call assert_match('testdir.Xactual$', getcwd()) + lcd .. + call assert_match('testdir$', getcwd()) + edit + call assert_match('testdir.Xactual$', getcwd()) + call assert_match('testdir$', getcwd(win_getid())) + + set noautochdir + bwipe! + call chdir(startdir) + call delete('Xactual', 'rf') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cdo.vim b/src/nvim/testdir/test_cdo.vim new file mode 100644 index 0000000000..dbed7df4ac --- /dev/null +++ b/src/nvim/testdir/test_cdo.vim @@ -0,0 +1,216 @@ +" Tests for the :cdo, :cfdo, :ldo and :lfdo commands + +source check.vim +CheckFeature quickfix + +" Create the files used by the tests +func SetUp() + call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1') + call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2') + call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3') +endfunc + +" Remove the files used by the tests +func TearDown() + call delete('Xtestfile1') + call delete('Xtestfile2') + call delete('Xtestfile3') +endfunc + +" Returns the current line in '<filename> <linenum>L <column>C' format +func GetRuler() + return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' +endfunc + +" Tests for the :cdo and :ldo commands +func XdoTests(cchar) + enew + + " Shortcuts for calling the cdo and ldo commands + let Xdo = a:cchar . 'do' + let Xgetexpr = a:cchar . 'getexpr' + let Xprev = a:cchar. 'prev' + let XdoCmd = Xdo . ' call add(l, GetRuler())' + + " Try with an empty list + let l = [] + exe XdoCmd + call assert_equal([], l) + + " Populate the list and then try + exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:3:1:Line3']" + + let l = [] + exe XdoCmd + call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l) + + " Run command only on selected error lines + let l = [] + enew + exe "2,3" . XdoCmd + call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l) + + " Boundary condition tests + let l = [] + enew + exe "1,1" . XdoCmd + call assert_equal(['Xtestfile1 1L 3C'], l) + + let l = [] + enew + exe "3" . XdoCmd + call assert_equal(['Xtestfile3 3L 1C'], l) + + " Range test commands + let l = [] + enew + exe "%" . XdoCmd + call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l) + + let l = [] + enew + exe "1,$" . XdoCmd + call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l) + + let l = [] + enew + exe Xprev + exe "." . XdoCmd + call assert_equal(['Xtestfile2 2L 2C'], l) + + let l = [] + enew + exe "+" . XdoCmd + call assert_equal(['Xtestfile3 3L 1C'], l) + + " Invalid error lines test + let l = [] + enew + exe "silent! 27" . XdoCmd + exe "silent! 4,5" . XdoCmd + call assert_equal([], l) + + " Run commands from an unsaved buffer + let v:errmsg='' + let l = [] + enew + setlocal modified + exe "silent! 2,2" . XdoCmd + if v:errmsg !~# 'No write since last change' + call add(v:errors, 'Unsaved file change test failed') + endif + + " If the executed command fails, then the operation should be aborted + enew! + let subst_count = 0 + exe "silent!" . Xdo . " s/Line/xLine/ | let subst_count += 1" + if subst_count != 1 || getline('.') != 'xLine1' + call add(v:errors, 'Abort command on error test failed') + endif + + let l = [] + exe "2,2" . Xdo . "! call add(l, GetRuler())" + call assert_equal(['Xtestfile2 2L 2C'], l) + + " List with no valid error entries + let l = [] + edit! +2 Xtestfile1 + exe Xgetexpr . " ['non-error 1', 'non-error 2', 'non-error 3']" + exe XdoCmd + call assert_equal([], l) + exe "silent! 2" . XdoCmd + call assert_equal([], l) + let v:errmsg='' + exe "%" . XdoCmd + exe "1,$" . XdoCmd + exe "." . XdoCmd + call assert_equal('', v:errmsg) + + " List with only one valid entry + let l = [] + exe Xgetexpr . " ['Xtestfile3:3:1:Line3']" + exe XdoCmd + call assert_equal(['Xtestfile3 3L 1C'], l) + +endfunc + +" Tests for the :cfdo and :lfdo commands +func XfdoTests(cchar) + enew + + " Shortcuts for calling the cfdo and lfdo commands + let Xfdo = a:cchar . 'fdo' + let Xgetexpr = a:cchar . 'getexpr' + let XfdoCmd = Xfdo . ' call add(l, GetRuler())' + let Xpfile = a:cchar. 'pfile' + + " Clear the quickfix/location list + exe Xgetexpr . " []" + + " Try with an empty list + let l = [] + exe XfdoCmd + call assert_equal([], l) + + " Populate the list and then try + exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'Xtestfile1:2:1:Line2', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:2:3:Line2', 'Xtestfile3:3:1:Line3']" + + let l = [] + exe XfdoCmd + call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l) + + " Run command only on selected error lines + let l = [] + exe "2,3" . XfdoCmd + call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l) + + " Boundary condition tests + let l = [] + exe "3" . XfdoCmd + call assert_equal(['Xtestfile3 2L 3C'], l) + + " Range test commands + let l = [] + exe "%" . XfdoCmd + call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l) + + let l = [] + exe "1,$" . XfdoCmd + call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l) + + let l = [] + exe Xpfile + exe "." . XfdoCmd + call assert_equal(['Xtestfile2 2L 2C'], l) + + " List with only one valid entry + let l = [] + exe Xgetexpr . " ['Xtestfile2:2:5:Line2']" + exe XfdoCmd + call assert_equal(['Xtestfile2 2L 5C'], l) + +endfunc + +" Tests for cdo and cfdo +func Test_cdo() + call XdoTests('c') + call XfdoTests('c') +endfunc + +" Tests for ldo and lfdo +func Test_ldo() + call XdoTests('l') + call XfdoTests('l') +endfunc + +" Test for making 'shm' doesn't interfere with the output. +func Test_cdo_print() + enew | only! + cgetexpr ["Xtestfile1:1:Line1", "Xtestfile2:1:Line1", "Xtestfile3:1:Line1"] + cdo print + call assert_equal('Line1', Screenline(&lines)) + call assert_equal('Line1', Screenline(&lines - 3)) + call assert_equal('Line1', Screenline(&lines - 6)) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_charsearch.vim b/src/nvim/testdir/test_charsearch.vim index 17a49e02be..6f09e85a42 100644 --- a/src/nvim/testdir/test_charsearch.vim +++ b/src/nvim/testdir/test_charsearch.vim @@ -20,7 +20,7 @@ func Test_charsearch() " check that setcharsearch() changes the settings. 3 normal! ylfep - call setcharsearch({'char': 'k'}) + eval {'char': 'k'}->setcharsearch() normal! ;p call setcharsearch({'forward': 0}) normal! $;p diff --git a/src/nvim/testdir/test_charsearch_utf8.vim b/src/nvim/testdir/test_charsearch_utf8.vim index 09341a90b0..82a807ac5b 100644 --- a/src/nvim/testdir/test_charsearch_utf8.vim +++ b/src/nvim/testdir/test_charsearch_utf8.vim @@ -1,7 +1,7 @@ " Tests for related f{char} and t{char} using utf-8. " Test for t,f,F,T movement commands -function! Test_search_cmds() +func Test_search_cmds() new! call setline(1, "・最åˆã‹ã‚‰æœ€å¾Œã¾ã§æœ€å¼·ã®Vimã¯æœ€é«˜") 1 diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 562867f548..4d69aed96c 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -1,6 +1,5 @@ " Test for cinoptions and cindent " -" TODO: rewrite test3.in into this new style test func Test_cino_hash() " Test that curbuf->b_ind_hash_comment is correctly reset @@ -128,15 +127,5233 @@ func Test_cindent_func() bwipe! endfunc +func Test_cindent_1() + new + setl cindent ts=4 sw=4 + setl cino& sts& + + let code =<< trim [CODE] + /* start of AUTO matically checked vim: set ts=4 : */ + { + if (test) + cmd1; + cmd2; + } + + { + if (test) + cmd1; + else + cmd2; + } + + { + if (test) + { + cmd1; + cmd2; + } + } + + { + if (test) + { + cmd1; + else + } + } + + { + while (this) + if (test) + cmd1; + cmd2; + } + + { + while (this) + if (test) + cmd1; + else + cmd2; + } + + { + if (test) + { + cmd; + } + + if (test) + cmd; + } + + { + if (test) { + cmd; + } + + if (test) cmd; + } + + { + cmd1; + for (blah) + while (this) + if (test) + cmd2; + cmd3; + } + + { + cmd1; + for (blah) + while (this) + if (test) + cmd2; + cmd3; + + if (test) + { + cmd1; + cmd2; + cmd3; + } + } + + + /* Test for 'cindent' do/while mixed with if/else: */ + + { + do + if (asdf) + asdfasd; + while (cond); + + do + if (asdf) + while (asdf) + asdf; + while (asdf); + } + + /* Test for 'cindent' with two ) on a continuation line */ + { + if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d + aal;sdkjf ( ;asldfkja;sldfk + al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) + line up here; + } + + + /* C++ tests: */ + + // foo() these three lines should remain in column 0 + // { + // } + + /* Test for continuation and unterminated lines: */ + { + i = 99 + 14325 + + 21345 + + 21345 + + 21345 + ( 21345 + + 21345) + + 2345 + + 1234; + c = 1; + } + + /* + testje for indent with empty line + + here */ + + { + if (testing && + not a joke || + line up here) + hay; + if (testing && + (not a joke || testing + )line up here) + hay; + if (testing && + (not a joke || testing + line up here)) + hay; + } + + + { + switch (c) + { + case xx: + do + if (asdf) + do + asdfasdf; + while (asdf); + else + asdfasdf; + while (cond); + case yy: + case xx: + case zz: + testing; + } + } + + { + if (cond) { + foo; + } + else + { + bar; + } + } + + { + if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf + alsdkfj (asldk;fj + awith cino=(0 ;lf this one goes to below the paren with == + ;laksjfd ;lsakdjf ;alskdf asd) + asdfasdf;))) + asdfasdf; + } + + int + func(a, b) + int a; + int c; + { + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3) + ) + } + + { + while (asd) + { + if (asdf) + if (test) + if (that) + { + if (asdf) + do + cdasd; + while (as + df); + } + else + if (asdf) + asdf; + else + asdf; + asdf; + } + } + + { + s = "/*"; b = ';' + s = "/*"; b = ';'; + a = b; + } + + { + switch (a) + { + case a: + switch (t) + { + case 1: + cmd; + break; + case 2: + cmd; + break; + } + cmd; + break; + case b: + { + int i; + cmd; + } + break; + case c: { + int i; + cmd; + } + case d: if (cond && + test) { /* this line doesn't work right */ + int i; + cmd; + } + break; + } + } + + { + if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && + (bp_to->b_p_initialized || + (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) + return; + label : + asdf = asdf ? + asdf : asdf; + asdf = asdf ? + asdf: asdf; + } + + /* Special Comments : This function has the added complexity (compared */ + /* : to addtolist) of having to check for a detail */ + /* : texture and add that to the list first. */ + + char *(array[100]) = { + "testje", + "foo", + "bar", + } + + enum soppie + { + yes = 0, + no, + maybe + }; + + typedef enum soppie + { + yes = 0, + no, + maybe + }; + + static enum + { + yes = 0, + no, + maybe + } soppie; + + public static enum + { + yes = 0, + no, + maybe + } soppie; + + static private enum + { + yes = 0, + no, + maybe + } soppie; + + { + int a, + b; + } + + { + struct Type + { + int i; + char *str; + } var[] = + { + 0, "zero", + 1, "one", + 2, "two", + 3, "three" + }; + + float matrix[3][3] = + { + { + 0, + 1, + 2 + }, + { + 3, + 4, + 5 + }, + { + 6, + 7, + 8 + } + }; + } + + { + /* blah ( blah */ + /* where does this go? */ + + /* blah ( blah */ + cmd; + + func(arg1, + /* comment */ + arg2); + a; + { + b; + { + c; /* Hey, NOW it indents?! */ + } + } + + { + func(arg1, + arg2, + arg3); + /* Hey, what am I doing here? Is this coz of the ","? */ + } + } + + main () + { + if (cond) + { + a = b; + } + if (cond) { + a = c; + } + if (cond) + a = d; + return; + } + + { + case 2: if (asdf && + asdfasdf) + aasdf; + a = 9; + case 3: if (asdf) + aasdf; + a = 9; + case 4: x = 1; + y = 2; + + label: if (asdf) + here; + + label: if (asdf && + asdfasdf) + { + } + + label: if (asdf && + asdfasdf) { + there; + } + + label: if (asdf && + asdfasdf) + there; + } + + { + /* + hello with ":set comments= cino=c5" + */ + + /* + hello with ":set comments= cino=" + */ + } + + + { + if (a < b) { + a = a + 1; + } else + a = a + 2; + + if (a) + do { + testing; + } while (asdfasdf); + a = b + 1; + asdfasdf + } + + { + for ( int i = 0; + i < 10; i++ ) + { + } + i = 0; + } + + class bob + { + int foo() {return 1;} + int bar; + } + + main() + { + while(1) + if (foo) + { + bar; + } + else { + asdf; + } + misplacedline; + } + + { + if (clipboard.state == SELECT_DONE + && ((row == clipboard.start.lnum + && col >= clipboard.start.col) + || row > clipboard.start.lnum)) + } + + { + if (1) {i += 4;} + where_am_i; + return 0; + } + + { + { + } // sdf(asdf + if (asdf) + asd; + } + + { + label1: + label2: + } + + { + int fooRet = foo(pBar1, false /*fKB*/, + true /*fPTB*/, 3 /*nT*/, false /*fDF*/); + f() { + for ( i = 0; + i < m; + /* c */ i++ ) { + a = b; + } + } + } + + { + f1(/*comment*/); + f2(); + } + + { + do { + if (foo) { + } else + ; + } while (foo); + foo(); // was wrong + } + + int x; // no extra indent because of the ; + void func() + { + } + + char *tab[] = {"aaa", + "};", /* }; */ NULL} + int indented; + {} + + char *a[] = {"aaa", "bbb", + "ccc", NULL}; + // here + + char *tab[] = {"aaa", + "xx", /* xx */}; /* asdf */ + int not_indented; + + { + do { + switch (bla) + { + case 1: if (foo) + bar; + } + } while (boo); + wrong; + } + + int foo, + bar; + int foo; + + #if defined(foo) \ + && defined(bar) + char * xx = "asdf\ + foo\ + bor"; + int x; + + char *foo = "asdf\ + asdf\ + asdf", + *bar; + + void f() + { + #if defined(foo) \ + && defined(bar) + char *foo = "asdf\ + asdf\ + asdf", + *bar; + { + int i; + char *foo = "asdf\ + asdf\ + asdf", + *bar; + } + #endif + } + #endif + + int y; // comment + // comment + + // comment + + { + Constructor(int a, + int b ) : BaseClass(a) + { + } + } + + void foo() + { + char one, + two; + struct bla piet, + jan; + enum foo kees, + jannie; + static unsigned sdf, + krap; + unsigned int piet, + jan; + int + kees, + jan; + } + + { + t(int f, + int d); // ) + d(); + } + + Constructor::Constructor(int a, + int b + ) : + BaseClass(a, + b, + c), + mMember(b), + { + } + + Constructor::Constructor(int a, + int b ) : + BaseClass(a) + { + } + + Constructor::Constructor(int a, + int b ) /*x*/ : /*x*/ BaseClass(a), + member(b) + { + } + + A::A(int a, int b) + : aa(a), + bb(b), + cc(c) + { + } + + class CAbc : + public BaseClass1, + protected BaseClass2 + { + int Test() { return FALSE; } + int Test1() { return TRUE; } + + CAbc(int a, int b ) : + BaseClass(a) + { + switch(xxx) + { + case abc: + asdf(); + break; + + case 999: + baer(); + break; + } + } + + public: // <-- this was incorrectly indented before!! + void testfall(); + protected: + void testfall(); + }; + + class CAbc : public BaseClass1, + protected BaseClass2 + { + }; + + static struct + { + int a; + int b; + } variable[COUNT] = + { + { + 123, + 456 + }, + { + 123, + 456 + } + }; + + static struct + { + int a; + int b; + } variable[COUNT] = + { + { 123, 456 }, + { 123, 456 } + }; + + void asdf() /* ind_maxparen may cause trouble here */ + { + if ((0 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1)) break; + } + + foo() + { + a = cond ? foo() : asdf + + asdf; + + a = cond ? + foo() : asdf + + asdf; + } + + int main(void) + { + if (a) + if (b) + 2; + else 3; + next_line_of_code(); + } + + barry() + { + Foo::Foo (int one, + int two) + : something(4) + {} + } + + barry() + { + Foo::Foo (int one, int two) + : something(4) + {} + } + + Constructor::Constructor(int a, + int b + ) : + BaseClass(a, + b, + c), + mMember(b) + { + } + int main () + { + if (lala) + do + ++(*lolo); + while (lili + && lele); + lulu; + } + + int main () + { + switch (c) + { + case 'c': if (cond) + { + } + } + } + + main() + { + (void) MyFancyFuasdfadsfnction( + argument); + } + + main() + { + char foo[] = "/*"; + /* as + df */ + hello + } + + /* valid namespaces with normal indent */ + namespace + { + { + 111111111111; + } + } + namespace /* test */ + { + 11111111111111111; + } + namespace // test + { + 111111111111111111; + } + namespace + { + 111111111111111111; + } + namespace test + { + 111111111111111111; + } + namespace{ + 111111111111111111; + } + namespace test{ + 111111111111111111; + } + namespace { + 111111111111111111; + } + namespace test { + 111111111111111111; + namespace test2 { + 22222222222222222; + } + } + inline namespace { + 111111111111111111; + } + inline /* test */ namespace { + 111111111111111111; + } + inline/* test */namespace { + 111111111111111111; + } + + /* invalid namespaces use block indent */ + namespace test test2 { + 111111111111111111111; + } + namespace11111111111 { + 111111111111; + } + namespace() { + 1111111111111; + } + namespace() + { + 111111111111111111; + } + namespace test test2 + { + 1111111111111111111; + } + namespace111111111 + { + 111111111111111111; + } + inlinenamespace { + 111111111111111111; + } + + void getstring() { + /* Raw strings */ + const char* s = R"( + test { + # comment + field: 123 + } + )"; + } + + void getstring() { + const char* s = R"foo( + test { + # comment + field: 123 + } + )foo"; + } + + { + int a[4] = { + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 3, + }; + } + + { + a = b[2] + + 3; + } + + { + if (1) + /* aaaaa + * bbbbb + */ + a = 1; + } + + void func() + { + switch (foo) + { + case (bar): + if (baz()) + quux(); + break; + case (shmoo): + if (!bar) + { + } + case (foo1): + switch (bar) + { + case baz: + baz_f(); + break; + } + break; + default: + baz(); + baz(); + break; + } + } + + /* end of AUTO */ + [CODE] + + call append(0, code) + normal gg + call search('start of AUTO') + exe "normal =/end of AUTO\<CR>" + + let expected =<< trim [CODE] + /* start of AUTO matically checked vim: set ts=4 : */ + { + if (test) + cmd1; + cmd2; + } + + { + if (test) + cmd1; + else + cmd2; + } + + { + if (test) + { + cmd1; + cmd2; + } + } + + { + if (test) + { + cmd1; + else + } + } + + { + while (this) + if (test) + cmd1; + cmd2; + } + + { + while (this) + if (test) + cmd1; + else + cmd2; + } + + { + if (test) + { + cmd; + } + + if (test) + cmd; + } + + { + if (test) { + cmd; + } + + if (test) cmd; + } + + { + cmd1; + for (blah) + while (this) + if (test) + cmd2; + cmd3; + } + + { + cmd1; + for (blah) + while (this) + if (test) + cmd2; + cmd3; + + if (test) + { + cmd1; + cmd2; + cmd3; + } + } + + + /* Test for 'cindent' do/while mixed with if/else: */ + + { + do + if (asdf) + asdfasd; + while (cond); + + do + if (asdf) + while (asdf) + asdf; + while (asdf); + } + + /* Test for 'cindent' with two ) on a continuation line */ + { + if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d + aal;sdkjf ( ;asldfkja;sldfk + al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) + line up here; + } + + + /* C++ tests: */ + + // foo() these three lines should remain in column 0 + // { + // } + + /* Test for continuation and unterminated lines: */ + { + i = 99 + 14325 + + 21345 + + 21345 + + 21345 + ( 21345 + + 21345) + + 2345 + + 1234; + c = 1; + } + + /* + testje for indent with empty line + + here */ + + { + if (testing && + not a joke || + line up here) + hay; + if (testing && + (not a joke || testing + )line up here) + hay; + if (testing && + (not a joke || testing + line up here)) + hay; + } + + + { + switch (c) + { + case xx: + do + if (asdf) + do + asdfasdf; + while (asdf); + else + asdfasdf; + while (cond); + case yy: + case xx: + case zz: + testing; + } + } + + { + if (cond) { + foo; + } + else + { + bar; + } + } + + { + if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf + alsdkfj (asldk;fj + awith cino=(0 ;lf this one goes to below the paren with == + ;laksjfd ;lsakdjf ;alskdf asd) + asdfasdf;))) + asdfasdf; + } + + int + func(a, b) + int a; + int c; + { + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3) + ) + } + + { + while (asd) + { + if (asdf) + if (test) + if (that) + { + if (asdf) + do + cdasd; + while (as + df); + } + else + if (asdf) + asdf; + else + asdf; + asdf; + } + } + + { + s = "/*"; b = ';' + s = "/*"; b = ';'; + a = b; + } + + { + switch (a) + { + case a: + switch (t) + { + case 1: + cmd; + break; + case 2: + cmd; + break; + } + cmd; + break; + case b: + { + int i; + cmd; + } + break; + case c: { + int i; + cmd; + } + case d: if (cond && + test) { /* this line doesn't work right */ + int i; + cmd; + } + break; + } + } + + { + if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && + (bp_to->b_p_initialized || + (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) + return; + label : + asdf = asdf ? + asdf : asdf; + asdf = asdf ? + asdf: asdf; + } + + /* Special Comments : This function has the added complexity (compared */ + /* : to addtolist) of having to check for a detail */ + /* : texture and add that to the list first. */ + + char *(array[100]) = { + "testje", + "foo", + "bar", + } + + enum soppie + { + yes = 0, + no, + maybe + }; + + typedef enum soppie + { + yes = 0, + no, + maybe + }; + + static enum + { + yes = 0, + no, + maybe + } soppie; + + public static enum + { + yes = 0, + no, + maybe + } soppie; + + static private enum + { + yes = 0, + no, + maybe + } soppie; + + { + int a, + b; + } + + { + struct Type + { + int i; + char *str; + } var[] = + { + 0, "zero", + 1, "one", + 2, "two", + 3, "three" + }; + + float matrix[3][3] = + { + { + 0, + 1, + 2 + }, + { + 3, + 4, + 5 + }, + { + 6, + 7, + 8 + } + }; + } + + { + /* blah ( blah */ + /* where does this go? */ + + /* blah ( blah */ + cmd; + + func(arg1, + /* comment */ + arg2); + a; + { + b; + { + c; /* Hey, NOW it indents?! */ + } + } + + { + func(arg1, + arg2, + arg3); + /* Hey, what am I doing here? Is this coz of the ","? */ + } + } + + main () + { + if (cond) + { + a = b; + } + if (cond) { + a = c; + } + if (cond) + a = d; + return; + } + + { + case 2: if (asdf && + asdfasdf) + aasdf; + a = 9; + case 3: if (asdf) + aasdf; + a = 9; + case 4: x = 1; + y = 2; + + label: if (asdf) + here; + + label: if (asdf && + asdfasdf) + { + } + + label: if (asdf && + asdfasdf) { + there; + } + + label: if (asdf && + asdfasdf) + there; + } + + { + /* + hello with ":set comments= cino=c5" + */ + + /* + hello with ":set comments= cino=" + */ + } + + + { + if (a < b) { + a = a + 1; + } else + a = a + 2; + + if (a) + do { + testing; + } while (asdfasdf); + a = b + 1; + asdfasdf + } + + { + for ( int i = 0; + i < 10; i++ ) + { + } + i = 0; + } + + class bob + { + int foo() {return 1;} + int bar; + } + + main() + { + while(1) + if (foo) + { + bar; + } + else { + asdf; + } + misplacedline; + } + + { + if (clipboard.state == SELECT_DONE + && ((row == clipboard.start.lnum + && col >= clipboard.start.col) + || row > clipboard.start.lnum)) + } + + { + if (1) {i += 4;} + where_am_i; + return 0; + } + + { + { + } // sdf(asdf + if (asdf) + asd; + } + + { + label1: + label2: + } + + { + int fooRet = foo(pBar1, false /*fKB*/, + true /*fPTB*/, 3 /*nT*/, false /*fDF*/); + f() { + for ( i = 0; + i < m; + /* c */ i++ ) { + a = b; + } + } + } + + { + f1(/*comment*/); + f2(); + } + + { + do { + if (foo) { + } else + ; + } while (foo); + foo(); // was wrong + } + + int x; // no extra indent because of the ; + void func() + { + } + + char *tab[] = {"aaa", + "};", /* }; */ NULL} + int indented; + {} + + char *a[] = {"aaa", "bbb", + "ccc", NULL}; + // here + + char *tab[] = {"aaa", + "xx", /* xx */}; /* asdf */ + int not_indented; + + { + do { + switch (bla) + { + case 1: if (foo) + bar; + } + } while (boo); + wrong; + } + + int foo, + bar; + int foo; + + #if defined(foo) \ + && defined(bar) + char * xx = "asdf\ + foo\ + bor"; + int x; + + char *foo = "asdf\ + asdf\ + asdf", + *bar; + + void f() + { + #if defined(foo) \ + && defined(bar) + char *foo = "asdf\ + asdf\ + asdf", + *bar; + { + int i; + char *foo = "asdf\ + asdf\ + asdf", + *bar; + } + #endif + } + #endif + + int y; // comment + // comment + + // comment + + { + Constructor(int a, + int b ) : BaseClass(a) + { + } + } + + void foo() + { + char one, + two; + struct bla piet, + jan; + enum foo kees, + jannie; + static unsigned sdf, + krap; + unsigned int piet, + jan; + int + kees, + jan; + } + + { + t(int f, + int d); // ) + d(); + } + + Constructor::Constructor(int a, + int b + ) : + BaseClass(a, + b, + c), + mMember(b), + { + } + + Constructor::Constructor(int a, + int b ) : + BaseClass(a) + { + } + + Constructor::Constructor(int a, + int b ) /*x*/ : /*x*/ BaseClass(a), + member(b) + { + } + + A::A(int a, int b) + : aa(a), + bb(b), + cc(c) + { + } + + class CAbc : + public BaseClass1, + protected BaseClass2 + { + int Test() { return FALSE; } + int Test1() { return TRUE; } + + CAbc(int a, int b ) : + BaseClass(a) + { + switch(xxx) + { + case abc: + asdf(); + break; + + case 999: + baer(); + break; + } + } + + public: // <-- this was incorrectly indented before!! + void testfall(); + protected: + void testfall(); + }; + + class CAbc : public BaseClass1, + protected BaseClass2 + { + }; + + static struct + { + int a; + int b; + } variable[COUNT] = + { + { + 123, + 456 + }, + { + 123, + 456 + } + }; + + static struct + { + int a; + int b; + } variable[COUNT] = + { + { 123, 456 }, + { 123, 456 } + }; + + void asdf() /* ind_maxparen may cause trouble here */ + { + if ((0 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1)) break; + } + + foo() + { + a = cond ? foo() : asdf + + asdf; + + a = cond ? + foo() : asdf + + asdf; + } + + int main(void) + { + if (a) + if (b) + 2; + else 3; + next_line_of_code(); + } + + barry() + { + Foo::Foo (int one, + int two) + : something(4) + {} + } + + barry() + { + Foo::Foo (int one, int two) + : something(4) + {} + } + + Constructor::Constructor(int a, + int b + ) : + BaseClass(a, + b, + c), + mMember(b) + { + } + int main () + { + if (lala) + do + ++(*lolo); + while (lili + && lele); + lulu; + } + + int main () + { + switch (c) + { + case 'c': if (cond) + { + } + } + } + + main() + { + (void) MyFancyFuasdfadsfnction( + argument); + } + + main() + { + char foo[] = "/*"; + /* as + df */ + hello + } + + /* valid namespaces with normal indent */ + namespace + { + { + 111111111111; + } + } + namespace /* test */ + { + 11111111111111111; + } + namespace // test + { + 111111111111111111; + } + namespace + { + 111111111111111111; + } + namespace test + { + 111111111111111111; + } + namespace{ + 111111111111111111; + } + namespace test{ + 111111111111111111; + } + namespace { + 111111111111111111; + } + namespace test { + 111111111111111111; + namespace test2 { + 22222222222222222; + } + } + inline namespace { + 111111111111111111; + } + inline /* test */ namespace { + 111111111111111111; + } + inline/* test */namespace { + 111111111111111111; + } + + /* invalid namespaces use block indent */ + namespace test test2 { + 111111111111111111111; + } + namespace11111111111 { + 111111111111; + } + namespace() { + 1111111111111; + } + namespace() + { + 111111111111111111; + } + namespace test test2 + { + 1111111111111111111; + } + namespace111111111 + { + 111111111111111111; + } + inlinenamespace { + 111111111111111111; + } + + void getstring() { + /* Raw strings */ + const char* s = R"( + test { + # comment + field: 123 + } + )"; + } + + void getstring() { + const char* s = R"foo( + test { + # comment + field: 123 + } + )foo"; + } + + { + int a[4] = { + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 3, + }; + } + + { + a = b[2] + + 3; + } + + { + if (1) + /* aaaaa + * bbbbb + */ + a = 1; + } + + void func() + { + switch (foo) + { + case (bar): + if (baz()) + quux(); + break; + case (shmoo): + if (!bar) + { + } + case (foo1): + switch (bar) + { + case baz: + baz_f(); + break; + } + break; + default: + baz(); + baz(); + break; + } + } + + /* end of AUTO */ + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_2() + new + setl cindent ts=4 sw=4 + setl tw=0 noai fo=croq + let &wm = &columns - 20 + + let code =<< trim [CODE] + { + + /* this is + * a real serious important big + * comment + */ + /* insert " about life, the universe, and the rest" after "serious" */ + } + [CODE] + + call append(0, code) + normal gg + call search('serious', 'e') + normal a about life, the universe, and the rest + + let expected =<< trim [CODE] + { + + /* this is + * a real serious + * about life, the + * universe, and the + * rest important big + * comment + */ + /* insert " about life, the universe, and the rest" after "serious" */ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + set wm& + enew! | close +endfunc + +func Test_cindent_3() + new + setl nocindent ts=4 sw=4 + + let code =<< trim [CODE] + { + /* + * Testing for comments, without 'cin' set + */ + + /* + * what happens here? + */ + + /* + the end of the comment, try inserting a line below */ + + /* how about + this one */ + } + [CODE] + + call append(0, code) + normal gg + call search('comments') + normal joabout life + call search('happens') + normal jothere + call search('below') + normal oline + call search('this') + normal Ohello + + let expected =<< trim [CODE] + { + /* + * Testing for comments, without 'cin' set + */ + about life + + /* + * what happens here? + */ + there + + /* + the end of the comment, try inserting a line below */ + line + + /* how about + hello + this one */ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_4() + new + setl cindent ts=4 sw=4 + + let code =<< trim [CODE] + { + var = this + that + vec[0] * vec[0] + + vec[1] * vec[1] + + vec2[2] * vec[2]; + } + [CODE] + + call append(0, code) + normal gg + call search('vec2') + normal == + + let expected =<< trim [CODE] + { + var = this + that + vec[0] * vec[0] + + vec[1] * vec[1] + + vec2[2] * vec[2]; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_5() + new + setl cindent ts=4 sw=4 + setl cino=}4 + + let code =<< trim [CODE] + { + asdf asdflkajds f; + if (tes & ting) { + asdf asdf asdf ; + asdfa sdf asdf; + } + testing1; + if (tes & ting) + { + asdf asdf asdf ; + asdfa sdf asdf; + } + testing2; + } + [CODE] + + call append(0, code) + normal gg + call search('testing1') + exe "normal k2==/testing2\<CR>" + normal k2== + + let expected =<< trim [CODE] + { + asdf asdflkajds f; + if (tes & ting) { + asdf asdf asdf ; + asdfa sdf asdf; + } + testing1; + if (tes & ting) + { + asdf asdf asdf ; + asdfa sdf asdf; + } + testing2; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_6() + new + setl cindent ts=4 sw=4 + setl cino=(0,)20 + + let code =<< trim [CODE] + main ( int first_par, /* + * Comment for + * first par + */ + int second_par /* + * Comment for + * second par + */ + ) + { + func( first_par, /* + * Comment for + * first par + */ + second_par /* + * Comment for + * second par + */ + ); + + } + [CODE] + + call append(0, code) + normal gg + call search('main') + normal =][ + + let expected =<< trim [CODE] + main ( int first_par, /* + * Comment for + * first par + */ + int second_par /* + * Comment for + * second par + */ + ) + { + func( first_par, /* + * Comment for + * first par + */ + second_par /* + * Comment for + * second par + */ + ); + + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_7() + new + setl cindent ts=4 sw=4 + setl cino=es,n0s + + let code =<< trim [CODE] + main(void) + { + /* Make sure that cino=X0s is not parsed like cino=Xs. */ + if (cond) + foo(); + else + { + bar(); + } + } + [CODE] + + call append(0, code) + normal gg + call search('main') + normal =][ + + let expected =<< trim [CODE] + main(void) + { + /* Make sure that cino=X0s is not parsed like cino=Xs. */ + if (cond) + foo(); + else + { + bar(); + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_8() + new + setl cindent ts=4 sw=4 + setl cino= + + let code =<< trim [CODE] + + { + do + { + if () + { + if () + asdf; + else + asdf; + } + } while (); + cmd; /* this should go under the } */ + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + + { + do + { + if () + { + if () + asdf; + else + asdf; + } + } while (); + cmd; /* this should go under the } */ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_9() + new + setl cindent ts=4 sw=4 + + let code =<< trim [CODE] + + void f() + { + if ( k() ) { + l(); + + } else { /* Start (two words) end */ + m(); + } + + n(); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + + void f() + { + if ( k() ) { + l(); + + } else { /* Start (two words) end */ + m(); + } + + n(); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_10() + new + setl cindent ts=4 sw=4 + setl cino={s,e-s + + let code =<< trim [CODE] + + void f() + { + if ( k() ) + { + l(); + } else { /* Start (two words) end */ + m(); + } + n(); /* should be under the if () */ + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + + void f() + { + if ( k() ) + { + l(); + } else { /* Start (two words) end */ + m(); + } + n(); /* should be under the if () */ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_11() + new + setl cindent ts=4 sw=4 + setl cino={s,fs + + let code =<< trim [CODE] + void bar(void) + { + static array[2][2] = + { + { 1, 2 }, + { 3, 4 }, + } + + while (a) + { + foo(&a); + } + + { + int a; + { + a = a + 1; + } + } + b = a; + } + + void func(void) + { + a = 1; + { + b = 2; + } + c = 3; + d = 4; + } + /* foo */ + [CODE] + + call append(0, code) + normal gg + exe "normal ]]=/ foo\<CR>" + + let expected =<< trim [CODE] + void bar(void) + { + static array[2][2] = + { + { 1, 2 }, + { 3, 4 }, + } + + while (a) + { + foo(&a); + } + + { + int a; + { + a = a + 1; + } + } + b = a; + } + + void func(void) + { + a = 1; + { + b = 2; + } + c = 3; + d = 4; + } + /* foo */ + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_12() + new + setl cindent ts=4 sw=4 + setl cino= + + let code =<< trim [CODE] + a() + { + do { + a = a + + a; + } while ( a ); /* add text under this line */ + if ( a ) + a; + } + [CODE] + + call append(0, code) + normal gg + call search('while') + normal ohere + + let expected =<< trim [CODE] + a() + { + do { + a = a + + a; + } while ( a ); /* add text under this line */ + here + if ( a ) + a; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_13() + new + setl cindent ts=4 sw=4 + setl cino= com= + + let code =<< trim [CODE] + a() + { + label1: + /* hmm */ + // comment + } + [CODE] + + call append(0, code) + normal gg + call search('comment') + exe "normal olabel2: b();\rlabel3 /* post */:\r/* pre */ label4:\r" . + \ "f(/*com*/);\rif (/*com*/)\rcmd();" + + let expected =<< trim [CODE] + a() + { + label1: + /* hmm */ + // comment + label2: b(); + label3 /* post */: + /* pre */ label4: + f(/*com*/); + if (/*com*/) + cmd(); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_14() + new + setl cindent ts=4 sw=4 + setl comments& comments^=s:/*,m:**,ex:*/ + + let code =<< trim [CODE] + /* + * A simple comment + */ + + /* + ** A different comment + */ + [CODE] + + call append(0, code) + normal gg + call search('simple') + normal =5j + + let expected =<< trim [CODE] + /* + * A simple comment + */ + + /* + ** A different comment + */ + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_15() + new + setl cindent ts=4 sw=4 + setl cino=c0 + setl comments& comments-=s1:/* comments^=s0:/* + + let code =<< trim [CODE] + void f() + { + + /********* + A comment. + *********/ + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + + /********* + A comment. + *********/ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_16() + new + setl cindent ts=4 sw=4 + setl cino=c0,C1 + setl comments& comments-=s1:/* comments^=s0:/* + + let code =<< trim [CODE] + void f() + { + + /********* + A comment. + *********/ + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + + /********* + A comment. + *********/ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_17() + new + setl cindent ts=4 sw=4 + setl cino= + + let code =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_18() + new + setl cindent ts=4 sw=4 + setl cino=(s + + let code =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_19() + new + setl cindent ts=4 sw=4 + set cino=(s,U1 + + let code =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_20() + new + setl cindent ts=4 sw=4 + setl cino=(0 + + let code =<< trim [CODE] + void f() + { + if ( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + if ( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_21() + new + setl cindent ts=4 sw=4 + setl cino=(0,w1 + + let code =<< trim [CODE] + void f() + { + if ( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + if ( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_22() + new + setl cindent ts=4 sw=4 + setl cino=(s + + let code =<< trim [CODE] + void f() + { + c = c1 && ( + c2 || + c3 + ) && c4; + if ( + c1 && c2 + ) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && ( + c2 || + c3 + ) && c4; + if ( + c1 && c2 + ) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_23() + new + setl cindent ts=4 sw=4 + setl cino=(s,m1 + + let code =<< trim [CODE] + void f() + { + c = c1 && ( + c2 || + c3 + ) && c4; + if ( + c1 && c2 + ) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && ( + c2 || + c3 + ) && c4; + if ( + c1 && c2 + ) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_24() + new + setl cindent ts=4 sw=4 + setl cino=b1 + + let code =<< trim [CODE] + void f() + { + switch (x) + { + case 1: + a = b; + break; + default: + a = 0; + break; + } + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + switch (x) + { + case 1: + a = b; + break; + default: + a = 0; + break; + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_25() + new + setl cindent ts=4 sw=4 + setl cino=(0,W5 + + let code =<< trim [CODE] + void f() + { + invokeme( + argu, + ment); + invokeme( + argu, + ment + ); + invokeme(argu, + ment + ); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + invokeme( + argu, + ment); + invokeme( + argu, + ment + ); + invokeme(argu, + ment + ); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_26() + new + setl cindent ts=4 sw=4 + setl cino=/6 + + let code =<< trim [CODE] + void f() + { + statement; + // comment 1 + // comment 2 + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + statement; + // comment 1 + // comment 2 + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_27() + new + setl cindent ts=4 sw=4 + setl cino= + + let code =<< trim [CODE] + void f() + { + statement; + // comment 1 + // comment 2 + } + [CODE] + + call append(0, code) + normal gg + exe "normal ]]/comment 1/+1\<CR>==" + + let expected =<< trim [CODE] + void f() + { + statement; + // comment 1 + // comment 2 + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_28() + new + setl cindent ts=4 sw=4 + setl cino=g0 + + let code =<< trim [CODE] + class CAbc + { + int Test() { return FALSE; } + + public: // comment + void testfall(); + protected: + void testfall(); + }; + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + class CAbc + { + int Test() { return FALSE; } + + public: // comment + void testfall(); + protected: + void testfall(); + }; + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_29() + new + setl cindent ts=4 sw=4 + setl cino=(0,gs,hs + + let code =<< trim [CODE] + class Foo : public Bar + { + public: + virtual void method1(void) = 0; + virtual void method2(int arg1, + int arg2, + int arg3) = 0; + }; + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + class Foo : public Bar + { + public: + virtual void method1(void) = 0; + virtual void method2(int arg1, + int arg2, + int arg3) = 0; + }; + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_30() + new + setl cindent ts=4 sw=4 + setl cino=+20 + + let code =<< [CODE] + void +foo() +{ + if (a) + { + } else + asdf; +} +[CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< [CODE] + void +foo() +{ + if (a) + { + } else + asdf; +} + +[CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_31() + new + setl cindent ts=4 sw=4 + setl cino=(0,W2s + + let code =<< trim [CODE] + + { + averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( + asdasdf, + func(asdf, + asdfadsf), + asdfasdf + ); + + /* those are ugly, but consequent */ + + func()->asd(asdasdf, + averylongfunctionname( + abc, + dec)->averylongfunctionname( + asdfadsf, + asdfasdf, + asdfasdf, + ), + func(asdfadf, + asdfasdf + ), + asdasdf + ); + + averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( + abc, + dec)->asdfasdfasdf( + asdfadsf, + asdfasdf, + asdfasdf, + ), + func(asdfadf, + asdfasdf), + asdasdf + ); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + + { + averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( + asdasdf, + func(asdf, + asdfadsf), + asdfasdf + ); + + /* those are ugly, but consequent */ + + func()->asd(asdasdf, + averylongfunctionname( + abc, + dec)->averylongfunctionname( + asdfadsf, + asdfasdf, + asdfasdf, + ), + func(asdfadf, + asdfasdf + ), + asdasdf + ); + + averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( + abc, + dec)->asdfasdfasdf( + asdfadsf, + asdfasdf, + asdfasdf, + ), + func(asdfadf, + asdfasdf), + asdasdf + ); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_32() + new + setl cindent ts=4 sw=4 + setl cino=M1 + + let code =<< trim [CODE] + int main () + { + if (cond1 && + cond2 + ) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + int main () + { + if (cond1 && + cond2 + ) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_33() + new + setl cindent ts=4 sw=4 + setl cino=(0,ts + + let code =<< trim [CODE] + void func(int a + #if defined(FOO) + , int b + , int c + #endif + ) + { + } + [CODE] + + call append(0, code) + normal gg + normal 2j=][ + + let expected =<< trim [CODE] + void func(int a + #if defined(FOO) + , int b + , int c + #endif + ) + { + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_34() + new + setl cindent ts=4 sw=4 + setl cino=(0 + + let code =<< trim [CODE] + + void + func(int a + #if defined(FOO) + , int b + , int c + #endif + ) + { + } + [CODE] + + call append(0, code) + normal gg + normal =][ + + let expected =<< trim [CODE] + + void + func(int a + #if defined(FOO) + , int b + , int c + #endif + ) + { + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_35() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + void func(void) + { + if(x==y) + if(y==z) + foo=1; + else { bar=1; + baz=2; + } + printf("Foo!\n"); + } + + void func1(void) + { + char* tab[] = {"foo", "bar", + "baz", "quux", + "this line used", "to be indented incorrectly"}; + foo(); + } + + void func2(void) + { + int tab[] = + {1, 2, + 3, 4, + 5, 6}; + + printf("This line used to be indented incorrectly.\n"); + } + + int foo[] + #ifdef BAR + + = { 1, 2, 3, + 4, 5, 6 } + + #endif + ; + int baz; + + void func3(void) + { + int tab[] = { + 1, 2, + 3, 4, + 5, 6}; + + printf("Don't you dare indent this line incorrectly!\n"); + } + + void + func4(a, b, + c) + int a; + int b; + int c; + { + } + + void + func5( + int a, + int b) + { + } + + void + func6( + int a) + { + } + [CODE] + + call append(0, code) + normal gg + normal ]]=7][ + + let expected =<< trim [CODE] + void func(void) + { + if(x==y) + if(y==z) + foo=1; + else { bar=1; + baz=2; + } + printf("Foo!\n"); + } + + void func1(void) + { + char* tab[] = {"foo", "bar", + "baz", "quux", + "this line used", "to be indented incorrectly"}; + foo(); + } + + void func2(void) + { + int tab[] = + {1, 2, + 3, 4, + 5, 6}; + + printf("This line used to be indented incorrectly.\n"); + } + + int foo[] + #ifdef BAR + + = { 1, 2, 3, + 4, 5, 6 } + + #endif + ; + int baz; + + void func3(void) + { + int tab[] = { + 1, 2, + 3, 4, + 5, 6}; + + printf("Don't you dare indent this line incorrectly!\n"); + } + + void + func4(a, b, + c) + int a; + int b; + int c; + { + } + + void + func5( + int a, + int b) + { + } + + void + func6( + int a) + { + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_36() + new + setl cindent ts=4 sw=4 + setl cino& + setl cino+=l1 + + let code =<< trim [CODE] + void func(void) + { + int tab[] = + { + 1, 2, 3, + 4, 5, 6}; + + printf("Indent this line correctly!\n"); + + switch (foo) + { + case bar: + printf("bar"); + break; + case baz: { + printf("baz"); + break; + } + case quux: + printf("But don't break the indentation of this instruction\n"); + break; + } + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + int tab[] = + { + 1, 2, 3, + 4, 5, 6}; + + printf("Indent this line correctly!\n"); + + switch (foo) + { + case bar: + printf("bar"); + break; + case baz: { + printf("baz"); + break; + } + case quux: + printf("But don't break the indentation of this instruction\n"); + break; + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_37() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + void func(void) + { + cout << "a" + << "b" + << ") :" + << "c"; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + cout << "a" + << "b" + << ") :" + << "c"; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_38() + new + setl cindent ts=4 sw=4 + setl com=s1:/*,m:*,ex:*/ + + let code =<< trim [CODE] + void func(void) + { + /* + * This is a comment. + */ + } + [CODE] + + call append(0, code) + normal gg + normal ]]3jofoo(); + + let expected =<< trim [CODE] + void func(void) + { + /* + * This is a comment. + */ + foo(); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_39() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + void func(void) + { + for (int i = 0; i < 10; ++i) + if (i & 1) { + foo(1); + } else + foo(0); + baz(); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + for (int i = 0; i < 10; ++i) + if (i & 1) { + foo(1); + } else + foo(0); + baz(); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_40() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(0 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_41() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(s + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_42() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(s,U1 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + c = c1 && + ( + c2 || + c3 + ) && c4; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + c = c1 && + ( + c2 || + c3 + ) && c4; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_43() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(0,W4 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + if ( c1 + && ( c2 + || c3)) + foo; + + a_long_line( + argument, + argument); + a_short_line(argument, + argument); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + if ( c1 + && ( c2 + || c3)) + foo; + + a_long_line( + argument, + argument); + a_short_line(argument, + argument); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_44() + new + setl cindent ts=4 sw=4 + setl cino=k2s,u2 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_45() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(0,w1 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_46() + new + setl cindent ts=4 sw=4 + setl cino=k2,(s + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_47() + new + setl cindent ts=4 sw=4 + setl cino=N-s + + let code =<< trim [CODE] + NAMESPACESTART + /* valid namespaces with normal indent */ + namespace + { + { + 111111111111; + } + } + namespace /* test */ + { + 11111111111111111; + } + namespace // test + { + 111111111111111111; + } + namespace + { + 111111111111111111; + } + namespace test + { + 111111111111111111; + } + namespace test::cpp17 + { + 111111111111111111; + } + namespace ::incorrectcpp17 + { + 111111111111111111; + } + namespace test::incorrectcpp17:: + { + 111111111111111111; + } + namespace test:incorrectcpp17 + { + 111111111111111111; + } + namespace test:::incorrectcpp17 + { + 111111111111111111; + } + namespace{ + 111111111111111111; + } + namespace test{ + 111111111111111111; + } + namespace { + 111111111111111111; + } + namespace test { + 111111111111111111; + namespace test2 { + 22222222222222222; + } + } + inline namespace { + 111111111111111111; + } + inline /* test */ namespace { + 111111111111111111; + } + inline/* test */namespace { + 111111111111111111; + } + + /* invalid namespaces use block indent */ + namespace test test2 { + 111111111111111111111; + } + namespace11111111111 { + 111111111111; + } + namespace() { + 1111111111111; + } + namespace() + { + 111111111111111111; + } + namespace test test2 + { + 1111111111111111111; + } + namespace111111111 + { + 111111111111111111; + } + inlinenamespace { + 111111111111111111; + } + NAMESPACEEND + [CODE] + + call append(0, code) + normal gg + call search('^NAMESPACESTART') + exe "normal =/^NAMESPACEEND\n" + + let expected =<< trim [CODE] + NAMESPACESTART + /* valid namespaces with normal indent */ + namespace + { + { + 111111111111; + } + } + namespace /* test */ + { + 11111111111111111; + } + namespace // test + { + 111111111111111111; + } + namespace + { + 111111111111111111; + } + namespace test + { + 111111111111111111; + } + namespace test::cpp17 + { + 111111111111111111; + } + namespace ::incorrectcpp17 + { + 111111111111111111; + } + namespace test::incorrectcpp17:: + { + 111111111111111111; + } + namespace test:incorrectcpp17 + { + 111111111111111111; + } + namespace test:::incorrectcpp17 + { + 111111111111111111; + } + namespace{ + 111111111111111111; + } + namespace test{ + 111111111111111111; + } + namespace { + 111111111111111111; + } + namespace test { + 111111111111111111; + namespace test2 { + 22222222222222222; + } + } + inline namespace { + 111111111111111111; + } + inline /* test */ namespace { + 111111111111111111; + } + inline/* test */namespace { + 111111111111111111; + } + + /* invalid namespaces use block indent */ + namespace test test2 { + 111111111111111111111; + } + namespace11111111111 { + 111111111111; + } + namespace() { + 1111111111111; + } + namespace() + { + 111111111111111111; + } + namespace test test2 + { + 1111111111111111111; + } + namespace111111111 + { + 111111111111111111; + } + inlinenamespace { + 111111111111111111; + } + NAMESPACEEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_48() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + var bar = { + foo: { + that: this, + some: ok, + }, + "bar":{ + a : 2, + b: "123abc", + x: 4, + "y": 5 + } + } + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + var bar = { + foo: { + that: this, + some: ok, + }, + "bar":{ + a : 2, + b: "123abc", + x: 4, + "y": 5 + } + } + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_49() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + var foo = [ + 1, + 2, + 3 + ]; + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + var foo = [ + 1, + 2, + 3 + ]; + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_50() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + function bar() { + var foo = [ + 1, + 2, + 3 + ]; + } + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + function bar() { + var foo = [ + 1, + 2, + 3 + ]; + } + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_51() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + (function($){ + + if (cond && + cond) { + stmt; + } + window.something.left = + (width - 50 + offset) + "px"; + var class_name='myclass'; + + function private_method() { + } + + var public_method={ + method: function(options,args){ + private_method(); + } + } + + function init(options) { + + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + + $.fn[class_name]=function() { + + var _arguments=arguments; + return this.each(function(){ + + var options=$(this).data(class_name+'_public'); + if (!options) { + init.apply(this,_arguments); + + } else { + var method=public_method[_arguments[0]]; + + if (typeof(method)!='function') { + console.log(class_name+' has no method "'+_arguments[0]+'"'); + return false; + } + _arguments[0]=options; + method.apply(this,_arguments); + } + }); + } + + })(jQuery); + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + (function($){ + + if (cond && + cond) { + stmt; + } + window.something.left = + (width - 50 + offset) + "px"; + var class_name='myclass'; + + function private_method() { + } + + var public_method={ + method: function(options,args){ + private_method(); + } + } + + function init(options) { + + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + + $.fn[class_name]=function() { + + var _arguments=arguments; + return this.each(function(){ + + var options=$(this).data(class_name+'_public'); + if (!options) { + init.apply(this,_arguments); + + } else { + var method=public_method[_arguments[0]]; + + if (typeof(method)!='function') { + console.log(class_name+' has no method "'+_arguments[0]+'"'); + return false; + } + _arguments[0]=options; + method.apply(this,_arguments); + } + }); + } + + })(jQuery); + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_52() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + function init(options) { + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + function init(options) { + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_53() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + (function($){ + function init(options) { + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + })(jQuery); + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + (function($){ + function init(options) { + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + })(jQuery); + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_54() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1,+2 + + let code =<< trim [CODE] + JSSTART + // Results of JavaScript indent + // 1 + (function(){ + var a = [ + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 2 + (function(){ + var a = [ + 0 + + 5 * + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 3 + (function(){ + var a = [ + 0 + + // comment 1 + 5 * + /* comment 2 */ + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 4 + { + var a = [ + 0, + 1 + ]; + var b; + var c; + } + + // 5 + { + var a = [ + [ + 0 + ], + 2, + 3 + ]; + } + + // 6 + { + var a = [ + [ + 0, + 1 + ], + 2, + 3 + ]; + } + + // 7 + { + var a = [ + // [ + 0, + // 1 + // ], + 2, + 3 + ]; + } + + // 8 + var x = [ + (function(){ + var a, + b, + c, + d, + e, + f, + g, + h, + i; + }) + ]; + + // 9 + var a = [ + 0 + + 5 * + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + + // 10 + var a, + b, + c, + d, + e, + f, + g, + h, + i; + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + // Results of JavaScript indent + // 1 + (function(){ + var a = [ + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 2 + (function(){ + var a = [ + 0 + + 5 * + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 3 + (function(){ + var a = [ + 0 + + // comment 1 + 5 * + /* comment 2 */ + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 4 + { + var a = [ + 0, + 1 + ]; + var b; + var c; + } + + // 5 + { + var a = [ + [ + 0 + ], + 2, + 3 + ]; + } + + // 6 + { + var a = [ + [ + 0, + 1 + ], + 2, + 3 + ]; + } + + // 7 + { + var a = [ + // [ + 0, + // 1 + // ], + 2, + 3 + ]; + } + + // 8 + var x = [ + (function(){ + var a, + b, + c, + d, + e, + f, + g, + h, + i; + }) + ]; + + // 9 + var a = [ + 0 + + 5 * + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + + // 10 + var a, + b, + c, + d, + e, + f, + g, + h, + i; + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_55() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + /* start of define */ + { + } + #define AAA \ + BBB\ + CCC + + #define CNT \ + 1 + \ + 2 + \ + 4 + /* end of define */ + [CODE] + + call append(0, code) + normal gg + call search('start of define') + exe "normal =/end of define\n" + + let expected =<< trim [CODE] + /* start of define */ + { + } + #define AAA \ + BBB\ + CCC + + #define CNT \ + 1 + \ + 2 + \ + 4 + /* end of define */ + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_56() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + { + a = second/*bug*/*line; + } + [CODE] + + call append(0, code) + normal gg + call search('a = second') + normal ox + + let expected =<< trim [CODE] + { + a = second/*bug*/*line; + x + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + " this was going beyond the end of the line. func Test_cindent_case() new - call setline(1, "case x: // x") + call setline(1, 'case x: // x') set cindent norm! f:a: + call assert_equal('case x:: // x', getline(1)) + + set cindent& bwipe! endfunc +func Test_cindent_scopedecls() + new + setl cindent ts=4 sw=4 + setl cino=g0 + setl cinsd+=public\ slots,signals + + let code =<< trim [CODE] + class Foo + { + public: + virtual void foo() = 0; + public slots: + void onBar(); + signals: + void baz(); + private: + int x; + }; + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + class Foo + { + public: + virtual void foo() = 0; + public slots: + void onBar(); + signals: + void baz(); + private: + int x; + }; + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + func Test_cindent_pragma() new setl cindent ts=4 sw=4 @@ -173,4 +5390,23 @@ func Test_cindent_pragma() enew! | close endfunc +func Test_backslash_at_end_of_line() + new + exe "norm v>O'\\\<C-m>-" + exe "norm \<C-q>=" + bwipe! +endfunc + +func Test_find_brace_backwards() + " this was looking beyond the end of the line + new + norm R/* + norm o0{ + norm o// + norm V{= + call assert_equal(['/*', ' 0{', '//'], getline(1, 3)) + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index f3db472b03..922803438f 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -82,7 +82,7 @@ func Test_client_server() call remote_send(name, ":call server2client(expand('<client>'), 'got it')\<CR>", 'g:myserverid') call assert_equal('got it', g:myserverid->remote_read(2)) - call remote_send(name, ":call server2client(expand('<client>'), 'another')\<CR>", 'g:myserverid') + call remote_send(name, ":eval expand('<client>')->server2client('another')\<CR>", 'g:myserverid') let peek_result = 'nothing' let r = g:myserverid->remote_peek('peek_result') " unpredictable whether the result is already available. diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 98340d0ac6..c589d941da 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -428,14 +428,17 @@ func Test_getcompletion() call assert_equal([], l) func T(a, c, p) + let g:cmdline_compl_params = [a:a, a:c, a:p] return "oneA\noneB\noneC" endfunc command -nargs=1 -complete=custom,T MyCmd let l = getcompletion('MyCmd ', 'cmdline') call assert_equal(['oneA', 'oneB', 'oneC'], l) + call assert_equal(['', 'MyCmd ', 6], g:cmdline_compl_params) delcommand MyCmd delfunc T + unlet g:cmdline_compl_params " For others test if the name is recognized. let names = ['buffer', 'environment', 'file_in_path', 'mapping', 'tag', 'tag_listfiles', 'user'] @@ -464,6 +467,51 @@ func Test_getcompletion() call assert_fails('call getcompletion("abc", [])', 'E475:') endfunc +func Test_fullcommand() + let tests = { + \ '': '', + \ ':': '', + \ ':::': '', + \ ':::5': '', + \ 'not_a_cmd': '', + \ 'Check': '', + \ 'syntax': 'syntax', + \ ':syntax': 'syntax', + \ '::::syntax': 'syntax', + \ 'sy': 'syntax', + \ 'syn': 'syntax', + \ 'synt': 'syntax', + \ ':sy': 'syntax', + \ '::::sy': 'syntax', + \ 'match': 'match', + \ '2match': 'match', + \ '3match': 'match', + \ 'aboveleft': 'aboveleft', + \ 'abo': 'aboveleft', + \ 's': 'substitute', + \ '5s': 'substitute', + \ ':5s': 'substitute', + \ "'<,'>s": 'substitute', + \ ":'<,'>s": 'substitute', + \ 'CheckUni': 'CheckUnix', + \ 'CheckUnix': 'CheckUnix', + \ } + + for [in, want] in items(tests) + call assert_equal(want, fullcommand(in)) + endfor + call assert_equal('', fullcommand(v:_null_string)) + + call assert_equal('syntax', 'syn'->fullcommand()) + + command -buffer BufferLocalCommand : + command GlobalCommand : + call assert_equal('GlobalCommand', fullcommand('GlobalCom')) + call assert_equal('BufferLocalCommand', fullcommand('BufferL')) + delcommand BufferLocalCommand + delcommand GlobalCommand +endfunc + func Test_shellcmd_completion() let save_path = $PATH @@ -902,7 +950,7 @@ func Test_setcmdpos() call assert_equal('"12ab', @:) " setcmdpos() returns 1 when not editing the command line. - call assert_equal(1, setcmdpos(3)) + call assert_equal(1, 3->setcmdpos()) endfunc func Test_cmdline_overstrike() @@ -1075,6 +1123,18 @@ func Test_cmdlineclear_tabenter() call delete('XtestCmdlineClearTabenter') endfunc +func Test_cmdwin_tabpage() + tabedit + " v8.2.1919 isn't ported yet, so E492 is thrown after E11 here. + " v8.2.1183 also isn't ported yet, so we also can't assert E11 directly. + " For now, assert E11 and E492 seperately. When v8.2.1183 is ported, the + " assert for E492 will fail and this workaround should be removed. + " call assert_fails("silent norm q/g :I\<Esc>", 'E11:') + call assert_fails("silent norm q/g ", 'E11:') + call assert_fails("silent norm q/g :I\<Esc>", 'E492:') + tabclose! +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 c3de7d0050..c0c572ce65 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -19,6 +19,9 @@ func Test_compiler() call assert_equal('perl', b:current_compiler) call assert_fails('let g:current_compiler', 'E121:') + let verbose_efm = execute('verbose set efm') + call assert_match('Last set from .*[/\\]compiler[/\\]perl.vim ', verbose_efm) + call setline(1, ['#!/usr/bin/perl -w', 'use strict;', 'my $foo=1']) w! call feedkeys(":make\<CR>\<CR>", 'tx') @@ -38,10 +41,9 @@ func Test_compiler() endfunc func GetCompilerNames() - " return glob('$VIMRUNTIME/compiler/*.vim', 0, 1) - " \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')}) - " \ ->sort() - return sort(map(glob('$VIMRUNTIME/compiler/*.vim', 0, 1), {i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')})) + return glob('$VIMRUNTIME/compiler/*.vim', 0, 1) + \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')}) + \ ->sort() endfunc func Test_compiler_without_arg() @@ -54,8 +56,7 @@ func Test_compiler_without_arg() endfunc func Test_compiler_completion() - " let clist = GetCompilerNames()->join(' ') - let clist = join(GetCompilerNames(), ' ') + let clist = GetCompilerNames()->join(' ') call feedkeys(":compiler \<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('^"compiler ' .. clist .. '$', @:) diff --git a/src/nvim/testdir/test_conceal.vim b/src/nvim/testdir/test_conceal.vim index 1306dbe5cf..bffc2f49d3 100644 --- a/src/nvim/testdir/test_conceal.vim +++ b/src/nvim/testdir/test_conceal.vim @@ -4,10 +4,10 @@ source check.vim CheckFeature conceal source screendump.vim -" CheckScreendump func Test_conceal_two_windows() CheckScreendump + let code =<< trim [CODE] let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"] call setline(1, lines) @@ -111,6 +111,7 @@ endfunc func Test_conceal_with_cursorline() CheckScreendump + " Opens a help window, where 'conceal' is set, switches to the other window " where 'cursorline' needs to be updated when the cursor moves. let code =<< trim [CODE] @@ -139,6 +140,7 @@ endfunc func Test_conceal_resize_term() CheckScreendump + let code =<< trim [CODE] call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed') setl cocu=n cole=3 diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim index cc6154af69..faf37485cd 100644 --- a/src/nvim/testdir/test_cscope.vim +++ b/src/nvim/testdir/test_cscope.vim @@ -102,7 +102,7 @@ func Test_cscopeWithCscopeConnections() for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c'] enew let a = execute(cmd) - call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+C') + call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+B') call assert_equal('Xmemfile_test.c', @%) endfor @@ -112,7 +112,7 @@ func Test_cscopeWithCscopeConnections() let a = execute(cmd) let alines = split(a, '\n', 1) call assert_equal('', alines[0]) - call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+C') + call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+B') call assert_equal('(1 of 1): <<global>> #include <assert.h>', alines[2]) call assert_equal('#include <assert.h>', getline('.')) endfor diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index 46847e0663..9ba82e3b70 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -1,4 +1,4 @@ -" Tests for cursor(). +" Tests for cursor() and other functions that get/set the cursor position func Test_wrong_arguments() call assert_fails('call cursor(1. 3)', 'E474:') @@ -24,6 +24,9 @@ func Test_move_cursor() " below last line goes to last line call cursor(9, 1) call assert_equal([4, 1, 0, 1], getcurpos()[1:]) + " pass string arguments + call cursor('3', '3') + call assert_equal([3, 3, 0, 3], getcurpos()[1:]) call setline(1, ["\<TAB>"]) call cursor(1, 1, 1) @@ -37,7 +40,7 @@ endfunc " Very short version of what matchparen does. function s:Highlight_Matching_Pair() let save_cursor = getcurpos() - call setpos('.', save_cursor) + eval save_cursor->setpos('.') endfunc func Test_curswant_with_autocommand() @@ -72,7 +75,6 @@ func Test_curswant_with_cursorline() endfunc func Test_screenpos() - throw 'skipped: TODO: ' rightbelow new rightbelow 20vsplit call setline(1, ["\tsome text", "long wrapping line here", "next line"]) @@ -82,11 +84,11 @@ func Test_screenpos() call assert_equal({'row': winrow, \ 'col': wincol + 0, \ 'curscol': wincol + 7, - \ 'endcol': wincol + 7}, screenpos(winid, 1, 1)) + \ 'endcol': wincol + 7}, winid->screenpos(1, 1)) call assert_equal({'row': winrow, \ 'col': wincol + 13, \ 'curscol': wincol + 13, - \ 'endcol': wincol + 13}, screenpos(winid, 1, 7)) + \ 'endcol': wincol + 13}, winid->screenpos(1, 7)) call assert_equal({'row': winrow + 2, \ 'col': wincol + 1, \ 'curscol': wincol + 1, @@ -100,9 +102,10 @@ func Test_screenpos() bwipe! call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) - nmenu WinBar.TEST : - call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) - nunmenu WinBar.TEST + " Needs WinBar + " nmenu WinBar.TEST : + " call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) + " nunmenu WinBar.TEST endfunc func Test_screenpos_number() @@ -119,3 +122,253 @@ func Test_screenpos_number() close bwipe! endfunc + +" Save the visual start character position +func SaveVisualStartCharPos() + call add(g:VisualStartPos, getcharpos('v')) + return '' +endfunc + +" Save the current cursor character position in insert mode +func SaveInsertCurrentCharPos() + call add(g:InsertCurrentPos, getcharpos('.')) + return '' +endfunc + +" Test for the getcharpos() function +func Test_getcharpos() + call assert_fails('call getcharpos({})', 'E731:') + call assert_equal([0, 0, 0, 0], getcharpos(0)) + new + call setline(1, ['', "01\tà 4è678", 'â…¥', '012345678', ' │ x']) + + " Test for '.' and '$' + normal 1G + call assert_equal([0, 1, 1, 0], getcharpos('.')) + call assert_equal([0, 5, 1, 0], getcharpos('$')) + normal 2G6l + call assert_equal([0, 2, 7, 0], getcharpos('.')) + normal 3G$ + call assert_equal([0, 3, 1, 0], getcharpos('.')) + normal 4G$ + call assert_equal([0, 4, 9, 0], getcharpos('.')) + + " Test for a mark + normal 2G7lmmgg + call assert_equal([0, 2, 8, 0], getcharpos("'m")) + delmarks m + call assert_equal([0, 0, 0, 0], getcharpos("'m")) + + " Check mark does not move + normal 5Gfxma + call assert_equal([0, 5, 5, 0], getcharpos("'a")) + call assert_equal([0, 5, 5, 0], getcharpos("'a")) + call assert_equal([0, 5, 5, 0], getcharpos("'a")) + + " Test for the visual start column + vnoremap <expr> <F3> SaveVisualStartCharPos() + let g:VisualStartPos = [] + exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>" + call assert_equal([[0, 2, 7, 0], [0, 2, 10, 0], [0, 2, 5, 0]], g:VisualStartPos) + call assert_equal([0, 2, 9, 0], getcharpos('v')) + let g:VisualStartPos = [] + exe "normal 3Gv$\<F3>o\<F3>" + call assert_equal([[0, 3, 1, 0], [0, 3, 2, 0]], g:VisualStartPos) + let g:VisualStartPos = [] + exe "normal 1Gv$\<F3>o\<F3>" + call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos) + vunmap <F3> + + " Test for getting the position in insert mode with the cursor after the + " last character in a line + inoremap <expr> <F3> SaveInsertCurrentCharPos() + let g:InsertCurrentPos = [] + exe "normal 1GA\<F3>" + exe "normal 2GA\<F3>" + exe "normal 3GA\<F3>" + exe "normal 4GA\<F3>" + exe "normal 2G6li\<F3>" + call assert_equal([[0, 1, 1, 0], [0, 2, 10, 0], [0, 3, 2, 0], [0, 4, 10, 0], + \ [0, 2, 7, 0]], g:InsertCurrentPos) + iunmap <F3> + + %bw! +endfunc + +" Test for the setcharpos() function +func Test_setcharpos() + call assert_equal(-1, setcharpos('.', v:_null_list)) + new + call setline(1, ['', "01\tà 4è678", 'â…¥', '012345678']) + call setcharpos('.', [0, 1, 1, 0]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 4, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 1, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 20, 0]) + call assert_equal([4, 9], [line('.'), col('.')]) + + " Test for mark + delmarks m + call setcharpos("'m", [0, 2, 9, 0]) + normal `m + call assert_equal([2, 11], [line('.'), col('.')]) + " unload the buffer and try to set the mark + let bnr = bufnr() + enew! + call assert_equal(-1, setcharpos("'m", [bnr, 2, 2, 0])) + + %bw! + call assert_equal(-1, setcharpos('.', [10, 3, 1, 0])) +endfunc + +func SaveVisualStartCharCol() + call add(g:VisualStartCol, charcol('v')) + return '' +endfunc + +func SaveInsertCurrentCharCol() + call add(g:InsertCurrentCol, charcol('.')) + return '' +endfunc + +" Test for the charcol() function +func Test_charcol() + call assert_fails('call charcol({})', 'E731:') + call assert_equal(0, charcol(0)) + new + call setline(1, ['', "01\tà 4è678", 'â…¥', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal(1, charcol('.')) + call assert_equal(1, charcol('$')) + normal 2G6l + call assert_equal(7, charcol('.')) + call assert_equal(10, charcol('$')) + normal 3G$ + call assert_equal(1, charcol('.')) + call assert_equal(2, charcol('$')) + normal 4G$ + call assert_equal(9, charcol('.')) + call assert_equal(10, charcol('$')) + + " Test for [lnum, '$'] + call assert_equal(1, charcol([1, '$'])) + call assert_equal(10, charcol([2, '$'])) + call assert_equal(2, charcol([3, '$'])) + call assert_equal(0, charcol([5, '$'])) + + " Test for a mark + normal 2G7lmmgg + call assert_equal(8, charcol("'m")) + delmarks m + call assert_equal(0, charcol("'m")) + + " Test for the visual start column + vnoremap <expr> <F3> SaveVisualStartCharCol() + let g:VisualStartCol = [] + exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>" + call assert_equal([7, 10, 5], g:VisualStartCol) + call assert_equal(9, charcol('v')) + let g:VisualStartCol = [] + exe "normal 3Gv$\<F3>o\<F3>" + call assert_equal([1, 2], g:VisualStartCol) + let g:VisualStartCol = [] + exe "normal 1Gv$\<F3>o\<F3>" + call assert_equal([1, 1], g:VisualStartCol) + vunmap <F3> + + " Test for getting the column number in insert mode with the cursor after + " the last character in a line + inoremap <expr> <F3> SaveInsertCurrentCharCol() + let g:InsertCurrentCol = [] + exe "normal 1GA\<F3>" + exe "normal 2GA\<F3>" + exe "normal 3GA\<F3>" + exe "normal 4GA\<F3>" + exe "normal 2G6li\<F3>" + call assert_equal([1, 10, 2, 10, 7], g:InsertCurrentCol) + iunmap <F3> + + %bw! +endfunc + +func SaveInsertCursorCharPos() + call add(g:InsertCursorPos, getcursorcharpos('.')) + return '' +endfunc + +" Test for getcursorcharpos() +func Test_getcursorcharpos() + call assert_equal(getcursorcharpos(), getcursorcharpos(0)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999)) + + new + call setline(1, ['', "01\tà 4è678", 'â…¥', '012345678']) + normal 1G9l + call assert_equal([0, 1, 1, 0, 1], getcursorcharpos()) + normal 2G9l + call assert_equal([0, 2, 9, 0, 14], getcursorcharpos()) + normal 3G9l + call assert_equal([0, 3, 1, 0, 1], getcursorcharpos()) + normal 4G9l + call assert_equal([0, 4, 9, 0, 9], getcursorcharpos()) + + " Test for getting the cursor position in insert mode with the cursor after + " the last character in a line + inoremap <expr> <F3> SaveInsertCursorCharPos() + let g:InsertCursorPos = [] + exe "normal 1GA\<F3>" + exe "normal 2GA\<F3>" + exe "normal 3GA\<F3>" + exe "normal 4GA\<F3>" + exe "normal 2G6li\<F3>" + call assert_equal([[0, 1, 1, 0, 1], [0, 2, 10, 0, 15], [0, 3, 2, 0, 2], + \ [0, 4, 10, 0, 10], [0, 2, 7, 0, 12]], g:InsertCursorPos) + iunmap <F3> + + let winid = win_getid() + normal 2G5l + wincmd w + call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid)) + %bw! +endfunc + +" Test for setcursorcharpos() +func Test_setcursorcharpos() + call assert_fails('call setcursorcharpos(v:_null_list)', 'E474:') + call assert_fails('call setcursorcharpos([1])', 'E474:') + call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:') + new + call setline(1, ['', "01\tà 4è678", 'â…¥', '012345678']) + normal G + call setcursorcharpos([1, 1]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcursorcharpos([2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcursorcharpos(3, 4) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([3, 1]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 0, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 20]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos([100, 100, 100, 100]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos('$', 1) + call assert_equal([4, 1], [line('.'), col('.')]) + + %bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cursorline.vim b/src/nvim/testdir/test_cursorline.vim index 39d8b901ed..7e97df6027 100644 --- a/src/nvim/testdir/test_cursorline.vim +++ b/src/nvim/testdir/test_cursorline.vim @@ -268,4 +268,51 @@ END call delete('Xtextfile') endfunc +func Test_cursorline_callback() + CheckScreendump + CheckFeature timers + + let lines =<< trim END + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set cursorline + call cursor(4, 1) + + func Func(timer) + call cursor(2, 1) + endfunc + + call timer_start(300, 'Func') + END + call writefile(lines, 'Xcul_timer') + + let buf = RunVimInTerminal('-S Xcul_timer', #{rows: 8}) + call TermWait(buf, 310) + call VerifyScreenDump(buf, 'Test_cursorline_callback_1', {}) + + call StopVimInTerminal(buf) + call delete('Xcul_timer') +endfunc + +func Test_cursorline_screenline_update() + CheckScreendump + + let lines =<< trim END + call setline(1, repeat('xyz ', 30)) + set cursorline cursorlineopt=screenline + inoremap <F2> <Cmd>call cursor(1, 1)<CR> + END + call writefile(lines, 'Xcul_screenline') + + let buf = RunVimInTerminal('-S Xcul_screenline', #{rows: 8}) + call term_sendkeys(buf, "A") + call VerifyScreenDump(buf, 'Test_cursorline_screenline_1', {}) + call term_sendkeys(buf, "\<F2>") + call VerifyScreenDump(buf, 'Test_cursorline_screenline_2', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xcul_screenline') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_delete.vim b/src/nvim/testdir/test_delete.vim new file mode 100644 index 0000000000..b23a3bd025 --- /dev/null +++ b/src/nvim/testdir/test_delete.vim @@ -0,0 +1,114 @@ +" Test for delete(). + +func Test_file_delete() + split Xfile + call setline(1, ['a', 'b']) + wq + call assert_equal(['a', 'b'], readfile('Xfile')) + call assert_equal(0, delete('Xfile')) + call assert_fails('call readfile("Xfile")', 'E484:') + call assert_equal(-1, delete('Xfile')) + bwipe Xfile +endfunc + +func Test_dir_delete() + call mkdir('Xdir1') + call assert_true(isdirectory('Xdir1')) + call assert_equal(0, delete('Xdir1', 'd')) + call assert_false(isdirectory('Xdir1')) + call assert_equal(-1, delete('Xdir1', 'd')) +endfunc + +func Test_recursive_delete() + call mkdir('Xdir1') + call mkdir('Xdir1/subdir') + call mkdir('Xdir1/empty') + split Xdir1/Xfile + call setline(1, ['a', 'b']) + w + w Xdir1/subdir/Xfile + close + call assert_true(isdirectory('Xdir1')) + call assert_equal(['a', 'b'], readfile('Xdir1/Xfile')) + call assert_true(isdirectory('Xdir1/subdir')) + call assert_equal(['a', 'b'], readfile('Xdir1/subdir/Xfile')) + call assert_true('Xdir1/empty'->isdirectory()) + call assert_equal(0, delete('Xdir1', 'rf')) + call assert_false(isdirectory('Xdir1')) + call assert_equal(-1, delete('Xdir1', 'd')) + bwipe Xdir1/Xfile + bwipe Xdir1/subdir/Xfile +endfunc + +func Test_symlink_delete() + if !has('unix') + return + endif + split Xfile + call setline(1, ['a', 'b']) + wq + silent !ln -s Xfile Xlink + " Delete the link, not the file + call assert_equal(0, delete('Xlink')) + call assert_equal(-1, delete('Xlink')) + call assert_equal(0, delete('Xfile')) + bwipe Xfile +endfunc + +func Test_symlink_dir_delete() + if !has('unix') + return + endif + call mkdir('Xdir1') + silent !ln -s Xdir1 Xlink + call assert_true(isdirectory('Xdir1')) + call assert_true(isdirectory('Xlink')) + " Delete the link, not the directory + call assert_equal(0, delete('Xlink')) + call assert_equal(-1, delete('Xlink')) + call assert_equal(0, delete('Xdir1', 'd')) +endfunc + +func Test_symlink_recursive_delete() + if !has('unix') + return + endif + call mkdir('Xdir3') + call mkdir('Xdir3/subdir') + call mkdir('Xdir4') + split Xdir3/Xfile + call setline(1, ['a', 'b']) + w + w Xdir3/subdir/Xfile + w Xdir4/Xfile + close + silent !ln -s ../Xdir4 Xdir3/Xlink + + call assert_true(isdirectory('Xdir3')) + call assert_equal(['a', 'b'], readfile('Xdir3/Xfile')) + call assert_true(isdirectory('Xdir3/subdir')) + call assert_equal(['a', 'b'], readfile('Xdir3/subdir/Xfile')) + call assert_true(isdirectory('Xdir4')) + call assert_true(isdirectory('Xdir3/Xlink')) + call assert_equal(['a', 'b'], readfile('Xdir4/Xfile')) + + call assert_equal(0, delete('Xdir3', 'rf')) + call assert_false(isdirectory('Xdir3')) + call assert_equal(-1, delete('Xdir3', 'd')) + " symlink is deleted, not the directory it points to + call assert_true(isdirectory('Xdir4')) + call assert_equal(['a', 'b'], readfile('Xdir4/Xfile')) + call assert_equal(0, delete('Xdir4/Xfile')) + call assert_equal(0, delete('Xdir4', 'd')) + + bwipe Xdir3/Xfile + bwipe Xdir3/subdir/Xfile + bwipe Xdir4/Xfile +endfunc + +func Test_delete_errors() + call assert_fails('call delete('''')', 'E474:') + call assert_fails('call delete(''foo'', 0)', 'E15:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 61da3cbcaa..be9a77ee75 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -387,7 +387,7 @@ func Test_diffoff() call setline(1, ['One', '', 'Two', 'Three']) diffthis redraw - call assert_notequal(normattr, screenattr(1, 1)) + call assert_notequal(normattr, 1->screenattr(1)) diffoff! redraw call assert_equal(normattr, screenattr(1, 1)) @@ -1017,6 +1017,32 @@ func Test_diff_with_cursorline() call delete('Xtest_diff_cursorline') endfunc +func Test_diff_with_cursorline_number() + CheckScreendump + + let lines =<< trim END + hi CursorLine ctermbg=red ctermfg=white + hi CursorLineNr ctermbg=white ctermfg=black cterm=underline + set cursorline number + call setline(1, ["baz", "foo", "foo", "bar"]) + 2 + vnew + call setline(1, ["foo", "foo", "bar"]) + windo diffthis + 1wincmd w + END + call writefile(lines, 'Xtest_diff_cursorline_number') + let buf = RunVimInTerminal('-S Xtest_diff_cursorline_number', {}) + + call VerifyScreenDump(buf, 'Test_diff_with_cursorline_number_01', {}) + call term_sendkeys(buf, ":set cursorlineopt=number\r") + call VerifyScreenDump(buf, 'Test_diff_with_cursorline_number_02', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_diff_cursorline_number') +endfunc + func Test_diff_with_cursorline_breakindent() CheckScreendump @@ -1146,6 +1172,35 @@ func Test_diff_followwrap() bwipe! endfunc +func Test_diff_maintains_change_mark() + func DiffMaintainsChangeMark() + enew! + call setline(1, ['a', 'b', 'c', 'd']) + diffthis + new + call setline(1, ['a', 'b', 'c', 'e']) + " Set '[ and '] marks + 2,3yank + call assert_equal([2, 3], [line("'["), line("']")]) + " Verify they aren't affected by the implicit diff + diffthis + call assert_equal([2, 3], [line("'["), line("']")]) + " Verify they aren't affected by an explicit diff + diffupdate + call assert_equal([2, 3], [line("'["), line("']")]) + bwipe! + bwipe! + endfunc + + set diffopt-=internal + call DiffMaintainsChangeMark() + set diffopt+=internal + call DiffMaintainsChangeMark() + + set diffopt& + delfunc DiffMaintainsChangeMark +endfunc + func Test_diff_rnu() CheckScreendump @@ -1240,4 +1295,41 @@ func Test_diff_filler_cursorcolumn() endfunc +func Test_diff_binary() + CheckScreendump + + let content =<< trim END + call setline(1, ['a', 'b', "c\n", 'd', 'e', 'f', 'g']) + vnew + call setline(1, ['A', 'b', 'c', 'd', 'E', 'f', 'g']) + windo diffthis + wincmd p + norm! gg0 + redraw! + END + call writefile(content, 'Xtest_diff_bin') + let buf = RunVimInTerminal('-S Xtest_diff_bin', {}) + + " Test using internal diff + call VerifyScreenDump(buf, 'Test_diff_bin_01', {}) + + " Test using internal diff and case folding + call term_sendkeys(buf, ":set diffopt+=icase\<cr>") + call term_sendkeys(buf, "\<C-l>") + call VerifyScreenDump(buf, 'Test_diff_bin_02', {}) + " Test using external diff + call term_sendkeys(buf, ":set diffopt=filler\<cr>") + call term_sendkeys(buf, "\<C-l>") + call VerifyScreenDump(buf, 'Test_diff_bin_03', {}) + " Test using external diff and case folding + call term_sendkeys(buf, ":set diffopt=filler,icase\<cr>") + call term_sendkeys(buf, "\<C-l>") + call VerifyScreenDump(buf, 'Test_diff_bin_04', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_diff_bin') + set diffopt&vim +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index d23748a3e3..7b6bc940d3 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -81,7 +81,7 @@ func Test_digraphs() call Put_Dig(".e") call Put_Dig("a.") " not defined call assert_equal(['ḃ', 'Ä—', '.'], getline(line('.')-2,line('.'))) - " Diaresis + " Diaeresis call Put_Dig("a:") call Put_Dig(":u") call Put_Dig("b:") " not defined @@ -212,7 +212,7 @@ func Test_digraphs() call Put_Dig("el") call assert_equal(['â€', 'ü', '∞', 'l'], getline(line('.')-3,line('.'))) call assert_fails('digraph xy z', 'E39:') - call assert_fails('digraph x', 'E474:') + call assert_fails('digraph x', 'E1214:') bw! endfunc @@ -288,7 +288,7 @@ func Test_digraphs_option() call Put_Dig_BS(".","e") call Put_Dig_BS("a",".") " not defined call assert_equal(['ḃ', 'Ä—', '.'], getline(line('.')-2,line('.'))) - " Diaresis + " Diaeresis call Put_Dig_BS("a",":") call Put_Dig_BS(":","u") call Put_Dig_BS("b",":") " not defined @@ -505,4 +505,85 @@ func Test_entering_digraph() call StopVimInTerminal(buf) endfunc +func Test_digraph_set_function() + new + call digraph_set('aa', 'ã‚') + call Put_Dig('aa') + call assert_equal('ã‚', getline('$')) + call digraph_set(' i', 'ã„') + call Put_Dig(' i') + call assert_equal('ã„', getline('$')) + call digraph_set(' ', 'ã†') + call Put_Dig(' ') + call assert_equal('ã†', getline('$')) + + eval 'aa'->digraph_set('ãˆ') + call Put_Dig('aa') + call assert_equal('ãˆ', getline('$')) + + call assert_fails('call digraph_set("aaa", "ã‚")', 'E1214: Digraph must be just two characters: aaa') + call assert_fails('call digraph_set("b", "ã‚")', 'E1214: Digraph must be just two characters: b') + call assert_fails('call digraph_set("ã‚", "ã‚")', 'E1214: Digraph must be just two characters: ã‚') + call assert_fails('call digraph_set("aa", "ã‚ã‚")', 'E1215: Digraph must be one character: ã‚ã‚') + call assert_fails('call digraph_set("aa", "ã‹" .. nr2char(0x3099))', 'E1215: Digraph must be one character: ã‹' .. nr2char(0x3099)) + bwipe! +endfunc + +func Test_digraph_get_function() + " Built-in digraphs + call assert_equal('∞', digraph_get('00')) + + " User-defined digraphs + call digraph_set('aa', 'ã‚') + call digraph_set(' i', 'ã„') + call digraph_set(' ', 'ã†') + call assert_equal('ã‚', digraph_get('aa')) + call assert_equal('ã‚', 'aa'->digraph_get()) + call assert_equal('ã„', digraph_get(' i')) + call assert_equal('ã†', digraph_get(' ')) + call assert_fails('call digraph_get("aaa")', 'E1214: Digraph must be just two characters: aaa') + call assert_fails('call digraph_get("b")', 'E1214: Digraph must be just two characters: b') +endfunc + +func Test_digraph_get_function_encode() + throw 'Skipped: Nvim does not support setting encoding=japan' + CheckFeature iconv + + let testcases = { + \'00': '∞', + \'aa': 'ã‚', + \} + for [key, ch] in items(testcases) + call digraph_set(key, ch) + set encoding=japan + call assert_equal(iconv(ch, 'utf-8', 'japan'), digraph_get(key)) + set encoding=utf-8 + endfor +endfunc + +func Test_digraph_setlist_function() + call digraph_setlist([['aa', 'ã'], ['bb', 'ã']]) + call assert_equal('ã', digraph_get('aa')) + call assert_equal('ã', digraph_get('bb')) + + call assert_fails('call digraph_setlist([[]])', 'E1216:') + call assert_fails('call digraph_setlist([["aa", "b", "cc"]])', '1216:') + call assert_fails('call digraph_setlist([["ã‚", "ã‚"]])', 'E1214: Digraph must be just two characters: ã‚') +endfunc + +func Test_digraph_getlist_function() + " Make sure user-defined digraphs are defined + call digraph_setlist([['aa', 'ã'], ['bb', 'ã']]) + + for pair in digraph_getlist(1) + call assert_equal(digraph_get(pair[0]), pair[1]) + endfor + + " We don't know how many digraphs are registered before, so check the number + " of digraphs returned. + call assert_equal(digraph_getlist()->len(), digraph_getlist(0)->len()) + call assert_notequal((digraph_getlist()->len()), digraph_getlist(1)->len()) +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 12327f34d6..6938abbc28 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -41,7 +41,7 @@ func Test_display_foldcolumn() quit! endfunc -func! Test_display_foldtext_mbyte() +func Test_display_foldtext_mbyte() CheckFeature folding call NewWindow(10, 40) @@ -263,6 +263,52 @@ func Test_display_scroll_at_topline() call StopVimInTerminal(buf) endfunc +func Test_display_scroll_update_visual() + CheckScreendump + + let lines =<< trim END + set scrolloff=0 + call setline(1, repeat(['foo'], 10)) + call sign_define('foo', { 'text': '>' }) + call sign_place(1, 'bar', 'foo', bufnr(), { 'lnum': 2 }) + call sign_place(2, 'bar', 'foo', bufnr(), { 'lnum': 1 }) + autocmd CursorMoved * if getcurpos()[1] == 2 | call sign_unplace('bar', { 'id': 1 }) | endif + END + call writefile(lines, 'XupdateVisual.vim') + + let buf = RunVimInTerminal('-S XupdateVisual.vim', #{rows: 8, cols: 60}) + call term_sendkeys(buf, "VG7kk") + call VerifyScreenDump(buf, 'Test_display_scroll_update_visual', {}) + + call StopVimInTerminal(buf) + call delete('XupdateVisual.vim') +endfunc + +" Test for 'eob' (EndOfBuffer) item in 'fillchars' +func Test_eob_fillchars() + " default value (skipped) + " call assert_match('eob:\~', &fillchars) + " invalid values + call assert_fails(':set fillchars=eob:', 'E474:') + call assert_fails(':set fillchars=eob:xy', 'E474:') + call assert_fails(':set fillchars=eob:\255', 'E474:') + call assert_fails(':set fillchars=eob:<ff>', 'E474:') + call assert_fails(":set fillchars=eob:\x01", 'E474:') + call assert_fails(':set fillchars=eob:\\x01', 'E474:') + " default is ~ + new + redraw + call assert_equal('~', Screenline(2)) + set fillchars=eob:+ + redraw + call assert_equal('+', Screenline(2)) + set fillchars=eob:\ + redraw + call assert_equal(' ', nr2char(screenchar(2, 1))) + set fillchars& + close +endfunc + func Test_display_linebreak_breakat() new vert resize 25 @@ -279,4 +325,31 @@ func Test_display_linebreak_breakat() let &breakat=_breakat endfunc +func Test_display_lastline() + CheckScreendump + + let lines =<< trim END + call setline(1, ['aaa', 'b'->repeat(100)]) + set display=truncate + vsplit + 100wincmd < + END + call writefile(lines, 'XdispLastline') + let buf = RunVimInTerminal('-S XdispLastline', #{rows: 10}) + call VerifyScreenDump(buf, 'Test_display_lastline_1', {}) + + call term_sendkeys(buf, ":set display=lastline\<CR>") + call VerifyScreenDump(buf, 'Test_display_lastline_2', {}) + + call term_sendkeys(buf, ":100wincmd >\<CR>") + call VerifyScreenDump(buf, 'Test_display_lastline_3', {}) + + call term_sendkeys(buf, ":set display=truncate\<CR>") + call VerifyScreenDump(buf, 'Test_display_lastline_4', {}) + + call StopVimInTerminal(buf) + call delete('XdispLastline') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 23ad8dbfc5..eea5d190b2 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -213,7 +213,7 @@ func Test_edit_07() bw! endfunc -func! Test_edit_08() +func Test_edit_08() throw 'skipped: moved to test/functional/legacy/edit_spec.lua' " reset insertmode from i_ctrl-r_= let g:bufnr = bufnr('%') @@ -417,7 +417,7 @@ func Test_edit_13() bwipe! endfunc -func! Test_edit_CR() +func Test_edit_CR() " Test for <CR> in insert mode " basically only in quickfix mode ist tested, the rest " has been taken care of by other tests @@ -450,7 +450,7 @@ func! Test_edit_CR() call delete('Xqflist.txt') endfunc -func! Test_edit_CTRL_() +func Test_edit_CTRL_() " disabled for Windows builds, why? if !has("rightleft") || has("win32") return @@ -590,7 +590,7 @@ func Test_edit_CTRL_K() call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<down>\<cr>\<esc>", 'tnix') call assert_equal(['AA'], getline(1, '$')) - " press an unexecpted key after dictionary completion + " press an unexpected key after dictionary completion %d call setline(1, 'A') call cursor(1, 1) @@ -734,7 +734,7 @@ func Test_edit_CTRL_O() bw! endfunc -func! Test_edit_CTRL_R() +func Test_edit_CTRL_R() " Insert Register new " call test_override("ALL", 1) @@ -1006,16 +1006,14 @@ func Test_edit_DROP() endfunc func Test_edit_CTRL_V() - if has("ebcdic") - return - endif new call setline(1, ['abc']) call cursor(2, 1) + " force some redraws set showmode showcmd - "call test_override_char_avail(1) - " call test_override('ALL', 1) + " call test_override('char_avail', 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, '$')) @@ -1028,8 +1026,19 @@ func Test_edit_CTRL_V() set norl endif - " call test_override('ALL', 0) set noshowmode showcmd + " call test_override('char_avail', 0) + + " No modifiers should be applied to the char typed using i_CTRL-V_digit. + call feedkeys(":append\<CR>\<C-V>76c\<C-V>76\<C-F2>\<C-V>u3c0j\<C-V>u3c0\<M-F3>\<CR>.\<CR>", 'tnix') + call assert_equal('LcL<C-F2>Ï€jÏ€<M-F3>', getline(2)) + + if has('osx') + " A char with a modifier should not be a valid char for i_CTRL-V_digit. + call feedkeys("o\<C-V>\<D-j>\<C-V>\<D-1>\<C-V>\<D-o>\<C-V>\<D-x>\<C-V>\<D-u>", 'tnix') + call assert_equal('<D-j><D-1><D-o><D-x><D-u>', getline(3)) + endif + bw! endfunc @@ -1294,6 +1303,7 @@ func Test_edit_forbidden() call assert_fails(':Sandbox', 'E48:') delcom Sandbox call assert_equal(['a'], getline(1,'$')) + " 2) edit with textlock set fu! DoIt() call feedkeys("i\<del>\<esc>", 'tnix') @@ -1313,6 +1323,7 @@ func Test_edit_forbidden() catch /^Vim\%((\a\+)\)\=:E117/ " catch E117: unknown function endtry au! InsertCharPre + " 3) edit when completion is shown fun! Complete(findstart, base) if a:findstart @@ -1330,6 +1341,7 @@ func Test_edit_forbidden() endtry delfu Complete set completefunc= + if has("rightleft") && exists("+fkmap") " 4) 'R' when 'fkmap' and 'revins' is set. set revins fkmap @@ -1547,11 +1559,7 @@ endfunc func Test_edit_special_chars() new - if has("ebcdic") - let t = "o\<C-V>193\<C-V>xc2\<C-V>o303 \<C-V>90a\<C-V>xfg\<C-V>o578\<Esc>" - else - let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>" - endif + let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>" exe "normal " . t call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2)) @@ -1619,6 +1627,29 @@ func Test_edit_is_a_directory() call delete(dirname, 'rf') endfunc +" Using :edit without leaving 'insertmode' should not cause Insert mode to be +" re-entered immediately after <C-L> +func Test_edit_insertmode_ex_edit() + CheckRunVimInTerminal + + let lines =<< trim END + set insertmode noruler + inoremap <C-B> <Cmd>edit Xfoo<CR> + END + call writefile(lines, 'Xtest_edit_insertmode_ex_edit') + + let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6}) + call TermWait(buf, 50) + call assert_match('^-- INSERT --\s*$', term_getline(buf, 6)) + call term_sendkeys(buf, "\<C-B>\<C-L>") + call TermWait(buf, 50) + call assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6)) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_edit_insertmode_ex_edit') +endfunc + func Test_edit_browse() " in the GUI this opens a file picker, we only test the terminal behavior CheckNotGui @@ -1644,4 +1675,95 @@ func Test_read_invalid() set encoding=utf-8 endfunc +" Test for ModeChanged pattern +func Test_mode_changes() + let g:index = 0 + let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'i', 'ix', 'i', 'ic', 'i', 'n', 'no', 'n', 'V', 'v', 's', 'n'] + func! TestMode() + call assert_equal(g:mode_seq[g:index], get(v:event, "old_mode")) + call assert_equal(g:mode_seq[g:index + 1], get(v:event, "new_mode")) + call assert_equal(mode(1), get(v:event, "new_mode")) + let g:index += 1 + endfunc + + au ModeChanged * :call TestMode() + let g:n_to_any = 0 + au ModeChanged n:* let g:n_to_any += 1 + call feedkeys("i\<esc>vVca\<CR>\<C-X>\<C-L>\<esc>ggdG", 'tnix') + + let g:V_to_v = 0 + au ModeChanged V:v let g:V_to_v += 1 + call feedkeys("Vv\<C-G>\<esc>", 'tnix') + call assert_equal(len(filter(g:mode_seq[1:], {idx, val -> val == 'n'})), g:n_to_any) + call assert_equal(1, g:V_to_v) + call assert_equal(len(g:mode_seq) - 1, g:index) + + let g:n_to_i = 0 + au ModeChanged n:i let g:n_to_i += 1 + let g:n_to_niI = 0 + au ModeChanged i:niI let g:n_to_niI += 1 + let g:niI_to_i = 0 + au ModeChanged niI:i let g:niI_to_i += 1 + let g:nany_to_i = 0 + au ModeChanged n*:i let g:nany_to_i += 1 + let g:i_to_n = 0 + au ModeChanged i:n let g:i_to_n += 1 + let g:nori_to_any = 0 + au ModeChanged [ni]:* let g:nori_to_any += 1 + let g:i_to_any = 0 + au ModeChanged i:* let g:i_to_any += 1 + let g:index = 0 + let g:mode_seq = ['n', 'i', 'niI', 'i', 'n'] + call feedkeys("a\<C-O>l\<esc>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(1, g:n_to_i) + call assert_equal(1, g:n_to_niI) + call assert_equal(1, g:niI_to_i) + call assert_equal(2, g:nany_to_i) + call assert_equal(1, g:i_to_n) + call assert_equal(2, g:i_to_any) + call assert_equal(3, g:nori_to_any) + + if has('terminal') + let g:mode_seq += ['c', 'n', 't', 'nt', 'c', 'nt', 'n'] + call feedkeys(":term\<CR>\<C-W>N:bd!\<CR>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(1, g:n_to_i) + call assert_equal(1, g:n_to_niI) + call assert_equal(1, g:niI_to_i) + call assert_equal(2, g:nany_to_i) + call assert_equal(1, g:i_to_n) + call assert_equal(2, g:i_to_any) + call assert_equal(5, g:nori_to_any) + endif + + au! ModeChanged + delfunc TestMode + unlet! g:mode_seq + unlet! g:index + unlet! g:n_to_any + unlet! g:V_to_v + unlet! g:n_to_i + unlet! g:n_to_niI + unlet! g:niI_to_i + unlet! g:nany_to_i + unlet! g:i_to_n + unlet! g:nori_to_any + unlet! g:i_to_any +endfunc + +func Test_recursive_ModeChanged() + au! ModeChanged * norm 0u + sil! norm + au! +endfunc + +func Test_ModeChanged_starts_visual() + " This was triggering ModeChanged before setting VIsual, causing a crash. + au! ModeChanged * norm 0u + sil! norm + + au! ModeChanged +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim index cc15b63824..dd34983ee5 100644 --- a/src/nvim/testdir/test_environ.vim +++ b/src/nvim/testdir/test_environ.vim @@ -22,7 +22,7 @@ endfunc func Test_setenv() unlet! $TESTENV - call setenv('TEST ENV', 'foo') + eval 'foo'->setenv('TEST ENV') call assert_equal('foo', getenv('TEST ENV')) call setenv('TEST ENV', v:null) call assert_equal(v:null, getenv('TEST ENV')) diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 883ba5de3d..12febfeb93 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -65,11 +65,9 @@ func Test_E963() endfunc func Test_for_invalid() - " Vim gives incorrect emsg here until v8.2.3284, but the exact emsg from that - " patch cannot be used until v8.2.2658 is ported (for loop over Strings) - call assert_fails("for x in 99", 'E897:') - call assert_fails("for x in function('winnr')", 'E897:') - call assert_fails("for x in {'a': 9}", 'E897:') + call assert_fails("for x in 99", 'E1098:') + call assert_fails("for x in function('winnr')", 'E1098:') + call assert_fails("for x in {'a': 9}", 'E1098:') if 0 /1/5/2/s/\n @@ -187,7 +185,7 @@ func Test_let_register() call Assert_reg('"', 'v', "abc", "['abc']", "abc", "['abc']") let @" = "abc\n" call Assert_reg('"', 'V', "abc\n", "['abc']", "abc\n", "['abc']") - let @" = "abc\<C-m>" + let @" = "abc\r" call Assert_reg('"', 'V', "abc\r\n", "['abc\r']", "abc\r\n", "['abc\r']") let @= = '"abc"' call Assert_reg('=', 'v', "abc", "['abc']", '"abc"', "['\"abc\"']") diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 1c645ad0f8..dcec5f7cc6 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -29,12 +29,11 @@ endfunc " Test editing line in Ex mode (both Q and gQ) func Test_ex_mode() - throw 'skipped: TODO: ' + throw 'Skipped: Nvim only supports Vim Ex mode' let encoding_save = &encoding set sw=2 - " for e in ['utf8', 'latin1'] - for e in ['utf8'] + for e in ['utf8', 'latin1'] exe 'set encoding=' . e call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e) @@ -85,7 +84,7 @@ endfunc func Test_ex_mode_count_overflow() " this used to cause a crash let lines =<< trim END - call feedkeys("\<Esc>Q\<CR>") + call feedkeys("\<Esc>gQ\<CR>") v9|9silent! vi|333333233333y32333333%O call writefile(['done'], 'Xdidexmode') qall! @@ -98,4 +97,14 @@ func Test_ex_mode_count_overflow() call delete('Xexmodescript') endfunc +func Test_ex_mode_large_indent() + new + set ts=500 ai + call setline(1, "\t") + exe "normal gQi\<CR>." + set ts=8 noai + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 2d01cbba83..8055a51a11 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -1,6 +1,8 @@ " Tests for various Ex commands. source check.vim +source shared.vim +source term_util.vim func Test_ex_delete() new @@ -122,6 +124,27 @@ func Test_append_cmd() close! endfunc +func Test_append_cmd_empty_buf() + CheckRunVimInTerminal + let lines =<< trim END + func Timer(timer) + append + aaaaa + bbbbb + . + endfunc + call timer_start(10, 'Timer') + END + call writefile(lines, 'Xtest_append_cmd_empty_buf') + let buf = RunVimInTerminal('-S Xtest_append_cmd_empty_buf', {'rows': 6}) + call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_append_cmd_empty_buf') +endfunc + " Test for the :insert command func Test_insert_cmd() set noautoindent " test assumes noautoindent, but it's on by default in Nvim @@ -151,6 +174,27 @@ func Test_insert_cmd() close! endfunc +func Test_insert_cmd_empty_buf() + CheckRunVimInTerminal + let lines =<< trim END + func Timer(timer) + insert + aaaaa + bbbbb + . + endfunc + call timer_start(10, 'Timer') + END + call writefile(lines, 'Xtest_insert_cmd_empty_buf') + let buf = RunVimInTerminal('-S Xtest_insert_cmd_empty_buf', {'rows': 6}) + call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_insert_cmd_empty_buf') +endfunc + " Test for the :change command func Test_change_cmd() set noautoindent " test assumes noautoindent, but it's on by default in Nvim @@ -356,3 +400,27 @@ func Test_winsize_cmd() call assert_fails('win_getid(1)', 'E475: Invalid argument: _getid(1)') " Actually changing the window size would be flaky. endfunc + +func Test_not_break_expression_register() + call setreg('=', '1+1') + if 0 + put =1 + endif + call assert_equal('1+1', getreg('=', 1)) +endfunc + +func Test_address_line_overflow() + throw 'Skipped: v:sizeoflong is N/A' " use legacy/excmd_spec.lua instead + + if v:sizeoflong < 8 + throw 'Skipped: only works with 64 bit long ints' + endif + new + call setline(1, 'text') + call assert_fails('|.44444444444444444444444', 'E1247:') + call assert_fails('|.9223372036854775806', 'E1247:') + bwipe! +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_exec_while_if.vim b/src/nvim/testdir/test_exec_while_if.vim index 3da2784d77..3f13b09945 100644 --- a/src/nvim/testdir/test_exec_while_if.vim +++ b/src/nvim/testdir/test_exec_while_if.vim @@ -6,11 +6,7 @@ func Test_exec_while_if() let i = 0 while i < 12 let i = i + 1 - if has("ebcdic") - execute "normal o" . i . "\047" - else - execute "normal o" . i . "\033" - endif + execute "normal o" . i . "\033" if i % 2 normal Ax if i == 9 @@ -21,21 +17,13 @@ func Test_exec_while_if() else let j = 9 while j > 0 - if has("ebcdic") - execute "normal" j . "a" . j . "\x27" - else - execute "normal" j . "a" . j . "\x1b" - endif + execute "normal" j . "a" . j . "\x1b" let j = j - 1 endwhile endif endif if i == 9 - if has("ebcdic") - execute "normal Az\047" - else - execute "normal Az\033" - endif + execute "normal Az\033" endif endwhile unlet i j diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim index f2c7da0aa9..16cc20e9a7 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -99,7 +99,7 @@ func Test_win_execute() if has('textprop') let popupwin = popup_create('the popup win', {'line': 2, 'col': 3}) redraw - let line = win_execute(popupwin, 'echo getline(1)') + let line = 'echo getline(1)'->win_execute(popupwin) call assert_match('the popup win', line) call popup_close(popupwin) @@ -147,3 +147,30 @@ func Test_win_execute_other_tab() tabclose unlet xyz endfunc + +func Test_win_execute_visual_redraw() + call setline(1, ['a', 'b', 'c']) + new + wincmd p + " start Visual in current window, redraw in other window with fewer lines + call feedkeys("G\<C-V>", 'txn') + call win_execute(winnr('#')->win_getid(), 'redraw') + call feedkeys("\<Esc>", 'txn') + bwipe! + bwipe! + + enew + new + call setline(1, ['a', 'b', 'c']) + let winid = win_getid() + wincmd p + " start Visual in current window, extend it in other window with more lines + call feedkeys("\<C-V>", 'txn') + call win_execute(winid, 'call feedkeys("G\<C-V>", ''txn'')') + redraw + + bwipe! + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim index bd3e9eb4d4..befcaec2b2 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -1,6 +1,7 @@ " Tests for exiting Vim. source shared.vim +source check.vim func Test_exiting() let after =<< trim [CODE] @@ -109,4 +110,25 @@ func Test_exit_code() call delete('Xtestout') endfunc +func Test_exit_error_reading_input() + throw 'Skipped: Nvim does not exit after stdin is read' + + CheckNotGui + CheckNotMSWindows + " The early exit causes memory not to be freed somehow + CheckNotAsan + + call writefile([":au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout')", ":tabnew", "q:"], 'Xscript', 'b') + + " Nvim requires "-s -" to read stdin as Normal mode input + " if RunVim([], [], '<Xscript') + if RunVim([], [], '-s - <Xscript') + call assert_equal(1, v:shell_error) + call assert_equal(['l = 1'], readfile('Xtestout')) + endif + call delete('Xscript') + call delete('Xtestout') +endfun + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim new file mode 100644 index 0000000000..48dce25bb3 --- /dev/null +++ b/src/nvim/testdir/test_expand.vim @@ -0,0 +1,83 @@ +" Test for expanding file names + +func Test_with_directories() + call mkdir('Xdir1') + call mkdir('Xdir2') + call mkdir('Xdir3') + cd Xdir3 + call mkdir('Xdir4') + cd .. + + split Xdir1/file + call setline(1, ['a', 'b']) + w + w Xdir3/Xdir4/file + close + + next Xdir?/*/file + call assert_equal('Xdir3/Xdir4/file', expand('%')) + if has('unix') + next! Xdir?/*/nofile + call assert_equal('Xdir?/*/nofile', expand('%')) + endif + " Edit another file, on MS-Windows the swap file would be in use and can't + " be deleted. + edit foo + + call assert_equal(0, delete('Xdir1', 'rf')) + call assert_equal(0, delete('Xdir2', 'rf')) + call assert_equal(0, delete('Xdir3', 'rf')) +endfunc + +func Test_with_tilde() + let dir = getcwd() + call mkdir('Xdir ~ dir') + call assert_true(isdirectory('Xdir ~ dir')) + cd Xdir\ ~\ dir + call assert_true(getcwd() =~ 'Xdir \~ dir') + call chdir(dir) + call delete('Xdir ~ dir', 'd') + call assert_false(isdirectory('Xdir ~ dir')) +endfunc + +func Test_expand_tilde_filename() + split ~ + call assert_equal('~', expand('%')) + call assert_notequal(expand('%:p'), expand('~/')) + call assert_match('\~', expand('%:p')) + bwipe! +endfunc + +func Test_expandcmd() + let $FOO = 'Test' + call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) + unlet $FOO + + new + edit Xfile1 + call assert_equal('e Xfile1', expandcmd('e %')) + edit Xfile2 + edit Xfile1 + call assert_equal('e Xfile2', 'e #'->expandcmd()) + edit Xfile2 + edit Xfile3 + edit Xfile4 + let bnum = bufnr('Xfile2') + call assert_equal('e Xfile2', expandcmd('e #' . bnum)) + call setline('.', 'Vim!@#') + call assert_equal('e Vim', expandcmd('e <cword>')) + call assert_equal('e Vim!@#', expandcmd('e <cWORD>')) + enew! + edit Xfile.java + call assert_equal('e Xfile.py', expandcmd('e %:r.py')) + call assert_equal('make abc.java', expandcmd('make abc.%:e')) + call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?')) + edit a1a2a3.rb + call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) + + call assert_fails('call expandcmd("make <afile>")', 'E495:') + call assert_fails('call expandcmd("make <afile>")', 'E495:') + enew + call assert_fails('call expandcmd("make %")', 'E499:') + close +endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 6343c47fde..5b10e691e5 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -56,7 +56,7 @@ endfunc func Test_strgetchar() call assert_equal(char2nr('a'), strgetchar('axb', 0)) - call assert_equal(char2nr('x'), strgetchar('axb', 1)) + call assert_equal(char2nr('x'), 'axb'->strgetchar(1)) call assert_equal(char2nr('b'), strgetchar('axb', 2)) call assert_equal(-1, strgetchar('axb', -1)) @@ -66,7 +66,7 @@ endfunc func Test_strcharpart() call assert_equal('a', strcharpart('axb', 0, 1)) - call assert_equal('x', strcharpart('axb', 1, 1)) + call assert_equal('x', 'axb'->strcharpart(1, 1)) call assert_equal('b', strcharpart('axb', 2, 1)) call assert_equal('xb', strcharpart('axb', 1)) @@ -283,6 +283,71 @@ function Test_printf_misc() call assert_equal('ðŸ', printf('%.2S', 'ðŸðŸ')) call assert_equal('', printf('%.1S', 'ðŸðŸ')) + call assert_equal('[ ã‚ã„ã†]', printf('[%10.6S]', 'ã‚ã„ã†ãˆãŠ')) + call assert_equal('[ ã‚ã„ã†ãˆ]', printf('[%10.8S]', 'ã‚ã„ã†ãˆãŠ')) + call assert_equal('[ã‚ã„ã†ãˆãŠ]', printf('[%10.10S]', 'ã‚ã„ã†ãˆãŠ')) + call assert_equal('[ã‚ã„ã†ãˆãŠ]', printf('[%10.12S]', 'ã‚ã„ã†ãˆãŠ')) + + call assert_equal('ã‚ã„ã†', printf('%S', 'ã‚ã„ã†')) + call assert_equal('ã‚ã„ã†', printf('%#S', 'ã‚ã„ã†')) + + call assert_equal('ã‚b', printf('%2S', 'ã‚b')) + call assert_equal('ã‚b', printf('%.4S', 'ã‚b')) + call assert_equal('ã‚', printf('%.2S', 'ã‚b')) + call assert_equal(' ã‚b', printf('%4S', 'ã‚b')) + call assert_equal('0ã‚b', printf('%04S', 'ã‚b')) + call assert_equal('ã‚b ', printf('%-4S', 'ã‚b')) + call assert_equal('ã‚ ', printf('%-4.2S', 'ã‚b')) + + call assert_equal('aã„', printf('%2S', 'aã„')) + call assert_equal('aã„', printf('%.4S', 'aã„')) + call assert_equal('a', printf('%.2S', 'aã„')) + call assert_equal(' aã„', printf('%4S', 'aã„')) + call assert_equal('0aã„', printf('%04S', 'aã„')) + call assert_equal('aã„ ', printf('%-4S', 'aã„')) + call assert_equal('a ', printf('%-4.2S', 'aã„')) + + call assert_equal('[ã‚ã„ã†]', printf('[%05S]', 'ã‚ã„ã†')) + call assert_equal('[ã‚ã„ã†]', printf('[%06S]', 'ã‚ã„ã†')) + call assert_equal('[0ã‚ã„ã†]', printf('[%07S]', 'ã‚ã„ã†')) + + call assert_equal('[ã‚iã†]', printf('[%05S]', 'ã‚iã†')) + call assert_equal('[0ã‚iã†]', printf('[%06S]', 'ã‚iã†')) + call assert_equal('[00ã‚iã†]', printf('[%07S]', 'ã‚iã†')) + + call assert_equal('[0ã‚ã„]', printf('[%05.4S]', 'ã‚ã„ã†')) + call assert_equal('[00ã‚ã„]', printf('[%06.4S]', 'ã‚ã„ã†')) + call assert_equal('[000ã‚ã„]', printf('[%07.4S]', 'ã‚ã„ã†')) + + call assert_equal('[00ã‚i]', printf('[%05.4S]', 'ã‚iã†')) + call assert_equal('[000ã‚i]', printf('[%06.4S]', 'ã‚iã†')) + call assert_equal('[0000ã‚i]', printf('[%07.4S]', 'ã‚iã†')) + + call assert_equal('[0ã‚ã„]', printf('[%05.5S]', 'ã‚ã„ã†')) + call assert_equal('[00ã‚ã„]', printf('[%06.5S]', 'ã‚ã„ã†')) + call assert_equal('[000ã‚ã„]', printf('[%07.5S]', 'ã‚ã„ã†')) + + call assert_equal('[ã‚iã†]', printf('[%05.5S]', 'ã‚iã†')) + call assert_equal('[0ã‚iã†]', printf('[%06.5S]', 'ã‚iã†')) + call assert_equal('[00ã‚iã†]', printf('[%07.5S]', 'ã‚iã†')) + + call assert_equal('[0000000000]', printf('[%010.0S]', 'ã‚ã„ã†')) + call assert_equal('[0000000000]', printf('[%010.1S]', 'ã‚ã„ã†')) + call assert_equal('[00000000ã‚]', printf('[%010.2S]', 'ã‚ã„ã†')) + call assert_equal('[00000000ã‚]', printf('[%010.3S]', 'ã‚ã„ã†')) + call assert_equal('[000000ã‚ã„]', printf('[%010.4S]', 'ã‚ã„ã†')) + call assert_equal('[000000ã‚ã„]', printf('[%010.5S]', 'ã‚ã„ã†')) + call assert_equal('[0000ã‚ã„ã†]', printf('[%010.6S]', 'ã‚ã„ã†')) + call assert_equal('[0000ã‚ã„ã†]', printf('[%010.7S]', 'ã‚ã„ã†')) + + call assert_equal('[0000000000]', printf('[%010.1S]', 'ã‚iã†')) + call assert_equal('[00000000ã‚]', printf('[%010.2S]', 'ã‚iã†')) + call assert_equal('[0000000ã‚i]', printf('[%010.3S]', 'ã‚iã†')) + call assert_equal('[0000000ã‚i]', printf('[%010.4S]', 'ã‚iã†')) + call assert_equal('[00000ã‚iã†]', printf('[%010.5S]', 'ã‚iã†')) + call assert_equal('[00000ã‚iã†]', printf('[%010.6S]', 'ã‚iã†')) + call assert_equal('[00000ã‚iã†]', printf('[%010.7S]', 'ã‚iã†')) + call assert_equal('1%', printf('%d%%', 1)) endfunc @@ -493,7 +558,7 @@ func Test_setmatches() let set[0]['conceal'] = 5 let exp[0]['conceal'] = '5' endif - call setmatches(set) + eval set->setmatches() call assert_equal(exp, getmatches()) endfunc diff --git a/src/nvim/testdir/test_feedkeys.vim b/src/nvim/testdir/test_feedkeys.vim index 70500f2bb5..f343b0174c 100644 --- a/src/nvim/testdir/test_feedkeys.vim +++ b/src/nvim/testdir/test_feedkeys.vim @@ -12,3 +12,15 @@ func Test_feedkeys_x_with_empty_string() call assert_equal('foo', getline('.')) quit! endfunc + +func Test_feedkeys_with_abbreviation() + new + inoreabbrev trigger value + call feedkeys("atrigger ", 'x') + call feedkeys("atrigger ", 'x') + call assert_equal('value value ', getline(1)) + bwipe! + iunabbrev trigger +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_file_perm.vim b/src/nvim/testdir/test_file_perm.vim new file mode 100644 index 0000000000..1cb09e8647 --- /dev/null +++ b/src/nvim/testdir/test_file_perm.vim @@ -0,0 +1,30 @@ +" Test getting and setting file permissions. + +func Test_file_perm() + call assert_equal('', getfperm('Xtest')) + call assert_equal(0, 'Xtest'->setfperm('r--------')) + + call writefile(['one'], 'Xtest') + call assert_true(len('Xtest'->getfperm()) == 9) + + call assert_equal(1, setfperm('Xtest', 'rwx------')) + if has('win32') + call assert_equal('rw-rw-rw-', getfperm('Xtest')) + else + call assert_equal('rwx------', getfperm('Xtest')) + endif + + call assert_equal(1, setfperm('Xtest', 'r--r--r--')) + call assert_equal('r--r--r--', getfperm('Xtest')) + + call assert_fails("setfperm('Xtest', '---')") + + call assert_equal(1, setfperm('Xtest', 'rwx------')) + call delete('Xtest') + + call assert_fails("call setfperm(['Xfile'], 'rw-rw-rw-')", 'E730:') + call assert_fails("call setfperm('Xfile', [])", 'E730:') + call assert_fails("call setfperm('Xfile', 'rwxrwxrwxrw')", 'E475:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim new file mode 100644 index 0000000000..8e1cb2c3ee --- /dev/null +++ b/src/nvim/testdir/test_filechanged.vim @@ -0,0 +1,247 @@ +" Tests for when a file was changed outside of Vim. + +source check.vim + +func Test_FileChangedShell_reload() + CheckUnix + + augroup testreload + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload' + augroup END + new Xchanged_r + call setline(1, 'reload this') + write + " Need to wait until the timestamp would change by at least a second. + sleep 2 + silent !echo 'extra line' >>Xchanged_r + checktime + call assert_equal('changed', g:reason) + call assert_equal(2, line('$')) + call assert_equal('extra line', getline(2)) + + " Only triggers once + let g:reason = '' + checktime + call assert_equal('', g:reason) + + " When deleted buffer is not reloaded + silent !rm Xchanged_r + let g:reason = '' + checktime + call assert_equal('deleted', g:reason) + call assert_equal(2, line('$')) + call assert_equal('extra line', getline(2)) + + " When recreated buffer is reloaded + call setline(1, 'buffer is changed') + silent !echo 'new line' >>Xchanged_r + let g:reason = '' + checktime + call assert_equal('conflict', g:reason) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only mode changed + silent !chmod +x Xchanged_r + let g:reason = '' + checktime + call assert_equal('mode', g:reason) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only time changed + sleep 2 + silent !touch Xchanged_r + let g:reason = '' + checktime + call assert_equal('time', g:reason) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + if has('persistent_undo') + " With an undo file the reload can be undone and a change before the + " reload. + set undofile + call setline(2, 'before write') + write + call setline(2, 'after write') + sleep 2 + silent !echo 'different line' >>Xchanged_r + let g:reason = '' + checktime + call assert_equal('conflict', g:reason) + call assert_equal(3, line('$')) + call assert_equal('before write', getline(2)) + call assert_equal('different line', getline(3)) + " undo the reload + undo + call assert_equal(2, line('$')) + call assert_equal('after write', getline(2)) + " undo the change before reload + undo + call assert_equal(2, line('$')) + call assert_equal('before write', getline(2)) + + set noundofile + endif + + au! testreload + bwipe! + call delete(undofile('Xchanged_r')) + call delete('Xchanged_r') +endfunc + +func Test_FileChangedShell_edit() + CheckUnix + + new Xchanged_r + call setline(1, 'reload this') + set fileformat=unix + write + + " File format changed, reload (content only, no 'ff' etc) + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload' + augroup END + call assert_equal(&fileformat, 'unix') + sleep 10m " make the test less flaky in Nvim + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'unix') + call assert_equal("line1\r", getline(1)) + call assert_equal("line2\r", getline(2)) + %s/\r + write + + " File format changed, reload with 'ff', etc + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'edit' + augroup END + call assert_equal(&fileformat, 'unix') + sleep 10m " make the test less flaky in Nvim + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'dos') + call assert_equal('line1', getline(1)) + call assert_equal('line2', getline(2)) + set fileformat=unix + write + + au! testreload + bwipe! + call delete(undofile('Xchanged_r')) + call delete('Xchanged_r') +endfunc + +func Test_FileChangedShell_edit_dialog() + throw 'Skipped: requires a UI to be active' + CheckNotGui + CheckUnix " Using low level feedkeys() does not work on MS-Windows. + + new Xchanged_r + call setline(1, 'reload this') + set fileformat=unix + write + + " File format changed, reload (content only) via prompt + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask' + augroup END + call assert_equal(&fileformat, 'unix') + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + call feedkeys('L', 'L') " load file content only + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'unix') + call assert_equal("line1\r", getline(1)) + call assert_equal("line2\r", getline(2)) + %s/\r + write + + " File format changed, reload (file and options) via prompt + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask' + augroup END + call assert_equal(&fileformat, 'unix') + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + call feedkeys('a', 'L') " load file content and options + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'dos') + call assert_equal("line1", getline(1)) + call assert_equal("line2", getline(2)) + set fileformat=unix + write + + au! testreload + bwipe! + call delete(undofile('Xchanged_r')) + call delete('Xchanged_r') +endfunc + +func Test_file_changed_dialog() + throw 'Skipped: requires a UI to be active' + CheckUnix + CheckNotGui + au! FileChangedShell + + new Xchanged_d + call setline(1, 'reload this') + write + " Need to wait until the timestamp would change by at least a second. + sleep 2 + silent !echo 'extra line' >>Xchanged_d + call feedkeys('L', 'L') + checktime + call assert_match('W11:', v:warningmsg) + call assert_equal(2, line('$')) + call assert_equal('reload this', getline(1)) + call assert_equal('extra line', getline(2)) + + " delete buffer, only shows an error, no prompt + silent !rm Xchanged_d + checktime + call assert_match('E211:', v:warningmsg) + call assert_equal(2, line('$')) + call assert_equal('extra line', getline(2)) + let v:warningmsg = 'empty' + + " change buffer, recreate the file and reload + call setline(1, 'buffer is changed') + silent !echo 'new line' >Xchanged_d + call feedkeys('L', 'L') + checktime + call assert_match('W12:', v:warningmsg) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only mode changed, reload + silent !chmod +x Xchanged_d + call feedkeys('L', 'L') + checktime + call assert_match('W16:', v:warningmsg) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only time changed, no prompt + sleep 2 + silent !touch Xchanged_d + let v:warningmsg = '' + checktime + call assert_equal('', v:warningmsg) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + bwipe! + call delete('Xchanged_d') +endfunc diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim index 465613f1cf..81127ea59a 100644 --- a/src/nvim/testdir/test_fileformat.vim +++ b/src/nvim/testdir/test_fileformat.vim @@ -1,5 +1,4 @@ " Test behavior of fileformat after bwipeout of last buffer - func Test_fileformat_after_bw() bwipeout set fileformat& @@ -32,6 +31,251 @@ func Test_fileformat_autocommand() bw! endfunc +" Convert the contents of a file into a literal string +func s:file2str(fname) + let b = readfile(a:fname, 'B') + let s = '' + for c in b + let s .= nr2char(c) + endfor + return s +endfunc + +" Concatenate the contents of files 'f1' and 'f2' and create 'destfile' +func s:concat_files(f1, f2, destfile) + let b1 = readfile(a:f1, 'B') + let b2 = readfile(a:f2, 'B') + let b3 = b1 + b2 + call writefile(b3, a:destfile) +endfun + +" Test for a lot of variations of the 'fileformats' option +func Test_fileformats() + " create three test files, one in each format + call writefile(['unix', 'unix'], 'XXUnix') + call writefile(["dos\r", "dos\r"], 'XXDos') + call writefile(["mac\rmac\r"], 'XXMac', 'b') + " create a file with no End Of Line + call writefile(["noeol"], 'XXEol', 'b') + " create mixed format files + call s:concat_files('XXUnix', 'XXDos', 'XXUxDs') + call s:concat_files('XXUnix', 'XXMac', 'XXUxMac') + call s:concat_files('XXDos', 'XXMac', 'XXDosMac') + call s:concat_files('XXMac', 'XXEol', 'XXMacEol') + call s:concat_files('XXUxDs', 'XXMac', 'XXUxDsMc') + + new + + " Test 1: try reading and writing with 'fileformats' empty + set fileformats= + + " try with 'fileformat' set to 'unix' + set fileformat=unix + e! XXUnix + w! Xtest + call assert_equal("unix\nunix\n", s:file2str('Xtest')) + e! XXDos + w! Xtest + call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest')) + e! XXMac + w! Xtest + call assert_equal("mac\rmac\r\n", s:file2str('Xtest')) + bwipe XXUnix XXDos XXMac + + " try with 'fileformat' set to 'dos' + set fileformat=dos + e! XXUnix + w! Xtest + call assert_equal("unix\r\nunix\r\n", s:file2str('Xtest')) + e! XXDos + w! Xtest + call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest')) + e! XXMac + w! Xtest + call assert_equal("mac\rmac\r\r\n", s:file2str('Xtest')) + bwipe XXUnix XXDos XXMac + + " try with 'fileformat' set to 'mac' + set fileformat=mac + e! XXUnix + w! Xtest + call assert_equal("unix\nunix\n\r", s:file2str('Xtest')) + e! XXDos + w! Xtest + call assert_equal("dos\r\ndos\r\n\r", s:file2str('Xtest')) + e! XXMac + w! Xtest + call assert_equal("mac\rmac\r", s:file2str('Xtest')) + bwipe XXUnix XXDos XXMac + + " Test 2: try reading and writing with 'fileformats' set to one format + + " try with 'fileformats' set to 'unix' + set fileformats=unix + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + " try with 'fileformats' set to 'dos' + set fileformats=dos + e! XXUxDsMc + w! Xtest + call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + " try with 'fileformats' set to 'mac' + set fileformats=mac + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + " Test 3: try reading and writing with 'fileformats' set to two formats + + " try with 'fileformats' set to 'unix,dos' + set fileformats=unix,dos + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXUxMac + w! Xtest + call assert_equal("unix\nunix\nmac\rmac\r\n", s:file2str('Xtest')) + bwipe XXUxMac + + e! XXDosMac + w! Xtest + call assert_equal("dos\r\ndos\r\nmac\rmac\r\r\n", s:file2str('Xtest')) + bwipe XXDosMac + + " try with 'fileformats' set to 'unix,mac' + set fileformats=unix,mac + e! XXUxDs + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\n", s:file2str('Xtest')) + bwipe XXUxDs + + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXDosMac + w! Xtest + call assert_equal("dos\r\ndos\r\nmac\rmac\r", s:file2str('Xtest')) + bwipe XXDosMac + + e! XXEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("unix,mac:unix\nnoeol\n", s:file2str('Xtest')) + bwipe! XXEol + + " try with 'fileformats' set to 'dos,mac' + set fileformats=dos,mac + e! XXUxDs + w! Xtest + call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\n", s:file2str('Xtest')) + bwipe XXUxDs + + e! XXUxMac + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("dos,mac:dos\r\nunix\r\nunix\r\nmac\rmac\r\r\n", + \ s:file2str('Xtest')) + bwipe! XXUxMac + + e! XXUxDsMc + w! Xtest + call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXMacEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("dos,mac:mac\rmac\rmac\rnoeol\r", s:file2str('Xtest')) + bwipe! XXMacEol + + " Test 4: try reading and writing with 'fileformats' set to three formats + set fileformats=unix,dos,mac + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("unix,dos,mac:unix\nnoeol\n", s:file2str('Xtest')) + bwipe! XXEol + + set fileformats=mac,dos,unix + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("mac,dos,unix:mac\rnoeol\r", s:file2str('Xtest')) + bwipe! XXEol + + " Test 5: try with 'binary' set + set fileformats=mac,unix,dos + set binary + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + set fileformats=mac + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + set fileformats=dos + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXUnix + w! Xtest + call assert_equal("unix\nunix\n", s:file2str('Xtest')) + bwipe! XXUnix + + set nobinary ff& ffs& + + " cleanup + only + %bwipe! + call delete('XXUnix') + call delete('XXDos') + call delete('XXMac') + call delete('XXEol') + call delete('XXUxDs') + call delete('XXUxMac') + call delete('XXDosMac') + call delete('XXMacEol') + call delete('XXUxDsMc') + call delete('Xtest') +endfunc + " Test for changing the fileformat using ++read func Test_fileformat_plusplus_read() new diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 9651b8dce0..197a9edb76 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -59,7 +59,7 @@ let s:filename_checks = { \ 'aml': ['file.aml'], \ 'ampl': ['file.run'], \ 'ant': ['build.xml'], - \ 'apache': ['.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config', '/etc/apache2/conf.file/file', '/etc/apache2/file.conf', '/etc/apache2/file.conf-file', '/etc/apache2/mods-file/file', '/etc/apache2/sites-file/file', '/etc/apache2/sites-file/file.com', '/etc/httpd/conf.d/file.conf', '/etc/httpd/conf.d/file.conf-file', 'access.conf', 'access.conf-file', 'any/etc/apache2/conf.file/file', 'any/etc/apache2/file.conf', 'any/etc/apache2/file.conf-file', 'any/etc/apache2/mods-file/file', 'any/etc/apache2/sites-file/file', 'any/etc/apache2/sites-file/file.com', 'any/etc/httpd/conf.d/file.conf', 'any/etc/httpd/conf.d/file.conf-file', 'any/etc/httpd/file.conf', 'apache.conf', 'apache.conf-file', 'apache2.conf', 'apache2.conf-file', 'httpd.conf', 'httpd.conf-file', 'srm.conf', 'srm.conf-file'], + \ 'apache': ['.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config', '/etc/apache2/conf.file/file', '/etc/apache2/file.conf', '/etc/apache2/file.conf-file', '/etc/apache2/mods-file/file', '/etc/apache2/sites-file/file', '/etc/apache2/sites-file/file.com', '/etc/httpd/conf.d/file.conf', '/etc/httpd/conf.d/file.conf-file', 'access.conf', 'access.conf-file', 'any/etc/apache2/conf.file/file', 'any/etc/apache2/file.conf', 'any/etc/apache2/file.conf-file', 'any/etc/apache2/mods-file/file', 'any/etc/apache2/sites-file/file', 'any/etc/apache2/sites-file/file.com', 'any/etc/httpd/conf.d/file.conf', 'any/etc/httpd/conf.d/file.conf-file', 'any/etc/httpd/file.conf', 'apache.conf', 'apache.conf-file', 'apache2.conf', 'apache2.conf-file', 'httpd.conf', 'httpd.conf-file', 'srm.conf', 'srm.conf-file', '/etc/httpd/mods-some/file', '/etc/httpd/sites-some/file', '/etc/httpd/conf.file/conf'], \ 'apachestyle': ['/etc/proftpd/file.config,/etc/proftpd/conf.file/file', '/etc/proftpd/conf.file/file', '/etc/proftpd/file.conf', '/etc/proftpd/file.conf-file', 'any/etc/proftpd/conf.file/file', 'any/etc/proftpd/file.conf', 'any/etc/proftpd/file.conf-file', 'proftpd.conf', 'proftpd.conf-file'], \ 'applescript': ['file.scpt'], \ 'aptconf': ['apt.conf', '/.aptitude/config', 'any/.aptitude/config'], @@ -76,10 +76,12 @@ let s:filename_checks = { \ 'ave': ['file.ave'], \ 'awk': ['file.awk', 'file.gawk'], \ 'b': ['file.mch', 'file.ref', 'file.imp'], + \ 'basic': ['file.bas', 'file.bi', 'file.bm'], \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'], \ 'bc': ['file.bc'], \ 'bdf': ['file.bdf'], \ 'bib': ['file.bib'], + \ 'bicep': ['file.bicep'], \ 'beancount': ['file.beancount'], \ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'], \ 'blank': ['file.bl'], @@ -97,7 +99,7 @@ let s:filename_checks = { \ 'cdrtoc': ['file.toc'], \ 'cf': ['file.cfm', 'file.cfi', 'file.cfc'], \ 'cfengine': ['cfengine.conf'], - \ 'cfg': ['file.cfg', 'file.hgrc', 'filehgrc', 'hgrc', 'some-hgrc'], + \ 'cfg': ['file.hgrc', 'filehgrc', 'hgrc', 'some-hgrc'], \ 'ch': ['file.chf'], \ 'chaiscript': ['file.chai'], \ 'chaskell': ['file.chs'], @@ -114,10 +116,11 @@ let s:filename_checks = { \ 'conf': ['auto.master'], \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file'], \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'], + \ 'cook': ['file.cook'], \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], \ 'crm': ['file.crm'], \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'], - \ 'cs': ['file.cs'], + \ 'cs': ['file.cs', 'file.csx'], \ 'csc': ['file.csc'], \ 'csdl': ['file.csdl'], \ 'csp': ['file.csp', 'file.fdr'], @@ -130,6 +133,7 @@ let s:filename_checks = { \ 'cvs': ['cvs123'], \ 'cvsrc': ['.cvsrc'], \ 'cynpp': ['file.cyn'], + \ 'd': ['file.d'], \ 'dart': ['file.dart', 'file.drt'], \ 'datascript': ['file.ds'], \ 'dcd': ['file.dcd'], @@ -142,16 +146,17 @@ let s:filename_checks = { \ 'desc': ['file.desc'], \ 'desktop': ['file.desktop', '.directory', 'file.directory'], \ 'dictconf': ['dict.conf', '.dictrc'], - \ 'dictdconf': ['dictd.conf'], + \ 'dictdconf': ['dictd.conf', 'dictdfile.conf', 'dictd-file.conf'], \ 'diff': ['file.diff', 'file.rej'], \ 'dircolors': ['.dir_colors', '.dircolors', '/etc/DIR_COLORS', 'any/etc/DIR_COLORS'], \ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'], - \ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile'], - \ 'dosbatch': ['file.bat', 'file.sys'], + \ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile', 'Dockerfile.debian', 'Containerfile.something'], + \ 'dosbatch': ['file.bat'], \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'], \ 'dot': ['file.dot', 'file.gv'], \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'], \ 'dtd': ['file.dtd'], + \ 'dtrace': ['/usr/lib/dtrace/io.d'], \ 'dts': ['file.dts', 'file.dtsi'], \ 'dune': ['jbuild', 'dune', 'dune-project', 'dune-workspace'], \ 'dylan': ['file.dylan'], @@ -164,6 +169,7 @@ let s:filename_checks = { \ 'eelixir': ['file.eex', 'file.leex'], \ 'elm': ['file.elm'], \ 'elmfilt': ['filter-rules'], + \ 'elvish': ['file.elv'], \ 'epuppet': ['file.epp'], \ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'], \ 'eruby': ['file.erb', 'file.rhtml'], @@ -182,49 +188,64 @@ let s:filename_checks = { \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'], \ 'fish': ['file.fish'], \ 'focexec': ['file.fex', 'file.focexec'], - \ 'forth': ['file.fs', 'file.ft', 'file.fth'], + \ 'form': ['file.frm'], + \ 'forth': ['file.ft', 'file.fth'], \ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'], \ 'fpcmake': ['file.fpc'], \ 'framescript': ['file.fsl'], - \ 'freebasic': ['file.fb', 'file.bi'], + \ 'freebasic': ['file.fb'], + \ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'], \ 'fstab': ['fstab', 'mtab'], + \ 'fusion': ['file.fusion'], \ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'], - \ 'gdb': ['.gdbinit', 'gdbinit'], + \ 'gdb': ['.gdbinit', 'gdbinit', 'file.gdb', '.config/gdbearlyinit', '.gdbearlyinit'], + \ 'gdresource': ['file.tscn', 'file.tres'], + \ 'gdscript': ['file.gd'], \ 'gdmo': ['file.mo', 'file.gdmo'], \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'], \ 'gemtext': ['file.gmi', 'file.gemini'], \ 'gift': ['file.gift'], - \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG'], - \ 'gitconfig': ['file.git/config', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'], + \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], + \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], \ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'], + \ 'glsl': ['file.glsl'], \ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'], - \ 'gnuplot': ['file.gpi'], + \ 'gnuplot': ['file.gpi', '.gnuplot'], \ 'go': ['file.go'], + \ 'gomod': ['go.mod'], + \ 'gowork': ['go.work'], \ 'gp': ['file.gp', '.gprc'], \ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'], \ 'grads': ['file.gs'], + \ 'graphql': ['file.graphql', 'file.graphqls', 'file.gql'], \ 'gretl': ['file.gretl'], \ 'groovy': ['file.gradle', 'file.groovy'], \ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak', '/etc/group', '/etc/group-', '/etc/group.edit', '/etc/gshadow', '/etc/gshadow-', '/etc/gshadow.edit', '/var/backups/group.bak', '/var/backups/gshadow.bak'], \ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'], \ 'gsp': ['file.gsp'], \ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'], + \ 'hack': ['file.hack', 'file.hackpartial'], \ 'haml': ['file.haml'], \ 'hamster': ['file.hsm'], + \ 'handlebars': ['file.hbs'], \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'], \ 'haste': ['file.ht'], \ 'hastepreproc': ['file.htpp'], \ 'hb': ['file.hb'], + \ 'hcl': ['file.hcl'], \ 'hercules': ['file.vc', 'file.ev', 'file.sum', 'file.errsum'], + \ 'heex': ['file.heex'], \ 'hex': ['file.hex', 'file.h32'], \ 'hgcommit': ['hg-editor-file.txt'], + \ 'hjson': ['file.hjson'], \ 'hog': ['file.hog', 'snort.conf', 'vision.conf'], \ 'hollywood': ['file.hws'], \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'], \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'], + \ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'], \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'], \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig'], \ 'natural': ['file.NSA', 'file.NSC', 'file.NSG', 'file.NSL', 'file.NSM', 'file.NSN', 'file.NSP', 'file.NSS'], @@ -255,12 +276,14 @@ let s:filename_checks = { \ 'java': ['file.java', 'file.jav'], \ 'javacc': ['file.jj', 'file.jjt'], \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'], + \ 'javascript.glimmer': ['file.gjs'], \ 'javascriptreact': ['file.jsx'], \ 'jess': ['file.clp'], \ 'jgraph': ['file.jgr'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'], - \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc'], + \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc', 'file.slnf'], + \ 'json5': ['file.json5'], \ 'jsonc': ['file.jsonc'], \ 'jsp': ['file.jsp'], \ 'julia': ['file.jl'], @@ -268,12 +291,14 @@ let s:filename_checks = { \ 'kivy': ['file.kv'], \ 'kix': ['file.kix'], \ 'kotlin': ['file.kt', 'file.ktm', 'file.kts'], + \ 'krl': ['file.sub', 'file.Sub', 'file.SUB'], \ 'kscript': ['file.ks'], \ 'kwt': ['file.k'], \ 'lace': ['file.ace', 'file.ACE'], \ 'latte': ['file.latte', 'file.lte'], \ 'ld': ['file.ld'], \ 'ldif': ['file.ldif'], + \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'], \ 'less': ['file.less'], \ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'], \ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'], @@ -283,7 +308,7 @@ let s:filename_checks = { \ 'lilo': ['lilo.conf', 'lilo.conf-file'], \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'], \ 'liquid': ['file.liquid'], - \ 'lisp': ['file.lsp', 'file.lisp', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'], + \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'], \ 'lite': ['file.lite', 'file.lt'], \ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'], \ 'loginaccess': ['/etc/login.access', 'any/etc/login.access'], @@ -338,7 +363,7 @@ let s:filename_checks = { \ 'msql': ['file.msql'], \ 'mupad': ['file.mu'], \ 'mush': ['file.mush'], - \ 'muttrc': ['Muttngrc', 'Muttrc', '.muttngrc', '.muttngrc-file', '.muttrc', '.muttrc-file', '/.mutt/muttngrc', '/.mutt/muttngrc-file', '/.mutt/muttrc', '/.mutt/muttrc-file', '/.muttng/muttngrc', '/.muttng/muttngrc-file', '/.muttng/muttrc', '/.muttng/muttrc-file', '/etc/Muttrc.d/file', 'Muttngrc-file', 'Muttrc-file', 'any/.mutt/muttngrc', 'any/.mutt/muttngrc-file', 'any/.mutt/muttrc', 'any/.mutt/muttrc-file', 'any/.muttng/muttngrc', 'any/.muttng/muttngrc-file', 'any/.muttng/muttrc', 'any/.muttng/muttrc-file', 'any/etc/Muttrc.d/file', 'muttngrc', 'muttngrc-file', 'muttrc', 'muttrc-file'], + \ 'muttrc': ['Muttngrc', 'Muttrc', '.muttngrc', '.muttngrc-file', '.muttrc', '.muttrc-file', '/.mutt/muttngrc', '/.mutt/muttngrc-file', '/.mutt/muttrc', '/.mutt/muttrc-file', '/.muttng/muttngrc', '/.muttng/muttngrc-file', '/.muttng/muttrc', '/.muttng/muttrc-file', '/etc/Muttrc.d/file', '/etc/Muttrc.d/file.rc', 'Muttngrc-file', 'Muttrc-file', 'any/.mutt/muttngrc', 'any/.mutt/muttngrc-file', 'any/.mutt/muttrc', 'any/.mutt/muttrc-file', 'any/.muttng/muttngrc', 'any/.muttng/muttngrc-file', 'any/.muttng/muttrc', 'any/.muttng/muttrc-file', 'any/etc/Muttrc.d/file', 'muttngrc', 'muttngrc-file', 'muttrc', 'muttrc-file'], \ 'mysql': ['file.mysql'], \ 'n1ql': ['file.n1ql', 'file.nql'], \ 'named': ['namedfile.conf', 'rndcfile.conf', 'named-file.conf', 'named.conf', 'rndc-file.conf', 'rndc-file.key', 'rndc.conf', 'rndc.key'], @@ -347,6 +372,7 @@ let s:filename_checks = { \ 'netrc': ['.netrc'], \ 'nginx': ['file.nginx', 'nginxfile.conf', 'filenginx.conf', 'any/etc/nginx/file', 'any/usr/local/nginx/conf/file', 'any/nginx/file.conf'], \ 'ninja': ['file.ninja'], + \ 'nix': ['file.nix'], \ 'nqc': ['file.nqc'], \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'], \ 'nsis': ['file.nsi', 'file.nsh'], @@ -358,6 +384,7 @@ let s:filename_checks = { \ 'opam': ['opam', 'file.opam', 'file.opam.template'], \ 'openroad': ['file.or'], \ 'ora': ['file.ora'], + \ 'org': ['file.org', 'file.org_archive'], \ 'pamconf': ['/etc/pam.conf', '/etc/pam.d/file', 'any/etc/pam.conf', 'any/etc/pam.d/file'], \ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment', '.pam_environment', 'pam_env.conf'], \ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'], @@ -369,7 +396,7 @@ let s:filename_checks = { \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'], \ 'pf': ['pf.conf'], \ 'pfmain': ['main.cf'], - \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp'], + \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp', 'file.phpt'], \ 'lpc': ['file.lpc', 'file.ulpc'], \ 'pike': ['file.pike', 'file.pmod'], \ 'cmod': ['file.cmod'], @@ -389,6 +416,7 @@ let s:filename_checks = { \ 'ppd': ['file.ppd'], \ 'ppwiz': ['file.it', 'file.ih'], \ 'privoxy': ['file.action'], + \ 'prisma': ['file.prisma'], \ 'proc': ['file.pc'], \ 'procmail': ['.procmail', '.procmailrc'], \ 'prolog': ['file.pdb'], @@ -399,10 +427,12 @@ let s:filename_checks = { \ 'ps1xml': ['file.ps1xml'], \ 'psf': ['file.psf'], \ 'psl': ['file.psl'], + \ 'pug': ['file.pug'], \ 'puppet': ['file.pp'], \ 'pyret': ['file.arr'], \ 'pyrex': ['file.pyx', 'file.pxd'], \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'], + \ 'ql': ['file.ql', 'file.qll'], \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'], \ 'radiance': ['file.rad', 'file.mat'], \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'], @@ -413,6 +443,7 @@ let s:filename_checks = { \ 'readline': ['.inputrc', 'inputrc'], \ 'remind': ['.reminders', 'file.remind', 'file.rem', '.reminders-file'], \ 'rego': ['file.rego'], + \ 'rescript': ['file.res', 'file.resi'], \ 'resolv': ['resolv.conf'], \ 'reva': ['file.frt'], \ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'], @@ -425,25 +456,24 @@ let s:filename_checks = { \ 'rpl': ['file.rpl'], \ 'rst': ['file.rst'], \ 'rtf': ['file.rtf'], - \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile'], + \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile', 'Vagrantfile'], \ 'rust': ['file.rs'], \ 'samba': ['smb.conf'], \ 'sas': ['file.sas'], \ 'sass': ['file.sass'], \ 'sather': ['file.sa'], \ 'sbt': ['file.sbt'], - \ 'scala': ['file.scala', 'file.sc'], - \ 'scheme': ['file.scm', 'file.ss', 'file.rkt', 'file.rktd', 'file.rktl'], + \ 'scala': ['file.scala'], + \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.rkt', 'file.rktd', 'file.rktl'], \ 'scilab': ['file.sci', 'file.sce'], \ 'screen': ['.screenrc', 'screenrc'], \ 'sexplib': ['file.sexp'], - \ 'scdoc': ['file.scd'], \ 'scss': ['file.scss'], \ 'sd': ['file.sd'], \ 'sdc': ['file.sdc'], \ 'sdl': ['file.sdl', 'file.pr'], \ 'sed': ['file.sed'], - \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', 'any/etc/sensors.conf', 'any/etc/sensors3.conf'], + \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', '/etc/sensors.d/file', 'any/etc/sensors.conf', 'any/etc/sensors3.conf', 'any/etc/sensors.d/file'], \ 'services': ['/etc/services', 'any/etc/services'], \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'], \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'], @@ -454,6 +484,8 @@ let s:filename_checks = { \ 'skill': ['file.il', 'file.ils', 'file.cdf'], \ 'slang': ['file.sl'], \ 'slice': ['file.ice'], + \ 'solidity': ['file.sol'], + \ 'solution': ['file.sln'], \ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'], \ 'slpreg': ['/etc/slp.reg', 'any/etc/slp.reg'], \ 'slpspi': ['/etc/slp.spi', 'any/etc/slp.spi'], @@ -475,13 +507,16 @@ let s:filename_checks = { \ 'sqlj': ['file.sqlj'], \ 'sqr': ['file.sqr', 'file.sqi'], \ 'squid': ['squid.conf'], + \ 'squirrel': ['file.nut'], \ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'], - \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config'], + \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config', 'any/.ssh/file.conf'], \ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'], \ 'st': ['file.st'], \ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'], \ 'stp': ['file.stp'], \ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'], + \ 'supercollider': ['file.quark'], + \ 'surface': ['file.sface'], \ 'svg': ['file.svg'], \ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'], \ 'swift': ['file.swift'], @@ -495,15 +530,18 @@ let s:filename_checks = { \ 'taskdata': ['pending.data', 'completed.data', 'undo.data'], \ 'taskedit': ['file.task'], \ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc'], + \ 'teal': ['file.tl'], \ 'teraterm': ['file.ttl'], \ 'terminfo': ['file.ti'], + \ 'terraform': ['file.tfvars'], \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'], \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'], \ 'texmf': ['texmf.cnf'], - \ 'text': ['file.text', 'README', '/usr/share/doc/bash-completion/AUTHORS'], + \ 'text': ['file.text', 'file.txt', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'], \ 'tf': ['file.tf', '.tfrc', 'tfrc'], \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'], \ 'tilde': ['file.t.html'], + \ 'tla': ['file.tla'], \ 'tli': ['file.tli'], \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'], \ 'toml': ['file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config'], @@ -515,6 +553,7 @@ let s:filename_checks = { \ 'tssgm': ['file.tssgm'], \ 'tssop': ['file.tssop'], \ 'twig': ['file.twig'], + \ 'typescript.glimmer': ['file.gts'], \ 'typescriptreact': ['file.tsx'], \ 'uc': ['file.uc'], \ 'udevconf': ['/etc/udev/udev.conf', 'any/etc/udev/udev.conf'], @@ -529,6 +568,7 @@ let s:filename_checks = { \ 'usserverlog': ['usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'], \ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'], \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'], + \ 'vala': ['file.vala'], \ 'vera': ['file.vr', 'file.vri', 'file.vrh'], \ 'verilog': ['file.v'], \ 'verilogams': ['file.va', 'file.vams'], @@ -553,7 +593,7 @@ let s:filename_checks = { \ 'xhtml': ['file.xhtml', 'file.xht'], \ 'xinetd': ['/etc/xinetd.conf', '/etc/xinetd.d/file', 'any/etc/xinetd.conf', 'any/etc/xinetd.d/file'], \ 'xmath': ['file.msc', 'file.msf'], - \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu'], + \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1', 'file.mpd'], \ 'xmodmap': ['anyXmodmap', 'Xmodmap', 'some-Xmodmap', 'some-xmodmap', 'some-xmodmap-file', 'xmodmap', 'xmodmap-file'], \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'], \ 'xpm': ['file.xpm'], @@ -564,8 +604,10 @@ let s:filename_checks = { \ 'xslt': ['file.xsl', 'file.xslt'], \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'], \ 'yaml': ['file.yaml', 'file.yml'], + \ 'yang': ['file.yang'], \ 'raml': ['file.raml'], \ 'z8a': ['file.z8a'], + \ 'zig': ['file.zig'], \ 'zimbu': ['file.zu'], \ 'zimbutempl': ['file.zut'], \ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh', '.zcompdump', '.zlogin', '.zlogout', '.zshenv', '.zshrc', '.zcompdump-file', '.zlog', '.zlog-file', '.zsh', '.zsh-file', 'any/etc/zprofile', 'zlog', 'zlog-file', 'zsh', 'zsh-file'], @@ -574,7 +616,7 @@ let s:filename_checks = { \ } let s:filename_case_checks = { - \ 'modula2': ['file.DEF', 'file.MOD'], + \ 'modula2': ['file.DEF'], \ 'bzl': ['file.BUILD', 'BUILD'], \ } @@ -647,7 +689,7 @@ let s:script_checks = { \ ['#!/path/nodejs'], \ ['#!/path/rhino']], \ 'bc': [['#!/path/bc']], - \ 'sed': [['#!/path/sed']], + \ 'sed': [['#!/path/sed'], ['#n'], ['#n comment']], \ 'ocaml': [['#!/path/ocaml']], \ 'awk': [['#!/path/awk'], \ ['#!/path/gawk']], @@ -663,6 +705,7 @@ let s:script_checks = { \ 'fennel': [['#!/path/fennel']], \ 'routeros': [['#!/path/rsc']], \ 'fish': [['#!/path/fish']], + \ 'forth': [['#!/path/gforth']], \ } " Various forms of "env" optional arguments. @@ -699,83 +742,227 @@ func Test_setfiletype_completion() call assert_equal('"setfiletype java javacc javascript javascriptreact', @:) endfunc -func Test_hook_file() +""""""""""""""""""""""""""""""""""""""""""""""""" +" Tests for specific extentions and filetypes. +" Keep sorted. +""""""""""""""""""""""""""""""""""""""""""""""""" + +func Test_bas_file() filetype on - call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook') - split Xfile.hook - call assert_equal('dosini', &filetype) + call writefile(['looks like BASIC'], 'Xfile.bas') + split Xfile.bas + call assert_equal('basic', &filetype) bwipe! - call writefile(['not pacman'], 'Xfile.hook') - split Xfile.hook - call assert_notequal('dosini', &filetype) + " Test dist#ft#FTbas() + + let g:filetype_bas = 'freebasic' + split Xfile.bas + call assert_equal('freebasic', &filetype) bwipe! + unlet g:filetype_bas - call delete('Xfile.hook') + " FreeBASIC + + call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! + + call writefile(['#define TESTING'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! + + call writefile(['option byval'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! + + call writefile(['extern "C"'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! + + " QB64 + + call writefile(['$LET TESTING = 1'], 'Xfile.bas') + split Xfile.bas + call assert_equal('qb64', &filetype) + bwipe! + + call writefile(['OPTION _EXPLICIT'], 'Xfile.bas') + split Xfile.bas + call assert_equal('qb64', &filetype) + bwipe! + + " Visual Basic + + call writefile(['Attribute VB_NAME = "Testing"'], 'Xfile.bas') + split Xfile.bas + call assert_equal('vb', &filetype) + bwipe! + + call delete('Xfile.bas') filetype off endfunc -func Test_ts_file() +" Test dist#ft#FTcfg() +func Test_cfg_file() filetype on - call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts') - split Xfile.ts - call assert_equal('xml', &filetype) + " *.cfg defaults to cfg + call writefile(['looks like cfg'], 'cfgfile.cfg') + split cfgfile.cfg + call assert_equal('cfg', &filetype) + + let g:filetype_cfg = 'other' + edit + call assert_equal('other', &filetype) bwipe! + unlet g:filetype_cfg + + " RAPID cfg + let ext = 'cfg' + for i in ['EIO', 'MMC', 'MOC', 'PROC', 'SIO', 'SYS'] + call writefile([i .. ':CFG'], 'cfgfile.' .. ext) + execute "split cfgfile." .. ext + call assert_equal('rapid', &filetype) + bwipe! + call delete('cfgfile.' .. ext) + " check different case of file extension + let ext = substitute(ext, '\(\l\)', '\u\1', '') + endfor - call writefile(['// looks like Typescript'], 'Xfile.ts') - split Xfile.ts - call assert_equal('typescript', &filetype) + filetype off +endfunc + +func Test_d_file() + filetype on + + call writefile(['looks like D'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) + bwipe! + + call writefile(['#!/some/bin/dtrace'], 'Xfile.d') + split Xfile.d + call assert_equal('dtrace', &filetype) + bwipe! + + call writefile(['#pragma D option'], 'Xfile.d') + split Xfile.d + call assert_equal('dtrace', &filetype) + bwipe! + + call writefile([':some:thing:'], 'Xfile.d') + split Xfile.d + call assert_equal('dtrace', &filetype) + bwipe! + + call writefile(['module this', '#pragma D option'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) + bwipe! + + call writefile(['import that', '#pragma D option'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) bwipe! - call delete('Xfile.hook') filetype off endfunc -func Test_ttl_file() +func Test_dat_file() filetype on - call writefile(['@base <http://example.org/> .'], 'Xfile.ttl') - split Xfile.ttl - call assert_equal('turtle', &filetype) + " KRL header start with "&WORD", but is not always present. + call writefile(['&ACCESS'], 'datfile.dat') + split datfile.dat + call assert_equal('krl', &filetype) bwipe! + call delete('datfile.dat') - call writefile(['looks like Tera Term Language'], 'Xfile.ttl') - split Xfile.ttl - call assert_equal('teraterm', &filetype) + " KRL defdat with leading spaces, for KRL file extension is not case + " sensitive. + call writefile([' DEFDAT datfile'], 'datfile.Dat') + split datfile.Dat + call assert_equal('krl', &filetype) bwipe! + call delete('datfile.Dat') + + " KRL defdat with embedded spaces, file starts with empty line(s). + call writefile(['', 'defdat datfile public'], 'datfile.DAT') + split datfile.DAT + call assert_equal('krl', &filetype) + bwipe! + + " User may overrule file inspection + let g:filetype_dat = 'dat' + split datfile.DAT + call assert_equal('dat', &filetype) + bwipe! + call delete('datfile.DAT') + unlet g:filetype_dat - call delete('Xfile.ttl') filetype off endfunc -func Test_pp_file() +func Test_dep3patch_file() filetype on - call writefile(['looks like puppet'], 'Xfile.pp') - split Xfile.pp - call assert_equal('puppet', &filetype) + call assert_true(mkdir('debian/patches', 'p')) + + " series files are not patches + call writefile(['Description: some awesome patch'], 'debian/patches/series') + split debian/patches/series + call assert_notequal('dep3patch', &filetype) bwipe! - let g:filetype_pp = 'pascal' - split Xfile.pp - call assert_equal('pascal', &filetype) + " diff/patch files without the right headers should still show up as ft=diff + call writefile([], 'debian/patches/foo.diff') + split debian/patches/foo.diff + call assert_equal('diff', &filetype) bwipe! - unlet g:filetype_pp - " Test dist#ft#FTpp() - call writefile(['{ pascal comment'], 'Xfile.pp') - split Xfile.pp - call assert_equal('pascal', &filetype) + " Files with the right headers are detected as dep3patch, even if they don't + " have a diff/patch extension + call writefile(['Subject: dep3patches'], 'debian/patches/bar') + split debian/patches/bar + call assert_equal('dep3patch', &filetype) bwipe! - call writefile(['procedure pascal'], 'Xfile.pp') - split Xfile.pp - call assert_equal('pascal', &filetype) + " Files in sub-directories are detected + call assert_true(mkdir('debian/patches/s390x', 'p')) + call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar') + split debian/patches/s390x/bar + call assert_equal('dep3patch', &filetype) bwipe! - call delete('Xfile.pp') + " The detection stops when seeing the "header end" marker + call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz') + split debian/patches/baz + call assert_notequal('dep3patch', &filetype) + bwipe! + + call delete('debian', 'rf') +endfunc + +func Test_dsl_file() + filetype on + + call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl') + split dslfile.dsl + call assert_equal('dsl', &filetype) + bwipe! + + call writefile(['workspace {'], 'dslfile.dsl') + split dslfile.dsl + call assert_equal('structurizr', &filetype) + bwipe! + + call delete('dslfile.dsl') filetype off endfunc @@ -816,20 +1003,183 @@ func Test_ex_file() filetype off endfunc -func Test_dsl_file() +func Test_foam_file() filetype on + call assert_true(mkdir('0', 'p')) + call assert_true(mkdir('0.orig', 'p')) - call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl') - split dslfile.dsl - call assert_equal('dsl', &filetype) + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict') + split Xfile1Dict + call assert_equal('foam', &filetype) bwipe! - call writefile(['workspace {'], 'dslfile.dsl') - split dslfile.dsl - call assert_equal('structurizr', &filetype) + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something') + split Xfile1Dict.something + call assert_equal('foam', &filetype) bwipe! - call delete('dslfile.dsl') + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0/Xfile') + split 0/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile') + split 0.orig/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call delete('0', 'rf') + call delete('0.orig', 'rf') + call delete('Xfile1Dict') + call delete('Xfile1Dict.something') + call delete('XfileProperties') + call delete('XfileProperties.something') + filetype off +endfunc + +func Test_frm_file() + filetype on + + call writefile(['looks like FORM'], 'Xfile.frm') + split Xfile.frm + call assert_equal('form', &filetype) + bwipe! + + " Test dist#ft#FTfrm() + + let g:filetype_frm = 'form' + split Xfile.frm + call assert_equal('form', &filetype) + bwipe! + unlet g:filetype_frm + + " Visual Basic + + call writefile(['Begin VB.Form Form1'], 'Xfile.frm') + split Xfile.frm + call assert_equal('vb', &filetype) + bwipe! + + call delete('Xfile.frm') + filetype off +endfunc + +func Test_fs_file() + filetype on + + call writefile(['looks like F#'], 'Xfile.fs') + split Xfile.fs + call assert_equal('fsharp', &filetype) + bwipe! + + let g:filetype_fs = 'forth' + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + unlet g:filetype_fs + + " Test dist#ft#FTfs() + + " Forth (Gforth) + + call writefile(['( Forth inline comment )'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile(['\ Forth line comment'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + " empty line comment - no space required + call writefile(['\'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile(['\G Forth documentation comment '], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call delete('Xfile.fs') + filetype off +endfunc + +func Test_git_file() + filetype on + + call assert_true(mkdir('Xrepo.git', 'p')) + + call writefile([], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('', &filetype) + bwipe! + + call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('git', &filetype) + bwipe! + + call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('git', &filetype) + bwipe! + + call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('git', &filetype) + bwipe! + + call delete('Xrepo.git', 'rf') + filetype off +endfunc + +func Test_hook_file() + filetype on + + call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook') + split Xfile.hook + call assert_equal('dosini', &filetype) + bwipe! + + call writefile(['not pacman'], 'Xfile.hook') + split Xfile.hook + call assert_notequal('dosini', &filetype) + bwipe! + + call delete('Xfile.hook') filetype off endfunc @@ -866,6 +1216,16 @@ func Test_m_file() call assert_equal('objc', &filetype) bwipe! + call writefile(['#include <header.h>'], 'Xfile.m') + split Xfile.m + call assert_equal('objc', &filetype) + bwipe! + + call writefile(['#define FORTY_TWO'], 'Xfile.m') + split Xfile.m + call assert_equal('objc', &filetype) + bwipe! + " Octave call writefile(['# Octave line comment'], 'Xfile.m') @@ -923,6 +1283,380 @@ func Test_m_file() filetype off endfunc +func Test_mod_file() + filetype on + + " *.mod defaults to Modsim III + call writefile(['locks like Modsim III'], 'modfile.mod') + split modfile.mod + call assert_equal('modsim3', &filetype) + bwipe! + + " Users preference set by g:filetype_mod + let g:filetype_mod = 'lprolog' + split modfile.mod + call assert_equal('lprolog', &filetype) + unlet g:filetype_mod + bwipe! + + " RAPID header start with a line containing only "%%%", + " but is not always present. + call writefile(['%%%'], 'modfile.mod') + split modfile.mod + call assert_equal('rapid', &filetype) + bwipe! + call delete('modfile.mod') + + " RAPID supports umlauts in module names, leading spaces, + " the .mod extension is not case sensitive. + call writefile([' module ÜmlautModule'], 'modfile.Mod') + split modfile.Mod + call assert_equal('rapid', &filetype) + bwipe! + call delete('modfile.Mod') + + " RAPID is not case sensitive, embedded spaces, sysmodule, + " file starts with empty line(s). + call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'modfile.MOD') + split modfile.MOD + call assert_equal('rapid', &filetype) + bwipe! + + " Modula-2 MODULE not start of line + call writefile(['IMPLEMENTATION MODULE Module2Mod;'], 'modfile.MOD') + split modfile.MOD + call assert_equal('modula2', &filetype) + bwipe! + + " Modula-2 with comment and empty lines prior MODULE + call writefile(['', '(* with', ' comment *)', '', 'MODULE Module2Mod;'], 'modfile.MOD') + split modfile.MOD + call assert_equal('modula2', &filetype) + bwipe! + call delete('modfile.MOD') + + " LambdaProlog module + call writefile(['module lpromod.'], 'modfile.mod') + split modfile.mod + call assert_equal('lprolog', &filetype) + bwipe! + + " LambdaProlog with comment and empty lines prior module + call writefile(['', '% with', '% comment', '', 'module lpromod.'], 'modfile.mod') + split modfile.mod + call assert_equal('lprolog', &filetype) + bwipe! + call delete('modfile.mod') + + " go.mod + call writefile(['module example.com/M'], 'go.mod') + split go.mod + call assert_equal('gomod', &filetype) + bwipe! + call delete('go.mod') + + filetype off +endfunc + +func Test_patch_file() + filetype on + + call writefile([], 'Xfile.patch') + split Xfile.patch + call assert_equal('diff', &filetype) + bwipe! + + call writefile(['From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch') + split Xfile.patch + call assert_equal('gitsendemail', &filetype) + bwipe! + + call writefile(['From 0000000000000000000000000000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch') + split Xfile.patch + call assert_equal('gitsendemail', &filetype) + bwipe! + + call delete('Xfile.patch') + filetype off +endfunc + +func Test_perl_file() + filetype on + + " only tests one case, should do more + let lines =<< trim END + + use a + END + call writefile(lines, "Xfile.t") + split Xfile.t + call assert_equal('perl', &filetype) + bwipe + + call delete('Xfile.t') + filetype off +endfunc + +func Test_pp_file() + filetype on + + call writefile(['looks like puppet'], 'Xfile.pp') + split Xfile.pp + call assert_equal('puppet', &filetype) + bwipe! + + let g:filetype_pp = 'pascal' + split Xfile.pp + call assert_equal('pascal', &filetype) + bwipe! + unlet g:filetype_pp + + " Test dist#ft#FTpp() + call writefile(['{ pascal comment'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) + bwipe! + + call writefile(['procedure pascal'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) + bwipe! + + call delete('Xfile.pp') + filetype off +endfunc + +" Test dist#ft#FTprg() +func Test_prg_file() + filetype on + + " *.prg defaults to clipper + call writefile(['looks like clipper'], 'prgfile.prg') + split prgfile.prg + call assert_equal('clipper', &filetype) + bwipe! + + " Users preference set by g:filetype_prg + let g:filetype_prg = 'eviews' + split prgfile.prg + call assert_equal('eviews', &filetype) + unlet g:filetype_prg + bwipe! + + " RAPID header start with a line containing only "%%%", + " but is not always present. + call writefile(['%%%'], 'prgfile.prg') + split prgfile.prg + call assert_equal('rapid', &filetype) + bwipe! + call delete('prgfile.prg') + + " RAPID supports umlauts in module names, leading spaces, + " the .prg extension is not case sensitive. + call writefile([' module ÜmlautModule'], 'prgfile.Prg') + split prgfile.Prg + call assert_equal('rapid', &filetype) + bwipe! + call delete('prgfile.Prg') + + " RAPID is not case sensitive, embedded spaces, sysmodule, + " file starts with empty line(s). + call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'prgfile.PRG') + split prgfile.PRG + call assert_equal('rapid', &filetype) + bwipe! + call delete('prgfile.PRG') + + filetype off +endfunc + +" Test dist#ft#FTsc() +func Test_sc_file() + filetype on + + " SC file mehtods are defined 'Class : Method' + call writefile(['SCNvimDocRenderer : SCDocHTMLRenderer {'], 'srcfile.sc') + split srcfile.sc + call assert_equal('supercollider', &filetype) + bwipe! + call delete('srcfile.sc') + + " SC classes are defined with '+ Class {}' + call writefile(['+ SCNvim {', '*methodArgs {|method|'], 'srcfile.sc') + split srcfile.sc + call assert_equal('supercollider', &filetype) + bwipe! + call delete('srcfile.sc') + + " Some SC class files start with comment and define methods many lines later + call writefile(['// Query', '//Method','^this {'], 'srcfile.sc') + split srcfile.sc + call assert_equal('supercollider', &filetype) + bwipe! + call delete('srcfile.sc') + + " Some SC class files put comments between method declaration after class + call writefile(['PingPong {', '//comment','*ar { arg'], 'srcfile.sc') + split srcfile.sc + call assert_equal('supercollider', &filetype) + bwipe! + call delete('srcfile.sc') + + filetype off +endfunc + +" Test dist#ft#FTscd() +func Test_scd_file() + filetype on + + call writefile(['ijq(1)'], 'srcfile.scd') + split srcfile.scd + call assert_equal('scdoc', &filetype) + bwipe! + call delete('srcfile.scd') + + filetype off +endfunc + +func Test_src_file() + filetype on + + " KRL header start with "&WORD", but is not always present. + call writefile(['&ACCESS'], 'srcfile.src') + split srcfile.src + call assert_equal('krl', &filetype) + bwipe! + call delete('srcfile.src') + + " KRL def with leading spaces, for KRL file extension is not case sensitive. + call writefile([' DEF srcfile()'], 'srcfile.Src') + split srcfile.Src + call assert_equal('krl', &filetype) + bwipe! + call delete('srcfile.Src') + + " KRL global def with embedded spaces, file starts with empty line(s). + call writefile(['', 'global def srcfile()'], 'srcfile.SRC') + split srcfile.SRC + call assert_equal('krl', &filetype) + bwipe! + + " User may overrule file inspection + let g:filetype_src = 'src' + split srcfile.SRC + call assert_equal('src', &filetype) + bwipe! + call delete('srcfile.SRC') + unlet g:filetype_src + + filetype off +endfunc + +func Test_sys_file() + filetype on + + " *.sys defaults to Batch file for MSDOS + call writefile(['looks like dos batch'], 'sysfile.sys') + split sysfile.sys + call assert_equal('bat', &filetype) + bwipe! + + " RAPID header start with a line containing only "%%%", + " but is not always present. + call writefile(['%%%'], 'sysfile.sys') + split sysfile.sys + call assert_equal('rapid', &filetype) + bwipe! + call delete('sysfile.sys') + + " RAPID supports umlauts in module names, leading spaces, + " the .sys extension is not case sensitive. + call writefile([' module ÜmlautModule'], 'sysfile.Sys') + split sysfile.Sys + call assert_equal('rapid', &filetype) + bwipe! + call delete('sysfile.Sys') + + " RAPID is not case sensitive, embedded spaces, sysmodule, + " file starts with empty line(s). + call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'sysfile.SYS') + split sysfile.SYS + call assert_equal('rapid', &filetype) + bwipe! + call delete('sysfile.SYS') + + filetype off +endfunc + +func Test_tex_file() + filetype on + + " only tests one case, should do more + let lines =<< trim END + % This is a sentence. + + This is a sentence. + END + call writefile(lines, "Xfile.tex") + split Xfile.tex + call assert_equal('plaintex', &filetype) + bwipe + + call delete('Xfile.tex') + filetype off +endfunc + +func Test_tf_file() + filetype on + + call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf') + split Xfile.tf + call assert_equal('tf', &filetype) + bwipe! + + call writefile(['provider "azurerm" {'], 'Xfile.tf') + split Xfile.tf + call assert_equal('terraform', &filetype) + bwipe! + + call delete('Xfile.tf') + filetype off +endfunc + +func Test_ts_file() + filetype on + + call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts') + split Xfile.ts + call assert_equal('xml', &filetype) + bwipe! + + call writefile(['// looks like Typescript'], 'Xfile.ts') + split Xfile.ts + call assert_equal('typescript', &filetype) + bwipe! + + call delete('Xfile.ts') + filetype off +endfunc + +func Test_ttl_file() + filetype on + + call writefile(['@base <http://example.org/> .'], 'Xfile.ttl') + split Xfile.ttl + call assert_equal('turtle', &filetype) + bwipe! + + call writefile(['looks like Tera Term Language'], 'Xfile.ttl') + split Xfile.ttl + call assert_equal('teraterm', &filetype) + bwipe! + + call delete('Xfile.ttl') + filetype off +endfunc + func Test_xpm_file() filetype on @@ -935,4 +1669,5 @@ func Test_xpm_file() filetype off endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filetype_lua.vim b/src/nvim/testdir/test_filetype_lua.vim new file mode 100644 index 0000000000..f73e4ca33f --- /dev/null +++ b/src/nvim/testdir/test_filetype_lua.vim @@ -0,0 +1,2 @@ +let g:do_filetype_lua = 1 +source test_filetype.vim diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim index 0c45db049b..d465e48c7b 100644 --- a/src/nvim/testdir/test_filter_cmd.vim +++ b/src/nvim/testdir/test_filter_cmd.vim @@ -145,3 +145,38 @@ func Test_filter_commands() bwipe! file.h bwipe! file.hs endfunc + +func Test_filter_display() + edit Xdoesnotmatch + let @a = '!!willmatch' + let @b = '!!doesnotmatch' + let @c = "oneline\ntwoline\nwillmatch\n" + let @/ = '!!doesnotmatch' + call feedkeys(":echo '!!doesnotmatch:'\<CR>", 'ntx') + let lines = map(split(execute('filter /willmatch/ display'), "\n"), 'v:val[5:6]') + + call assert_true(index(lines, '"a') >= 0) + call assert_false(index(lines, '"b') >= 0) + call assert_true(index(lines, '"c') >= 0) + call assert_false(index(lines, '"/') >= 0) + call assert_false(index(lines, '":') >= 0) + call assert_false(index(lines, '"%') >= 0) + + let lines = map(split(execute('filter /doesnotmatch/ display'), "\n"), 'v:val[5:6]') + call assert_true(index(lines, '"a') < 0) + call assert_false(index(lines, '"b') < 0) + call assert_true(index(lines, '"c') < 0) + call assert_false(index(lines, '"/') < 0) + call assert_false(index(lines, '":') < 0) + call assert_false(index(lines, '"%') < 0) + + bwipe! +endfunc + +func Test_filter_scriptnames() + let lines = split(execute('filter /test_filter_cmd/ scriptnames'), "\n") + call assert_equal(1, len(lines)) + call assert_match('filter_cmd', lines[0]) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim index a52a66ac2f..1cd3a2287b 100644 --- a/src/nvim/testdir/test_filter_map.vim +++ b/src/nvim/testdir/test_filter_map.vim @@ -88,4 +88,14 @@ func Test_map_filter_fails() call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:') endfunc +func Test_map_and_modify() + let l = ["abc"] + " cannot change the list halfway a map() + call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:') + + let d = #{a: 1, b: 2, c: 3} + call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:') + call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim index 5a20475d3d..1684c5d30a 100644 --- a/src/nvim/testdir/test_findfile.vim +++ b/src/nvim/testdir/test_findfile.vim @@ -226,4 +226,26 @@ func Test_find_cmd() call assert_fails('tabfind', 'E471:') endfunc +func Test_find_non_existing_path() + new + let save_path = &path + let save_dir = getcwd() + call mkdir('dir1/dir2', 'p') + call writefile([], 'dir1/file.txt') + call writefile([], 'dir1/dir2/base.txt') + call chdir('dir1/dir2') + e base.txt + set path=../include + + call assert_fails(':find file.txt', 'E345:') + + call chdir(save_dir) + bw! + call delete('dir1/dir2/base.txt', 'rf') + call delete('dir1/dir2', 'rf') + call delete('dir1/file.txt', 'rf') + call delete('dir1', 'rf') + let &path = save_path +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim index 411f7ebbb3..5ae2a5ee17 100644 --- a/src/nvim/testdir/test_fnamemodify.vim +++ b/src/nvim/testdir/test_fnamemodify.vim @@ -3,8 +3,10 @@ func Test_fnamemodify() let save_home = $HOME let save_shell = &shell + let save_shellslash = &shellslash let $HOME = fnamemodify('.', ':p:h:h') set shell=sh + set shellslash call assert_equal('/', fnamemodify('.', ':p')[-1:]) call assert_equal('r', fnamemodify('.', ':p:h')[-1:]) @@ -27,6 +29,21 @@ func Test_fnamemodify() call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e')) call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e:e')) call assert_equal('tar', fnamemodify('abc.fb2.tar.gz', ':e:e:r')) + call assert_equal(getcwd(), fnamemodify('', ':p:h')) + + let cwd = getcwd() + call chdir($HOME) + call assert_equal('foobar', fnamemodify('~/foobar', ':~:.')) + call chdir(cwd) + call mkdir($HOME . '/XXXXXXXX/a', 'p') + call mkdir($HOME . '/XXXXXXXX/b', 'p') + call chdir($HOME . '/XXXXXXXX/a/') + call assert_equal('foo', fnamemodify($HOME . '/XXXXXXXX/a/foo', ':p:~:.')) + call assert_equal('~/XXXXXXXX/b/foo', fnamemodify($HOME . '/XXXXXXXX/b/foo', ':p:~:.')) + call mkdir($HOME . '/XXXXXXXX/a.ext', 'p') + call assert_equal('~/XXXXXXXX/a.ext/foo', fnamemodify($HOME . '/XXXXXXXX/a.ext/foo', ':p:~:.')) + call chdir(cwd) + call delete($HOME . '/XXXXXXXX', 'rf') call assert_equal('''abc def''', fnamemodify('abc def', ':S')) call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S')) @@ -44,6 +61,7 @@ func Test_fnamemodify() let $HOME = save_home let &shell = save_shell + let &shellslash = save_shellslash endfunc func Test_fnamemodify_er() @@ -73,6 +91,7 @@ func Test_fnamemodify_er() call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e')) call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e')) + call assert_equal('', fnamemodify('', ':p:t')) call assert_equal('', fnamemodify(v:_null_string, v:_null_string)) endfunc diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 5586fe2151..6da1b3d4a0 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -809,8 +809,7 @@ func Test_undo_fold_deletion() g/"/d undo redo - " eval getline(1, '$')->assert_equal(['']) - eval assert_equal(getline(1, '$'), ['']) + eval getline(1, '$')->assert_equal(['']) set fdm&vim bwipe! diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 4a2ade5afa..f8be250f73 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -143,7 +143,7 @@ func Test_str2nr() call assert_equal(-123456789, str2nr('-123456789')) call assert_equal(5, str2nr('101', 2)) - call assert_equal(5, str2nr('0b101', 2)) + call assert_equal(5, '0b101'->str2nr(2)) call assert_equal(5, str2nr('0B101', 2)) call assert_equal(-5, str2nr('-101', 2)) call assert_equal(-5, str2nr('-0b101', 2)) @@ -200,7 +200,7 @@ func Test_strftime() " of strftime() can be 17 or 18, depending on timezone. call assert_match('^2017-01-1[78]$', strftime('%Y-%m-%d', 1484695512)) " - call assert_match('^\d\d\d\d-\(0\d\|1[012]\)-\([012]\d\|3[01]\) \([01]\d\|2[0-3]\):[0-5]\d:\([0-5]\d\|60\)$', strftime('%Y-%m-%d %H:%M:%S')) + call assert_match('^\d\d\d\d-\(0\d\|1[012]\)-\([012]\d\|3[01]\) \([01]\d\|2[0-3]\):[0-5]\d:\([0-5]\d\|60\)$', '%Y-%m-%d %H:%M:%S'->strftime()) call assert_fails('call strftime([])', 'E730:') call assert_fails('call strftime("%Y", [])', 'E745:') @@ -307,13 +307,19 @@ func Test_resolve_unix() call assert_equal('/', resolve('/')) endfunc +func s:normalize_fname(fname) + let ret = substitute(a:fname, '\', '/', 'g') + let ret = substitute(ret, '//', '/', 'g') + return ret->tolower() +endfunc + func Test_simplify() call assert_equal('', simplify('')) call assert_equal('/', simplify('/')) call assert_equal('/', simplify('/.')) call assert_equal('/', simplify('/..')) call assert_equal('/...', simplify('/...')) - call assert_equal('./dir/file', simplify('./dir/file')) + call assert_equal('./dir/file', './dir/file'->simplify()) call assert_equal('./dir/file', simplify('.///dir//file')) call assert_equal('./dir/file', simplify('./dir/./file')) call assert_equal('./file', simplify('./dir/../file')) @@ -326,37 +332,6 @@ func Test_simplify() call assert_fails('call simplify(1.2)', 'E806:') endfunc -func Test_setbufvar_options() - " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the - " window layout. - call assert_equal(1, winnr('$')) - split dummy_preview - resize 2 - set winfixheight winfixwidth - let prev_id = win_getid() - - wincmd j - let wh = winheight(0) - let dummy_buf = bufnr('dummy_buf1', v:true) - call setbufvar(dummy_buf, '&buftype', 'nofile') - execute 'belowright vertical split #' . dummy_buf - call assert_equal(wh, winheight(0)) - let dum1_id = win_getid() - - wincmd h - let wh = winheight(0) - let dummy_buf = bufnr('dummy_buf2', v:true) - call setbufvar(dummy_buf, '&buftype', 'nofile') - execute 'belowright vertical split #' . dummy_buf - call assert_equal(wh, winheight(0)) - - bwipe! - call win_gotoid(prev_id) - bwipe! - call win_gotoid(dum1_id) - bwipe! -endfunc - func Test_pathshorten() call assert_equal('', pathshorten('')) call assert_equal('foo', pathshorten('foo')) @@ -370,12 +345,31 @@ func Test_pathshorten() call assert_equal('~.f/bar', pathshorten('~.foo/bar')) call assert_equal('.~f/bar', pathshorten('.~foo/bar')) call assert_equal('~/f/bar', pathshorten('~/foo/bar')) + call assert_fails('call pathshorten([])', 'E730:') + + " test pathshorten with optional variable to set preferred size of shortening + call assert_equal('', pathshorten('', 2)) + call assert_equal('foo', pathshorten('foo', 2)) + call assert_equal('/foo', pathshorten('/foo', 2)) + call assert_equal('fo/', pathshorten('foo/', 2)) + call assert_equal('fo/bar', pathshorten('foo/bar', 2)) + call assert_equal('fo/ba/foobar', pathshorten('foo/bar/foobar', 2)) + call assert_equal('/fo/ba/foobar', pathshorten('/foo/bar/foobar', 2)) + call assert_equal('.fo/bar', pathshorten('.foo/bar', 2)) + call assert_equal('~fo/bar', pathshorten('~foo/bar', 2)) + call assert_equal('~.fo/bar', pathshorten('~.foo/bar', 2)) + call assert_equal('.~fo/bar', pathshorten('.~foo/bar', 2)) + call assert_equal('~/fo/bar', pathshorten('~/foo/bar', 2)) + call assert_fails('call pathshorten([],2)', 'E730:') + call assert_notequal('~/fo/bar', pathshorten('~/foo/bar', 3)) + call assert_equal('~/foo/bar', pathshorten('~/foo/bar', 3)) + call assert_equal('~/f/bar', pathshorten('~/foo/bar', 0)) endfunc func Test_strpart() call assert_equal('de', strpart('abcdefg', 3, 2)) call assert_equal('ab', strpart('abcdefg', -2, 4)) - call assert_equal('abcdefg', strpart('abcdefg', -2)) + call assert_equal('abcdefg', 'abcdefg'->strpart(-2)) call assert_equal('fg', strpart('abcdefg', 5, 4)) call assert_equal('defg', strpart('abcdefg', 3)) @@ -469,7 +463,7 @@ func Test_toupper() \ toupper(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')) " Test with a few lowercase diacritics. - call assert_equal("AÀÃÂÃÄÅĀĂĄÇÇžÇ áº¢", toupper("aà áâãäåÄăąǎǟǡả")) + call assert_equal("AÀÃÂÃÄÅĀĂĄÇÇžÇ áº¢", "aà áâãäåÄăąǎǟǡả"->toupper()) call assert_equal("BḂḆ", toupper("bḃḇ")) call assert_equal("CÇĆĈĊČ", toupper("cçćĉċÄ")) call assert_equal("DÄŽÄḊḎá¸", toupper("dÄđḋá¸á¸‘")) @@ -532,6 +526,11 @@ func Test_toupper() call toupper("123\xC0\x80\xC0") endfunc +func Test_tr() + call assert_equal('foo', tr('bar', 'bar', 'foo')) + call assert_equal('zxy', 'cab'->tr('abc', 'xyz')) +endfunc + " Tests for the mode() function let current_modes = '' func Save_mode() @@ -809,11 +808,11 @@ endfunc func Test_stridx() call assert_equal(-1, stridx('', 'l')) call assert_equal(0, stridx('', '')) - call assert_equal(0, stridx('hello', '')) + call assert_equal(0, 'hello'->stridx('')) call assert_equal(-1, stridx('hello', 'L')) call assert_equal(2, stridx('hello', 'l', -1)) call assert_equal(2, stridx('hello', 'l', 0)) - call assert_equal(2, stridx('hello', 'l', 1)) + call assert_equal(2, 'hello'->stridx('l', 1)) call assert_equal(3, stridx('hello', 'l', 3)) call assert_equal(-1, stridx('hello', 'l', 4)) call assert_equal(-1, stridx('hello', 'l', 10)) @@ -826,7 +825,7 @@ func Test_strridx() call assert_equal(0, strridx('', '')) call assert_equal(5, strridx('hello', '')) call assert_equal(-1, strridx('hello', 'L')) - call assert_equal(3, strridx('hello', 'l')) + call assert_equal(3, 'hello'->strridx('l')) call assert_equal(3, strridx('hello', 'l', 10)) call assert_equal(3, strridx('hello', 'l', 3)) call assert_equal(2, strridx('hello', 'l', 2)) @@ -1219,7 +1218,7 @@ func Test_shellescape() let save_shell = &shell set shell=bash call assert_equal("'text'", shellescape('text')) - call assert_equal("'te\"xt'", shellescape('te"xt')) + call assert_equal("'te\"xt'", 'te"xt'->shellescape()) call assert_equal("'te'\\''xt'", shellescape("te'xt")) call assert_equal("'te%xt'", shellescape("te%xt")) @@ -1263,6 +1262,37 @@ func Test_shellescape() let &shell = save_shell endfunc +func Test_setbufvar_options() + " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the + " window layout. + call assert_equal(1, winnr('$')) + split dummy_preview + resize 2 + set winfixheight winfixwidth + let prev_id = win_getid() + + wincmd j + let wh = winheight(0) + let dummy_buf = bufnr('dummy_buf1', v:true) + call setbufvar(dummy_buf, '&buftype', 'nofile') + execute 'belowright vertical split #' . dummy_buf + call assert_equal(wh, winheight(0)) + let dum1_id = win_getid() + + wincmd h + let wh = winheight(0) + let dummy_buf = bufnr('dummy_buf2', v:true) + eval 'nofile'->setbufvar(dummy_buf, '&buftype') + execute 'belowright vertical split #' . dummy_buf + call assert_equal(wh, winheight(0)) + + bwipe! + call win_gotoid(prev_id) + bwipe! + call win_gotoid(dum1_id) + bwipe! +endfunc + func Test_redo_in_nested_functions() nnoremap g. :set opfunc=Operator<CR>g@ function Operator( type, ... ) @@ -1293,7 +1323,7 @@ endfunc func Test_trim() call assert_equal("Testing", trim(" \t\r\r\x0BTesting \t\n\r\n\t\x0B\x0B")) - call assert_equal("Testing", trim(" \t \r\r\n\n\x0BTesting \t\n\r\n\t\x0B\x0B")) + call assert_equal("Testing", " \t \r\r\n\n\x0BTesting \t\n\r\n\t\x0B\x0B"->trim()) call assert_equal("RESERVE", trim("xyz \twwRESERVEzyww \t\t", " wxyz\t")) call assert_equal("wRE \tSERVEzyww", trim("wRE \tSERVEzyww")) call assert_equal("abcd\t xxxx tail", trim(" \tabcd\t xxxx tail")) @@ -1320,68 +1350,6 @@ func Test_trim() call assert_equal("x", trim(chars . "x" . chars)) endfunc -func EditAnotherFile() - let word = expand('<cword>') - edit Xfuncrange2 -endfunc - -func Test_func_range_with_edit() - " Define a function that edits another buffer, then call it with a range that - " is invalid in that buffer. - call writefile(['just one line'], 'Xfuncrange2') - new - call setline(1, 10->range()) - write Xfuncrange1 - call assert_fails('5,8call EditAnotherFile()', 'E16:') - - call delete('Xfuncrange1') - call delete('Xfuncrange2') - bwipe! -endfunc - -func Test_func_exists_on_reload() - call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists') - call assert_equal(0, exists('*ExistingFunction')) - source Xfuncexists - call assert_equal(1, '*ExistingFunction'->exists()) - " Redefining a function when reloading a script is OK. - source Xfuncexists - call assert_equal(1, exists('*ExistingFunction')) - - " But redefining in another script is not OK. - call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2') - call assert_fails('source Xfuncexists2', 'E122:') - - delfunc ExistingFunction - call assert_equal(0, exists('*ExistingFunction')) - call writefile([ - \ 'func ExistingFunction()', 'echo "yes"', 'endfunc', - \ 'func ExistingFunction()', 'echo "no"', 'endfunc', - \ ], 'Xfuncexists') - call assert_fails('source Xfuncexists', 'E122:') - call assert_equal(1, exists('*ExistingFunction')) - - call delete('Xfuncexists2') - call delete('Xfuncexists') - delfunc ExistingFunction -endfunc - -sandbox function Fsandbox() - normal ix -endfunc - -func Test_func_sandbox() - sandbox let F = {-> 'hello'} - call assert_equal('hello', F()) - - sandbox let F = {-> "normal ix\<Esc>"->execute()} - call assert_fails('call F()', 'E48:') - unlet F - - call assert_fails('call Fsandbox()', 'E48:') - delfunc Fsandbox -endfunc - " Test for reg_recording() and reg_executing() func Test_reg_executing_and_recording() let s:reg_stat = '' @@ -1483,6 +1451,10 @@ func Test_getchar() call assert_equal('', getcharstr(0)) call assert_equal('', getcharstr(1)) + call feedkeys("\<M-F2>", '') + call assert_equal("\<M-F2>", getchar(0)) + call assert_equal(0, getchar(0)) + call setline(1, 'xxxx') " call test_setmouse(1, 3) " let v:mouse_win = 9 @@ -1508,24 +1480,31 @@ func Test_libcall_libcallnr() let libc = 'msvcrt.dll' elseif has('mac') let libc = 'libSystem.B.dylib' - elseif system('uname -s') =~ 'SunOS' - " Set the path to libc.so according to the architecture. - let test_bits = system('file ' . GetVimProg()) - let test_arch = system('uname -p') - if test_bits =~ '64-bit' && test_arch =~ 'sparc' - let libc = '/usr/lib/sparcv9/libc.so' - elseif test_bits =~ '64-bit' && test_arch =~ 'i386' - let libc = '/usr/lib/amd64/libc.so' + elseif executable('ldd') + let libc = matchstr(split(system('ldd ' . GetVimProg())), '/libc\.so\>') + endif + if get(l:, 'libc', '') ==# '' + " On Unix, libc.so can be in various places. + if has('linux') + " There is not documented but regarding the 1st argument of glibc's + " dlopen an empty string and nullptr are equivalent, so using an empty + " string for the 1st argument of libcall allows to call functions. + let libc = '' + elseif has('sun') + " Set the path to libc.so according to the architecture. + let test_bits = system('file ' . GetVimProg()) + let test_arch = system('uname -p') + if test_bits =~ '64-bit' && test_arch =~ 'sparc' + let libc = '/usr/lib/sparcv9/libc.so' + elseif test_bits =~ '64-bit' && test_arch =~ 'i386' + let libc = '/usr/lib/amd64/libc.so' + else + let libc = '/usr/lib/libc.so' + endif else - let libc = '/usr/lib/libc.so' + " Unfortunately skip this test until a good way is found. + return endif - elseif system('uname -s') =~ 'OpenBSD' - let libc = 'libc.so' - else - " On Unix, libc.so can be in various places. - " Interestingly, using an empty string for the 1st argument of libcall - " allows to call functions from libc which is not documented. - let libc = '' endif if has('win32') @@ -1548,54 +1527,99 @@ func Test_libcall_libcallnr() call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:') endfunc -func Test_bufadd_bufload() - call assert_equal(0, bufexists('someName')) - let buf = bufadd('someName') - call assert_notequal(0, buf) - call assert_equal(1, bufexists('someName')) - call assert_equal(0, getbufvar(buf, '&buflisted')) - call assert_equal(0, bufloaded(buf)) - call bufload(buf) - call assert_equal(1, bufloaded(buf)) - call assert_equal([''], getbufline(buf, 1, '$')) +sandbox function Fsandbox() + normal ix +endfunc - let curbuf = bufnr('') - call writefile(['some', 'text'], 'XotherName') - let buf = 'XotherName'->bufadd() - call assert_notequal(0, buf) - eval 'XotherName'->bufexists()->assert_equal(1) - call assert_equal(0, getbufvar(buf, '&buflisted')) - call assert_equal(0, bufloaded(buf)) - eval buf->bufload() - call assert_equal(1, bufloaded(buf)) - call assert_equal(['some', 'text'], getbufline(buf, 1, '$')) - call assert_equal(curbuf, bufnr('')) +func Test_func_sandbox() + sandbox let F = {-> 'hello'} + call assert_equal('hello', F()) - let buf1 = bufadd('') - let buf2 = bufadd('') - call assert_notequal(0, buf1) - call assert_notequal(0, buf2) - call assert_notequal(buf1, buf2) - call assert_equal(1, bufexists(buf1)) - call assert_equal(1, bufexists(buf2)) - call assert_equal(0, bufloaded(buf1)) - exe 'bwipe ' .. buf1 - call assert_equal(0, bufexists(buf1)) - call assert_equal(1, bufexists(buf2)) - exe 'bwipe ' .. buf2 - call assert_equal(0, bufexists(buf2)) + sandbox let F = {-> "normal ix\<Esc>"->execute()} + call assert_fails('call F()', 'E48:') + unlet F - bwipe someName - bwipe XotherName - call assert_equal(0, bufexists('someName')) - call delete('XotherName') + call assert_fails('call Fsandbox()', 'E48:') + delfunc Fsandbox endfunc -func Test_readdir() - if isdirectory('Xdir') - call delete('Xdir', 'rf') +func EditAnotherFile() + let word = expand('<cword>') + edit Xfuncrange2 +endfunc + +func Test_func_range_with_edit() + " Define a function that edits another buffer, then call it with a range that + " is invalid in that buffer. + call writefile(['just one line'], 'Xfuncrange2') + new + eval 10->range()->setline(1) + write Xfuncrange1 + call assert_fails('5,8call EditAnotherFile()', 'E16:') + + call delete('Xfuncrange1') + call delete('Xfuncrange2') + bwipe! +endfunc + +func Test_func_exists_on_reload() + call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists') + call assert_equal(0, exists('*ExistingFunction')) + source Xfuncexists + call assert_equal(1, '*ExistingFunction'->exists()) + " Redefining a function when reloading a script is OK. + source Xfuncexists + call assert_equal(1, exists('*ExistingFunction')) + + " But redefining in another script is not OK. + call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2') + call assert_fails('source Xfuncexists2', 'E122:') + + delfunc ExistingFunction + call assert_equal(0, exists('*ExistingFunction')) + call writefile([ + \ 'func ExistingFunction()', 'echo "yes"', 'endfunc', + \ 'func ExistingFunction()', 'echo "no"', 'endfunc', + \ ], 'Xfuncexists') + call assert_fails('source Xfuncexists', 'E122:') + call assert_equal(1, exists('*ExistingFunction')) + + call delete('Xfuncexists2') + call delete('Xfuncexists') + delfunc ExistingFunction +endfunc + +func Test_platform_name() + " The system matches at most only one name. + let names = ['amiga', 'beos', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix'] + call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)'))) + + " Is Unix? + call assert_equal(has('beos'), has('beos') && has('unix')) + call assert_equal(has('bsd'), has('bsd') && has('unix')) + call assert_equal(has('hpux'), has('hpux') && has('unix')) + call assert_equal(has('linux'), has('linux') && has('unix')) + call assert_equal(has('mac'), has('mac') && has('unix')) + call assert_equal(has('qnx'), has('qnx') && has('unix')) + call assert_equal(has('sun'), has('sun') && has('unix')) + call assert_equal(has('win32'), has('win32') && !has('unix')) + call assert_equal(has('win32unix'), has('win32unix') && has('unix')) + + if has('unix') && executable('uname') + let uname = system('uname') + call assert_equal(uname =~? 'BeOS', has('beos')) + " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined + call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd')) + call assert_equal(uname =~? 'HP-UX', has('hpux')) + call assert_equal(uname =~? 'Linux', has('linux')) + call assert_equal(uname =~? 'Darwin', has('mac')) + call assert_equal(uname =~? 'QNX', has('qnx')) + call assert_equal(uname =~? 'SunOS', has('sun')) + call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix')) endif +endfunc +func Test_readdir() call mkdir('Xdir') call writefile([], 'Xdir/foo.txt') call writefile([], 'Xdir/bar.txt') @@ -1625,6 +1649,30 @@ func Test_readdir() call delete('Xdir', 'rf') endfunc +func Test_delete_rf() + call mkdir('Xdir') + call writefile([], 'Xdir/foo.txt') + call writefile([], 'Xdir/bar.txt') + call mkdir('Xdir/[a-1]') " issue #696 + call writefile([], 'Xdir/[a-1]/foo.txt') + call writefile([], 'Xdir/[a-1]/bar.txt') + call assert_true(filereadable('Xdir/foo.txt')) + call assert_true('Xdir/[a-1]/foo.txt'->filereadable()) + + call assert_equal(0, delete('Xdir', 'rf')) + call assert_false(filereadable('Xdir/foo.txt')) + call assert_false(filereadable('Xdir/[a-1]/foo.txt')) + + if has('unix') + call mkdir('Xdir/Xdir2', 'p') + silent !chmod 555 Xdir + call assert_equal(-1, delete('Xdir/Xdir2', 'rf')) + call assert_equal(-1, delete('Xdir', 'rf')) + silent !chmod 755 Xdir + call assert_equal(0, delete('Xdir', 'rf')) + endif +endfunc + func Test_call() call assert_equal(3, call('len', [123])) call assert_equal(3, 'len'->call([123])) @@ -1647,6 +1695,49 @@ func Test_eventhandler() call assert_equal(0, eventhandler()) endfunc +func Test_bufadd_bufload() + call assert_equal(0, bufexists('someName')) + let buf = bufadd('someName') + call assert_notequal(0, buf) + call assert_equal(1, bufexists('someName')) + call assert_equal(0, getbufvar(buf, '&buflisted')) + call assert_equal(0, bufloaded(buf)) + call bufload(buf) + call assert_equal(1, bufloaded(buf)) + call assert_equal([''], getbufline(buf, 1, '$')) + + let curbuf = bufnr('') + eval ['some', 'text']->writefile('XotherName') + let buf = 'XotherName'->bufadd() + call assert_notequal(0, buf) + eval 'XotherName'->bufexists()->assert_equal(1) + call assert_equal(0, getbufvar(buf, '&buflisted')) + call assert_equal(0, bufloaded(buf)) + eval buf->bufload() + call assert_equal(1, bufloaded(buf)) + call assert_equal(['some', 'text'], getbufline(buf, 1, '$')) + call assert_equal(curbuf, bufnr('')) + + let buf1 = bufadd('') + let buf2 = bufadd('') + call assert_notequal(0, buf1) + call assert_notequal(0, buf2) + call assert_notequal(buf1, buf2) + call assert_equal(1, bufexists(buf1)) + call assert_equal(1, bufexists(buf2)) + call assert_equal(0, bufloaded(buf1)) + exe 'bwipe ' .. buf1 + call assert_equal(0, bufexists(buf1)) + call assert_equal(1, bufexists(buf2)) + exe 'bwipe ' .. buf2 + call assert_equal(0, bufexists(buf2)) + + bwipe someName + bwipe XotherName + call assert_equal(0, bufexists('someName')) + call delete('XotherName') +endfunc + " Test for the eval() function func Test_eval() call assert_fails("call eval('5 a')", 'E488:') @@ -1664,6 +1755,102 @@ func Test_nr2char() call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"')) endfunc +" Test for getcurpos() and setpos() +func Test_getcurpos_setpos() + new + call setline(1, ['012345678', '012345678']) + normal gg6l + let sp = getcurpos() + normal 0 + call setpos('.', sp) + normal jyl + call assert_equal('6', @") + call assert_equal(-1, setpos('.', v:_null_list)) + call assert_equal(-1, setpos('.', {})) + + let winid = win_getid() + normal G$ + let pos = getcurpos() + wincmd w + call assert_equal(pos, getcurpos(winid)) + + wincmd w + close! + + call assert_equal(getcurpos(), getcurpos(0)) + call assert_equal([0, 0, 0, 0, 0], getcurpos(-1)) + call assert_equal([0, 0, 0, 0, 0], getcurpos(1999)) +endfunc + +func Test_getmousepos() + enew! + call setline(1, "\t\t\t1234") + " call test_setmouse(1, 1) + call nvim_input_mouse('left', 'press', '', 0, 0, 0) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 1, + \ screencol: 1, + \ winid: win_getid(), + \ winrow: 1, + \ wincol: 1, + \ line: 1, + \ column: 1, + \ }, getmousepos()) + " call test_setmouse(1, 25) + call nvim_input_mouse('left', 'press', '', 0, 0, 24) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 1, + \ screencol: 25, + \ winid: win_getid(), + \ winrow: 1, + \ wincol: 25, + \ line: 1, + \ column: 4, + \ }, getmousepos()) + " call test_setmouse(1, 50) + call nvim_input_mouse('left', 'press', '', 0, 0, 49) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 1, + \ screencol: 50, + \ winid: win_getid(), + \ winrow: 1, + \ wincol: 50, + \ line: 1, + \ column: 8, + \ }, getmousepos()) + + " If the mouse is positioned past the last buffer line, "line" and "column" + " should act like it's positioned on the last buffer line. + " call test_setmouse(2, 25) + call nvim_input_mouse('left', 'press', '', 0, 1, 24) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 2, + \ screencol: 25, + \ winid: win_getid(), + \ winrow: 2, + \ wincol: 25, + \ line: 1, + \ column: 4, + \ }, getmousepos()) + " call test_setmouse(2, 50) + call nvim_input_mouse('left', 'press', '', 0, 1, 49) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 2, + \ screencol: 50, + \ winid: win_getid(), + \ winrow: 2, + \ wincol: 50, + \ line: 1, + \ column: 8, + \ }, getmousepos()) + bwipe! +endfunc + func HasDefault(msg = 'msg') return a:msg endfunc diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index 43efd6248e..589899f532 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -19,11 +19,7 @@ func Test_gf_url() call search("^second") call search("URL") call assert_equal("URL://machine.name/tmp/vimtest2b", expand("<cfile>")) - if has("ebcdic") - set isf=@,240-249,/,.,-,_,+,,,$,:,~,\ - else - set isf=@,48-57,/,.,-,_,+,,,$,~,\ - endif + set isf=@,48-57,/,.,-,_,+,,,$,~,\ call search("^third") call search("name") call assert_equal("URL:\\\\machine.name\\vimtest2c", expand("<cfile>")) @@ -76,11 +72,7 @@ endfunc " Test for invoking 'gf' on a ${VAR} variable func Test_gf() - if has("ebcdic") - set isfname=@,240-249,/,.,-,_,+,,,$,:,~,{,} - else - set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,} - endif + set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,} call writefile(["Test for gf command"], "Xtest1") if has("unix") diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index 8edc9c2608..ad561baf4a 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -36,6 +36,36 @@ func Test_global_error() call assert_fails('g/\(/y', 'E476:') endfunc +" Test for printing lines using :g with different search patterns +func Test_global_print() + new + call setline(1, ['foo', 'bar', 'foo', 'foo']) + let @/ = 'foo' + let t = execute("g/")->trim()->split("\n") + call assert_equal(['foo', 'foo', 'foo'], t) + + " Test for Vi compatible patterns + let @/ = 'bar' + let t = execute('g\/')->trim()->split("\n") + call assert_equal(['bar'], t) + + normal gg + s/foo/foo/ + let t = execute('g\&')->trim()->split("\n") + call assert_equal(['foo', 'foo', 'foo'], t) + + let @/ = 'bar' + let t = execute('g?')->trim()->split("\n") + call assert_equal(['bar'], t) + + " Test for the 'Pattern found in every line' message + let v:statusmsg = '' + v/foo\|bar/p + call assert_notequal('', v:statusmsg) + + close! +endfunc + func Test_wrong_delimiter() call assert_fails('g x^bxd', 'E146:') endfunc diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim index 8e59efd22d..b2d943be00 100644 --- a/src/nvim/testdir/test_help.vim +++ b/src/nvim/testdir/test_help.vim @@ -1,4 +1,3 @@ - " Tests for :help func Test_help_restore_snapshot() @@ -13,6 +12,18 @@ endfunc func Test_help_errors() call assert_fails('help doesnotexist', 'E149:') call assert_fails('help!', 'E478:') + if has('multi_lang') + call assert_fails('help help@xy', 'E661:') + endif + + let save_hf = &helpfile + set helpfile=help_missing + help + call assert_equal(1, winnr('$')) + call assert_notequal('help', &buftype) + let &helpfile = save_hf + + call assert_fails('help ' . repeat('a', 1048), 'E149:') new set keywordprg=:help @@ -101,4 +112,13 @@ func Test_helptag_cmd() call delete('Xdir', 'rf') endfunc +func Test_help_long_argument() + try + exe 'help \%' .. repeat('0', 1021) + catch + call assert_match("E149:", v:exception) + endtry +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index a6494c531c..a43889b57e 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -23,6 +23,11 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*bar\*') helpclose + help " + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*quote\*') + helpclose + help "* call assert_equal("help", &filetype) call assert_true(getline('.') =~ '\*quotestar\*') @@ -86,11 +91,40 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*i_^_CTRL-D\*') helpclose + help i^x^y + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*i_CTRL-X_CTRL-Y\*') + helpclose + + exe "help i\<C-\>\<C-G>" + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*i_CTRL-\\_CTRL-G\*') + helpclose + exec "help \<C-V>" call assert_equal("help", &filetype) call assert_true(getline('.') =~ '\*CTRL-V\*') helpclose + help /\| + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*/\\bar\*') + helpclose + + help CTRL-\_CTRL-N + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*CTRL-\\_CTRL-N\*') + helpclose + + help `:pwd`, + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:pwd\*') + helpclose + + help `:ls`. + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:ls\*') + helpclose exec "help! ('textwidth'" call assert_equal("help", &filetype) @@ -122,6 +156,15 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*{address}\*') helpclose + " Use special patterns in the help tag + for h in ['/\w', '/\%^', '/\%(', '/\zs', '/\@<=', '/\_$', '[++opt]', '/\{'] + exec "help! " . h + call assert_equal("help", &filetype) + let pat = '\*' . escape(h, '\$[') . '\*' + call assert_true(getline('.') =~ pat, pat) + helpclose + endfor + exusage call assert_equal("help", &filetype) call assert_true(getline('.') =~ '\*:index\*') diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 6fd9477ce9..efdf44a0d6 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -146,7 +146,7 @@ func Test_highlight_eol_with_cursorline_vertsplit() " 'abcd |abcd ' " ^^^^ ^^^^^^^^^ no highlight " ^ 'Search' highlight - " ^ 'VertSplit' highlight + " ^ 'WinSeparator' highlight let attrs0 = ScreenAttrs(1, 15)[0] call assert_equal(repeat([attrs0[0]], 4), attrs0[0:3]) call assert_equal(repeat([attrs0[0]], 9), attrs0[6:14]) @@ -160,7 +160,7 @@ func Test_highlight_eol_with_cursorline_vertsplit() " 'abcd |abcd ' " ^^^^ underline " ^ 'Search' highlight with underline - " ^ 'VertSplit' highlight + " ^ 'WinSeparator' highlight " ^^^^^^^^^ no highlight " underline @@ -597,6 +597,86 @@ func Test_cursorline_with_visualmode() call delete('Xtest_cursorline_with_visualmode') endfunc +func Test_cursorcolumn_insert_on_tab() + CheckScreendump + + let lines =<< trim END + call setline(1, ['123456789', "a\tb"]) + set cursorcolumn + call cursor(2, 2) + END + call writefile(lines, 'Xcuc_insert_on_tab') + + let buf = RunVimInTerminal('-S Xcuc_insert_on_tab', #{rows: 8}) + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_1', {}) + + call term_sendkeys(buf, 'i') + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {}) + + call term_sendkeys(buf, "\<C-O>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_3', {}) + + call term_sendkeys(buf, 'i') + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {}) + + call StopVimInTerminal(buf) + call delete('Xcuc_insert_on_tab') +endfunc + +func Test_cursorcolumn_callback() + CheckScreendump + CheckFeature timers + + let lines =<< trim END + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set cursorcolumn + call cursor(4, 5) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + END + call writefile(lines, 'Xcuc_timer') + + let buf = RunVimInTerminal('-S Xcuc_timer', #{rows: 8}) + call TermWait(buf, 310) + call VerifyScreenDump(buf, 'Test_cursorcolumn_callback_1', {}) + + call StopVimInTerminal(buf) + call delete('Xcuc_timer') +endfunc + +func Test_colorcolumn() + CheckScreendump + + " check that setting 'colorcolumn' when entering a buffer works + let lines =<< trim END + split + edit X + call setline(1, ["1111111111","22222222222","3333333333"]) + set nomodified + set colorcolumn=3,9 + set number cursorline cursorlineopt=number + wincmd w + buf X + END + call writefile(lines, 'Xtest_colorcolumn') + let buf = RunVimInTerminal('-S Xtest_colorcolumn', {'rows': 10}) + call term_sendkeys(buf, ":\<CR>") + call term_wait(buf) + call VerifyScreenDump(buf, 'Test_colorcolumn_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_colorcolumn') +endfunc + func Test_colorcolumn_bri() CheckScreendump @@ -651,6 +731,23 @@ func Test_1_highlight_Normalgroup_exists() endif endfunc +function Test_no_space_before_xxx() + " Note: we need to create this highlight group in the test because it does not exist in Neovim + execute('hi StatusLineTermNC ctermfg=green') + let l:org_columns = &columns + set columns=17 + let l:hi_StatusLineTermNC = join(split(execute('hi StatusLineTermNC'))) + call assert_match('StatusLineTermNC xxx', l:hi_StatusLineTermNC) + let &columns = l:org_columns +endfunction + +" Test for :highlight command errors +func Test_highlight_cmd_errors() + if has('gui_running') || has('nvim') + call assert_fails('hi ' .. repeat('a', 201) .. ' ctermfg=black', 'E1249:') + endif +endfunc + " Test for using RGB color values in a highlight group func Test_xxlast_highlight_RGB_color() CheckCanRunGui diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index ce75799551..24eaf9e8b1 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -109,7 +109,7 @@ func s:CompleteDone_CompleteFuncNone( findstart, base ) return v:none endfunc -function! s:CompleteDone_CompleteFuncDict( findstart, base ) +func s:CompleteDone_CompleteFuncDict( findstart, base ) if a:findstart return 0 endif @@ -126,7 +126,7 @@ function! s:CompleteDone_CompleteFuncDict( findstart, base ) \ } \ ] \ } -endfunction +endfunc func s:CompleteDone_CheckCompletedItemNone() let s:called_completedone = 1 @@ -341,6 +341,14 @@ func Test_compl_feedkeys() set completeopt& endfunc +func s:ComplInCmdwin_GlobalCompletion(a, l, p) + return 'global' +endfunc + +func s:ComplInCmdwin_LocalCompletion(a, l, p) + return 'local' +endfunc + func Test_compl_in_cmdwin() set wildmenu wildchar=<Tab> com! -nargs=1 -complete=command GetInput let input = <q-args> @@ -376,6 +384,47 @@ func Test_compl_in_cmdwin() call feedkeys("q::GetInput b:test_\<Tab>\<CR>:q\<CR>", 'tx!') call assert_equal('b:test_', input) + + " Argument completion of buffer-local command + func s:ComplInCmdwin_GlobalCompletionList(a, l, p) + return ['global'] + endfunc + + func s:ComplInCmdwin_LocalCompletionList(a, l, p) + return ['local'] + endfunc + + func s:ComplInCmdwin_CheckCompletion(arg) + call assert_equal('local', a:arg) + endfunc + + com! -nargs=1 -complete=custom,<SID>ComplInCmdwin_GlobalCompletion + \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>) + com! -buffer -nargs=1 -complete=custom,<SID>ComplInCmdwin_LocalCompletion + \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>) + call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!') + + com! -nargs=1 -complete=customlist,<SID>ComplInCmdwin_GlobalCompletionList + \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>) + com! -buffer -nargs=1 -complete=customlist,<SID>ComplInCmdwin_LocalCompletionList + \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>) + + call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!') + + func! s:ComplInCmdwin_CheckCompletion(arg) + call assert_equal('global', a:arg) + endfunc + new + call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!') + quit + + delfunc s:ComplInCmdwin_GlobalCompletion + delfunc s:ComplInCmdwin_LocalCompletion + delfunc s:ComplInCmdwin_GlobalCompletionList + delfunc s:ComplInCmdwin_LocalCompletionList + delfunc s:ComplInCmdwin_CheckCompletion + + delcom -buffer TestCommand delcom TestCommand delcom GetInput unlet w:test_winvar @@ -445,6 +494,28 @@ func Test_issue_7021() set completeslash= endfunc +func Test_pum_stopped_by_timer() + CheckScreendump + + let lines =<< trim END + call setline(1, ['hello', 'hullo', 'heeee', '']) + func StartCompl() + call timer_start(100, { -> execute('stopinsert') }) + call feedkeys("Gah\<C-N>") + endfunc + END + + call writefile(lines, 'Xpumscript') + let buf = RunVimInTerminal('-S Xpumscript', #{rows: 12}) + call term_sendkeys(buf, ":call StartCompl()\<CR>") + call TermWait(buf, 200) + call term_sendkeys(buf, "k") + call VerifyScreenDump(buf, 'Test_pum_stopped_by_timer', {}) + + call StopVimInTerminal(buf) + call delete('Xpumscript') +endfunc + func Test_pum_with_folds_two_tabs() CheckScreendump @@ -636,4 +707,23 @@ func Test_z1_complete_no_history() close! endfunc +func FooBarComplete(findstart, base) + if a:findstart + return col('.') - 1 + else + return ["Foo", "Bar", "}"] + endif +endfunc + +func Test_complete_smartindent() + new + setlocal smartindent completefunc=FooBarComplete + + exe "norm! o{\<cr>\<c-x>\<c-u>\<c-p>}\<cr>\<esc>" + let result = getline(1,'$') + call assert_equal(['', '{','}',''], result) + bw! + delfunction! FooBarComplete +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_jumplist.vim b/src/nvim/testdir/test_jumplist.vim index 9cfbbe2029..91ad940e18 100644 --- a/src/nvim/testdir/test_jumplist.vim +++ b/src/nvim/testdir/test_jumplist.vim @@ -64,3 +64,44 @@ func Test_getjumplist() call delete("Xtest") endfunc + +func Test_jumplist_invalid() + new + clearjumps + " put some randome text + put ='a' + let prev = bufnr('%') + setl nomodified bufhidden=wipe + e XXJumpListBuffer + let bnr = bufnr('%') + " 1) empty jumplist + let expected = [[ + \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}], 1] + call assert_equal(expected, getjumplist()) + let jumps = execute(':jumps') + call assert_equal('>', jumps[-1:]) + " now jump back + exe ":norm! \<c-o>" + let expected = [[ + \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}, + \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 0] + call assert_equal(expected, getjumplist()) + let jumps = execute(':jumps') + call assert_match('> 0 2 0 -invalid-', jumps) +endfunc + +" Test for '' mark in an empty buffer + +func Test_empty_buffer() + new + insert +a +b +c +d +. + call assert_equal(1, line("''")) + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_jumps.vim b/src/nvim/testdir/test_jumps.vim deleted file mode 100644 index 5a3717d165..0000000000 --- a/src/nvim/testdir/test_jumps.vim +++ /dev/null @@ -1,11 +0,0 @@ -func Test_empty_buffer() - new - insert -a -b -c -d -. - call assert_equal(1, line("''")) - bwipe! -endfunc diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index 63bb4ae1ef..c1fe47d1c9 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -1,24 +1,24 @@ " Test for lambda and closure -function! Test_lambda_feature() +func Test_lambda_feature() call assert_equal(1, has('lambda')) -endfunction +endfunc -function! Test_lambda_with_filter() +func Test_lambda_with_filter() let s:x = 2 call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x})) -endfunction +endfunc -function! Test_lambda_with_map() +func Test_lambda_with_map() let s:x = 1 call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x})) -endfunction +endfunc -function! Test_lambda_with_sort() +func Test_lambda_with_sort() call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b})) -endfunction +endfunc -function! Test_lambda_with_timer() +func Test_lambda_with_timer() if !has('timers') return endif @@ -54,10 +54,10 @@ function! Test_lambda_with_timer() call assert_true(s:n > m) endfunc -function! Test_lambda_with_partial() +func Test_lambda_with_partial() let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two']) call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three')) -endfunction +endfunc function Test_lambda_fails() call assert_equal(3, {a, b -> a + b}(1, 2)) @@ -70,59 +70,59 @@ func Test_not_lambda() call assert_equal('foo', x['>']) endfunc -function! Test_lambda_capture_by_reference() +func Test_lambda_capture_by_reference() let v = 1 let l:F = {x -> x + v} let v = 2 call assert_equal(12, l:F(10)) -endfunction +endfunc -function! Test_lambda_side_effect() - function! s:update_and_return(arr) +func Test_lambda_side_effect() + func! s:update_and_return(arr) let a:arr[1] = 5 return a:arr - endfunction + endfunc - function! s:foo(arr) + func! s:foo(arr) return {-> s:update_and_return(a:arr)} - endfunction + endfunc let arr = [3,2,1] call assert_equal([3, 5, 1], s:foo(arr)()) -endfunction +endfunc -function! Test_lambda_refer_local_variable_from_other_scope() - function! s:foo(X) +func Test_lambda_refer_local_variable_from_other_scope() + func! s:foo(X) return a:X() " refer l:x in s:bar() - endfunction + endfunc - function! s:bar() + func! s:bar() let x = 123 return s:foo({-> x}) - endfunction + endfunc call assert_equal(123, s:bar()) -endfunction +endfunc -function! Test_lambda_do_not_share_local_variable() - function! s:define_funcs() +func Test_lambda_do_not_share_local_variable() + func! s:define_funcs() let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]} let l:Two = {-> exists("a") ? a : "no"} return [l:One, l:Two] - endfunction + endfunc let l:F = s:define_funcs() call assert_equal('no', l:F[1]()) call assert_equal('abc', l:F[0]()) call assert_equal('no', l:F[1]()) -endfunction +endfunc -function! Test_lambda_closure_counter() - function! s:foo() +func Test_lambda_closure_counter() + func! s:foo() let x = 0 return {-> [execute("let x += 1"), x][-1]} - endfunction + endfunc let l:F = s:foo() call garbagecollect() @@ -130,52 +130,52 @@ function! Test_lambda_closure_counter() call assert_equal(2, l:F()) call assert_equal(3, l:F()) call assert_equal(4, l:F()) -endfunction +endfunc -function! Test_lambda_with_a_var() - function! s:foo() +func Test_lambda_with_a_var() + func! s:foo() let x = 2 return {... -> a:000 + [x]} - endfunction - function! s:bar() + endfunc + func! s:bar() return s:foo()(1) - endfunction + endfunc call assert_equal([1, 2], s:bar()) -endfunction +endfunc -function! Test_lambda_call_lambda_from_lambda() - function! s:foo(x) +func Test_lambda_call_lambda_from_lambda() + func! s:foo(x) let l:F1 = {-> {-> a:x}} return {-> l:F1()} - endfunction + endfunc let l:F = s:foo(1) call assert_equal(1, l:F()()) -endfunction +endfunc -function! Test_lambda_delfunc() - function! s:gen() +func Test_lambda_delfunc() + func! s:gen() let pl = l: let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))} let l:Bar = l:Foo delfunction l:Foo return l:Bar - endfunction + endfunc let l:F = s:gen() call assert_fails(':call l:F()', 'E933:') -endfunction +endfunc -function! Test_lambda_scope() - function! s:NewCounter() +func Test_lambda_scope() + func! s:NewCounter() let c = 0 return {-> [execute('let c += 1'), c][-1]} - endfunction + endfunc - function! s:NewCounter2() + func! s:NewCounter2() return {-> [execute('let c += 100'), c][-1]} - endfunction + endfunc let l:C = s:NewCounter() let l:D = s:NewCounter2() @@ -183,37 +183,37 @@ function! Test_lambda_scope() call assert_equal(1, l:C()) call assert_fails(':call l:D()', 'E121:') call assert_equal(2, l:C()) -endfunction +endfunc -function! Test_lambda_share_scope() - function! s:New() +func Test_lambda_share_scope() + func! s:New() let c = 0 let l:Inc0 = {-> [execute('let c += 1'), c][-1]} let l:Dec0 = {-> [execute('let c -= 1'), c][-1]} return [l:Inc0, l:Dec0] - endfunction + endfunc let [l:Inc, l:Dec] = s:New() call assert_equal(1, l:Inc()) call assert_equal(2, l:Inc()) call assert_equal(1, l:Dec()) -endfunction +endfunc -function! Test_lambda_circular_reference() - function! s:Foo() +func Test_lambda_circular_reference() + func! s:Foo() let d = {} let d.f = {-> d} return d.f - endfunction + endfunc call s:Foo() call garbagecollect() let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile call garbagecollect() -endfunction +endfunc -function! Test_lambda_combination() +func Test_lambda_combination() call assert_equal(2, {x -> {x -> x}}(1)(2)) call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z})) call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0)) @@ -226,17 +226,17 @@ function! Test_lambda_combination() let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})} let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}} call assert_equal(120, Z(Fact)(5)) -endfunction +endfunc -function! Test_closure_counter() - function! s:foo() +func Test_closure_counter() + func! s:foo() let x = 0 - function! s:bar() closure + func! s:bar() closure let x += 1 return x - endfunction + endfunc return function('s:bar') - endfunction + endfunc let l:F = s:foo() call garbagecollect() @@ -244,30 +244,30 @@ function! Test_closure_counter() call assert_equal(2, l:F()) call assert_equal(3, l:F()) call assert_equal(4, l:F()) -endfunction +endfunc -function! Test_closure_unlet() - function! s:foo() +func Test_closure_unlet() + func! s:foo() let x = 1 - function! s:bar() closure + func! s:bar() closure unlet x - endfunction + endfunc call s:bar() return l: - endfunction + endfunc call assert_false(has_key(s:foo(), 'x')) call garbagecollect() -endfunction +endfunc -function! LambdaFoo() +func LambdaFoo() let x = 0 - function! LambdaBar() closure + func! LambdaBar() closure let x += 1 return x - endfunction + endfunc return function('LambdaBar') -endfunction +endfunc func Test_closure_refcount() let g:Count = LambdaFoo() @@ -303,3 +303,8 @@ func Test_lambda_with_index() let Extract = {-> function(List, ['foobar'])()[0]} call assert_equal('foobar', Extract()) endfunc + +func Test_lambda_error() + " This was causing a crash + call assert_fails('ec{@{->{d->()()', 'E15') +endfunc diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim index 751be8eff5..b239c9c6b5 100644 --- a/src/nvim/testdir/test_listchars.vim +++ b/src/nvim/testdir/test_listchars.vim @@ -1,6 +1,8 @@ " Tests for 'listchars' display with 'list' and :list +source check.vim source view_util.vim +source screendump.vim func Test_listchars() enew! @@ -25,7 +27,7 @@ func Test_listchars() redraw! for i in range(1, 5) call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + call assert_equal([expected[i - 1]], ScreenLines(i, '$'->virtcol())) endfor set listchars-=trail:< @@ -112,7 +114,7 @@ func Test_listchars() " Test lead and trail normal ggdG - set listchars=eol:$ + set listchars=eol:$ " Accommodate Nvim default set listchars+=lead:>,trail:<,space:x set list @@ -142,7 +144,7 @@ func Test_listchars() " Test multispace normal ggdG - set listchars=eol:$ + set listchars=eol:$ " Accommodate Nvim default set listchars+=multispace:yYzZ set list @@ -305,7 +307,7 @@ func Test_listchars_invalid() enew! set ff=unix - set listchars=eol:$ + set listchars=eol:$ " Accommodate Nvim default set list set ambiwidth=double @@ -331,7 +333,7 @@ func Test_listchars_invalid() call assert_fails('set listchars=space:xx', 'E474:') call assert_fails('set listchars=tab:xxxx', 'E474:') - " Has non-single width character + " Has double-width character call assert_fails('set listchars=space:·', 'E474:') call assert_fails('set listchars=tab:·x', 'E474:') call assert_fails('set listchars=tab:x·', 'E474:') @@ -339,6 +341,20 @@ func Test_listchars_invalid() call assert_fails('set listchars=multispace:·', 'E474:') call assert_fails('set listchars=multispace:xxx·', 'E474:') + " Has control character + call assert_fails("set listchars=space:\x01", 'E474:') + call assert_fails("set listchars=tab:\x01x", 'E474:') + call assert_fails("set listchars=tab:x\x01", 'E474:') + call assert_fails("set listchars=tab:xx\x01", 'E474:') + call assert_fails("set listchars=multispace:\x01", 'E474:') + call assert_fails("set listchars=multispace:xxx\x01", 'E474:') + call assert_fails('set listchars=space:\\x01', 'E474:') + call assert_fails('set listchars=tab:\\x01x', 'E474:') + call assert_fails('set listchars=tab:x\\x01', 'E474:') + call assert_fails('set listchars=tab:xx\\x01', 'E474:') + call assert_fails('set listchars=multispace:\\x01', 'E474:') + call assert_fails('set listchars=multispace:xxx\\x01', 'E474:') + enew! set ambiwidth& listchars& ff& endfunction @@ -369,3 +385,174 @@ func Test_listchars_composing() enew! set listchars& ff& endfunction + +" Check for the value of the 'listchars' option +func s:CheckListCharsValue(expected) + call assert_equal(a:expected, &listchars) + call assert_equal(a:expected, getwinvar(0, '&listchars')) +endfunc + +" Test for using a window local value for 'listchars' +func Test_listchars_window_local() + %bw! + set list listchars& + let l:default_listchars = &listchars " Accommodate Nvim default + new + " set a local value for 'listchars' + setlocal listchars=tab:+-,eol:# + call s:CheckListCharsValue('tab:+-,eol:#') + " When local value is reset, global value should be used + setlocal listchars= + call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + " Use 'setlocal <' to copy global value + setlocal listchars=space:.,extends:> + setlocal listchars< + call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + " Use 'set <' to copy global value + setlocal listchars=space:.,extends:> + set listchars< + call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + " Changing global setting should not change the local setting + setlocal listchars=space:.,extends:> + setglobal listchars=tab:+-,eol:# + call s:CheckListCharsValue('space:.,extends:>') + " when split opening a new window, local value should be copied + split + call s:CheckListCharsValue('space:.,extends:>') + " clearing local value in one window should not change the other window + set listchars& + call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + close + call s:CheckListCharsValue('space:.,extends:>') + + " use different values for 'listchars' items in two different windows + call setline(1, ["\t one two "]) + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + split + setlocal listchars=tab:[.],lead:#,space:_,trail:.,eol:& + split + set listchars=tab:+-+,lead:^,space:>,trail:<,eol:% + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + " changing the global setting should not change the local value + setglobal listchars=tab:[.],lead:#,space:_,trail:.,eol:& + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + set listchars< + call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$'))) + + " Using setglobal in a window with local setting should not affect the + " window. But should impact other windows using the global setting. + enew! | only + call setline(1, ["\t one two "]) + set listchars=tab:[.],lead:#,space:_,trail:.,eol:& + split + setlocal listchars=tab:+-+,lead:^,space:>,trail:<,eol:% + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + setglobal listchars=tab:{.},lead:-,space:=,trail:#,eol:$ + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$'))) + + " Setting the global setting to the default value should not impact a window + " using a local setting. + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + setglobal listchars=eol:$ " Accommodate Nvim default + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$'))) + + " Setting the local setting to the default value should not impact a window + " using a global setting. + set listchars=tab:{.},lead:-,space:=,trail:#,eol:$ + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + setlocal listchars=eol:$ " Accommodate Nvim default + call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$'))) + + " Using set in a window with a local setting should change it to use the + " global setting and also impact other windows using the global setting. + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + set listchars=tab:+-+,lead:^,space:>,trail:<,eol:% + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + + " Setting invalid value for a local setting should not impact the local and + " global settings. + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + let cmd = 'setlocal listchars=tab:{.},lead:-,space:=,trail:#,eol:$,x' + call assert_fails(cmd, 'E474:') + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + + " Setting invalid value for a global setting should not impact the local and + " global settings. + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + let cmd = 'setglobal listchars=tab:{.},lead:-,space:=,trail:#,eol:$,x' + call assert_fails(cmd, 'E474:') + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + + " Closing window with local lcs-multispace should not cause a memory leak. + setlocal listchars=multispace:---+ + split + call s:CheckListCharsValue('multispace:---+') + close + + %bw! + set list& listchars& +endfunc + +func Test_listchars_foldcolumn() + CheckScreendump + + let lines =<< trim END + call setline(1, ['aaa', '', 'a', 'aaaaaa']) + vsplit + vsplit + windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:< + END + call writefile(lines, 'XTest_listchars') + + let buf = RunVimInTerminal('-S XTest_listchars', {'rows': 10, 'cols': 60}) + + call term_sendkeys(buf, "13\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_01', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_02', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_03', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_04', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_05', {}) + call term_sendkeys(buf, "\<C-W>h") + call term_sendkeys(buf, ":set nowrap foldcolumn=4\<CR>") + call term_sendkeys(buf, "15\<C-W><") + call VerifyScreenDump(buf, 'Test_listchars_06', {}) + call term_sendkeys(buf, "4\<C-W><") + call VerifyScreenDump(buf, 'Test_listchars_07', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_listchars') +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index f6c404d390..aa66d86af1 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -516,22 +516,22 @@ func Test_dict_lock_operator() endfunc " No remove() of write-protected scope-level variable -func! Tfunc(this_is_a_long_parameter_name) +func Tfunc1(this_is_a_long_parameter_name) call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742') -endfun +endfunc func Test_dict_scope_var_remove() - call Tfunc('testval') + call Tfunc1('testval') endfunc " No extend() of write-protected scope-level variable func Test_dict_scope_var_extend() call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742') endfunc -func! Tfunc(this_is_a_long_parameter_name) +func Tfunc2(this_is_a_long_parameter_name) call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742') endfunc func Test_dict_scope_var_extend_overwrite() - call Tfunc('testval') + call Tfunc2('testval') endfunc " No :unlet of variable in locked scope @@ -620,6 +620,49 @@ func Test_reverse_sort_uniq() call assert_fails('call reverse("")', 'E899:') endfunc +" reduce a list or a blob +func Test_reduce() + call assert_equal(1, reduce([], { acc, val -> acc + val }, 1)) + call assert_equal(10, reduce([1, 3, 5], { acc, val -> acc + val }, 1)) + call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], { acc, val -> 2 * acc + val }, 1)) + call assert_equal('a x y z', ['x', 'y', 'z']->reduce({ acc, val -> acc .. ' ' .. val}, 'a')) + call assert_equal(#{ x: 1, y: 1, z: 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {})) + call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0])) + + let l = ['x', 'y', 'z'] + call assert_equal(42, reduce(l, function('get'), #{ x: #{ y: #{ z: 42 } } })) + call assert_equal(['x', 'y', 'z'], l) + + call assert_equal(1, reduce([1], { acc, val -> acc + val })) + call assert_equal('x y z', reduce(['x', 'y', 'z'], { acc, val -> acc .. ' ' .. val })) + call assert_equal(120, range(1, 5)->reduce({ acc, val -> acc * val })) + call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value') + + call assert_equal(1, reduce(0z, { acc, val -> acc + val }, 1)) + call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, { acc, val -> acc + val }, 1)) + call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce({ acc, val -> 2 * acc + val }, 1)) + + call assert_equal(0xff, reduce(0zff, { acc, val -> acc + val })) + call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, { acc, val -> 2 * acc + val })) + call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value') + + call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:') + call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:') + call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:') + + let g:lut = [1, 2, 3, 4] + func EvilRemove() + call remove(g:lut, 1) + return 1 + endfunc + call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:') + unlet g:lut + delfunc EvilRemove + + call assert_equal(42, reduce(v:_null_list, function('add'), 42)) + call assert_equal(42, reduce(v:_null_blob, function('add'), 42)) +endfunc + " splitting a string to a List func Test_str_split() call assert_equal(['aa', 'bb'], split(' aa bb ')) diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim index e0518de3c2..2fda12d8b4 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -43,6 +43,7 @@ endfunc func Test_linebreak_with_list() throw 'skipped: Nvim does not support enc=latin1' + set listchars= call s:test_windows('setl ts=4 sbr=+ list listchars=') call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ") let lines = s:screen_lines([1, 4], winwidth(0)) @@ -54,6 +55,7 @@ func Test_linebreak_with_list() \ ] call s:compare_lines(expect, lines) call s:close_windows() + set listchars&vim endfunc func Test_linebreak_with_nolist() diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index c38e0c5f3c..1f100d6244 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -69,6 +69,16 @@ func Test_nolinebreak_with_list() call s:close_windows() endfunc +" this was causing a crash +func Test_linebreak_with_list_and_tabs() + set linebreak list listchars=tab:⇤\ ⇥ tabstop=100 + new + call setline(1, "\t\t\ttext") + redraw + bwipe! + set nolinebreak nolist listchars&vim tabstop=8 +endfunc + func Test_linebreak_with_nolist() call s:test_windows('setl nolist') call setline(1, "\t*mask = nil;") diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index f88e8cf843..a8dd0ca286 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -1,6 +1,8 @@ " Tests for mappings and abbreviations source shared.vim +source check.vim +source screendump.vim func Test_abbreviation() " abbreviation with 0x80 should work @@ -451,6 +453,82 @@ func Test_expr_map_gets_cursor() nunmap ! endfunc +func Test_expr_map_restore_cursor() + CheckScreendump + + let lines =<< trim END + call setline(1, ['one', 'two', 'three']) + 2 + set ls=2 + hi! link StatusLine ErrorMsg + noremap <expr> <C-B> Func() + func Func() + let g:on = !get(g:, 'on', 0) + redraws + return '' + endfunc + func Status() + return get(g:, 'on', 0) ? '[on]' : '' + endfunc + set stl=%{Status()} + END + call writefile(lines, 'XtestExprMap') + let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10}) + call term_sendkeys(buf, "\<C-B>") + call VerifyScreenDump(buf, 'Test_map_expr_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestExprMap') +endfunc + +func Test_map_listing() + CheckScreendump + + let lines =<< trim END + nmap a b + END + call writefile(lines, 'XtestMapList') + let buf = RunVimInTerminal('-S XtestMapList', #{rows: 6}) + call term_sendkeys(buf, ": nmap a\<CR>") + call VerifyScreenDump(buf, 'Test_map_list_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestMapList') +endfunc + +func Test_expr_map_error() + CheckScreendump + + let lines =<< trim END + func Func() + throw 'test' + return '' + endfunc + + nnoremap <expr> <F2> Func() + cnoremap <expr> <F2> Func() + + call test_override('ui_delay', 10) + END + call writefile(lines, 'XtestExprMap') + let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10}) + call term_sendkeys(buf, "\<F2>") + call TermWait(buf) + call term_sendkeys(buf, "\<CR>") + call VerifyScreenDump(buf, 'Test_map_expr_2', {}) + + call term_sendkeys(buf, ":abc\<F2>") + call VerifyScreenDump(buf, 'Test_map_expr_3', {}) + call term_sendkeys(buf, "\<Esc>0") + call VerifyScreenDump(buf, 'Test_map_expr_4', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestExprMap') +endfunc + " Test for mapping errors func Test_map_error() call assert_fails('unmap', 'E474:') @@ -568,4 +646,68 @@ func Test_abbreviate_multi_byte() bwipe! endfunc +" Test for <Plug> always being mapped, even when used with "noremap". +func Test_plug_remap() + let g:foo = 0 + nnoremap <Plug>(Increase_x) <Cmd>let g:foo += 1<CR> + nmap <F2> <Plug>(Increase_x) + nnoremap <F3> <Plug>(Increase_x) + call feedkeys("\<F2>", 'xt') + call assert_equal(1, g:foo) + call feedkeys("\<F3>", 'xt') + call assert_equal(2, g:foo) + nnoremap x <Nop> + nmap <F4> x<Plug>(Increase_x)x + nnoremap <F5> x<Plug>(Increase_x)x + call setline(1, 'Some text') + normal! gg$ + call feedkeys("\<F4>", 'xt') + call assert_equal(3, g:foo) + call assert_equal('Some text', getline(1)) + call feedkeys("\<F5>", 'xt') + call assert_equal(4, g:foo) + call assert_equal('Some te', getline(1)) + nunmap <Plug>(Increase_x) + nunmap <F2> + nunmap <F3> + nunmap <F4> + nunmap <F5> + unlet g:foo + %bw! +endfunc + +" Test for mapping <LeftDrag> in Insert mode +func Test_mouse_drag_insert_map() + CheckFunction test_setmouse + set mouse=a + func ClickExpr() + call test_setmouse(1, 1) + return "\<LeftMouse>" + endfunc + func DragExpr() + call test_setmouse(1, 2) + return "\<LeftDrag>" + endfunc + inoremap <expr> <F2> ClickExpr() + imap <expr> <F3> DragExpr() + + inoremap <LeftDrag> <LeftDrag><Cmd>let g:dragged = 1<CR> + exe "normal i\<F2>\<F3>" + call assert_equal(1, g:dragged) + call assert_equal('v', mode()) + exe "normal! \<C-\>\<C-N>" + unlet g:dragged + + inoremap <LeftDrag> <LeftDrag><C-\><C-N> + exe "normal i\<F2>\<F3>" + call assert_equal('n', mode()) + + iunmap <LeftDrag> + iunmap <F2> + iunmap <F3> + delfunc ClickExpr + delfunc DragExpr + set mouse& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 2fd82a4b6d..00ee8f6d6a 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -1,6 +1,6 @@ " Test that a deleted mark is restored after delete-undo-redo-undo. -function! Test_Restore_DelMark() +func Test_Restore_DelMark() enew! call append(0, [" textline A", " textline B", " textline C"]) normal! 2gg @@ -11,10 +11,10 @@ function! Test_Restore_DelMark() call assert_equal(2, pos[1]) call assert_equal(1, pos[2]) enew! -endfunction +endfunc " Test that CTRL-A and CTRL-X updates last changed mark '[, ']. -function! Test_Incr_Marks() +func Test_Incr_Marks() enew! call append(0, ["123 123 123", "123 123 123", "123 123 123"]) normal! gg @@ -23,7 +23,17 @@ function! Test_Incr_Marks() call assert_equal("123 XXXXXXX", getline(2)) call assert_equal("XXX 123 123", getline(3)) enew! -endfunction +endfunc + +func Test_previous_jump_mark() + new + call setline(1, ['']->repeat(6)) + normal Ggg + call assert_equal(6, getpos("''")[1]) + normal jjjjj + call assert_equal(6, getpos("''")[1]) + bwipe! +endfunc func Test_setpos() new Xone @@ -207,6 +217,21 @@ func Test_mark_error() call assert_fails('mark _', 'E191:') endfunc +" Test for :lockmarks when pasting content +func Test_lockmarks_with_put() + new + call append(0, repeat(['sky is blue'], 4)) + normal gg + 1,2yank r + put r + normal G + lockmarks put r + call assert_equal(2, line("'[")) + call assert_equal(3, line("']")) + + bwipe! +endfunc + " Test for the getmarklist() function func Test_getmarklist() new @@ -227,7 +252,9 @@ func Test_getmarklist() call cursor(2, 2) normal mr call assert_equal({'mark' : "'r", 'pos' : [bufnr(), 2, 2, 0]}, - \ getmarklist(bufnr())[0]) - call assert_equal([], getmarklist({})) + \ bufnr()->getmarklist()[0]) + call assert_equal([], {}->getmarklist()) close! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim index 2cbaf5cb76..29a2c30b0d 100644 --- a/src/nvim/testdir/test_matchadd_conceal.vim +++ b/src/nvim/testdir/test_matchadd_conceal.vim @@ -7,7 +7,7 @@ source shared.vim source term_util.vim source view_util.vim -function! Test_simple_matchadd() +func Test_simple_matchadd() new 1put='# This is a Test' @@ -333,7 +333,7 @@ func Test_matchadd_and_syn_conceal() call assert_notequal(screenattr(1, 10) , screenattr(1, 11)) call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) call assert_equal(screenattr(1, 11) , screenattr(1, 32)) -endfunction +endfunc func Test_cursor_column_in_concealed_line_after_window_scroll() CheckRunVimInTerminal diff --git a/src/nvim/testdir/test_matchadd_conceal_utf8.vim b/src/nvim/testdir/test_matchadd_conceal_utf8.vim index 34c8c49dd5..1d0c740734 100644 --- a/src/nvim/testdir/test_matchadd_conceal_utf8.vim +++ b/src/nvim/testdir/test_matchadd_conceal_utf8.vim @@ -3,19 +3,19 @@ if !has('conceal') finish endif -function! s:screenline(lnum) abort +func s:screenline(lnum) abort let line = [] for c in range(1, winwidth(0)) - call add(line, nr2char(screenchar(a:lnum, c))) + call add(line, nr2char(a:lnum->screenchar(c))) endfor return s:trim(join(line, '')) -endfunction +endfunc -function! s:trim(str) abort +func s:trim(str) abort return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$') -endfunction +endfunc -function! Test_match_using_multibyte_conceal_char() +func Test_match_using_multibyte_conceal_char() new setlocal concealcursor=n conceallevel=1 diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim new file mode 100644 index 0000000000..abcc9b40c1 --- /dev/null +++ b/src/nvim/testdir/test_matchfuzzy.vim @@ -0,0 +1,248 @@ +" Tests for fuzzy matching + +source shared.vim +source check.vim + +" Test for matchfuzzy() +func Test_matchfuzzy() + call assert_fails('call matchfuzzy(10, "abc")', 'E686:') + " Needs v8.2.1183; match the final error that's thrown for now + " call assert_fails('call matchfuzzy(["abc"], [])', 'E730:') + call assert_fails('call matchfuzzy(["abc"], [])', 'E475:') + call assert_fails("let x = matchfuzzy(v:_null_list, 'foo')", 'E686:') + call assert_fails('call matchfuzzy(["abc"], v:_null_string)', 'E475:') + call assert_equal([], matchfuzzy([], 'abc')) + call assert_equal([], matchfuzzy(['abc'], '')) + call assert_equal(['abc'], matchfuzzy(['abc', 10], 'ac')) + call assert_equal([], matchfuzzy([10, 20], 'ac')) + call assert_equal(['abc'], matchfuzzy(['abc'], 'abc')) + call assert_equal(['crayon', 'camera'], matchfuzzy(['camera', 'crayon'], 'cra')) + call assert_equal(['aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa', 'aba'], matchfuzzy(['aba', 'aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa'], 'aa')) + call assert_equal(['one'], matchfuzzy(['one', 'two'], 'one')) + call assert_equal(['oneTwo', 'onetwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo')) + call assert_equal(['onetwo', 'one_two'], matchfuzzy(['onetwo', 'one_two'], 'oneTwo')) + call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa')) + call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len()) + call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257))) + " matches with same score should not be reordered + let l = ['abc1', 'abc2', 'abc3'] + call assert_equal(l, l->matchfuzzy('abc')) + + " Tests for match preferences + " preference for camel case match + call assert_equal(['oneTwo', 'onetwo'], ['onetwo', 'oneTwo']->matchfuzzy('onetwo')) + " preference for match after a separator (_ or space) + call assert_equal(['onetwo', 'one_two', 'one two'], ['onetwo', 'one_two', 'one two']->matchfuzzy('onetwo')) + " preference for leading letter match + call assert_equal(['onetwo', 'xonetwo'], ['xonetwo', 'onetwo']->matchfuzzy('onetwo')) + " preference for sequential match + call assert_equal(['onetwo', 'oanbectdweo'], ['oanbectdweo', 'onetwo']->matchfuzzy('onetwo')) + " non-matching leading letter(s) penalty + call assert_equal(['xonetwo', 'xxonetwo'], ['xxonetwo', 'xonetwo']->matchfuzzy('onetwo')) + " total non-matching letter(s) penalty + call assert_equal(['one', 'onex', 'onexx'], ['onexx', 'one', 'onex']->matchfuzzy('one')) + " prefer complete matches over separator matches + call assert_equal(['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c'], ['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c']->matchfuzzy('vimrc')) + " gap penalty + call assert_equal(['xxayybxxxx', 'xxayyybxxx', 'xxayyyybxx'], ['xxayyyybxx', 'xxayyybxxx', 'xxayybxxxx']->matchfuzzy('ab')) + " path separator vs word separator + call assert_equal(['color/setup.vim', 'color\\setup.vim', 'color setup.vim', 'color_setup.vim', 'colorsetup.vim'], matchfuzzy(['colorsetup.vim', 'color setup.vim', 'color/setup.vim', 'color_setup.vim', 'color\\setup.vim'], 'setup.vim')) + + " match multiple words (separated by space) + call assert_equal(['foo bar baz'], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('baz foo')) + call assert_equal([], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('one two')) + call assert_equal([], ['foo bar']->matchfuzzy(" \t ")) + + " test for matching a sequence of words + call assert_equal(['bar foo'], ['foo bar', 'bar foo', 'foobar', 'barfoo']->matchfuzzy('bar foo', {'matchseq' : 1})) + call assert_equal([#{text: 'two one'}], [#{text: 'one two'}, #{text: 'two one'}]->matchfuzzy('two one', #{key: 'text', matchseq: v:true})) + + %bw! + eval ['somebuf', 'anotherone', 'needle', 'yetanotherone']->map({_, v -> bufadd(v) + bufload(v)}) + let l = getbufinfo()->map({_, v -> v.name})->matchfuzzy('ndl') + call assert_equal(1, len(l)) + call assert_match('needle', l[0]) + + " Test for fuzzy matching dicts + let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}] + call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'text_cb' : {v -> v.val}})) + call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'key' : 'val'})) + call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> v.val}})) + call assert_equal([], matchfuzzy(l, 'day', {'key' : 'val'})) + call assert_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E715:') + call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> []}})) + call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> 1}})) + call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') + call assert_equal([], matchfuzzy(l, 'cam')) + " Nvim's callback implementation is different, so E6000 is expected instead, + " but we need v8.2.1183 to assert it + " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:') + " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E475:') + " call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:') + call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:') + call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:') + " Nvim doesn't have null functions + " call assert_fails("let x = matchfuzzy(l, 'foo', {'text_cb' : test_null_function()})", 'E475:') + " matches with same score should not be reordered + let l = [#{text: 'abc', id: 1}, #{text: 'abc', id: 2}, #{text: 'abc', id: 3}] + call assert_equal(l, l->matchfuzzy('abc', #{key: 'text'})) + + let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}] + call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 'name'})", 'E730:') + + " Test in latin1 encoding + let save_enc = &encoding + " Nvim supports utf-8 encoding only + " set encoding=latin1 + call assert_equal(['abc'], matchfuzzy(['abc'], 'abc')) + let &encoding = save_enc +endfunc + +" Test for the matchfuzzypos() function +func Test_matchfuzzypos() + call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl')) + call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl')) + call assert_equal([['hello', 'hello world hello world'], + \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]], + \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello')) + call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa')) + call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) + call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) + call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a ')) + call assert_equal([[], [], []], matchfuzzypos(['a b'], ' ')) + call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab')) + let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256)) + call assert_equal(range(256), x[1][0]) + call assert_equal([[], [], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257))) + call assert_equal([[], [], []], matchfuzzypos([], 'abc')) + + " match in a long string + call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]], + \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc')) + + " preference for camel case match + call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc')) + " preference for match after a separator (_ or space) + call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab')) + " preference for leading letter match + call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab')) + " preference for sequential match + call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one')) + " best recursive match + call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one')) + + " match multiple words (separated by space) + call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo')) + call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two')) + call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t ")) + call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace')) + + let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}] + call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], + \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}})) + call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], + \ matchfuzzypos(l, 'cam', {'key' : 'val'})) + call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}})) + call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'})) + call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E715:') + call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}})) + call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}})) + call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') + call assert_equal([[], [], []], matchfuzzypos(l, 'cam')) + " Nvim's callback implementation is different, so E6000 is expected instead, + " but we need v8.2.1183 to assert it + " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:') + " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E475:') + " call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:') + call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:') + call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:') + " Nvim doesn't have null functions + " call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:') + + let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}] + call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:') +endfunc + +" Test for matchfuzzy() with multibyte characters +func Test_matchfuzzy_mbyte() + CheckFeature multi_lang + call assert_equal(['ンヹㄇヺヴ'], matchfuzzy(['ンヹㄇヺヴ'], 'ヹヺ')) + " reverse the order of characters + call assert_equal([], matchfuzzy(['ンヹㄇヺヴ'], 'ヺヹ')) + call assert_equal(['αβΩxxx', 'xαxβxΩx'], + \ matchfuzzy(['αβΩxxx', 'xαxβxΩx'], 'αβΩ')) + call assert_equal(['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'Ï€bÏ€'], + \ matchfuzzy(['Ï€bÏ€', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ')) + + " match multiple words (separated by space) + call assert_equal(['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€'], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzy('ë¼ì§€ 마리ì˜')) + call assert_equal([], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzy('파란 하늘')) + + " preference for camel case match + call assert_equal(['oneÄ„wo', 'oneÄ…wo'], + \ ['oneÄ…wo', 'oneÄ„wo']->matchfuzzy('oneÄ…wo')) + " preference for complete match then match after separator (_ or space) + call assert_equal(['â… â…¡abㄟㄠ'] + sort(['â… â…¡a_bㄟㄠ', 'â… â…¡a bㄟㄠ']), + \ ['â… â…¡abㄟㄠ', 'â… â…¡a bㄟㄠ', 'â… â…¡a_bㄟㄠ']->matchfuzzy('â… â…¡abㄟㄠ')) + " preference for match after a separator (_ or space) + call assert_equal(['ã„“ã„”abㄟㄠ', 'ã„“ã„”a_bㄟㄠ', 'ã„“ã„”a bㄟㄠ'], + \ ['ã„“ã„”a_bㄟㄠ', 'ã„“ã„”a bㄟㄠ', 'ã„“ã„”abㄟㄠ']->matchfuzzy('ã„“ã„”abㄟㄠ')) + " preference for leading letter match + call assert_equal(['Å—Åţũŵż', 'xÅ—Åţũŵż'], + \ ['xÅ—Åţũŵż', 'Å—Åţũŵż']->matchfuzzy('Å—Åţũŵż')) + " preference for sequential match + call assert_equal(['ㄞㄡㄤffï¬ï¬‚', 'ㄞaã„¡bㄤcffdï¬efl'], + \ ['ㄞaã„¡bㄤcffdï¬efl', 'ㄞㄡㄤffï¬ï¬‚']->matchfuzzy('ㄞㄡㄤffï¬ï¬‚')) + " non-matching leading letter(s) penalty + call assert_equal(['xㄞㄡㄤffï¬ï¬‚', 'xxㄞㄡㄤffï¬ï¬‚'], + \ ['xxㄞㄡㄤffï¬ï¬‚', 'xㄞㄡㄤffï¬ï¬‚']->matchfuzzy('ㄞㄡㄤffï¬ï¬‚')) + " total non-matching letter(s) penalty + call assert_equal(['Å—ÅÅ£', 'Å—ÅÅ£x', 'Å—ÅÅ£xx'], + \ ['Å—ÅÅ£xx', 'Å—ÅÅ£', 'Å—ÅÅ£x']->matchfuzzy('Å—ÅÅ£')) +endfunc + +" Test for matchfuzzypos() with multibyte characters +func Test_matchfuzzypos_mbyte() + CheckFeature multi_lang + call assert_equal([['ã“ã‚“ã«ã¡ã¯ä¸–界'], [[0, 1, 2, 3, 4]], [273]], + \ matchfuzzypos(['ã“ã‚“ã«ã¡ã¯ä¸–界'], 'ã“ã‚“ã«ã¡ã¯')) + call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ')) + " reverse the order of characters + call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ')) + call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]], + \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ')) + call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'Ï€bÏ€'], + \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]], + \ matchfuzzypos(['Ï€bÏ€', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ')) + call assert_equal([['ααααααα'], [[0, 1, 2]], [191]], + \ matchfuzzypos(['ααααααα'], 'ααα')) + + call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'Å—ÅÅ£'], 'ffï¬ï¬‚')) + let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256)) + call assert_equal(range(256), x[1][0]) + call assert_equal([[], [], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257))) + + " match multiple words (separated by space) + call assert_equal([['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€'], [[9, 10, 2, 3, 4]], [328]], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzypos('ë¼ì§€ 마리ì˜')) + call assert_equal([[], [], []], ['세 ë§ˆë¦¬ì˜ ìž‘ì€ ë¼ì§€', '마리ì˜', 'ë§ˆë¦¬ì˜ ìž‘ì€', 'ìž‘ì€ ë¼ì§€']->matchfuzzypos('파란 하늘')) + + " match in a long string + call assert_equal([[repeat('ã¶', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]], + \ matchfuzzypos([repeat('ã¶', 300) .. 'ẼẼẼ'], 'ẼẼẼ')) + " preference for camel case match + call assert_equal([['xѳѵÒxxѳѴÒ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵÒxxѳѴÒ'], 'ѳѵÒ')) + " preference for match after a separator (_ or space) + call assert_equal([['xã¡ã x_ã¡ã '], [[5, 6]], [145]], matchfuzzypos(['xã¡ã x_ã¡ã '], 'ã¡ã ')) + " preference for leading letter match + call assert_equal([['ѳѵÒxѳѵÒ'], [[0, 1]], [150]], matchfuzzypos(['ѳѵÒxѳѵÒ'], 'ѳѵ')) + " preference for sequential match + call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ')) + " best recursive match + call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд')) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 08586dffe1..82c4cc128b 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -2,6 +2,9 @@ source check.vim source shared.vim +source term_util.vim +source view_util.vim +source screendump.vim func Test_messages() let oldmore = &more @@ -40,7 +43,7 @@ endfunc " indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message " output could then be disturbed when 'cmdheight' was greater than one. " This test ensures that the bugfix for this issue remains in place. -function! Test_stopinsert_does_not_break_message_output() +func Test_stopinsert_does_not_break_message_output() set cmdheight=2 redraw! @@ -55,7 +58,7 @@ function! Test_stopinsert_does_not_break_message_output() redraw! set cmdheight& -endfunction +endfunc func Test_message_completion() call feedkeys(":message \<C-A>\<C-B>\"\<CR>", 'tx') @@ -87,7 +90,7 @@ func Test_echoerr() if has('float') call assert_equal("\n1.23 IgNoRe", execute(':echoerr 1.23 "IgNoRe"')) endif - call test_ignore_error('<lambda>') + eval '<lambda>'->test_ignore_error() call assert_match("function('<lambda>\\d*')", execute(':echoerr {-> 1234}')) call test_ignore_error('RESET') endfunc @@ -108,3 +111,61 @@ func Test_echospace() set ruler& showcmd& endfunc + +func Test_quit_long_message() + CheckScreendump + + let content =<< trim END + echom range(9999)->join("\x01") + END + call writefile(content, 'Xtest_quit_message') + let buf = RunVimInTerminal('-S Xtest_quit_message', #{rows: 6}) + call term_sendkeys(buf, "q") + call VerifyScreenDump(buf, 'Test_quit_long_message', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_quit_message') +endfunc + +" this was missing a terminating NUL +func Test_echo_string_partial() + function CountSpaces() + endfunction + call assert_equal("function('CountSpaces', [{'ccccccccccc': ['ab', 'cd'], 'aaaaaaaaaaa': v:false, 'bbbbbbbbbbbb': ''}])", string(function('CountSpaces', [#{aaaaaaaaaaa: v:false, bbbbbbbbbbbb: '', ccccccccccc: ['ab', 'cd']}]))) +endfunc + +" Message output was previously overwritten by the fileinfo display, shown +" when switching buffers. If a buffer is switched to, then a message if +" echoed, we should show the message, rather than overwriting it with +" fileinfo. +func Test_fileinfo_after_echo() + CheckScreendump + + let content =<< trim END + file a.txt + + hide edit b.txt + call setline(1, "hi") + setlocal modified + + hide buffer a.txt + + autocmd CursorHold * buf b.txt | w | echo "'b' written" + END + + call writefile(content, 'Xtest_fileinfo_after_echo') + let buf = RunVimInTerminal('-S Xtest_fileinfo_after_echo', #{rows: 6}) + call term_sendkeys(buf, ":set updatetime=50\<CR>") + call term_sendkeys(buf, "0$") + call VerifyScreenDump(buf, 'Test_fileinfo_after_echo', {}) + + call term_sendkeys(buf, ":q\<CR>") + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_fileinfo_after_echo') + call delete('b.txt') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index c96c6a9678..5dbe2cd366 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -156,8 +156,7 @@ func Test_mksession_zero_winheight() wincmd _ mksession! Xtest_mks_zero set winminheight& - " let text = readfile('Xtest_mks_zero')->join() - let text = join(readfile('Xtest_mks_zero')) + let text = readfile('Xtest_mks_zero')->join() call delete('Xtest_mks_zero') close " check there is no divide by zero @@ -220,6 +219,7 @@ func Test_mksession_one_buffer_two_windows() let count1 = 0 let count2 = 0 let count2buf = 0 + let bufexists = 0 for line in lines if line =~ 'edit \f*Xtest1$' let count1 += 1 @@ -230,10 +230,14 @@ func Test_mksession_one_buffer_two_windows() if line =~ 'buffer \f\{-}Xtest2' let count2buf += 1 endif + if line =~ 'bufexists(fnamemodify(.*, ":p")' + let bufexists += 1 + endif endfor call assert_equal(1, count1, 'Xtest1 count') call assert_equal(2, count2, 'Xtest2 count') call assert_equal(2, count2buf, 'Xtest2 buffer count') + call assert_equal(2, bufexists) close bwipe! @@ -310,6 +314,31 @@ func Test_mksession_buffer_count() set nohidden endfunc +func Test_mksession_buffer_order() + %bwipe! + e Xfoo | e Xbar | e Xbaz | e Xqux + bufdo write + mksession! Xtest_mks.out + + " Verify that loading the session preserves order of buffers + %bwipe! + source Xtest_mks.out + + let s:buf_info = getbufinfo() + call assert_true(s:buf_info[0]['name'] =~# 'Xfoo$') + call assert_true(s:buf_info[1]['name'] =~# 'Xbar$') + call assert_true(s:buf_info[2]['name'] =~# 'Xbaz$') + call assert_true(s:buf_info[3]['name'] =~# 'Xqux$') + + " Clean up. + call delete('Xfoo') + call delete('Xbar') + call delete('Xbaz') + call delete('Xqux') + call delete('Xtest_mks.out') + %bwipe! +endfunc + if has('extra_search') func Test_mksession_hlsearch() @@ -697,6 +726,36 @@ func Test_mksession_foldopt() set sessionoptions& endfunc +" Test for mksession with "help" but not "options" in 'sessionoptions' +func Test_mksession_help_noopt() + set sessionoptions-=options + set sessionoptions+=help + help + let fname = expand('%') + mksession! Xtest_mks.out + bwipe + + source Xtest_mks.out + call assert_equal('help', &buftype) + call assert_equal('help', &filetype) + call assert_equal(fname, expand('%')) + call assert_false(&modifiable) + call assert_true(&readonly) + + helpclose + help index + let fname = expand('%') + mksession! Xtest_mks.out + bwipe + + source Xtest_mks.out + call assert_equal('help', &buftype) + call assert_equal(fname, expand('%')) + + call delete('Xtest_mks.out') + set sessionoptions& +endfunc + " Test for mksession with window position func Test_mksession_winpos() if !has('gui_running') @@ -736,6 +795,49 @@ func Test_mksession_winminheight() set sessionoptions& endfunc +" Test for mksession with and without options restores shortmess +func Test_mksession_shortmess() + " Without options + set sessionoptions-=options + split + mksession! Xtest_mks.out + let found_save = 0 + let found_restore = 0 + let lines = readfile('Xtest_mks.out') + for line in lines + let line = trim(line) + + if line ==# 'let s:shortmess_save = &shortmess' + let found_save += 1 + endif + + if found_save !=# 0 && line ==# 'let &shortmess = s:shortmess_save' + let found_restore += 1 + endif + endfor + call assert_equal(1, found_save) + call assert_equal(1, found_restore) + call delete('Xtest_mks.out') + close + set sessionoptions& + + " With options + set sessionoptions+=options + split + mksession! Xtest_mks.out + let found_restore = 0 + let lines = readfile('Xtest_mks.out') + for line in lines + if line =~# 's:shortmess_save' + let found_restore += 1 + endif + endfor + call assert_equal(0, found_restore) + call delete('Xtest_mks.out') + close + set sessionoptions& +endfunc + " Test for mksession with 'compatible' option func Test_mksession_compatible() throw 'skipped: Nvim does not support "compatible" option' diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index aff22f5d01..1feffe1f8c 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1118,7 +1118,7 @@ func Test_normal20_exmode() endif call writefile(['1a', 'foo', 'bar', '.', 'w! Xfile2', 'q!'], 'Xscript') call writefile(['1', '2'], 'Xfile') - call system(v:progpath .' -e -s < Xscript Xfile') + call system(GetVimCommand() .. ' -e -s < Xscript Xfile') let a=readfile('Xfile2') call assert_equal(['1', 'foo', 'bar', '2'], a) @@ -1171,13 +1171,13 @@ func Test_normal22_zet() endfor call writefile(['1', '2'], 'Xfile_Test_normal22_zet') - let args = ' --headless -u NONE -N -U NONE -i NONE --noplugins' - call system(v:progpath . args . ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet') + let args = ' -N -i NONE --noplugins -X --headless' + call system(GetVimCommand() .. args .. ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet') let a = readfile('Xfile_Test_normal22_zet') call assert_equal([], a) " Test for ZQ call writefile(['1', '2'], 'Xfile_Test_normal22_zet') - call system(v:progpath . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet') + call system(GetVimCommand() . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet') let a = readfile('Xfile_Test_normal22_zet') call assert_equal(['1', '2'], a) @@ -1812,7 +1812,15 @@ fun! Test_normal33_g_cmd2() call assert_equal(87, col('.')) call assert_equal('E', getreg(0)) + " Test for gM with Tab characters + call setline('.', "\ta\tb\tc\td\te\tf") + norm! gMyl + call assert_equal(6, col('.')) + call assert_equal("c", getreg(0)) + " Test for g Ctrl-G + call setline('.', lineC) + norm! 60gMyl set ff=unix let a=execute(":norm! g\<c-g>") call assert_match('Col 87 of 144; Line 2 of 2; Word 1 of 1; Byte 88 of 146', a) @@ -1835,6 +1843,16 @@ fun! Test_normal33_g_cmd2() bw! endfunc +func Test_normal_ex_substitute() + " This was hanging on the substitute prompt. + new + call setline(1, 'a') + exe "normal! gggQs/a/b/c\<CR>" + call assert_equal('a', getline(1)) + bwipe! +endfunc + +" Test for g CTRL-G func Test_g_ctrl_g() new @@ -2759,4 +2777,37 @@ func Test_normal_count_after_operator() bw! endfunc +func Test_normal_gj_on_extra_wide_char() + new | 25vsp + let text='1 foooooooo ar e insâ€zwe1 foooooooo insâ€zwei' . + \ ' i drei vier fünf sechs sieben acht un zehn elf zwöfl' . + \ ' dreizehn v ierzehn fünfzehn' + put =text + call cursor(2,1) + norm! gj + call assert_equal([0,2,25,0], getpos('.')) + bw! +endfunc + +func Test_normal_count_out_of_range() + new + call setline(1, 'text') + normal 44444444444| + call assert_equal(999999999, v:count) + normal 444444444444| + call assert_equal(999999999, v:count) + normal 4444444444444| + call assert_equal(999999999, v:count) + normal 4444444444444444444| + call assert_equal(999999999, v:count) + + normal 9y99999999| + call assert_equal(899999991, v:count) + normal 10y99999999| + call assert_equal(999999999, v:count) + normal 44444444444y44444444444| + call assert_equal(999999999, v:count) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim index d737ebe9f0..521b0cf706 100644 --- a/src/nvim/testdir/test_number.vim +++ b/src/nvim/testdir/test_number.vim @@ -284,10 +284,10 @@ func Test_relativenumber_colors() " Default colors call VerifyScreenDump(buf, 'Test_relnr_colors_1', {}) - call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>") + call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>:\<CR>") call VerifyScreenDump(buf, 'Test_relnr_colors_2', {}) - call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>") + call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>:\<CR>") call VerifyScreenDump(buf, 'Test_relnr_colors_3', {}) call term_sendkeys(buf, ":hi clear LineNrAbove\<CR>") @@ -298,6 +298,31 @@ func Test_relativenumber_colors() call delete('XTest_relnr') endfunc +func Test_relativenumber_callback() + CheckScreendump + CheckFeature timers + + let lines =<< trim END + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set relativenumber + call cursor(4, 1) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + END + call writefile(lines, 'Xrnu_timer') + + let buf = RunVimInTerminal('-S Xrnu_timer', #{rows: 8}) + call TermWait(buf, 310) + call VerifyScreenDump(buf, 'Test_relativenumber_callback_1', {}) + + call StopVimInTerminal(buf) + call delete('Xrnu_timer') +endfunc + " Test for displaying line numbers with 'rightleft' func Test_number_rightleft() CheckFeature rightleft diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 41c689849b..8612b7013b 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -22,16 +22,16 @@ func Test_whichwrap() call assert_equal('h', &whichwrap) set whichwrap& -endfunction +endfunc -function! Test_isfname() +func Test_isfname() " This used to cause Vim to access uninitialized memory. set isfname= call assert_equal("~X", expand("~X")) set isfname& -endfunction +endfunc -function Test_wildchar() +func Test_wildchar() " Empty 'wildchar' used to access invalid memory. call assert_fails('set wildchar=', 'E521:') call assert_fails('set wildchar=abc', 'E521:') @@ -42,7 +42,7 @@ function Test_wildchar() let a=execute('set wildchar?') call assert_equal("\n wildchar=<Esc>", a) set wildchar& -endfunction +endfunc func Test_wildoptions() set wildoptions= @@ -51,7 +51,7 @@ func Test_wildoptions() call assert_equal('tagfile', &wildoptions) endfunc -function! Test_options() +func Test_options_command() let caught = 'ok' try options @@ -88,9 +88,9 @@ function! Test_options() " close option-window close -endfunction +endfunc -function! Test_path_keep_commas() +func Test_path_keep_commas() " Test that changing 'path' keeps two commas. set path=foo,,bar set path-=bar @@ -98,7 +98,7 @@ function! Test_path_keep_commas() call assert_equal('foo,,bar', &path) set path& -endfunction +endfunc func Test_filetype_valid() set ft=valid_name @@ -235,8 +235,7 @@ func Test_set_completion() call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt') call assert_equal('"set filetype=sshdconfig', @:) call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt') - " call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:) - call assert_equal('"set filetype=' .. join(getcompletion('a*', 'filetype')), @:) + call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:) endfunc func Test_set_errors() @@ -260,6 +259,8 @@ func Test_set_errors() call assert_fails('set shiftwidth=-1', 'E487:') call assert_fails('set sidescroll=-1', 'E487:') call assert_fails('set tabstop=-1', 'E487:') + call assert_fails('set tabstop=10000', 'E474:') + call assert_fails('set tabstop=5500000000', 'E474:') call assert_fails('set textwidth=-1', 'E487:') call assert_fails('set timeoutlen=-1', 'E487:') call assert_fails('set updatecount=-1', 'E487:') @@ -313,21 +314,50 @@ func Test_set_errors() set modifiable& endfunc +func CheckWasSet(name) + let verb_cm = execute('verbose set ' .. a:name .. '?') + call assert_match('Last set from.*test_options.vim', verb_cm) +endfunc +func CheckWasNotSet(name) + let verb_cm = execute('verbose set ' .. a:name .. '?') + call assert_notmatch('Last set from', verb_cm) +endfunc + " Must be executed before other tests that set 'term'. func Test_000_term_option_verbose() if has('nvim') || has('gui_running') return endif - let verb_cm = execute('verbose set t_cm') - call assert_notmatch('Last set from', verb_cm) + + call CheckWasNotSet('t_cm') let term_save = &term set term=ansi - let verb_cm = execute('verbose set t_cm') - call assert_match('Last set from.*test_options.vim', verb_cm) + call CheckWasSet('t_cm') let &term = term_save endfunc +func Test_copy_context() + setlocal list + call CheckWasSet('list') + split + call CheckWasSet('list') + quit + setlocal nolist + + set ai + call CheckWasSet('ai') + set filetype=perl + call CheckWasSet('filetype') + set fo=tcroq + call CheckWasSet('fo') + + split Xsomebuf + call CheckWasSet('ai') + call CheckWasNotSet('filetype') + call CheckWasSet('fo') +endfunc + func Test_set_ttytype() " Nvim does not support 'ttytype'. if !has('nvim') && !has('gui_running') && has('unix') @@ -369,6 +399,13 @@ func Test_set_all() set tw& iskeyword& splitbelow& endfunc +func Test_set_one_column() + let out_mult = execute('set all')->split("\n") + let out_one = execute('set! all')->split("\n") + " one column should be two to four times as many lines + call assert_inrange(len(out_mult) * 2, len(out_mult) * 4, len(out_one)) +endfunc + func Test_set_values() " The file is only generated when running "make test" in the src directory. if filereadable('opt_test.vim') @@ -648,6 +685,19 @@ func Test_buftype() close! endfunc +" Test for the 'shellquote' option +func Test_shellquote() + CheckUnix + set shellquote=# + set verbose=20 + redir => v + silent! !echo Hello + redir END + set verbose& + set shellquote& + call assert_match(': "#echo Hello#"', v) +endfunc + " Test for setting option values using v:false and v:true func Test_opt_boolean() set number& @@ -733,4 +783,25 @@ func Test_opt_reset_scroll() call delete('Xscroll') endfunc +" Test for the 'cdhome' option +func Test_opt_cdhome() + if has('unix') || has('vms') + throw 'Skipped: only works on non-Unix' + endif + + set cdhome& + call assert_equal(0, &cdhome) + set cdhome + + " This paragraph is copied from Test_cd_no_arg(). + let path = getcwd() + cd + call assert_equal($HOME, getcwd()) + call assert_notequal(path, getcwd()) + exe 'cd ' .. fnameescape(path) + call assert_equal(path, getcwd()) + + set cdhome& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_packadd.vim b/src/nvim/testdir/test_packadd.vim new file mode 100644 index 0000000000..fcb8b8033b --- /dev/null +++ b/src/nvim/testdir/test_packadd.vim @@ -0,0 +1,361 @@ +" Tests for 'packpath' and :packadd + + +func SetUp() + let s:topdir = getcwd() . '/Xdir' + exe 'set packpath=' . s:topdir + let s:plugdir = s:topdir . '/pack/mine/opt/mytest' +endfunc + +func TearDown() + call delete(s:topdir, 'rf') +endfunc + +func Test_packadd() + if !exists('s:plugdir') + echomsg 'when running this test manually, call SetUp() first' + return + endif + + call mkdir(s:plugdir . '/plugin/also', 'p') + call mkdir(s:plugdir . '/ftdetect', 'p') + call mkdir(s:plugdir . '/after', 'p') + set rtp& + let rtp = &rtp + filetype on + + let rtp_entries = split(rtp, ',') + for entry in rtp_entries + if entry =~? '\<after\>' + let first_after_entry = entry + break + endif + endfor + + exe 'split ' . s:plugdir . '/plugin/test.vim' + call setline(1, 'let g:plugin_works = 42') + wq + + exe 'split ' . s:plugdir . '/plugin/also/loaded.vim' + call setline(1, 'let g:plugin_also_works = 77') + wq + + exe 'split ' . s:plugdir . '/ftdetect/test.vim' + call setline(1, 'let g:ftdetect_works = 17') + wq + + packadd mytest + + call assert_equal(42, g:plugin_works) + call assert_equal(77, g:plugin_also_works) + call assert_equal(17, g:ftdetect_works) + call assert_true(len(&rtp) > len(rtp)) + call assert_match('/testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp) + + let new_after = match(&rtp, '/testdir/Xdir/pack/mine/opt/mytest/after,') + let forwarded = substitute(first_after_entry, '\\', '[/\\\\]', 'g') + let old_after = match(&rtp, ',' . forwarded . '\>') + call assert_true(new_after > 0, 'rtp is ' . &rtp) + call assert_true(old_after > 0, 'match ' . forwarded . ' in ' . &rtp) + call assert_true(new_after < old_after, 'rtp is ' . &rtp) + + " NOTE: '/.../opt/myte' forwardly matches with '/.../opt/mytest' + call mkdir(fnamemodify(s:plugdir, ':h') . '/myte', 'p') + let rtp = &rtp + packadd myte + + " Check the path of 'myte' is added + call assert_true(len(&rtp) > len(rtp)) + call assert_match('/testdir/Xdir/pack/mine/opt/myte\($\|,\)', &rtp) + + " Check exception + call assert_fails("packadd directorynotfound", 'E919:') + call assert_fails("packadd", 'E471:') +endfunc + +func Test_packadd_start() + let plugdir = s:topdir . '/pack/mine/start/other' + call mkdir(plugdir . '/plugin', 'p') + set rtp& + let rtp = &rtp + filetype on + + exe 'split ' . plugdir . '/plugin/test.vim' + call setline(1, 'let g:plugin_works = 24') + wq + + packadd other + + call assert_equal(24, g:plugin_works) + call assert_true(len(&rtp) > len(rtp)) + call assert_match('/testdir/Xdir/pack/mine/start/other\($\|,\)', &rtp) +endfunc + +func Test_packadd_noload() + call mkdir(s:plugdir . '/plugin', 'p') + call mkdir(s:plugdir . '/syntax', 'p') + set rtp& + let rtp = &rtp + + exe 'split ' . s:plugdir . '/plugin/test.vim' + call setline(1, 'let g:plugin_works = 42') + wq + let g:plugin_works = 0 + + packadd! mytest + + call assert_true(len(&rtp) > len(rtp)) + call assert_match('testdir/Xdir/pack/mine/opt/mytest\($\|,\)', &rtp) + call assert_equal(0, g:plugin_works) + + " check the path is not added twice + let new_rtp = &rtp + packadd! mytest + call assert_equal(new_rtp, &rtp) +endfunc + +func Test_packadd_symlink_dir() + if !has('unix') + return + endif + let top2_dir = s:topdir . '/Xdir2' + let real_dir = s:topdir . '/Xsym' + call mkdir(real_dir, 'p') + exec "silent !ln -s Xsym" top2_dir + let &rtp = top2_dir . ',' . top2_dir . '/after' + let &packpath = &rtp + + let s:plugdir = top2_dir . '/pack/mine/opt/mytest' + call mkdir(s:plugdir . '/plugin', 'p') + + exe 'split ' . s:plugdir . '/plugin/test.vim' + call setline(1, 'let g:plugin_works = 44') + wq + let g:plugin_works = 0 + + packadd mytest + + " Must have been inserted in the middle, not at the end + call assert_match('/pack/mine/opt/mytest,', &rtp) + call assert_equal(44, g:plugin_works) + + " No change when doing it again. + let rtp_before = &rtp + packadd mytest + call assert_equal(rtp_before, &rtp) + + set rtp& + let rtp = &rtp + exec "silent !rm" top2_dir +endfunc + +func Test_packadd_symlink_dir2() + if !has('unix') + return + endif + let top2_dir = s:topdir . '/Xdir2' + let real_dir = s:topdir . '/Xsym/pack' + call mkdir(top2_dir, 'p') + call mkdir(real_dir, 'p') + let &rtp = top2_dir . ',' . top2_dir . '/after' + let &packpath = &rtp + + exec "silent !ln -s ../Xsym/pack" top2_dir . '/pack' + let s:plugdir = top2_dir . '/pack/mine/opt/mytest' + call mkdir(s:plugdir . '/plugin', 'p') + + exe 'split ' . s:plugdir . '/plugin/test.vim' + call setline(1, 'let g:plugin_works = 48') + wq + let g:plugin_works = 0 + + packadd mytest + + " Must have been inserted in the middle, not at the end + call assert_match('/Xdir2/pack/mine/opt/mytest,', &rtp) + call assert_equal(48, g:plugin_works) + + " No change when doing it again. + let rtp_before = &rtp + packadd mytest + call assert_equal(rtp_before, &rtp) + + set rtp& + let rtp = &rtp + exec "silent !rm" top2_dir . '/pack' + exec "silent !rmdir" top2_dir +endfunc + +" Check command-line completion for 'packadd' +func Test_packadd_completion() + let optdir1 = &packpath . '/pack/mine/opt' + let optdir2 = &packpath . '/pack/candidate/opt' + + call mkdir(optdir1 . '/pluginA', 'p') + call mkdir(optdir1 . '/pluginC', 'p') + call mkdir(optdir2 . '/pluginB', 'p') + call mkdir(optdir2 . '/pluginC', 'p') + + let li = [] + call feedkeys(":packadd \<Tab>')\<C-B>call add(li, '\<CR>", 't') + call feedkeys(":packadd " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't') + call feedkeys(":packadd " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't') + call feedkeys(":packadd " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx') + call assert_equal("packadd pluginA", li[0]) + call assert_equal("packadd pluginB", li[1]) + call assert_equal("packadd pluginC", li[2]) + call assert_equal("packadd ", li[3]) +endfunc + +func Test_packloadall() + " plugin foo with an autoload directory + let fooplugindir = &packpath . '/pack/mine/start/foo/plugin' + call mkdir(fooplugindir, 'p') + call writefile(['let g:plugin_foo_number = 1234', + \ 'let g:plugin_foo_auto = bbb#value', + \ 'let g:plugin_extra_auto = extra#value'], fooplugindir . '/bar.vim') + let fooautodir = &packpath . '/pack/mine/start/foo/autoload' + call mkdir(fooautodir, 'p') + call writefile(['let bar#value = 77'], fooautodir . '/bar.vim') + + " plugin aaa with an autoload directory + let aaaplugindir = &packpath . '/pack/mine/start/aaa/plugin' + call mkdir(aaaplugindir, 'p') + call writefile(['let g:plugin_aaa_number = 333', + \ 'let g:plugin_aaa_auto = bar#value'], aaaplugindir . '/bbb.vim') + let aaaautodir = &packpath . '/pack/mine/start/aaa/autoload' + call mkdir(aaaautodir, 'p') + call writefile(['let bbb#value = 55'], aaaautodir . '/bbb.vim') + + " plugin extra with only an autoload directory + let extraautodir = &packpath . '/pack/mine/start/extra/autoload' + call mkdir(extraautodir, 'p') + call writefile(['let extra#value = 99'], extraautodir . '/extra.vim') + + packloadall + call assert_equal(1234, g:plugin_foo_number) + call assert_equal(55, g:plugin_foo_auto) + call assert_equal(99, g:plugin_extra_auto) + call assert_equal(333, g:plugin_aaa_number) + call assert_equal(77, g:plugin_aaa_auto) + + " only works once + call writefile(['let g:plugin_bar_number = 4321'], fooplugindir . '/bar2.vim') + packloadall + call assert_false(exists('g:plugin_bar_number')) + + " works when ! used + packloadall! + call assert_equal(4321, g:plugin_bar_number) +endfunc + +func Test_helptags() + let docdir1 = &packpath . '/pack/mine/start/foo/doc' + let docdir2 = &packpath . '/pack/mine/start/bar/doc' + call mkdir(docdir1, 'p') + call mkdir(docdir2, 'p') + call writefile(['look here: *look-here*'], docdir1 . '/bar.txt') + call writefile(['look away: *look-away*'], docdir2 . '/foo.txt') + exe 'set rtp=' . &packpath . '/pack/mine/start/foo,' . &packpath . '/pack/mine/start/bar' + + helptags ALL + + let tags1 = readfile(docdir1 . '/tags') + call assert_match('look-here', tags1[0]) + let tags2 = readfile(docdir2 . '/tags') + call assert_match('look-away', tags2[0]) + + call assert_fails('helptags abcxyz', 'E150:') +endfunc + +func Test_colorscheme() + let colordirrun = &packpath . '/runtime/colors' + let colordirstart = &packpath . '/pack/mine/start/foo/colors' + let colordiropt = &packpath . '/pack/mine/opt/bar/colors' + call mkdir(colordirrun, 'p') + call mkdir(colordirstart, 'p') + call mkdir(colordiropt, 'p') + call writefile(['let g:found_one = 1'], colordirrun . '/one.vim') + call writefile(['let g:found_two = 1'], colordirstart . '/two.vim') + call writefile(['let g:found_three = 1'], colordiropt . '/three.vim') + exe 'set rtp=' . &packpath . '/runtime' + + colorscheme one + call assert_equal(1, g:found_one) + colorscheme two + call assert_equal(1, g:found_two) + colorscheme three + call assert_equal(1, g:found_three) +endfunc + +func Test_colorscheme_completion() + let colordirrun = &packpath . '/runtime/colors' + let colordirstart = &packpath . '/pack/mine/start/foo/colors' + let colordiropt = &packpath . '/pack/mine/opt/bar/colors' + call mkdir(colordirrun, 'p') + call mkdir(colordirstart, 'p') + call mkdir(colordiropt, 'p') + call writefile(['let g:found_one = 1'], colordirrun . '/one.vim') + call writefile(['let g:found_two = 1'], colordirstart . '/two.vim') + call writefile(['let g:found_three = 1'], colordiropt . '/three.vim') + exe 'set rtp=' . &packpath . '/runtime' + + let li=[] + call feedkeys(":colorscheme " . repeat("\<Tab>", 1) . "')\<C-B>call add(li, '\<CR>", 't') + call feedkeys(":colorscheme " . repeat("\<Tab>", 2) . "')\<C-B>call add(li, '\<CR>", 't') + call feedkeys(":colorscheme " . repeat("\<Tab>", 3) . "')\<C-B>call add(li, '\<CR>", 't') + call feedkeys(":colorscheme " . repeat("\<Tab>", 4) . "')\<C-B>call add(li, '\<CR>", 'tx') + call assert_equal("colorscheme one", li[0]) + call assert_equal("colorscheme three", li[1]) + call assert_equal("colorscheme two", li[2]) + call assert_equal("colorscheme ", li[3]) +endfunc + +func Test_runtime() + let rundir = &packpath . '/runtime/extra' + let startdir = &packpath . '/pack/mine/start/foo/extra' + let optdir = &packpath . '/pack/mine/opt/bar/extra' + call mkdir(rundir, 'p') + call mkdir(startdir, 'p') + call mkdir(optdir, 'p') + call writefile(['let g:sequence .= "run"'], rundir . '/bar.vim') + call writefile(['let g:sequence .= "start"'], startdir . '/bar.vim') + call writefile(['let g:sequence .= "foostart"'], startdir . '/foo.vim') + call writefile(['let g:sequence .= "opt"'], optdir . '/bar.vim') + call writefile(['let g:sequence .= "xxxopt"'], optdir . '/xxx.vim') + exe 'set rtp=' . &packpath . '/runtime' + + let g:sequence = '' + runtime extra/bar.vim + call assert_equal('run', g:sequence) + let g:sequence = '' + runtime START extra/bar.vim + call assert_equal('start', g:sequence) + let g:sequence = '' + runtime OPT extra/bar.vim + call assert_equal('opt', g:sequence) + let g:sequence = '' + runtime PACK extra/bar.vim + call assert_equal('start', g:sequence) + let g:sequence = '' + runtime! PACK extra/bar.vim + call assert_equal('startopt', g:sequence) + let g:sequence = '' + runtime PACK extra/xxx.vim + call assert_equal('xxxopt', g:sequence) + + let g:sequence = '' + runtime ALL extra/bar.vim + call assert_equal('run', g:sequence) + let g:sequence = '' + runtime ALL extra/foo.vim + call assert_equal('foostart', g:sequence) + let g:sequence = '' + runtime! ALL extra/xxx.vim + call assert_equal('xxxopt', g:sequence) + let g:sequence = '' + runtime! ALL extra/bar.vim + call assert_equal('runstartopt', g:sequence) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index 4b0097617e..fdb6f13e2b 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -1,8 +1,9 @@ " Test Vim profiler -if !has('profile') - finish -endif +source check.vim +CheckFeature profile + +source shared.vim source screendump.vim func Test_profile_func() @@ -37,7 +38,7 @@ func Test_profile_func() [CODE] call writefile(lines, 'Xprofile_func.vim') - call system(v:progpath + call system(GetVimCommand() \ . ' -es --clean' \ . ' -c "so Xprofile_func.vim"' \ . ' -c "qall!"') @@ -124,8 +125,8 @@ func Test_profile_func_with_ifelse() [CODE] call writefile(lines, 'Xprofile_func.vim') - call system(v:progpath - \ . ' -es -u NONE -U NONE -i NONE --noplugin' + call system(GetVimCommand() + \ . ' -es -i NONE --noplugin' \ . ' -c "profile start Xprofile_func.log"' \ . ' -c "profile func Foo*"' \ . ' -c "so Xprofile_func.vim"' @@ -237,8 +238,8 @@ func Test_profile_func_with_trycatch() [CODE] call writefile(lines, 'Xprofile_func.vim') - call system(v:progpath - \ . ' -es -u NONE -U NONE -i NONE --noplugin' + call system(GetVimCommand() + \ . ' -es -i NONE --noplugin' \ . ' -c "profile start Xprofile_func.log"' \ . ' -c "profile func Foo*"' \ . ' -c "so Xprofile_func.vim"' @@ -324,8 +325,8 @@ func Test_profile_file() [CODE] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath - \ . ' -es --clean' + call system(GetVimCommandClean() + \ . ' -es' \ . ' -c "profile start Xprofile_file.log"' \ . ' -c "profile file Xprofile_file.vim"' \ . ' -c "so Xprofile_file.vim"' @@ -369,8 +370,8 @@ func Test_profile_file_with_cont() \ ] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath - \ . ' -es -u NONE -U NONE -i NONE --noplugin' + call system(GetVimCommandClean() + \ . ' -es' \ . ' -c "profile start Xprofile_file.log"' \ . ' -c "profile file Xprofile_file.vim"' \ . ' -c "so Xprofile_file.vim"' @@ -427,7 +428,7 @@ func Test_profile_truncate_mbyte() \ ] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath + call system(GetVimCommandClean() \ . ' -es --cmd "set enc=utf-8"' \ . ' -c "profile start Xprofile_file.log"' \ . ' -c "profile file Xprofile_file.vim"' @@ -474,7 +475,7 @@ func Test_profdel_func() call Foo3() [CODE] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q') + call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q') call assert_equal(0, v:shell_error) let lines = readfile('Xprofile_file.log') @@ -509,7 +510,7 @@ func Test_profdel_star() call Foo() [CODE] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q') + call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q') call assert_equal(0, v:shell_error) let lines = readfile('Xprofile_file.log') diff --git a/src/nvim/testdir/test_prompt_buffer.vim b/src/nvim/testdir/test_prompt_buffer.vim index 3da46eb1a6..8f94a8572b 100644 --- a/src/nvim/testdir/test_prompt_buffer.vim +++ b/src/nvim/testdir/test_prompt_buffer.vim @@ -41,6 +41,10 @@ func WriteScript(name) \ ' set nomodified', \ 'endfunc', \ '', + \ 'func SwitchWindows()', + \ ' call timer_start(0, {-> execute("wincmd p|wincmd p", "")})', + \ 'endfunc', + \ '', \ 'call setline(1, "other buffer")', \ 'set nomodified', \ 'new', @@ -89,9 +93,12 @@ func Test_prompt_editing() call term_sendkeys(buf, left . left . left . bs . '-') call WaitForAssert({-> assert_equal('cmd: -hel', term_getline(buf, 1))}) + call term_sendkeys(buf, "\<C-O>lz") + call WaitForAssert({-> assert_equal('cmd: -hzel', term_getline(buf, 1))}) + let end = "\<End>" call term_sendkeys(buf, end . "x") - call WaitForAssert({-> assert_equal('cmd: -helx', term_getline(buf, 1))}) + call WaitForAssert({-> assert_equal('cmd: -hzelx', term_getline(buf, 1))}) call term_sendkeys(buf, "\<C-U>exit\<CR>") call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))}) @@ -100,6 +107,28 @@ func Test_prompt_editing() call delete(scriptName) endfunc +func Test_prompt_switch_windows() + throw 'skipped: TODO' + call CanTestPromptBuffer() + let scriptName = 'XpromptSwitchWindows' + call WriteScript(scriptName) + + let buf = RunVimInTerminal('-S ' . scriptName, {'rows': 12}) + call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))}) + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 12))}) + + call term_sendkeys(buf, "\<C-O>:call SwitchWindows()\<CR>") + call term_wait(buf, 50) + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 12))}) + + call term_sendkeys(buf, "\<Esc>") + call term_wait(buf, 50) + call WaitForAssert({-> assert_match('^ *$', term_getline(buf, 12))}) + + call StopVimInTerminal(buf) + call delete(scriptName) +endfunc + func Test_prompt_garbage_collect() func MyPromptCallback(x, text) " NOP @@ -126,6 +155,14 @@ func Test_prompt_garbage_collect() bwipe! endfunc +func Test_prompt_backspace() + new + set buftype=prompt + call feedkeys("A123456\<Left>\<BS>\<Esc>", 'xt') + call assert_equal('% 12346', getline(1)) + bwipe! +endfunc + " Test for editing the prompt buffer func Test_prompt_buffer_edit() new @@ -145,10 +182,9 @@ func Test_prompt_buffer_edit() call assert_beeps("normal! \<C-X>") " pressing CTRL-W in the prompt buffer should trigger the window commands call assert_equal(1, winnr()) - " In Nvim, CTRL-W commands aren't usable from insert mode in a prompt buffer - " exe "normal A\<C-W>\<C-W>" - " call assert_equal(2, winnr()) - " wincmd w + exe "normal A\<C-W>\<C-W>" + call assert_equal(2, winnr()) + wincmd w close! call assert_equal(0, prompt_setprompt([], '')) endfunc @@ -165,9 +201,7 @@ func Test_prompt_buffer_getbufinfo() call assert_equal('This is a test: ', prompt_getprompt('%')) call prompt_setprompt( bufnr( '%' ), '' ) - " Nvim doesn't support method call syntax yet. - " call assert_equal('', '%'->prompt_getprompt()) - call assert_equal('', prompt_getprompt('%')) + call assert_equal('', '%'->prompt_getprompt()) call prompt_setprompt( bufnr( '%' ), 'Another: ' ) call assert_equal('Another: ', prompt_getprompt('%')) @@ -189,4 +223,38 @@ func Test_prompt_buffer_getbufinfo() %bwipe! endfunc +function! Test_prompt_while_writing_to_hidden_buffer() + throw 'skipped: TODO' + call CanTestPromptBuffer() + CheckUnix + + " Make a job continuously write to a hidden buffer, check that the prompt + " buffer is not affected. + let scriptName = 'XpromptscriptHiddenBuf' + let script =<< trim END + set buftype=prompt + call prompt_setprompt( bufnr(), 'cmd:' ) + let job = job_start(['/bin/sh', '-c', + \ 'while true; + \ do echo line; + \ sleep 0.1; + \ done'], #{out_io: 'buffer', out_name: ''}) + startinsert + END + eval script->writefile(scriptName) + + let buf = RunVimInTerminal('-S ' .. scriptName, {}) + call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))}) + + call term_sendkeys(buf, 'test') + call WaitForAssert({-> assert_equal('cmd:test', term_getline(buf, 1))}) + call term_sendkeys(buf, 'test') + call WaitForAssert({-> assert_equal('cmd:testtest', term_getline(buf, 1))}) + call term_sendkeys(buf, 'test') + call WaitForAssert({-> assert_equal('cmd:testtesttest', term_getline(buf, 1))}) + + call StopVimInTerminal(buf) + call delete(scriptName) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index 15745d5619..65232175c6 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -1,5 +1,7 @@ " Tests for put commands, e.g. ":put", "p", "gp", "P", "gP", etc. +source check.vim + func Test_put_block() new call feedkeys("i\<C-V>u2500\<CR>x\<ESC>", 'x') @@ -39,7 +41,7 @@ func Test_put_lines() call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1, '$')) " clean up bw! - call setreg('a', a[0], a[1]) + eval a[0]->setreg('a', a[1]) endfunc func Test_put_expr() @@ -111,3 +113,93 @@ func Test_put_p_indent_visual() call assert_equal('select that text', getline(2)) bwipe! endfunc + +func Test_gp_with_count_leaves_cursor_at_end() + new + call setline(1, '<---->') + call setreg('@', "foo\nbar", 'c') + normal 1G3|3gp + call assert_equal([0, 4, 4, 0], getpos(".")) + call assert_equal(['<--foo', 'barfoo', 'barfoo', 'bar-->'], getline(1, '$')) + call assert_equal([0, 4, 3, 0], getpos("']")) + + bwipe! +endfunc + +func Test_p_with_count_leaves_mark_at_end() + new + call setline(1, '<---->') + call setreg('@', "start\nend", 'c') + normal 1G3|3p + call assert_equal([0, 1, 4, 0], getpos(".")) + call assert_equal(['<--start', 'endstart', 'endstart', 'end-->'], getline(1, '$')) + call assert_equal([0, 4, 3, 0], getpos("']")) + + bwipe! +endfunc + +func Test_very_large_count() + new + " total put-length (21474837 * 100) brings 32 bit int overflow + let @" = repeat('x', 100) + call assert_fails('norm 21474837p', 'E1240:') + bwipe! +endfunc + +func Test_very_large_count_64bit() + throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead + + if v:sizeoflong < 8 + throw 'Skipped: only works with 64 bit long ints' + endif + + new + let @" = repeat('x', 100) + call assert_fails('norm 999999999p', 'E1240:') + bwipe! +endfunc + +func Test_very_large_count_block() + new + " total put-length (21474837 * 100) brings 32 bit int overflow + call setline(1, repeat('x', 100)) + exe "norm \<C-V>99ly" + call assert_fails('norm 21474837p', 'E1240:') + bwipe! +endfunc + +func Test_very_large_count_block_64bit() + throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead + + if v:sizeoflong < 8 + throw 'Skipped: only works with 64 bit long ints' + endif + + new + call setline(1, repeat('x', 100)) + exe "norm \<C-V>$y" + call assert_fails('norm 999999999p', 'E1240:') + bwipe! +endfunc + +func Test_put_above_first_line() + new + let @" = 'text' + silent! normal 0o00 + 0put + call assert_equal('text', getline(1)) + bwipe! +endfunc + +func Test_multibyte_op_end_mark() + new + call setline(1, 'теÑÑ‚') + normal viwdp + call assert_equal([0, 1, 7, 0], getpos("'>")) + call assert_equal([0, 1, 7, 0], getpos("']")) + + normal Vyp + call assert_equal([0, 1, 2147483647, 0], getpos("'>")) + call assert_equal([0, 2, 7, 0], getpos("']")) + bwipe! +endfunc diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index 54da3d2eba..f6a1942e24 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -69,6 +69,7 @@ func Test_vim_function() endfunc func Test_skipped_python3_command_does_not_affect_pyxversion() + throw 'skipped: Nvim hardcodes pyxversion=3' set pyxversion=0 if 0 python3 import vim diff --git a/src/nvim/testdir/test_pyx2.vim b/src/nvim/testdir/test_pyx2.vim index b6ed80f842..6a8ebf3da0 100644 --- a/src/nvim/testdir/test_pyx2.vim +++ b/src/nvim/testdir/test_pyx2.vim @@ -1,9 +1,9 @@ " Test for pyx* commands and functions with Python 2. -set pyx=2 if !has('python') finish endif +set pyx=2 let s:py2pattern = '^2\.[0-7]\.\d\+' let s:py3pattern = '^3\.\d\+\.\d\+' diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index b38a59e98f..29722ef09b 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1,4 +1,4 @@ -" Test for the quickfix commands. +" Test for the quickfix feature. source check.vim CheckFeature quickfix @@ -32,7 +32,7 @@ func s:setup_commands(cchar) command! -count -nargs=* -bang Xnfile <mods><count>cnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args> command! -nargs=* Xexpr <mods>cexpr <args> - command! -count -nargs=* Xvimgrep <mods> <count>vimgrep <args> + command! -count=999 -nargs=* Xvimgrep <mods> <count>vimgrep <args> command! -nargs=* Xvimgrepadd <mods> vimgrepadd <args> command! -nargs=* Xgrep <mods> grep <args> command! -nargs=* Xgrepadd <mods> grepadd <args> @@ -69,7 +69,7 @@ func s:setup_commands(cchar) command! -count -nargs=* -bang Xnfile <mods><count>lnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args> command! -nargs=* Xexpr <mods>lexpr <args> - command! -count -nargs=* Xvimgrep <mods> <count>lvimgrep <args> + command! -count=999 -nargs=* Xvimgrep <mods> <count>lvimgrep <args> command! -nargs=* Xvimgrepadd <mods> lvimgrepadd <args> command! -nargs=* Xgrep <mods> lgrep <args> command! -nargs=* Xgrepadd <mods> lgrepadd <args> @@ -169,8 +169,8 @@ func XlistTests(cchar) \ {'lnum':30,'col':15,'type':'W','filename':'Data/Text.hs','text':'FileWarning','nr':33,'valid':v:true}]) let l = split(execute('Xlist', ""), "\n") call assert_equal([' 1 Data.Text:10 col 5 warning 11: ModuleWarning', - \ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning', - \ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l) + \ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning', + \ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l) " For help entries in the quickfix list, only the filename without directory " should be displayed @@ -796,101 +796,102 @@ func ReadTestProtocol(name) endfunc func Test_locationlist() - enew + enew - augroup testgroup - au! - autocmd BufReadCmd test://* call ReadTestProtocol(expand("<amatch>")) - augroup END + augroup testgroup + au! + autocmd BufReadCmd test://* call ReadTestProtocol(expand("<amatch>")) + augroup END - let words = [ "foo", "bar", "baz", "quux", "shmoo", "spam", "eggs" ] + let words = [ "foo", "bar", "baz", "quux", "shmoo", "spam", "eggs" ] - let qflist = [] - for word in words - call add(qflist, {'filename': 'test://' . word . '.txt', 'text': 'file ' . word . '.txt', }) - " NOTE: problem 1: - " intentionally not setting 'lnum' so that the quickfix entries are not - " valid - call setloclist(0, qflist, ' ') - endfor + let qflist = [] + for word in words + call add(qflist, {'filename': 'test://' . word . '.txt', 'text': 'file ' . word . '.txt', }) + " NOTE: problem 1: + " intentionally not setting 'lnum' so that the quickfix entries are not + " valid + eval qflist->setloclist(0, ' ') + endfor - " Test A - lrewind - enew - lopen - 4lnext - vert split - wincmd L - lopen - wincmd p - lnext - let fileName = expand("%") - wincmd p - let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '') - let fileName = substitute(fileName, '\\', '/', 'g') - let locationListFileName = substitute(locationListFileName, '\\', '/', 'g') - call assert_equal("test://bar.txt", fileName) - call assert_equal("test://bar.txt", locationListFileName) + " Test A + lrewind + enew + lopen + 4lnext + vert split + wincmd L + lopen + wincmd p + lnext + let fileName = expand("%") + wincmd p + let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '') + let fileName = substitute(fileName, '\\', '/', 'g') + let locationListFileName = substitute(locationListFileName, '\\', '/', 'g') + call assert_equal("test://bar.txt", fileName) + call assert_equal("test://bar.txt", locationListFileName) - wincmd n | only + wincmd n | only - " Test B: - lrewind - lopen - 2 - exe "normal \<CR>" - wincmd p - 3 - exe "normal \<CR>" - wincmd p - 4 - exe "normal \<CR>" - call assert_equal(2, winnr('$')) - wincmd n | only + " Test B: + lrewind + lopen + 2 + exe "normal \<CR>" + wincmd p + 3 + exe "normal \<CR>" + wincmd p + 4 + exe "normal \<CR>" + call assert_equal(2, winnr('$')) + wincmd n | only - " Test C: - lrewind - lopen - " Let's move the location list window to the top to check whether it (the - " first window found) will be reused when we try to open new windows: - wincmd K - 2 - exe "normal \<CR>" - wincmd p - 3 - exe "normal \<CR>" - wincmd p - 4 - exe "normal \<CR>" - 1wincmd w - call assert_equal('quickfix', &buftype) - 2wincmd w - let bufferName = expand("%") - let bufferName = substitute(bufferName, '\\', '/', 'g') - call assert_equal('test://quux.txt', bufferName) + " Test C: + lrewind + lopen + " Let's move the location list window to the top to check whether it (the + " first window found) will be reused when we try to open new windows: + wincmd K + 2 + exe "normal \<CR>" + wincmd p + 3 + exe "normal \<CR>" + wincmd p + 4 + exe "normal \<CR>" + 1wincmd w + call assert_equal('quickfix', &buftype) + 2wincmd w + let bufferName = expand("%") + let bufferName = substitute(bufferName, '\\', '/', 'g') + call assert_equal('test://quux.txt', bufferName) - wincmd n | only + wincmd n | only - augroup! testgroup + augroup! testgroup endfunc func Test_locationlist_curwin_was_closed() - augroup testgroup - au! - autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>")) - augroup END + augroup testgroup + au! + autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>")) + augroup END - func! R(n) - quit - endfunc + func! R(n) + quit + endfunc - new - let q = [] - call add(q, {'filename': 'test_curwin.txt' }) - call setloclist(0, q) - call assert_fails('lrewind', 'E924:') + new + let q = [] + call add(q, {'filename': 'test_curwin.txt' }) + call setloclist(0, q) + call assert_fails('lrewind', 'E924:') - augroup! testgroup + augroup! testgroup + delfunc R endfunc func Test_locationlist_cross_tab_jump() @@ -1037,21 +1038,20 @@ func s:dir_stack_tests(cchar) let save_efm=&efm set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f' - let lines =<< trim [DATA] - Entering dir 'dir1/a' - habits2.txt:1:Nine Healthy Habits - Entering dir 'b' - habits3.txt:2:0 Hours of television - habits2.txt:7:5 Small meals - Entering dir 'dir1/c' - habits4.txt:3:1 Hour of exercise - Leaving dir 'dir1/c' - Leaving dir 'dir1/a' - habits1.txt:4:2 Liters of water - Entering dir 'dir2' - habits5.txt:5:3 Cups of hot green tea - Leaving dir 'dir2 - [DATA] + let lines = ["Entering dir 'dir1/a'", + \ 'habits2.txt:1:Nine Healthy Habits', + \ "Entering dir 'b'", + \ 'habits3.txt:2:0 Hours of television', + \ 'habits2.txt:7:5 Small meals', + \ "Entering dir 'dir1/c'", + \ 'habits4.txt:3:1 Hour of exercise', + \ "Leaving dir 'dir1/c'", + \ "Leaving dir 'dir1/a'", + \ 'habits1.txt:4:2 Liters of water', + \ "Entering dir 'dir2'", + \ 'habits5.txt:5:3 Cups of hot green tea', + \ "Leaving dir 'dir2'" + \] Xexpr "" for l in lines @@ -1085,19 +1085,18 @@ func Test_efm_dirstack() call mkdir('dir1/c') call mkdir('dir2') - let lines =<< trim [DATA] - Nine Healthy Habits, - 0 Hours of television, - 1 Hour of exercise, - 2 Liters of water, - 3 Cups of hot green tea, - 4 Short mental breaks, - 5 Small meals, - 6 AM wake up time, - 7 Minutes of laughter, - 8 Hours of sleep (at least), - 9 PM end of the day and off to bed - [DATA] + let lines = ["Nine Healthy Habits", + \ "0 Hours of television", + \ "1 Hour of exercise", + \ "2 Liters of water", + \ "3 Cups of hot green tea", + \ "4 Short mental breaks", + \ "5 Small meals", + \ "6 AM wake up time", + \ "7 Minutes of laughter", + \ "8 Hours of sleep (at least)", + \ "9 PM end of the day and off to bed" + \ ] call writefile(lines, 'habits1.txt') call writefile(lines, 'dir1/a/habits2.txt') @@ -1219,6 +1218,7 @@ func Test_efm2() (67,3) warning: 's' already defined -- [DATA] + set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r " To exercise the push/pop file functionality in quickfix, the test files " need to be created. @@ -1279,20 +1279,21 @@ func Test_efm2() " Test for %A, %C and other formats let lines =<< trim [DATA] - ============================================================== - FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest) - -------------------------------------------------------------- - Traceback (most recent call last): - File "unittests/dbfacadeTest.py", line 89, in testFoo - self.assertEquals(34, dtid) - File "/usr/lib/python2.2/unittest.py", line 286, in - failUnlessEqual - raise self.failureException, \\ - W:AssertionError: 34 != 33 - - -------------------------------------------------------------- - Ran 27 tests in 0.063s + ============================================================== + FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest) + -------------------------------------------------------------- + Traceback (most recent call last): + File "unittests/dbfacadeTest.py", line 89, in testFoo + self.assertEquals(34, dtid) + File "/usr/lib/python2.2/unittest.py", line 286, in + failUnlessEqual + raise self.failureException, \\ + W:AssertionError: 34 != 33 + + -------------------------------------------------------------- + Ran 27 tests in 0.063s [DATA] + set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%t:%m cgetexpr lines let l = getqflist() @@ -1384,6 +1385,29 @@ func Test_efm_error_type() let &efm = save_efm endfunc +" Test for end_lnum ('%e') and end_col ('%k') fields in 'efm' +func Test_efm_end_lnum_col() + let save_efm = &efm + + " single line + set efm=%f:%l-%e:%c-%k:%t:%m + cexpr ["Xfile1:10-20:1-2:E:msg1", "Xfile1:20-30:2-3:W:msg2",] + let output = split(execute('clist'), "\n") + call assert_equal([ + \ ' 1 Xfile1:10-20 col 1-2 error: msg1', + \ ' 2 Xfile1:20-30 col 2-3 warning: msg2'], output) + + " multiple lines + set efm=%A%n)%m,%Z%f:%l-%e:%c-%k + cexpr ["1)msg1", "Xfile1:14-24:1-2", + \ "2)msg2", "Xfile1:24-34:3-4"] + let output = split(execute('clist'), "\n") + call assert_equal([ + \ ' 1 Xfile1:14-24 col 1-2 error 1: msg1', + \ ' 2 Xfile1:24-34 col 3-4 error 2: msg2'], output) + let &efm = save_efm +endfunc + func XquickfixChangedByAutocmd(cchar) call s:setup_commands(a:cchar) if a:cchar == 'c' @@ -1632,7 +1656,7 @@ func XquickfixSetListWithAct(cchar) \ {'filename': 'fnameD', 'text': 'D'}, \ {'filename': 'fnameE', 'text': 'E'}] - " {action} is unspecified. Same as specifing ' '. + " {action} is unspecified. Same as specifying ' '. new | only silent! Xnewer 99 call g:Xsetlist(list1) @@ -1699,7 +1723,7 @@ endfunc func Test_setqflist_invalid_nr() " The following command used to crash Vim - call setqflist([], ' ', {'nr' : $XXX_DOES_NOT_EXIST}) + eval []->setqflist(' ', {'nr' : $XXX_DOES_NOT_EXIST}) endfunc func Test_setqflist_user_sets_buftype() @@ -1914,6 +1938,7 @@ func Test_switchbuf() " If opening a file changes 'switchbuf', then the new value should be " retained. + set modeline&vim call writefile(["vim: switchbuf=split"], 'Xqftestfile1') enew | only set switchbuf&vim @@ -1996,6 +2021,7 @@ func s:test_xgrep(cchar) enew set makeef=Temp_File_## silent Xgrepadd GrepAdd_Test_Text: test_quickfix.vim + call assert_true(len(g:Xgetlist()) == 9) " Try with 'grepprg' set to 'internal' set grepprg=internal @@ -2004,12 +2030,12 @@ func s:test_xgrep(cchar) call assert_true(len(g:Xgetlist()) == 9) set grepprg&vim - call writefile(['Vim'], 'XtestTempFile') - set makeef=XtestTempFile - silent Xgrep Grep_Test_Text: test_quickfix.vim - call assert_equal(5, len(g:Xgetlist())) - call assert_false(filereadable('XtestTempFile')) - set makeef&vim + call writefile(['Vim'], 'XtestTempFile') + set makeef=XtestTempFile + silent Xgrep Grep_Test_Text: test_quickfix.vim + call assert_equal(5, len(g:Xgetlist())) + call assert_false(filereadable('XtestTempFile')) + set makeef&vim endfunc func Test_grep() @@ -2682,7 +2708,7 @@ func Test_cwindow_jump() " Open a new window and create a location list " Open the location list window and close the other window " Jump to an entry. - " Should create a new window and jump to the entry. The scrtach buffer + " Should create a new window and jump to the entry. The scratch buffer " should not be used. enew | only set buftype=nofile @@ -2715,7 +2741,26 @@ func Test_cwindow_jump() call assert_true(winnr('$') == 2) call assert_true(winnr() == 1) - " Jumping to a file from the location list window should find a usuable + " open the quickfix buffer in two windows and jump to an entry. Should open + " the file in the first quickfix window. + enew | only + copen + let bnum = bufnr('') + exe 'sbuffer ' . bnum + wincmd b + cfirst + call assert_equal(2, winnr()) + call assert_equal('F1', bufname('')) + enew | only + exe 'sb' bnum + exe 'botright sb' bnum + wincmd t + clast + call assert_equal(2, winnr()) + call assert_equal('quickfix', getwinvar(1, '&buftype')) + call assert_equal('quickfix', getwinvar(3, '&buftype')) + + " Jumping to a file from the location list window should find a usable " window by wrapping around the window list. enew | only call setloclist(0, [], 'f') @@ -3524,20 +3569,21 @@ func Xgetlist_empty_tests(cchar) call assert_equal(0, g:Xgetlist({'changedtick' : 0}).changedtick) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, - \ 'items' : [], 'nr' : 0, 'size' : 0, + \ 'items' : [], 'nr' : 0, 'size' : 0, 'qfbufnr' : 0, \ 'title' : '', 'winid' : 0, 'changedtick': 0, \ 'quickfixtextfunc' : ''}, g:Xgetlist({'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, \ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0, - \ 'quickfixtextfunc' : ''}, + \ 'qfbufnr' : 0, 'quickfixtextfunc' : ''}, \ g:Xgetlist({'all' : 0})) endif " Quickfix window with empty stack silent! Xopen let qfwinid = (a:cchar == 'c') ? win_getid() : 0 + let qfbufnr = (a:cchar == 'c') ? bufnr('') : 0 call assert_equal(qfwinid, g:Xgetlist({'winid' : 0}).winid) Xclose @@ -3569,12 +3615,12 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'quickfixtextfunc' : '', + \ 'qfbufnr' : qfbufnr, 'quickfixtextfunc' : '', \ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, 'filewinid' : 0, + \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0, \ 'quickfixtextfunc' : ''}, \ g:Xgetlist({'id' : qfid, 'all' : 0})) endif @@ -3592,12 +3638,12 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, + \ 'changedtick' : 0, 'qfbufnr' : qfbufnr, \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, 'filewinid' : 0, + \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0, \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0})) endif endfunc @@ -4336,7 +4382,7 @@ func Test_splitview() 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. + " window should be opened with a copy of the location list. lhelpgrep window let locid = getloclist(0, {'id' : 0}).id lwindow @@ -4432,11 +4478,19 @@ func Xqfbuf_test(cchar) Xclose " Even after the quickfix window is closed, the buffer should be loaded call assert_true(bufloaded(qfbnum)) + call assert_true(qfbnum, g:Xgetlist({'qfbufnr' : 0}).qfbufnr) Xopen " Buffer should be reused when opening the window again call assert_equal(qfbnum, bufnr('')) Xclose + " When quickfix buffer is wiped out, getqflist() should return 0 + %bw! + Xexpr "" + Xopen + bw! + call assert_equal(0, g:Xgetlist({'qfbufnr': 0}).qfbufnr) + if a:cchar == 'l' %bwipe " For a location list, when both the file window and the location list @@ -4450,7 +4504,7 @@ func Xqfbuf_test(cchar) 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_match(qfbnum . 'u h- "\[Location List]"', execute('ls!')) call assert_true(bufloaded(qfbnum)) " After deleting a location list buffer using ":bdelete", opening the @@ -4467,6 +4521,7 @@ func Xqfbuf_test(cchar) " removed call setloclist(0, [], 'f') call assert_false(bufexists(qfbnum)) + call assert_equal(0, getloclist(0, {'qfbufnr' : 0}).qfbufnr) " 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 @@ -4491,11 +4546,36 @@ func Xqfbuf_test(cchar) endfunc func Test_qfbuf() - throw 'skipped: enable after porting patch 8.1.0877' call Xqfbuf_test('c') call Xqfbuf_test('l') endfunc +" If there is an autocmd to use only one window, then opening the location +" list window used to crash Vim. +func Test_winonly_autocmd() + call s:create_test_file('Xtest1') + " Autocmd to show only one Vim window at a time + autocmd WinEnter * only + new + " Load the location list + lexpr "Xtest1:5:Line5\nXtest1:10:Line10\nXtest1:15:Line15" + let loclistid = getloclist(0, {'id' : 0}).id + " Open the location list window. Only this window will be shown and the file + " window is closed. + lopen + call assert_equal(loclistid, getloclist(0, {'id' : 0}).id) + " Jump to an entry in the location list and make sure that the cursor is + " positioned correctly. + ll 3 + call assert_equal(loclistid, getloclist(0, {'id' : 0}).id) + call assert_equal('Xtest1', bufname('')) + call assert_equal(15, line('.')) + " Cleanup + autocmd! WinEnter + new | only + call delete('Xtest1') +endfunc + " Test to make sure that an empty quickfix buffer is not reused for loading " a normal buffer. func Test_empty_qfbuf() @@ -5027,6 +5107,52 @@ func Test_qfbuf_update() call Xqfbuf_update('l') endfunc +" Test for the :vimgrep 'f' flag (fuzzy match) +func Xvimgrep_fuzzy_match(cchar) + call s:setup_commands(a:cchar) + + Xvimgrep /three one/f Xfile* + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal(['Xfile1', 1, 9, 'one two three'], + \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text]) + call assert_equal(['Xfile2', 2, 1, 'three one two'], + \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text]) + + Xvimgrep /the/f Xfile* + let l = g:Xgetlist() + call assert_equal(3, len(l)) + call assert_equal(['Xfile1', 1, 9, 'one two three'], + \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text]) + call assert_equal(['Xfile2', 2, 1, 'three one two'], + \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text]) + call assert_equal(['Xfile2', 4, 4, 'aaathreeaaa'], + \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text]) + + Xvimgrep /aaa/fg Xfile* + let l = g:Xgetlist() + call assert_equal(4, len(l)) + call assert_equal(['Xfile1', 2, 1, 'aaaaaa'], + \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text]) + call assert_equal(['Xfile1', 2, 4, 'aaaaaa'], + \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text]) + call assert_equal(['Xfile2', 4, 1, 'aaathreeaaa'], + \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text]) + call assert_equal(['Xfile2', 4, 9, 'aaathreeaaa'], + \ [bufname(l[3].bufnr), l[3].lnum, l[3].col, l[3].text]) + + call assert_fails('Xvimgrep /xyz/fg Xfile*', 'E480:') +endfunc + +func Test_vimgrep_fuzzy_match() + call writefile(['one two three', 'aaaaaa'], 'Xfile1') + call writefile(['one', 'three one two', 'two', 'aaathreeaaa'], 'Xfile2') + call Xvimgrep_fuzzy_match('c') + call Xvimgrep_fuzzy_match('l') + call delete('Xfile1') + call delete('Xfile2') +endfunc + " Test for getting a specific item from a quickfix list func Xtest_getqflist_by_idx(cchar) call s:setup_commands(a:cchar) @@ -5137,16 +5263,14 @@ func Xtest_qftextfunc(cchar) " Non-existing function set quickfixtextfunc=Tabc - " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:') - Xexpr ['F1:10:2:green', 'F1:20:4:blue']" + call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:') call assert_fails("Xwindow", 'E117:') Xclose set quickfixtextfunc& " set option to a non-function set quickfixtextfunc=[10,\ 20] - " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:') - Xexpr ['F1:10:2:green', 'F1:20:4:blue']" + call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:') call assert_fails("Xwindow", 'E117:') Xclose set quickfixtextfunc& @@ -5156,8 +5280,7 @@ func Xtest_qftextfunc(cchar) return a:a .. a:b .. a:c endfunc set quickfixtextfunc=Xqftext - " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E119:') - Xexpr ['F1:10:2:green', 'F1:20:4:blue']" + call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E119:') call assert_fails("Xwindow", 'E119:') Xclose @@ -5166,9 +5289,8 @@ func Xtest_qftextfunc(cchar) return ['one', [], 'two'] endfunc set quickfixtextfunc=Xqftext2 - " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']", - " \ 'E730:') - Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red'] + call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']", + \ 'E730:') call assert_fails('Xwindow', 'E730:') call assert_equal(['one', 'F1|20 col 4| blue', 'F1|30 col 6| red'], \ getline(1, '$')) @@ -5332,4 +5454,81 @@ func Test_win_gettype() lclose endfunc +" Test for opening the quickfix window in two tab pages and then closing one +" of the quickfix windows. This should not make the quickfix buffer unlisted. +" (github issue #9300). +func Test_two_qf_windows() + cexpr "F1:1:line1" + copen + tabnew + copen + call assert_true(&buflisted) + cclose + tabfirst + call assert_true(&buflisted) + let bnum = bufnr() + cclose + " if all the quickfix windows are closed, then buffer should be unlisted. + call assert_false(buflisted(bnum)) + %bw! + + " Repeat the test for a location list + lexpr "F2:2:line2" + lopen + let bnum = bufnr() + tabnew + exe "buffer" bnum + tabfirst + lclose + tablast + call assert_true(buflisted(bnum)) + tabclose + lopen + call assert_true(buflisted(bnum)) + lclose + call assert_false(buflisted(bnum)) + %bw! +endfunc + +" Weird sequence of commands that caused entering a wiped-out buffer +func Test_lopen_bwipe() + func R() + silent! tab lopen + e x + silent! lfile + endfunc + + cal R() + cal R() + cal R() + bw! + delfunc R +endfunc + +" Another sequence of commands that caused all buffers to be wiped out +func Test_lopen_bwipe_all() + let lines =<< trim END + func R() + silent! tab lopen + e foo + silent! lfile + endfunc + cal R() + exe "norm \<C-W>\<C-V>0" + cal R() + bwipe + + call writefile(['done'], 'Xresult') + qall! + END + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -n -S Xscript') + call assert_equal(['done'], readfile('Xresult')) + endif + + call delete('Xscript') + call delete('Xresult') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_random.vim b/src/nvim/testdir/test_random.vim new file mode 100644 index 0000000000..6d3f7dcfd9 --- /dev/null +++ b/src/nvim/testdir/test_random.vim @@ -0,0 +1,51 @@ +" Tests for srand() and rand() + +func Test_Rand() + let r = srand(123456789) + call assert_equal([1573771921, 319883699, 2742014374, 1324369493], r) + call assert_equal(4284103975, rand(r)) + call assert_equal(1001954530, rand(r)) + call assert_equal(2701803082, rand(r)) + call assert_equal(2658065534, rand(r)) + call assert_equal(3104308804, rand(r)) + + " Nvim does not support test_settime + " call test_settime(12341234) + let s = srand() + if !has('win32') && filereadable('/dev/urandom') + " using /dev/urandom + call assert_notequal(s, srand()) + " else + " " using time() + " call assert_equal(s, srand()) + " call test_settime(12341235) + " call assert_notequal(s, srand()) + endif + + " Nvim does not support test_srand_seed + " call test_srand_seed(123456789) + " call assert_equal(4284103975, rand()) + " call assert_equal(1001954530, rand()) + " call test_srand_seed() + + if has('float') + call assert_fails('echo srand(1.2)', 'E805:') + endif + call assert_fails('echo srand([1])', 'E745:') + call assert_fails('echo rand("burp")', 'E475:') + call assert_fails('echo rand([1, 2, 3])', 'E475:') + call assert_fails('echo rand([[1], 2, 3, 4])', 'E475:') + call assert_fails('echo rand([1, [2], 3, 4])', 'E475:') + call assert_fails('echo rand([1, 2, [3], 4])', 'E475:') + call assert_fails('echo rand([1, 2, 3, [4]])', 'E475:') + + " call test_settime(0) +endfunc + +func Test_issue_5587() + call rand() + call garbagecollect() + call rand() +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_regex_char_classes.vim b/src/nvim/testdir/test_regex_char_classes.vim index c1a4202c2b..b0d76a15e2 100644 --- a/src/nvim/testdir/test_regex_char_classes.vim +++ b/src/nvim/testdir/test_regex_char_classes.vim @@ -66,22 +66,22 @@ func Test_regex_char_classes() let save_enc = &encoding set encoding=utf-8 - let input = "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé" + let input = "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé" " Format is [cmd_to_run, expected_output] let tests = [ \ [':s/\%#=0\d//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1\d//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2\d//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[0-9]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1[0-9]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2[0-9]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0\D//g', \ "0123456789"], \ [':s/\%#=1\D//g', @@ -95,17 +95,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^0-9]//g', \ "0123456789"], \ [':s/\%#=0\o//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1\o//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2\o//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[0-7]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1[0-7]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2[0-7]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0\O//g', \ "01234567"], \ [':s/\%#=1\O//g', @@ -119,17 +119,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^0-7]//g', \ "01234567"], \ [':s/\%#=0\x//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1\x//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2\x//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[0-9A-Fa-f]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1[0-9A-Fa-f]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2[0-9A-Fa-f]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0\X//g', \ "0123456789ABCDEFabcdef"], \ [':s/\%#=1\X//g', @@ -143,17 +143,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^0-9A-Fa-f]//g', \ "0123456789ABCDEFabcdef"], \ [':s/\%#=0\w//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1\w//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2\w//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[0-9A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1[0-9A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2[0-9A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0\W//g', \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], \ [':s/\%#=1\W//g', @@ -167,17 +167,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^0-9A-Za-z_]//g', \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], \ [':s/\%#=0\h//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1\h//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2\h//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1[A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2[A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0\H//g', \ "ABCDEFGHIXYZ_abcdefghiwxyz"], \ [':s/\%#=1\H//g', @@ -191,17 +191,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^A-Za-z_]//g', \ "ABCDEFGHIXYZ_abcdefghiwxyz"], \ [':s/\%#=0\a//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1\a//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2\a//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[A-Za-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1[A-Za-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2[A-Za-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0\A//g', \ "ABCDEFGHIXYZabcdefghiwxyz"], \ [':s/\%#=1\A//g', @@ -215,17 +215,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^A-Za-z]//g', \ "ABCDEFGHIXYZabcdefghiwxyz"], \ [':s/\%#=0\l//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1\l//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2\l//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[a-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1[a-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2[a-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0\L//g', \ "abcdefghiwxyz"], \ [':s/\%#=1\L//g', @@ -239,17 +239,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^a-z]//g', \ "abcdefghiwxyz"], \ [':s/\%#=0\u//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1\u//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2\u//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[A-Z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1[A-Z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2[A-Z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0\U//g', \ "ABCDEFGHIXYZ"], \ [':s/\%#=1\U//g', @@ -269,11 +269,11 @@ func Test_regex_char_classes() \ [':s/\%#=2\%' . line('.') . 'l^\t...//g', \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[0-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=1[0-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=2[0-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], \ [':s/\%#=0[^0-z]//g', \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"], \ [':s/\%#=1[^0-z]//g', diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index 712f1e6025..13e44b090f 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -6,7 +6,10 @@ scriptencoding latin1 source check.vim func s:equivalence_test() - let str = "AÀÃÂÃÄÅ B C D EÈÉÊË F G H IÃŒÃÃŽÃ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X Yà Z aà áâãäå b c d eèéêë f g h iìÃîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z" + let str = 'AÀÁÂÃÄÅ B C D EÈÉÊË F G H IÌÍÎÏ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X YÝ Z ' + \ .. 'aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z ' + \ .. "0 1 2 3 4 5 6 7 8 9 " + \ .. "` ~ ! ? ; : . , / \\ ' \" | < > [ ] { } ( ) @ # $ % ^ & * _ - + \b \e \f \n \r \t" let groups = split(str) for group1 in groups for c in split(group1, '\zs') @@ -337,7 +340,7 @@ func Test_regexp_single_line_pat() call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab']) call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', '']) call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', '']) - call add(tl, [2, '\p*', 'aá ', 'aá ']) + call add(tl, [2, '\p*', 'aá ', 'aá ']) " Test greedy-ness and lazy-ness call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa']) @@ -787,4 +790,125 @@ func Test_regexp_error() set re& endfunc +" Check patterns matching cursor position. +func s:curpos_test2() + new + call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse', + \ '3 foobar eins zwei drei vier fünf sechse', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 foobar eins zwei drei vier fünf sechse', + \ '6 foobar eins zwei drei vier fünf sechse', + \ '7 foobar eins zwei drei vier fünf sechse']) + call setpos('.', [0, 2, 10, 0]) + s/\%.c.*//g + call setpos('.', [0, 3, 15, 0]) + s/\%.l.*//g + call setpos('.', [0, 5, 3, 0]) + s/\%.v.*/_/g + call assert_equal(['1', + \ '2 foobar ', + \ '', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 _', + \ '6 foobar eins zwei drei vier fünf sechse', + \ '7 foobar eins zwei drei vier fünf sechse'], + \ getline(1, '$')) + call assert_fails('call search("\\%.1l")', 'E1204:') + call assert_fails('call search("\\%.1c")', 'E1204:') + call assert_fails('call search("\\%.1v")', 'E1204:') + bwipe! +endfunc + +" Check patterns matching before or after cursor position. +func s:curpos_test3() + new + call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse', + \ '3 foobar eins zwei drei vier fünf sechse', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 foobar eins zwei drei vier fünf sechse', + \ '6 foobar eins zwei drei vier fünf sechse', + \ '7 foobar eins zwei drei vier fünf sechse']) + call setpos('.', [0, 2, 10, 0]) + " Note: This removes all columns, except for the column directly in front of + " the cursor. Bug???? + :s/^.*\%<.c// + call setpos('.', [0, 3, 10, 0]) + :s/\%>.c.*$// + call setpos('.', [0, 5, 4, 0]) + " Note: This removes all columns, except for the column directly in front of + " the cursor. Bug???? + :s/^.*\%<.v/_/ + call setpos('.', [0, 6, 4, 0]) + :s/\%>.v.*$/_/ + call assert_equal(['1', + \ ' eins zwei drei vier fünf sechse', + \ '3 foobar e', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '_foobar eins zwei drei vier fünf sechse', + \ '6 fo_', + \ '7 foobar eins zwei drei vier fünf sechse'], + \ getline(1, '$')) + sil %d + call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse', + \ '3 foobar eins zwei drei vier fünf sechse', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 foobar eins zwei drei vier fünf sechse', + \ '6 foobar eins zwei drei vier fünf sechse', + \ '7 foobar eins zwei drei vier fünf sechse']) + call setpos('.', [0, 4, 4, 0]) + %s/\%<.l.*// + call setpos('.', [0, 5, 4, 0]) + %s/\%>.l.*// + call assert_equal(['', '', '', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 foobar eins zwei drei vier fünf sechse', + \ '', ''], + \ getline(1, '$')) + bwipe! +endfunc + +" Test that matching below, at or after the +" cursor position work +func Test_matching_pos() + for val in range(3) + exe "set re=" .. val + " Match at cursor position + call s:curpos_test2() + " Match before or after cursor position + call s:curpos_test3() + endfor + set re& +endfunc + +func Test_using_mark_position() + " this was using freed memory + " new engine + new + norm O0 + call assert_fails("s/\\%')", 'E486:') + bwipe! + + " old engine + new + norm O0 + call assert_fails("s/\\%#=1\\%')", 'E486:') + bwipe! +endfunc + +func Test_using_visual_position() + " this was using freed memory + new + exe "norm 0o\<Esc>\<C-V>k\<C-X>o0" + /\%V + bwipe! +endfunc + +func Test_using_invalid_visual_position() + " this was going beyond the end of the line + new + exe "norm 0o000\<Esc>0\<C-V>$s0" + /\%V + bwipe! +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 d8d5797dcf..191cd948ac 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -1,21 +1,21 @@ " Tests for regexp in utf8 encoding func s:equivalence_test() - let str = "AÀÃÂÃÄÅĀĂĄÇÇžÇ áº¢ BḂḆ CÇĆĈĊČ DÄŽÄḊḎḠEÈÉÊËĒĔĖĘĚẺẼ FḞ GÄœÄžÄ Ä¢Ç¤Ç¦Ç´á¸ HĤĦḢḦḨ IÃŒÃÃŽÃĨĪĬĮİÇỈ JÄ´ KĶǨḰḴ LĹĻĽĿÅḺ MḾṀ NÑŃŅŇṄṈ OÃ’Ã“Ã”Ã•Ã–Ã˜ÅŒÅŽÅÆ ǑǪǬỎ PṔṖ Q RŔŖŘṘṞ SŚŜŞŠṠTŢŤŦṪṮ UÙÚÛÜŨŪŬŮŰŲƯǓỦ Vá¹¼ WŴẀẂẄẆ XẊẌ YÃŶŸẎỲỶỸ ZŹŻŽƵáºáº” aà áâãäåÄăąǎǟǡả bḃḇ cÃ§Ä‡Ä‰Ä‹Ä dÄđḋá¸á¸‘ eèéêëēĕėęěẻẽ fḟ gÄğġģǥǧǵḡ hĥħḣḧḩẖ iìÃîïĩīÄįÇỉ jĵǰ kķǩḱḵ lĺļľŀłḻ mḿṠnñńņňʼnṅṉ oòóôõöøÅÅőơǒǫÇá» pṕṗ q rŕŗřṙṟ sÅ›Åşšṡ tţťŧṫṯẗ uùúûüũūÅůűųưǔủ vá¹½ wŵáºáºƒáº…ẇẘ xẋẠyýÿŷáºáº™á»³á»·á»¹ zźżžƶẑẕ" + let str = "AÀÃÂÃÄÅĀĂĄÇÇžÇ ÇºÈ‚È¦Èºá¸€áº áº¢áº¤áº¦áº¨áºªáº¬áº®áº°áº²áº´áº¶ BÆÉƒá¸‚ḄḆ CÇĆĈĊČƇȻḈꞒ DÄŽÄÆŠá¸Šá¸Œá¸Žá¸á¸’ EÈÉÊËĒĔĖĘĚȄȆȨɆḔḖḘḚḜẸẺẼẾỀỂỄỆ FƑḞꞘ GÄœÄžÄ Ä¢Æ“Ç¤Ç¦Ç´á¸ êž HĤĦȞḢḤḦḨḪⱧ IÃŒÃÃŽÃĨĪĬĮİƗÇȈȊḬḮỈỊ JĴɈ KÄ¶Æ˜Ç¨á¸°á¸²á¸´â±©ê€ LĹĻĽĿÅȽḶḸḺḼⱠMḾṀṂ NÑŃŅŇǸṄṆṈṊꞤ OÃ’Ã“Ã”Ã•Ã–Ã˜ÅŒÅŽÅÆŸÆ ǑǪǬǾȌȎȪȬȮȰṌṎá¹á¹’ỌỎá»á»’ỔỖỘỚỜỞỠỢ PƤṔṖⱣ QÉŠ RŔŖŘÈȒɌṘṚṜṞⱤꞦ SŚŜŞŠȘṠṢṤṦṨⱾꞨ TŢŤŦƬƮȚȾṪṬṮṰ UÙÚÛÜŨŪŬŮŰƯǕǙǛǓǗȔȖɄṲṴṶṸṺỤỦỨỪỬỮỰ VƲṼṾ WŴẀẂẄẆẈ XẊẌ YÃŶŸƳȲɎẎỲỴỶỸ ZŹŻŽƵáºáº’ẔⱫ aà áâãäåÄăąǎǟǡǻȃȧá¶á¸áºšáº¡áº£áº¥áº§áº©áº«áºáº¯áº±áº³áºµáº·â±¥ bƀɓᵬᶀḃḅḇ cÃ§Ä‡Ä‰Ä‹ÄÆˆÈ¼á¸‰êž“êž” dÄđɗáµá¶á¶‘ḋá¸á¸á¸‘ḓ eèéêëēĕėęěȅȇȩɇᶒḕḗḙḛá¸áº¹áº»áº½áº¿á»á»ƒá»…ệ fƒᵮᶂḟꞙ gÄÄŸÄ¡Ä£Ç¥Ç§ÇµÉ á¶ƒá¸¡êž¡ hĥħȟḣḥḧḩḫẖⱨꞕ iìÃîïĩīÄįÇȉȋɨᶖá¸á¸¯á»‰á»‹ jĵǰɉ kķƙǩᶄḱḳḵⱪê lĺļľŀłƚḷḹḻḽⱡ mᵯḿá¹á¹ƒ nñńņňʼnǹᵰᶇṅṇṉṋꞥ oòóôõöøÅÅőơǒǫÇÇ¿ÈÈÈ«Èȯȱɵá¹á¹á¹‘ṓá»á»á»‘ồổỗộớá»á»Ÿá»¡á»£ pƥᵱᵽᶈṕṗ qÉ‹Ê rŕŗřȑȓÉɽᵲᵳᶉṛá¹á¹Ÿêž§ sÅ›Åşšșȿᵴᶊṡṣṥṧṩꞩ tţťŧƫÆÈ›Êˆáµµá¹«á¹á¹¯á¹±áº—ⱦ uùúûüũūÅůűųǚǖưǔǘǜȕȗʉᵾᶙṳṵṷṹṻụủứừá»á»¯á»± vʋᶌṽṿ wŵáºáºƒáº…ẇẉẘ xẋẠyýÿŷƴȳÉáºáº™á»³á»µá»·á»¹ zźżžƶᵶᶎẑẓẕⱬ" let groups = split(str) for group1 in groups - for c in split(group1, '\zs') - " next statement confirms that equivalence class matches every - " character in group - call assert_match('^[[=' . c . '=]]*$', group1) - for group2 in groups - if group2 != group1 - " next statement converts that equivalence class doesn't match - " character in any other group - call assert_equal(-1, match(group2, '[[=' . c . '=]]')) - endif + for c in split(group1, '\zs') + " next statement confirms that equivalence class matches every + " character in group + call assert_match('^[[=' .. c .. '=]]*$', group1) + for group2 in groups + if group2 != group1 + " next statement converts that equivalence class doesn't match + " character in any other group + call assert_equal(-1, match(group2, '[[=' .. c .. '=]]'), c) + endif + endfor endfor - endfor endfor endfunc @@ -152,9 +152,6 @@ func s:classes_test() 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µÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖרÙÚÛÜÃÞßà áâãäåæçèéêëìÃîïðñòóôõö÷øùúûüýþÿ' @@ -166,8 +163,6 @@ func s:classes_test() 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 @@ -228,7 +223,7 @@ endfunc func Test_reversed_range() for re in range(0, 2) exe 'set re=' . re - call assert_fails('call match("abc def", "[c-a]")', 'E944:') + call assert_fails('call match("abc def", "[c-a]")', 'E944:', re) endfor set re=0 endfunc @@ -545,7 +540,6 @@ endfunc " Check that [[:upper:]] matches for automatic engine func Test_match_char_class_upper() new - let _engine=®expengine " Test 1: [[:upper:]]\{2,\} set regexpengine=0 @@ -586,8 +580,25 @@ func Test_match_char_class_upper() call assert_equal(4, searchcount().total, 'TEST 3 lower') " clean up - let ®expengine=_engine + set regexpengine=0 + bwipe! +endfunc + +func Test_match_invalid_byte() + call writefile(0z630a.765d30aa0a.2e0a.790a.4030, 'Xinvalid') + new + source Xinvalid bwipe! + call delete('Xinvalid') endfunc +func Test_match_too_complicated() + set regexpengine=1 + exe "noswapfile vsplit \xeb\xdb\x99" + silent! buf \&\zs*\zs*0 + bwipe! + set regexpengine=0 +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 84a5aca3d5..b852cfd22f 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -1,6 +1,4 @@ -" " Tests for register operations -" source check.vim source view_util.vim @@ -13,6 +11,8 @@ func Test_aaa_empty_reg_test() call assert_fails('normal @!', 'E354:') call assert_fails('normal @:', 'E30:') call assert_fails('normal @.', 'E29:') + call assert_fails('put /', 'E35:') + call assert_fails('put .', 'E29:') endfunc func Test_yank_shows_register() @@ -62,7 +62,6 @@ func Test_display_registers() call assert_match('^\nType Name Content\n' \ . ' c "" a\n' \ . ' c "0 ba\n' - \ . ' c "1 b\n' \ . ' c "a b\n' \ . '.*' \ . ' c "- a\n' @@ -85,6 +84,90 @@ func Test_display_registers() let g:clipboard = save_clipboard endfunc +func Test_register_one() + " delete a line goes into register one + new + call setline(1, "one") + normal dd + call assert_equal("one\n", @1) + + " delete a word does not change register one, does change "- + call setline(1, "two") + normal de + call assert_equal("one\n", @1) + call assert_equal("two", @-) + + " delete a word with a register does not change register one + call setline(1, "three") + normal "ade + call assert_equal("three", @a) + call assert_equal("one\n", @1) + + " delete a word with register DOES change register one with one of a list of + " operators + " % + call setline(1, ["(12)3"]) + normal "ad% + call assert_equal("(12)", @a) + call assert_equal("(12)", @1) + + " ( + call setline(1, ["first second"]) + normal $"ad( + call assert_equal("first secon", @a) + call assert_equal("first secon", @1) + + " ) + call setline(1, ["First Second."]) + normal gg0"ad) + call assert_equal("First Second.", @a) + call assert_equal("First Second.", @1) + + " ` + call setline(1, ["start here."]) + normal gg0fhmx0"ad`x + call assert_equal("start ", @a) + call assert_equal("start ", @1) + + " / + call setline(1, ["searchX"]) + exe "normal gg0\"ad/X\<CR>" + call assert_equal("search", @a) + call assert_equal("search", @1) + + " ? + call setline(1, ["Ysearch"]) + exe "normal gg$\"ad?Y\<CR>" + call assert_equal("Ysearc", @a) + call assert_equal("Ysearc", @1) + + " n + call setline(1, ["Ynext"]) + normal gg$"adn + call assert_equal("Ynex", @a) + call assert_equal("Ynex", @1) + + " N + call setline(1, ["prevY"]) + normal gg0"adN + call assert_equal("prev", @a) + call assert_equal("prev", @1) + + " } + call setline(1, ["one", ""]) + normal gg0"ad} + call assert_equal("one\n", @a) + call assert_equal("one\n", @1) + + " { + call setline(1, ["", "two"]) + normal 2G$"ad{ + call assert_equal("\ntw", @a) + call assert_equal("\ntw", @1) + + bwipe! +endfunc + func Test_recording_status_in_ex_line() norm qx redraw! @@ -119,6 +202,17 @@ func Test_recording_esc_sequence() endif endfunc +func Test_recording_with_select_mode() + new + call feedkeys("qacc12345\<Esc>gH98765\<Esc>q", "tx") + call assert_equal("98765", getline(1)) + call assert_equal("cc12345\<Esc>gH98765\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("98765", getline(1)) + bwipe! +endfunc + " Test for executing the last used register (@) func Test_last_used_exec_reg() " Test for the @: command @@ -141,6 +235,14 @@ func Test_last_used_exec_reg() normal @@ call assert_equal('EditEdit', a) + " Test for repeating the last command-line in visual mode + call append(0, 'register') + normal gg + let @r = '' + call feedkeys("v:yank R\<CR>", 'xt') + call feedkeys("v@:", 'xt') + call assert_equal("\nregister\nregister\n", @r) + enew! endfunc @@ -164,6 +266,19 @@ func Test_get_register() call assert_equal('', getregtype('!')) + " Test for inserting an invalid register content + call assert_beeps('exe "normal i\<C-R>!"') + + " Test for inserting a register with multiple lines + call deletebufline('', 1, '$') + call setreg('r', ['a', 'b']) + exe "normal i\<C-R>r" + call assert_equal(['a', 'b', ''], getline(1, '$')) + + " Test for inserting a multi-line register in the command line + call feedkeys(":\<C-R>r\<Esc>", 'xt') + call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137 + enew! endfunc @@ -187,9 +302,200 @@ func Test_set_register() call setreg('=', 'b', 'a') call assert_equal('regwrite', getreg('=')) + " Test for setting a list of lines to special registers + call setreg('/', []) + call assert_equal('', @/) + call setreg('=', []) + call assert_equal('', @=) + call assert_fails("call setreg('/', ['a', 'b'])", 'E883:') + call assert_fails("call setreg('=', ['a', 'b'])", 'E883:') + call assert_equal(0, setreg('_', ['a', 'b'])) + + " Test for recording to a invalid register + call assert_beeps('normal q$') + + " Appending to a register when recording + call append(0, "text for clipboard test") + normal gg + call feedkeys('qrllq', 'xt') + call feedkeys('qRhhq', 'xt') + call assert_equal('llhh', getreg('r')) + + " Appending a list of characters to a register from different lines + let @r = '' + call append(0, ['abcdef', '123456']) + normal gg"ry3l + call cursor(2, 4) + normal "Ry3l + call assert_equal('abc456', @r) + + " Test for gP with multiple lines selected using characterwise motion + %delete + call append(0, ['vim editor', 'vim editor']) + let @r = '' + exe "normal ggwy/vim /e\<CR>gP" + call assert_equal(['vim editor', 'vim editor', 'vim editor'], getline(1, 3)) + + " Test for gP with . register + %delete + normal iabc + normal ".gp + call assert_equal('abcabc', getline(1)) + normal 0".gP + call assert_equal('abcabcabc', getline(1)) + enew! endfunc +" Test for clipboard registers (* and +) +func Test_clipboard_regs() + throw 'skipped: needs clipboard=autoselect,autoselectplus' + + CheckNotGui + CheckFeature clipboard_working + + new + call append(0, "text for clipboard test") + normal gg"*yiw + call assert_equal('text', getreg('*')) + normal gg2w"+yiw + call assert_equal('clipboard', getreg('+')) + + " Test for replacing the clipboard register contents + set clipboard=unnamed + let @* = 'food' + normal ggviw"*p + call assert_equal('text', getreg('*')) + call assert_equal('food for clipboard test', getline(1)) + normal ggviw"*p + call assert_equal('food', getreg('*')) + call assert_equal('text for clipboard test', getline(1)) + + " Test for replacing the selection register contents + set clipboard=unnamedplus + let @+ = 'food' + normal ggviw"+p + call assert_equal('text', getreg('+')) + call assert_equal('food for clipboard test', getline(1)) + normal ggviw"+p + call assert_equal('food', getreg('+')) + call assert_equal('text for clipboard test', getline(1)) + + " Test for auto copying visually selected text to clipboard register + call setline(1, "text for clipboard test") + let @* = '' + set clipboard=autoselect + normal ggwwviwy + call assert_equal('clipboard', @*) + + " Test for auto copying visually selected text to selection register + let @+ = '' + set clipboard=autoselectplus + normal ggwviwy + call assert_equal('for', @+) + + set clipboard&vim + bwipe! +endfunc + +" Test for restarting the current mode (insert or virtual replace) after +" executing the contents of a register +func Test_put_reg_restart_mode() + new + call append(0, 'editor') + normal gg + let @r = "ivim \<Esc>" + call feedkeys("i\<C-O>@r\<C-R>=mode()\<CR>", 'xt') + call assert_equal('vimi editor', getline(1)) + + call setline(1, 'editor') + normal gg + call feedkeys("gR\<C-O>@r\<C-R>=mode()\<CR>", 'xt') + call assert_equal('vimReditor', getline(1)) + + bwipe! +endfunc + +" Test for getting register info +func Test_get_reginfo() + enew + call setline(1, ['foo', 'bar']) + + exe 'norm! "zyy' + let info = getreginfo('"') + call assert_equal('z', info.points_to) + call setreg('y', 'baz') + call assert_equal('z', getreginfo('').points_to) + call setreg('y', { 'isunnamed': v:true }) + call assert_equal('y', getreginfo('"').points_to) + + exe '$put' + call assert_equal(getreg('y'), getline(3)) + call setreg('', 'qux') + call assert_equal('0', getreginfo('').points_to) + call setreg('x', 'quux') + call assert_equal('0', getreginfo('').points_to) + + let info = getreginfo('') + call assert_equal(getreg('', 1, 1), info.regcontents) + call assert_equal(getregtype(''), info.regtype) + + exe "norm! 0\<c-v>e" .. '"zy' + let info = getreginfo('z') + call assert_equal(getreg('z', 1, 1), info.regcontents) + call assert_equal(getregtype('z'), info.regtype) + call assert_equal(1, +info.isunnamed) + + let info = getreginfo('"') + call assert_equal('z', info.points_to) + + bwipe! +endfunc + +" Test for restoring register with dict from getreginfo +func Test_set_register_dict() + enew! + + call setreg('"', #{ regcontents: ['one', 'two'], + \ regtype: 'V', points_to: 'z' }) + call assert_equal(['one', 'two'], getreg('"', 1, 1)) + let info = getreginfo('"') + call assert_equal('z', info.points_to) + call assert_equal('V', info.regtype) + call assert_equal(1, +getreginfo('z').isunnamed) + + call setreg('x', #{ regcontents: ['three', 'four'], + \ regtype: 'v', isunnamed: v:true }) + call assert_equal(['three', 'four'], getreg('"', 1, 1)) + let info = getreginfo('"') + call assert_equal('x', info.points_to) + call assert_equal('v', info.regtype) + call assert_equal(1, +getreginfo('x').isunnamed) + + call setreg('y', #{ regcontents: 'five', + \ regtype: "\<c-v>", isunnamed: v:false }) + call assert_equal("\<c-v>4", getreginfo('y').regtype) + call assert_equal(0, +getreginfo('y').isunnamed) + call assert_equal(['three', 'four'], getreg('"', 1, 1)) + call assert_equal('x', getreginfo('"').points_to) + + call setreg('"', #{ regcontents: 'six' }) + call assert_equal('0', getreginfo('"').points_to) + call assert_equal(1, +getreginfo('0').isunnamed) + call assert_equal(['six'], getreginfo('0').regcontents) + call assert_equal(['six'], getreginfo('"').regcontents) + + let @x = 'one' + call setreg('x', {}) + call assert_equal(1, len(split(execute('reg x'), '\n'))) + + call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:') + call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:') + call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:') + + bwipe! +endfunc + func Test_v_register() enew call setline(1, 'nothing') @@ -259,6 +565,82 @@ func Test_v_register() bwipe! endfunc +" Test for executing the contents of a register as an Ex command with line +" continuation. +func Test_execute_reg_as_ex_cmd() + " Line continuation with just two lines + let code =<< trim END + let l = [ + \ 1] + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1], l) + + " Line continuation with more than two lines + let code =<< trim END + let l = [ + \ 1, + \ 2, + \ 3] + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1, 2, 3], l) + + " use comments interspersed with code + let code =<< trim END + let l = [ + "\ one + \ 1, + "\ two + \ 2, + "\ three + \ 3] + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1, 2, 3], l) + + " use line continuation in the middle + let code =<< trim END + let a = "one" + let l = [ + \ 1, + \ 2] + let b = "two" + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1, 2], l) + call assert_equal("one", a) + call assert_equal("two", b) + + " only one line with a \ + let @r = "\\let l = 1" + call assert_fails('@r', 'E10:') + + " only one line with a "\ + let @r = ' "\ let i = 1' + @r + call assert_false(exists('i')) + + " first line also begins with a \ + let @r = "\\let l = [\n\\ 1]" + call assert_fails('@r', 'E10:') + + " Test with a large number of lines + let @r = "let str = \n" + let @r ..= repeat(" \\ 'abcdefghijklmnopqrstuvwxyz' ..\n", 312) + let @r ..= ' \ ""' + @r + call assert_equal(repeat('abcdefghijklmnopqrstuvwxyz', 312), str) +endfunc + func Test_ve_blockpaste() new set ve=all @@ -288,84 +670,64 @@ func Test_insert_small_delete() bwipe! endfunc -" Test for getting register info -func Test_get_reginfo() - enew - call setline(1, ['foo', 'bar']) - - exe 'norm! "zyy' - let info = getreginfo('"') - call assert_equal('z', info.points_to) - call setreg('y', 'baz') - call assert_equal('z', getreginfo('').points_to) - call setreg('y', { 'isunnamed': v:true }) - call assert_equal('y', getreginfo('"').points_to) - - exe '$put' - call assert_equal(getreg('y'), getline(3)) - call setreg('', 'qux') - call assert_equal('0', getreginfo('').points_to) - call setreg('x', 'quux') - call assert_equal('0', getreginfo('').points_to) - - let info = getreginfo('') - call assert_equal(getreg('', 1, 1), info.regcontents) - call assert_equal(getregtype(''), info.regtype) - - exe "norm! 0\<c-v>e" .. '"zy' - let info = getreginfo('z') - call assert_equal(getreg('z', 1, 1), info.regcontents) - call assert_equal(getregtype('z'), info.regtype) - call assert_equal(1, +info.isunnamed) - - let info = getreginfo('"') - call assert_equal('z', info.points_to) +func Test_record_in_select_mode() + new + call setline(1, 'text') + sil norm q00 + sil norm q + call assert_equal('0ext', getline(1)) + + %delete + let @r = '' + call setline(1, ['abc', 'abc', 'abc']) + smap <F2> <Right><Right>, + call feedkeys("qrgh\<F2>Dk\<Esc>q", 'xt') + call assert_equal("gh\<F2>Dk\<Esc>", @r) + norm j0@rj0@@ + call assert_equal([',Dk', ',Dk', ',Dk'], getline(1, 3)) + sunmap <F2> bwipe! endfunc -" Test for restoring register with dict from getreginfo -func Test_set_register_dict() - enew! +" mapping that ends macro recording should be removed from recorded macro +func Test_end_record_using_mapping() + call setline(1, 'aaa') + nnoremap s q + call feedkeys('safas', 'tx') + call assert_equal('fa', @a) + nunmap s - call setreg('"', #{ regcontents: ['one', 'two'], - \ regtype: 'V', points_to: 'z' }) - call assert_equal(['one', 'two'], getreg('"', 1, 1)) - let info = getreginfo('"') - call assert_equal('z', info.points_to) - call assert_equal('V', info.regtype) - call assert_equal(1, +getreginfo('z').isunnamed) + nnoremap xx q + call feedkeys('0xxafaxx', 'tx') + call assert_equal('fa', @a) + nunmap xx - call setreg('x', #{ regcontents: ['three', 'four'], - \ regtype: 'v', isunnamed: v:true }) - call assert_equal(['three', 'four'], getreg('"', 1, 1)) - let info = getreginfo('"') - call assert_equal('x', info.points_to) - call assert_equal('v', info.regtype) - call assert_equal(1, +getreginfo('x').isunnamed) + nnoremap xsx q + call feedkeys('0qafaxsx', 'tx') + call assert_equal('fa', @a) + nunmap xsx - call setreg('y', #{ regcontents: 'five', - \ regtype: "\<c-v>", isunnamed: v:false }) - call assert_equal("\<c-v>4", getreginfo('y').regtype) - call assert_equal(0, +getreginfo('y').isunnamed) - call assert_equal(['three', 'four'], getreg('"', 1, 1)) - call assert_equal('x', getreginfo('"').points_to) - - call setreg('"', #{ regcontents: 'six' }) - call assert_equal('0', getreginfo('"').points_to) - call assert_equal(1, +getreginfo('0').isunnamed) - call assert_equal(['six'], getreginfo('0').regcontents) - call assert_equal(['six'], getreginfo('"').regcontents) - - let @x = 'one' - call setreg('x', {}) - call assert_equal(1, len(split(execute('reg x'), '\n'))) - - call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:') - call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:') - call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:') + bwipe! +endfunc +func Test_end_reg_executing() + nnoremap s <Nop> + let @a = 's' + call feedkeys("@aqaq\<Esc>", 'tx') + call assert_equal('', @a) + call assert_equal('', getline(1)) + + call setline(1, 'aaa') + nnoremap s qa + let @a = 'fa' + call feedkeys("@asq\<Esc>", 'tx') + call assert_equal('', @a) + call assert_equal('aaa', getline(1)) + + nunmap s bwipe! endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim index 3887fcfabf..5359b84923 100644 --- a/src/nvim/testdir/test_rename.vim +++ b/src/nvim/testdir/test_rename.vim @@ -1,5 +1,7 @@ " Test rename() +source shared.vim + func Test_rename_file_to_file() call writefile(['foo'], 'Xrename1') @@ -81,7 +83,7 @@ func Test_rename_copy() call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile')) - if !has('win32') + if !has('win32') && !IsRoot() " On Windows, the source file is removed despite " its directory being made not writable. call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile')) diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim index f11a32bade..1650a03876 100644 --- a/src/nvim/testdir/test_retab.vim +++ b/src/nvim/testdir/test_retab.vim @@ -69,9 +69,33 @@ func Test_retab() call assert_equal(" a b c ", Retab('!', 3)) call assert_equal(" a b c ", Retab('', 5)) call assert_equal(" a b c ", Retab('!', 5)) + + set tabstop& expandtab& endfunc func Test_retab_error() call assert_fails('retab -1', 'E487:') call assert_fails('retab! -1', 'E487:') + call assert_fails('ret -1000', 'E487:') + call assert_fails('ret 10000', 'E475:') + call assert_fails('ret 80000000000000000000', 'E475:') endfunc + +func Test_retab_endless() + new + call setline(1, "\t0\t") + let caught = 'no' + try + while 1 + set ts=4000 + retab 4 + endwhile + catch /E1240/ + let caught = 'yes' + endtry + bwipe! + set tabstop& +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ruby.vim b/src/nvim/testdir/test_ruby.vim index 1a274d1fec..1fbf3392d9 100644 --- a/src/nvim/testdir/test_ruby.vim +++ b/src/nvim/testdir/test_ruby.vim @@ -60,7 +60,7 @@ func Test_ruby_set_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_equal([2, 5], '$curwin.cursor'->rubyeval()) " call assert_fails('ruby $curwin.cursor = [1]', " \ 'ArgumentError: array length must be 2') diff --git a/src/nvim/testdir/test_scriptnames.vim b/src/nvim/testdir/test_scriptnames.vim index fc6c910bfa..44ec146666 100644 --- a/src/nvim/testdir/test_scriptnames.vim +++ b/src/nvim/testdir/test_scriptnames.vim @@ -23,4 +23,10 @@ func Test_scriptnames() bwipe call delete('Xscripting') + + let msgs = execute('messages') + scriptnames + call assert_equal(msgs, execute('messages')) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 7570049e7c..8154bd9c4d 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -292,15 +292,84 @@ endfunc func Test_searchpair() new - call setline(1, ['other code here', '', '[', '" cursor here', ']']) + call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]']) + + 4 + call assert_equal(3, searchpair('\[', '', ']', 'bW')) + call assert_equal([0, 3, 2, 0], getpos('.')) + 4 + call assert_equal(2, searchpair('\[', '', ']', 'bWr')) + call assert_equal([0, 2, 6, 0], getpos('.')) 4 - let a = searchpair('\[','',']','bW') - call assert_equal(3, a) + call assert_equal(1, searchpair('\[', '', ']', 'bWm')) + call assert_equal([0, 3, 2, 0], getpos('.')) + 4|norm ^ + call assert_equal(5, searchpair('\[', '', ']', 'Wn')) + call assert_equal([0, 4, 2, 0], getpos('.')) + 4 + call assert_equal(2, searchpair('\[', '', ']', 'bW', + \ 'getline(".") =~ "^\\s*\["')) + call assert_equal([0, 2, 6, 0], getpos('.')) set nomagic 4 - let a = searchpair('\[','',']','bW') - call assert_equal(3, a) + call assert_equal(3, searchpair('\[', '', ']', 'bW')) + call assert_equal([0, 3, 2, 0], getpos('.')) set magic + 4|norm ^ + call assert_equal(0, searchpair('{', '', '}', 'bW')) + call assert_equal([0, 4, 2, 0], getpos('.')) + + %d + call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1']) + + /\<if 1 + call assert_equal(5, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W')) + call assert_equal([0, 5, 1, 0], getpos('.')) + /\<if 2 + call assert_equal(3, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W')) + call assert_equal([0, 3, 3, 0], getpos('.')) + + q! +endfunc + +func Test_searchpairpos() + new + call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]']) + + 4 + call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW')) + call assert_equal([0, 3, 2, 0], getpos('.')) + 4 + call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr')) + call assert_equal([0, 2, 6, 0], getpos('.')) + 4|norm ^ + call assert_equal([5, 2], searchpairpos('\[', '', ']', 'Wn')) + call assert_equal([0, 4, 2, 0], getpos('.')) + 4 + call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bW', + \ 'getline(".") =~ "^\\s*\["')) + call assert_equal([0, 2, 6, 0], getpos('.')) + 4 + call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr')) + call assert_equal([0, 2, 6, 0], getpos('.')) + set nomagic + 4 + call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW')) + call assert_equal([0, 3, 2, 0], getpos('.')) + set magic + 4|norm ^ + call assert_equal([0, 0], searchpairpos('{', '', '}', 'bW')) + call assert_equal([0, 4, 2, 0], getpos('.')) + + %d + call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1']) + /\<if 1 + call assert_equal([5, 1], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W')) + call assert_equal([0, 5, 1, 0], getpos('.')) + /\<if 2 + call assert_equal([3, 3], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W')) + call assert_equal([0, 3, 3, 0], getpos('.')) + q! endfunc @@ -309,17 +378,29 @@ func Test_searchpair_errors() call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String') call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String') call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags') - call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0') call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99') call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100') + call assert_fails("call searchpair('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e') + call assert_fails("call searchpair('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn') +endfunc + +func Test_searchpairpos_errors() + call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String') + call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String') + call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String') + call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags') + call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99') + call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100') + call assert_fails("call searchpairpos('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e') + call assert_fails("call searchpairpos('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn') endfunc func Test_searchpair_skip() func Zero() - return 0 + return 0 endfunc func Partial(x) - return a:x + return a:x endfunc new call setline(1, ['{', 'foo', 'foo', 'foo', '}']) @@ -1192,7 +1273,7 @@ endfunc " This was causing E874. Also causes an invalid read? func Test_look_behind() new - call setline(1, '0\|\&\n\@<=') + call setline(1, '0\|\&\n\@<=') call search(getline(".")) bwipe! endfunc @@ -1236,11 +1317,11 @@ 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" + " '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" + " ' Ì£' U+0323 Dec:803 COMBINING DOT BELOW ̣ /\%u323 "\u0323" " Those should also appear on the commandline CheckOption incsearch @@ -1315,7 +1396,7 @@ func Test_search_match_at_curpos() normal gg - call search('foobar', 'c') + eval 'foobar'->search('c') call assert_equal([1, 1], [line('.'), col('.')]) normal j @@ -1354,6 +1435,41 @@ func Test_search_display_pattern() endif endfunc +func Test_searchdecl() + let lines =<< trim END + int global; + + func() + { + int global; + if (cond) { + int local; + } + int local; + // comment + } + END + new + call setline(1, lines) + 10 + call assert_equal(0, searchdecl('local', 0, 0)) + call assert_equal(7, getcurpos()[1]) + + 10 + call assert_equal(0, 'local'->searchdecl(0, 1)) + call assert_equal(9, getcurpos()[1]) + + 10 + call assert_equal(0, searchdecl('global')) + call assert_equal(5, getcurpos()[1]) + + 10 + call assert_equal(0, searchdecl('global', 1)) + call assert_equal(1, getcurpos()[1]) + + bwipe! +endfunc + func Test_search_special() " this was causing illegal memory access and an endless loop set t_PE= @@ -1411,4 +1527,31 @@ func Test_incsearch_highlighting_newline() bw endfunc +func Test_no_last_search_pattern() + CheckOption incsearch + + let @/ = "" + set incsearch + " these were causing a crash + call feedkeys("//\<C-G>", 'xt') + call feedkeys("//\<C-T>", 'xt') + call feedkeys("??\<C-G>", 'xt') + call feedkeys("??\<C-T>", 'xt') +endfunc + +func Test_search_with_invalid_range() + new + let lines =<< trim END + /\%.v + 5/ + c + END + call writefile(lines, 'Xrangesearch') + source Xrangesearch + + bwipe! + call delete('Xrangesearch') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim index 335a51268d..d950626615 100644 --- a/src/nvim/testdir/test_search_stat.vim +++ b/src/nvim/testdir/test_search_stat.vim @@ -1,14 +1,15 @@ " Tests for search_stats, when "S" is not in 'shortmess' -source screendump.vim source check.vim +source screendump.vim func Test_search_stat() new set shortmess-=S " Append 50 lines with text to search for, "foobar" appears 20 times call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10)) - call nvim_win_set_cursor(0, [1, 0]) + + call cursor(1, 1) " searchcount() returns an empty dictionary when previous pattern was not set call assert_equal({}, searchcount(#{pattern: ''})) @@ -45,7 +46,6 @@ func Test_search_stat() \ searchcount(#{pattern: 'fooooobar', maxcount: 1})) " match at second line - call cursor(1, 1) let messages_before = execute('messages') let @/ = 'fo*\(bar\?\)\?' let g:a = execute(':unsilent :norm! n') @@ -73,7 +73,6 @@ func Test_search_stat() let stat = '\[2/50\]' let g:a = execute(':unsilent :norm! n') call assert_notmatch(pat .. stat, g:a) - call writefile(getline(1, '$'), 'sample.txt') " n does not update search stat call assert_equal( \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, @@ -263,6 +262,34 @@ func Test_searchcount_fails() call assert_fails('echo searchcount("boo!")', 'E715:') 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 + func Test_search_stat_foldopen() CheckScreendump @@ -320,30 +347,29 @@ func! Test_search_stat_screendump() call delete('Xsearchstat') endfunc -func Test_searchcount_in_statusline() +func Test_search_stat_then_gd() CheckScreendump let lines =<< trim END + call setline(1, ['int cat;', 'int dog;', 'cat = dog;']) 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 writefile(lines, 'Xsearchstatgd') + + let buf = RunVimInTerminal('-S Xsearchstatgd', #{rows: 10}) + call term_sendkeys(buf, "/dog\<CR>") call TermWait(buf) - call term_sendkeys(buf, "/something") - call VerifyScreenDump(buf, 'Test_searchstat_4', {}) + call VerifyScreenDump(buf, 'Test_searchstatgd_1', {}) + + call term_sendkeys(buf, "G0gD") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_searchstatgd_2', {}) - call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xsearchstatusline') + call delete('Xsearchstatgd') endfunc + + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_searchpos.vim b/src/nvim/testdir/test_searchpos.vim new file mode 100644 index 0000000000..dd13c305c5 --- /dev/null +++ b/src/nvim/testdir/test_searchpos.vim @@ -0,0 +1,30 @@ +" Tests for searchpos() + +func Test_searchpos() + new one + 0put ='1a3' + 1put ='123xyz' + call cursor(1, 1) + call assert_equal([1, 1, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')) + call cursor(1, 2) + call assert_equal([2, 1, 1], '\%(\([a-z]\)\|\_.\)\{-}xyz'->searchpos('pcW')) + set cpo-=c + call cursor(1, 2) + call assert_equal([1, 2, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')) + call cursor(1, 3) + call assert_equal([1, 3, 1], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')) + + " Now with \zs, first match is in column 0, "a" is matched. + call cursor(1, 3) + call assert_equal([2, 4, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcW')) + " With z flag start at cursor column, don't see the "a". + call cursor(1, 3) + call assert_equal([2, 4, 1], searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcWz')) + + set cpo+=c + " close the window + q! + +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim new file mode 100644 index 0000000000..b483841060 --- /dev/null +++ b/src/nvim/testdir/test_selectmode.vim @@ -0,0 +1,57 @@ +" Test for Select-mode + +source shared.vim + +" Test for selecting a register with CTRL-R +func Test_selectmode_register() + new + + " Default behavior: use unnamed register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('baz', getreg('a')) + + " Use the black hole register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>_a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('bar', getreg('"')) + call assert_equal('baz', getreg('a')) + + " Invalid register: use unnamed register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>?a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('baz', getreg('a')) + + " Use unnamed register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>\"a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('baz', getreg('a')) + + " use specicifed register, unnamed register is also written + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>aa" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('f', getreg('a')) + + bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_set.vim b/src/nvim/testdir/test_set.vim new file mode 100644 index 0000000000..2b1e9eeee0 --- /dev/null +++ b/src/nvim/testdir/test_set.vim @@ -0,0 +1,29 @@ +" Tests for the :set command + +function Test_set_backslash() + let isk_save = &isk + + set isk=a,b,c + set isk+=d + call assert_equal('a,b,c,d', &isk) + set isk+=\\,e + call assert_equal('a,b,c,d,\,e', &isk) + set isk-=e + call assert_equal('a,b,c,d,\', &isk) + set isk-=\\ + call assert_equal('a,b,c,d', &isk) + + let &isk = isk_save +endfunction + +function Test_set_add() + let wig_save = &wig + + set wildignore=*.png, + set wildignore+=*.jpg + call assert_equal('*.png,*.jpg', &wig) + + let &wig = wig_save +endfunction + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_sha256.vim b/src/nvim/testdir/test_sha256.vim index dd4707977e..76d1306836 100644 --- a/src/nvim/testdir/test_sha256.vim +++ b/src/nvim/testdir/test_sha256.vim @@ -6,17 +6,17 @@ endif function Test_sha256() " test for empty string: - call assert_equal(sha256(""), 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') + call assert_equal('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', sha256("")) "'test for 1 char: - call assert_equal(sha256("a"), 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb') + call assert_equal('ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', sha256("a")) " "test for 3 chars: - call assert_equal(sha256("abc"), 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') + call assert_equal('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', "abc"->sha256()) " test for contains meta char: - call assert_equal(sha256("foo\nbar"), '807eff6267f3f926a21d234f7b0cf867a86f47e07a532f15e8cc39ed110ca776') + call assert_equal('807eff6267f3f926a21d234f7b0cf867a86f47e07a532f15e8cc39ed110ca776', sha256("foo\nbar")) " test for contains non-ascii char: - call assert_equal(sha256("\xde\xad\xbe\xef"), '5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953') + call assert_equal('5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953', sha256("\xde\xad\xbe\xef")) endfunction diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 9753100375..ff9ba3d8ed 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -15,13 +15,13 @@ func Test_sign() " the icon name when listing signs. sign define Sign1 text=x - call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error icon=../../pixmaps/stock_vim_find_help.png') + call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error culhl=Search icon=../../pixmaps/stock_vim_find_help.png') " Test listing signs. let a=execute('sign list') call assert_match('^\nsign Sign1 text=x \nsign Sign2 ' . \ 'icon=../../pixmaps/stock_vim_find_help.png .*text=xy ' . - \ 'linehl=Error texthl=Title$', a) + \ 'linehl=Error texthl=Title culhl=Search$', a) let a=execute('sign list Sign1') call assert_equal("\nsign Sign1 text=x ", a) @@ -126,6 +126,30 @@ func Test_sign() " call assert_fails("sign define Sign4 text= linehl=Comment", 'E239:') call assert_fails("sign define Sign4 text=\\ ab linehl=Comment", 'E239:') + " an empty highlight argument for an existing sign clears it + sign define SignY texthl=TextHl culhl=CulHl linehl=LineHl + let sl = sign_getdefined('SignY')[0] + call assert_equal('TextHl', sl.texthl) + call assert_equal('CulHl', sl.culhl) + call assert_equal('LineHl', sl.linehl) + + sign define SignY texthl= culhl=CulHl linehl=LineHl + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'texthl')) + call assert_equal('CulHl', sl.culhl) + call assert_equal('LineHl', sl.linehl) + + sign define SignY linehl= + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'linehl')) + call assert_equal('CulHl', sl.culhl) + + sign define SignY culhl= + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'culhl')) + + sign undefine SignY + " define sign with whitespace sign define Sign4 text=\ X linehl=Comment sign undefine Sign4 @@ -194,15 +218,13 @@ func Test_sign_completion() call assert_equal('"sign define jump list place undefine unplace', @:) call feedkeys(":sign define Sign \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign icon= linehl= numhl= text= texthl=', @:) + call assert_equal('"sign define Sign culhl= icon= linehl= numhl= text= texthl=', @:) - call feedkeys(":sign define Sign linehl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign linehl=SpellBad SpellCap ' . - \ 'SpellLocal SpellRare', @:) - - call feedkeys(":sign define Sign texthl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign texthl=SpellBad SpellCap ' . - \ 'SpellLocal SpellRare', @:) + for hl in ['culhl', 'linehl', 'numhl', 'texthl'] + call feedkeys(":sign define Sign "..hl.."=Spell\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign define Sign '..hl..'=SpellBad SpellCap ' . + \ 'SpellLocal SpellRare', @:) + endfor call writefile(repeat(["Sun is shining"], 30), "XsignOne") call writefile(repeat(["Sky is blue"], 30), "XsignTwo") @@ -392,25 +414,28 @@ func Test_sign_funcs() call sign_undefine() " Tests for sign_define() - let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} - call assert_equal(0, sign_define("sign1", attr)) - call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', - \ 'linehl' : 'Search', 'text' : '=>'}], sign_getdefined()) + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error', + \ 'culhl': 'Visual', 'numhl': 'Number'} + call assert_equal(0, "sign1"->sign_define(attr)) + call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', 'linehl': 'Search', + \ 'culhl': 'Visual', 'numhl': 'Number', 'text' : '=>'}], + \ sign_getdefined()) " Define a new sign without attributes and then update it call sign_define("sign2") let attr = {'text' : '!!', 'linehl' : 'DiffAdd', 'texthl' : 'DiffChange', - \ 'icon' : 'sign2.ico'} + \ 'culhl': 'DiffDelete', 'numhl': 'Number', 'icon' : 'sign2.ico'} call Sign_define_ignore_error("sign2", attr) call assert_equal([{'name' : 'sign2', 'texthl' : 'DiffChange', - \ 'linehl' : 'DiffAdd', 'text' : '!!', 'icon' : 'sign2.ico'}], - \ sign_getdefined("sign2")) + \ 'linehl' : 'DiffAdd', 'culhl' : 'DiffDelete', 'text' : '!!', + \ 'numhl': 'Number', 'icon' : 'sign2.ico'}], + \ "sign2"->sign_getdefined()) " Test for a sign name with digits call assert_equal(0, sign_define(0002, {'linehl' : 'StatusLine'})) call assert_equal([{'name' : '2', 'linehl' : 'StatusLine'}], \ sign_getdefined(0002)) - call sign_undefine(0002) + eval 0002->sign_undefine() " Tests for invalid arguments to sign_define() call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:') @@ -434,7 +459,7 @@ func Test_sign_funcs() call assert_equal([{'bufnr' : bufnr(''), 'signs' : \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', \ 'priority' : 10}]}], - \ sign_getplaced('%', {'lnum' : 20})) + \ '%'->sign_getplaced({'lnum' : 20})) call assert_equal([{'bufnr' : bufnr(''), 'signs' : \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', \ 'priority' : 10}]}], @@ -490,10 +515,10 @@ func Test_sign_funcs() \ 'E745:') " Tests for sign_unplace() - call sign_place(20, '', 'sign2', 'Xsign', {"lnum" : 30}) + eval 20->sign_place('', 'sign2', 'Xsign', {"lnum" : 30}) call assert_equal(0, sign_unplace('', \ {'id' : 20, 'buffer' : 'Xsign'})) - call assert_equal(-1, sign_unplace('', + call assert_equal(-1, ''->sign_unplace( \ {'id' : 30, 'buffer' : 'Xsign'})) call sign_place(20, '', 'sign2', 'Xsign', {"lnum" : 30}) call assert_fails("call sign_unplace('', @@ -1693,7 +1718,7 @@ func Test_sign_jump_func() let r = sign_jump(5, '', 'foo') call assert_equal(2, r) call assert_equal(2, line('.')) - let r = sign_jump(6, 'g1', 'foo') + let r = 6->sign_jump('g1', 'foo') call assert_equal(5, r) call assert_equal(5, line('.')) let r = sign_jump(5, '', 'bar') @@ -1921,8 +1946,7 @@ func Test_sign_funcs_multi() \ 'group' : 'g1', 'priority' : 10}], s[0].signs) " Change an existing sign without specifying the group - call assert_equal([5], sign_placelist([ - \ {'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}])) + call assert_equal([5], [{'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}]->sign_placelist()) let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''}) call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11, \ 'group' : '', 'priority' : 10}], s[0].signs) @@ -1955,7 +1979,7 @@ func Test_sign_funcs_multi() \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}])) " Invalid arguments - call assert_equal([], sign_unplacelist([])) + call assert_equal([], []->sign_unplacelist()) call assert_fails('call sign_unplacelist({})', "E714:") call assert_fails('call sign_unplacelist([[]])', "E715:") call assert_fails('call sign_unplacelist(["abc"])', "E715:") diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index 570c4415c6..5d7dd7bfff 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -12,6 +12,7 @@ func Compare2(a, b) abort endfunc func Test_sort_strings() + CheckNotMSWindows " FIXME: Why does this fail with MSVC? " numbers compared as strings call assert_equal([1, 2, 3], sort([3, 2, 1])) call assert_equal([13, 28, 3], sort([3, 28, 13])) @@ -58,6 +59,7 @@ endfunc func Test_sort_numbers() call assert_equal([3, 13, 28], sort([13, 28, 3], 'N')) call assert_equal(['3', '13', '28'], sort(['13', '28', '3'], 'N')) + call assert_equal([3997, 4996], sort([4996, 3997], 'Compare1')) endfunc func Test_sort_float() diff --git a/src/nvim/testdir/test_source_utf8.vim b/src/nvim/testdir/test_source_utf8.vim index e93ea29dff..66fabe0442 100644 --- a/src/nvim/testdir/test_source_utf8.vim +++ b/src/nvim/testdir/test_source_utf8.vim @@ -1,4 +1,5 @@ " Test the :source! command +source check.vim func Test_source_utf8() " check that sourcing a script with 0x80 as second byte works @@ -31,24 +32,24 @@ endfunc " Test for sourcing a file with CTRL-V's at the end of the line func Test_source_ctrl_v() - call writefile(['map __1 afirst', - \ 'map __2 asecond', - \ 'map __3 athird', - \ 'map __4 afourth', - \ 'map __5 afifth', - \ "map __1 asd\<C-V>", - \ "map __2 asd\<C-V>\<C-V>", - \ "map __3 asd\<C-V>\<C-V>", - \ "map __4 asd\<C-V>\<C-V>\<C-V>", - \ "map __5 asd\<C-V>\<C-V>\<C-V>", - \ ], 'Xtestfile') + call writefile(['map __1 afirst', + \ 'map __2 asecond', + \ 'map __3 athird', + \ 'map __4 afourth', + \ 'map __5 afifth', + \ "map __1 asd\<C-V>", + \ "map __2 asd\<C-V>\<C-V>", + \ "map __3 asd\<C-V>\<C-V>", + \ "map __4 asd\<C-V>\<C-V>\<C-V>", + \ "map __5 asd\<C-V>\<C-V>\<C-V>", + \ ], 'Xtestfile') source Xtestfile enew! exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>" exe "%s/\<C-J>/0/g" call assert_equal(['sd', - \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"], - \ getline(1, 2)) + \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"], + \ getline(1, 2)) enew! call delete('Xtestfile') diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index e525d06ea2..56ed97cdd9 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -77,7 +77,7 @@ func Test_spellbadword() set spell call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.')) - call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence')) + call assert_equal(['another', 'caps'], 'A sentence. another sentence'->spellbadword()) call assert_equal(['TheCamelWord', 'bad'], spellbadword('TheCamelWord asdf')) set spelloptions=camel @@ -407,7 +407,7 @@ func Test_zz_basic() \ ) call assert_equal("gebletegek", soundfold('goobledygoook')) - call assert_equal("kepereneven", soundfold('kóopërÿnôven')) + call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold()) call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) endfunc @@ -588,7 +588,7 @@ func Test_zz_sal_and_addition() mkspell! Xtest Xtest set spl=Xtest.latin1.spl spell call assert_equal('kbltykk', soundfold('goobledygoook')) - call assert_equal('kprnfn', soundfold('kóopërÿnôven')) + call assert_equal('kprnfn', soundfold('kóopërÿnôven')) call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) "also use an addition file @@ -681,6 +681,14 @@ func Test_spell_long_word() set nospell endfunc +func Test_spellsuggest_too_deep() + " This was incrementing "depth" over MAXWLEN. + new + norm s000G00ý000000000000 + sil norm ..vzG................vvzG0 v z= + bwipe! +endfunc + func LoadAffAndDic(aff_contents, dic_contents) throw 'skipped: Nvim does not support enc=latin1' set enc=latin1 @@ -711,7 +719,7 @@ func TestGoodBadBase() break endif let prevbad = bad - let lst = spellsuggest(bad, 3) + let lst = bad->spellsuggest(3) normal mm call add(result, [bad, lst]) @@ -768,6 +776,14 @@ func Test_spell_screendump() call delete('XtestSpell') endfunc +func Test_spell_single_word() + new + silent! norm 0R00 + spell! ß + silent 0norm 0r$ Dvz= + bwipe! +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_spell_utf8.vim b/src/nvim/testdir/test_spell_utf8.vim index cafdb97f28..3d159f3352 100644 --- a/src/nvim/testdir/test_spell_utf8.vim +++ b/src/nvim/testdir/test_spell_utf8.vim @@ -512,8 +512,7 @@ func TestGoodBadBase() break endif let prevbad = bad - " let lst = bad->spellsuggest(3) - let lst = spellsuggest(bad, 3) + let lst = bad->spellsuggest(3) normal mm call add(result, [bad, lst]) @@ -552,8 +551,7 @@ func Test_spell_basic() \ ) call assert_equal("gebletegek", soundfold('goobledygoook')) - " call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold()) - call assert_equal("kepereneven", soundfold('kóopërÿnôven')) + call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold()) call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) endfunc diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index b140077111..d830f5216d 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -905,15 +905,13 @@ func Test_not_a_term() " This will take 2 seconds because of the missing --not-a-term let cmd = GetVimProg() .. ' --cmd quit ' .. redir exe "silent !" . cmd - " call assert_match("\<Esc>", readfile('Xvimout')->join()) - call assert_match("\<Esc>", join(readfile('Xvimout'))) + call assert_match("\<Esc>", readfile('Xvimout')->join()) call delete('Xvimout') " With --not-a-term there are no escape sequences. let cmd = GetVimProg() .. ' --not-a-term --cmd quit ' .. redir exe "silent !" . cmd - " call assert_notmatch("\<Esc>", readfile('Xvimout')->join()) - call assert_notmatch("\<Esc>", join(readfile('Xvimout'))) + call assert_notmatch("\<Esc>", readfile('Xvimout')->join()) call delete('Xvimout') endfunc diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index 170358e023..d3059664e9 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -1,11 +1,13 @@ " Tests for stat functions and checktime +source check.vim + func CheckFileTime(doSleep) let fnames = ['Xtest1.tmp', 'Xtest2.tmp', 'Xtest3.tmp'] let times = [] let result = 0 - " Use three files istead of localtim(), with a network filesystem the file + " Use three files instead of localtim(), with a network filesystem the file " times may differ at bit let fl = ['Hello World!'] for fname in fnames @@ -74,6 +76,44 @@ func Test_checktime() call delete(fname) endfunc +func Test_checktime_fast() + CheckFeature nanotime + + let fname = 'Xtest.tmp' + + let fl = ['Hello World!'] + call writefile(fl, fname) + set autoread + exec 'e' fname + let fl = readfile(fname) + let fl[0] .= ' - checktime' + sleep 10m " make test less flaky in Nvim + call writefile(fl, fname) + checktime + call assert_equal(fl[0], getline(1)) + + call delete(fname) +endfunc + +func Test_autoread_fast() + CheckFeature nanotime + + " this is timing sensitive + let g:test_is_flaky = 1 + + new Xautoread + setlocal autoread + call setline(1, 'foo') + w! + sleep 10m + call writefile(['bar'], 'Xautoread') + sleep 10m + checktime + call assert_equal('bar', trim(getline(1))) + + call delete('Xautoread') +endfunc + func Test_autoread_file_deleted() new Xautoread set autoread diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index a3e4dcdd25..fad13e3340 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -186,7 +186,16 @@ func Test_statusline() set virtualedit=all norm 10| call assert_match('^10,-10\s*$', s:get_statusline()) + set list + call assert_match('^10,-10\s*$', s:get_statusline()) set virtualedit& + exe "norm A\<Tab>\<Tab>a\<Esc>" + " In list mode a <Tab> is shown as "^I", which is 2-wide. + call assert_match('^9,-9\s*$', s:get_statusline()) + set list& + " Now the second <Tab> ends at the 16th screen column. + call assert_match('^17,-17\s*$', s:get_statusline()) + undo " %w: Preview window flag, text is "[Preview]". " %W: Preview window flag, text is ",PRV". @@ -498,5 +507,20 @@ func Test_statusline_after_split_vsplit() set ls& stl& endfunc +" Test using a multibyte character for 'stl' and 'stlnc' items in 'fillchars' +" with a custom 'statusline' +func Test_statusline_mbyte_fillchar() + only + set laststatus=2 + set fillchars=vert:\|,fold:-,stl:â”,stlnc:â• + set statusline=a%=b + call assert_match('^a\+â”\+b$', s:get_statusline()) + vnew + call assert_match('^a\+â”\+bâ”a\+â•\+b$', s:get_statusline()) + wincmd w + call assert_match('^a\+â•\+bâ•a\+â”\+b$', s:get_statusline()) + set statusline& fillchars& laststatus& + %bw! +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index e7f9bb76f2..86fd0147a5 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -51,10 +51,12 @@ func Test_substitute_variants() \ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' }, \ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' }, \ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/c', 'exp': 'Testing string', 'prompt': 'n' }, \ { 'cmd': ':s/t/r/cn', 'exp': ln }, \ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' }, \ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' }, \ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' }, + \ { 'cmd': ':s/i/I/gc', 'exp': 'TestIng string', 'prompt': 'l' }, \ { 'cmd': ':s/foo/bar/ge', 'exp': ln }, \ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' }, \ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' }, @@ -86,6 +88,7 @@ func Test_substitute_variants() \ { 'cmd': ':s//r/rp', 'exp': 'Testr string' }, \ { 'cmd': ':s//r/rl', 'exp': 'Testr string' }, \ { 'cmd': ':s//r/r', 'exp': 'Testr string' }, + \ { 'cmd': ':s/i/I/gc', 'exp': 'Testing string', 'prompt': 'q' }, \] for var in variants @@ -105,7 +108,7 @@ func Test_substitute_variants() call assert_equal(var.exp, getline('.'), msg) endfor endfor -endfunction +endfunc " Test the l, p, # flags. func Test_substitute_flags_lp() @@ -137,7 +140,7 @@ func Test_substitute_repeat() " This caused an invalid memory access. split Xfile s/^/x - call feedkeys("Qsc\<CR>y", 'tx') + call feedkeys("gQsc\<CR>y", 'tx') bwipe! endfunc @@ -174,9 +177,9 @@ func Test_sub_cmd_1() \ ['I', 's/I/\lII/', ['iI']], \ ['J', 's/J/\LJ\EJ/', ['jJ']], \ ['K', 's/K/\Uk\ek/', ['Kk']], - \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']], + \ ['lLl', "s/L/\<C-V>\r/", ["l\<C-V>", 'l']], \ ['mMm', 's/M/\r/', ['m', 'm']], - \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']], + \ ['nNn', "s/N/\\\<C-V>\r/", ["n\<C-V>", 'n']], \ ['oOo', 's/O/\n/', ["o\no"]], \ ['pPp', 's/P/\b/', ["p\<C-H>p"]], \ ['qQq', 's/Q/\t/', ["q\tq"]], @@ -205,9 +208,9 @@ func Test_sub_cmd_2() \ ['I', 's/I/\lII/', ['iI']], \ ['J', 's/J/\LJ\EJ/', ['jJ']], \ ['K', 's/K/\Uk\ek/', ['Kk']], - \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']], + \ ['lLl', "s/L/\<C-V>\r/", ["l\<C-V>", 'l']], \ ['mMm', 's/M/\r/', ['m', 'm']], - \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']], + \ ['nNn', "s/N/\\\<C-V>\r/", ["n\<C-V>", 'n']], \ ['oOo', 's/O/\n/', ["o\no"]], \ ['pPp', 's/P/\b/', ["p\<C-H>p"]], \ ['qQq', 's/Q/\t/', ["q\tq"]], @@ -227,9 +230,9 @@ func Test_sub_cmd_3() " List entry format: [input, cmd, output] let tests = [['aAa', "s/A/\\='\\'/", ['a\a']], \ ['bBb', "s/B/\\='\\\\'/", ['b\\b']], - \ ['cCc', "s/C/\\='\<C-V>\<C-M>'/", ["c\<C-V>", 'c']], - \ ['dDd', "s/D/\\='\\\<C-V>\<C-M>'/", ["d\\\<C-V>", 'd']], - \ ['eEe', "s/E/\\='\\\\\<C-V>\<C-M>'/", ["e\\\\\<C-V>", 'e']], + \ ['cCc', "s/C/\\='\<C-V>\r'/", ["c\<C-V>", 'c']], + \ ['dDd', "s/D/\\='\\\<C-V>\r'/", ["d\\\<C-V>", 'd']], + \ ['eEe', "s/E/\\='\\\\\<C-V>\r'/", ["e\\\\\<C-V>", 'e']], \ ['fFf', "s/F/\\='\r'/", ['f', 'f']], \ ['gGg', "s/G/\\='\<C-V>\<C-J>'/", ["g\<C-V>", 'g']], \ ['hHh', "s/H/\\='\\\<C-V>\<C-J>'/", ["h\\\<C-V>", 'h']], @@ -251,11 +254,11 @@ func Test_sub_cmd_4() \ ['a\a']], \ ['bBb', "s/B/\\=substitute(submatch(0), '.', '\\', '')/", \ ['b\b']], - \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\<C-M>', '')/", + \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\r', '')/", \ ["c\<C-V>", 'c']], - \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\<C-M>', '')/", + \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\r', '')/", \ ["d\<C-V>", 'd']], - \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\<C-M>', '')/", + \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\r', '')/", \ ["e\\\<C-V>", 'e']], \ ['fFf', "s/F/\\=substitute(submatch(0), '.', '\\r', '')/", \ ['f', 'f']], @@ -313,7 +316,7 @@ func Test_sub_cmd_7() set cpo& " List entry format: [input, cmd, output] - let tests = [ ["A\<C-V>\<C-M>A", 's/A./\=submatch(0)/', ['A', 'A']], + let tests = [ ["A\<C-V>\rA", 's/A./\=submatch(0)/', ['A', 'A']], \ ["B\<C-V>\<C-J>B", 's/B./\=submatch(0)/', ['B', 'B']], \ ["C\<C-V>\<C-J>C", 's/C./\=strtrans(string(submatch(0, 1)))/', [strtrans("['C\<C-J>']C")]], \ ["D\<C-V>\<C-J>\nD", 's/D.\nD/\=strtrans(string(submatch(0, 1)))/', [strtrans("['D\<C-J>', 'D']")]], @@ -384,6 +387,10 @@ func Test_substitute_join() call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) call assert_equal('\n', histget("search", -1)) + call setline(1, ['foo', 'bar', 'baz', 'qux']) + call execute('1,2s/\n//') + call assert_equal(['foobarbaz', 'qux'], getline(1, '$')) + bwipe! endfunc @@ -398,6 +405,11 @@ func Test_substitute_count() call assert_fails('s/foo/bar/0', 'E939:') + call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo']) + 2,4s/foo/bar/ 10 + call assert_equal(['foo foo', 'foo foo', 'foo foo', 'bar foo', 'bar foo'], + \ getline(1, '$')) + bwipe! endfunc @@ -416,6 +428,10 @@ func Test_substitute_flag_n() " No substitution should have been done. call assert_equal(lines, getline(1, '$')) + %delete _ + call setline(1, ['A', 'Bar', 'Baz']) + call assert_equal("\n1 match on 1 line", execute('s/\nB\@=//gn')) + bwipe! endfunc @@ -451,11 +467,11 @@ func Test_sub_replace_1() call assert_equal('iI', substitute('I', 'I', '\lII', '')) call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', '')) call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', '')) - call assert_equal("l\<C-V>\<C-M>l", - \ substitute('lLl', 'L', "\<C-V>\<C-M>", '')) - call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', '')) - call assert_equal("n\<C-V>\<C-M>n", - \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", '')) + call assert_equal("l\<C-V>\rl", + \ substitute('lLl', 'L', "\<C-V>\r", '')) + call assert_equal("m\rm", substitute('mMm', 'M', '\r', '')) + call assert_equal("n\<C-V>\rn", + \ substitute('nNn', 'N', "\\\<C-V>\r", '')) call assert_equal("o\no", substitute('oOo', 'O', '\n', '')) call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', '')) call assert_equal("q\tq", substitute('qQq', 'Q', '\t', '')) @@ -464,7 +480,7 @@ func Test_sub_replace_1() call assert_equal("u\nu", substitute('uUu', 'U', "\n", '')) call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", '')) call assert_equal("w\\w", substitute('wWw', 'W', "\\", '')) - call assert_equal("x\<C-M>x", substitute('xXx', 'X', "\r", '')) + call assert_equal("x\rx", substitute('xXx', 'X', "\r", '')) call assert_equal("YyyY", substitute('Y', 'Y', '\L\uyYy\l\EY', '')) call assert_equal("zZZz", substitute('Z', 'Z', '\U\lZzZ\u\Ez', '')) endfunc @@ -484,17 +500,17 @@ func Test_sub_replace_2() call assert_equal('iI', substitute('I', 'I', '\lII', '')) call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', '')) call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', '')) - call assert_equal("l\<C-V>\<C-M>l", - \ substitute('lLl', 'L', "\<C-V>\<C-M>", '')) - call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', '')) - call assert_equal("n\<C-V>\<C-M>n", - \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", '')) + call assert_equal("l\<C-V>\rl", + \ substitute('lLl', 'L', "\<C-V>\r", '')) + call assert_equal("m\rm", substitute('mMm', 'M', '\r', '')) + call assert_equal("n\<C-V>\rn", + \ substitute('nNn', 'N', "\\\<C-V>\r", '')) call assert_equal("o\no", substitute('oOo', 'O', '\n', '')) call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', '')) call assert_equal("q\tq", substitute('qQq', 'Q', '\t', '')) call assert_equal('r\r', substitute('rRr', 'R', '\\', '')) call assert_equal('scs', substitute('sSs', 'S', '\c', '')) - call assert_equal("t\<C-M>t", substitute('tTt', 'T', "\r", '')) + call assert_equal("t\rt", substitute('tTt', 'T', "\r", '')) call assert_equal("u\nu", substitute('uUu', 'U', "\n", '')) call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", '')) call assert_equal('w\w', substitute('wWw', 'W', "\\", '')) @@ -512,7 +528,7 @@ func Test_sub_replace_3() call assert_equal("e\\\\\re", substitute('eEe', 'E', "\\=\"\\\\\\\\\r\"", '')) call assert_equal('f\rf', substitute('fFf', 'F', '\="\\r"', '')) call assert_equal('j\nj', substitute('jJj', 'J', '\="\\n"', '')) - call assert_equal("k\<C-M>k", substitute('kKk', 'K', '\="\r"', '')) + call assert_equal("k\rk", substitute('kKk', 'K', '\="\r"', '')) call assert_equal("l\nl", substitute('lLl', 'L', '\="\n"', '')) endfunc @@ -524,10 +540,10 @@ func Test_sub_replace_4() \ '\=substitute(submatch(0), ".", "\\", "")', '')) call assert_equal('b\b', substitute('bBb', 'B', \ '\=substitute(submatch(0), ".", "\\\\", "")', '')) - call assert_equal("c\<C-V>\<C-M>c", substitute('cCc', 'C', '\=substitute(submatch(0), ".", "\<C-V>\<C-M>", "")', '')) - call assert_equal("d\<C-V>\<C-M>d", substitute('dDd', 'D', '\=substitute(submatch(0), ".", "\\\<C-V>\<C-M>", "")', '')) - call assert_equal("e\\\<C-V>\<C-M>e", substitute('eEe', 'E', '\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-M>", "")', '')) - call assert_equal("f\<C-M>f", substitute('fFf', 'F', '\=substitute(submatch(0), ".", "\\r", "")', '')) + call assert_equal("c\<C-V>\rc", substitute('cCc', 'C', '\=substitute(submatch(0), ".", "\<C-V>\r", "")', '')) + call assert_equal("d\<C-V>\rd", substitute('dDd', 'D', '\=substitute(submatch(0), ".", "\\\<C-V>\r", "")', '')) + call assert_equal("e\\\<C-V>\re", substitute('eEe', 'E', '\=substitute(submatch(0), ".", "\\\\\<C-V>\r", "")', '')) + call assert_equal("f\rf", substitute('fFf', 'F', '\=substitute(submatch(0), ".", "\\r", "")', '')) call assert_equal("j\nj", substitute('jJj', 'J', '\=substitute(submatch(0), ".", "\\n", "")', '')) call assert_equal("k\rk", substitute('kKk', 'K', '\=substitute(submatch(0), ".", "\r", "")', '')) call assert_equal("l\nl", substitute('lLl', 'L', '\=substitute(submatch(0), ".", "\n", "")', '')) @@ -547,7 +563,7 @@ func Test_sub_replace_5() \ substitute('A123456789', \ 'A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', \ '\=string([submatch(0, 1), submatch(9, 1), ' . - \ 'submatch(8, 1), submatch(7, 1), submatch(6, 1), ' . + \ 'submatch(8, 1), 7->submatch(1), submatch(6, 1), ' . \ 'submatch(5, 1), submatch(4, 1), submatch(3, 1), ' . \ 'submatch(2, 1), submatch(1, 1)])', \ '')) @@ -749,11 +765,49 @@ func Test_sub_beyond_end() bwipe! endfunc +" Test for repeating last substitution using :~ and :&r +func Test_repeat_last_sub() + new + call setline(1, ['blue green yellow orange white']) + s/blue/red/ + let @/ = 'yellow' + ~ + let @/ = 'white' + :&r + let @/ = 'green' + s//gray + call assert_equal('red gray red orange red', getline(1)) + close! +endfunc + +" Test for Vi compatible substitution: +" \/{string}/, \?{string}? and \&{string}& +func Test_sub_vi_compatibility() + new + call setline(1, ['blue green yellow orange blue']) + let @/ = 'orange' + s\/white/ + let @/ = 'blue' + s\?amber? + let @/ = 'white' + s\&green& + call assert_equal('amber green yellow white green', getline(1)) + close! +endfunc + +" Test for substitute with the new text longer than the original text +func Test_sub_expand_text() + new + call setline(1, 'abcabcabcabcabcabcabcabc') + s/b/\=repeat('B', 10)/g + call assert_equal(repeat('aBBBBBBBBBBc', 8), getline(1)) + close! +endfunc + func Test_submatch_list_concatenate() let pat = 'A\(.\)' let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])} - " call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]") - call assert_equal(substitute('A1', pat, Rep, ''), "[['A1'], ['1']]") + call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]") endfunc func Test_substitute_skipped_range() @@ -765,4 +819,22 @@ func Test_substitute_skipped_range() bwipe! endfunc +" This was using "old_sub" after it was freed. +func Test_using_old_sub() + " set compatible maxfuncdepth=10 + set maxfuncdepth=10 + new + call setline(1, 'some text.') + func Repl() + ~ + s/ + endfunc + silent! s/\%')/\=Repl() + + delfunc Repl + bwipe! + set nocompatible +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim index 4b3bd5eadf..bf88bd4453 100644 --- a/src/nvim/testdir/test_suspend.vim +++ b/src/nvim/testdir/test_suspend.vim @@ -26,8 +26,8 @@ func Test_suspend() " Wait for shell prompt. call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))}) - call term_sendkeys(buf, v:progpath - \ . " --clean -X" + call term_sendkeys(buf, GetVimCommandClean() + \ . " -X" \ . " -c 'set nu'" \ . " -c 'call setline(1, \"foo\")'" \ . " Xfoo\<CR>") diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index e3101d4e44..b180f27685 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -1,6 +1,7 @@ " Tests for the swap feature source check.vim +source shared.vim func s:swapname() return trim(execute('swapname')) @@ -113,7 +114,7 @@ func Test_swapinfo() w let fname = s:swapname() call assert_match('Xswapinfo', fname) - let info = swapinfo(fname) + let info = fname->swapinfo() let ver = printf('VIM %d.%d', v:version / 100, v:version % 100) call assert_equal(ver, info.version) @@ -155,7 +156,7 @@ func Test_swapname() let buf = bufnr('%') let expected = s:swapname() wincmd p - call assert_equal(expected, swapname(buf)) + call assert_equal(expected, buf->swapname()) new Xtest3 setlocal noswapfile @@ -198,14 +199,17 @@ func Test_swapfile_delete() quit call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t')) - " Write the swapfile with a modified PID, now it will be automatically - " deleted. Process one should never be Vim. - let swapfile_bytes[24:27] = 0z01000000 - call writefile(swapfile_bytes, swapfile_name) - let s:swapname = '' - split XswapfileText - quit - call assert_equal('', s:swapname) + " This test won't work as root because root can successfully run kill(1, 0) + if !IsRoot() + " Write the swapfile with a modified PID, now it will be automatically + " deleted. Process one should never be Vim. + let swapfile_bytes[24:27] = 0z01000000 + call writefile(swapfile_bytes, swapfile_name) + let s:swapname = '' + split XswapfileText + quit + call assert_equal('', s:swapname) + endif " Now set the modified flag, the swap file will not be deleted let swapfile_bytes[28 + 80 + 899] = 0x55 diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 914d9c2782..b047b53b6f 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -30,23 +30,17 @@ func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "") " If groups are provided as a string, each character is assumed to be a " group and spaces represent no group, useful for visually describing tests. let l:expectedGroups = type(a:expected) == v:t_string - "\ ? a:expected->split('\zs')->map({_, v -> trim(v)}) - \ ? map(split(a:expected, '\zs'), {_, v -> trim(v)}) + \ ? a:expected->split('\zs')->map({_, v -> trim(v)}) \ : a:expected let l:errors = 0 - " let l:msg = (a:msg->empty() ? "" : a:msg .. ": ") - let l:msg = (empty(a:msg) ? "" : a:msg .. ": ") + let l:msg = (a:msg->empty() ? "" : a:msg .. ": ") \ .. "Wrong highlight group at " .. a:lnum .. "," - " for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1) - " let l:errors += synID(a:lnum, l:i, a:trans) - " \ ->synIDattr("name") - " \ ->assert_equal(l:expectedGroups[l:i - 1], - for l:i in range(a:startcol, a:startcol + len(l:expectedGroups) - 1) - let l:errors += - \ assert_equal(synIDattr(synID(a:lnum, l:i, a:trans), "name"), - \ l:expectedGroups[l:i - 1], - \ l:msg .. l:i) + for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1) + let l:errors += synID(a:lnum, l:i, a:trans) + \ ->synIDattr("name") + \ ->assert_equal(l:expectedGroups[l:i - 1], + \ l:msg .. l:i) endfor endfunc @@ -734,6 +728,56 @@ func Test_syntax_foldlevel() quit! endfunc +func Test_search_syntax_skip() + new + let lines =<< trim END + + /* This is VIM */ + Another Text for VIM + let a = "VIM" + END + call setline(1, lines) + syntax on + syntax match Comment "^/\*.*\*/" + syntax match String '".*"' + + " Skip argument using string evaluation. + 1 + call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"') + call assert_equal('Another Text for VIM', getline('.')) + 1 + call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") !~? "string"') + call assert_equal(' let a = "VIM"', getline('.')) + + " Skip argument using Lambda. + 1 + call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"}) + call assert_equal('Another Text for VIM', getline('.')) + + 1 + call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") !~? "string"}) + call assert_equal(' let a = "VIM"', getline('.')) + + " Skip argument using funcref. + func InComment() + return synIDattr(synID(line("."), col("."), 1), "name") =~? "comment" + endfunc + func InString() + return synIDattr(synID(line("."), col("."), 1), "name") !~? "string" + endfunc + 1 + call search('VIM', 'w', '', 0, function('InComment')) + call assert_equal('Another Text for VIM', getline('.')) + + 1 + call search('VIM', 'w', '', 0, function('InString')) + call assert_equal(' let a = "VIM"', getline('.')) + + delfunc InComment + delfunc InString + bwipe! +endfunc + func Test_syn_include_contains_TOP() let l:case = "TOP in included syntax means its group list name" new diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 7b8ee778cc..18692f42c9 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -46,15 +46,15 @@ func Test_System() bwipe! call assert_fails('call system("wc -l", 99999)', 'E86:') -endfunction +endfunc -function! Test_system_exmode() +func Test_system_exmode() if has('unix') " echo $? only works on Unix - let cmd = ' -es --headless -u NONE -c "source Xscript" +q; echo "result=$?"' + let cmd = ' -es -c "source Xscript" +q; echo "result=$?"' " Need to put this in a script, "catch" isn't found after an unknown " function. call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') - let a = system(v:progpath . cmd) + let a = system(GetVimCommand() . cmd) call assert_match('result=0', a) call assert_equal(0, v:shell_error) endif @@ -62,33 +62,33 @@ function! Test_system_exmode() " Error before try does set error flag. call writefile(['call nosuchfunction()', 'try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') if has('unix') " echo $? only works on Unix - let a = system(v:progpath . cmd) + let a = system(GetVimCommand() . cmd) call assert_notequal('0', a[0]) endif - let cmd = ' -es --headless -u NONE -c "source Xscript" +q' - let a = system(v:progpath . cmd) + let cmd = ' -es -c "source Xscript" +q' + let a = system(GetVimCommand() . cmd) call assert_notequal(0, v:shell_error) call delete('Xscript') if has('unix') " echo $? only works on Unix - let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q; echo $?' - let a = system(v:progpath. cmd) + let cmd = ' -es -c "call doesnotexist()" +q; echo $?' + let a = system(GetVimCommand() . cmd) call assert_notequal(0, a[0]) endif - let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q' - let a = system(v:progpath. cmd) + let cmd = ' -es -c "call doesnotexist()" +q' + let a = system(GetVimCommand(). cmd) call assert_notequal(0, v:shell_error) if has('unix') " echo $? only works on Unix - let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q; echo $?' - let a = system(v:progpath. cmd) + let cmd = ' -es -c "call doesnotexist()|let a=1" +q; echo $?' + let a = system(GetVimCommand() . cmd) call assert_notequal(0, a[0]) endif - let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q' - let a = system(v:progpath. cmd) + let cmd = ' -es -c "call doesnotexist()|let a=1" +q' + let a = system(GetVimCommand() . cmd) call assert_notequal(0, v:shell_error) endfunc @@ -121,8 +121,7 @@ func Test_system_with_shell_quote() let msg = printf('shell=%s shellxquote=%s', &shell, &shellxquote) try - " let out = 'echo 123'->system() - let out = system('echo 123') + let out = 'echo 123'->system() catch call assert_report(printf('%s: %s', msg, v:exception)) continue diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim index 117d962d08..3a18206078 100644 --- a/src/nvim/testdir/test_tabline.vim +++ b/src/nvim/testdir/test_tabline.vim @@ -86,6 +86,17 @@ func Test_tabline_empty_group() set tabline= endfunc +" When there are exactly 20 tabline format items (the exact size of the +" initial tabline items array), test that we don't write beyond the size +" of the array. +func Test_tabline_20_format_items_no_overrun() + set showtabline=2 + + let tabline = repeat('%#StatColorHi2#', 20) + let &tabline = tabline + redrawtabline + set showtabline& tabline& +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index b261b96c3b..51ab5c1022 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -35,7 +35,7 @@ function Test_tabpage() tabnew tabfirst call settabvar(2, 'val_num', 100) - call settabvar(2, 'val_str', 'SetTabVar test') + eval 'SetTabVar test'->settabvar(2, 'val_str') call settabvar(2, 'val_list', ['red', 'blue', 'green']) " call assert_true(gettabvar(2, 'val_num') == 100 && gettabvar(2, 'val_str') == 'SetTabVar test' && gettabvar(2, 'val_list') == ['red', 'blue', 'green']) @@ -128,6 +128,8 @@ function Test_tabpage() 1tabmove call assert_equal(2, tabpagenr()) + call assert_fails('let t = tabpagenr("@")', 'E15:') + call assert_equal(0, tabpagewinnr(-1)) call assert_fails("99tabmove", 'E16:') call assert_fails("+99tabmove", 'E16:') call assert_fails("-99tabmove", 'E16:') @@ -184,7 +186,7 @@ function Test_tabpage_with_autocmd() let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+') call assert_equal(['a', 'a'], s:li) let s:li = [] - C call map(copy(winr), 'settabwinvar('.tabn.', v:val, ''a'', v:val*2)') + C call map(copy(winr), '(v:val*2)->settabwinvar(' .. tabn .. ', v:val, ''a'')') let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+') call assert_equal(['2', '4'], s:li) @@ -683,4 +685,73 @@ func Test_tabline_tabmenu() %bw! endfunc +" Test for jumping to last accessed tabpage +func Test_lastused_tabpage() + tabonly! + call assert_equal(0, tabpagenr('#')) + call assert_beeps('call feedkeys("g\<Tab>", "xt")') + call assert_beeps('call feedkeys("\<C-Tab>", "xt")') + call assert_beeps('call feedkeys("\<C-W>g\<Tab>", "xt")') + call assert_fails('tabnext #', 'E475:') + + " open four tab pages + tabnew + tabnew + tabnew + + 2tabnext + + " Test for g<Tab> + call assert_equal(4, tabpagenr('#')) + call feedkeys("g\<Tab>", "xt") + call assert_equal(4, tabpagenr()) + call assert_equal(2, tabpagenr('#')) + + " Test for <C-Tab> + call feedkeys("\<C-Tab>", "xt") + call assert_equal(2, tabpagenr()) + call assert_equal(4, tabpagenr('#')) + + " Test for <C-W>g<Tab> + call feedkeys("\<C-W>g\<Tab>", "xt") + call assert_equal(4, tabpagenr()) + call assert_equal(2, tabpagenr('#')) + + " Test for :tabnext # + tabnext # + call assert_equal(2, tabpagenr()) + call assert_equal(4, tabpagenr('#')) + + " Try to jump to a closed tab page + tabclose # + call assert_equal(0, tabpagenr('#')) + call feedkeys("g\<Tab>", "xt") + call assert_equal(2, tabpagenr()) + call feedkeys("\<C-Tab>", "xt") + call assert_equal(2, tabpagenr()) + call feedkeys("\<C-W>g\<Tab>", "xt") + call assert_equal(2, tabpagenr()) + call assert_fails('tabnext #', 'E475:') + call assert_equal(2, tabpagenr()) + + " Test for :tabonly # + let wnum = win_getid() + $tabnew + tabonly # + call assert_equal(wnum, win_getid()) + call assert_equal(1, tabpagenr('$')) + + " Test for :tabmove # + tabnew + let wnum = win_getid() + tabnew + tabnew + tabnext 2 + tabmove # + call assert_equal(4, tabpagenr()) + call assert_equal(wnum, win_getid()) + + tabonly! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 15182893e9..e0b05edf15 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -351,7 +351,7 @@ func Test_getsettagstack() " Try to set current index to invalid values call settagstack(1, {'curidx' : -1}) call assert_equal(1, gettagstack().curidx) - call settagstack(1, {'curidx' : 50}) + eval {'curidx' : 50}->settagstack(1) call assert_equal(4, gettagstack().curidx) " Try pushing invalid items onto the stack @@ -641,7 +641,7 @@ func Test_tag_envvar() endfunc " Test for :ptag -func Test_ptag() +func Test_tag_preview() call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "second\tXfile1\t2", \ "third\tXfile1\t3",], @@ -655,11 +655,19 @@ func Test_ptag() call assert_equal(2, winnr('$')) call assert_equal(1, getwinvar(1, '&previewwindow')) call assert_equal(0, getwinvar(2, '&previewwindow')) - wincmd w + wincmd P call assert_equal(3, line('.')) " jump to the tag again + wincmd w ptag third + wincmd P + call assert_equal(3, line('.')) + + " jump to the newer tag + wincmd w + ptag + wincmd P call assert_equal(3, line('.')) " close the preview window @@ -829,8 +837,8 @@ func Test_tag_last_search_pat() %bwipe endfunc -" Test for jumping to a tag when the tag stack is full -func Test_tag_stack_full() +" Tag stack tests +func Test_tag_stack() let l = [] for i in range(10, 31) let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"] @@ -844,6 +852,7 @@ func Test_tag_stack_full() endfor call writefile(l, 'Xfoo') + " Jump to a tag when the tag stack is full. Oldest entry should be removed. enew for i in range(10, 30) exe "tag var" .. i @@ -856,9 +865,15 @@ func Test_tag_stack_full() call assert_equal('var12', l.items[0].tagname) call assert_equal('var31', l.items[19].tagname) - " Jump from the top of the stack + " Use tnext with a single match + call assert_fails('tnext', 'E427:') + + " Jump to newest entry from the top of the stack call assert_fails('tag', 'E556:') + " Pop with zero count from the top of the stack + call assert_fails('0pop', 'E556:') + " Pop from an unsaved buffer enew! call append(1, "sample text") @@ -869,6 +884,13 @@ func Test_tag_stack_full() " Pop all the entries in the tag stack call assert_fails('30pop', 'E555:') + " Pop with a count when already at the bottom of the stack + call assert_fails('exe "normal 4\<C-T>"', 'E555:') + call assert_equal(1, gettagstack().curidx) + + " Jump to newest entry from the bottom of the stack with zero count + call assert_fails('0tag', 'E555:') + " Pop the tag stack when it is empty call settagstack(1, {'items' : []}) call assert_fails('pop', 'E73:') @@ -895,6 +917,7 @@ func Test_tag_multimatch() [CODE] call writefile(code, 'Xfoo') + call settagstack(1, {'items' : []}) tag first tlast call assert_equal(3, line('.')) @@ -903,6 +926,151 @@ func Test_tag_multimatch() call assert_equal(1, line('.')) call assert_fails('tprev', 'E425:') + tlast + call feedkeys("5\<CR>", 't') + tselect first + call assert_equal(2, gettagstack().curidx) + + set ignorecase + tag FIRST + tnext + call assert_equal(2, line('.')) + set ignorecase& + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for previewing multiple matching tags +func Test_preview_tag_multimatch() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1", + \ "first\tXfoo\t2", + \ "first\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int first() {} + int first() {} + [CODE] + call writefile(code, 'Xfoo') + + enew | only + ptag first + ptlast + wincmd P + call assert_equal(3, line('.')) + wincmd w + call assert_fails('ptnext', 'E428:') + ptprev + wincmd P + call assert_equal(2, line('.')) + wincmd w + ptfirst + wincmd P + call assert_equal(1, line('.')) + wincmd w + call assert_fails('ptprev', 'E425:') + ptnext + wincmd P + call assert_equal(2, line('.')) + wincmd w + ptlast + call feedkeys("5\<CR>", 't') + ptselect first + wincmd P + call assert_equal(3, line('.')) + + pclose + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for jumping to multiple matching tags across multiple :tags commands +func Test_tnext_multimatch() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo1\t1", + \ "first\tXfoo2\t1", + \ "first\tXfoo3\t1"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + [CODE] + call writefile(code, 'Xfoo1') + call writefile(code, 'Xfoo2') + call writefile(code, 'Xfoo3') + + tag first + tag first + pop + tnext + tnext + call assert_fails('tnext', 'E428:') + + call delete('Xtags') + call delete('Xfoo1') + call delete('Xfoo2') + call delete('Xfoo3') + set tags& + %bwipe +endfunc + +" Test for jumping to multiple matching tags in non-existing files +func Test_multimatch_non_existing_files() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo1\t1", + \ "first\tXfoo2\t1", + \ "first\tXfoo3\t1"], + \ 'Xtags') + set tags=Xtags + + call settagstack(1, {'items' : []}) + call assert_fails('tag first', 'E429:') + call assert_equal(3, gettagstack().items[0].matchnr) + + call delete('Xtags') + set tags& + %bwipe +endfunc + +func Test_tselect_listing() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:", + \ "first\tXfoo\t2" .. ';"' .. "\tv\ttyperef:typename:char\tfile:"], + \ 'Xtags') + set tags=Xtags + + let code =<< trim [CODE] + static int first; + static char first; + [CODE] + call writefile(code, 'Xfoo') + + call feedkeys("\<CR>", "t") + let l = split(execute("tselect first"), "\n") + let expected =<< [DATA] + # pri kind tag file + 1 FS v first Xfoo + typeref:typename:int + 1 + 2 FS v first Xfoo + typeref:typename:char + 2 +Type number and <Enter> (q or empty cancels): +[DATA] + call assert_equal(expected, l) + call delete('Xtags') call delete('Xfoo') set tags& diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index e830813081..e11815ff33 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -14,7 +14,7 @@ func Test_taglist() split Xtext call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo"), {i, v -> v.name})) - call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo", "Xtext"), {i, v -> v.name})) + call assert_equal(['FFoo', 'BFoo'], map("Foo"->taglist("Xtext"), {i, v -> v.name})) call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo", "Xfoo"), {i, v -> v.name})) call assert_equal(['BFoo', 'FFoo'], map(taglist("Foo", "Xbar"), {i, v -> v.name})) diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index bf0706a0c2..b6be3d8861 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -196,6 +196,143 @@ func Test_text_format() enew! endfunc +func Test_format_c_comment() + new + setl ai cindent tw=40 et fo=croql + let text =<< trim END + var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf + END + call setline(1, text) + normal gql + let expected =<< trim END + var = 2345; // asdf asdf asdf asdf asdf + // asdf asdf asdf asdf asdf + END + call assert_equal(expected, getline(1, '$')) + + %del + let text =<< trim END + var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf + END + call setline(1, text) + normal gql + let expected =<< trim END + var = 2345; // asdf asdf asdf asdf asdf + // asdf asdf asdf asdf asdf + // asdf asdf + END + call assert_equal(expected, getline(1, '$')) + + %del + let text =<< trim END + #if 0 // This is another long end of + // line comment that + // wraps. + END + call setline(1, text) + normal gq2j + let expected =<< trim END + #if 0 // This is another long + // end of line comment + // that wraps. + END + call assert_equal(expected, getline(1, '$')) + + " Using either "o" or "O" repeats a line comment occupying a whole line. + %del + let text =<< trim END + nop; + // This is a comment + val = val; + END + call setline(1, text) + normal 2Go + let expected =<< trim END + nop; + // This is a comment + // + val = val; + END + call assert_equal(expected, getline(1, '$')) + normal 2GO + let expected =<< trim END + nop; + // + // This is a comment + // + val = val; + END + call assert_equal(expected, getline(1, '$')) + + " Using "o" repeats a line comment after a statement, "O" does not. + %del + let text =<< trim END + nop; + val = val; // This is a comment + END + call setline(1, text) + normal 2Go + let expected =<< trim END + nop; + val = val; // This is a comment + // + END + call assert_equal(expected, getline(1, '$')) + + " using 'indentexpr' instead of 'cindent' does not repeat a comment + setl nocindent indentexpr=2 + 3delete + normal 2Gox + let expected =<< trim END + nop; + val = val; // This is a comment + x + END + call assert_equal(expected, getline(1, '$')) + setl cindent indentexpr= + 3delete + + normal 2GO + let expected =<< trim END + nop; + + val = val; // This is a comment + END + call assert_equal(expected, getline(1, '$')) + + " Using "o" does not repeat a comment in a string + %del + let text =<< trim END + nop; + val = " // This is not a comment"; + END + call setline(1, text) + normal 2Gox + let expected =<< trim END + nop; + val = " // This is not a comment"; + x + END + call assert_equal(expected, getline(1, '$')) + + " Using CTRL-U after "o" fixes the indent + %del + let text =<< trim END + { + val = val; // This is a comment + END + call setline(1, text) + exe "normal! 2Go\<C-U>x\<Esc>" + let expected =<< trim END + { + val = val; // This is a comment + x + END + call assert_equal(expected, getline(1, '$')) + + bwipe! +endfunc + " Tests for :right, :center and :left on text with embedded TAB. func Test_format_align() enew! @@ -433,6 +570,21 @@ func Test_format_align() call assert_equal("\t\t Vim", getline(1)) q! + " align text with 'rightleft' + if has('rightleft') + new + call setline(1, 'Vim') + setlocal rightleft + left 20 + setlocal norightleft + call assert_equal("\t\t Vim", getline(1)) + setlocal rightleft + right + setlocal norightleft + call assert_equal("Vim", getline(1)) + close! + endif + set tw& endfunc diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index c259453b5e..2b6bb8b302 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -421,4 +421,36 @@ func Test_textobj_quote() close! endfunc +" Test for i(, i<, etc. when cursor is in front of a block +func Test_textobj_find_paren_forward() + new + + " i< and a> when cursor is in front of a block + call setline(1, '#include <foo.h>') + normal 0yi< + call assert_equal('foo.h', @") + normal 0ya> + call assert_equal('<foo.h>', @") + + " 2i(, 3i( in front of a block enters second/third nested '(' + call setline(1, 'foo (bar (baz (quux)))') + normal 0yi) + call assert_equal('bar (baz (quux))', @") + normal 02yi) + call assert_equal('baz (quux)', @") + normal 03yi) + call assert_equal('quux', @") + + " 3i( in front of a block doesn't enter third but un-nested '(' + call setline(1, 'foo (bar (baz) (quux))') + normal 03di) + call assert_equal('foo (bar (baz) (quux))', getline(1)) + normal 02di) + call assert_equal('foo (bar () (quux))', getline(1)) + normal 0di) + call assert_equal('foo ()', getline(1)) + + close! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index ceaa5de92b..09eed4e10d 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -77,7 +77,7 @@ endfunc func Test_info() let id = timer_start(1000, 'MyHandler') - let info = timer_info(id) + let info = id->timer_info() call assert_equal(id, info[0]['id']) call assert_equal(1000, info[0]['time']) call assert_equal("function('MyHandler')", string(info[0]['callback'])) @@ -113,7 +113,7 @@ func Test_paused() let info = timer_info(id) call assert_equal(0, info[0]['paused']) - call timer_pause(id, 1) + eval id->timer_pause(1) let info = timer_info(id) call assert_equal(1, info[0]['paused']) sleep 200m @@ -148,7 +148,7 @@ func Test_delete_myself() endfunc func StopTimer1(timer) - let g:timer2 = timer_start(10, 'StopTimer2') + let g:timer2 = 10->timer_start('StopTimer2') " avoid maxfuncdepth error call timer_pause(g:timer1, 1) sleep 40m @@ -239,7 +239,7 @@ func FeedAndPeek(timer) endfunc func Interrupt(timer) - " call test_feedinput("\<C-C>") + " eval "\<C-C>"->test_feedinput() call nvim_input("\<C-C>") endfunc @@ -251,7 +251,7 @@ func Test_peek_and_get_char() let intr = timer_start(100, 'Interrupt') let c = getchar() call assert_equal(char2nr('a'), c) - call timer_stop(intr) + eval intr->timer_stop() endfunc func Test_getchar_zero() @@ -279,7 +279,7 @@ func Test_ex_mode() endfunc let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) " This used to throw error E749. - exe "normal Qsleep 100m\rvi\r" + exe "normal gQsleep 100m\rvi\r" call timer_stop(timer) endfunc @@ -379,4 +379,27 @@ func Test_timer_invalid_callback() call assert_fails('call timer_start(0, "0")', 'E921') endfunc +func Test_timer_using_win_execute_undo_sync() + let bufnr1 = bufnr() + new + let g:bufnr2 = bufnr() + let g:winid = win_getid() + exe "buffer " .. bufnr1 + wincmd w + call setline(1, ['test']) + autocmd InsertEnter * call timer_start(100, { -> win_execute(g:winid, 'buffer ' .. g:bufnr2) }) + call timer_start(200, { -> feedkeys("\<CR>bbbb\<Esc>") }) + call feedkeys("Oaaaa", 'x!t') + " will hang here until the second timer fires + call assert_equal(['aaaa', 'bbbb', 'test'], getline(1, '$')) + undo + call assert_equal(['test'], getline(1, '$')) + + bwipe! + bwipe! + unlet g:winid + unlet g:bufnr2 + au! InsertEnter +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index 7e513180a3..adc1745b39 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -1972,6 +1972,29 @@ func Test_builtin_func_error() call assert_equal('jlmnpqrtueghivyzACD', g:Xpath) endfunc -" Modelines {{{1 +func Test_reload_in_try_catch() + call writefile(['x'], 'Xreload') + set autoread + edit Xreload + tabnew + call writefile(['xx'], 'Xreload') + augroup ReLoad + au FileReadPost Xreload let x = doesnotexist + au BufReadPost Xreload let x = doesnotexist + augroup END + try + edit Xreload + catch + endtry + tabnew + + tabclose + tabclose + autocmd! ReLoad + set noautoread + bwipe! Xreload + call delete('Xreload') +endfunc + +" Modeline {{{1 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker -"------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index c7dcaa0f36..30e00e7ad4 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -490,7 +490,7 @@ funct Test_undofile() call delete('Xundodir', 'd') " Test undofile() with 'undodir' set to a non-existing directory. - " call assert_equal('', undofile('Xundofoo')) + " call assert_equal('', 'Xundofoo'->undofile()) if isdirectory('/tmp') set undodir=/tmp diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index 29e578ac6d..e96e3d94e5 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -269,10 +269,10 @@ endfunc func Test_CmdCompletion() call feedkeys(":com -\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"com -addr bang bar buffer complete count nargs range register', @:) + call assert_equal('"com -addr bang bar buffer complete count keepscript nargs range register', @:) call feedkeys(":com -nargs=0 -\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"com -nargs=0 -addr bang bar buffer complete count nargs range register', @:) + call assert_equal('"com -nargs=0 -addr bang bar buffer complete count keepscript nargs range register', @:) call feedkeys(":com -nargs=\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"com -nargs=* + 0 1 ?', @:) @@ -350,7 +350,6 @@ func Test_use_execute_in_completion() 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) @@ -551,3 +550,46 @@ func Test_command_list() call assert_equal("\nNo user-defined commands found", execute(':command Xxx')) call assert_equal("\nNo user-defined commands found", execute('command')) endfunc + +func Test_delcommand_buffer() + command Global echo 'global' + command -buffer OneBuffer echo 'one' + new + command -buffer TwoBuffer echo 'two' + call assert_equal(0, exists(':OneBuffer')) + call assert_equal(2, exists(':Global')) + call assert_equal(2, exists(':TwoBuffer')) + delcommand -buffer TwoBuffer + call assert_equal(0, exists(':TwoBuffer')) + call assert_fails('delcommand -buffer Global', 'E1237:') + call assert_fails('delcommand -buffer OneBuffer', 'E1237:') + bwipe! + call assert_equal(2, exists(':OneBuffer')) + delcommand -buffer OneBuffer + call assert_equal(0, exists(':OneBuffer')) + call assert_fails('delcommand -buffer Global', 'E1237:') + delcommand Global + call assert_equal(0, exists(':Global')) +endfunc + +func DefCmd(name) + if len(a:name) > 30 + return + endif + exe 'command ' .. a:name .. ' call DefCmd("' .. a:name .. 'x")' + echo a:name + exe a:name +endfunc + +func Test_recursive_define() + call DefCmd('Command') + + let name = 'Command' + while len(name) < 30 + exe 'delcommand ' .. name + let name ..= 'x' + endwhile +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index da72da087f..9b010a5dbc 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -1,5 +1,6 @@ " Tests for Unicode manipulations +source check.vim source view_util.vim " Visual block Insert adjusts for multi-byte char @@ -7,7 +8,7 @@ func Test_visual_block_insert() new call setline(1, ["aaa", "ã‚ã‚ã‚", "bbb"]) exe ":norm! gg0l\<C-V>jjIx\<Esc>" - call assert_equal(['axaa', 'xã‚ã‚ã‚', 'bxbb'], getline(1, '$')) + call assert_equal(['axaa', ' xã‚ã‚ã‚', 'bxbb'], getline(1, '$')) bwipeout! endfunc @@ -17,7 +18,7 @@ func Test_strchars() let exp = [[1, 1, 1], [3, 3, 3], [2, 2, 1], [3, 3, 1], [1, 1, 1]] for i in range(len(inp)) call assert_equal(exp[i][0], strchars(inp[i])) - call assert_equal(exp[i][1], strchars(inp[i], 0)) + call assert_equal(exp[i][1], inp[i]->strchars(0)) call assert_equal(exp[i][2], strchars(inp[i], 1)) endfor endfunc @@ -69,7 +70,7 @@ func Test_screenchar_utf8() call setline(1, ["ABC\u0308"]) redraw call assert_equal([0x0041], screenchars(1, 1)) - call assert_equal([0x0042], screenchars(1, 2)) + call assert_equal([0x0042], 1->screenchars(2)) call assert_equal([0x0043, 0x0308], screenchars(1, 3)) call assert_equal("A", screenstring(1, 1)) call assert_equal("B", screenstring(1, 2)) @@ -148,4 +149,55 @@ func Test_print_overlong() bwipe! endfunc +func Test_recording_with_select_mode_utf8() + call Run_test_recording_with_select_mode_utf8() +endfunc + +func Run_test_recording_with_select_mode_utf8() + new + + " No escaping + call feedkeys("qacc12345\<Esc>gH哦\<Esc>q", "tx") + call assert_equal("哦", getline(1)) + call assert_equal("cc12345\<Esc>gH哦\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("哦", getline(1)) + + " 固 is 0xE5 0x9B 0xBA where 0x9B is CSI + call feedkeys("qacc12345\<Esc>gH固\<Esc>q", "tx") + call assert_equal("固", getline(1)) + call assert_equal("cc12345\<Esc>gH固\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("固", getline(1)) + + " å›› is 0xE5 0x9B 0x9B where 0x9B is CSI + call feedkeys("qacc12345\<Esc>gHå››\<Esc>q", "tx") + call assert_equal("å››", getline(1)) + call assert_equal("cc12345\<Esc>gHå››\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("å››", getline(1)) + + " 倒 is 0xE5 0x80 0x92 where 0x80 is K_SPECIAL + call feedkeys("qacc12345\<Esc>gH倒\<Esc>q", "tx") + call assert_equal("倒", getline(1)) + call assert_equal("cc12345\<Esc>gH倒\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("倒", getline(1)) + + bwipe! +endfunc + +" This must be done as one of the last tests, because it starts the GUI, which +" cannot be undone. +func Test_zz_recording_with_select_mode_utf8_gui() + CheckCanRunGui + + gui -f + call Run_test_recording_with_select_mode_utf8() +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_utf8_comparisons.vim b/src/nvim/testdir/test_utf8_comparisons.vim index fdf9d80802..f3c86b44fb 100644 --- a/src/nvim/testdir/test_utf8_comparisons.vim +++ b/src/nvim/testdir/test_utf8_comparisons.vim @@ -1,12 +1,12 @@ " Tests for case-insensitive UTF-8 comparisons (utf_strnicmp() in mbyte.c) " Also test "g~ap". -function! Ch(a, op, b, expected) +func Ch(a, op, b, expected) call assert_equal(eval(printf('"%s" %s "%s"', a:a, a:op, a:b)), a:expected, \ printf('"%s" %s "%s" should return %d', a:a, a:op, a:b, a:expected)) -endfunction +endfunc -function! Chk(a, b, result) +func Chk(a, b, result) if a:result == 0 call Ch(a:a, '==?', a:b, 1) call Ch(a:a, '!=?', a:b, 0) @@ -86,6 +86,9 @@ endfunc " test that g~ap changes one paragraph only. func Test_gap() new - call feedkeys("iabcd\n\ndefggg0g~ap", "tx") + " setup text + call feedkeys("iabcd\<cr>\<cr>defg", "tx") + " modify only first line + call feedkeys("gg0g~ap", "tx") call assert_equal(["ABCD", "", "defg"], getline(1,3)) endfunc diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim index 2fbf130345..017bb6675d 100644 --- a/src/nvim/testdir/test_vartabs.vim +++ b/src/nvim/testdir/test_vartabs.vim @@ -135,7 +135,17 @@ func Test_vartabs() bwipeout! endfunc -func! Test_vartabs_breakindent() +func Test_retab_invalid_arg() + new + call setline(1, "\ttext") + retab 0 + call assert_fails("retab -8", 'E487: Argument must be positive') + call assert_fails("retab 10000", 'E475:') + call assert_fails("retab 720575940379279360", 'E475:') + bwipe! +endfunc + +func Test_vartabs_breakindent() if !exists("+breakindent") return endif @@ -330,7 +340,7 @@ func Test_vartabs_shiftwidth() let lines = ScreenLines([1, 2], winwidth(0)) call s:compare_lines(expect2, lines) call assert_equal(20, shiftwidth(virtcol('.')-2)) - call assert_equal(30, shiftwidth(virtcol('.'))) + call assert_equal(30, virtcol('.')->shiftwidth()) norm! $>> let expect3 = [' ', ' x ', '~ '] let lines = ScreenLines([1, 3], winwidth(0)) diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 34733f127f..f93eb6e274 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1,6 +1,9 @@ " Test various aspects of the Vim script language. " Most of this was formerly in test49. +source check.vim +source shared.vim + "------------------------------------------------------------------------------- " Test environment {{{1 "------------------------------------------------------------------------------- @@ -25,7 +28,7 @@ com! -nargs=1 Xout call Xout(<args>) " in the variable argument list. This function is useful if similar tests are " to be made for a ":return" from a function call or a ":finish" in a script " file. -function! MakeScript(funcname, ...) +func MakeScript(funcname, ...) let script = tempname() execute "redir! >" . script execute "function" a:funcname @@ -1156,6 +1159,82 @@ func Test_type() call assert_equal(v:t_list, type(v:_null_list)) call assert_equal(v:t_dict, type(v:_null_dict)) call assert_equal(v:t_blob, type(v:_null_blob)) + + call assert_equal(0, 0 + v:false) + call assert_equal(1, 0 + v:true) + " call assert_equal(0, 0 + v:none) + call assert_equal(0, 0 + v:null) + + call assert_equal('false', '' . v:false) + call assert_equal('true', '' . v:true) + " call assert_equal('none', '' . v:none) + call assert_equal('null', '' . v:null) + + call assert_true(v:false == 0) + call assert_false(v:false != 0) + call assert_true(v:true == 1) + call assert_false(v:true != 1) + call assert_false(v:true == v:false) + call assert_true(v:true != v:false) + + call assert_true(v:null == 0) + call assert_false(v:null != 0) + " call assert_true(v:none == 0) + " call assert_false(v:none != 0) + + call assert_true(v:false is v:false) + call assert_true(v:true is v:true) + " call assert_true(v:none is v:none) + call assert_true(v:null is v:null) + + call assert_false(v:false isnot v:false) + call assert_false(v:true isnot v:true) + " call assert_false(v:none isnot v:none) + call assert_false(v:null isnot v:null) + + call assert_false(v:false is 0) + call assert_false(v:true is 1) + call assert_false(v:true is v:false) + " call assert_false(v:none is 0) + call assert_false(v:null is 0) + " call assert_false(v:null is v:none) + + call assert_true(v:false isnot 0) + call assert_true(v:true isnot 1) + call assert_true(v:true isnot v:false) + " call assert_true(v:none isnot 0) + call assert_true(v:null isnot 0) + " call assert_true(v:null isnot v:none) + + call assert_equal(v:false, eval(string(v:false))) + call assert_equal(v:true, eval(string(v:true))) + " call assert_equal(v:none, eval(string(v:none))) + call assert_equal(v:null, eval(string(v:null))) + + call assert_equal(v:false, copy(v:false)) + call assert_equal(v:true, copy(v:true)) + " call assert_equal(v:none, copy(v:none)) + call assert_equal(v:null, copy(v:null)) + + call assert_equal([v:false], deepcopy([v:false])) + call assert_equal([v:true], deepcopy([v:true])) + " call assert_equal([v:none], deepcopy([v:none])) + call assert_equal([v:null], deepcopy([v:null])) + + call assert_true(empty(v:false)) + call assert_false(empty(v:true)) + call assert_true(empty(v:null)) + " call assert_true(empty(v:none)) + + func ChangeYourMind() + try + return v:true + finally + return 'something else' + endtry + endfunc + + call ChangeYourMind() endfunc "------------------------------------------------------------------------------- @@ -1668,7 +1747,7 @@ func Test_function_defined_line() [CODE] call writefile(lines, 'Xtest.vim') - let res = system(v:progpath .. ' --clean -es -X -S Xtest.vim') + let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim') call assert_equal(0, v:shell_error) let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*') @@ -1692,6 +1771,26 @@ func Test_function_defined_line() call delete('Xtest.vim') endfunc +func Test_for_over_string() + let res = '' + for c in 'aécÌ€d' + let res ..= c .. '-' + endfor + call assert_equal('a-é-cÌ€-d-', res) + + let res = '' + for c in '' + let res ..= c .. '-' + endfor + call assert_equal('', res) + + let res = '' + for c in v:_null_string + let res ..= c .. '-' + endfor + call assert_equal('', res) +endfunc + "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 8f992f7501..250b896532 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -81,6 +81,133 @@ func Test_edit_change() normal Cx call assert_equal('x', getline(1)) bwipe! + set virtualedit= +endfunc + +" Tests for pasting at the beginning, end and middle of a tab character +" in virtual edit mode. +func Test_paste_in_tab() + new + call append(0, '') + set virtualedit=all + + " Tests for pasting a register with characterwise mode type + call setreg('"', 'xyz', 'c') + + " paste (p) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal p + call assert_equal('a xyz b', getline(1)) + + " paste (P) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal P + call assert_equal("axyz\tb", getline(1)) + + " paste (p) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal p + call assert_equal("a\txyzb", getline(1)) + + " paste (P) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal P + call assert_equal('a xyz b', getline(1)) + + " Tests for pasting a register with blockwise mode type + call setreg('"', 'xyz', 'b') + + " paste (p) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal p + call assert_equal('a xyz b', getline(1)) + + " paste (P) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal P + call assert_equal("axyz\tb", getline(1)) + + " paste (p) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal p + call assert_equal("a\txyzb", getline(1)) + + " paste (P) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal P + call assert_equal('a xyz b', getline(1)) + + " Tests for pasting with gp and gP in virtual edit mode + + " paste (gp) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal gp + call assert_equal('a xyz b', getline(1)) + call assert_equal([0, 1, 12, 0, 12], getcurpos()) + + " paste (gP) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal gP + call assert_equal("axyz\tb", getline(1)) + call assert_equal([0, 1, 5, 0, 5], getcurpos()) + + " paste (gp) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal gp + call assert_equal("a\txyzb", getline(1)) + call assert_equal([0, 1, 6, 0, 12], getcurpos()) + + " paste (gP) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal gP + call assert_equal('a xyz b', getline(1)) + call assert_equal([0, 1, 12, 0, 12], getcurpos()) + + " Tests for pasting a named register + let @r = 'xyz' + + " paste (gp) named register in the middle of a tab + call setline(1, "a\tb") + call cursor(1, 2, 2) + normal "rgp + call assert_equal('a xyz b', getline(1)) + call assert_equal([0, 1, 8, 0, 8], getcurpos()) + + " paste (gP) named register in the middle of a tab + call setline(1, "a\tb") + call cursor(1, 2, 2) + normal "rgP + call assert_equal('a xyz b', getline(1)) + call assert_equal([0, 1, 7, 0, 7], getcurpos()) + + bwipe! + set virtualedit= +endfunc + +" Test for yanking a few spaces within a tab to a register +func Test_yank_in_tab() + new + let @r = '' + call setline(1, "a\tb") + set virtualedit=all + call cursor(1, 2, 2) + normal "ry5l + call assert_equal(' ', @r) + + bwipe! + set virtualedit= endfunc " Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword". @@ -216,4 +343,139 @@ func Test_yank_paste_small_del_reg() set virtualedit= endfunc +" After calling s:TryVirtualeditReplace(), line 1 will contain one of these +" two strings, depending on whether virtual editing is on or off. +let s:result_ve_on = 'a x' +let s:result_ve_off = 'x' + +" Utility function for Test_global_local_virtualedit() +func s:TryVirtualeditReplace() + call setline(1, 'a') + normal gg7l + normal rx +endfunc + +" Test for :set and :setlocal +func Test_global_local_virtualedit() + new + + " Verify that 'virtualedit' is initialized to empty, can be set globally to + " all and to empty, and can be set locally to all and to empty. + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + set ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + set ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + " Verify that :set affects multiple windows. + split + set ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + wincmd p + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + set ve= + wincmd p + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + bwipe! + + " Verify that :setlocal affects only the current window. + new + split + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + wincmd p + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + bwipe! + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + " Verify that the buffer 'virtualedit' state follows the global value only + " when empty and that "none" works as expected. + " + " 'virtualedit' State + " +--------+--------------------------+ + " | Local | Global | + " | | | + " +--------+--------+--------+--------+ + " | | "" | "all" | "none" | + " +--------+--------+--------+--------+ + " | "" | off | on | off | + " | "all" | on | on | on | + " | "none" | off | off | off | + " +--------+--------+--------+--------+ + new + + setglobal ve= + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=none + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + setglobal ve=all + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=none + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=NONE + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + setglobal ve=none + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=none + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + bwipe! + + " Verify that the 'virtualedit' state is copied to new windows. + new + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + split + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + split + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve= + split + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + bwipe! + + setlocal virtualedit& + set virtualedit& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index dbabdcf427..ae8f9ba70b 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -22,6 +22,14 @@ func Test_block_shift_overflow() q! endfunc +func Test_dotregister_paste() + new + exe "norm! ihello world\<esc>" + norm! 0ve".p + call assert_equal('hello world world', getline(1)) + q! +endfunc + func Test_Visual_ctrl_o() new call setline(1, ['one', 'two', 'three']) @@ -42,14 +50,6 @@ func Test_Visual_vapo() bwipe! endfunc -func Test_dotregister_paste() - new - exe "norm! ihello world\<esc>" - norm! 0ve".p - call assert_equal('hello world world', getline(1)) - q! -endfunc - func Test_Visual_inner_quote() new normal oxX @@ -57,9 +57,37 @@ func Test_Visual_inner_quote() bwipe! endfunc +" Test for Visual mode not being reset causing E315 error. +func TriggerTheProblem() + " At this point there is no visual selection because :call reset it. + " Let's restore the selection: + normal gv + '<,'>del _ + try + exe "normal \<Esc>" + catch /^Vim\%((\a\+)\)\=:E315/ + echom 'Snap! E315 error!' + let g:msg = 'Snap! E315 error!' + endtry +endfunc + +func Test_visual_mode_reset() + enew + let g:msg = "Everything's fine." + enew + setl buftype=nofile + call append(line('$'), 'Delete this line.') + + " NOTE: this has to be done by a call to a function because executing :del + " the ex-way will require the colon operator which resets the visual mode + " thus preventing the problem: + exe "normal! GV:call TriggerTheProblem()\<CR>" + call assert_equal("Everything's fine.", g:msg) +endfunc + " Test for visual block shift and tab characters. func Test_block_shift_tab() - enew! + new call append(0, repeat(['one two three'], 5)) call cursor(1,1) exe "normal i\<C-G>u" @@ -68,7 +96,7 @@ func Test_block_shift_tab() call assert_equal('on1 two three', getline(2)) call assert_equal('on1 two three', getline(5)) - enew! + %d _ call append(0, repeat(['abcdefghijklmnopqrstuvwxyz'], 5)) call cursor(1,1) exe "normal \<C-V>4jI \<Esc>j<<11|D" @@ -93,12 +121,26 @@ func Test_block_shift_tab() call assert_equal(" abc\<Tab>\<Tab>defghijklmnopqrstuvwxyz", getline(4)) call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5)) - enew! + " Test for block shift with space characters at the beginning and with + " 'noexpandtab' and 'expandtab' + %d _ + call setline(1, [" 1", " 2", " 3"]) + setlocal shiftwidth=2 noexpandtab + exe "normal gg\<C-V>3j>" + call assert_equal(["\t1", "\t2", "\t3"], getline(1, '$')) + %d _ + call setline(1, [" 1", " 2", " 3"]) + setlocal shiftwidth=2 expandtab + exe "normal gg\<C-V>3j>" + call assert_equal([" 1", " 2", " 3"], getline(1, '$')) + setlocal shiftwidth& + + bw! endfunc " Tests Blockwise Visual when there are TABs before the text. func Test_blockwise_visual() - enew! + new call append(0, ['123456', \ '234567', \ '345678', @@ -120,31 +162,35 @@ func Test_blockwise_visual() \ "\t\tsomext", \ "\t\ttesext"], getline(1, 7)) - enew! + bw! endfunc " Test swapping corners in blockwise visual mode with o and O func Test_blockwise_visual_o_O() - enew! + new exe "norm! 10i.\<Esc>Y4P3lj\<C-V>4l2jr " exe "norm! gvO\<Esc>ra" exe "norm! gvO\<Esc>rb" exe "norm! gvo\<C-c>rc" exe "norm! gvO\<C-c>rd" + set selection=exclusive + exe "norm! gvOo\<C-c>re" + call assert_equal('...a be.', getline(4)) + exe "norm! gvOO\<C-c>rf" + set selection& call assert_equal(['..........', \ '...c d..', \ '... ..', - \ '...a b..', + \ '...a bf.', \ '..........'], getline(1, '$')) - enew! + bw! endfun " Test Virtual replace mode. func Test_virtual_replace() - throw 'skipped: TODO: ' if exists('&t_kD') let save_t_kD = &t_kD endif @@ -166,7 +212,6 @@ func Test_virtual_replace() \ ], getline(1, 6)) normal G mark a - inoremap <C-D> <Del> exe "normal o0\<C-D>\nabcdefghi\njk\tlmn\n opq\trst\n\<C-D>uvwxyz\n" exe "normal 'ajgR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR" . repeat("\<BS>", 29) call assert_equal([' 1', @@ -244,35 +289,6 @@ func Test_virtual_replace2() set bs&vim endfunc -" Test for Visual mode not being reset causing E315 error. -func TriggerTheProblem() - " At this point there is no visual selection because :call reset it. - " Let's restore the selection: - normal gv - '<,'>del _ - try - exe "normal \<Esc>" - catch /^Vim\%((\a\+)\)\=:E315/ - echom 'Snap! E315 error!' - let g:msg = 'Snap! E315 error!' - endtry -endfunc - -func Test_visual_mode_reset() - enew - let g:msg = "Everything's fine." - enew - setl buftype=nofile - call append(line('$'), 'Delete this line.') - - " NOTE: this has to be done by a call to a function because executing :del - " the ex-way will require the colon operator which resets the visual mode - " thus preventing the problem: - exe "normal! GV:call TriggerTheProblem()\<CR>" - call assert_equal("Everything's fine.", g:msg) - -endfunc - func Test_Visual_word_textobject() new call setline(1, ['First sentence. Second sentence.']) @@ -351,17 +367,6 @@ func Test_Visual_sentence_textobject() bwipe! endfunc -func Test_curswant_not_changed() - new - call setline(1, ['one', 'two']) - au InsertLeave * call getcurpos() - call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt') - call assert_equal([0, 2, 1, 0, 1], getcurpos()) - - bwipe! - au! InsertLeave -endfunc - func Test_Visual_paragraph_textobject() new call setline(1, ['First line.', @@ -411,6 +416,17 @@ func Test_Visual_paragraph_textobject() bwipe! endfunc +func Test_curswant_not_changed() + new + call setline(1, ['one', 'two']) + au InsertLeave * call getcurpos() + call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt') + call assert_equal([0, 2, 1, 0, 1], getcurpos()) + + bwipe! + au! InsertLeave +endfunc + " Tests for "vaBiB", end could be wrong. func Test_Visual_Block() new @@ -435,13 +451,15 @@ func Test_Visual_Block() close! endfunc -func Test_visual_put_in_block() +" Test for 'p'ut in visual block mode +func Test_visual_block_put() 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! + call append(0, ['One', 'Two', 'Three']) + normal gg + yank + call feedkeys("jl\<C-V>ljp", 'xt') + call assert_equal(['One', 'T', 'Tee', 'One', ''], getline(1, '$')) + bw! endfunc " Visual modes (v V CTRL-V) followed by an operator; count; repeating @@ -612,6 +630,17 @@ func Test_characterwise_visual_mode() normal Gkvj$d call assert_equal(['', 'a', ''], getline(1, '$')) + " characterwise visual mode: replace a single character line and the eol + %d _ + call setline(1, "a") + normal v$rx + call assert_equal(['x'], getline(1, '$')) + + " replace a character with composing characters + call setline(1, "xã̳x") + normal gg0lvrb + call assert_equal("xbx", getline(1)) + bwipe! endfunc @@ -647,6 +676,16 @@ func Test_characterwise_select_mode() exe "normal Gkgh\<Down>\<End>\<Del>" call assert_equal(['', 'a', ''], getline(1, '$')) + " CTRL-H in select mode behaves like 'x' + call setline(1, 'abcdef') + exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>" + call assert_equal('ef', getline(1)) + + " CTRL-O in select mode switches to visual mode for one command + call setline(1, 'abcdef') + exe "normal! gggh\<C-O>3lm" + call assert_equal('mef', getline(1)) + sunmap <lt>End> sunmap <lt>Down> sunmap <lt>Del> @@ -746,8 +785,7 @@ endfunc func Test_visual_block_mode() new call append(0, '') - call setline(1, ['abcdefghijklm', 'abcdefghijklm', 'abcdefghijklm', - \ 'abcdefghijklm', 'abcdefghijklm']) + call setline(1, repeat(['abcdefghijklm'], 5)) call cursor(1, 1) " Test shift-right of a block @@ -766,6 +804,76 @@ func Test_visual_block_mode() \ 'axyzqqqqefgmnoklm', \ 'abcdqqqqijklm'], getline(1, 5)) + " Test 'C' to change till the end of the line + call cursor(3, 4) + exe "normal! \<C-V>j3lCooo" + call assert_equal(['axyooo', 'axyooo'], getline(3, 4)) + + " Test 'D' to delete till the end of the line + call cursor(3, 3) + exe "normal! \<C-V>j2lD" + call assert_equal(['ax', 'ax'], getline(3, 4)) + + " Test block insert with a short line that ends before the block + %d _ + call setline(1, [" one", "a", " two"]) + exe "normal gg\<C-V>2jIx" + call assert_equal([" xone", "a", " xtwo"], getline(1, '$')) + + " Test block append at EOL with '$' and without '$' + %d _ + call setline(1, ["one", "a", "two"]) + exe "normal gg$\<C-V>2jAx" + call assert_equal(["onex", "ax", "twox"], getline(1, '$')) + %d _ + call setline(1, ["one", "a", "two"]) + exe "normal gg3l\<C-V>2jAx" + call assert_equal(["onex", "a x", "twox"], getline(1, '$')) + + " Test block replace with an empty line in the middle and use $ to jump to + " the end of the line. + %d _ + call setline(1, ['one', '', 'two']) + exe "normal gg$\<C-V>2jrx" + call assert_equal(["onx", "", "twx"], getline(1, '$')) + + " Test block replace with an empty line in the middle and move cursor to the + " end of the line + %d _ + call setline(1, ['one', '', 'two']) + exe "normal gg2l\<C-V>2jrx" + call assert_equal(["onx", "", "twx"], getline(1, '$')) + + " Replace odd number of characters with a multibyte character + %d _ + call setline(1, ['abcd', 'efgh']) + exe "normal ggl\<C-V>2ljr\u1100" + call assert_equal(["a\u1100 ", "e\u1100 "], getline(1, '$')) + + " During visual block append, if the cursor moved outside of the selected + " range, then the edit should not be applied to the block. + %d _ + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "normal 2G\<C-V>jAx\<Up>" + call assert_equal(['aaa', 'bxbb', 'ccc'], getline(1, '$')) + + " During visual block append, if the cursor is moved before the start of the + " block, then the new text should be appended there. + %d _ + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "normal $\<C-V>2jA\<Left>x" + call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$')) + " Repeat the previous test but use 'l' to move the cursor instead of '$' + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "normal! gg2l\<C-V>2jA\<Left>x" + call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$')) + + " Change a characterwise motion to a blockwise motion using CTRL-V + %d _ + call setline(1, ['123', '456', '789']) + exe "normal ld\<C-V>j" + call assert_equal(['13', '46', '789'], getline(1, '$')) + " Test from ':help v_b_I_example' %d _ setlocal tabstop=8 shiftwidth=4 @@ -997,6 +1105,15 @@ func Test_block_insert_replace_tabs() bwipe! 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 + func Test_visual_put_in_block_using_zp() new " paste using zP @@ -1092,6 +1209,13 @@ func Test_visual_put_blockedit_zy_and_zp() bw! endfunc +func Test_visual_block_yank_zy() + new + " this was reading before the start of the line + exe "norm o\<C-T>\<Esc>\<C-V>zy" + bwipe! +endfunc + func Test_visual_block_with_virtualedit() CheckScreendump @@ -1106,11 +1230,154 @@ func Test_visual_block_with_virtualedit() call term_sendkeys(buf, "\<C-V>gg$") call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit', {}) + call term_sendkeys(buf, "\<Esc>gg\<C-V>G$") + call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit2', {}) + " clean up call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('XTest_beval') + call delete('XTest_block') +endfunc + +func Test_visual_block_ctrl_w_f() + " Emtpy block selected in new buffer should not result in an error. + au! BufNew foo sil norm f + edit foo + + au! BufNew +endfunc + +func Test_visual_block_append_invalid_char() + " this was going over the end of the line + new + call setline(1, [' let xxx', 'xxxxxˆ', 'xxxxxxxxxxx']) + exe "normal 0\<C-V>jjA-\<Esc>" + call assert_equal([' - let xxx', 'xxxxx -ˆ', 'xxxxxxxx-xxx'], getline(1, 3)) + bwipe! +endfunc + +func Test_visual_reselect_with_count() + " this was causing an illegal memory access + let lines =<< trim END + + + + : + r<sfile> + exe "%norm e3\<c-v>kr\t" + : + + : + END + call writefile(lines, 'XvisualReselect') + source XvisualReselect + + bwipe! + call delete('XvisualReselect') +endfunc + +func Test_visual_block_insert_round_off() + new + " The number of characters are tuned to fill a 4096 byte allocated block, + " so that valgrind reports going over the end. + call setline(1, ['xxxxx', repeat('0', 1350), "\t", repeat('x', 60)]) + exe "normal gg0\<C-V>GI" .. repeat('0', 1320) .. "\<Esc>" + bwipe! endfunc +" this was causing an ml_get error +func Test_visual_exchange_windows() + enew! + new + call setline(1, ['foo', 'bar']) + exe "normal G\<C-V>gg\<C-W>\<C-X>OO\<Esc>" + bwipe! + bwipe! +endfunc + +" this was leaving the end of the Visual area beyond the end of a line +func Test_visual_ex_copy_line() + new + call setline(1, ["aaa", "bbbbbbbbbxbb"]) + /x + exe "normal ggvjfxO" + t0 + normal gNU + bwipe! +endfunc + +" This was leaving the end of the Visual area beyond the end of a line. +" Set 'undolevels' to start a new undo block. +func Test_visual_undo_deletes_last_line() + new + call setline(1, ["aaa", "ccc", "dyd"]) + set undolevels=100 + exe "normal obbbbbbbbbxbb\<Esc>" + set undolevels=100 + /y + exe "normal ggvjfxO" + undo + normal gNU + + bwipe! +endfunc + +func Test_visual_paste() + new + + " v_p overwrites unnamed register. + call setline(1, ['xxxx']) + call setreg('"', 'foo') + call setreg('-', 'bar') + normal gg0vp + call assert_equal('x', @") + call assert_equal('x', @-) + call assert_equal('fooxxx', getline(1)) + normal $vp + call assert_equal('x', @") + call assert_equal('x', @-) + call assert_equal('fooxxx', getline(1)) + " Test with a different register as unnamed register. + call setline(2, ['baz']) + normal 2gg0"rD + call assert_equal('baz', @") + normal gg0vp + call assert_equal('f', @") + call assert_equal('f', @-) + call assert_equal('bazooxxx', getline(1)) + normal $vp + call assert_equal('x', @") + call assert_equal('x', @-) + call assert_equal('bazooxxf', getline(1)) + + if has('clipboard') + " v_P does not overwrite unnamed register. + call setline(1, ['xxxx']) + call setreg('"', 'foo') + call setreg('-', 'bar') + normal gg0vP + call assert_equal('foo', @") + call assert_equal('x', @-) + call assert_equal('fooxxx', getline(1)) + normal $vP + call assert_equal('foo', @") + call assert_equal('x', @-) + call assert_equal('fooxxfoo', getline(1)) + " Test with a different register as unnamed register. + call setline(2, ['baz']) + normal 2gg0"rD + call assert_equal('baz', @") + normal gg0vP + call assert_equal('baz', @") + call assert_equal('f', @-) + call assert_equal('bazooxxfoo', getline(1)) + normal $vP + call assert_equal('baz', @") + call assert_equal('o', @-) + call assert_equal('bazooxxfobaz', getline(1)) + endif + + bwipe! +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim index 7f5b80e8d3..f4878c2397 100644 --- a/src/nvim/testdir/test_winbuf_close.vim +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -194,3 +194,22 @@ func Test_tabwin_close() call assert_true(v:true) %bwipe! endfunc + +" Test when closing a split window (above/below) restores space to the window +" below when 'noequalalways' and 'splitright' are set. +func Test_window_close_splitright_noequalalways() + set noequalalways + set splitright + new + let w1 = win_getid() + new + let w2 = win_getid() + execute "normal \<c-w>b" + let h = winheight(0) + let w = win_getid() + new + q + call assert_equal(h, winheight(0), "Window height does not match eight before opening and closing another window") + call assert_equal(w, win_getid(), "Did not return to original window after opening and closing a window") +endfunc + diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 039de0c623..ef6dec580f 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -72,7 +72,7 @@ endfunc func Test_window_quit() e Xa split Xb - call assert_equal(2, winnr('$')) + call assert_equal(2, '$'->winnr()) call assert_equal('Xb', bufname(winbufnr(1))) call assert_equal('Xa', bufname(winbufnr(2))) @@ -88,7 +88,7 @@ func Test_window_horizontal_split() 3wincmd s call assert_equal(2, winnr('$')) call assert_equal(3, winheight(0)) - call assert_equal(winwidth(1), winwidth(2)) + call assert_equal(winwidth(1), 2->winwidth()) call assert_fails('botright topleft wincmd s', 'E442:') bw @@ -267,7 +267,7 @@ func Test_window_height() wincmd + call assert_equal(wh1, winheight(1)) - call assert_equal(wh2, winheight(2)) + call assert_equal(wh2, 2->winheight()) 2wincmd _ call assert_equal(2, winheight(1)) @@ -452,7 +452,7 @@ func Test_window_newtab() wincmd T call assert_equal(2, tabpagenr('$')) call assert_equal(['Xb', 'Xa'], map(tabpagebuflist(1), 'bufname(v:val)')) - call assert_equal(['Xc' ], map(tabpagebuflist(2), 'bufname(v:val)')) + call assert_equal(['Xc' ], map(2->tabpagebuflist(), 'bufname(v:val)')) %bw! endfunc @@ -575,14 +575,17 @@ func Test_winrestcmd() only endfunc -function! Fun_RenewFile() +func Fun_RenewFile() " Need to wait a bit for the timestamp to be older. - sleep 2 - silent execute '!echo "1" > tmp.txt' + let old_ftime = getftime("tmp.txt") + while getftime("tmp.txt") == old_ftime + sleep 100m + silent execute '!echo "1" > tmp.txt' + endwhile sp wincmd p edit! tmp.txt -endfunction +endfunc func Test_window_prevwin() " Can we make this work on MS-Windows? @@ -814,13 +817,25 @@ func Test_winnr() tabnew call assert_equal(8, tabpagewinnr(1, 'j')) - call assert_equal(2, tabpagewinnr(1, 'k')) + call assert_equal(2, 1->tabpagewinnr('k')) call assert_equal(4, tabpagewinnr(1, 'h')) call assert_equal(6, tabpagewinnr(1, 'l')) only | tabonly endfunc +func Test_winrestview() + split runtest.vim + normal 50% + let view = winsaveview() + close + split runtest.vim + eval view->winrestview() + call assert_equal(view, winsaveview()) + + bwipe! +endfunc + func Test_win_splitmove() edit a leftabove split b @@ -924,4 +939,124 @@ func Test_window_resize() %bwipe! endfunc +func Test_win_move_separator() + edit a + leftabove vsplit b + let w = winwidth(0) + " check win_move_separator from left window on left window + call assert_equal(1, winnr()) + for offset in range(5) + call assert_true(win_move_separator(0, offset)) + call assert_equal(w + offset, winwidth(0)) + call assert_true(0->win_move_separator(-offset)) + call assert_equal(w, winwidth(0)) + endfor + " check win_move_separator from right window on left window number + wincmd l + call assert_notequal(1, winnr()) + for offset in range(5) + call assert_true(1->win_move_separator(offset)) + call assert_equal(w + offset, winwidth(1)) + call assert_true(win_move_separator(1, -offset)) + call assert_equal(w, winwidth(1)) + endfor + " check win_move_separator from right window on left window ID + let id = win_getid(1) + for offset in range(5) + call assert_true(win_move_separator(id, offset)) + call assert_equal(w + offset, winwidth(id)) + call assert_true(id->win_move_separator(-offset)) + call assert_equal(w, winwidth(id)) + endfor + " check win_move_separator from right window on right window is no-op + let w0 = winwidth(0) + call assert_true(win_move_separator(0, 1)) + call assert_equal(w0, winwidth(0)) + call assert_true(win_move_separator(0, -1)) + call assert_equal(w0, winwidth(0)) + " check that win_move_separator doesn't error with offsets beyond moving + " possibility + call assert_true(win_move_separator(id, 5000)) + call assert_true(winwidth(id) > w) + call assert_true(win_move_separator(id, -5000)) + call assert_true(winwidth(id) < w) + " check that win_move_separator returns false for an invalid window + wincmd = + let w = winwidth(0) + call assert_false(win_move_separator(-1, 1)) + call assert_equal(w, winwidth(0)) + " check that win_move_separator returns false for a floating window + let id = nvim_open_win( + \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) + let w = winwidth(id) + call assert_false(win_move_separator(id, 1)) + call assert_equal(w, winwidth(id)) + call nvim_win_close(id, 1) + %bwipe! +endfunc + +func Test_win_move_statusline() + redraw " This test fails in Nvim without a redraw to clear messages. + edit a + leftabove split b + let h = winheight(0) + " check win_move_statusline from top window on top window + call assert_equal(1, winnr()) + for offset in range(5) + call assert_true(win_move_statusline(0, offset)) + call assert_equal(h + offset, winheight(0)) + call assert_true(0->win_move_statusline(-offset)) + call assert_equal(h, winheight(0)) + endfor + " check win_move_statusline from bottom window on top window number + wincmd j + call assert_notequal(1, winnr()) + for offset in range(5) + call assert_true(1->win_move_statusline(offset)) + call assert_equal(h + offset, winheight(1)) + call assert_true(win_move_statusline(1, -offset)) + call assert_equal(h, winheight(1)) + endfor + " check win_move_statusline from bottom window on bottom window + let h0 = winheight(0) + for offset in range(5) + call assert_true(0->win_move_statusline(-offset)) + call assert_equal(h0 - offset, winheight(0)) + call assert_equal(1 + offset, &cmdheight) + call assert_true(win_move_statusline(0, offset)) + call assert_equal(h0, winheight(0)) + call assert_equal(1, &cmdheight) + endfor + call assert_true(win_move_statusline(0, 1)) + call assert_equal(h0, winheight(0)) + call assert_equal(1, &cmdheight) + " check win_move_statusline from bottom window on top window ID + let id = win_getid(1) + for offset in range(5) + call assert_true(win_move_statusline(id, offset)) + call assert_equal(h + offset, winheight(id)) + call assert_true(id->win_move_statusline(-offset)) + call assert_equal(h, winheight(id)) + endfor + " check that win_move_statusline doesn't error with offsets beyond moving + " possibility + call assert_true(win_move_statusline(id, 5000)) + call assert_true(winheight(id) > h) + call assert_true(win_move_statusline(id, -5000)) + call assert_true(winheight(id) < h) + " check that win_move_statusline returns false for an invalid window + wincmd = + let h = winheight(0) + call assert_false(win_move_statusline(-1, 1)) + call assert_equal(h, winheight(0)) + " check that win_move_statusline returns false for a floating window + let id = nvim_open_win( + \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) + let h = winheight(id) + call assert_false(win_move_statusline(id, 1)) + call assert_equal(h, winheight(id)) + call nvim_win_close(id, 1) + %bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_window_id.vim b/src/nvim/testdir/test_window_id.vim index d10d831650..8bf4ede350 100644 --- a/src/nvim/testdir/test_window_id.vim +++ b/src/nvim/testdir/test_window_id.vim @@ -67,7 +67,7 @@ func Test_win_getid() call win_gotoid(id2) call assert_equal("two", expand("%")) - call win_gotoid(id4) + eval id4->win_gotoid() call assert_equal("four", expand("%")) call win_gotoid(id1) call assert_equal("one", expand("%")) @@ -75,17 +75,17 @@ func Test_win_getid() call assert_equal("five", expand("%")) call assert_equal(0, win_id2win(9999)) - call assert_equal(nr5, win_id2win(id5)) + call assert_equal(nr5, id5->win_id2win()) call assert_equal(0, win_id2win(id1)) tabnext call assert_equal(nr1, win_id2win(id1)) call assert_equal([0, 0], win_id2tabwin(9999)) - call assert_equal([1, nr2], win_id2tabwin(id2)) + call assert_equal([1, nr2], id2->win_id2tabwin()) call assert_equal([2, nr4], win_id2tabwin(id4)) call assert_equal([], win_findbuf(9999)) - call assert_equal([id2], win_findbuf(bufnr2)) + call assert_equal([id2], bufnr2->win_findbuf()) call win_gotoid(id5) split call assert_equal(sort([id5, win_getid()]), sort(win_findbuf(bufnr5))) @@ -98,7 +98,7 @@ func Test_win_getid_curtab() tabfirst copen only - call assert_equal(win_getid(1), win_getid(1, 1)) + call assert_equal(win_getid(1), 1->win_getid( 1)) tabclose! endfunc @@ -120,4 +120,11 @@ func Test_winlayout() call assert_equal(['col', [['leaf', w3], ['row', [['leaf', w4], ['leaf', w2]]], ['leaf', w1]]], winlayout()) only! + + let w1 = win_getid() + call assert_equal(['leaf', w1], winlayout(1)) + tabnew + let w2 = win_getid() + call assert_equal(['leaf', w2], 2->winlayout()) + tabclose endfunc diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index aa7882d129..b42665c9b5 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -43,7 +43,7 @@ func Test_writefile_fails_gently() endfunc func Test_writefile_fails_conversion() - if !has('iconv') || system('uname -s') =~ 'SunOS' + if !has('iconv') || has('sun') return endif " Without a backup file the write won't happen if there is a conversion @@ -169,9 +169,7 @@ endfunc " Test for ':w !<cmd>' to pipe lines from the current buffer to an external " command. func Test_write_pipe_to_cmd() - if !has('unix') - return - endif + CheckUnix new call setline(1, ['L1', 'L2', 'L3', 'L4']) 2,3w !cat > Xfile @@ -371,6 +369,154 @@ func Test_write_file_encoding() %bw! endfunc +" Test for writing and reading a file starting with a BOM. +" Byte Order Mark (BOM) character for various encodings is below: +" UTF-8 : EF BB BF +" UTF-16 (BE): FE FF +" UTF-16 (LE): FF FE +" UTF-32 (BE): 00 00 FE FF +" UTF-32 (LE): FF FE 00 00 +func Test_readwrite_file_with_bom() + let utf8_bom = "\xEF\xBB\xBF" + let utf16be_bom = "\xFE\xFF" + let utf16le_bom = "\xFF\xFE" + let utf32be_bom = "\n\n\xFE\xFF" + let utf32le_bom = "\xFF\xFE\n\n" + let save_fileencoding = &fileencoding + set cpoptions+=S + + " Check that editing a latin1 file doesn't see a BOM + call writefile(["\xFE\xFElatin-1"], 'Xtest1') + edit Xtest1 + call assert_equal('latin1', &fileencoding) + call assert_equal(0, &bomb) + set fenc=latin1 + write Xfile2 + call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xfile2', 'b')) + set bomb fenc=latin1 + write Xtest3 + call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xtest3', 'b')) + set bomb& + + " Check utf-8 BOM + %bw! + call writefile([utf8_bom .. "utf-8"], 'Xtest1') + edit! Xtest1 + call assert_equal('utf-8', &fileencoding) + call assert_equal(1, &bomb) + call assert_equal('utf-8', getline(1)) + set fenc=latin1 + write! Xfile2 + call assert_equal(['utf-8', ''], readfile('Xfile2', 'b')) + set fenc=utf-8 + w! Xtest3 + call assert_equal([utf8_bom .. "utf-8", ''], readfile('Xtest3', 'b')) + + " Check utf-8 with an error (will fall back to latin-1) + %bw! + call writefile([utf8_bom .. "utf-8\x80err"], 'Xtest1') + edit! Xtest1 + call assert_equal('latin1', &fileencoding) + call assert_equal(0, &bomb) + call assert_equal("\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", getline(1)) + set fenc=latin1 + write! Xfile2 + call assert_equal([utf8_bom .. "utf-8\x80err", ''], readfile('Xfile2', 'b')) + set fenc=utf-8 + w! Xtest3 + call assert_equal(["\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", ''], + \ readfile('Xtest3', 'b')) + + " Check ucs-2 BOM + %bw! + call writefile([utf16be_bom .. "\nu\nc\ns\n-\n2\n"], 'Xtest1') + edit! Xtest1 + call assert_equal('utf-16', &fileencoding) + call assert_equal(1, &bomb) + call assert_equal('ucs-2', getline(1)) + set fenc=latin1 + write! Xfile2 + call assert_equal(["ucs-2", ''], readfile('Xfile2', 'b')) + set fenc=ucs-2 + w! Xtest3 + call assert_equal([utf16be_bom .. "\nu\nc\ns\n-\n2\n", ''], + \ readfile('Xtest3', 'b')) + + " Check ucs-2le BOM + %bw! + call writefile([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n"], 'Xtest1') + " Need to add a NUL byte after the NL byte + call writefile(0z00, 'Xtest1', 'a') + edit! Xtest1 + call assert_equal('utf-16le', &fileencoding) + call assert_equal(1, &bomb) + call assert_equal('ucs-2le', getline(1)) + set fenc=latin1 + write! Xfile2 + call assert_equal(["ucs-2le", ''], readfile('Xfile2', 'b')) + set fenc=ucs-2le + w! Xtest3 + call assert_equal([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n", "\n"], + \ readfile('Xtest3', 'b')) + + " Check ucs-4 BOM + %bw! + call writefile([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n"], 'Xtest1') + edit! Xtest1 + call assert_equal('ucs-4', &fileencoding) + call assert_equal(1, &bomb) + call assert_equal('ucs-4', getline(1)) + set fenc=latin1 + write! Xfile2 + call assert_equal(["ucs-4", ''], readfile('Xfile2', 'b')) + set fenc=ucs-4 + w! Xtest3 + call assert_equal([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n", ''], readfile('Xtest3', 'b')) + + " Check ucs-4le BOM + %bw! + call writefile([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n"], 'Xtest1') + " Need to add three NUL bytes after the NL byte + call writefile(0z000000, 'Xtest1', 'a') + edit! Xtest1 + call assert_equal('ucs-4le', &fileencoding) + call assert_equal(1, &bomb) + call assert_equal('ucs-4le', getline(1)) + set fenc=latin1 + write! Xfile2 + call assert_equal(["ucs-4le", ''], readfile('Xfile2', 'b')) + set fenc=ucs-4le + w! Xtest3 + call assert_equal([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n", "\n\n\n"], readfile('Xtest3', 'b')) + + set cpoptions-=S + let &fileencoding = save_fileencoding + call delete('Xtest1') + call delete('Xfile2') + call delete('Xtest3') + %bw! +endfunc + +func Test_read_write_bin() + " write file missing EOL + call writefile(['noeol'], "XNoEolSetEol", 'bS') + call assert_equal(0z6E6F656F6C, readfile('XNoEolSetEol', 'B')) + + " when file is read 'eol' is off + set nofixeol + e! ++ff=unix XNoEolSetEol + call assert_equal(0, &eol) + + " writing with 'eol' set adds the newline + setlocal eol + w + call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B')) + + call delete('XNoEolSetEol') + set ff& + bwipe! XNoEolSetEol +endfunc + " Check that buffer is written before triggering QuitPre func Test_wq_quitpre_autocommand() edit Xsomefile diff --git a/src/nvim/testing.c b/src/nvim/testing.c new file mode 100644 index 0000000000..5771ec4445 --- /dev/null +++ b/src/nvim/testing.c @@ -0,0 +1,562 @@ +// 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 + +// testing.c: Support for tests + +#include "nvim/eval/encode.h" +#include "nvim/ex_docmd.h" +#include "nvim/eval.h" +#include "nvim/os/os.h" +#include "nvim/testing.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "testing.c.generated.h" +#endif + +/// Prepare "gap" for an assert error and add the sourcing position. +static void prepare_assert_error(garray_T *gap) +{ + char buf[NUMBUFLEN]; + + ga_init(gap, 1, 100); + if (sourcing_name != NULL) { + ga_concat(gap, (char *)sourcing_name); + if (sourcing_lnum > 0) { + ga_concat(gap, " "); + } + } + if (sourcing_lnum > 0) { + vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)sourcing_lnum); + ga_concat(gap, buf); + } + if (sourcing_name != NULL || sourcing_lnum > 0) { + ga_concat(gap, ": "); + } +} + +/// Append "p[clen]" to "gap", escaping unprintable characters. +/// Changes NL to \n, CR to \r, etc. +static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) + FUNC_ATTR_NONNULL_ALL +{ + char_u buf[NUMBUFLEN]; + + if (clen > 1) { + memmove(buf, p, (size_t)clen); + buf[clen] = NUL; + ga_concat(gap, (char *)buf); + } else { + switch (*p) { + case BS: + ga_concat(gap, "\\b"); break; + case ESC: + ga_concat(gap, "\\e"); break; + case FF: + ga_concat(gap, "\\f"); break; + case NL: + ga_concat(gap, "\\n"); break; + case TAB: + ga_concat(gap, "\\t"); break; + case CAR: + ga_concat(gap, "\\r"); break; + case '\\': + ga_concat(gap, "\\\\"); break; + default: + if (*p < ' ') { + vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); + ga_concat(gap, (char *)buf); + } else { + ga_append(gap, (char)(*p)); + } + break; + } + } +} + +/// Append "str" to "gap", escaping unprintable characters. +/// Changes NL to \n, CR to \r, etc. +static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) + FUNC_ATTR_NONNULL_ARG(1) +{ + char_u buf[NUMBUFLEN]; + + if (str == NULL) { + ga_concat(gap, "NULL"); + return; + } + + for (const char_u *p = str; *p != NUL; p++) { + int same_len = 1; + const char_u *s = p; + const int c = mb_ptr2char_adv(&s); + const int clen = (int)(s - p); + while (*s != NUL && c == utf_ptr2char(s)) { + same_len++; + s += clen; + } + if (same_len > 20) { + ga_concat(gap, "\\["); + ga_concat_esc(gap, p, clen); + ga_concat(gap, " occurs "); + vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); + ga_concat(gap, (char *)buf); + ga_concat(gap, " times]"); + p = s - 1; + } else { + ga_concat_esc(gap, p, clen); + } + } +} + +/// Fill "gap" with information about an assert error. +static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, + typval_T *exp_tv, typval_T *got_tv, assert_type_T atype) +{ + char_u *tofree; + + if (opt_msg_tv->v_type != VAR_UNKNOWN) { + tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); + ga_concat(gap, (char *)tofree); + xfree(tofree); + ga_concat(gap, ": "); + } + + if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { + ga_concat(gap, "Pattern "); + } else if (atype == ASSERT_NOTEQUAL) { + ga_concat(gap, "Expected not equal to "); + } else { + ga_concat(gap, "Expected "); + } + + if (exp_str == NULL) { + tofree = (char_u *)encode_tv2string(exp_tv, NULL); + ga_concat_shorten_esc(gap, tofree); + xfree(tofree); + } else { + ga_concat_shorten_esc(gap, exp_str); + } + + if (atype != ASSERT_NOTEQUAL) { + if (atype == ASSERT_MATCH) { + ga_concat(gap, " does not match "); + } else if (atype == ASSERT_NOTMATCH) { + ga_concat(gap, " does match "); + } else { + ga_concat(gap, " but got "); + } + tofree = (char_u *)encode_tv2string(got_tv, NULL); + ga_concat_shorten_esc(gap, tofree); + xfree(tofree); + } +} + +static int assert_equal_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL +{ + garray_T ga; + + if (tv_equal(&argvars[0], &argvars[1], false, false) + != (atype == ASSERT_EQUAL)) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, + &argvars[0], &argvars[1], atype); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +static int assert_match_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1); + const char *const text = tv_get_string_buf_chk(&argvars[1], buf2); + + if (pat == NULL || text == NULL) { + emsg(_(e_invarg)); + } else if (pattern_match((char_u *)pat, (char_u *)text, false) + != (atype == ASSERT_MATCH)) { + garray_T ga; + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +/// Common for assert_true() and assert_false(). +static int assert_bool(typval_T *argvars, bool is_true) + FUNC_ATTR_NONNULL_ALL +{ + bool error = false; + garray_T ga; + + if ((argvars[0].v_type != VAR_NUMBER + || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true + || error) + && (argvars[0].v_type != VAR_BOOL + || (argvars[0].vval.v_bool + != (BoolVarValue)(is_true + ? kBoolVarTrue + : kBoolVarFalse)))) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], + (char_u *)(is_true ? "True" : "False"), + NULL, &argvars[0], ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, const char *cmd) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(gap, tofree); + xfree(tofree); + } else { + ga_concat(gap, cmd); + } +} + +static int assert_beeps(typval_T *argvars, bool no_beep) + FUNC_ATTR_NONNULL_ALL +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + int ret = 0; + + called_vim_beep = false; + suppress_errthrow = true; + emsg_silent = false; + do_cmdline_cmd(cmd); + if (no_beep ? called_vim_beep : !called_vim_beep) { + garray_T ga; + prepare_assert_error(&ga); + if (no_beep) { + ga_concat(&ga, "command did beep: "); + } else { + ga_concat(&ga, "command did not beep: "); + } + ga_concat(&ga, cmd); + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } + + suppress_errthrow = false; + emsg_on_display = false; + return ret; +} + +/// "assert_beeps(cmd [, error])" function +void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_beeps(argvars, false); +} + +/// "assert_nobeep(cmd [, error])" function +void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_beeps(argvars, true); +} + +/// "assert_equal(expected, actual[, msg])" function +void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); +} + +static int assert_equalfile(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); + const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); + garray_T ga; + + if (fname1 == NULL || fname2 == NULL) { + return 0; + } + + IObuff[0] = NUL; + FILE *const fd1 = os_fopen(fname1, READBIN); + char line1[200]; + char line2[200]; + ptrdiff_t lineidx = 0; + if (fd1 == NULL) { + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); + } else { + FILE *const fd2 = os_fopen(fname2, READBIN); + if (fd2 == NULL) { + fclose(fd1); + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); + } else { + int64_t linecount = 1; + for (int64_t count = 0;; count++) { + const int c1 = fgetc(fd1); + const int c2 = fgetc(fd2); + if (c1 == EOF) { + if (c2 != EOF) { + STRCPY(IObuff, "first file is shorter"); + } + break; + } else if (c2 == EOF) { + STRCPY(IObuff, "second file is shorter"); + break; + } else { + line1[lineidx] = (char)c1; + line2[lineidx] = (char)c2; + lineidx++; + if (c1 != c2) { + snprintf((char *)IObuff, IOSIZE, + "difference at byte %" PRId64 ", line %" PRId64, + count, linecount); + break; + } + } + if (c1 == NL) { + linecount++; + lineidx = 0; + } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) { + memmove(line1, line1 + 100, (size_t)(lineidx - 100)); + memmove(line2, line2 + 100, (size_t)(lineidx - 100)); + lineidx -= 100; + } + } + fclose(fd1); + fclose(fd2); + } + } + if (IObuff[0] != NUL) { + prepare_assert_error(&ga); + if (argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, tofree); + xfree(tofree); + ga_concat(&ga, ": "); + } + ga_concat(&ga, (char *)IObuff); + if (lineidx > 0) { + line1[lineidx] = NUL; + line2[lineidx] = NUL; + ga_concat(&ga, " after \""); + ga_concat(&ga, line1); + if (STRCMP(line1, line2) != 0) { + ga_concat(&ga, "\" vs \""); + ga_concat(&ga, line2); + } + ga_concat(&ga, "\""); + } + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +/// "assert_equalfile(fname-one, fname-two[, msg])" function +void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equalfile(argvars); +} + +/// "assert_notequal(expected, actual[, msg])" function +void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); +} + +/// "assert_exception(string[, msg])" function +void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + const char *const error = tv_get_string_chk(&argvars[0]); + if (*get_vim_var_str(VV_EXCEPTION) == NUL) { + prepare_assert_error(&ga); + ga_concat(&ga, "v:exception is not set"); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } else if (error != NULL + && strstr((char *)get_vim_var_str(VV_EXCEPTION), error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], + get_vim_var_tv(VV_EXCEPTION), ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } +} + +/// "assert_fails(cmd [, error [, msg]])" function +void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + garray_T ga; + int save_trylevel = trylevel; + + // trylevel must be zero for a ":throw" command to be considered failed + trylevel = 0; + called_emsg = false; + suppress_errthrow = true; + emsg_silent = true; + + do_cmdline_cmd(cmd); + if (!called_emsg) { + prepare_assert_error(&ga); + ga_concat(&ga, "command did not fail: "); + assert_append_cmd_or_arg(&ga, argvars, cmd); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } else if (argvars[1].v_type != VAR_UNKNOWN) { + char buf[NUMBUFLEN]; + const char *const error = tv_get_string_buf_chk(&argvars[1], buf); + + if (error == NULL + || strstr((char *)get_vim_var_str(VV_ERRMSG), error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], + get_vim_var_tv(VV_ERRMSG), ASSERT_OTHER); + ga_concat(&ga, ": "); + assert_append_cmd_or_arg(&ga, argvars, cmd); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } + } + + trylevel = save_trylevel; + called_emsg = false; + suppress_errthrow = false; + emsg_silent = false; + emsg_on_display = false; + set_vim_var_string(VV_ERRMSG, NULL, 0); +} + +// "assert_false(actual[, msg])" function +void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_bool(argvars, false); +} + +static int assert_inrange(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + bool error = false; + + if (argvars[0].v_type == VAR_FLOAT + || argvars[1].v_type == VAR_FLOAT + || argvars[2].v_type == VAR_FLOAT) { + const float_T flower = tv_get_float(&argvars[0]); + const float_T fupper = tv_get_float(&argvars[1]); + const float_T factual = tv_get_float(&argvars[2]); + + if (factual < flower || factual > fupper) { + garray_T ga; + prepare_assert_error(&ga); + if (argvars[3].v_type != VAR_UNKNOWN) { + char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL); + ga_concat(&ga, (char *)tofree); + xfree(tofree); + } else { + char msg[80]; + vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g", + flower, fupper, factual); + ga_concat(&ga, msg); + } + assert_error(&ga); + ga_clear(&ga); + return 1; + } + } else { + const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); + const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); + const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); + + if (error) { + return 0; + } + if (actual < lower || actual > upper) { + garray_T ga; + prepare_assert_error(&ga); + + char msg[55]; + vim_snprintf(msg, sizeof(msg), + "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", + lower, upper); // -V576 + fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], + ASSERT_INRANGE); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + } + return 0; +} + +/// "assert_inrange(lower, upper[, msg])" function +void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_inrange(argvars); +} + +/// "assert_match(pattern, actual[, msg])" function +void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); +} + +/// "assert_notmatch(pattern, actual[, msg])" function +void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); +} + +/// "assert_report(msg)" function +void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + prepare_assert_error(&ga); + ga_concat(&ga, tv_get_string(&argvars[0])); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; +} + +/// "assert_true(actual[, msg])" function +void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_bool(argvars, true); +} + +/// "test_garbagecollect_now()" function +void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // This is dangerous, any Lists and Dicts used internally may be freed + // while still in use. + garbage_collect(true); +} + +/// "test_write_list_log()" function +void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + diff --git a/src/nvim/testing.h b/src/nvim/testing.h new file mode 100644 index 0000000000..34ee1d10df --- /dev/null +++ b/src/nvim/testing.h @@ -0,0 +1,10 @@ +#ifndef NVIM_TESTING_H +#define NVIM_TESTING_H + +#include "nvim/eval/typval.h" +#include "nvim/eval/funcs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "testing.h.generated.h" +#endif +#endif // NVIM_TESTING_H diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 5bb6059fa7..17656c5ddc 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -5,7 +5,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/aucmd.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/ex_docmd.h" #include "nvim/macros.h" @@ -19,6 +19,7 @@ # include "nvim/os/os_win_console.h" #endif #include "nvim/event/rstream.h" +#include "nvim/msgpack_rpc/channel.h" #define KEY_BUFFER_SIZE 0xfff @@ -114,20 +115,39 @@ static void tinput_done_event(void **argv) static void tinput_wait_enqueue(void **argv) { TermInput *input = argv[0]; - RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { - const String keys = { .data = buf, .size = len }; - if (input->paste) { - String copy = copy_string(keys); + if (input->paste) { // produce exactly one paste event + const size_t len = rbuffer_size(input->key_buffer); + String keys = { .data = xmallocz(len), .size = len }; + rbuffer_read(input->key_buffer, keys.data, len); + if (ui_client_channel_id) { + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(keys)); // 'data' + ADD(args, BOOLEAN_OBJ(true)); // 'crlf' + ADD(args, INTEGER_OBJ(input->paste)); // 'phase' + rpc_send_event(ui_client_channel_id, "nvim_paste", args); + } else { multiqueue_put(main_loop.events, tinput_paste_event, 3, - copy.data, copy.size, (intptr_t)input->paste); - if (input->paste == 1) { - // Paste phase: "continue" - input->paste = 2; + keys.data, keys.size, (intptr_t)input->paste); + } + if (input->paste == 1) { + // Paste phase: "continue" + input->paste = 2; + } + rbuffer_reset(input->key_buffer); + } else { // enqueue input for the main thread or Nvim server + RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { + const String keys = { .data = buf, .size = len }; + size_t consumed; + if (ui_client_channel_id) { + Array args = ARRAY_DICT_INIT; + Error err = ERROR_INIT; + ADD(args, STRING_OBJ(copy_string(keys))); + // TODO(bfredl): could be non-blocking now with paste? + Object result = rpc_send_call(ui_client_channel_id, "nvim_input", args, &err); + consumed = result.type == kObjectTypeInteger ? (size_t)result.data.integer : 0; + } else { + consumed = input_enqueue(keys); } - rbuffer_consumed(input->key_buffer, len); - rbuffer_reset(input->key_buffer); - } else { - const size_t consumed = input_enqueue(keys); if (consumed) { rbuffer_consumed(input->key_buffer, consumed); } @@ -221,7 +241,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key) && !(key->modifiers & TERMKEY_KEYMOD_SHIFT) && ASCII_ISUPPER(key->code.codepoint)) { assert(len <= 62); - // Make remove for the S- + // Make room for the S- memmove(buf + 3, buf + 1, len - 1); buf[1] = 'S'; buf[2] = '-'; @@ -309,7 +329,6 @@ static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force) return force ? termkey_getkey_force(tk, key) : termkey_getkey(tk, key); } -static void tinput_timer_cb(TimeWatcher *watcher, void *data); static void tk_getkeys(TermInput *input, bool force) { @@ -373,7 +392,7 @@ static bool handle_focus_event(TermInput *input) bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I'; // Advance past the sequence rbuffer_consumed(input->read_stream.buffer, 3); - aucmd_schedule_focusgained(focus_gained); + autocmd_schedule_focusgained(focus_gained); return true; } return false; @@ -420,22 +439,6 @@ static HandleState handle_bracketed_paste(TermInput *input) return kNotApplicable; } -// ESC NUL => <Esc> -static bool handle_forced_escape(TermInput *input) -{ - if (rbuffer_size(input->read_stream.buffer) > 1 - && !rbuffer_cmp(input->read_stream.buffer, "\x1b\x00", 2)) { - // skip the ESC and NUL and push one <esc> to the input buffer - size_t rcnt; - termkey_push_bytes(input->tk, rbuffer_read_ptr(input->read_stream.buffer, - &rcnt), 1); - rbuffer_consumed(input->read_stream.buffer, 2); - tk_getkeys(input, true); - return true; - } - return false; -} - static void set_bg_deferred(void **argv) { char *bgvalue = argv[0]; @@ -564,7 +567,6 @@ static void handle_raw_buffer(TermInput *input, bool force) if (!force && (handle_focus_event(input) || (is_paste = handle_bracketed_paste(input)) != kNotApplicable - || handle_forced_escape(input) || (is_bc = handle_background_color(input)) != kNotApplicable)) { if (is_paste == kIncomplete || is_bc == kIncomplete) { // Wait for the next input, leaving it in the raw buffer due to an diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index bb75286369..a67bcf98dc 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -131,6 +131,7 @@ typedef struct { int get_bg; int set_underline_style; int set_underline_color; + int enable_extended_keys, disable_extended_keys; } unibi_ext; char *space_buf; } TUIData; @@ -168,7 +169,7 @@ UI *tui_start(void) ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; ui->screenshot = tui_screenshot; - ui->option_set= tui_option_set; + ui->option_set = tui_option_set; ui->raw_line = tui_raw_line; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); @@ -225,6 +226,8 @@ static void terminfo_start(UI *ui) data->unibi_ext.reset_cursor_style = -1; data->unibi_ext.get_bg = -1; data->unibi_ext.set_underline_color = -1; + data->unibi_ext.enable_extended_keys = -1; + data->unibi_ext.disable_extended_keys = -1; data->out_fd = STDOUT_FILENO; data->out_isatty = os_isatty(data->out_fd); @@ -308,23 +311,43 @@ static void terminfo_start(UI *ui) // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); + // Enable extended keys (also known as 'modifyOtherKeys' or CSI u). On terminals that don't + // support this, this sequence is ignored. + unibi_out_ext(ui, data->unibi_ext.enable_extended_keys); + + int ret; uv_loop_init(&data->write_loop); if (data->out_isatty) { - uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); + ret = uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); + if (ret) { + ELOG("uv_tty_init failed: %s", uv_strerror(ret)); + } #ifdef WIN32 - uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW); + ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW); + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } #else 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 + while ((ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO)) == UV_EINTR && retry_count > 0) { retry_count--; } + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } #endif } else { - uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); - uv_pipe_open(&data->output_handle.pipe, data->out_fd); + ret = uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); + if (ret) { + ELOG("uv_pipe_init failed: %s", uv_strerror(ret)); + } + ret = uv_pipe_open(&data->output_handle.pipe, data->out_fd); + if (ret) { + ELOG("uv_pipe_open failed: %s", uv_strerror(ret)); + } } flush_buf(ui); } @@ -349,6 +372,8 @@ static void terminfo_stop(UI *ui) unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); // Disable focus reporting unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting); + // Disable extended keys + unibi_out_ext(ui, data->unibi_ext.disable_extended_keys); flush_buf(ui); uv_tty_reset_mode(); uv_close((uv_handle_t *)&data->output_handle, NULL); @@ -512,7 +537,7 @@ static bool attrs_differ(UI *ui, int id1, int id2, bool rgb) return a1.cterm_fg_color != a2.cterm_fg_color || a1.cterm_bg_color != a2.cterm_bg_color || a1.cterm_ae_attr != a2.cterm_ae_attr - || (a1.cterm_ae_attr & (HL_UNDERLINE|HL_UNDERCURL) + || (a1.cterm_ae_attr & HL_ANY_UNDERLINE && a1.rgb_sp_color != a2.rgb_sp_color); } } @@ -536,15 +561,27 @@ static void update_attrs(UI *ui, int attr_id) bool strikethrough = attr & HL_STRIKETHROUGH; bool underline; + bool underlineline; bool undercurl; + bool underdot; + bool underdash; if (data->unibi_ext.set_underline_style != -1) { underline = attr & HL_UNDERLINE; + underlineline = attr & HL_UNDERLINELINE; undercurl = attr & HL_UNDERCURL; + underdash = attr & HL_UNDERDASH; + underdot = attr & HL_UNDERDOT; } else { - underline = (attr & HL_UNDERLINE) || (attr & HL_UNDERCURL); + underline = attr & HL_ANY_UNDERLINE; + underlineline = false; undercurl = false; + underdot = false; + underdash = false; } + bool has_any_underline = undercurl || underline + || underdot || underdash || underlineline; + if (unibi_get_str(data->ut, unibi_set_attributes)) { if (bold || reverse || underline || standout) { UNIBI_SET_NUM_VAR(data->params[0], standout); @@ -583,11 +620,24 @@ static void update_attrs(UI *ui, int attr_id) if (strikethrough && data->unibi_ext.enter_strikethrough_mode != -1) { unibi_out_ext(ui, data->unibi_ext.enter_strikethrough_mode); } + if (underlineline && data->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(data->params[0], 2); + unibi_out_ext(ui, data->unibi_ext.set_underline_style); + } if (undercurl && data->unibi_ext.set_underline_style != -1) { UNIBI_SET_NUM_VAR(data->params[0], 3); unibi_out_ext(ui, data->unibi_ext.set_underline_style); } - if ((undercurl || underline) && data->unibi_ext.set_underline_color != -1) { + if (underdot && data->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(data->params[0], 4); + unibi_out_ext(ui, data->unibi_ext.set_underline_style); + } + if (underdash && data->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(data->params[0], 5); + unibi_out_ext(ui, data->unibi_ext.set_underline_style); + } + + if (has_any_underline && data->unibi_ext.set_underline_color != -1) { int color = attrs.rgb_sp_color; if (color != -1) { UNIBI_SET_NUM_VAR(data->params[0], (color >> 16) & 0xff); // red @@ -636,13 +686,13 @@ static void update_attrs(UI *ui, int attr_id) data->default_attr = fg == -1 && bg == -1 - && !bold && !italic && !underline && !undercurl && !reverse && !standout + && !bold && !italic && !has_any_underline && !reverse && !standout && !strikethrough; // Non-BCE terminals can't clear with non-default background color. Some BCE // terminals don't support attributes either, so don't rely on it. But assume // italic and bold has no effect if there is no text. - data->can_clear_attr = !reverse && !standout && !underline && !undercurl + data->can_clear_attr = !reverse && !standout && !has_any_underline && !strikethrough && (data->bce || bg == -1); } @@ -1086,8 +1136,14 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx) // 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); + int ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL); + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } + ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO); + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } } #endif tui_set_mode(ui, (ModeShape)mode_idx); @@ -1331,7 +1387,6 @@ static void tui_screenshot(UI *ui, String path) fclose(f); } - static void tui_option_set(UI *ui, String name, Object value) { TUIData *data = ui->data; @@ -1340,11 +1395,9 @@ static void tui_option_set(UI *ui, String name, Object value) data->print_attr_id = -1; invalidate(ui, 0, data->grid.height, 0, data->grid.width); - } - if (strequal(name.data, "ttimeout")) { + } else if (strequal(name.data, "ttimeout")) { data->input.ttimeout = value.data.boolean; - } - if (strequal(name.data, "ttimeoutlen")) { + } else if (strequal(name.data, "ttimeoutlen")) { data->input.ttimeoutlen = (long)value.data.integer; } } @@ -1428,8 +1481,8 @@ static void tui_guess_size(UI *ui) // 1 - look for non-default 'columns' and 'lines' options during startup if (data->is_starting && (Columns != DFLT_COLS || Rows != DFLT_ROWS)) { did_user_set_dimensions = true; - assert(Columns >= INT_MIN && Columns <= INT_MAX); - assert(Rows >= INT_MIN && Rows <= INT_MAX); + assert(Columns >= 0); + assert(Rows >= 0); width = Columns; height = Rows; goto end; @@ -1855,7 +1908,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col "\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 + // side effects on temporary font resizing. #6798 data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?" @@ -1897,6 +1950,7 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, || terminfo_is_term_family(term, "iTerm.app") || terminfo_is_term_family(term, "iTerm2.app"); bool alacritty = terminfo_is_term_family(term, "alacritty"); + bool kitty = terminfo_is_term_family(term, "xterm-kitty"); // None of the following work over SSH; see :help TERM . bool iterm_pretending_xterm = xterm && iterm_env; @@ -2020,6 +2074,15 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, data->unibi_ext.set_underline_color = (int)unibi_add_ext_str(ut, "ext.set_underline_color", "\x1b[58:2::%p1%d:%p2%d:%p3%dm"); } + + if (!kitty) { + // Kitty does not support these sequences; it only supports it's own CSI > 1 u which enables the + // Kitty keyboard protocol + data->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", + "\x1b[>4;2m"); + data->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", + "\x1b[>4;0m"); + } } static void flush_buf(UI *ui) @@ -2081,8 +2144,11 @@ static void flush_buf(UI *ui) 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); + int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), + bufs, (unsigned)(bufp - bufs), NULL); + if (ret) { + ELOG("uv_write failed: %s", uv_strerror(ret)); + } uv_run(&data->write_loop, UV_RUN_DEFAULT); } data->bufpos = 0; diff --git a/src/nvim/types.h b/src/nvim/types.h index 604155c33e..73cd2204d6 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -32,4 +32,6 @@ typedef enum { kTrue = 1, } TriState; +typedef struct Decoration Decoration; + #endif // NVIM_TYPES_H diff --git a/src/nvim/ui.c b/src/nvim/ui.c index aad72af025..4fe3e1157c 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -8,10 +8,11 @@ #include <string.h> #include "nvim/ascii.h" -#include "nvim/aucmd.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/diff.h" #include "nvim/event/loop.h" #include "nvim/ex_cmds2.h" @@ -23,7 +24,6 @@ #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -223,10 +223,23 @@ void ui_refresh(void) ui_default_colors_set(); - int save_p_lz = p_lz; - p_lz = false; // convince redrawing() to return true ... - screen_resize(width, height); - p_lz = save_p_lz; + if (!ui_client_channel_id) { + int save_p_lz = p_lz; + p_lz = false; // convince redrawing() to return true ... + screen_resize(width, height); + p_lz = save_p_lz; + } else { + Array args = ARRAY_DICT_INIT; + Error err = ERROR_INIT; + ADD(args, INTEGER_OBJ((int)width)); + ADD(args, INTEGER_OBJ((int)height)); + rpc_send_call(ui_client_channel_id, "nvim_ui_try_resize", args, &err); + + if (ERROR_SET(&err)) { + ELOG("ui_client resize: %s", err.msg); + } + api_clear_error(&err); + } if (ext_widgets[kUIMessages]) { p_ch = 0; @@ -300,6 +313,44 @@ void ui_busy_stop(void) } } +/// Emit a bell or visualbell as a warning +/// +/// val is one of the BO_ values, e.g., BO_OPER +void vim_beep(unsigned val) +{ + called_vim_beep = true; + + if (emsg_silent == 0) { + if (!((bo_flags & val) || (bo_flags & BO_ALL))) { + static int beeps = 0; + static uint64_t start_time = 0; + + // Only beep up to three times per half a second, + // otherwise a sequence of beeps would freeze Vim. + if (start_time == 0 || os_hrtime() - start_time > 500000000u) { + beeps = 0; + start_time = os_hrtime(); + } + beeps++; + if (beeps <= 3) { + if (p_vb) { + ui_call_visual_bell(); + } else { + ui_call_bell(); + } + } + } + + // When 'debug' contains "beep" produce a message. If we are sourcing + // a script or executing a function give the user a hint where the beep + // comes from. + if (vim_strchr(p_debug, 'e') != NULL) { + msg_source(HL_ATTR(HLF_W)); + msg_attr(_("Beep!"), HL_ATTR(HLF_W)); + } + } +} + void ui_attach_impl(UI *ui, uint64_t chanid) { if (ui_count == MAX_UI_COUNT) { @@ -602,8 +653,8 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) } } else { // non-positive indicates no request - wp->w_height_request = (int)MAX(height, 0); - wp->w_width_request = (int)MAX(width, 0); + wp->w_height_request = MAX(height, 0); + wp->w_width_request = MAX(width, 0); win_set_inner_size(wp); } } diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c new file mode 100644 index 0000000000..6a7695bf72 --- /dev/null +++ b/src/nvim/ui_client.c @@ -0,0 +1,214 @@ +// 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 <stdbool.h> +#include <stdint.h> +#include <assert.h> + +#include "nvim/vim.h" +#include "nvim/log.h" +#include "nvim/map.h" +#include "nvim/ui_client.h" +#include "nvim/api/private/helpers.h" +#include "nvim/msgpack_rpc/channel.h" +#include "nvim/api/private/dispatch.h" +#include "nvim/ui.h" +#include "nvim/highlight.h" +#include "nvim/screen.h" + +static Map(String, UIClientHandler) ui_client_handlers = MAP_INIT; + +// Temporary buffer for converting a single grid_line event +static size_t buf_size = 0; +static schar_T *buf_char = NULL; +static sattr_T *buf_attr = NULL; + +static void add_ui_client_event_handler(String method, UIClientHandler handler) +{ + map_put(String, UIClientHandler)(&ui_client_handlers, method, handler); +} + +void ui_client_init(uint64_t chan) +{ + Array args = ARRAY_DICT_INIT; + int width = Columns; + int height = Rows; + Dictionary opts = ARRAY_DICT_INIT; + + PUT(opts, "rgb", BOOLEAN_OBJ(true)); + PUT(opts, "ext_linegrid", BOOLEAN_OBJ(true)); + PUT(opts, "ext_termcolors", BOOLEAN_OBJ(true)); + + ADD(args, INTEGER_OBJ((int)width)); + ADD(args, INTEGER_OBJ((int)height)); + ADD(args, DICTIONARY_OBJ(opts)); + + rpc_send_event(chan, "nvim_ui_attach", args); + msgpack_rpc_add_redraw(); // GAME! + // TODO(bfredl): use a keyset instead + ui_client_methods_table_init(); + ui_client_channel_id = chan; +} + +/// Handler for "redraw" events sent by the NVIM server +/// +/// This function will be called by handle_request (in msgpack_rpc/channel.c) +/// The individual ui_events sent by the server are individually handled +/// by their respective handlers defined in ui_events_client.generated.h +/// +/// @note The "flush" event is called only once and only after handling all +/// the other events +/// @param channel_id: The id of the rpc channel +/// @param uidata: The dense array containing the ui_events sent by the server +/// @param[out] err Error details, if any +Object ui_client_handle_redraw(uint64_t channel_id, Array args, Error *error) +{ + for (size_t i = 0; i < args.size; i++) { + Array call = args.items[i].data.array; + String name = call.items[0].data.string; + + UIClientHandler handler = map_get(String, UIClientHandler)(&ui_client_handlers, name); + if (!handler) { + ELOG("No ui client handler for %s", name.size ? name.data : "<empty>"); + continue; + } + + // fprintf(stderr, "%s: %zu\n", name.data, call.size-1); + DLOG("Invoke ui client handler for %s", name.data); + for (size_t j = 1; j < call.size; j++) { + handler(call.items[j].data.array); + } + } + + return NIL; +} + +/// run the main thread in ui client mode +/// +/// This is just a stub. the full version will handle input, resizing, etc +void ui_client_execute(uint64_t chan) + FUNC_ATTR_NORETURN +{ + while (true) { + loop_poll_events(&main_loop, -1); + multiqueue_process_events(resize_events); + } + + getout(0); +} + +static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb) +{ + Error err = ERROR_INIT; + Dict(highlight) dict = { 0 }; + if (!api_dict_to_keydict(&dict, KeyDict_highlight_get_field, d, &err)) { + // TODO(bfredl): log "err" + return HLATTRS_INIT; + } + return dict2hlattrs(&dict, true, NULL, &err); +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "ui_events_client.generated.h" +#endif + +void ui_client_event_grid_resize(Array args) +{ + if (args.size < 3 + || args.items[0].type != kObjectTypeInteger + || args.items[1].type != kObjectTypeInteger + || args.items[2].type != kObjectTypeInteger) { + ELOG("Error handling ui event 'grid_resize'"); + return; + } + + Integer grid = args.items[0].data.integer; + Integer width = args.items[1].data.integer; + Integer height = args.items[2].data.integer; + ui_call_grid_resize(grid, width, height); + + if (buf_size < (size_t)width) { + xfree(buf_char); + xfree(buf_attr); + buf_size = (size_t)width; + buf_char = xmalloc(buf_size * sizeof(schar_T)); + buf_attr = xmalloc(buf_size * sizeof(sattr_T)); + } +} + +void ui_client_event_grid_line(Array args) +{ + if (args.size < 4 + || args.items[0].type != kObjectTypeInteger + || args.items[1].type != kObjectTypeInteger + || args.items[2].type != kObjectTypeInteger + || args.items[3].type != kObjectTypeArray) { + goto error; + } + + Integer grid = args.items[0].data.integer; + Integer row = args.items[1].data.integer; + Integer startcol = args.items[2].data.integer; + Array cells = args.items[3].data.array; + + // TODO(hlpr98): Accommodate other LineFlags when included in grid_line + LineFlags lineflags = 0; + + size_t j = 0; + int cur_attr = 0; + int clear_attr = 0; + int clear_width = 0; + for (size_t i = 0; i < cells.size; i++) { + if (cells.items[i].type != kObjectTypeArray) { + goto error; + } + Array cell = cells.items[i].data.array; + + if (cell.size < 1 || cell.items[0].type != kObjectTypeString) { + goto error; + } + String sstring = cell.items[0].data.string; + + char *schar = sstring.data; + int repeat = 1; + if (cell.size >= 2) { + if (cell.items[1].type != kObjectTypeInteger + || cell.items[1].data.integer < 0) { + goto error; + } + cur_attr = (int)cell.items[1].data.integer; + } + + if (cell.size >= 3) { + if (cell.items[2].type != kObjectTypeInteger + || cell.items[2].data.integer < 0) { + goto error; + } + repeat = (int)cell.items[2].data.integer; + } + + if (i == cells.size - 1 && sstring.size == 1 && sstring.data[0] == ' ' && repeat > 1) { + clear_width = repeat; + break; + } + + for (int r = 0; r < repeat; r++) { + if (j >= buf_size) { + goto error; // _YIKES_ + } + STRLCPY(buf_char[j], schar, sizeof(schar_T)); + buf_attr[j++] = cur_attr; + } + } + + Integer endcol = startcol + (int)j; + Integer clearcol = endcol + clear_width; + clear_attr = cur_attr; + + ui_call_raw_line(grid, row, startcol, endcol, clearcol, clear_attr, lineflags, + (const schar_T *)buf_char, (const sattr_T *)buf_attr); + return; + +error: + ELOG("Error handling ui event 'grid_line'"); +} diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h new file mode 100644 index 0000000000..253deecc52 --- /dev/null +++ b/src/nvim/ui_client.h @@ -0,0 +1,13 @@ +#ifndef NVIM_UI_CLIENT_H +#define NVIM_UI_CLIENT_H + +#include "nvim/api/private/defs.h" + +typedef void (*UIClientHandler)(Array args); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "ui_client.h.generated.h" +#include "ui_events_client.h.generated.h" +#endif + +#endif // NVIM_UI_CLIENT_H diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index d7becb4fd4..e356960cc8 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -14,6 +14,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/lua/executor.h" @@ -23,7 +24,6 @@ #include "nvim/os/os.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" -#include "nvim/syntax.h" #include "nvim/ugrid.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 8161fce9f4..2d8df4cad8 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -93,13 +93,14 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/getchar.h" #include "nvim/lib/kvec.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os_unix.h" @@ -2621,7 +2622,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) if (uhp == NULL) { *msgbuf = NUL; } else { - add_time(msgbuf, sizeof(msgbuf), uhp->uh_time); + undo_fmt_time(msgbuf, sizeof(msgbuf), uhp->uh_time); } { @@ -2632,6 +2633,10 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) } } + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } + smsg_attr_keep(0, _("%" PRId64 " %s; %s #%" PRId64 " %s"), u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount, @@ -2641,6 +2646,29 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) msgbuf); } +/// Put the timestamp of an undo header in "buf[buflen]" in a nice format. +void undo_fmt_time(char_u *buf, size_t buflen, time_t tt) +{ + struct tm curtime; + + if (time(NULL) - tt >= 100) { + os_localtime_r(&tt, &curtime); + if (time(NULL) - tt < (60L * 60L * 12L)) { + // within 12 hours + (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); + } else { + // longer ago + (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); + } + } else { + int64_t seconds = time(NULL) - tt; + vim_snprintf((char *)buf, buflen, + NGETTEXT("%" PRId64 " second ago", + "%" PRId64 " seconds ago", (uint32_t)seconds), + seconds); + } +} + /// u_sync: stop adding to the current entry list /// /// @param force if true, also sync when no_u_sync is set. @@ -2683,16 +2711,13 @@ void ex_undolist(exarg_T *eap) while (uhp != NULL) { if (uhp->uh_prev.ptr == NULL && uhp->uh_walk != nomark && uhp->uh_walk != mark) { - vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ", - uhp->uh_seq, changes); - add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), - uhp->uh_time); + vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ", uhp->uh_seq, changes); + undo_fmt_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), uhp->uh_time); if (uhp->uh_save_nr > 0) { while (STRLEN(IObuff) < 33) { STRCAT(IObuff, " "); } - vim_snprintf_add((char *)IObuff, IOSIZE, - " %3ld", uhp->uh_save_nr); + vim_snprintf_add((char *)IObuff, IOSIZE, " %3ld", uhp->uh_save_nr); } GA_APPEND(char_u *, &ga, vim_strsave(IObuff)); } diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index 3267b2f71e..d8470b07b1 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -33,8 +33,8 @@ struct u_entry { }; struct u_header { - /* The following have a pointer and a number. The number is used when - * reading the undo file in u_read_undo() */ + // The following have a pointer and a number. The number is used when reading + // the undo file in u_read_undo() union { u_header_T *ptr; // pointer to next undo header in list long seq; @@ -80,4 +80,4 @@ typedef struct { FILE *bi_fp; } bufinfo_T; -#endif // NVIM_UNDO_DEFS_H +#endif // NVIM_UNDO_DEFS_H diff --git a/src/nvim/version.c b/src/nvim/version.c index 5e2a81795a..71cca52773 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2093,7 +2093,7 @@ void list_in_columns(char_u **items, int size, int current) // The rightmost column doesn't need a separator. // Sacrifice it to fit in one more column if possible. - int ncol = (int)(Columns + 1) / width; + int ncol = (Columns + 1) / width; int nrow = item_count / ncol + (item_count % ncol ? 1 : 0); int cur_row = 1; diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 726670f082..64333e9c3d 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -141,6 +141,7 @@ enum { EXPAND_COMPILER, EXPAND_USER_DEFINED, EXPAND_USER_LIST, + EXPAND_USER_LUA, EXPAND_SHELLCMD, EXPAND_CSCOPE, EXPAND_SIGN, @@ -213,6 +214,11 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // (vim_strchr() is now in strings.c) #define STRLEN(s) strlen((char *)(s)) +#ifdef HAVE_STRNLEN +# define STRNLEN(s, n) strnlen((char *)(s), (size_t)(n)) +#else +# define STRNLEN(s, n) xstrnlen((char *)(s), (size_t)(n)) +#endif #define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) #define STRNCPY(d, s, n) strncpy((char *)(d), (char *)(s), (size_t)(n)) #define STRLCPY(d, s, n) xstrlcpy((char *)(d), (char *)(s), (size_t)(n)) @@ -256,7 +262,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // Prefer using semsg(), because perror() may send the output to the wrong // destination and mess up the screen. -#define PERROR(msg) (void)semsg("%s: %s", msg, strerror(errno)) +#define PERROR(msg) (void)semsg("%s: %s", (msg), strerror(errno)) #define SHOWCMD_COLS 10 // columns needed by shown command @@ -311,7 +317,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() #endif // Replacement for nchar used by nv_replace(). -#define REPLACE_CR_NCHAR -1 -#define REPLACE_NL_NCHAR -2 +#define REPLACE_CR_NCHAR (-1) +#define REPLACE_NL_NCHAR (-2) #endif // NVIM_VIM_H diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 800ecf10db..9d1318724e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -204,40 +204,40 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) } \ } while (0) switch (schar) { - // Paired brackets. + // Paired brackets. #define BRACKET(typ, opning, clsing) \ -case opning: \ -case clsing: { \ - ret.type = typ; \ - ret.data.brc.closing = (schar == clsing); \ - break; \ -} - BRACKET(kExprLexParenthesis, '(', ')') - BRACKET(kExprLexBracket, '[', ']') - BRACKET(kExprLexFigureBrace, '{', '}') + case opning: \ + case clsing: { \ + ret.type = typ; \ + ret.data.brc.closing = (schar == clsing); \ + break; \ + } + BRACKET(kExprLexParenthesis, '(', ')') + BRACKET(kExprLexBracket, '[', ']') + BRACKET(kExprLexFigureBrace, '{', '}') #undef BRACKET - // Single character tokens without data. + // Single character tokens without data. #define CHAR(typ, ch) \ -case ch: { \ - ret.type = typ; \ - break; \ -} - CHAR(kExprLexQuestion, '?') - CHAR(kExprLexColon, ':') - CHAR(kExprLexComma, ',') + case ch: { \ + ret.type = typ; \ + break; \ + } + CHAR(kExprLexQuestion, '?') + CHAR(kExprLexColon, ':') + CHAR(kExprLexComma, ',') #undef CHAR - // Multiplication/division/modulo. + // Multiplication/division/modulo. #define MUL(mul_type, ch) \ -case ch: { \ - ret.type = kExprLexMultiplication; \ - ret.data.mul.type = mul_type; \ - break; \ -} - MUL(kExprLexMulMul, '*') - MUL(kExprLexMulDiv, '/') - MUL(kExprLexMulMod, '%') + case ch: { \ + ret.type = kExprLexMultiplication; \ + ret.data.mul.type = mul_type; \ + break; \ + } + MUL(kExprLexMulMul, '*') + MUL(kExprLexMulDiv, '/') + MUL(kExprLexMulMod, '%') #undef MUL #define CHARREG(typ, cond) \ @@ -653,16 +653,16 @@ case ch: { \ // Sign or augmented assignment. #define CHAR_OR_ASSIGN(ch, ch_type, ass_type) \ -case ch: { \ - if (pline.size > 1 && pline.data[1] == '=') { \ - ret.len++; \ - ret.type = kExprLexAssignment; \ - ret.data.ass.type = ass_type; \ - } else { \ - ret.type = ch_type; \ - } \ - break; \ -} + case ch: { \ + if (pline.size > 1 && pline.data[1] == '=') { \ + ret.len++; \ + ret.type = kExprLexAssignment; \ + ret.data.ass.type = ass_type; \ + } else { \ + ret.type = ch_type; \ + } \ + break; \ + } CHAR_OR_ASSIGN('+', kExprLexPlus, kExprAsgnAdd) CHAR_OR_ASSIGN('.', kExprLexDot, kExprAsgnConcat) #undef CHAR_OR_ASSIGN @@ -811,19 +811,19 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, const LexExpr eltkn_type_tab[token.type]); switch (token.type) { #define TKNARGS(tkn_type, ...) \ -case tkn_type: { \ - ADDSTR(__VA_ARGS__); \ - break; \ -} - TKNARGS(kExprLexComparison, "(type=%s,ccs=%s,inv=%i)", - eltkn_cmp_type_tab[token.data.cmp.type], - ccs_tab[token.data.cmp.ccs], - (int)token.data.cmp.inv) - TKNARGS(kExprLexMultiplication, "(type=%s)", - eltkn_mul_type_tab[token.data.mul.type]) - TKNARGS(kExprLexAssignment, "(type=%s)", - expr_asgn_type_tab[token.data.ass.type]) - TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) + case tkn_type: { \ + ADDSTR(__VA_ARGS__); \ + break; \ + } + TKNARGS(kExprLexComparison, "(type=%s,ccs=%s,inv=%i)", + eltkn_cmp_type_tab[token.data.cmp.type], + ccs_tab[token.data.cmp.ccs], + (int)token.data.cmp.inv) + TKNARGS(kExprLexMultiplication, "(type=%s)", + eltkn_mul_type_tab[token.data.mul.type]) + TKNARGS(kExprLexAssignment, "(type=%s)", + expr_asgn_type_tab[token.data.ass.type]) + TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) case kExprLexDoubleQuotedString: TKNARGS(kExprLexSingleQuotedString, "(closed=%i)", (int)token.data.str.closed) @@ -1536,25 +1536,25 @@ static inline void east_set_error(const ParserState *const pstate, ExprASTError /* TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to */ \ /* handle environment variables like those bash uses for */ \ /* `export -f`: their names consist not only of alphanumeric */ \ - /* characetrs. */ \ + /* characters. */ \ case kExprNodeComplexIdentifier: \ case kExprNodePlainIdentifier: \ case kExprNodeCurlyBracesIdentifier: { \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); \ - cur_node->len = 0; \ - cur_node->children = *top_node_p; \ - *top_node_p = cur_node; \ - kvi_push(ast_stack, &cur_node->children->next); \ - ExprASTNode **const new_top_node_p = kv_last(ast_stack); \ - assert(*new_top_node_p == NULL); \ - new_ident_node_code; \ - *new_top_node_p = cur_node; \ - HL_CUR_TOKEN(hl); \ - break; \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); \ + cur_node->len = 0; \ + cur_node->children = *top_node_p; \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children->next); \ + ExprASTNode **const new_top_node_p = kv_last(ast_stack); \ + assert(*new_top_node_p == NULL); \ + new_ident_node_code; \ + *new_top_node_p = cur_node; \ + HL_CUR_TOKEN(hl); \ + break; \ } \ default: { \ - OP_MISSING; \ - break; \ + OP_MISSING; \ + break; \ } \ } \ } while (0) @@ -1592,7 +1592,7 @@ typedef struct { /// string is a regex. /// @param[in] is_invalid Whether currently processed token is not valid. static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const node, - const LexExprToken token, const ExprASTStack ast_stack, + const LexExprToken token, const ExprASTStack *ast_stack, const bool is_invalid) FUNC_ATTR_NONNULL_ALL { @@ -1747,19 +1747,19 @@ static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const no const char *const v_p_start = v_p; switch (*p) { #define SINGLE_CHAR_ESC(ch, real_ch) \ -case ch: { \ - *v_p++ = real_ch; \ - p++; \ - break; \ -} - SINGLE_CHAR_ESC('b', BS) - SINGLE_CHAR_ESC('e', ESC) - SINGLE_CHAR_ESC('f', FF) - SINGLE_CHAR_ESC('n', NL) - SINGLE_CHAR_ESC('r', CAR) - SINGLE_CHAR_ESC('t', TAB) - SINGLE_CHAR_ESC('"', '"') - SINGLE_CHAR_ESC('\\', '\\') + case ch: { \ + *v_p++ = real_ch; \ + p++; \ + break; \ + } + SINGLE_CHAR_ESC('b', BS) + SINGLE_CHAR_ESC('e', ESC) + SINGLE_CHAR_ESC('f', FF) + SINGLE_CHAR_ESC('n', NL) + SINGLE_CHAR_ESC('r', CAR) + SINGLE_CHAR_ESC('t', TAB) + SINGLE_CHAR_ESC('"', '"') + SINGLE_CHAR_ESC('\\', '\\') #undef SINGLE_CHAR_ESC // Hexadecimal or unicode. @@ -2141,32 +2141,32 @@ viml_pexpr_parse_process_token: break; } #define SIMPLE_UB_OP(op) \ -case kExprLex##op: { \ - if (want_node == kENodeValue) { \ - /* Value level: assume unary operator. */ \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ - *top_node_p = cur_node; \ - kvi_push(ast_stack, &cur_node->children); \ - HL_CUR_TOKEN(Unary##op); \ - } else { \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ - ADD_OP_NODE(cur_node); \ - HL_CUR_TOKEN(Binary##op); \ - } \ - want_node = kENodeValue; \ - break; \ -} + case kExprLex##op: { \ + if (want_node == kENodeValue) { \ + /* Value level: assume unary operator. */ \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children); \ + HL_CUR_TOKEN(Unary##op); \ + } else { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ + ADD_OP_NODE(cur_node); \ + HL_CUR_TOKEN(Binary##op); \ + } \ + want_node = kENodeValue; \ + break; \ + } SIMPLE_UB_OP(Plus) SIMPLE_UB_OP(Minus) #undef SIMPLE_UB_OP #define SIMPLE_B_OP(op, msg) \ -case kExprLex##op: { \ - ADD_VALUE_IF_MISSING(_("E15: Unexpected " msg ": %.*s")); \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##op); \ - HL_CUR_TOKEN(op); \ - ADD_OP_NODE(cur_node); \ - break; \ -} + case kExprLex##op: { \ + ADD_VALUE_IF_MISSING(_("E15: Unexpected " msg ": %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##op); \ + HL_CUR_TOKEN(op); \ + ADD_OP_NODE(cur_node); \ + break; \ + } SIMPLE_B_OP(Or, "or operator") SIMPLE_B_OP(And, "and operator") #undef SIMPLE_B_OP @@ -2174,14 +2174,14 @@ case kExprLex##op: { \ ADD_VALUE_IF_MISSING(_("E15: Unexpected multiplication-like operator: %.*s")); switch (cur_token.data.mul.type) { #define MUL_OP(lex_op_tail, node_op_tail) \ -case kExprLexMul##lex_op_tail: { \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##node_op_tail); \ - HL_CUR_TOKEN(node_op_tail); \ - break; \ -} - MUL_OP(Mul, Multiplication) - MUL_OP(Div, Division) - MUL_OP(Mod, Mod) + case kExprLexMul##lex_op_tail: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##node_op_tail); \ + HL_CUR_TOKEN(node_op_tail); \ + break; \ + } + MUL_OP(Mul, Multiplication) + MUL_OP(Div, Division) + MUL_OP(Mod, Mod) #undef MUL_OP } ADD_OP_NODE(cur_node); @@ -2907,7 +2907,7 @@ viml_pexpr_parse_no_paren_closing_error: {} ? kExprNodeDoubleQuotedString : kExprNodeSingleQuotedString)); *top_node_p = cur_node; - parse_quoted_string(pstate, cur_node, cur_token, ast_stack, is_invalid); + parse_quoted_string(pstate, cur_node, cur_token, &ast_stack, is_invalid); want_node = kENodeOperator; break; } @@ -2929,11 +2929,11 @@ viml_pexpr_parse_no_paren_closing_error: {} cur_node->data.ass.type = cur_token.data.ass.type; switch (cur_token.data.ass.type) { #define HL_ASGN(asgn, hl) \ -case kExprAsgn##asgn: { HL_CUR_TOKEN(hl); break; } - HL_ASGN(Plain, PlainAssignment) - HL_ASGN(Add, AssignmentWithAddition) - HL_ASGN(Subtract, AssignmentWithSubtraction) - HL_ASGN(Concat, AssignmentWithConcatenation) + case kExprAsgn##asgn: { HL_CUR_TOKEN(hl); break; } + HL_ASGN(Plain, PlainAssignment) + HL_ASGN(Add, AssignmentWithAddition) + HL_ASGN(Subtract, AssignmentWithSubtraction) + HL_ASGN(Concat, AssignmentWithConcatenation) #undef HL_ASGN } ADD_OP_NODE(cur_node); diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index fe9327b27d..9d0bc9d468 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -57,7 +57,7 @@ typedef enum { } LexExprTokenType; typedef enum { - kExprCmpEqual, ///< Equality, unequality. + kExprCmpEqual, ///< Equality, inequality. kExprCmpMatches, ///< Matches regex, not matches regex. kExprCmpGreater, ///< `>` or `<=` kExprCmpGreaterOrEqual, ///< `>=` or `<`. diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index b2933c3781..b8835127e7 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -81,10 +81,8 @@ typedef struct { bool can_continuate; } ParserState; -static inline void viml_parser_init( - ParserState *const ret_pstate, - const ParserLineGetter get_line, void *const cookie, - ParserHighlight *const colors) +static inline void viml_parser_init(ParserState *const ret_pstate, const ParserLineGetter get_line, + void *const cookie, ParserHighlight *const colors) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2); /// Initialize a new parser state instance diff --git a/src/nvim/window.c b/src/nvim/window.c index 9c13264684..2ca5128445 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -27,10 +27,10 @@ #include "nvim/hashtab.h" #include "nvim/main.h" #include "nvim/mark.h" +#include "nvim/match.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -59,9 +59,9 @@ #endif -#define NOWIN (win_T *)-1 // non-existing window +#define NOWIN ((win_T *)-1) // non-existing window -#define ROWS_AVAIL (Rows - p_ch - tabline_height()) +#define ROWS_AVAIL (Rows - p_ch - tabline_height() - global_stl_height()) /// flags for win_enter_ext() typedef enum { @@ -74,6 +74,15 @@ typedef enum { static char *m_onlyone = N_("Already only one window"); +/// @return the current window, unless in the cmdline window and "prevwin" is +/// set, then return "prevwin". +win_T *prevwin_curwin(void) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + // In cmdwin, the alternative buffer should be used. + return is_in_cmdwin() && prevwin != NULL ? prevwin : curwin; +} + /// all CTRL-W window commands are handled here, called from normal_cmd(). /// /// @param xchar extra char from ":wincmd gx" or NUL @@ -291,7 +300,7 @@ newwindow: // move window to new tab page case 'T': - if (one_window()) { + if (one_window(curwin)) { msg(_(m_onlyone)); } else { tabpage_T *oldtab = curtab; @@ -305,7 +314,7 @@ newwindow: newtab = curtab; goto_tabpage_tp(oldtab, true, true); if (curwin == wp) { - win_close(curwin, false); + win_close(curwin, false, false); } if (valid_tabpage(newtab)) { goto_tabpage_tp(newtab, true, true); @@ -450,7 +459,7 @@ wingotofile: RESET_BINDING(curwin); if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) { // Failed to open the file, close the window opened for it. - win_close(curwin, false); + win_close(curwin, false, false); goto_tabpage_win(oldtab, oldwin); } else if (nchar == 'F' && lnum >= 0) { curwin->w_cursor.lnum = lnum; @@ -522,10 +531,6 @@ wingotofile: do_nv_ident('g', xchar); break; - case TAB: - goto_tabpage_lastused(); - break; - case 'f': // CTRL-W gf: "gf" in a new tab page case 'F': // CTRL-W gF: "gF" in a new tab page cmdmod.tab = tabpage_index(curtab) + 1; @@ -539,6 +544,12 @@ wingotofile: goto_tabpage(-(int)Prenum1); break; + case TAB: // CTRL-W g<Tab>: go to last used tab page + if (!goto_tabpage_lastused()) { + beep_flush(); + } + break; + case 'e': if (curwin->w_floating || !ui_has(kUIMultigrid)) { beep_flush(); @@ -549,7 +560,7 @@ wingotofile: config.height = curwin->w_height; config.external = true; Error err = ERROR_INIT; - if (!win_new_float(curwin, config, &err)) { + if (!win_new_float(curwin, false, config, &err)) { emsg(err.msg); api_clear_error(&err); beep_flush(); @@ -578,18 +589,19 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, int64_t Pren void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) { - win_T *win = find_window_by_handle(window, err), *save_curwin = curwin; + win_T *win = find_window_by_handle(window, err); buf_T *buf = find_buffer_by_handle(buffer, err); - tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab; + tabpage_T *tab = win_find_tabpage(win); if (!win || !buf) { return; } - if (noautocmd) { block_autocmds(); } - if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) { + + switchwin_T switchwin; + if (switch_win_noblock(&switchwin, win, tab, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to switch to window %d", @@ -609,7 +621,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) // So do it now. validate_cursor(); - restore_win_noblock(save_curwin, save_curtab, false); + restore_win_noblock(&switchwin, false); if (noautocmd) { unblock_autocmds(); } @@ -617,16 +629,18 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) /// Create a new float. /// -/// if wp == NULL allocate a new window, otherwise turn existing window into a -/// float. It must then already belong to the current tabpage! -/// -/// config must already have been validated! -win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) +/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float. +/// It must then already belong to the current tabpage! +/// @param last make the window the last one in the window list. +/// Only used when allocating the autocommand window. +/// @param config must already have been validated! +win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err) { if (wp == NULL) { - wp = win_alloc(lastwin_nofloating(), false); + wp = win_alloc(last ? lastwin : lastwin_nofloating(), false); win_init(wp, curwin, 0); } else { + assert(!last); assert(!wp->w_floating); if (firstwin == wp && lastwin_nofloating() == wp) { // last non-float @@ -647,6 +661,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) } wp->w_floating = 1; wp->w_status_height = 0; + wp->w_hsep_height = 0; wp->w_vsep_width = 0; win_config_float(wp, fconfig); @@ -821,7 +836,7 @@ void ui_ext_win_position(win_T *wp) FloatConfig c = wp->w_float_config; if (!c.external) { ScreenGrid *grid = &default_grid; - float row = c.row, col = c.col; + Float row = c.row, col = c.col; if (c.relative == kFloatRelativeWindow) { Error dummy = ERROR_INIT; win_T *win = find_window_by_handle(c.window, &dummy); @@ -958,6 +973,11 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) int wmh1; bool did_set_fraction = false; + // aucmd_win should always remain floating + if (new_wp != NULL && new_wp == aucmd_win) { + return FAIL; + } + if (flags & WSP_TOP) { oldwin = firstwin; } else if (flags & WSP_BOT || curwin->w_floating) { @@ -1151,8 +1171,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) while (frp != NULL) { if (frp->fr_win != oldwin && frp->fr_win != NULL && (frp->fr_win->w_height > new_size - || frp->fr_win->w_height > oldwin_height - new_size - - STATUS_HEIGHT)) { + || frp->fr_win->w_height > oldwin_height - new_size - STATUS_HEIGHT)) { do_equal = true; break; } @@ -1278,13 +1297,15 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) if (flags & (WSP_TOP | WSP_BOT)) { // set height and row of new window to full height wp->w_winrow = tabline_height(); - win_new_height(wp, curfrp->fr_height - (p_ls > 0)); - wp->w_status_height = (p_ls > 0); + win_new_height(wp, curfrp->fr_height - (p_ls == 1 || p_ls == 2)); + wp->w_status_height = (p_ls == 1 || p_ls == 2); + wp->w_hsep_height = 0; } else { // height and row of new window is same as current window wp->w_winrow = oldwin->w_winrow; win_new_height(wp, oldwin->w_height); wp->w_status_height = oldwin->w_status_height; + wp->w_hsep_height = oldwin->w_hsep_height; } frp->fr_height = curfrp->fr_height; @@ -1317,6 +1338,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) frame_fix_width(oldwin); frame_fix_width(wp); } else { + bool is_stl_global = global_stl_height() > 0; // width and column of new window is same as current window if (flags & (WSP_TOP | WSP_BOT)) { wp->w_wincol = 0; @@ -1332,28 +1354,52 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // "new_size" of the current window goes to the new window, use // one row for the status line win_new_height(wp, new_size); + if (before) { + wp->w_hsep_height = is_stl_global ? 1 : 0; + } else { + wp->w_hsep_height = oldwin->w_hsep_height; + oldwin->w_hsep_height = is_stl_global ? 1 : 0; + } if (flags & (WSP_TOP | WSP_BOT)) { int new_fr_height = curfrp->fr_height - new_size; - if (!((flags & WSP_BOT) && p_ls == 0)) { + if (!((flags & WSP_BOT) && p_ls == 0) && global_stl_height() == 0) { new_fr_height -= STATUS_HEIGHT; + } else if (global_stl_height() > 0) { + if (flags & WSP_BOT) { + frame_add_hsep(curfrp); + } else { + new_fr_height -= 1; + } } frame_new_height(curfrp, new_fr_height, flags & WSP_TOP, false); } else { win_new_height(oldwin, oldwin_height - (new_size + STATUS_HEIGHT)); } + if (before) { // new window above current one wp->w_winrow = oldwin->w_winrow; - wp->w_status_height = STATUS_HEIGHT; - oldwin->w_winrow += wp->w_height + STATUS_HEIGHT; + + if (is_stl_global) { + wp->w_status_height = 0; + oldwin->w_winrow += wp->w_height + 1; + } else { + wp->w_status_height = STATUS_HEIGHT; + oldwin->w_winrow += wp->w_height + STATUS_HEIGHT; + } } else { // new window below current one - wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT; - wp->w_status_height = oldwin->w_status_height; - if (!(flags & WSP_BOT)) { - oldwin->w_status_height = STATUS_HEIGHT; + if (is_stl_global) { + wp->w_winrow = oldwin->w_winrow + oldwin->w_height + 1; + wp->w_status_height = 0; + } else { + wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT; + wp->w_status_height = oldwin->w_status_height; + if (!(flags & WSP_BOT)) { + oldwin->w_status_height = STATUS_HEIGHT; + } } } - if (flags & WSP_BOT) { + if ((flags & WSP_BOT) && global_stl_height() == 0) { frame_add_statusline(curfrp); } frame_fix_height(wp); @@ -1415,13 +1461,11 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) p_wh = i; } - if (!win_valid(oldwin)) { - return FAIL; + if (win_valid(oldwin)) { + // Send the window positions to the UI + oldwin->w_pos_changed = true; } - // Send the window positions to the UI - oldwin->w_pos_changed = true; - return OK; } @@ -1482,8 +1526,6 @@ static void win_init(win_T *newp, win_T *oldp, int flags) copyFoldingState(oldp, newp); win_init_some(newp, oldp); - - didset_window_options(newp); } /* @@ -1592,14 +1634,14 @@ int make_windows(int count, bool vertical) int todo; if (vertical) { - // Each windows needs at least 'winminwidth' lines and a separator + // Each window needs at least 'winminwidth' lines and a separator // column. maxcount = (curwin->w_width + curwin->w_vsep_width - (p_wiw - p_wmw)) / (p_wmw + 1); } else { - // Each window needs at least 'winminheight' lines and a status line. - maxcount = (curwin->w_height - + curwin->w_status_height + // Each window needs at least 'winminheight' lines + // If statusline isn't global, each window also needs a statusline + maxcount = (curwin->w_height + curwin->w_hsep_height + curwin->w_status_height - (p_wh - p_wmh)) / (p_wmh + STATUS_HEIGHT); } @@ -1695,7 +1737,7 @@ static void win_exchange(long Prenum) * if wp != wp2 * 3. remove wp from the list * 4. insert wp after wp2 - * 5. exchange the status line height and vsep width. + * 5. exchange the status line height, hsep height and vsep width. */ wp2 = curwin->w_prev; frp2 = curwin->w_frame->fr_prev; @@ -1721,6 +1763,9 @@ static void win_exchange(long Prenum) temp = curwin->w_vsep_width; curwin->w_vsep_width = wp->w_vsep_width; wp->w_vsep_width = temp; + temp = curwin->w_hsep_height; + curwin->w_hsep_height = wp->w_hsep_height; + wp->w_hsep_height = temp; frame_fix_height(curwin); frame_fix_height(wp); @@ -1729,6 +1774,12 @@ static void win_exchange(long Prenum) (void)win_comp_pos(); // recompute window positions + if (wp->w_buffer != curbuf) { + reset_VIsual_and_resel(); + } else if (VIsual_active) { + wp->w_cursor = curwin->w_cursor; + } + win_enter(wp, true); redraw_later(curwin, NOT_VALID); redraw_later(wp, NOT_VALID); @@ -1795,10 +1846,13 @@ static void win_rotate(bool upwards, int count) frame_insert(frp->fr_parent->fr_child, frp); } - // exchange status height and vsep width of old and new last window + // exchange status height, hsep height and vsep width of old and new last window n = wp2->w_status_height; wp2->w_status_height = wp1->w_status_height; wp1->w_status_height = n; + n = wp2->w_hsep_height; + wp2->w_hsep_height = wp1->w_hsep_height; + wp1->w_hsep_height = n; frame_fix_height(wp1); frame_fix_height(wp2); n = wp2->w_vsep_width; @@ -1829,6 +1883,9 @@ static void win_totop(int size, int flags) beep_flush(); return; } + if (curwin == aucmd_win) { + return; + } if (curwin->w_floating) { ui_comp_remove_grid(&curwin->w_grid_alloc); @@ -1872,11 +1929,16 @@ void win_move_after(win_T *win1, win_T *win2) // check if there is something to do if (win2->w_next != win1) { - // may need move the status line/vertical separator of the last window + // may need move the status line, horizontal or vertical separator of the last window if (win1 == lastwin) { height = win1->w_prev->w_status_height; win1->w_prev->w_status_height = win1->w_status_height; win1->w_status_height = height; + + height = win1->w_prev->w_hsep_height; + win1->w_prev->w_hsep_height = win1->w_hsep_height; + win1->w_hsep_height = height; + if (win1->w_prev->w_vsep_width == 1) { // Remove the vertical separator from the last-but-one window, // add it to the last window. Adjust the frame widths. @@ -1889,6 +1951,11 @@ void win_move_after(win_T *win1, win_T *win2) height = win1->w_status_height; win1->w_status_height = win2->w_status_height; win2->w_status_height = height; + + height = win1->w_hsep_height; + win1->w_hsep_height = win2->w_hsep_height; + win2->w_hsep_height = height; + if (win1->w_vsep_width == 1) { // Remove the vertical separator from win1, add it to the last // window, win2. Adjust the frame widths. @@ -2103,13 +2170,15 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int if (dir != 'h') { // equalize frame heights // Compute maximum number of windows vertically in this frame. n = frame_minheight(topfr, NOWIN); - // add one for the bottom window if it doesn't have a statusline + // add one for the bottom window if it doesn't have a statusline or separator if (row + height == cmdline_row && p_ls == 0) { + extra_sep = STATUS_HEIGHT; + } else if (global_stl_height() > 0) { extra_sep = 1; } else { extra_sep = 0; } - totwincount = (n + extra_sep) / (p_wmh + 1); + totwincount = (n + extra_sep) / (p_wmh + STATUS_HEIGHT); has_next_curwin = frame_has_win(topfr, next_curwin); /* @@ -2120,7 +2189,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int m = frame_minheight(topfr, next_curwin); room = height - m; if (room < 0) { - // The room is less then 'winheight', use all space for the + // The room is less than 'winheight', use all space for the // current window. next_curwin_size = p_wh + room; room = 0; @@ -2144,7 +2213,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } else { // These windows don't use up room. totwincount -= (n + (fr->fr_next == NULL - ? extra_sep : 0)) / (p_wmh + 1); + ? extra_sep : 0)) / (p_wmh + STATUS_HEIGHT); } room -= new_size - n; if (room < 0) { @@ -2190,7 +2259,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int // Compute the maximum number of windows vert. in "fr". n = frame_minheight(fr, NOWIN); wincount = (n + (fr->fr_next == NULL ? extra_sep : 0)) - / (p_wmh + 1); + / (p_wmh + STATUS_HEIGHT); m = frame_minheight(fr, next_curwin); if (has_next_curwin) { hnc = frame_has_win(fr, next_curwin); @@ -2229,28 +2298,102 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } } -/// Closes all windows for buffer `buf`. +static void leaving_window(win_T *const win) + FUNC_ATTR_NONNULL_ALL +{ + // Only matters for a prompt window. + if (!bt_prompt(win->w_buffer)) { + return; + } + + // When leaving a prompt window stop Insert mode and perhaps restart + // it when entering that window again. + win->w_buffer->b_prompt_insert = restart_edit; + if (restart_edit != NUL && mode_displayed) { + clear_cmdline = true; // unshow mode later + } + restart_edit = NUL; + + // When leaving the window (or closing the window) was done from a + // callback we need to break out of the Insert mode loop and restart Insert + // mode when entering the window again. + if (State & INSERT) { + stop_insert_mode = true; + if (win->w_buffer->b_prompt_insert == NUL) { + win->w_buffer->b_prompt_insert = 'A'; + } + } +} + +void entering_window(win_T *const win) + FUNC_ATTR_NONNULL_ALL +{ + // Only matters for a prompt window. + if (!bt_prompt(win->w_buffer)) { + return; + } + + // When switching to a prompt buffer that was in Insert mode, don't stop + // Insert mode, it may have been set in leaving_window(). + if (win->w_buffer->b_prompt_insert != NUL) { + stop_insert_mode = false; + } + + // When entering the prompt window restart Insert mode if we were in Insert + // mode when we left it and not already in Insert mode. + if ((State & INSERT) == 0) { + restart_edit = win->w_buffer->b_prompt_insert; + } +} + +void win_init_empty(win_T *wp) +{ + redraw_later(wp, NOT_VALID); + wp->w_lines_valid = 0; + wp->w_cursor.lnum = 1; + wp->w_curswant = wp->w_cursor.col = 0; + wp->w_cursor.coladd = 0; + wp->w_pcmark.lnum = 1; // pcmark not cleared but set to line 1 + wp->w_pcmark.col = 0; + wp->w_prev_pcmark.lnum = 0; + wp->w_prev_pcmark.col = 0; + wp->w_topline = 1; + wp->w_topfill = 0; + wp->w_botline = 2; + wp->w_s = &wp->w_buffer->b_s; +} + +/// Init the current window "curwin". +/// Called when a new file is being edited. +void curwin_init(void) +{ + win_init_empty(curwin); +} + +/// Closes all windows for buffer `buf` unless there is only one non-floating window. /// -/// @param keep_curwin don't close `curwin` -void close_windows(buf_T *buf, int keep_curwin) +/// @param keep_curwin don't close `curwin` +void close_windows(buf_T *buf, bool keep_curwin) { tabpage_T *tp, *nexttp; int h = tabline_height(); ++RedrawingDisabled; - for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW;) { + // Start from lastwin to close floating windows with the same buffer first. + // When the autocommand window is involved win_close() may need to print an error message. + for (win_T *wp = lastwin; wp != NULL && (lastwin == aucmd_win || !one_window(wp));) { if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) && !(wp->w_closing || wp->w_buffer->b_locked > 0)) { - if (win_close(wp, false) == FAIL) { + if (win_close(wp, false, false) == FAIL) { // If closing the window fails give up, to avoid looping forever. break; } // Start all over, autocommands may change the window layout. - wp = firstwin; + wp = lastwin; } else { - wp = wp->w_next; + wp = wp->w_prev; } } @@ -2280,23 +2423,24 @@ void close_windows(buf_T *buf, int keep_curwin) } } -/// Check that current window is the last one. +/// Check that the specified window is the last one. +/// @param win counted even if floating /// -/// @return true if the current window is the only window that exists, false if -/// there is another, possibly in another tab page. -static bool last_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +/// @return true if the specified window is the only window that exists, +/// false if there is another, possibly in another tab page. +bool last_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return one_window() && first_tabpage->tp_next == NULL; + return one_window(win) && first_tabpage->tp_next == NULL; } -/// Check that current tab page contains no more then one window other than -/// "aucmd_win". Only counts floating window if it is current. -bool one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +/// Check that current tab page contains no more then one window other than `aucmd_win`. +/// @param counted_float counted even if floating, but not if it is `aucmd_win` +bool one_window(win_T *counted_float) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { bool seen_one = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp != aucmd_win && (!wp->w_floating || wp == curwin)) { + if (wp != aucmd_win && (!wp->w_floating || wp == counted_float)) { if (seen_one) { return false; } @@ -2320,6 +2464,24 @@ bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT return wp != NULL && firstwin == wp && !(wp->w_next && !wp->w_floating); } +/// Check if floating windows in the current tab can be closed. +/// Do not call this when the autocommand window is in use! +/// +/// @return true if all floating windows can be closed +static bool can_close_floating_windows(void) +{ + assert(lastwin != aucmd_win); + for (win_T *wp = lastwin; wp->w_floating; wp = wp->w_prev) { + buf_T *buf = wp->w_buffer; + int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); + + if (need_hide && !buf_hide(buf)) { + return false; + } + } + return true; +} + /// Close the possibly last window in a tab page. /// /// @param win window to close @@ -2367,6 +2529,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev shell_new_rows(); } } + entering_window(curwin); // Since goto_tabpage_tp above did not trigger *Enter autocommands, do // that now. @@ -2378,12 +2541,47 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev return true; } +/// Close the buffer of "win" and unload it if "free_buf" is true. +/// "abort_if_last" is passed to close_buffer(): abort closing if all other +/// windows are closed. +static void win_close_buffer(win_T *win, bool free_buf, bool abort_if_last) +{ + // Free independent synblock before the buffer is freed. + if (win->w_buffer != NULL) { + reset_synblock(win); + } + + // When a quickfix/location list window is closed and the buffer is + // displayed in only one window, then unlist the buffer. + if (win->w_buffer != NULL && bt_quickfix(win->w_buffer) + && win->w_buffer->b_nwindows == 1) { + win->w_buffer->b_p_bl = false; + } + + // Close the link to the buffer. + if (win->w_buffer != NULL) { + bufref_T bufref; + set_bufref(&bufref, curbuf); + win->w_closing = true; + close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, abort_if_last, true); + if (win_valid_any_tab(win)) { + win->w_closing = false; + } + + // Make sure curbuf is valid. It can become invalid if 'bufhidden' is + // "wipe". + if (!bufref_valid(&bufref)) { + curbuf = firstbuf; + } + } +} + // Close window "win". Only works for the current tab page. // If "free_buf" is true related buffer may be unloaded. // // Called by :quit, :close, :xit, :wq and findtag(). // Returns FAIL when the window was not closed. -int win_close(win_T *win, bool free_buf) +int win_close(win_T *win, bool free_buf, bool force) { win_T *wp; bool other_buffer = false; @@ -2394,7 +2592,7 @@ int win_close(win_T *win, bool free_buf) frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent; const bool had_diffmode = win->w_p_diff; - if (last_window() && !win->w_floating) { + if (last_window(win)) { emsg(_("E444: Cannot close last window")); return FAIL; } @@ -2407,15 +2605,24 @@ int win_close(win_T *win, bool free_buf) emsg(_(e_autocmd_close)); return FAIL; } - if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) { - emsg(_("E814: Cannot close window, only autocmd window would remain")); - return FAIL; - } - if ((firstwin == win && lastwin_nofloating() == win) - && lastwin->w_floating) { - // TODO(bfredl): we might close the float also instead - emsg(e_floatonly); - return FAIL; + if (lastwin->w_floating && one_window(win)) { + if (lastwin == aucmd_win) { + emsg(_("E814: Cannot close window, only autocmd window would remain")); + return FAIL; + } + if (force || can_close_floating_windows()) { + // close the last window until the there are no floating windows + while (lastwin->w_floating) { + // `force` flag isn't actually used when closing a floating window. + if (win_close(lastwin, free_buf, true) == FAIL) { + // If closing the window fails give up, to avoid looping forever. + return FAIL; + } + } + } else { + emsg(e_floatonly); + return FAIL; + } } // When closing the last window in a tab page first go to another tab page @@ -2434,10 +2641,10 @@ int win_close(win_T *win, bool free_buf) } if (win == curwin) { - /* - * Guess which window is going to be the new current window. - * This may change because of the autocommands (sigh). - */ + leaving_window(curwin); + + // Guess which window is going to be the new current window. + // This may change because of the autocommands (sigh). if (!win->w_floating) { wp = frame2win(win_altframe(win, NULL)); } else { @@ -2460,7 +2667,7 @@ int win_close(win_T *win, bool free_buf) return FAIL; } win->w_closing = false; - if (last_window()) { + if (last_window(win)) { return FAIL; } } @@ -2470,7 +2677,7 @@ int win_close(win_T *win, bool free_buf) return FAIL; } win->w_closing = false; - if (last_window()) { + if (last_window(win)) { return FAIL; } // autocmds may abort script processing @@ -2507,32 +2714,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) { - reset_synblock(win); - } - - /* - * Close the link to the buffer. - */ - if (win->w_buffer != NULL) { - bufref_T bufref; - set_bufref(&bufref, curbuf); - win->w_closing = true; - close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, true); - if (win_valid_any_tab(win)) { - win->w_closing = false; - } - - // Make sure curbuf is valid. It can become invalid if 'bufhidden' is - // "wipe". - if (!bufref_valid(&bufref)) { - curbuf = firstbuf; - } - } + win_close_buffer(win, free_buf, true); if (only_one_window() && win_valid(win) && win->w_buffer == NULL - && (last_window() || curtab != prev_curtab + && (last_window(win) || curtab != prev_curtab || close_last_window_tabpage(win, free_buf, prev_curtab)) && !win->w_floating) { // Autocommands have closed all windows, quit now. Restore @@ -2552,7 +2737,7 @@ int win_close(win_T *win, bool free_buf) // Autocommands may have closed the window already, or closed the only // other window or moved to another tab page. - if (!win_valid(win) || (!win->w_floating && last_window()) + if (!win_valid(win) || (!win->w_floating && last_window(win)) || close_last_window_tabpage(win, free_buf, prev_curtab)) { return FAIL; } @@ -2666,7 +2851,7 @@ static void do_autocmd_winclosed(win_T *win) } recursive = true; char_u winid[NUMBUFLEN]; - vim_snprintf((char *)winid, sizeof(winid), "%i", win->handle); + vim_snprintf((char *)winid, sizeof(winid), "%d", win->handle); apply_autocmds(EVENT_WINCLOSED, winid, winid, false, win->w_buffer); recursive = false; } @@ -2700,7 +2885,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) if (win->w_buffer != NULL) { // Close the link to the buffer. - close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false); + close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false, true); } // Careful: Autocommands may have closed the tab page or made it the @@ -2708,6 +2893,13 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) for (ptp = first_tabpage; ptp != NULL && ptp != tp; ptp = ptp->tp_next) { } if (ptp == NULL || tp == curtab) { + // If the buffer was removed from the window we have to give it any + // buffer. + if (win_valid_any_tab(win) && win->w_buffer == NULL) { + win->w_buffer = firstbuf; + firstbuf->b_nwindows++; + win_init_empty(win); + } return; } @@ -2804,6 +2996,9 @@ void win_free_all(void) { int dummy; + // avoid an error for switching tabpage with the cmdline window open + cmdwin_type = 0; + while (first_tabpage->tp_next != NULL) { tabpage_close(TRUE); } @@ -3007,9 +3202,21 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp) return frp->fr_prev; } + // By default the next window will get the space that was abandoned by this + // window frame_T *target_fr = frp->fr_next; frame_T *other_fr = frp->fr_prev; - if (p_spr || p_sb) { + + // If this is part of a column of windows and 'splitbelow' is true then the + // previous window will get the space. + if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_COL && p_sb) { + target_fr = frp->fr_prev; + other_fr = frp->fr_next; + } + + // If this is part of a row of windows, and 'splitright' is true then the + // previous window will get the space. + if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW && p_spr) { target_fr = frp->fr_prev; other_fr = frp->fr_next; } @@ -3050,7 +3257,7 @@ static tabpage_T *alt_tabpage(void) /* * Find the left-upper window in frame "frp". */ -static win_T *frame2win(frame_T *frp) +win_T *frame2win(frame_T *frp) { while (frp->fr_win == NULL) { frp = frp->fr_child; @@ -3077,23 +3284,40 @@ static bool frame_has_win(const frame_T *frp, const win_T *wp) return false; } +/// Check if current window is at the bottom +/// Returns true if there are no windows below current window +static bool is_bottom_win(win_T *wp) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + frame_T *frp; + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_COL && frp->fr_next != NULL) { + return false; + } + } + return true; +} /// Set a new height for a frame. Recursively sets the height for contained /// frames and windows. Caller must take care of positions. /// /// @param topfirst resize topmost contained frame first. /// @param wfh obey 'winfixheight' when there is a choice; /// may cause the height not to be set. -static void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) +void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) FUNC_ATTR_NONNULL_ALL { frame_T *frp; int extra_lines; int h; + win_T *wp; if (topfrp->fr_win != NULL) { // Simple case: just one window. - win_new_height(topfrp->fr_win, - height - topfrp->fr_win->w_status_height); + wp = topfrp->fr_win; + if (is_bottom_win(wp)) { + wp->w_hsep_height = 0; + } + win_new_height(wp, height - wp->w_hsep_height - wp->w_status_height); } else if (topfrp->fr_layout == FR_ROW) { do { // All frames in this row get the same new height. @@ -3249,8 +3473,8 @@ static void frame_add_statusline(frame_T *frp) if (frp->fr_layout == FR_LEAF) { wp = frp->fr_win; if (wp->w_status_height == 0) { - if (wp->w_height > 0) { // don't make it negative - --wp->w_height; + if (wp->w_height - STATUS_HEIGHT >= 0) { // don't make it negative + wp->w_height -= STATUS_HEIGHT; } wp->w_status_height = STATUS_HEIGHT; } @@ -3370,10 +3594,8 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw topfrp->fr_width = width; } -/* - * Add the vertical separator to windows at the right side of "frp". - * Note: Does not check if there is room! - */ +/// Add the vertical separator to windows at the right side of "frp". +/// Note: Does not check if there is room! static void frame_add_vsep(const frame_T *frp) FUNC_ATTR_NONNULL_ARG(1) { @@ -3403,6 +3625,37 @@ static void frame_add_vsep(const frame_T *frp) } } +/// Add the horizontal separator to windows at the bottom of "frp". +/// Note: Does not check if there is room or whether the windows have a statusline! +static void frame_add_hsep(const frame_T *frp) + FUNC_ATTR_NONNULL_ARG(1) +{ + win_T *wp; + + if (frp->fr_layout == FR_LEAF) { + wp = frp->fr_win; + if (wp->w_hsep_height == 0) { + if (wp->w_height > 0) { // don't make it negative + wp->w_height++; + } + wp->w_hsep_height = 1; + } + } else if (frp->fr_layout == FR_ROW) { + // Handle all the frames in the row. + FOR_ALL_FRAMES(frp, frp->fr_child) { + frame_add_hsep(frp); + } + } else { + assert(frp->fr_layout == FR_COL); + // Only need to handle the last frame in the column. + frp = frp->fr_child; + while (frp->fr_next != NULL) { + frp = frp->fr_next; + } + frame_add_hsep(frp); + } +} + /* * Set frame width from the window it contains. */ @@ -3417,7 +3670,7 @@ static void frame_fix_width(win_T *wp) static void frame_fix_height(win_T *wp) FUNC_ATTR_NONNULL_ALL { - wp->w_frame->fr_height = wp->w_height + wp->w_status_height; + wp->w_frame->fr_height = wp->w_height + wp->w_hsep_height + wp->w_status_height; } /* @@ -3435,10 +3688,11 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) if (topfrp->fr_win != NULL) { if (topfrp->fr_win == next_curwin) { - m = p_wh + topfrp->fr_win->w_status_height; + m = p_wh + topfrp->fr_win->w_hsep_height + topfrp->fr_win->w_status_height; } else { - // window: minimal height of the window plus status line - m = p_wmh + topfrp->fr_win->w_status_height; + // window: minimal height of the window plus separator column or status line + // depending on whether global statusline is enabled + m = p_wmh + topfrp->fr_win->w_hsep_height + topfrp->fr_win->w_status_height; if (topfrp->fr_win == curwin && next_curwin == NULL) { // Current window is minimal one line high. if (p_wmh == 0) { @@ -3529,7 +3783,7 @@ void close_others(int message, int forceit) return; } - if (one_window() && !lastwin->w_floating) { + if (one_nonfloat() && !lastwin->w_floating) { if (message && !autocmd_busy) { msg(_(m_onlyone)); @@ -3562,7 +3816,9 @@ void close_others(int message, int forceit) continue; } } - win_close(wp, !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); + win_close(wp, + !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer), + false); } if (message && !ONE_WINDOW) { @@ -3570,33 +3826,6 @@ void close_others(int message, int forceit) } } - -/* - * Init the current window "curwin". - * Called when a new file is being edited. - */ -void curwin_init(void) -{ - win_init_empty(curwin); -} - -void win_init_empty(win_T *wp) -{ - redraw_later(wp, NOT_VALID); - wp->w_lines_valid = 0; - wp->w_cursor.lnum = 1; - wp->w_curswant = wp->w_cursor.col = 0; - wp->w_cursor.coladd = 0; - wp->w_pcmark.lnum = 1; // pcmark not cleared but set to line 1 - wp->w_pcmark.col = 0; - wp->w_prev_pcmark.lnum = 0; - wp->w_prev_pcmark.col = 0; - wp->w_topline = 1; - wp->w_topfill = 0; - wp->w_botline = 2; - wp->w_s = &wp->w_buffer->b_s; -} - /* * Allocate the first window and put an empty buffer in it. * Called from main(). @@ -3628,7 +3857,7 @@ void win_alloc_aucmd_win(void) fconfig.width = Columns; fconfig.height = 5; fconfig.focusable = false; - aucmd_win = win_new_float(NULL, fconfig, &err); + aucmd_win = win_new_float(NULL, true, fconfig, &err); aucmd_win->w_buffer->b_nwindows--; RESET_BINDING(aucmd_win); } @@ -3665,7 +3894,7 @@ static int win_alloc_firstwin(win_T *oldwin) new_frame(curwin); topframe = curwin->w_frame; topframe->fr_width = Columns; - topframe->fr_height = Rows - p_ch; + topframe->fr_height = Rows - p_ch - global_stl_height(); return OK; } @@ -3752,6 +3981,11 @@ int win_new_tabpage(int after, char_u *filename) tabpage_T *newtp; int n; + if (cmdwin_type != 0) { + emsg(_(e_cmdwin)); + return FAIL; + } + newtp = alloc_tabpage(); // Remember the current windows in this Tab page. @@ -3801,6 +4035,8 @@ int win_new_tabpage(int after, char_u *filename) lastused_tabpage = old_curtab; + entering_window(curwin); + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); @@ -3956,6 +4192,7 @@ static int leave_tabpage(buf_T *new_curbuf, bool trigger_leave_autocmds) { tabpage_T *tp = curtab; + leaving_window(curwin); reset_VIsual_and_resel(); // stop Visual mode if (trigger_leave_autocmds) { if (new_curbuf != curbuf) { @@ -3994,8 +4231,8 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a { int old_off = tp->tp_firstwin->w_winrow; win_T *next_prevwin = tp->tp_prevwin; - tabpage_T *old_curtab = curtab; + curtab = tp; firstwin = tp->tp_firstwin; lastwin = tp->tp_lastwin; @@ -4147,6 +4384,10 @@ void goto_tabpage(int n) /// @param trigger_leave_autocmds when true trigger *Leave autocommands. void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_leave_autocmds) { + if (trigger_enter_autocmds || trigger_leave_autocmds) { + CHECK_CMDWIN; + } + // Don't repeat a message in another tab page. set_keep_msg(NULL, 0); @@ -4162,13 +4403,15 @@ void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_le } } -// Go to the last accessed tab page, if there is one. -void goto_tabpage_lastused(void) +/// Go to the last accessed tab page, if there is one. +/// @return true if the tab page is valid, false otherwise. +bool goto_tabpage_lastused(void) { - int index = tabpage_index(lastused_tabpage); - if (index < tabpage_index(NULL)) { - goto_tabpage(index); + if (valid_tabpage(lastused_tabpage)) { + goto_tabpage_tp(lastused_tabpage, true, true); + return true; } + return false; } /* @@ -4478,6 +4721,10 @@ static void win_enter_ext(win_T *const wp, const int flags) return; } + if (!curwin_invalid) { + leaving_window(curwin); + } + if (!curwin_invalid && (flags & WEE_TRIGGER_LEAVE_AUTOCMDS)) { // Be careful: If autocommands delete the window, return now. if (wp->w_buffer != curbuf) { @@ -4525,6 +4772,8 @@ static void win_enter_ext(win_T *const wp, const int flags) fix_current_dir(); + entering_window(curwin); + // Careful: autocommands may close the window and make "wp" invalid if (flags & WEE_TRIGGER_NEW_AUTOCMDS) { apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); } @@ -4558,7 +4807,7 @@ static void win_enter_ext(win_T *const wp, const int flags) } // set window width to desired minimal value - if (curwin->w_width < p_wiw && !curwin->w_p_wfw && !wp->w_floating) { + if (curwin->w_width < p_wiw && !curwin->w_p_wfw && !curwin->w_floating) { win_setwidth((int)p_wiw); } @@ -4587,22 +4836,33 @@ void fix_current_dir(void) globaldir = (char_u *)xstrdup(cwd); } } + bool dir_differs = pathcmp(new_dir, cwd, -1) != 0; + if (!p_acd && dir_differs) { + do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage, + kCdCauseWindow, true); + } if (os_chdir(new_dir) == 0) { - if (!p_acd && pathcmp(new_dir, cwd, -1) != 0) { - do_autocmd_dirchanged(new_dir, curwin->w_localdir - ? kCdScopeWindow : kCdScopeTabpage, kCdCauseWindow); + if (!p_acd && dir_differs) { + do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage, + kCdCauseWindow, false); } - shorten_fnames(true); } + last_chdir_reason = NULL; + shorten_fnames(true); } else if (globaldir != NULL) { // Window doesn't have a local directory and we are not in the global // directory: Change to the global directory. + bool dir_differs = pathcmp((char *)globaldir, cwd, -1) != 0; + if (!p_acd && dir_differs) { + do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, true); + } if (os_chdir((char *)globaldir) == 0) { - if (!p_acd && pathcmp((char *)globaldir, cwd, -1) != 0) { - do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow); + if (!p_acd && dir_differs) { + do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, false); } } XFREE_CLEAR(globaldir); + last_chdir_reason = NULL; shorten_fnames(true); } } @@ -4754,6 +5014,8 @@ static void win_free(win_T *wp, tabpage_T *tp) clear_winopt(&wp->w_onebuf_opt); clear_winopt(&wp->w_allbuf_opt); + xfree(wp->w_p_lcs_chars.multispace); + vars_clear(&wp->w_vars->dv_hashtab); // free all w: variables hash_init(&wp->w_vars->dv_hashtab); unref_var_dict(wp->w_vars); @@ -4984,25 +5246,35 @@ 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) +/// Trigger WinScrolled for "curwin" if needed. +void may_trigger_winscrolled(void) { - 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); -} + static bool recursive = false; -/// Trigger WinScrolled autocmd -void do_autocmd_winscrolled(win_T *wp) -{ - apply_autocmds(EVENT_WINSCROLLED, NULL, NULL, false, curbuf); + if (recursive || !has_event(EVENT_WINSCROLLED)) { + return; + } + + win_T *wp = curwin; + if (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) { + char_u winid[NUMBUFLEN]; + vim_snprintf((char *)winid, sizeof(winid), "%d", wp->handle); + + recursive = true; + apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, wp->w_buffer); + recursive = false; - 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; + // an autocmd may close the window, "wp" may be invalid now + if (win_valid_any_tab(wp)) { + 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; + } + } } /* @@ -5048,11 +5320,8 @@ void win_size_restore(garray_T *gap) } } -/* - * Update the position for all windows, using the width and height of the - * frames. - * Returns the row just after the last window. - */ +// Update the position for all windows, using the width and height of the frames. +// Returns the row just after the last window and global statusline (if there is one). int win_comp_pos(void) { int row = tabline_height(); @@ -5067,7 +5336,7 @@ int win_comp_pos(void) } } - return row; + return row + global_stl_height(); } void win_reconfig_floats(void) @@ -5101,7 +5370,7 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) wp->w_redr_status = true; wp->w_pos_changed = true; } - const int h = wp->w_height + wp->w_status_height; + const int h = wp->w_height + wp->w_hsep_height + wp->w_status_height; *row += h > topfrp->fr_height ? topfrp->fr_height : h; *col += wp->w_width + wp->w_vsep_width; } else { @@ -5150,7 +5419,7 @@ void win_setheight_win(int height, win_T *win) win_config_float(win, win->w_float_config); redraw_later(win, NOT_VALID); } else { - frame_setheight(win->w_frame, height + win->w_status_height); + frame_setheight(win->w_frame, height + win->w_hsep_height + win->w_status_height); // recompute the window positions int row = win_comp_pos(); @@ -5159,11 +5428,17 @@ void win_setheight_win(int height, win_T *win) // line, clear it. if (full_screen && msg_scrolled == 0 && row < cmdline_row) { grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0); + if (msg_grid.chars) { + clear_cmdline = true; + } } cmdline_row = row; + p_ch = MAX(Rows - cmdline_row, 1); + curtab->tp_ch_used = p_ch; msg_row = row; msg_col = 0; redraw_all_later(NOT_VALID); + showmode(); } } @@ -5199,7 +5474,9 @@ static void frame_setheight(frame_T *curfrp, int height) if (curfrp->fr_parent == NULL) { // topframe: can only change the command line if (height > ROWS_AVAIL) { - height = ROWS_AVAIL; + // If height is greater than the available space, try to create space for the frame by + // reducing 'cmdheight' if possible, while making sure `cmdheight` doesn't go below 1. + height = MIN(ROWS_AVAIL + (p_ch - 1), height); } if (height > 0) { frame_new_height(curfrp, height, false, false); @@ -5241,8 +5518,8 @@ static void frame_setheight(frame_T *curfrp, int height) room_cmdline = 0; } else { win_T *wp = lastwin_nofloating(); - room_cmdline = Rows - p_ch - - (wp->w_winrow + wp->w_height + wp->w_status_height); + room_cmdline = Rows - p_ch - global_stl_height() + - (wp->w_winrow + wp->w_height + wp->w_hsep_height + wp->w_status_height); if (room_cmdline < 0) { room_cmdline = 0; } @@ -5608,7 +5885,7 @@ void win_drag_status_line(win_T *dragwin, int offset) if (curfr->fr_next == NULL) { room -= 1; } else { - room -= p_ch; + room -= p_ch + global_stl_height(); } if (room < 0) { room = 0; @@ -5734,7 +6011,6 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } fr = curfr; // put fr at window that grows } - assert(fr); // Not enough room if (room < offset) { @@ -5747,7 +6023,9 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } if (fr == NULL) { - return; // Safety check, should not happen. + // This can happen when calling win_move_separator() on the rightmost + // window. Just don't do anything. + return; } // grow frame fr by offset lines @@ -6244,72 +6522,94 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u return find_file_name_in_path(ptr, len, options, count, rel_fname); } -/// Add or remove a status line for the bottom window(s), according to the +/// Add or remove a status line from window(s), according to the /// value of 'laststatus'. /// /// @param morewin pretend there are two or more windows if true. void last_status(bool morewin) { // Don't make a difference between horizontal or vertical split. - last_status_rec(topframe, (p_ls == 2 - || (p_ls == 1 && (morewin || !one_window())))); + last_status_rec(topframe, (p_ls == 2 || (p_ls == 1 && (morewin || !one_nonfloat()))), + global_stl_height() > 0); } -static void last_status_rec(frame_T *fr, bool statusline) +// Look for resizable frames and take lines from them to make room for the statusline +static void resize_frame_for_status(frame_T *fr) +{ + // Find a frame to take a line from. + frame_T *fp = fr; + win_T *wp = fr->fr_win; + + while (fp->fr_height <= frame_minheight(fp, NULL)) { + if (fp == topframe) { + emsg(_(e_noroom)); + return; + } + // In a column of frames: go to frame above. If already at + // the top or in a row of frames: go to parent. + if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) { + fp = fp->fr_prev; + } else { + fp = fp->fr_parent; + } + } + if (fp != fr) { + frame_new_height(fp, fp->fr_height - 1, false, false); + frame_fix_height(wp); + (void)win_comp_pos(); + } else { + win_new_height(wp, wp->w_height - 1); + } +} + +static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) { frame_T *fp; win_T *wp; if (fr->fr_layout == FR_LEAF) { wp = fr->fr_win; - if (wp->w_status_height != 0 && !statusline) { - // remove status line - win_new_height(wp, wp->w_height + 1); + bool is_last = is_bottom_win(wp); + + if (is_last) { + if (wp->w_status_height != 0 && (!statusline || is_stl_global)) { + // Remove status line + wp->w_status_height = 0; + win_new_height(wp, wp->w_height + STATUS_HEIGHT); + comp_col(); + } else if (wp->w_status_height == 0 && !is_stl_global && statusline) { + // Add statusline to window if needed + wp->w_status_height = STATUS_HEIGHT; + resize_frame_for_status(fr); + comp_col(); + } + } else if (wp->w_status_height != 0 && is_stl_global) { + // If statusline is global and the window has a statusline, replace it with a horizontal + // separator wp->w_status_height = 0; + wp->w_hsep_height = 1; comp_col(); - } else if (wp->w_status_height == 0 && statusline) { - // Find a frame to take a line from. - fp = fr; - while (fp->fr_height <= frame_minheight(fp, NULL)) { - if (fp == topframe) { - emsg(_(e_noroom)); - return; - } - // In a column of frames: go to frame above. If already at - // the top or in a row of frames: go to parent. - if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) { - fp = fp->fr_prev; - } else { - fp = fp->fr_parent; - } - } - wp->w_status_height = 1; - if (fp != fr) { - frame_new_height(fp, fp->fr_height - 1, false, false); - frame_fix_height(wp); - (void)win_comp_pos(); - } else { - win_new_height(wp, wp->w_height - 1); - } + } else if (wp->w_status_height == 0 && !is_stl_global) { + // If statusline isn't global and the window doesn't have a statusline, re-add it + wp->w_status_height = STATUS_HEIGHT; + wp->w_hsep_height = 0; comp_col(); - redraw_all_later(SOME_VALID); } - } else if (fr->fr_layout == FR_ROW) { - // vertically split windows, set status line for each one + redraw_all_later(SOME_VALID); + } else if (fr->fr_layout == FR_COL) { + // For a column frame, recursively call this function for all child frames FOR_ALL_FRAMES(fp, fr->fr_child) { - last_status_rec(fp, statusline); + last_status_rec(fp, statusline, is_stl_global); } } else { - // horizontally split window, set status line for last one - for (fp = fr->fr_child; fp->fr_next != NULL; fp = fp->fr_next) { + // For a row frame, recursively call this function for all child frames + FOR_ALL_FRAMES(fp, fr->fr_child) { + last_status_rec(fp, statusline, is_stl_global); } - last_status_rec(fp, statusline); } } -/* - * Return the number of lines used by the tab page line. - */ +/// Return the number of lines used by the tab page line. int tabline_height(void) { if (ui_has(kUITabline)) { @@ -6325,10 +6625,14 @@ int tabline_height(void) return 1; } -/* - * Return the minimal number of rows that is needed on the screen to display - * the current number of windows. - */ +/// Return the number of lines used by the global statusline +int global_stl_height(void) +{ + return (p_ls == 3) ? STATUS_HEIGHT : 0; +} + +/// Return the minimal number of rows that is needed on the screen to display +/// the current number of windows. int min_rows(void) { if (firstwin == NULL) { // not initialized yet @@ -6342,7 +6646,7 @@ int min_rows(void) total = n; } } - total += tabline_height(); + total += tabline_height() + global_stl_height(); total += 1; // count the room for the command line return total; } @@ -6569,20 +6873,27 @@ static win_T *get_snapshot_focus(int idx) /// triggered, another tabpage access is limited. /// /// @return FAIL if switching to "win" failed. -int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp, - bool no_display) +int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { block_autocmds(); - return switch_win_noblock(save_curwin, save_curtab, win, tp, no_display); + return switch_win_noblock(switchwin, win, tp, no_display); } // As switch_win() but without blocking autocommands. -int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp, - bool no_display) +int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { - *save_curwin = curwin; + memset(switchwin, 0, sizeof(switchwin_T)); + switchwin->sw_curwin = curwin; + if (win == curwin) { + switchwin->sw_same_win = true; + } else { + // Disable Visual selection, because redrawing may fail. + switchwin->sw_visual_active = VIsual_active; + VIsual_active = false; + } + if (tp != NULL) { - *save_curtab = curtab; + switchwin->sw_curtab = curtab; if (no_display) { curtab->tp_firstwin = firstwin; curtab->tp_lastwin = lastwin; @@ -6604,33 +6915,35 @@ int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, // Restore current tabpage and window saved by switch_win(), if still valid. // When "no_display" is true the display won't be affected, no redraw is // triggered. -void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) +void restore_win(switchwin_T *switchwin, bool no_display) { - restore_win_noblock(save_curwin, save_curtab, no_display); + restore_win_noblock(switchwin, no_display); unblock_autocmds(); } // As restore_win() but without unblocking autocommands. -void restore_win_noblock(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) +void restore_win_noblock(switchwin_T *switchwin, bool no_display) { - if (save_curtab != NULL && valid_tabpage(save_curtab)) { + if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) { if (no_display) { curtab->tp_firstwin = firstwin; curtab->tp_lastwin = lastwin; - curtab = save_curtab; + curtab = switchwin->sw_curtab; firstwin = curtab->tp_firstwin; lastwin = curtab->tp_lastwin; } else { - goto_tabpage_tp(save_curtab, false, false); + goto_tabpage_tp(switchwin->sw_curtab, false, false); } } - if (win_valid(save_curwin)) { - curwin = save_curwin; + + if (!switchwin->sw_same_win) { + VIsual_active = switchwin->sw_visual_active; + } + + if (win_valid(switchwin->sw_curwin)) { + curwin = switchwin->sw_curwin; curbuf = curwin->w_buffer; } - // If called by win_execute() and executing the command changed the - // directory, it now has to be restored. - fix_current_dir(); } /// Make "buf" the current buffer. @@ -6660,286 +6973,6 @@ void restore_buffer(bufref_T *save_curbuf) } } - -/// Add match to the match list of window 'wp'. The pattern 'pat' will be -/// highlighted with the group 'grp' with priority 'prio'. -/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1). -/// -/// @param[in] id a desired ID 'id' can be specified -/// (greater than or equal to 1). -1 must be specified if no -/// particular ID is desired -/// @param[in] conceal_char pointer to conceal replacement char -/// @return ID of added match, -1 on failure. -int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id, - list_T *pos_list, const char *const conceal_char) - FUNC_ATTR_NONNULL_ARG(1, 2) -{ - matchitem_T *cur; - matchitem_T *prev; - matchitem_T *m; - int hlg_id; - regprog_T *regprog = NULL; - int rtype = SOME_VALID; - - if (*grp == NUL || (pat != NULL && *pat == NUL)) { - return -1; - } - if (id < -1 || id == 0) { - semsg(_("E799: Invalid ID: %" PRId64 - " (must be greater than or equal to 1)"), - (int64_t)id); - return -1; - } - if (id != -1) { - cur = wp->w_match_head; - while (cur != NULL) { - if (cur->id == id) { - semsg(_("E801: ID already taken: %" PRId64), (int64_t)id); - return -1; - } - cur = cur->next; - } - } - if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) { - return -1; - } - if (pat != NULL && (regprog = vim_regcomp((char_u *)pat, RE_MAGIC)) == NULL) { - semsg(_(e_invarg2), pat); - return -1; - } - - // Find available match ID. - while (id == -1) { - cur = wp->w_match_head; - while (cur != NULL && cur->id != wp->w_next_match_id) { - cur = cur->next; - } - if (cur == NULL) { - id = wp->w_next_match_id; - } - wp->w_next_match_id++; - } - - // Build new match. - m = xcalloc(1, sizeof(matchitem_T)); - m->id = id; - m->priority = prio; - m->pattern = pat == NULL ? NULL: (char_u *)xstrdup(pat); - m->hlg_id = hlg_id; - m->match.regprog = regprog; - m->match.rmm_ic = FALSE; - m->match.rmm_maxcol = 0; - m->conceal_char = 0; - if (conceal_char != NULL) { - m->conceal_char = utf_ptr2char((const char_u *)conceal_char); - } - - // Set up position matches - if (pos_list != NULL) { - linenr_T toplnum = 0; - linenr_T botlnum = 0; - - int i = 0; - TV_LIST_ITER(pos_list, li, { - linenr_T lnum = 0; - colnr_T col = 0; - int len = 1; - bool error = false; - - if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { - const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; - const listitem_T *subli = tv_list_first(subl); - if (subli == NULL) { - semsg(_("E5030: Empty list at position %d"), - (int)tv_list_idx_of_item(pos_list, li)); - goto fail; - } - lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); - if (error) { - goto fail; - } - if (lnum <= 0) { - continue; - } - m->pos.pos[i].lnum = lnum; - subli = TV_LIST_ITEM_NEXT(subl, subli); - if (subli != NULL) { - col = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); - if (error) { - goto fail; - } - if (col < 0) { - continue; - } - subli = TV_LIST_ITEM_NEXT(subl, subli); - if (subli != NULL) { - len = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); - if (len < 0) { - continue; - } - if (error) { - goto fail; - } - } - } - m->pos.pos[i].col = col; - m->pos.pos[i].len = len; - } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { - if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) { - continue; - } - m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; - m->pos.pos[i].col = 0; - m->pos.pos[i].len = 0; - } else { - semsg(_("E5031: List or number required at position %d"), - (int)tv_list_idx_of_item(pos_list, li)); - goto fail; - } - if (toplnum == 0 || lnum < toplnum) { - toplnum = lnum; - } - if (botlnum == 0 || lnum >= botlnum) { - botlnum = lnum + 1; - } - i++; - if (i >= MAXPOSMATCH) { - break; - } - }); - - // Calculate top and bottom lines for redrawing area - if (toplnum != 0) { - if (wp->w_buffer->b_mod_set) { - if (wp->w_buffer->b_mod_top > toplnum) { - wp->w_buffer->b_mod_top = toplnum; - } - if (wp->w_buffer->b_mod_bot < botlnum) { - wp->w_buffer->b_mod_bot = botlnum; - } - } else { - wp->w_buffer->b_mod_set = true; - wp->w_buffer->b_mod_top = toplnum; - wp->w_buffer->b_mod_bot = botlnum; - wp->w_buffer->b_mod_xlines = 0; - } - m->pos.toplnum = toplnum; - m->pos.botlnum = botlnum; - rtype = VALID; - } - } - - // Insert new match. The match list is in ascending order with regard to - // the match priorities. - cur = wp->w_match_head; - prev = cur; - while (cur != NULL && prio >= cur->priority) { - prev = cur; - cur = cur->next; - } - if (cur == prev) { - wp->w_match_head = m; - } else { - prev->next = m; - } - m->next = cur; - - redraw_later(wp, rtype); - return id; - -fail: - xfree(m); - return -1; -} - - -/// Delete match with ID 'id' in the match list of window 'wp'. -/// -/// @param perr print error messages if true. -int match_delete(win_T *wp, int id, bool perr) -{ - matchitem_T *cur = wp->w_match_head; - matchitem_T *prev = cur; - int rtype = SOME_VALID; - - if (id < 1) { - if (perr) { - semsg(_("E802: Invalid ID: %" PRId64 - " (must be greater than or equal to 1)"), - (int64_t)id); - } - return -1; - } - while (cur != NULL && cur->id != id) { - prev = cur; - cur = cur->next; - } - if (cur == NULL) { - if (perr) { - semsg(_("E803: ID not found: %" PRId64), (int64_t)id); - } - return -1; - } - if (cur == prev) { - wp->w_match_head = cur->next; - } else { - prev->next = cur->next; - } - vim_regfree(cur->match.regprog); - xfree(cur->pattern); - if (cur->pos.toplnum != 0) { - if (wp->w_buffer->b_mod_set) { - if (wp->w_buffer->b_mod_top > cur->pos.toplnum) { - wp->w_buffer->b_mod_top = cur->pos.toplnum; - } - if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) { - wp->w_buffer->b_mod_bot = cur->pos.botlnum; - } - } else { - wp->w_buffer->b_mod_set = true; - wp->w_buffer->b_mod_top = cur->pos.toplnum; - wp->w_buffer->b_mod_bot = cur->pos.botlnum; - wp->w_buffer->b_mod_xlines = 0; - } - rtype = VALID; - } - xfree(cur); - redraw_later(wp, rtype); - return 0; -} - -/* - * Delete all matches in the match list of window 'wp'. - */ -void clear_matches(win_T *wp) -{ - matchitem_T *m; - - while (wp->w_match_head != NULL) { - m = wp->w_match_head->next; - vim_regfree(wp->w_match_head->match.regprog); - xfree(wp->w_match_head->pattern); - xfree(wp->w_match_head); - wp->w_match_head = m; - } - redraw_later(wp, SOME_VALID); -} - -/* - * Get match from ID 'id' in window 'wp'. - * Return NULL if match not found. - */ -matchitem_T *get_match(win_T *wp, int id) -{ - matchitem_T *cur = wp->w_match_head; - - while (cur != NULL && cur->id != id) { - cur = cur->next; - } - return cur; -} - - /// Check that "topfrp" and its children are at the right height. /// /// @param topfrp top frame pointer @@ -7065,16 +7098,14 @@ void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) tv_list_append_number(list, winnr); } -win_T *win_id2wp(typval_T *argvars) +win_T *win_id2wp(int id) { - return win_id2wp_tp(argvars, NULL); + return win_id2wp_tp(id, NULL); } // Return the window and tab pointer of window "id". -win_T *win_id2wp_tp(typval_T *argvars, tabpage_T **tpp) +win_T *win_id2wp_tp(int id, tabpage_T **tpp) { - int id = tv_get_number(&argvars[0]); - FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->handle == id) { if (tpp != NULL) { diff --git a/src/nvim/window.h b/src/nvim/window.h index 7e465a9f08..b75b8abd9b 100644 --- a/src/nvim/window.h +++ b/src/nvim/window.h @@ -4,19 +4,19 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" +#include "nvim/mark.h" +#include "nvim/os/os.h" // Values for file_name_in_line() #define FNAME_MESS 1 // give error message #define FNAME_EXP 2 // expand to path #define FNAME_HYP 4 // check for hypertext link #define FNAME_INCL 8 // apply 'includeexpr' -#define FNAME_REL 16 /* ".." and "./" are relative to the (current) - file instead of the current directory */ +#define FNAME_REL 16 // ".." and "./" are relative to the (current) + // file instead of the current directory #define FNAME_UNESC 32 // remove backslashes used for escaping -/* - * arguments for win_split() - */ +// arguments for win_split() #define WSP_ROOM 1 // require enough room #define WSP_VERT 2 // split vertically #define WSP_TOP 4 // window at top-left of shell @@ -26,12 +26,66 @@ #define WSP_ABOVE 64 // put new window above/left #define WSP_NEWLOC 128 // don't copy location list -/* - * Minimum screen size - */ +// Minimum screen size #define MIN_COLUMNS 12 // minimal columns for screen #define MIN_LINES 2 // minimal lines for screen +/// Structure used by switch_win() to pass values to restore_win() +typedef struct { + win_T *sw_curwin; + tabpage_T *sw_curtab; + bool sw_same_win; ///< VIsual_active was not reset + bool sw_visual_active; +} switchwin_T; + +/// Execute a block of code in the context of window `wp` in tabpage `tp`. +/// Ensures the status line is redrawn and cursor position is valid if it is moved. +#define WIN_EXECUTE(wp, tp, block) \ + do { \ + win_T *const wp_ = (wp); \ + const pos_T curpos_ = wp_->w_cursor; \ + char_u cwd_[MAXPATHL]; \ + char_u autocwd_[MAXPATHL]; \ + bool apply_acd_ = false; \ + int cwd_status_ = FAIL; \ + /* Getting and setting directory can be slow on some systems, only do */ \ + /* this when the current or target window/tab have a local directory or */ \ + /* 'acd' is set. */ \ + if (curwin != wp \ + && (curwin->w_localdir != NULL || wp->w_localdir != NULL \ + || (curtab != tp && (curtab->tp_localdir != NULL || tp->tp_localdir != NULL)) \ + || p_acd)) { \ + cwd_status_ = os_dirname(cwd_, MAXPATHL); \ + } \ + /* If 'acd' is set, check we are using that directory. If yes, then */ \ + /* apply 'acd' afterwards, otherwise restore the current directory. */ \ + if (cwd_status_ == OK && p_acd) { \ + do_autochdir(); \ + apply_acd_ = os_dirname(autocwd_, MAXPATHL) == OK && STRCMP(cwd_, autocwd_) == 0; \ + } \ + switchwin_T switchwin_; \ + if (switch_win_noblock(&switchwin_, wp_, (tp), true) == OK) { \ + check_cursor(); \ + block; \ + } \ + restore_win_noblock(&switchwin_, true); \ + if (apply_acd_) { \ + do_autochdir(); \ + } else if (cwd_status_ == OK) { \ + os_chdir((char *)cwd_); \ + } \ + /* Update the status line if the cursor moved. */ \ + if (win_valid(wp_) && !equalpos(curpos_, wp_->w_cursor)) { \ + wp_->w_redr_status = true; \ + } \ + /* In case the command moved the cursor or changed the Visual area, */ \ + /* check it is valid. */ \ + check_cursor(); \ + if (VIsual_active) { \ + check_pos(curbuf, &VIsual); \ + } \ + } while (false) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "window.h.generated.h" #endif diff --git a/src/uncrustify.cfg b/src/uncrustify.cfg index 558fa1759f..49ce394dc9 100644 --- a/src/uncrustify.cfg +++ b/src/uncrustify.cfg @@ -1,4 +1,4 @@ -# Uncrustify-0.73.0-199-0dfafb27 +# Uncrustify-0.74.0 # # General options @@ -214,6 +214,10 @@ sp_after_ptr_star_func = remove # ignore/add/remove/force/not_defined # function prototype or function definition. sp_after_ptr_star_trailing = ignore # ignore/add/remove/force/not_defined +# Add or remove space between the pointer star '*' and the name of the variable +# in a function pointer definition. +sp_ptr_star_func_var = ignore # ignore/add/remove/force/not_defined + # Add or remove space after a pointer star '*', if followed by an open # parenthesis, as in 'void* (*)()'. sp_ptr_star_paren = ignore # ignore/add/remove/force/not_defined @@ -311,19 +315,33 @@ sp_permit_cpp11_shift = false # true/false # 'while', etc.). sp_before_sparen = force # ignore/add/remove/force/not_defined -# Add or remove space inside '(' and ')' of control statements. +# Add or remove space inside '(' and ')' of control statements other than +# 'for'. sp_inside_sparen = remove # ignore/add/remove/force/not_defined -# Add or remove space after '(' of control statements. +# Add or remove space after '(' of control statements other than 'for'. # # Overrides sp_inside_sparen. sp_inside_sparen_open = remove # ignore/add/remove/force/not_defined -# Add or remove space before ')' of control statements. +# Add or remove space before ')' of control statements other than 'for'. # # Overrides sp_inside_sparen. sp_inside_sparen_close = ignore # ignore/add/remove/force/not_defined +# Add or remove space inside '(' and ')' of 'for' statements. +sp_inside_for = ignore # ignore/add/remove/force/not_defined + +# Add or remove space after '(' of 'for' statements. +# +# Overrides sp_inside_for. +sp_inside_for_open = ignore # ignore/add/remove/force/not_defined + +# Add or remove space before ')' of 'for' statements. +# +# Overrides sp_inside_for. +sp_inside_for_close = ignore # ignore/add/remove/force/not_defined + # Add or remove space between '((' or '))' of control statements. sp_sparen_paren = ignore # ignore/add/remove/force/not_defined @@ -648,6 +666,11 @@ sp_func_class_paren = ignore # ignore/add/remove/force/not_defined # and '()'. sp_func_class_paren_empty = ignore # ignore/add/remove/force/not_defined +# Add or remove space after 'return'. +# +# Default: force +sp_return = force # ignore/add/remove/force/not_defined + # Add or remove space between 'return' and '('. sp_return_paren = force # ignore/add/remove/force/not_defined @@ -971,11 +994,31 @@ sp_inside_newop_paren_open = ignore # ignore/add/remove/force/not_defined # Overrides sp_inside_newop_paren. sp_inside_newop_paren_close = ignore # ignore/add/remove/force/not_defined -# Add or remove space before a trailing or embedded comment. -sp_before_tr_emb_cmt = add # ignore/add/remove/force/not_defined +# Add or remove space before a trailing comment. +sp_before_tr_cmt = add # ignore/add/remove/force/not_defined + +# Number of spaces before a trailing comment. +sp_num_before_tr_cmt = 2 # unsigned number + +# Add or remove space before an embedded comment. +# +# Default: force +sp_before_emb_cmt = force # ignore/add/remove/force/not_defined + +# Number of spaces before an embedded comment. +# +# Default: 1 +sp_num_before_emb_cmt = 1 # unsigned number + +# Add or remove space after an embedded comment. +# +# Default: force +sp_after_emb_cmt = force # ignore/add/remove/force/not_defined -# Number of spaces before a trailing or embedded comment. -sp_num_before_tr_emb_cmt = 2 # unsigned number +# Number of spaces after an embedded comment. +# +# Default: 1 +sp_num_after_emb_cmt = 1 # unsigned number # (Java) Add or remove space between an annotation and the open parenthesis. sp_annotation_paren = ignore # ignore/add/remove/force/not_defined @@ -1216,12 +1259,16 @@ indent_sparen_extra = 0 # number indent_relative_single_line_comments = true # true/false # Spaces to indent 'case' from 'switch'. Usually 0 or indent_columns. -# It might wise to choose the same value for the option indent_case_brace. +# It might be wise to choose the same value for the option indent_case_brace. indent_switch_case = 0 # unsigned number +# Spaces to indent the body of a 'switch' before any 'case'. +# Usually the same as indent_columns or indent_switch_case. +indent_switch_body = 0 # unsigned number + # Spaces to indent '{' from 'case'. By default, the brace will appear under # the 'c' in case. Usually set to 0 or indent_columns. Negative values are OK. -# It might wise to choose the same value for the option indent_switch_case. +# It might be wise to choose the same value for the option indent_switch_case. indent_case_brace = 0 # number # indent 'break' with 'case' from 'switch'. @@ -1236,13 +1283,31 @@ indent_switch_pp = true # true/false # Usually 0. indent_case_shift = 0 # unsigned number +# Whether to align comments before 'case' with the 'case'. +# +# Default: true +indent_case_comment = true # true/false + +# Whether to indent comments not found in first column. +# +# Default: true +indent_comment = true # true/false + # Whether to indent comments found in first column. indent_col1_comment = false # true/false # Whether to indent multi string literal in first column. indent_col1_multi_string_literal = false # true/false -# How to indent goto labels. +# Align comments on adjacent lines that are this many columns apart or less. +# +# Default: 3 +indent_comment_align_thresh = 3 # unsigned number + +# Whether to ignore indent for goto labels. +indent_ignore_label = false # true/false + +# How to indent goto labels. Requires indent_ignore_label=false. # # >0: Absolute column where 1 is the leftmost column # <=0: Subtract from brace indent @@ -1414,7 +1479,7 @@ indent_using_block = true # true/false # 0: Off (default) # 1: When the `if_false` is a continuation, indent it under `if_false` # 2: When the `:` is a continuation, indent it under `?` -indent_ternary_operator = 2 # unsigned number +indent_ternary_operator = 0 # unsigned number # Whether to indent the statements inside ternary operator. indent_inside_ternary_operator = false # true/false @@ -2622,6 +2687,22 @@ align_right_cmt_at_col = 0 # unsigned number # 0: Don't align (default). align_func_proto_span = 0 # unsigned number +# How to consider (or treat) the '*' in the alignment of function prototypes. +# +# 0: Part of the type 'void * foo();' (default) +# 1: Part of the function 'void *foo();' +# 2: Dangling 'void *foo();' +# Dangling: the '*' will not be taken into account when aligning. +align_func_proto_star_style = 0 # unsigned number + +# How to consider (or treat) the '&' in the alignment of function prototypes. +# +# 0: Part of the type 'long & foo();' (default) +# 1: Part of the function 'long &foo();' +# 2: Dangling 'long &foo();' +# Dangling: the '&' will not be taken into account when aligning. +align_func_proto_amp_style = 0 # unsigned number + # The threshold for aligning function prototypes. # Use a negative number for absolute thresholds. # @@ -3101,6 +3182,9 @@ pp_indent_in_guard = false # true/false # indented from column 1. pp_define_at_level = false # true/false +# Whether to indent '#include' at the brace level. +pp_include_at_level = false # true/false + # Whether to ignore the '#define' body while formatting. pp_ignore_define_body = false # true/false @@ -3307,5 +3391,5 @@ set QUESTION REAL_FATTR_CONST set QUESTION REAL_FATTR_NONNULL_ALL set QUESTION REAL_FATTR_PURE set QUESTION REAL_FATTR_WARN_UNUSED_RESULT -# option(s) with 'not default' value: 87 +# option(s) with 'not default' value: 86 # |