diff options
Diffstat (limited to 'src')
53 files changed, 1108 insertions, 938 deletions
diff --git a/src/cjson/lua_cjson.c b/src/cjson/lua_cjson.c index b5f97bc485..c243f93c05 100644 --- a/src/cjson/lua_cjson.c +++ b/src/cjson/lua_cjson.c @@ -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 */ diff --git a/src/clint.py b/src/clint.py index 4b7bf002e6..37b44920af 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): diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 21150965f9..9f29cae29d 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -57,13 +57,13 @@ 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_LOAD_PACKAGE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_load_package.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) @@ -332,9 +332,9 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env "LUAC_PRG=${LUAC_PRG}" ${LUA_PRG} ${CHAR_BLOB_GENERATOR} -c ${VIM_MODULE_FILE} - ${LUA_LOAD_PACKAGE_MODULE_SOURCE} "vim._load_package" + ${LUA_INIT_PACKAGES_MODULE_SOURCE} "vim._init_packages" ${LUA_INSPECT_MODULE_SOURCE} "vim.inspect" - ${LUA_VIM_MODULE_SOURCE} "vim" + ${LUA_EDITOR_MODULE_SOURCE} "vim._editor" ${LUA_SHARED_MODULE_SOURCE} "vim.shared" ${LUA_F_MODULE_SOURCE} "vim.F" ${LUA_META_MODULE_SOURCE} "vim._meta" @@ -342,7 +342,8 @@ add_custom_command( ${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} diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 0ad7b320d0..5ede0e5265 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -40,8 +40,10 @@ static int64_t next_autocmd_id = 1; /// /// @param opts Optional Parameters: /// - event : Name or list of name of events to match against -/// - group (string): Name of group to match against -/// - pattern: Pattern or list of patterns to match against +/// - group (string|int): Name or id of group to match against +/// - pattern: Pattern or list of patterns to match against. Cannot be used with {buffer} +/// - buffer: Buffer number or list of buffer numbers for buffer local autocommands +/// |autocmd-buflocal|. Cannot be used with {pattern} /// /// @return A list of autocmds that match Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) @@ -53,24 +55,34 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) 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; - if (opts->group.type != kObjectTypeNil) { - Object v = opts->group; - if (v.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "group must be a string."); - goto cleanup; - } - - group = augroup_find(v.data.string.data); - - if (group < 0) { - api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + 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) { @@ -100,6 +112,12 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) } } + 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; @@ -107,25 +125,70 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) 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, + 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; + } - if (pattern_filter_count >= AUCMD_MAX_PATTERNS) { + 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 patterns. Please limit yourself to less"); + "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; @@ -234,6 +297,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) } cleanup: + api_free_array(buffers); return autocmd_list; } @@ -265,7 +329,7 @@ cleanup: /// - buffer: (bufnr) /// - create a |autocmd-buflocal| autocmd. /// - NOTE: Cannot be used with {pattern} -/// - group: (string) The augroup name +/// - group: (string|int) The augroup name or id /// - once: (boolean) - See |autocmd-once| /// - nested: (boolean) - See |autocmd-nested| /// - desc: (string) - Description of the autocmd @@ -350,23 +414,29 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc bool is_once = api_object_to_bool(opts->once, "once", false, err); bool is_nested = api_object_to_bool(opts->nested, "nested", false, err); - // TODO(tjdevries): accept number for namespace instead - if (opts->group.type != kObjectTypeNil) { - Object *v = &opts->group; - if (v->type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "'group' must be a string"); - goto cleanup; - } - - au_group = augroup_find(v->data.string.data); - - if (au_group == AUGROUP_ERROR) { - api_set_error(err, - kErrorTypeException, - "invalid augroup: %s", v->data.string.data); - + 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->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { @@ -568,7 +638,7 @@ void nvim_del_augroup_by_name(String name) /// - NOTE: Cannot be used with {pattern} /// - pattern (string|table) - optional, defaults to "*". /// - NOTE: Cannot be used with {buffer} -/// - group (string) - autocmd group name +/// - group (string|int) - autocmd group name or id /// - modeline (boolean) - Default true, see |<nomodeline>| void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) FUNC_API_SINCE(9) @@ -588,21 +658,29 @@ void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) goto cleanup; } - if (opts->group.type != kObjectTypeNil) { - if (opts->group.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "'group' must be a string"); - goto cleanup; - } - - au_group = augroup_find(opts->group.data.string.data); - - if (au_group == AUGROUP_ERROR) { - api_set_error(err, - kErrorTypeException, - "invalid augroup: %s", opts->group.data.string.data); - + 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) { diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 3a968f07ab..0ee8134ec4 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -445,6 +445,27 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - 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 /// /// @param[out] err Error details, if any /// @return Id of the created/updated extmark @@ -519,10 +540,25 @@ 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; + } } } @@ -622,6 +658,17 @@ 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); @@ -709,6 +756,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer error: clear_virttext(&decor.virt_text); + xfree(decor.sign_text); return 0; } diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index b05816f8ac..435e8195dd 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -22,6 +22,11 @@ return { "virt_lines_above"; "virt_lines_leftcol"; "strict"; + "sign_text"; + "sign_hl_group"; + "number_hl_group"; + "line_hl_group"; + "cursorline_hl_group"; }; keymap = { "noremap"; @@ -83,7 +88,10 @@ return { "standout"; "strikethrough"; "underline"; + "underlineline"; "undercurl"; + "underdot"; + "underdash"; "italic"; "reverse"; "nocombine"; @@ -105,7 +113,10 @@ return { "standout"; "strikethrough"; "underline"; + "underlineline"; "undercurl"; + "underdot"; + "underdash"; "italic"; "reverse"; "nocombine"; @@ -131,6 +142,7 @@ return { "event"; "group"; "pattern"; + "buffer"; }; create_augroup = { "clear"; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 35e8589255..8056950e26 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1643,3 +1643,39 @@ sctx_T api_set_sctx(uint64_t channel_id) } 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/autocmd.c b/src/nvim/autocmd.c index 7cb493f57d..a850e5c1a0 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -105,15 +105,24 @@ static char_u *old_termresponse = NULL; #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. +// Map of autocmd group names and ids. // name -> ID -static Map(String, int) augroup_map = MAP_INIT; +// 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(char *name) +static void augroup_map_del(int id, char *name) { - String key = map_key(String, int)(&augroup_map, cstr_as_string(name)); - map_del(String, int)(&augroup_map, key); - api_free_string(key); + 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); + } } @@ -382,12 +391,14 @@ int augroup_add(char *name) } if (existing_id == AUGROUP_DELETED) { - augroup_map_del(name); + augroup_map_del(existing_id, name); } int next_id = next_augroup_id++; - String name_copy = cstr_to_string(name); - map_put(String, int)(&augroup_map, name_copy, next_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; } @@ -416,7 +427,8 @@ void augroup_del(char *name, bool stupid_legacy_mode) 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)(&augroup_map, cstr_as_string(name), AUGROUP_DELETED); + map_put(String, int)(&map_augroup_name_to_id, cstr_as_string(name), AUGROUP_DELETED); + augroup_map_del(ap->group, NULL); return; } } @@ -432,7 +444,7 @@ void augroup_del(char *name, bool stupid_legacy_mode) } // Remove the group because it's not currently in use. - augroup_map_del(name); + augroup_map_del(i, name); au_cleanup(); } } @@ -445,7 +457,7 @@ void augroup_del(char *name, bool stupid_legacy_mode) int augroup_find(const char *name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - int existing_id = map_get(String, int)(&augroup_map, cstr_as_string((char *)name)); + 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; } @@ -487,13 +499,10 @@ char *augroup_name(int group) return NULL; } - String key; - int value; - map_foreach(&augroup_map, key, value, { - if (value == group) { - return key.data; - } - }); + 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(); @@ -526,7 +535,7 @@ void do_augroup(char_u *arg, int del_group) String name; int value; - map_foreach(&augroup_map, name, value, { + map_foreach(&map_augroup_name_to_id, name, value, { if (value > 0) { msg_puts(name.data); } else { @@ -556,11 +565,17 @@ void free_all_autocmds(void) // Delete the augroup_map, including free the data String name; int id; - map_foreach(&augroup_map, name, 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(String, int)(&augroup_map); + map_destroy(int, String)(&map_augroup_id_to_name); } #endif diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 9e82b4e80b..2bd14e2103 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -36,6 +36,7 @@ #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/digraph.h" +#include "nvim/decoration.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" @@ -1736,7 +1737,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); } @@ -5467,15 +5468,23 @@ static int buf_signcols_inner(buf_T *buf, int maximum) 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) { @@ -5483,8 +5492,22 @@ static int buf_signcols_inner(buf_T *buf, int maximum) } } + 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; } @@ -5493,28 +5516,96 @@ static int buf_signcols_inner(buf_T *buf, int 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++; + 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; + } +} + 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 (maximum > buf->b_signcols.max) { + buf->b_signcols.valid = false; } - if (!buf->b_signcols_valid) { + 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; - buf->b_signcols_max = maximum; + 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. diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 1e0c837056..7acb646980 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -862,9 +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 - int b_signcols_max; // Maximum value b_signcols is valid for. + 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 @@ -875,6 +878,7 @@ struct file_buffer { MarkTree b_marktree[1]; 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. diff --git a/src/nvim/charset.c b/src/nvim/charset.c index f4882e57e1..97aac67627 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1500,7 +1500,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) diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index 75ebf0084e..b48a3155b6 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -201,7 +201,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++; } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 935b233752..619e8fdadc 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,10 +1,12 @@ // 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/lua/executor.h" +#include "nvim/move.h" #include "nvim/screen.h" #include "nvim/syntax.h" #include "nvim/vim.h" @@ -65,8 +67,10 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) { - if ((!decor || 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 (decor && kv_size(decor->virt_text)) { @@ -82,9 +86,18 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) { decor_redraw(buf, row, row2, decor); - if (decor && kv_size(decor->virt_lines)) { - assert(buf->b_virt_line_blocks > 0); - buf->b_virt_line_blocks--; + 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); } @@ -97,6 +110,7 @@ void decor_free(Decoration *decor) clear_virttext(&kv_A(decor->virt_lines, i).line); } kv_destroy(decor->virt_lines); + xfree(decor->sign_text); xfree(decor); } } @@ -136,6 +150,7 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state) { state->row = -1; state->buf = buf; + state->has_sign_decor = false; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); if (item.virt_text_owned) { @@ -180,9 +195,23 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) 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; + } + + // Don't add signs for end marks as the start mark has already been added. + if (mt_end(mark) && decor_has_sign(&decor)) { + goto next_mark; + } + mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - if ((!mt_end(mark) && 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; @@ -241,6 +270,10 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r kv_A(state->active, index) = kv_A(state->active, index-1); } kv_A(state->active, index) = range; + + if (decor_has_sign(decor)) { + state->has_sign_decor = true; + } } int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *state) @@ -294,7 +327,8 @@ next_mark: bool active = false, keep = true; if (item.end_row < state->row || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && kv_size(item.decor.virt_text))) { + if (!(item.start_row >= state->row && kv_size(item.decor.virt_text)) + && !decor_has_sign(&item.decor)) { keep = false; } } else { @@ -329,6 +363,131 @@ next_mark: return attr; } +void decor_redraw_signs(buf_T *buf, DecorState *state, int row, + int *num_signs, sign_attrs_T sattrs[]) +{ + for (size_t i = 0; i < kv_size(state->active); i++) { + DecorRange item = kv_A(state->active, i); + Decoration *decor = &item.decor; + + if (!decor_has_sign(decor)) { + continue; + } + + if (state->row != item.start_row) { + continue; + } + + 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)++; + } + } +} + +// 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; diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 02472d09e4..7e6dbdf1a7 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -47,13 +47,18 @@ struct Decoration { bool virt_text_hide; bool hl_eol; bool virt_lines_above; - // TODO(bfredl): style, signs, etc + // 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; }; #define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, kHlModeUnknown, \ - false, false, false, DECOR_PRIORITY_BASE, 0, 0 } + false, false, false, DECOR_PRIORITY_BASE, 0, 0, NULL, 0, 0, 0, 0 } typedef struct { int start_row; @@ -75,6 +80,7 @@ typedef struct { int col_until; int current; int eol_col; + bool has_sign_decor; } DecorState; typedef struct { @@ -98,6 +104,15 @@ EXTERN bool provider_active INIT(= false); 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" #endif diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 00ffa7cba1..095e082f61 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -7776,7 +7776,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; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d95b9560c2..8cdb03c341 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4858,7 +4858,6 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval long numval; char_u *stringval; int opt_type; - int c; bool working = (**arg == '+'); // has("+option") int ret = OK; int opt_flags; @@ -4877,7 +4876,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); @@ -7790,8 +7789,7 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co break; case kCallbackLua: - ILOG(" We tryin to call dat dang lua ref "); - nlua_call_ref(callback->data.luaref, "aucmd", args, false, NULL); + nlua_call_ref(callback->data.luaref, NULL, args, false, NULL); return false; break; @@ -9442,7 +9440,7 @@ 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 diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 49dde537c3..f47c9c482b 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -8296,7 +8296,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) // Repeat until {skip} returns false. for (;;) { subpatnum - = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, options, RE_SEARCH, &sia); + = 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)) { @@ -9339,7 +9339,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; @@ -9569,7 +9569,6 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c */ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int regname; bool append = false; MotionType yank_type; long block_len; @@ -9583,13 +9582,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; @@ -9759,7 +9758,7 @@ 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; @@ -11409,14 +11408,22 @@ 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)); diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 5764f9fbd4..471c4092fe 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -516,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) { @@ -1713,7 +1713,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); @@ -1821,7 +1821,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; @@ -1888,7 +1888,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); } diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 8666c7e33a..5c040adc1c 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1698,7 +1698,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; @@ -1796,7 +1796,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; @@ -1852,7 +1852,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); @@ -2188,7 +2188,6 @@ scriptitem_T *get_current_script_id(char_u *fname, sctx_T *ret_sctx) } - /// ":scriptnames" void ex_scriptnames(exarg_T *eap) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d3203bcee8..a140d76858 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -80,7 +80,7 @@ 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"); + = N_("E1237: No such user-defined command in current buffer: %s"); static int quitmore = 0; static bool ex_pressedreturn = false; @@ -2835,7 +2835,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; } } @@ -8160,7 +8160,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 diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 851828afcf..25b6aa7d8a 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -430,7 +430,7 @@ 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; @@ -1602,7 +1602,7 @@ void ex_endtry(exarg_T *eap) { int idx; bool rethrow = false; - int pending = CSTP_NONE; + char pending = CSTP_NONE; void *rettv = NULL; cstack_T *const cstack = eap->cstack; diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 48e57e20e1..2916e3e978 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -67,7 +67,9 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col uint8_t decor_level = kDecorLevelNone; // no decor if (decor) { - if (kv_size(decor->virt_text) || kv_size(decor->virt_lines)) { + if (kv_size(decor->virt_text) + || kv_size(decor->virt_lines) + || decor_has_sign(decor)) { decor_full = true; decor = xmemdup(decor, sizeof *decor); } @@ -142,6 +144,13 @@ revised: 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); } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 965aa8749d..c5a89d3371 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3911,13 +3911,13 @@ static int check_mtime(buf_T *buf, FileInfo *file_info) static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST { - return (long)file_info->stat.st_mtim.tv_nsec != mtime_ns + 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!!! - || (long)file_info->stat.st_mtim.tv_sec - mtime > 1 - || mtime - (long)file_info->stat.st_mtim.tv_sec > 1; + || file_info->stat.st_mtim.tv_sec - mtime > 1 + || mtime - file_info->stat.st_mtim.tv_sec > 1; #else || (long)file_info->stat.st_mtim.tv_sec != mtime; #endif diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 8426cdb98c..299456f688 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1907,9 +1907,6 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // complete match if (keylen >= 0 && keylen <= typebuf.tb_len) { char_u *map_str = NULL; - 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 @@ -1946,9 +1943,9 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // 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 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; @@ -2661,9 +2658,9 @@ int fix_input_buffer(char_u *buf, int len) /// @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, - LuaRef rhs_lua, int cpo_flags, MapArguments *mapargs) +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, LuaRef rhs_lua, int cpo_flags, + MapArguments *mapargs) { char_u *lhs_buf = NULL; char_u *rhs_buf = NULL; @@ -3988,7 +3985,7 @@ 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(mapblock_T *mp, int c) +static char_u *eval_map_expr(mapblock_T *mp, int c) { char_u *res; char_u *p = NULL; @@ -4143,7 +4140,7 @@ int makemap(FILE *fd, buf_T *buf) } 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; } } diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index eb10c65be9..93c8c53e33 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -420,7 +420,10 @@ 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); uint32_t fg_color = prt_get_color(hl_id, modec); 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 e43a56086f..a1fd0d0d66 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -327,7 +327,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 +558,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 +576,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 +743,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)); } @@ -813,7 +826,10 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e 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); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 50a03e0c02..dcfb9358bb 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 diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index e233e0e6d0..6aaff100ca 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -525,24 +525,6 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread) lua_pop(lstate, 3); } -static void nlua_preload_modules(lua_State *lstate) -{ - 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")) { - break; - } - } - - lua_pop(lstate, 2); // [] -} - static int nlua_module_preloader(lua_State *lstate) { size_t i = (size_t)lua_tointeger(lstate, lua_upvalueindex(1)); @@ -561,24 +543,30 @@ static int nlua_module_preloader(lua_State *lstate) return 1; } -static bool nlua_common_package_init(lua_State *lstate) +static bool nlua_init_packages(lua_State *lstate) FUNC_ATTR_NONNULL_ALL { - nlua_preload_modules(lstate); + // 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] - lua_getglobal(lstate, "require"); - lua_pushstring(lstate, "vim._load_package"); - if (nlua_pcall(lstate, 1, 0)) { - nlua_error(lstate, _("E5106: Error while creating _load_package module: %.*s\n")); - return false; + if (nlua_disable_preload && strequal(def.name, "vim.inspect")) { + break; + } } - // TODO(bfredl): ideally all initialization should be done as a single require - // call. + lua_pop(lstate, 2); // [] + lua_getglobal(lstate, "require"); - lua_pushstring(lstate, "vim.shared"); + lua_pushstring(lstate, "vim._init_packages"); if (nlua_pcall(lstate, 1, 0)) { - nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n")); + mch_errmsg(lua_tostring(lstate, -1)); + mch_errmsg("\n"); return false; } @@ -654,14 +642,7 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setglobal(lstate, "vim"); - if (!nlua_common_package_init(lstate)) { - return false; - } - - lua_getglobal(lstate, "require"); - lua_pushstring(lstate, "vim"); - if (nlua_pcall(lstate, 1, 0)) { - nlua_error(lstate, _("E5106: Error while creating vim module: %.*s\n")); + if (!nlua_init_packages(lstate)) { return false; } @@ -732,7 +713,7 @@ static lua_State *nlua_thread_acquire_vm(void) lua_setglobal(lstate, "vim"); - nlua_common_package_init(lstate); + nlua_init_packages(lstate); lua_getglobal(lstate, "package"); lua_getfield(lstate, -1, "loaded"); diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua deleted file mode 100644 index f5f293939b..0000000000 --- a/src/nvim/lua/vim.lua +++ /dev/null @@ -1,666 +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) -assert(vim.inspect) - --- 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 == 'filetype' then - t.filetype = require('vim.filetype') - return t.filetype - 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 == 'keymap' then - t.keymap = require('vim.keymap') - return t.keymap - 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 - - --@private - 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 - - ---- Display a notification to the user. ---- ---- This function can be overridden by plugins to display notifications using a ---- custom provider (such as the system notification provider). By default, ---- writes to |:messages|. ---- ----@param msg string Content of the notification to show to the user. ----@param level number|nil One of the values from |vim.log.levels|. ----@param opts table|nil Optional parameters. Unused by default. -function vim.notify(msg, level, opts) -- luacheck: no unused args - if level == vim.log.levels.ERROR then - vim.api.nvim_err_writeln(msg) - elseif level == vim.log.levels.WARN then - vim.api.nvim_echo({{msg, 'WarningMsg'}}, true, {}) - else - vim.api.nvim_echo({{msg}}, true, {}) - end -end - -do - local notified = {} - - --- Display a notification only one time. - --- - --- Like |vim.notify()|, but subsequent calls with the same message will not - --- display a notification. - --- - ---@param msg string Content of the notification to show to the user. - ---@param level number|nil One of the values from |vim.log.levels|. - ---@param opts table|nil Optional parameters. Unused by default. - function vim.notify_once(msg, level, opts) -- luacheck: no unused args - if not notified[msg] then - vim.notify(msg, level, opts) - notified[msg] = true - end - 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_namespace()| 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 = {} - ---@private - 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 - ----Prints given arguments in human-readable format. ----Example: ----<pre> ---- -- Print highlight group Normal and store it's contents in a variable. ---- local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true)) ----</pre> ----@see |vim.inspect()| ----@return given arguments. -function vim.pretty_print(...) - local objects = {} - for i = 1, select('#', ...) do - local v = select(i, ...) - table.insert(objects, vim.inspect(v)) - end - - print(table.concat(objects, ' ')) - return ... -end - - -require('vim._meta') - -return vim diff --git a/src/nvim/main.c b/src/nvim/main.c index ae25ff63dd..7281809c06 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -823,7 +823,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--; @@ -845,7 +844,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) { diff --git a/src/nvim/map.c b/src/nvim/map.c index 091d653046..4e39eb8c07 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -178,6 +178,7 @@ 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(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index c9c89bf2fd..00f72386a7 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -47,6 +47,7 @@ MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(HlEntry, int) MAP_DECLS(String, handle_T) MAP_DECLS(String, int) +MAP_DECLS(int, String) MAP_DECLS(ColorKey, ColorItem) diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 918db8b76c..d2354dbf6b 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -558,8 +558,8 @@ void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_ { // 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)((uint16_t)rawkey(itr).flags & (uint16_t)~MT_FLAG_DECOR_MASK); - rawkey(itr).flags = (uint16_t)((uint16_t)rawkey(itr).flags + 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; diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index 30f5aacebc..440816930b 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -87,6 +87,11 @@ 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; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 004ef36b36..59f57aa667 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -303,7 +303,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; @@ -3709,7 +3709,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; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index b5c7020dee..23db7fe5a3 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1023,6 +1023,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 @@ -1111,7 +1165,21 @@ int do_execreg(int regname, int colon, int addcr, int silent) return FAIL; } } - escaped = vim_strsave_escape_ks(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) { @@ -1504,20 +1572,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); @@ -2579,12 +2647,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 @@ -2598,11 +2666,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; } @@ -6630,7 +6695,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else { curwin->w_p_lbr = lbr_saved; oap->excl_tr_ws = cap->cmdchar == 'z'; - (void)op_yank(oap, !gui_yank, false); + (void)op_yank(oap, !gui_yank); } check_cursor_col(); break; diff --git a/src/nvim/option.c b/src/nvim/option.c index 9068c90dd1..a0706dfa14 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3851,7 +3851,7 @@ static bool parse_winhl_opt(win_T *wp) 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; @@ -5267,7 +5267,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; } @@ -5666,11 +5666,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); + 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); + ru_col = Columns - ru_col; if (sc_col <= 0) { // screen too narrow, will become a mess sc_col = 1; } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 6a6c915094..b7f11b2de0 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -468,6 +468,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(_( \ @@ -1762,13 +1764,9 @@ static char_u *regpiece(int *flagp) if (re_multi_type(peekchr()) != NOT_MULTI) { // Can't have a multi follow a multi. if (peekchr() == Magic('*')) { - snprintf((char *)IObuff, IOSIZE, _("E61: Nested %s*"), - reg_magic >= MAGIC_ON ? "" : "\\"); - } else { - snprintf((char *)IObuff, IOSIZE, _("E62: Nested %s%c"), - reg_magic == MAGIC_ALL ? "" : "\\", no_Magic(peekchr())); + EMSG2_RET_NULL(_("E61: Nested %s*"), reg_magic >= MAGIC_ON); } - EMSG_RET_NULL((char *)IObuff); + EMSG3_RET_NULL(_("E62: Nested %s%c"), reg_magic == MAGIC_ALL, no_Magic(peekchr())); } return ret; @@ -1927,10 +1925,8 @@ static char_u *regatom(int *flagp) case Magic('{'): case Magic('*'): c = no_Magic(c); - snprintf((char *)IObuff, IOSIZE, _("E64: %s%c follows nothing"), - (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL) - ? "" : "\\", c); - EMSG_RET_NULL((char *)IObuff); + EMSG3_RET_NULL(_("E64: %s%c follows nothing"), + (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL), c); // NOTREACHED case Magic('~'): /* previous substitute pattern */ @@ -2537,7 +2533,9 @@ do_multibyte: static bool re_mult_next(char *what) { if (re_multi_type(peekchr()) == MULTI_MULT) { - EMSG2_RET_FAIL(_("E888: (NFA regexp) cannot repeat %s"), what); + semsg(_("E888: (NFA regexp) cannot repeat %s"), what); + rc_did_emsg = true; + return false; } return true; } @@ -3149,9 +3147,7 @@ static int read_limits(long *minval, long *maxval) regparse++; // Allow either \{...} or \{...\} } if (*regparse != '}') { - snprintf((char *)IObuff, IOSIZE, _("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); } /* @@ -5043,8 +5039,7 @@ static bool regmatch( } 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) { + && (behind_pos.rs_u.ptr - rp->rs_un.regsave.rs_u.ptr) > (ptrdiff_t)limit) { no = FAIL; } } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 7fafe3dd6e..1cdee7c972 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2208,6 +2208,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc buf_T *buf = wp->w_buffer; bool end_fill = (lnum == buf->b_ml.ml_line_count+1); + has_decor = decor_redraw_line(buf, lnum-1, &decor_state); + if (!number_only) { // To speed up the loop below, set extra_check when there is linebreak, // trailing white space and/or syntax processing to be done. @@ -2229,9 +2231,6 @@ 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); - for (size_t k = 0; k < kv_size(*providers); k++) { DecorProvider *p = kv_A(*providers, k); if (p && p->redraw_line != LUA_NOREF) { @@ -2460,6 +2459,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc memset(sattrs, 0, sizeof(sattrs)); num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs); + if (decor_state.has_sign_decor) { + decor_redraw_signs(buf, &decor_state, 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! @@ -2888,9 +2890,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } 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) { + && (row == startrow + filler_lines + || (row > startrow + filler_lines + && wp->w_p_culopt_flags & CULOPT_LINE))) { // When 'cursorline' is set highlight the line number of // the current line differently. // When 'cursorlineopt' has "screenline" only highlight diff --git a/src/nvim/search.c b/src/nvim/search.c index 682fa417a9..e6b47e75b2 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1071,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(); diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 8b41781c98..6d0ac30003 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -196,7 +196,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 @@ -506,6 +507,8 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) 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; @@ -539,7 +542,6 @@ linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) 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) { @@ -552,6 +554,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); } @@ -673,7 +676,7 @@ 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. @@ -735,14 +738,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; diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index c734502878..6d28c1849a 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -40,6 +40,7 @@ typedef struct sign_attrs_S { 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 1296d410f6..fb644daa39 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3824,7 +3824,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 (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); diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 291138ef23..9d4c64e4b1 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -354,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); diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index db10b71d38..54fce3d968 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -121,11 +121,11 @@ 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" }; +{ "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_UNDERCURL, HL_ITALIC, HL_INVERSE, - HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 }; +{ 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 }; static char e_illegal_arg[] = N_("E390: Illegal argument: %s"); @@ -4208,7 +4208,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; } } diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 53ed4617f7..4229095f9f 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -26,6 +26,54 @@ 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() @@ -42,20 +90,27 @@ func Test_verbose_pwd() set acd wincmd w call assert_match('\[autochdir\].*testdir$', execute('verbose pwd')) - execute 'lcd' cwd - call assert_match('\[window\].*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('\[global\].*testdir', execute('verbose pwd')) + 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')) diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 482d39056f..10eb979b45 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -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 diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 23e39eba35..f78b748d71 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -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! @@ -482,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 diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 58061f020d..1ad82b7290 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -528,7 +528,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); } } @@ -552,15 +552,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); @@ -599,11 +611,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 @@ -652,13 +677,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); } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 31b9614c34..7c67c058b0 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -639,8 +639,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/window.c b/src/nvim/window.c index 83048d911f..d5299202b0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6763,9 +6763,6 @@ void restore_win_noblock(switchwin_T *switchwin, bool no_display) 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. diff --git a/src/nvim/window.h b/src/nvim/window.h index e2fd2c515d..42701f72b4 100644 --- a/src/nvim/window.h +++ b/src/nvim/window.h @@ -5,6 +5,7 @@ #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 @@ -47,12 +48,36 @@ typedef struct { 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; \ |