vim-patch:8.2.3430: no generic way to trigger an autocommand on mode change

Problem:    No generic way to trigger an autocommand on mode change.
Solution:   Add the ModeChanged autocommand event. (Magnus Gross, closes vim/vim#8856)
f1e8876fa2

N/A patches for version.c:

vim-patch:8.2.3434: function prototype for trigger_modechanged() is incomplete

Problem:    Function prototype for trigger_modechanged() is incomplete.
Solution:   Add "void".
28e591dd50

Fixes #4399.
Fixes #7416.
This commit is contained in:
Magnus Groß
2021-09-29 16:36:48 +02:00
parent 36538417f0
commit 69bd1e4e36
13 changed files with 101 additions and 8 deletions

View File

@@ -718,7 +718,22 @@ MenuPopup Just before showing the popup menu (under the
o Operator-pending o Operator-pending
i Insert i Insert
c Command line c Command line
*OptionSet* *ModeChanged*
ModeChanged After changing the mode. The pattern is
matched against `'old_mode:new_mode'`, for
example match against `i:*` to simulate
|InsertLeave|.
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()|.
When ModeChanged is triggered, old_mode will
have the value of new_mode when the event was
last triggered.
Usage example to use relative line numbers
when entering visual mode: >
:autocmd ModeChanged *:v set rnu
< *OptionSet*
OptionSet After setting an option (except during OptionSet After setting an option (except during
|startup|). The |autocmd-pattern| is matched |startup|). The |autocmd-pattern| is matched
against the long option name. |<amatch>| against the long option name. |<amatch>|

View File

@@ -69,6 +69,7 @@ return {
'InsertLeave', -- just after leaving Insert mode 'InsertLeave', -- just after leaving Insert mode
'InsertLeavePre', -- just before leaving Insert mode 'InsertLeavePre', -- just before leaving Insert mode
'MenuPopup', -- just before popup menu is displayed 'MenuPopup', -- just before popup menu is displayed
'ModeChanged', -- after changing the mode
'OptionSet', -- after setting any option 'OptionSet', -- after setting any option
'QuickFixCmdPost', -- after :make, :grep etc. 'QuickFixCmdPost', -- after :make, :grep etc.
'QuickFixCmdPre', -- before :make, :grep etc. 'QuickFixCmdPre', -- before :make, :grep etc.

View File

@@ -1440,7 +1440,7 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
// invalid. // invalid.
if (fname_io == NULL) { if (fname_io == NULL) {
if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
|| event == EVENT_OPTIONSET) { || event == EVENT_OPTIONSET || event == EVENT_MODECHANGED) {
autocmd_fname = NULL; autocmd_fname = NULL;
} else if (fname != NULL && !ends_excmd(*fname)) { } else if (fname != NULL && !ends_excmd(*fname)) {
autocmd_fname = fname; autocmd_fname = fname;
@@ -1494,11 +1494,12 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|| event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED || event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED
|| event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
|| event == EVENT_DIRCHANGED || event == EVENT_FILETYPE || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE
|| event == EVENT_FUNCUNDEFINED || event == EVENT_OPTIONSET || event == EVENT_FUNCUNDEFINED || event == EVENT_MODECHANGED
|| event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST
|| event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING || event == EVENT_QUICKFIXCMDPRE || event == EVENT_REMOTEREPLY
|| event == EVENT_SYNTAX || event == EVENT_SIGNAL || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX
|| event == EVENT_TABCLOSED || event == EVENT_WINCLOSED) { || event == EVENT_SIGNAL || event == EVENT_TABCLOSED
|| event == EVENT_WINCLOSED) {
fname = vim_strsave(fname); fname = vim_strsave(fname);
} else { } else {
fname = (char_u *)FullName_save((char *)fname, false); fname = (char_u *)FullName_save((char *)fname, false);

View File

@@ -385,6 +385,7 @@ static void insert_enter(InsertState *s)
State = INSERT; State = INSERT;
} }
trigger_modechanged();
stop_insert_mode = false; stop_insert_mode = false;
// Need to recompute the cursor position, it might move when the cursor is // Need to recompute the cursor position, it might move when the cursor is
@@ -7965,6 +7966,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
State = NORMAL; State = NORMAL;
trigger_modechanged();
// need to position cursor again (e.g. when on a TAB ) // need to position cursor again (e.g. when on a TAB )
changed_cline_bef_curs(); changed_cline_bef_curs();
@@ -8066,6 +8068,7 @@ static void ins_insert(int replaceState)
} else { } else {
State = replaceState | (State & LANGMAP); State = replaceState | (State & LANGMAP);
} }
trigger_modechanged();
AppendCharToRedobuff(K_INS); AppendCharToRedobuff(K_INS);
showmode(); showmode();
ui_cursor_shape(); // may show different cursor shape ui_cursor_shape(); // may show different cursor shape

View File

@@ -196,6 +196,7 @@ void do_exmode(void)
exmode_active = true; exmode_active = true;
State = NORMAL; State = NORMAL;
trigger_modechanged();
// When using ":global /pat/ visual" and then "Q" we return to continue // When using ":global /pat/ visual" and then "Q" we return to continue
// the :global command. // the :global command.

View File

@@ -906,6 +906,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
} }
tl_ret = true; tl_ret = true;
} }
trigger_modechanged();
state_enter(&s->state); state_enter(&s->state);
@@ -6547,6 +6548,7 @@ static int open_cmdwin(void)
cmdmsg_rl = save_cmdmsg_rl; cmdmsg_rl = save_cmdmsg_rl;
State = save_State; State = save_State;
trigger_modechanged();
setmouse(); setmouse();
return cmdwin_result; return cmdwin_result;

