diff options
Diffstat (limited to 'src')
36 files changed, 748 insertions, 276 deletions
diff --git a/src/clint.py b/src/clint.py index 10f33d22e2..75aaba176d 100755 --- a/src/clint.py +++ b/src/clint.py @@ -3187,8 +3187,8 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, r'|li_(?:next|prev|tv))\b', line) if match: error(filename, linenum, 'runtime/deprecated', 4, - 'Accessing list_T internals directly is prohibited ' - '(hint: see commit d46e37cb4c71)') + 'Accessing list_T internals directly is prohibited; ' + 'see https://github.com/neovim/neovim/wiki/List-management-in-Neovim') # Check for suspicious usage of "if" like # } if (a == b) { diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 49f6f98ba4..f896671287 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -392,9 +392,6 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc FUNC_API_SINCE(9) { int64_t autocmd_id = -1; - - const char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; - int au_group = AUGROUP_DEFAULT; char *desc = NULL; Array patterns = ARRAY_DICT_INIT; @@ -404,7 +401,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc Callback cb = CALLBACK_NONE; - if (!unpack_string_or_array(&event_array, &event, "event", err)) { + if (!unpack_string_or_array(&event_array, &event, "event", true, err)) { goto cleanup; } @@ -466,84 +463,13 @@ 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); - 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."); + int au_group = get_augroup_from_object(opts->group, err); + if (au_group == AUGROUP_ERROR) { goto cleanup; } - if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, - "cannot pass both: 'pattern' and 'buffer' for the same autocmd"); + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { goto cleanup; - } else if (opts->pattern.type != kObjectTypeNil) { - Object *v = &opts->pattern; - - if (v->type == kObjectTypeString) { - char_u *pat = (char_u *)v->data.string.data; - size_t patlen = aucmd_pattern_length(pat); - while (patlen) { - ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); - - pat = aucmd_next_pattern(pat, patlen); - patlen = aucmd_pattern_length(pat); - } - } else if (v->type == kObjectTypeArray) { - if (!check_autocmd_string_array(patterns, "pattern", err)) { - goto cleanup; - } - - Array array = v->data.array; - for (size_t i = 0; i < array.size; i++) { - char_u *pat = (char_u *)array.items[i].data.string.data; - size_t patlen = aucmd_pattern_length(pat); - while (patlen) { - ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); - - pat = aucmd_next_pattern(pat, patlen); - patlen = aucmd_pattern_length(pat); - } - } - } else { - api_set_error(err, - kErrorTypeValidation, - "'pattern' must be a string"); - goto cleanup; - } - } else if (opts->buffer.type != kObjectTypeNil) { - if (opts->buffer.type != kObjectTypeInteger) { - api_set_error(err, - kErrorTypeValidation, - "'buffer' must be an integer"); - goto cleanup; - } - - buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err); - if (ERROR_SET(err)) { - goto cleanup; - } - - snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle); - ADD(patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal))); } if (opts->desc.type != kObjectTypeNil) { @@ -609,10 +535,104 @@ cleanup: /// NOTE: Only autocommands created via the API have an id. /// @param id Integer The id returned by nvim_create_autocmd /// @see |nvim_create_autocmd()| -void nvim_del_autocmd(Integer id) +void nvim_del_autocmd(Integer id, Error *err) FUNC_API_SINCE(9) { - autocmd_delete_id(id); + if (id <= 0) { + api_set_error(err, kErrorTypeException, "Invalid autocmd id"); + return; + } + if (!autocmd_delete_id(id)) { + api_set_error(err, kErrorTypeException, "Failed to delete autocmd"); + } +} + +/// Clear all autocommands that match the corresponding {opts}. To delete +/// a particular autocmd, see |nvim_del_autocmd|. +/// @param opts Parameters +/// - event: (string|table) +/// Examples: +/// - event: "pat1" +/// - event: { "pat1" } +/// - event: { "pat1", "pat2", "pat3" } +/// - pattern: (string|table) +/// - pattern or patterns to match exactly. +/// - For example, if you have `*.py` as that pattern for the autocmd, +/// you must pass `*.py` exactly to clear it. `test.py` will not +/// match the pattern. +/// - defaults to clearing all patterns. +/// - NOTE: Cannot be used with {buffer} +/// - buffer: (bufnr) +/// - clear only |autocmd-buflocal| autocommands. +/// - NOTE: Cannot be used with {pattern} +/// - group: (string|int) The augroup name or id. +/// - NOTE: If not passed, will only delete autocmds *not* in any group. +/// +void nvim_clear_autocmd(Dict(clear_autocmd) *opts, Error *err) + FUNC_API_SINCE(9) +{ + // TODO(tjdevries): Future improvements: + // - once: (boolean) - Only clear autocmds with once. See |autocmd-once| + // - nested: (boolean) - Only clear autocmds with nested. See |autocmd-nested| + // - group: Allow passing "*" or true or something like that to force doing all + // autocmds, regardless of their group. + + Array patterns = ARRAY_DICT_INIT; + Array event_array = ARRAY_DICT_INIT; + + if (!unpack_string_or_array(&event_array, &opts->event, "event", false, err)) { + goto cleanup; + } + + if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Cannot use both 'pattern' and 'buffer'"); + goto cleanup; + } + + int au_group = get_augroup_from_object(opts->group, err); + if (au_group == AUGROUP_ERROR) { + goto cleanup; + } + + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { + goto cleanup; + } + + // When we create the autocmds, we want to say that they are all matched, so that's * + // but when we clear them, we want to say that we didn't pass a pattern, so that's NUL + if (patterns.size == 0) { + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING(""))); + } + + // If we didn't pass any events, that means clear all events. + if (event_array.size == 0) { + FOR_ALL_AUEVENTS(event) { + FOREACH_ITEM(patterns, pat_object, { + char_u *pat = (char_u *)pat_object.data.string.data; + if (!clear_autocmd(event, pat, au_group, err)) { + goto cleanup; + } + }); + } + } else { + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup); + + FOREACH_ITEM(patterns, pat_object, { + char_u *pat = (char_u *)pat_object.data.string.data; + if (!clear_autocmd(event_nr, pat, au_group, err)) { + goto cleanup; + } + }); + }); + } + +cleanup: + api_free_array(event_array); + api_free_array(patterns); + + return; } /// Create or get an autocommand group |autocmd-groups|. @@ -664,11 +684,15 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou /// @param id Integer The id of the group. /// @see |nvim_del_augroup_by_name()| /// @see |nvim_create_augroup()| -void nvim_del_augroup_by_id(Integer id) +void nvim_del_augroup_by_id(Integer id, Error *err) FUNC_API_SINCE(9) { - char *name = augroup_name((int)id); - augroup_del(name, false); + TRY_WRAP({ + try_start(); + char *name = augroup_name((int)id); + augroup_del(name, false); + try_end(err); + }); } /// Delete an autocommand group by name. @@ -677,10 +701,14 @@ void nvim_del_augroup_by_id(Integer id) /// this group will also be deleted and cleared. This group will no longer exist. /// @param name String The name of the group. /// @see |autocommand-groups| -void nvim_del_augroup_by_name(String name) +void nvim_del_augroup_by_name(String name, Error *err) FUNC_API_SINCE(9) { - augroup_del(name.data, false); + TRY_WRAP({ + try_start(); + augroup_del(name.data, false); + try_end(err); + }); } /// Execute an autocommand |autocmd-execute|. @@ -709,7 +737,7 @@ void nvim_exec_autocmd(Object event, Dict(exec_autocmd) *opts, Error *err) Array event_array = ARRAY_DICT_INIT; - if (!unpack_string_or_array(&event_array, &event, "event", err)) { + if (!unpack_string_or_array(&event_array, &event, "event", true, err)) { goto cleanup; } @@ -808,7 +836,7 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err) return true; } -static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err) +static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err) { if (v->type == kObjectTypeString) { ADD(*array, copy_object(*v)); @@ -818,10 +846,119 @@ static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err) } *array = copy_array(v->data.array); } else { - api_set_error(err, - kErrorTypeValidation, - "'%s' must be an array or a string.", - k); + if (required) { + api_set_error(err, + kErrorTypeValidation, + "'%s' must be an array or a string.", + k); + return false; + } + } + + return true; +} + +// Returns AUGROUP_ERROR if there was a problem with {group} +static int get_augroup_from_object(Object group, Error *err) +{ + int au_group = AUGROUP_ERROR; + + switch (group.type) { + case kObjectTypeNil: + return AUGROUP_DEFAULT; + case kObjectTypeString: + au_group = augroup_find(group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", group.data.string.data); + + return AUGROUP_ERROR; + } + + return au_group; + case kObjectTypeInteger: + au_group = (int)group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + return AUGROUP_ERROR; + } + + return au_group; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + return AUGROUP_ERROR; + } +} + +static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer, + Error *err) +{ + const char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + + if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "cannot pass both: 'pattern' and 'buffer' for the same autocmd"); + return false; + } else if (pattern.type != kObjectTypeNil) { + Object *v = &pattern; + + if (v->type == kObjectTypeString) { + char_u *pat = (char_u *)v->data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } else if (v->type == kObjectTypeArray) { + if (!check_autocmd_string_array(*patterns, "pattern", err)) { + return false; + } + + Array array = v->data.array; + for (size_t i = 0; i < array.size; i++) { + char_u *pat = (char_u *)array.items[i].data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } + } else { + api_set_error(err, + kErrorTypeValidation, + "'pattern' must be a string"); + return false; + } + } else if (buffer.type != kObjectTypeNil) { + if (buffer.type != kObjectTypeInteger) { + api_set_error(err, + kErrorTypeValidation, + "'buffer' must be an integer"); + return false; + } + + buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err); + if (ERROR_SET(err)) { + return false; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle); + ADD(*patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal))); + } + + return true; +} + +static bool clear_autocmd(event_T event, char_u *pat, int au_group, Error *err) +{ + if (do_autocmd_event(event, pat, false, false, (char_u *)"", true, au_group) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to clear autocmd"); return false; } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 797b64e2af..8dca37a321 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -259,9 +259,9 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// local pos = a.nvim_win_get_cursor(0) /// local ns = a.nvim_create_namespace('my-plugin') /// -- Create new extmark at line 1, column 1. -/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {}) +/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {}) /// -- Create new extmark at line 3, column 1. -/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {}) +/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {}) /// -- Get extmarks only from line 3. /// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) /// -- Get all marks in this buffer + namespace. diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 6a29b0fb59..520bb7fbc6 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -123,14 +123,20 @@ return { "nocombine"; }; -- Autocmds + clear_autocmd = { + "buffer"; + "event"; + "group"; + "pattern"; + }; create_autocmd = { "buffer"; "callback"; "command"; "desc"; "group"; - "once"; "nested"; + "once"; "pattern"; }; exec_autocmd = { diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index bc7c2e6a60..650349cde7 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -140,8 +140,9 @@ typedef struct { // Useful macro for executing some `code` for each item in an array. #define FOREACH_ITEM(a, __foreach_item, code) \ - for (size_t __foreach_i = 0; __foreach_i < (a).size; __foreach_i++) { \ - Object __foreach_item = (a).items[__foreach_i]; \ + for (size_t (__foreach_item ## _index) = 0; (__foreach_item ## _index) < (a).size; \ + (__foreach_item ## _index)++) { \ + Object __foreach_item = (a).items[__foreach_item ## _index]; \ code; \ } diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index a36f2c97b5..c0a22d058c 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2022,6 +2022,11 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) verbose_leave_scroll(); } + // Make sure to set autocmd_nested before executing + // lua code, so that it works properly + autocmd_nested = ac->nested; + current_sctx = ac->script_ctx; + if (ac->exec.type == CALLABLE_CB) { typval_T argsin = TV_INITIAL_VALUE; typval_T rettv = TV_INITIAL_VALUE; @@ -2052,8 +2057,6 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) if (oneshot) { aucmd_del(ac); } - autocmd_nested = ac->nested; - current_sctx = ac->script_ctx; if (ac->last) { acp->nextcmd = NULL; } else { @@ -2347,17 +2350,20 @@ int autocmd_delete_event(int group, event_T event, char_u *pat) /// Deletes an autocmd by ID. /// Only autocmds created via the API have IDs associated with them. There /// is no way to delete a specific autocmd created via :autocmd -void autocmd_delete_id(int64_t id) +bool autocmd_delete_id(int64_t id) { + assert(id > 0); FOR_ALL_AUEVENTS(event) { FOR_ALL_AUPATS_IN_EVENT(event, ap) { for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { if (ac->id == id) { aucmd_del(ac); + return true; } } } } + return false; } // =========================================================================== diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 29413281ad..08ca1a6247 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1211,6 +1211,8 @@ struct window_S { colnr_T w_old_visual_col; ///< last known start of visual part colnr_T w_old_curswant; ///< last known value of Curswant + linenr_T w_last_cursor_lnum_rnu; ///< cursor lnum when 'rnu' was last redrawn + // 'listchars' characters. Defaults set in set_chars_option(). struct { int eol; diff --git a/src/nvim/change.c b/src/nvim/change.c index 6c3dbf72e4..0644b1d601 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -287,7 +287,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra } // Relative numbering may require updating more. - if (wp->w_p_rnu) { + if (wp->w_p_rnu && xtra != 0) { redraw_later(wp, SOME_VALID); } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index c087948810..815d57121b 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1480,8 +1480,6 @@ bool edit(int cmdchar, bool startln, long count) /// @param ready not busy with something static void ins_redraw(bool ready) { - bool conceal_cursor_moved = false; - if (char_avail()) { return; } @@ -1504,7 +1502,6 @@ static void ins_redraw(bool ready) update_curswant(); ins_apply_autocmds(EVENT_CURSORMOVEDI); } - conceal_cursor_moved = true; curwin->w_last_cursormoved = curwin->w_cursor; } @@ -1560,11 +1557,6 @@ static void ins_redraw(bool ready) curbuf->b_changed_invalid = false; } - if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin) - && conceal_cursor_moved) { - redrawWinline(curwin, curwin->w_cursor.lnum); - } - pum_check_clear(); if (must_redraw) { update_screen(0); diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 41b419c150..ae9cb3b1e8 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -9466,6 +9466,11 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) res = ITEM_COMPARE_FAIL; } else { res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); + if (res > 0) { + res = 1; + } else if (res < 0) { + res = -1; + } } if (sortinfo->item_compare_func_err) { res = ITEM_COMPARE_FAIL; // return value has wrong type diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 84fca137d2..1d02c74c41 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -192,13 +192,12 @@ typedef struct command_line_state { typedef struct cmdline_info CmdlineInfo; -/* The current cmdline_info. It is initialized in getcmdline() and after that - * used by other functions. When invoking getcmdline() recursively it needs - * to be saved with save_cmdline() and restored with restore_cmdline(). - * TODO: make it local to getcmdline() and pass it around. */ +/// The current cmdline_info. It is initialized in getcmdline() and after that +/// used by other functions. When invoking getcmdline() recursively it needs +/// to be saved with save_cmdline() and restored with restore_cmdline(). static struct cmdline_info ccline; -static int cmd_showtail; // Only show path tail in lists ? +static int cmd_showtail; // Only show path tail in lists ? static int new_cmdpos; // position set by set_cmdline_pos() @@ -732,9 +731,10 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool /// Internal entry point for cmdline mode. /// -/// caller must use save_cmdline and restore_cmdline. Best is to use -/// getcmdline or getcmdline_prompt, instead of calling this directly. -static uint8_t *command_line_enter(int firstc, long count, int indent) +/// @param count only used for incremental search +/// @param indent indent for inside conditionals +/// @param init_ccline clear ccline first +static uint8_t *command_line_enter(int firstc, long count, int indent, bool init_ccline) { // can be invoked recursively, identify each level static int cmdline_level = 0; @@ -751,6 +751,20 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) CommandLineState *s = &state; s->save_p_icm = vim_strsave(p_icm); init_incsearch_state(&s->is_state); + CmdlineInfo save_ccline; + bool did_save_ccline = false; + + if (ccline.cmdbuff != NULL) { + // Currently ccline can never be in use if init_ccline is false. + // Some changes will be needed if this is no longer the case. + assert(init_ccline); + // Being called recursively. Since ccline is global, we need to save + // the current buffer and restore it when returning. + save_cmdline(&save_ccline); + did_save_ccline = true; + } else if (init_ccline) { + memset(&ccline, 0, sizeof(struct cmdline_info)); + } if (s->firstc == -1) { s->firstc = NUL; @@ -997,6 +1011,13 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } cmdline_level--; + + if (did_save_ccline) { + restore_cmdline(&save_ccline); + } else { + ccline.cmdbuff = NULL; + } + return p; } @@ -1340,15 +1361,9 @@ static int command_line_execute(VimState *state, int key) s->c = get_expr_register(); if (s->c == '=') { - // Need to save and restore ccline. And set "textlock" - // to avoid nasty things like going to another buffer when - // evaluating an expression. - CmdlineInfo save_ccline; - save_cmdline(&save_ccline); textlock++; p = get_expr_line(); textlock--; - restore_cmdline(&save_ccline); if (p != NULL) { len = (int)STRLEN(p); @@ -1889,10 +1904,7 @@ static int command_line_handle_key(CommandLineState *s) beep_flush(); s->c = ESC; } else { - CmdlineInfo save_ccline; - save_cmdline(&save_ccline); s->c = get_expr_register(); - restore_cmdline(&save_ccline); } } @@ -2120,7 +2132,7 @@ static int command_line_handle_key(CommandLineState *s) int len = 0; int old_firstc; - xfree(ccline.cmdbuff); + XFREE_CLEAR(ccline.cmdbuff); s->xpc.xp_context = EXPAND_NOTHING; if (s->hiscnt == hislen) { p = s->lookfor; // back to the old one @@ -2403,14 +2415,7 @@ static void abandon_cmdline(void) /// @param indent indent for inside conditionals char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED) { - // Be prepared for situations where cmdline can be invoked recursively. - // That includes cmd mappings, event handlers, as well as update_screen() - // (custom status line eval), which all may invoke ":normal :". - CmdlineInfo save_ccline; - save_cmdline(&save_ccline); - char_u *retval = command_line_enter(firstc, count, indent); - restore_cmdline(&save_ccline); - return retval; + return command_line_enter(firstc, count, indent, true); } /// Get a command line with a prompt @@ -2434,8 +2439,14 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, const int a const int msg_col_save = msg_col; CmdlineInfo save_ccline; - save_cmdline(&save_ccline); - + bool did_save_ccline = false; + if (ccline.cmdbuff != NULL) { + // Save the values of the current cmdline and restore them below. + save_cmdline(&save_ccline); + did_save_ccline = true; + } else { + memset(&ccline, 0, sizeof(struct cmdline_info)); + } ccline.prompt_id = last_prompt_id++; ccline.cmdprompt = (char_u *)prompt; ccline.cmdattr = attr; @@ -2447,9 +2458,11 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, const int a int msg_silent_saved = msg_silent; msg_silent = 0; - char *const ret = (char *)command_line_enter(firstc, 1L, 0); + char *const ret = (char *)command_line_enter(firstc, 1L, 0, false); - restore_cmdline(&save_ccline); + if (did_save_ccline) { + restore_cmdline(&save_ccline); + } msg_silent = msg_silent_saved; // Restore msg_col, the prompt from input() may have changed it. // But only if called recursively and the commandline is therefore being @@ -2601,7 +2614,6 @@ bool cmdline_at_end(void) /* * Allocate a new command line buffer. * Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen. - * Returns the new value of ccline.cmdbuff and ccline.cmdbufflen. */ static void alloc_cmdbuff(int len) { @@ -3367,53 +3379,23 @@ void put_on_cmdline(char_u *str, int len, int redraw) } } -/* - * Save ccline, because obtaining the "=" register may execute "normal :cmd" - * and overwrite it. But get_cmdline_str() may need it, thus make it - * available globally in prev_ccline. - */ +/// Save ccline, because obtaining the "=" register may execute "normal :cmd" +/// and overwrite it. static void save_cmdline(struct cmdline_info *ccp) { *ccp = ccline; + memset(&ccline, 0, sizeof(struct cmdline_info)); ccline.prev_ccline = ccp; - ccline.cmdbuff = NULL; - ccline.cmdprompt = NULL; - ccline.xpc = NULL; - ccline.special_char = NUL; - ccline.level = 0; + ccline.cmdbuff = NULL; // signal that ccline is not in use } -/* - * Restore ccline after it has been saved with save_cmdline(). - */ +/// Restore ccline after it has been saved with save_cmdline(). static void restore_cmdline(struct cmdline_info *ccp) FUNC_ATTR_NONNULL_ALL { ccline = *ccp; } -/* - * Save the command line into allocated memory. Returns a pointer to be - * passed to restore_cmdline_alloc() later. - */ -char_u *save_cmdline_alloc(void) - FUNC_ATTR_NONNULL_RET -{ - struct cmdline_info *p = xmalloc(sizeof(struct cmdline_info)); - save_cmdline(p); - return (char_u *)p; -} - -/* - * Restore the command line from the return value of save_cmdline_alloc(). - */ -void restore_cmdline_alloc(char_u *p) - FUNC_ATTR_NONNULL_ALL -{ - restore_cmdline((struct cmdline_info *)p); - xfree(p); -} - /// Paste a yank register into the command line. /// Used by CTRL-R command in command-line mode. /// insert_reg() can't be used here, because special characters from the @@ -3429,7 +3411,6 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) char_u *arg; char_u *p; bool allocated; - struct cmdline_info save_ccline; // check for valid regname; also accept special characters for CTRL-R in // the command line @@ -3447,13 +3428,11 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) } - // Need to save and restore ccline. And set "textlock" to avoid nasty - // things like going to another buffer when evaluating an expression. - save_cmdline(&save_ccline); + // Need to set "textlock" to avoid nasty things like going to another + // buffer when evaluating an expression. textlock++; const bool i = get_spec_reg(regname, &arg, &allocated, true); textlock--; - restore_cmdline(&save_ccline); if (i) { // Got the value of a special register in "arg". @@ -5307,7 +5286,6 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T typval_T args[4]; char_u *pat = NULL; const sctx_T save_current_sctx = current_sctx; - struct cmdline_info save_ccline; if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) { return NULL; @@ -5329,15 +5307,10 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T args[1].vval.v_string = xp->xp_line; args[2].vval.v_number = xp->xp_col; - // Save the cmdline, we don't know what the function may do. - save_ccline = ccline; - ccline.cmdbuff = NULL; - ccline.cmdprompt = NULL; current_sctx = xp->xp_script_ctx; void *const ret = user_expand_func(xp->xp_arg, 3, args); - ccline = save_ccline; current_sctx = save_current_sctx; if (ccline.cmdbuff != NULL) { ccline.cmdbuff[ccline.cmdlen] = keep; @@ -5947,10 +5920,8 @@ int get_history_idx(int histype) } -/* - * Get pointer to the command line info to use. cmdline_paste() may clear - * ccline and put the previous value in prev_ccline. - */ +/// Get pointer to the command line info to use. save_cmdline() may clear +/// ccline and put the previous value in ccline.prev_ccline. static struct cmdline_info *get_ccline_ptr(void) { if ((State & CMDLINE) == 0) { @@ -6350,6 +6321,11 @@ int hist_type2char(int type) return NUL; } +void cmdline_init(void) +{ + memset(&ccline, 0, sizeof(struct cmdline_info)); +} + /// Open a window on the current command line and history. Allow editing in /// the window. Returns when the window is closed. /// Returns: @@ -6358,7 +6334,6 @@ int hist_type2char(int type) /// K_IGNORE if editing continues static int open_cmdwin(void) { - struct cmdline_info save_ccline; bufref_T old_curbuf; bufref_T bufref; win_T *old_curwin = curwin; @@ -6459,9 +6434,6 @@ static int open_cmdwin(void) } redraw_later(curwin, SOME_VALID); - // Save the command line info, can be used recursively. - save_cmdline(&save_ccline); - // No Ex mode here! exmode_active = false; @@ -6499,8 +6471,6 @@ static int open_cmdwin(void) // Restore KeyTyped in case it is modified by autocommands KeyTyped = save_KeyTyped; - // Restore the command line info. - restore_cmdline(&save_ccline); cmdwin_type = 0; cmdwin_level = 0; diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 39ff75d8c2..1235087500 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -589,12 +589,18 @@ static int makeopens(FILE *fd, char_u *dirnow) "if expand('%') == '' && !&modified && line('$') <= 1" " && getline(1) == ''\n" " let s:wipebuf = bufnr('%')\n" - "endif\n" - // Now save the current files, current buffer first. - "set shortmess=aoO\n") < 0) { + "endif\n") < 0) { return FAIL; } + // save 'shortmess' if not storing options + if ((ssop_flags & SSOP_OPTIONS) == 0) { + PUTLINE_FAIL("let s:shortmess_save = &shortmess"); + } + + // Now save the current files, current buffer first. + PUTLINE_FAIL("set shortmess=aoO"); + // Put all buffers into the buffer list. // Do it very early to preserve buffer order after loading session (which // can be disrupted by prior `edit` or `tabedit` calls). @@ -842,15 +848,21 @@ static int makeopens(FILE *fd, char_u *dirnow) return FAIL; } - // Re-apply 'winheight', 'winwidth' and 'shortmess'. - if (fprintf(fd, - "set winheight=%" PRId64 " winwidth=%" PRId64 - " shortmess=%s\n", - (int64_t)p_wh, - (int64_t)p_wiw, - p_shm) < 0) { + // Re-apply 'winheight' and 'winwidth'. + if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 "\n", + (int64_t)p_wh, (int64_t)p_wiw) < 0) { return FAIL; } + + // Restore 'shortmess'. + if (ssop_flags & SSOP_OPTIONS) { + if (fprintf(fd, "set shortmess=%s\n", p_shm) < 0) { + return FAIL; + } + } else { + PUTLINE_FAIL("let &shortmess = s:shortmess_save"); + } + if (tab_firstwin != NULL && tab_firstwin->w_next != NULL) { // Restore 'winminheight' and 'winminwidth'. PUTLINE_FAIL("let &winminheight = s:save_winminheight"); diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index c10172cc52..b12b99b7ee 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -4001,7 +4001,6 @@ static char_u *eval_map_expr(mapblock_T *mp, int c) char_u *res; char_u *p = NULL; char_u *expr = NULL; - char_u *save_cmd; pos_T save_cursor; int save_msg_col; int save_msg_row; @@ -4013,8 +4012,6 @@ static char_u *eval_map_expr(mapblock_T *mp, int c) vim_unescape_ks(expr); } - save_cmd = save_cmdline_alloc(); - // Forbid changing text or using ":normal" to avoid most of the bad side // effects. Also restore the cursor position. textlock++; @@ -4045,8 +4042,6 @@ static char_u *eval_map_expr(mapblock_T *mp, int c) msg_col = save_msg_col; msg_row = save_msg_row; - restore_cmdline_alloc(save_cmd); - if (p == NULL) { return NULL; } diff --git a/src/nvim/main.c b/src/nvim/main.c index afb9313cba..6ea1cb0875 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -20,6 +20,7 @@ #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" @@ -156,6 +157,7 @@ bool event_teardown(void) void early_init(mparm_T *paramp) { env_init(); + cmdline_init(); eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. diff --git a/src/nvim/message.c b/src/nvim/message.c index b3fefbc0f4..6708001495 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -327,11 +327,12 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) } retval = msg_end(); - if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1) - * Columns + sc_col) { + if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1) * Columns + sc_col) { set_keep_msg((char *)s, 0); } + need_fileinfo = false; + xfree(buf); --entered; return retval; @@ -382,6 +383,13 @@ void trunc_string(char_u *s, char_u *buf, int room_in, int buflen) int i; int n; + if (*s == NUL) { + if (buflen > 0) { + *buf = NUL; + } + return; + } + if (room_in < 3) { room = 0; } @@ -1348,6 +1356,7 @@ void msg_start(void) if (!msg_silent) { XFREE_CLEAR(keep_msg); // don't display old message now + need_fileinfo = false; } if (need_clr_eos) { @@ -1486,6 +1495,10 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) char_u *s; int mb_l; int c; + int save_got_int = got_int; + + // Only quit when got_int was set in here. + got_int = false; // if MSG_HIST flag set, add message to history if (attr & MSG_HIST) { @@ -1503,7 +1516,7 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) * Go over the string. Special characters are translated and printed. * Normal characters are printed several at a time. */ - while (--len >= 0) { + while (--len >= 0 && !got_int) { // Don't include composing chars after the end. mb_l = utfc_ptr2len_len((char_u *)str, len + 1); if (mb_l > 1) { @@ -1542,11 +1555,13 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) } } - if (str > plain_start) { + if (str > plain_start && !got_int) { // Print the printable chars at the end. msg_puts_attr_len(plain_start, str - plain_start, attr); } + got_int |= save_got_int; + return retval; } @@ -2013,6 +2028,8 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) if (!msg_use_printf() || (headless_mode && default_grid.chars)) { msg_puts_display((const char_u *)str, len, attr, false); } + + need_fileinfo = false; } /// Print a formatted message diff --git a/src/nvim/move.c b/src/nvim/move.c index 751e0046bc..5e02e355bf 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -95,18 +95,41 @@ static void comp_botline(win_T *wp) win_check_anchored_floats(wp); } -// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. +/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. +/// Also when concealing is on and 'concealcursor' is not active. void redraw_for_cursorline(win_T *wp) FUNC_ATTR_NONNULL_ALL { - if ((wp->w_p_rnu || win_cursorline_standout(wp)) - && (wp->w_valid & VALID_CROW) == 0 - && !pum_visible()) { + if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible() + && (wp->w_p_rnu || win_cursorline_standout(wp))) { // win_line() will redraw the number column and cursorline only. redraw_later(wp, VALID); } } +/// Redraw when w_virtcol changes and 'cursorcolumn' is set or 'cursorlineopt' +/// contains "screenline". +/// Also when concealing is on and 'concealcursor' is active. +static void redraw_for_cursorcolumn(win_T *wp) + FUNC_ATTR_NONNULL_ALL +{ + if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) { + if (wp->w_p_cuc) { + // When 'cursorcolumn' is set need to redraw with SOME_VALID. + redraw_later(wp, SOME_VALID); + } else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) { + // When 'cursorlineopt' contains "screenline" need to redraw with VALID. + redraw_later(wp, VALID); + } + } + // If the cursor moves horizontally when 'concealcursor' is active, then the + // current line needs to be redrawn in order to calculate the correct + // cursor position. + if ((wp->w_valid & VALID_VIRTCOL) == 0 && wp->w_p_cole > 0 && conceal_cursor_line(wp)) { + redrawWinline(wp, wp->w_cursor.lnum); + } +} + /* * Update curwin->w_topline and redraw if necessary. * Used to update the screen before printing a message. @@ -623,11 +646,8 @@ void validate_virtcol_win(win_T *wp) check_cursor_moved(wp); if (!(wp->w_valid & VALID_VIRTCOL)) { getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL); + redraw_for_cursorcolumn(wp); wp->w_valid |= VALID_VIRTCOL; - if (wp->w_p_cuc - && !pum_visible()) { - redraw_later(wp, SOME_VALID); - } } } @@ -930,11 +950,7 @@ void curs_columns(win_T *wp, int may_scroll) redraw_later(wp, NOT_VALID); } - // Redraw when w_virtcol changes and 'cursorcolumn' is set - if (wp->w_p_cuc && (wp->w_valid & VALID_VIRTCOL) == 0 - && !pum_visible()) { - redraw_later(wp, SOME_VALID); - } + redraw_for_cursorcolumn(curwin); // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise wp->w_valid_leftcol = wp->w_leftcol; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index e773351d63..72e80952ff 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1288,16 +1288,6 @@ static void normal_redraw(NormalState *s) update_topline(curwin); validate_cursor(); - // If the cursor moves horizontally when 'concealcursor' is active, then the - // current line needs to be redrawn in order to calculate the correct - // cursor position. - if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin)) { - redrawWinline(curwin, curwin->w_cursor.lnum); - } - - // Might need to update for 'cursorline'. - check_redraw_cursorline(); - if (VIsual_active) { update_curbuf(INVERTED); // update inverted part } else if (must_redraw) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 4404ee7b80..dab6d237bf 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3893,7 +3893,7 @@ void ex_display(exarg_T *eap) msg_puts_attr("^J", attr); n -= 2; } - for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 + for (p = yb->y_array[j]; *p != NUL && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 clen = utfc_ptr2len(p); msg_outtrans_len(p, clen); p += clen - 1; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index d88cd6b9b9..98c2b84770 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -523,11 +523,11 @@ EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' -EXTERN long p_mle; // 'modelineexpr' +EXTERN int p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' -EXTERN long p_mousef; // 'mousefocus' +EXTERN int p_mousef; // 'mousefocus' EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' EXTERN char_u *p_opfunc; // 'operatorfunc' diff --git a/src/nvim/path.c b/src/nvim/path.c index d3aa5e5bf2..75624778e3 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1777,7 +1777,7 @@ int path_with_url(const char *fname) } // check body: alpha or dash - for (p = fname; (isalpha(*p) || (*p == '-')); p++) {} + for (p = fname + 1; (isalpha(*p) || (*p == '-')); p++) {} // check last char is not a dash if (p[-1] == '-') { diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 9ee7b1cc8a..c8d9e91fb5 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2780,6 +2780,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int // present. if (qfl_type == QFLT_LOCATION) { win_T *wp = win_id2wp(prev_winid); + if (wp == NULL && curwin->w_llist != qi) { emsg(_("E924: Current window was closed")); *opened_window = false; diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 009a26d4e0..d3c43e530a 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -107,8 +107,10 @@ static char_u e_unmatchedpar[] = N_("E55: Unmatched %s)"); static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here"); static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%["); -static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); -static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); +static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); +static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); +static char_u e_regexp_number_after_dot_pos_search[] + = N_("E1204: No Number allowed after .: '\\%%%c'"); #define NOT_MULTI 0 #define MULTI_ONE 1 diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c index 7340957903..7d0b9a7a77 100644 --- a/src/nvim/regexp_bt.c +++ b/src/nvim/regexp_bt.c @@ -1578,14 +1578,19 @@ static char_u *regatom(int *flagp) } default: - if (ascii_isdigit(c) || c == '<' || c == '>' - || c == '\'') { + if (ascii_isdigit(c) || c == '<' || c == '>' || c == '\'' || c == '.') { uint32_t n = 0; int cmp; + bool cur = false; cmp = c; - if (cmp == '<' || cmp == '>') + if (cmp == '<' || cmp == '>') { c = getchr(); + } + if (no_Magic(c) == '.') { + cur = true; + c = getchr(); + } while (ascii_isdigit(c)) { n = n * 10 + (uint32_t)(c - '0'); c = getchr(); @@ -1602,14 +1607,31 @@ static char_u *regatom(int *flagp) } break; } else if (c == 'l' || c == 'c' || c == 'v') { + if (cur && n) { + semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + rc_did_emsg = true; + return NULL; + } if (c == 'l') { + if (cur) { + n = curwin->w_cursor.lnum; + } ret = regnode(RE_LNUM); if (save_prev_at_start) { at_start = true; } } else if (c == 'c') { + if (cur) { + n = curwin->w_cursor.col; + n++; + } ret = regnode(RE_COL); } else { + if (cur) { + colnr_T vcol = 0; + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); + n = ++vcol; + } ret = regnode(RE_VCOL); } if (ret == JUST_CALC_SIZE) { @@ -3130,9 +3152,17 @@ static bool regmatch( { int mark = OPERAND(scan)[0]; int cmp = OPERAND(scan)[1]; - pos_T *pos; + pos_T *pos; + size_t col = REG_MULTI ? rex.input - rex.line : 0; pos = getmark_buf(rex.reg_buf, mark, false); + + // Line may have been freed, get it again. + if (REG_MULTI) { + rex.line = reg_getline(rex.lnum); + rex.input = rex.line + col; + } + if (pos == NULL // mark doesn't exist || pos->lnum <= 0) { // mark isn't set in reg_buf status = RA_NOMATCH; diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 7e316624f8..a8d6e9c40b 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1639,10 +1639,20 @@ static int nfa_regatom(void) { int64_t n = 0; const int cmp = c; + bool cur = false; - if (c == '<' || c == '>') + if (c == '<' || c == '>') { c = getchr(); + } + if (no_Magic(c) == '.') { + cur = true; + c = getchr(); + } while (ascii_isdigit(c)) { + if (cur) { + semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + return FAIL; + } if (n > (INT32_MAX - (c - '0')) / 10) { // overflow. emsg(_(e_value_too_large)); @@ -1655,6 +1665,9 @@ static int nfa_regatom(void) int32_t limit = INT32_MAX; if (c == 'l') { + if (cur) { + n = curwin->w_cursor.lnum; + } // \%{n}l \%{n}<l \%{n}>l EMIT(cmp == '<' ? NFA_LNUM_LT : cmp == '>' ? NFA_LNUM_GT : NFA_LNUM); @@ -1662,10 +1675,19 @@ static int nfa_regatom(void) at_start = true; } } else if (c == 'c') { + if (cur) { + n = curwin->w_cursor.col; + n++; + } // \%{n}c \%{n}<c \%{n}>c EMIT(cmp == '<' ? NFA_COL_LT : cmp == '>' ? NFA_COL_GT : NFA_COL); } else { + if (cur) { + colnr_T vcol = 0; + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); + n = ++vcol; + } // \%{n}v \%{n}<v \%{n}>v EMIT(cmp == '<' ? NFA_VCOL_LT : cmp == '>' ? NFA_VCOL_GT : NFA_VCOL); @@ -6227,8 +6249,10 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_MARK_GT: case NFA_MARK_LT: { - size_t col = rex.input - rex.line; - pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false); + pos_T *pos; + size_t col = REG_MULTI ? rex.input - rex.line : 0; + + pos = getmark_buf(rex.reg_buf, t->state->val, false); // Line may have been freed, get it again. if (REG_MULTI) { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 41a129dba9..f922a50260 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -667,15 +667,11 @@ void conceal_check_cursor_line(void) /// Whether cursorline is drawn in a special way /// -/// If true, both old and new cursorline will need -/// to be redrawn when moving cursor within windows. -/// TODO(bfredl): VIsual_active shouldn't be needed, but is used to fix a glitch -/// caused by scrolling. +/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows. bool win_cursorline_standout(const win_T *wp) FUNC_ATTR_NONNULL_ALL { - return wp->w_p_cul - || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp))); + return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); } /* @@ -1577,9 +1573,9 @@ static void win_update(win_T *wp, DecorProviders *providers) idx++; lnum += foldinfo.fi_lines + 1; } else { - if (wp->w_p_rnu) { - // 'relativenumber' set: The text doesn't need to be drawn, but - // the number column nearly always does. + if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) { + // 'relativenumber' set and cursor moved vertically: The + // text doesn't need to be drawn, but the number column does. foldinfo_T info = fold_info(wp, lnum); (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true, info, &line_providers); @@ -1607,6 +1603,8 @@ static void win_update(win_T *wp, DecorProviders *providers) // update w_last_cursorline. wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0; + wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; + if (idx > wp->w_lines_valid) { wp->w_lines_valid = idx; } @@ -4333,6 +4331,9 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, break; } } + if (!*s.p) { + continue; + } int attr; bool through = false; if (hl_mode == kHlModeCombine) { @@ -7617,16 +7618,3 @@ win_T *get_win_by_grid_handle(handle_T handle) } return NULL; } - -/// Check if the cursor moved and 'cursorline' is set. Mark for a VALID redraw -/// if needed. -void check_redraw_cursorline(void) -{ - // When 'cursorlineopt' is "screenline" need to redraw always. - if (curwin->w_p_cul - && (curwin->w_last_cursorline != curwin->w_cursor.lnum - || (curwin->w_p_culopt_flags & CULOPT_SCRLINE)) - && !char_avail()) { - redraw_later(curwin, VALID); - } -} diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index d7b220b3f6..07058b34fd 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -5580,6 +5580,9 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo while (!vim_fgets(line, MAXWLEN * 2, fd)) { fpos = fpos_next; fpos_next = ftell(fd); + if (fpos_next < 0) { + break; // should never happen + } if (STRNCMP(word, line, len) == 0 && (line[len] == '/' || line[len] < ' ')) { // Found duplicate word. Remove it by writing a '#' at diff --git a/src/nvim/state.c b/src/nvim/state.c index f9a3aaab7f..3a7636085b 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -15,6 +15,7 @@ #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/os/input.h" +#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -54,6 +55,12 @@ getkey: // Event was made available after the last multiqueue_process_events call key = K_EVENT; } else { + // Duplicate display updating logic in vgetorpeek() + if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0 + && must_redraw != 0 && !need_wait_return) { + update_screen(0); + setcursor(); // put cursor back where it belongs + } // Flush screen updates before blocking ui_flush(); // Call `os_inchar` directly to block for events or user input without diff --git a/src/nvim/testdir/test_cursorline.vim b/src/nvim/testdir/test_cursorline.vim index bf049ec779..7e97df6027 100644 --- a/src/nvim/testdir/test_cursorline.vim +++ b/src/nvim/testdir/test_cursorline.vim @@ -293,5 +293,26 @@ func Test_cursorline_callback() call delete('Xcul_timer') endfunc +func Test_cursorline_screenline_update() + CheckScreendump + + let lines =<< trim END + call setline(1, repeat('xyz ', 30)) + set cursorline cursorlineopt=screenline + inoremap <F2> <Cmd>call cursor(1, 1)<CR> + END + call writefile(lines, 'Xcul_screenline') + + let buf = RunVimInTerminal('-S Xcul_screenline', #{rows: 8}) + call term_sendkeys(buf, "A") + call VerifyScreenDump(buf, 'Test_cursorline_screenline_1', {}) + call term_sendkeys(buf, "\<F2>") + call VerifyScreenDump(buf, 'Test_cursorline_screenline_2', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xcul_screenline') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim index 06ccd6e85f..8e1cb2c3ee 100644 --- a/src/nvim/testdir/test_filechanged.vim +++ b/src/nvim/testdir/test_filechanged.vim @@ -142,6 +142,7 @@ endfunc func Test_FileChangedShell_edit_dialog() throw 'Skipped: requires a UI to be active' CheckNotGui + CheckUnix " Using low level feedkeys() does not work on MS-Windows. new Xchanged_r call setline(1, 'reload this') diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index aa7b3a225b..6387ec62af 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -597,6 +597,31 @@ func Test_cursorline_with_visualmode() call delete('Xtest_cursorline_with_visualmode') endfunc +func Test_cursorcolumn_callback() + CheckScreendump + CheckFeature timers + + let lines =<< trim END + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set cursorcolumn + call cursor(4, 5) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + END + call writefile(lines, 'Xcuc_timer') + + let buf = RunVimInTerminal('-S Xcuc_timer', #{rows: 8}) + call TermWait(buf, 310) + call VerifyScreenDump(buf, 'Test_cursorcolumn_callback_1', {}) + + call StopVimInTerminal(buf) + call delete('Xcuc_timer') +endfunc + func Test_colorcolumn() CheckScreendump diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 9c84d77dd2..82c4cc128b 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -2,6 +2,9 @@ source check.vim source shared.vim +source term_util.vim +source view_util.vim +source screendump.vim func Test_messages() let oldmore = &more @@ -109,6 +112,22 @@ func Test_echospace() set ruler& showcmd& endfunc +func Test_quit_long_message() + CheckScreendump + + let content =<< trim END + echom range(9999)->join("\x01") + END + call writefile(content, 'Xtest_quit_message') + let buf = RunVimInTerminal('-S Xtest_quit_message', #{rows: 6}) + call term_sendkeys(buf, "q") + call VerifyScreenDump(buf, 'Test_quit_long_message', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_quit_message') +endfunc + " this was missing a terminating NUL func Test_echo_string_partial() function CountSpaces() @@ -116,3 +135,37 @@ func Test_echo_string_partial() call assert_equal("function('CountSpaces', [{'ccccccccccc': ['ab', 'cd'], 'aaaaaaaaaaa': v:false, 'bbbbbbbbbbbb': ''}])", string(function('CountSpaces', [#{aaaaaaaaaaa: v:false, bbbbbbbbbbbb: '', ccccccccccc: ['ab', 'cd']}]))) endfunc +" Message output was previously overwritten by the fileinfo display, shown +" when switching buffers. If a buffer is switched to, then a message if +" echoed, we should show the message, rather than overwriting it with +" fileinfo. +func Test_fileinfo_after_echo() + CheckScreendump + + let content =<< trim END + file a.txt + + hide edit b.txt + call setline(1, "hi") + setlocal modified + + hide buffer a.txt + + autocmd CursorHold * buf b.txt | w | echo "'b' written" + END + + call writefile(content, 'Xtest_fileinfo_after_echo') + let buf = RunVimInTerminal('-S Xtest_fileinfo_after_echo', #{rows: 6}) + call term_sendkeys(buf, ":set updatetime=50\<CR>") + call term_sendkeys(buf, "0$") + call VerifyScreenDump(buf, 'Test_fileinfo_after_echo', {}) + + call term_sendkeys(buf, ":q\<CR>") + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_fileinfo_after_echo') + call delete('b.txt') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 798cb9e54f..5dbe2cd366 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -795,6 +795,49 @@ func Test_mksession_winminheight() set sessionoptions& endfunc +" Test for mksession with and without options restores shortmess +func Test_mksession_shortmess() + " Without options + set sessionoptions-=options + split + mksession! Xtest_mks.out + let found_save = 0 + let found_restore = 0 + let lines = readfile('Xtest_mks.out') + for line in lines + let line = trim(line) + + if line ==# 'let s:shortmess_save = &shortmess' + let found_save += 1 + endif + + if found_save !=# 0 && line ==# 'let &shortmess = s:shortmess_save' + let found_restore += 1 + endif + endfor + call assert_equal(1, found_save) + call assert_equal(1, found_restore) + call delete('Xtest_mks.out') + close + set sessionoptions& + + " With options + set sessionoptions+=options + split + mksession! Xtest_mks.out + let found_restore = 0 + let lines = readfile('Xtest_mks.out') + for line in lines + if line =~# 's:shortmess_save' + let found_restore += 1 + endif + endfor + call assert_equal(0, found_restore) + call delete('Xtest_mks.out') + close + set sessionoptions& +endfunc + " Test for mksession with 'compatible' option func Test_mksession_compatible() throw 'skipped: Nvim does not support "compatible" option' diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim index dfbdc0bffd..521b0cf706 100644 --- a/src/nvim/testdir/test_number.vim +++ b/src/nvim/testdir/test_number.vim @@ -298,6 +298,31 @@ func Test_relativenumber_colors() call delete('XTest_relnr') endfunc +func Test_relativenumber_callback() + CheckScreendump + CheckFeature timers + + let lines =<< trim END + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set relativenumber + call cursor(4, 1) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + END + call writefile(lines, 'Xrnu_timer') + + let buf = RunVimInTerminal('-S Xrnu_timer', #{rows: 8}) + call TermWait(buf, 310) + call VerifyScreenDump(buf, 'Test_relativenumber_callback_1', {}) + + call StopVimInTerminal(buf) + call delete('Xrnu_timer') +endfunc + " Test for displaying line numbers with 'rightleft' func Test_number_rightleft() CheckFeature rightleft diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index a0f5ebfb9f..b733e334de 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -787,12 +787,109 @@ func Test_regexp_error() set re& endfunc +" Check patterns matching cursor position. +func s:curpos_test2() + new + call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse', + \ '3 foobar eins zwei drei vier fünf sechse', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 foobar eins zwei drei vier fünf sechse', + \ '6 foobar eins zwei drei vier fünf sechse', + \ '7 foobar eins zwei drei vier fünf sechse']) + call setpos('.', [0, 2, 10, 0]) + s/\%.c.*//g + call setpos('.', [0, 3, 15, 0]) + s/\%.l.*//g + call setpos('.', [0, 5, 3, 0]) + s/\%.v.*/_/g + call assert_equal(['1', + \ '2 foobar ', + \ '', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 _', + \ '6 foobar eins zwei drei vier fünf sechse', + \ '7 foobar eins zwei drei vier fünf sechse'], + \ getline(1, '$')) + call assert_fails('call search("\\%.1l")', 'E1204:') + call assert_fails('call search("\\%.1c")', 'E1204:') + call assert_fails('call search("\\%.1v")', 'E1204:') + bwipe! +endfunc + +" Check patterns matching before or after cursor position. +func s:curpos_test3() + new + call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse', + \ '3 foobar eins zwei drei vier fünf sechse', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 foobar eins zwei drei vier fünf sechse', + \ '6 foobar eins zwei drei vier fünf sechse', + \ '7 foobar eins zwei drei vier fünf sechse']) + call setpos('.', [0, 2, 10, 0]) + " Note: This removes all columns, except for the column directly in front of + " the cursor. Bug???? + :s/^.*\%<.c// + call setpos('.', [0, 3, 10, 0]) + :s/\%>.c.*$// + call setpos('.', [0, 5, 4, 0]) + " Note: This removes all columns, except for the column directly in front of + " the cursor. Bug???? + :s/^.*\%<.v/_/ + call setpos('.', [0, 6, 4, 0]) + :s/\%>.v.*$/_/ + call assert_equal(['1', + \ ' eins zwei drei vier fünf sechse', + \ '3 foobar e', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '_foobar eins zwei drei vier fünf sechse', + \ '6 fo_', + \ '7 foobar eins zwei drei vier fünf sechse'], + \ getline(1, '$')) + sil %d + call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse', + \ '3 foobar eins zwei drei vier fünf sechse', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 foobar eins zwei drei vier fünf sechse', + \ '6 foobar eins zwei drei vier fünf sechse', + \ '7 foobar eins zwei drei vier fünf sechse']) + call setpos('.', [0, 4, 4, 0]) + %s/\%<.l.*// + call setpos('.', [0, 5, 4, 0]) + %s/\%>.l.*// + call assert_equal(['', '', '', + \ '4 foobar eins zwei drei vier fünf sechse', + \ '5 foobar eins zwei drei vier fünf sechse', + \ '', ''], + \ getline(1, '$')) + bwipe! +endfunc + +" Test that matching below, at or after the +" cursor position work +func Test_matching_pos() + for val in range(3) + exe "set re=" .. val + " Match at cursor position + call s:curpos_test2() + " Match before or after cursor position + call s:curpos_test3() + endfor + set re& +endfunc + func Test_using_mark_position() " this was using freed memory + " new engine new norm O0 call assert_fails("s/\\%')", 'E486:') bwipe! + + " old engine + new + norm O0 + call assert_fails("s/\\%#=1\\%')", 'E486:') + bwipe! endfunc func Test_using_visual_position() diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index f72368fc59..5d7dd7bfff 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -59,6 +59,7 @@ endfunc func Test_sort_numbers() call assert_equal([3, 13, 28], sort([13, 28, 3], 'N')) call assert_equal(['3', '13', '28'], sort(['13', '28', '3'], 'N')) + call assert_equal([3997, 4996], sort([4996, 3997], 'Compare1')) endfunc func Test_sort_float() diff --git a/src/nvim/window.c b/src/nvim/window.c index 6bd2ef3ef6..632e9b3f44 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5857,11 +5857,11 @@ void win_drag_status_line(win_T *dragwin, int offset) } else { // drag down up = false; // Only dragging the last status line can reduce p_ch. - room = Rows - cmdline_row - global_stl_height(); + room = Rows - cmdline_row; if (curfr->fr_next == NULL) { room -= 1; } else { - room -= p_ch; + room -= p_ch + global_stl_height(); } if (room < 0) { room = 0; |