diff options
-rw-r--r-- | .github/workflows/backport.yml | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | runtime/doc/autocmd.txt | 22 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/handlers.lua | 4 | ||||
-rw-r--r-- | src/nvim/aucmd.c | 6 | ||||
-rw-r--r-- | src/nvim/auevents.lua | 1 | ||||
-rw-r--r-- | src/nvim/autocmd.c | 20 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 6 | ||||
-rw-r--r-- | src/nvim/change.c | 2 | ||||
-rw-r--r-- | src/nvim/channel.c | 6 | ||||
-rw-r--r-- | src/nvim/edit.c | 15 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 5 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 14 | ||||
-rw-r--r-- | src/nvim/file_search.c | 5 | ||||
-rw-r--r-- | src/nvim/fileio.c | 4 | ||||
-rw-r--r-- | src/nvim/globals.h | 1 | ||||
-rw-r--r-- | src/nvim/memory.c | 1 | ||||
-rw-r--r-- | src/nvim/misc1.c | 55 | ||||
-rw-r--r-- | src/nvim/normal.c | 8 | ||||
-rw-r--r-- | src/nvim/ops.c | 5 | ||||
-rw-r--r-- | src/nvim/search.c | 3 | ||||
-rw-r--r-- | src/nvim/sign.c | 2 | ||||
-rw-r--r-- | src/nvim/state.c | 2 | ||||
-rw-r--r-- | src/nvim/syntax.c | 2 | ||||
-rw-r--r-- | src/nvim/terminal.c | 6 | ||||
-rw-r--r-- | src/nvim/testdir/test_autocmd.vim | 35 | ||||
-rw-r--r-- | src/nvim/testdir/test_edit.vim | 91 | ||||
-rw-r--r-- | src/nvim/vim.h | 2 | ||||
-rw-r--r-- | src/nvim/window.c | 3 | ||||
-rw-r--r-- | test/functional/autocmd/modechanged_spec.lua | 31 |
30 files changed, 324 insertions, 39 deletions
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 25f8414008..d281bb1404 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -12,7 +12,7 @@ jobs: name: Backport Pull Request if: > github.repository_owner == 'neovim' && ( - github.event_name == 'pull_request' && + github.event_name == 'pull_request_target' && github.event.pull_request.merged ) || ( github.event_name == 'issue_comment' && diff --git a/CMakeLists.txt b/CMakeLists.txt index d71f5c6ceb..87223b9d4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,9 +136,9 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY # If not in a git repo (e.g., a tarball) these tokens define the complete # version string, else they are combined with the result of `git describe`. set(NVIM_VERSION_MAJOR 0) -set(NVIM_VERSION_MINOR 6) +set(NVIM_VERSION_MINOR 7) set(NVIM_VERSION_PATCH 0) -set(NVIM_VERSION_PRERELEASE "") # for package maintainers +set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level set(NVIM_API_LEVEL 8) # Bump this after any API change. diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 5d9f9590e2..879a34bcaa 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -718,7 +718,27 @@ MenuPopup Just before showing the popup menu (under the o Operator-pending i Insert c Command line - *OptionSet* + *ModeChanged* +ModeChanged After changing the mode. The pattern is + matched against `'old_mode:new_mode'`, for + example match against `*:c` to simulate + |CmdlineEnter|. + The following values of |v:event| are set: + old_mode The mode before it changed. + new_mode The new mode as also returned + by |mode()| called with a + non-zero argument. + When ModeChanged is triggered, old_mode will + have the value of new_mode when the event was + last triggered. + This will be triggered on every minor mode + change. + Usage example to use relative line numbers + when entering visual mode: > + :au ModeChanged [vV\x16]*:* let &l:rnu = mode() =~# '^[vV\x16]' + :au ModeChanged *:[vV\x16]* let &l:rnu = mode() =~# '^[vV\x16]' + :au WinEnter,WinLeave * let &l:rnu = mode() =~# '^[vV\x16]' +< *OptionSet* OptionSet After setting an option (except during |startup|). The |autocmd-pattern| is matched against the long option name. |<amatch>| diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index ebf504fd42..22089aaaa6 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -28,7 +28,7 @@ local function progress_handler(_, result, ctx, _) local client_name = client and client.name or string.format("id=%d", client_id) if not client then err_message("LSP[", client_name, "] client has shut down after sending the message") - return + return vim.NIL end local val = result.value -- unspecified yet local token = result.token -- string or number @@ -70,7 +70,7 @@ M['window/workDoneProgress/create'] = function(_, result, ctx) local client_name = client and client.name or string.format("id=%d", client_id) if not client then err_message("LSP[", client_name, "] client has shut down after sending the message") - return + return vim.NIL end client.messages.progress[token] = {} return vim.NIL diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c index af519dcba9..a236b47027 100644 --- a/src/nvim/aucmd.c +++ b/src/nvim/aucmd.c @@ -8,6 +8,7 @@ #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/main.h" +#include "nvim/misc1.h" #include "nvim/os/os.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -25,13 +26,14 @@ void do_autocmd_uienter(uint64_t chanid, bool attached) } recursive = true; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); assert(chanid < VARNUMBER_MAX); tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid); tv_dict_set_keys_readonly(dict); apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE, NULL, NULL, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); recursive = false; } diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 7e7114b291..1daae85c5e 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -70,6 +70,7 @@ return { 'InsertLeave', -- just after leaving Insert mode 'InsertLeavePre', -- just before leaving Insert mode 'MenuPopup', -- just before popup menu is displayed + 'ModeChanged', -- after changing the mode 'OptionSet', -- after setting any option 'QuickFixCmdPost', -- after :make, :grep etc. 'QuickFixCmdPre', -- before :make, :grep etc. diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 2d0c0f3fd5..490fe5a0ac 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -925,6 +925,13 @@ static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, c return FAIL; } } + + // need to initialize last_mode for the first ModeChanged autocmd + if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) { + xfree(last_mode); + last_mode = get_mode(); + } + ap->cmds = NULL; *prev_ap = ap; last_autopat[(int)event] = ap; @@ -1440,7 +1447,7 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, // invalid. if (fname_io == NULL) { if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE - || event == EVENT_OPTIONSET) { + || event == EVENT_OPTIONSET || event == EVENT_MODECHANGED) { autocmd_fname = NULL; } else if (fname != NULL && !ends_excmd(*fname)) { autocmd_fname = fname; @@ -1494,11 +1501,12 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, || event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE - || event == EVENT_FUNCUNDEFINED || event == EVENT_OPTIONSET - || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE - || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING - || event == EVENT_SYNTAX || event == EVENT_SIGNAL - || event == EVENT_TABCLOSED || event == EVENT_WINCLOSED) { + || event == EVENT_FUNCUNDEFINED || event == EVENT_MODECHANGED + || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST + || event == EVENT_QUICKFIXCMDPRE || event == EVENT_REMOTEREPLY + || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX + || event == EVENT_SIGNAL || event == EVENT_TABCLOSED + || event == EVENT_WINCLOSED) { fname = vim_strsave(fname); } else { fname = (char_u *)FullName_save((char *)fname, false); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 49e527e98b..e53b2d1dfa 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1116,6 +1116,12 @@ typedef struct { pos_T w_cursor_corr; // corrected cursor position } pos_save_T; +// Struct passed to get_v_event() and restore_v_event(). +typedef struct { + bool sve_did_save; + hashtab_T sve_hashtab; +} save_v_event_T; + /// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode /// \addtogroup MENU_INDEX /// @{ diff --git a/src/nvim/change.c b/src/nvim/change.c index c52d992fbe..ef771125f1 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -625,7 +625,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } } - char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen)); + char_u *newp = xmalloc(linelen + newlen - oldlen); // Copy bytes before the cursor. if (col > 0) { diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 9662f6205f..a662f3a951 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -10,6 +10,7 @@ #include "nvim/event/socket.h" #include "nvim/fileio.h" #include "nvim/lua/executor.h" +#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/shell.h" @@ -821,7 +822,8 @@ static void set_info_event(void **argv) Channel *chan = argv[0]; event_T event = (event_T)(ptrdiff_t)argv[1]; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); Dictionary info = channel_info(chan->id); typval_T retval; (void)object_to_vim(DICTIONARY_OBJ(info), &retval, NULL); @@ -829,7 +831,7 @@ static void set_info_event(void **argv) apply_autocmds(event, NULL, NULL, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); api_free_dictionary(info); channel_decref(chan); } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index d505b40935..f171897606 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -385,6 +385,7 @@ static void insert_enter(InsertState *s) State = INSERT; } + trigger_modechanged(); stop_insert_mode = false; // Need to recompute the cursor position, it might move when the cursor is @@ -2048,6 +2049,8 @@ static void ins_ctrl_x(void) // CTRL-V look like CTRL-N ctrl_x_mode = CTRL_X_CMDLINE_CTRL_X; } + + trigger_modechanged(); } // Whether other than default completion has been selected. @@ -2660,6 +2663,7 @@ void set_completion(colnr_T startcol, list_T *list) show_pum(save_w_wrow, save_w_leftcol); } + trigger_modechanged(); ui_flush(); } @@ -2715,12 +2719,13 @@ static bool pum_enough_matches(void) static void trigger_complete_changed_event(int cur) { static bool recursive = false; + save_v_event_T save_v_event; if (recursive) { return; } - dict_T *v_event = get_vim_var_dict(VV_EVENT); + dict_T *v_event = get_v_event(&save_v_event); if (cur < 0) { tv_dict_add_dict(v_event, S_LEN("completed_item"), tv_dict_alloc()); } else { @@ -2736,7 +2741,7 @@ static void trigger_complete_changed_event(int cur) textlock--; recursive = false; - tv_dict_clear(v_event); + restore_v_event(v_event, &save_v_event); } /// Show the popup menu for the list of matches. @@ -3838,6 +3843,8 @@ static bool ins_compl_prep(int c) ins_apply_autocmds(EVENT_COMPLETEDONE); } + trigger_modechanged(); + /* reset continue_* if we left expansion-mode, if we stay they'll be * (re)set properly in ins_complete() */ if (!vim_is_ctrl_x_key(c)) { @@ -4586,6 +4593,8 @@ static int ins_compl_get_exp(pos_T *ini) compl_curr_match = compl_old_match; } } + trigger_modechanged(); + return i; } @@ -7965,6 +7974,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) State = NORMAL; + trigger_modechanged(); // need to position cursor again (e.g. when on a TAB ) changed_cline_bef_curs(); @@ -8066,6 +8076,7 @@ static void ins_insert(int replaceState) } else { State = replaceState | (State & LANGMAP); } + trigger_modechanged(); AppendCharToRedobuff(K_INS); showmode(); ui_cursor_shape(); // may show different cursor shape diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 62919c98f7..024eb0a904 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -196,6 +196,7 @@ void do_exmode(void) exmode_active = true; State = NORMAL; + trigger_modechanged(); // When using ":global /pat/ visual" and then "Q" we return to continue // the :global command. @@ -818,7 +819,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) // of interrupts or errors to exceptions, and ensure that no more // commands are executed. if (current_exception) { - void *p = NULL; + char *p = NULL; char_u *saved_sourcing_name; int saved_sourcing_lnum; struct msglist *messages = NULL; @@ -835,7 +836,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) vim_snprintf((char *)IObuff, IOSIZE, _("E605: Exception not caught: %s"), current_exception->value); - p = vim_strsave(IObuff); + p = (char *)vim_strsave(IObuff); break; case ET_ERROR: messages = current_exception->messages; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 3b5953ae69..9cf39802de 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -880,7 +880,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) TryState tstate; Error err = ERROR_INIT; bool tl_ret = true; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; firstcbuf[0] = (char)(firstc > 0 ? firstc : '-'); firstcbuf[1] = 0; @@ -894,7 +895,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) apply_autocmds(EVENT_CMDLINEENTER, (char_u *)firstcbuf, (char_u *)firstcbuf, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); tl_ret = try_leave(&tstate, &err); @@ -906,6 +907,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } tl_ret = true; } + trigger_modechanged(); state_enter(&s->state); @@ -924,7 +926,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) if (tv_dict_get_number(dict, "abort") != 0) { s->gotesc = 1; } - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); } cmdmsg_rl = false; @@ -2289,7 +2291,8 @@ static int command_line_changed(CommandLineState *s) if (has_event(EVENT_CMDLINECHANGED)) { TryState tstate; Error err = ERROR_INIT; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-'); @@ -2303,7 +2306,7 @@ static int command_line_changed(CommandLineState *s) apply_autocmds(EVENT_CMDLINECHANGED, (char_u *)firstcbuf, (char_u *)firstcbuf, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); bool tl_ret = try_leave(&tstate, &err); if (!tl_ret && ERROR_SET(&err)) { @@ -6547,6 +6550,7 @@ static int open_cmdwin(void) cmdmsg_rl = save_cmdmsg_rl; State = save_State; + trigger_modechanged(); setmouse(); return cmdwin_result; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 3a9acbd61c..5953a574f3 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1603,7 +1603,8 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) recursive = true; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); char buf[8]; switch (scope) { @@ -1648,7 +1649,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); recursive = false; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 1b39a7410a..c90115d796 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -100,8 +100,8 @@ struct bw_info { char_u bw_rest[CONV_RESTLEN]; // not converted bytes int bw_restlen; // nr of bytes in bw_rest[] int bw_first; // first write call - char_u *bw_conv_buf; // buffer for writing converted chars - int bw_conv_buflen; // size of bw_conv_buf + char_u *bw_conv_buf; // buffer for writing converted chars + size_t bw_conv_buflen; // size of bw_conv_buf int bw_conv_error; // set for conversion error linenr_T bw_conv_error_lnum; // first line with error or zero linenr_T bw_start_lnum; // line number at start of buffer diff --git a/src/nvim/globals.h b/src/nvim/globals.h index b5e1fda9f1..7ae7b65702 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -727,6 +727,7 @@ EXTERN bool listcmd_busy INIT(= false); // set when :argdo, :windo or // :bufdo is executing EXTERN bool need_start_insertmode INIT(= false); // start insert mode soon +EXTERN char *last_mode INIT(= NULL); EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) EXTERN char_u *repeat_cmdline INIT(= NULL); // command line for "." EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 559a3cc435..3d621ebbb7 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -631,6 +631,7 @@ void free_all_mem(void) clear_sb_text(true); // free any scrollback text // Free some global vars. + xfree(last_mode); xfree(last_cmdline); xfree(new_last_cmdline); set_keep_msg(NULL, 0); diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index fd5d154cea..872a2c58e3 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -1059,3 +1059,58 @@ void add_time(char_u *buf, size_t buflen, time_t tt) seconds); } } + +dict_T *get_v_event(save_v_event_T *sve) +{ + dict_T *v_event = get_vim_var_dict(VV_EVENT); + + if (v_event->dv_hashtab.ht_used > 0) { + // recursive use of v:event, save, make empty and restore later + sve->sve_did_save = true; + sve->sve_hashtab = v_event->dv_hashtab; + hash_init(&v_event->dv_hashtab); + } else { + sve->sve_did_save = false; + } + return v_event; +} + +void restore_v_event(dict_T *v_event, save_v_event_T *sve) +{ + tv_dict_free_contents(v_event); + if (sve->sve_did_save) { + v_event->dv_hashtab = sve->sve_hashtab; + } else { + hash_init(&v_event->dv_hashtab); + } +} + +/// Fires a ModeChanged autocmd. +void trigger_modechanged(void) +{ + if (!has_event(EVENT_MODECHANGED)) { + return; + } + + char *mode = get_mode(); + if (STRCMP(mode, last_mode) == 0) { + xfree(mode); + return; + } + + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + tv_dict_add_str(v_event, S_LEN("new_mode"), mode); + tv_dict_add_str(v_event, S_LEN("old_mode"), last_mode); + + char_u *pat_pre = concat_str((char_u *)last_mode, (char_u *)":"); + char_u *pat = concat_str(pat_pre, (char_u *)mode); + xfree(pat_pre); + + apply_autocmds(EVENT_MODECHANGED, pat, NULL, false, curbuf); + xfree(last_mode); + last_mode = mode; + + xfree(pat); + restore_v_event(v_event, &save_v_event); +} diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 03312e5df4..f2b272a13f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -487,6 +487,7 @@ static void normal_prepare(NormalState *s) if (finish_op != c) { ui_cursor_shape(); // may show different cursor shape } + trigger_modechanged(); // When not finishing an operator and no register name typed, reset the count. if (!finish_op && !s->oa.regname) { @@ -928,6 +929,7 @@ normal_end: // Reset finish_op, in case it was set s->c = finish_op; finish_op = false; + trigger_modechanged(); // Redraw the cursor with another shape, if we were in Operator-pending // mode or did a replace command. if (s->c || s->ca.cmdchar == 'r') { @@ -965,6 +967,7 @@ normal_end: && s->oa.regname == 0) { if (restart_VIsual_select == 1) { VIsual_select = true; + trigger_modechanged(); showmode(); restart_VIsual_select = 0; } @@ -3066,6 +3069,7 @@ void end_visual_mode(void) may_clear_cmdline(); adjust_cursor_eol(); + trigger_modechanged(); } /* @@ -4851,6 +4855,7 @@ static void nv_ctrlg(cmdarg_T *cap) { if (VIsual_active) { // toggle Selection/Visual mode VIsual_select = !VIsual_select; + trigger_modechanged(); showmode(); } else if (!checkclearop(cap->oap)) { // print full name if count given or :cd used @@ -4894,6 +4899,7 @@ static void nv_ctrlo(cmdarg_T *cap) { if (VIsual_active && VIsual_select) { VIsual_select = false; + trigger_modechanged(); showmode(); restart_VIsual_select = 2; // restart Select mode later } else { @@ -6680,6 +6686,7 @@ static void nv_visual(cmdarg_T *cap) // or char/line mode VIsual_mode = cap->cmdchar; showmode(); + trigger_modechanged(); } redraw_curbuf_later(INVERTED); // update the inversion } else { // start Visual mode @@ -6793,6 +6800,7 @@ static void n_start_visual_mode(int c) foldAdjustVisual(); + trigger_modechanged(); setmouse(); // Check for redraw after changing the state. conceal_check_cursor_line(); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 09979c4ef5..38de24b8ad 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2809,8 +2809,9 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) recursive = true; + save_v_event_T save_v_event; // Set the v:event dictionary with information about the yank. - dict_T *dict = get_vim_var_dict(VV_EVENT); + dict_T *dict = get_v_event(&save_v_event); // The yanked text contents. list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); @@ -2847,7 +2848,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) textlock++; apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf); textlock--; - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); recursive = false; } diff --git a/src/nvim/search.c b/src/nvim/search.c index 2e45a8f509..f47315705c 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -5248,6 +5248,9 @@ search_line: if (depth == -1) { // match in current file if (l_g_do_tagpreview != 0) { + if (!win_valid(curwin_save)) { + break; + } if (!GETFILE_SUCCESS(getfile(curwin_save->w_buffer->b_fnum, NULL, NULL, true, lnum, false))) { break; // failed to jump to file diff --git a/src/nvim/sign.c b/src/nvim/sign.c index d8eea7f942..fca73dc9f2 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -80,7 +80,7 @@ static signgroup_T *sign_group_ref(const char_u *groupname) hi = hash_lookup(&sg_table, (char *)groupname, STRLEN(groupname), hash); if (HASHITEM_EMPTY(hi)) { // new group - group = xmalloc((unsigned)(sizeof(signgroup_T) + STRLEN(groupname))); + group = xmalloc(sizeof(signgroup_T) + STRLEN(groupname)); STRCPY(group->sg_name, groupname); group->sg_refcount = 1; diff --git a/src/nvim/state.c b/src/nvim/state.c index 4eb0073873..71db25664f 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -136,7 +136,7 @@ int get_real_state(void) /// @returns[allocated] mode string char *get_mode(void) { - char *buf = xcalloc(4, sizeof(char)); + char *buf = xcalloc(MODE_MAX_LENGTH, sizeof(char)); if (VIsual_active) { if (VIsual_select) { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index dd3f1b4dc9..49f8bbab37 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5401,7 +5401,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis do { for (end = p; *end && !ascii_iswhite(*end) && *end != ','; end++) { } - char_u *const name = xmalloc((int)(end - p + 3)); // leave room for "^$" + char_u *const name = xmalloc(end - p + 3); // leave room for "^$" STRLCPY(name + 1, p, end - p + 1); if (STRCMP(name + 1, "ALLBUT") == 0 || STRCMP(name + 1, "ALL") == 0 diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 35c68fa1f6..83ade74db1 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -324,10 +324,11 @@ void terminal_close(Terminal *term, int status) } if (buf && !is_autocmd_blocked()) { - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); tv_dict_add_nr(dict, S_LEN("status"), status); apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, buf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); } } @@ -412,6 +413,7 @@ void terminal_enter(void) curwin->w_redr_status = true; // For mode() in statusline. #8323 ui_busy_start(); apply_autocmds(EVENT_TERMENTER, NULL, NULL, false, curbuf); + trigger_modechanged(); s->state.execute = terminal_execute; s->state.check = terminal_check; diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 0c8b8a45d9..49d56349a5 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -33,7 +33,7 @@ if has('timers') let g:triggered = 0 au CursorHoldI * let g:triggered += 1 set updatetime=20 - call timer_start(LoadAdjust(100), 'ExitInsertMode') + call timer_start(LoadAdjust(200), 'ExitInsertMode') call feedkeys('a', 'x!') call assert_equal(1, g:triggered) unlet g:triggered @@ -1897,6 +1897,26 @@ func Test_autocmd_CmdWinEnter() call delete(filename) endfunc +func Test_autocmd_was_using_freed_memory() + pedit xx + n x + augroup winenter + au WinEnter * if winnr('$') > 2 | quit | endif + augroup END + " Nvim needs large 'winwidth' and 'nowinfixwidth' to crash + set winwidth=99999 nowinfixwidth + split + + augroup winenter + au! WinEnter + augroup END + + set winwidth& winfixwidth& + bwipe xx + bwipe x + pclose +endfunc + func Test_FileChangedShell_reload() if !has('unix') return @@ -2125,6 +2145,19 @@ func Test_autocmd_closes_window() au! BufWinLeave endfunc +func Test_autocmd_quit_psearch() + sn aa bb + augroup aucmd_win_test + au! + au BufEnter,BufLeave,BufNew,WinEnter,WinLeave,WinNew * if winnr('$') > 1 | q | endif + augroup END + ps / + + augroup aucmd_win_test + au! + augroup END +endfunc + func Test_autocmd_closing_cmdwin() au BufWinLeave * nested q call assert_fails("norm 7q?\n", 'E855:') diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 23ad8dbfc5..37786f3ca0 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -1644,4 +1644,95 @@ func Test_read_invalid() set encoding=utf-8 endfunc +" Test for ModeChanged pattern +func Test_mode_changes() + let g:index = 0 + let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'i', 'ix', 'i', 'ic', 'i', 'n', 'no', 'n', 'V', 'v', 's', 'n'] + func! TestMode() + call assert_equal(g:mode_seq[g:index], get(v:event, "old_mode")) + call assert_equal(g:mode_seq[g:index + 1], get(v:event, "new_mode")) + call assert_equal(mode(1), get(v:event, "new_mode")) + let g:index += 1 + endfunc + + au ModeChanged * :call TestMode() + let g:n_to_any = 0 + au ModeChanged n:* let g:n_to_any += 1 + call feedkeys("i\<esc>vVca\<CR>\<C-X>\<C-L>\<esc>ggdG", 'tnix') + + let g:V_to_v = 0 + au ModeChanged V:v let g:V_to_v += 1 + call feedkeys("Vv\<C-G>\<esc>", 'tnix') + call assert_equal(len(filter(g:mode_seq[1:], {idx, val -> val == 'n'})), g:n_to_any) + call assert_equal(1, g:V_to_v) + call assert_equal(len(g:mode_seq) - 1, g:index) + + let g:n_to_i = 0 + au ModeChanged n:i let g:n_to_i += 1 + let g:n_to_niI = 0 + au ModeChanged i:niI let g:n_to_niI += 1 + let g:niI_to_i = 0 + au ModeChanged niI:i let g:niI_to_i += 1 + let g:nany_to_i = 0 + au ModeChanged n*:i let g:nany_to_i += 1 + let g:i_to_n = 0 + au ModeChanged i:n let g:i_to_n += 1 + let g:nori_to_any = 0 + au ModeChanged [ni]:* let g:nori_to_any += 1 + let g:i_to_any = 0 + au ModeChanged i:* let g:i_to_any += 1 + let g:index = 0 + let g:mode_seq = ['n', 'i', 'niI', 'i', 'n'] + call feedkeys("a\<C-O>l\<esc>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(1, g:n_to_i) + call assert_equal(1, g:n_to_niI) + call assert_equal(1, g:niI_to_i) + call assert_equal(2, g:nany_to_i) + call assert_equal(1, g:i_to_n) + call assert_equal(2, g:i_to_any) + call assert_equal(3, g:nori_to_any) + + if has('terminal') + let g:mode_seq += ['c', 'n', 't', 'nt', 'c', 'nt', 'n'] + call feedkeys(":term\<CR>\<C-W>N:bd!\<CR>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(1, g:n_to_i) + call assert_equal(1, g:n_to_niI) + call assert_equal(1, g:niI_to_i) + call assert_equal(2, g:nany_to_i) + call assert_equal(1, g:i_to_n) + call assert_equal(2, g:i_to_any) + call assert_equal(5, g:nori_to_any) + endif + + au! ModeChanged + delfunc TestMode + unlet! g:mode_seq + unlet! g:index + unlet! g:n_to_any + unlet! g:V_to_v + unlet! g:n_to_i + unlet! g:n_to_niI + unlet! g:niI_to_i + unlet! g:nany_to_i + unlet! g:i_to_n + unlet! g:nori_to_any + unlet! g:i_to_any +endfunc + +func Test_recursive_ModeChanged() + au! ModeChanged * norm 0u + sil! norm + au! +endfunc + +func Test_ModeChanged_starts_visual() + " This was triggering ModeChanged before setting VIsual, causing a crash. + au! ModeChanged * norm 0u + sil! norm + + au! ModeChanged +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 726670f082..e3539c1a57 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -72,6 +72,8 @@ enum { NUMBUFLEN = 65, }; #define TERM_FOCUS 0x2000 // Terminal focus mode #define CMDPREVIEW 0x4000 // Showing 'inccommand' command "live" preview. +#define MODE_MAX_LENGTH 4 // max mode length returned in mode() + // all mode bits used for mapping #define MAP_ALL_MODES (0x3f | SELECTMODE | TERM_FOCUS) diff --git a/src/nvim/window.c b/src/nvim/window.c index e328ff5467..3e6e42dec2 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4525,6 +4525,7 @@ static void win_enter_ext(win_T *const wp, const int flags) fix_current_dir(); + // Careful: autocommands may close the window and make "wp" invalid if (flags & WEE_TRIGGER_NEW_AUTOCMDS) { apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); } @@ -4558,7 +4559,7 @@ static void win_enter_ext(win_T *const wp, const int flags) } // set window width to desired minimal value - if (curwin->w_width < p_wiw && !curwin->w_p_wfw && !wp->w_floating) { + if (curwin->w_width < p_wiw && !curwin->w_p_wfw && !curwin->w_floating) { win_setwidth((int)p_wiw); } diff --git a/test/functional/autocmd/modechanged_spec.lua b/test/functional/autocmd/modechanged_spec.lua new file mode 100644 index 0000000000..be5a291ac9 --- /dev/null +++ b/test/functional/autocmd/modechanged_spec.lua @@ -0,0 +1,31 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq +local feed, command = helpers.feed, helpers.command + +describe('ModeChanged', function() + before_each(function() + clear() + command('let g:count = 0') + command('au ModeChanged * let g:event = copy(v:event)') + command('au ModeChanged * let g:count += 1') + end) + + it('picks up terminal mode changes', function() + command("term") + feed('i') + eq({ + old_mode = 'nt', + new_mode = 't' + }, eval('g:event')) + feed('<c-\\><c-n>') + eq({ + old_mode = 't', + new_mode = 'nt' + }, eval('g:event')) + eq(3, eval('g:count')) + command("bd!") + + -- v:event is cleared after the autocommand is done + eq({}, eval('v:event')) + end) +end) |