View File

@@ -727,6 +727,7 @@ EXTERN bool listcmd_busy INIT(= false); // set when :argdo, :windo or
// :bufdo is executing // :bufdo is executing
EXTERN bool need_start_insertmode INIT(= false); EXTERN bool need_start_insertmode INIT(= false);
// start insert mode soon // start insert mode soon
EXTERN char *last_mode INIT(= NULL);
EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":)
EXTERN char_u *repeat_cmdline INIT(= NULL); // 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 EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline

View File

@@ -632,6 +632,7 @@ void free_all_mem(void)
clear_sb_text(true); // free any scrollback text clear_sb_text(true); // free any scrollback text
// Free some global vars. // Free some global vars.
xfree(last_mode);
xfree(last_cmdline); xfree(last_cmdline);
xfree(new_last_cmdline); xfree(new_last_cmdline);
set_keep_msg(NULL, 0); set_keep_msg(NULL, 0);

View File

@@ -1059,3 +1059,32 @@ void add_time(char_u *buf, size_t buflen, time_t tt)
seconds); seconds);
} }
} }
/// Fires a ModeChanged autocmd.
void trigger_modechanged(void)
{
if (!has_event(EVENT_MODECHANGED)) {
return;
}
dict_T *v_event = get_vim_var_dict(VV_EVENT);
char *mode = get_mode();
if (last_mode == NULL) {
last_mode = (char *)vim_strsave((char_u *)"n");
}
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);
tv_dict_free_contents(v_event);
hash_init(&v_event->dv_hashtab);
}

View File

@@ -3050,6 +3050,7 @@ static int get_mouse_class(char_u *p)
void end_visual_mode(void) void end_visual_mode(void)
{ {
VIsual_active = false; VIsual_active = false;
trigger_modechanged();
setmouse(); setmouse();
mouse_dragging = 0; mouse_dragging = 0;
@@ -6680,6 +6681,7 @@ static void nv_visual(cmdarg_T *cap)
// or char/line mode // or char/line mode
VIsual_mode = cap->cmdchar; VIsual_mode = cap->cmdchar;
showmode(); showmode();
trigger_modechanged();
} }
redraw_curbuf_later(INVERTED); // update the inversion redraw_curbuf_later(INVERTED); // update the inversion
} else { // start Visual mode } else { // start Visual mode
@@ -6782,6 +6784,7 @@ static void n_start_visual_mode(int c)
VIsual_mode = c; VIsual_mode = c;
VIsual_active = true; VIsual_active = true;
VIsual_reselect = true; VIsual_reselect = true;
trigger_modechanged();
// Corner case: the 0 position in a tab may change when going into // Corner case: the 0 position in a tab may change when going into
// virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting. // virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting.
// //

View File

@@ -136,7 +136,7 @@ int get_real_state(void)
/// @returns[allocated] mode string /// @returns[allocated] mode string
char *get_mode(void) char *get_mode(void)
{ {
char *buf = xcalloc(4, sizeof(char)); char *buf = xcalloc(MODE_MAX_LENGTH, sizeof(char));
if (VIsual_active) { if (VIsual_active) {
if (VIsual_select) { if (VIsual_select) {

View File

@@ -1644,4 +1644,38 @@ func Test_read_invalid()
set encoding=utf-8 set encoding=utf-8
endfunc endfunc
" Test for ModeChanged pattern
func Test_mode_changes()
let g:count = 0
func! DoIt()
let g:count += 1
endfunc
let g:index = 0
let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'n', 'V', 'v', '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(), get(v:event, "new_mode"))
let g:index += 1
endfunc
au ModeChanged * :call TestMode()
au ModeChanged n:* :call DoIt()
call feedkeys("i\<esc>vV\<esc>", 'tnix')
call assert_equal(2, g:count)
au ModeChanged V:v :call DoIt()
call feedkeys("Vv\<esc>", 'tnix')
call assert_equal(4, g:count)
call assert_equal(len(g:mode_seq) - 1, g:index)
au! ModeChanged
delfunc TestMode
unlet! g:mode_seq
unlet! g:index
delfunc DoIt
unlet! g:count
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@@ -72,6 +72,8 @@ enum { NUMBUFLEN = 65, };
#define TERM_FOCUS 0x2000 // Terminal focus mode #define TERM_FOCUS 0x2000 // Terminal focus mode
#define CMDPREVIEW 0x4000 // Showing 'inccommand' command "live" preview. #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 // all mode bits used for mapping
#define MAP_ALL_MODES (0x3f | SELECTMODE | TERM_FOCUS) #define MAP_ALL_MODES (0x3f | SELECTMODE | TERM_FOCUS)