Patch 8.2.3430
Problem: No generic way to trigger an autocommand on mode change.
Solution: Add the ModeChanged autocommand event. (Magnus Gross, closes #8856)
Files: runtime/doc/autocmd.txt, src/autocmd.c, src/edit.c,
src/ex_docmd.c, src/ex_getln.c, src/globals.h, src/misc1.c,
src/normal.c, src/proto/
autocmd.pro, src/proto/
misc1.pro,
src/testdir/test_edit.vim, src/vim.h
*** ../vim-8.2.3429/runtime/doc/autocmd.txt 2021-08-01 14:52:05.554645412 +0200
--- runtime/doc/autocmd.txt 2021-09-12 13:30:35.724067946 +0200
***************
*** 54,60 ****
:au[tocmd] [group] {event} {pat} [++once] [++nested] {cmd}
Add {cmd} to the list of commands that Vim will
execute automatically on {event} for a file matching
! {pat} |autocmd-patterns|.
Here {event} cannot be "*". *E1155*
Note: A quote character is seen as argument to the
:autocmd and won't start a comment.
--- 54,60 ----
:au[tocmd] [group] {event} {pat} [++once] [++nested] {cmd}
Add {cmd} to the list of commands that Vim will
execute automatically on {event} for a file matching
! {pat} |autocmd-patterns|.
Here {event} cannot be "*". *E1155*
Note: A quote character is seen as argument to the
:autocmd and won't start a comment.
***************
*** 76,82 ****
script. Thus this depends on where the autocmd is defined, not where it is
triggered.
! {cmd} can use a block, like with `:command`, see |:command-repl|. Example: >
au BufReadPost *.xml {
setlocal matchpairs+=<:>
/<start
--- 76,82 ----
script. Thus this depends on where the autocmd is defined, not where it is
triggered.
! {cmd} can be a block, like with `:command`, see |:command-repl|. Example: >
au BufReadPost *.xml {
setlocal matchpairs+=<:>
/<start
***************
*** 366,371 ****
--- 366,373 ----
|InsertCharPre| when a character was typed in Insert mode, before
inserting it
+ |ModeChanged| after changing the mode
+
|TextChanged| after a change was made to the text in Normal mode
|TextChangedI| after a change was made to the text in Insert mode
when popup menu is not visible
***************
*** 922,928 ****
i Insert
c Command line
tl Terminal
! *OptionSet*
OptionSet After setting an option. The pattern is
matched against the long option name.
|<amatch>| indicates what option has been set.
--- 927,948 ----
i Insert
c Command line
tl Terminal
! *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 relativenumber
! < *OptionSet*
OptionSet After setting an option. The pattern is
matched against the long option name.
|<amatch>| indicates what option has been set.
*** ../vim-8.2.3429/src/autocmd.c 2021-08-07 13:59:38.298919436 +0200
--- src/autocmd.c 2021-09-12 13:30:35.728067940 +0200
***************
*** 150,155 ****
--- 150,156 ----
{"InsertLeavePre", EVENT_INSERTLEAVEPRE},
{"InsertCharPre", EVENT_INSERTCHARPRE},
{"MenuPopup", EVENT_MENUPOPUP},
+ {"ModeChanged", EVENT_MODECHANGED},
{"OptionSet", EVENT_OPTIONSET},
{"QuickFixCmdPost", EVENT_QUICKFIXCMDPOST},
{"QuickFixCmdPre", EVENT_QUICKFIXCMDPRE},
***************
*** 1817,1822 ****
--- 1818,1834 ----
}
#endif
+ #if defined(FEAT_EVAL) || defined(PROTO)
+ /*
+ * Return TRUE when there is a ModeChanged autocommand defined.
+ */
+ int
+ has_modechanged(void)
+ {
+ return (first_autopat[(int)EVENT_MODECHANGED] != NULL);
+ }
+ #endif
+
/*
* Execute autocommands for "event" and file name "fname".
* Return TRUE if some commands were executed.
***************
*** 1938,1944 ****
if (fname_io == NULL)
{
if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
! || event == EVENT_OPTIONSET)
autocmd_fname = NULL;
else if (fname != NULL && !ends_excmd(*fname))
autocmd_fname = fname;
--- 1950,1957 ----
if (fname_io == NULL)
{
if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
! || event == EVENT_OPTIONSET
! || event == EVENT_MODECHANGED)
autocmd_fname = NULL;
else if (fname != NULL && !ends_excmd(*fname))
autocmd_fname = fname;
***************
*** 2011,2017 ****
|| event == EVENT_COLORSCHEMEPRE
|| event == EVENT_OPTIONSET
|| event == EVENT_QUICKFIXCMDPOST
! || event == EVENT_DIRCHANGED)
{
fname = vim_strsave(fname);
autocmd_fname_full = TRUE; // don't expand it later
--- 2024,2031 ----
|| event == EVENT_COLORSCHEMEPRE
|| event == EVENT_OPTIONSET
|| event == EVENT_QUICKFIXCMDPOST
! || event == EVENT_DIRCHANGED
! || event == EVENT_MODECHANGED)
{
fname = vim_strsave(fname);
autocmd_fname_full = TRUE; // don't expand it later
*** ../vim-8.2.3429/src/edit.c 2021-08-09 19:59:01.438811254 +0200
--- src/edit.c 2021-09-12 13:30:35.728067940 +0200
***************
*** 284,289 ****
--- 284,290 ----
else
State = INSERT;
+ trigger_modechanged();
stop_insert_mode = FALSE;
#ifdef FEAT_CONCEAL
***************
*** 3681,3686 ****
--- 3682,3688 ----
#endif
State = NORMAL;
+ trigger_modechanged();
// need to position cursor again (e.g. when on a TAB )
changed_cline_bef_curs();
***************
*** 3811,3816 ****
--- 3813,3819 ----
State = INSERT | (State & LANGMAP);
else
State = replaceState | (State & LANGMAP);
+ trigger_modechanged();
AppendCharToRedobuff(K_INS);
showmode();
#ifdef CURSOR_SHAPE
*** ../vim-8.2.3429/src/ex_docmd.c 2021-09-08 14:29:43.117509762 +0200
--- src/ex_docmd.c 2021-09-12 13:30:35.728067940 +0200
***************
*** 485,490 ****
--- 485,491 ----
else
exmode_active = EXMODE_NORMAL;
State = NORMAL;
+ trigger_modechanged();
// When using ":global /pat/ visual" and then "Q" we return to continue
// the :global command.
*** ../vim-8.2.3429/src/ex_getln.c 2021-07-27 22:00:39.745712396 +0200
--- src/ex_getln.c 2021-09-12 13:30:35.728067940 +0200
***************
*** 1696,1701 ****
--- 1696,1705 ----
// Trigger CmdlineEnter autocommands.
cmdline_type = firstc == NUL ? '-' : firstc;
trigger_cmd_autocmd(cmdline_type, EVENT_CMDLINEENTER);
+ #ifdef FEAT_EVAL
+ if (!debug_mode)
+ trigger_modechanged();
+ #endif
init_history();
hiscnt = get_hislen(); // set hiscnt to impossible history value
***************
*** 2461,2466 ****
--- 2465,2476 ----
trigger_cmd_autocmd(cmdline_type, EVENT_CMDLINELEAVE);
State = save_State;
+
+ #ifdef FEAT_EVAL
+ if (!debug_mode)
+ trigger_modechanged();
+ #endif
+
#ifdef HAVE_INPUT_METHOD
if (b_im_ptr != NULL && *b_im_ptr != B_IMODE_LMAP)
im_save_status(b_im_ptr);
*** ../vim-8.2.3429/src/globals.h 2021-08-28 14:42:20.044971678 +0200
--- src/globals.h 2021-09-12 13:30:35.728067940 +0200
***************
*** 1256,1261 ****
--- 1256,1264 ----
// :bufdo is executing
EXTERN int need_start_insertmode INIT(= FALSE);
// start insert mode soon
+ #if defined(FEAT_EVAL) || defined(PROTO)
+ EXTERN char_u last_mode[MODE_MAX_LENGTH] INIT(= "n"); // for ModeChanged event
+ #endif
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
*** ../vim-8.2.3429/src/misc1.c 2021-07-28 16:51:49.857364325 +0200
--- src/misc1.c 2021-09-12 13:32:56.219902210 +0200
***************
*** 630,636 ****
void
f_mode(typval_T *argvars, typval_T *rettv)
{
! char_u buf[4];
if (in_vim9script() && check_for_opt_bool_arg(argvars, 0) == FAIL)
return;
--- 630,636 ----
void
f_mode(typval_T *argvars, typval_T *rettv)
{
! char_u buf[MODE_MAX_LENGTH];
if (in_vim9script() && check_for_opt_bool_arg(argvars, 0) == FAIL)
return;
***************
*** 2643,2645 ****
--- 2643,2684 ----
// "://" or ":\\" must follow
return path_is_url(p);
}
+
+ /*
+ * Fires a ModeChanged autocmd
+ */
+ void
+ trigger_modechanged()
+ {
+ #if defined(FEAT_EVAL) || defined(PROTO)
+ dict_T *v_event;
+ typval_T rettv;
+ typval_T tv;
+ char_u *pat_pre;
+ char_u *pat;
+
+ if (!has_modechanged())
+ return;
+
+ v_event = get_vim_var_dict(VV_EVENT);
+
+ tv.v_type = VAR_UNKNOWN;
+ f_mode(&tv, &rettv);
+ (void)dict_add_string(v_event, "new_mode", rettv.vval.v_string);
+ (void)dict_add_string(v_event, "old_mode", last_mode);
+ dict_set_items_ro(v_event);
+
+ // concatenate modes in format "old_mode:new_mode"
+ pat_pre = concat_str(last_mode, (char_u*)":");
+ pat = concat_str(pat_pre, rettv.vval.v_string);
+ vim_free(pat_pre);
+
+ apply_autocmds(EVENT_MODECHANGED, pat, NULL, FALSE, curbuf);
+ STRCPY(last_mode, rettv.vval.v_string);
+
+ vim_free(rettv.vval.v_string);
+ vim_free(pat);
+ dict_free_contents(v_event);
+ hash_init(&v_event->dv_hashtab);
+ #endif
+ }
*** ../vim-8.2.3429/src/normal.c 2021-09-11 21:14:16.830577302 +0200
--- src/normal.c 2021-09-12 13:30:35.732067934 +0200
***************
*** 1386,1391 ****
--- 1386,1392 ----
#endif
VIsual_active = FALSE;
+ trigger_modechanged();
setmouse();
mouse_dragging = 0;
***************
*** 5642,5647 ****
--- 5643,5649 ----
{ // or char/line mode
VIsual_mode = cap->cmdchar;
showmode();
+ trigger_modechanged();
}
redraw_curbuf_later(INVERTED); // update the inversion
}
***************
*** 5757,5762 ****
--- 5759,5765 ----
VIsual_mode = c;
VIsual_active = TRUE;
VIsual_reselect = TRUE;
+ trigger_modechanged();
// Corner case: the 0 position in a tab may change when going into
// virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting.
*** ../vim-8.2.3429/src/proto/
autocmd.pro 2021-08-01 14:52:05.554645412 +0200
--- src/proto/
autocmd.pro 2021-09-12 13:30:35.732067934 +0200
***************
*** 25,30 ****
--- 25,31 ----
int has_cmdundefined(void);
int has_textyankpost(void);
int has_completechanged(void);
+ int has_modechanged(void);
void block_autocmds(void);
void unblock_autocmds(void);
int is_autocmd_blocked(void);
*** ../vim-8.2.3429/src/proto/
misc1.pro 2021-07-10 21:28:55.327050110 +0200
--- src/proto/
misc1.pro 2021-09-12 13:30:35.732067934 +0200
***************
*** 47,50 ****
--- 47,51 ----
char_u *get_isolated_shell_name(void);
int path_is_url(char_u *p);
int path_with_url(char_u *fname);
+ void trigger_modechanged();
/* vim: set ft=c : */
*** ../vim-8.2.3429/src/testdir/test_edit.vim 2021-09-11 21:14:16.830577302 +0200
--- src/testdir/test_edit.vim 2021-09-12 13:36:30.671648994 +0200
***************
*** 1907,1910 ****
--- 1907,1944 ----
set encoding=utf-8
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-8.2.3429/src/vim.h 2021-09-02 18:49:02.748932320 +0200
--- src/vim.h 2021-09-12 13:30:35.732067934 +0200
***************
*** 688,693 ****
--- 688,695 ----
#define TERMINAL 0x2000 // Terminal mode
#define MODE_ALL 0xffff
+ #define MODE_MAX_LENGTH 4 // max mode length returned in mode()
+
// all mode bits used for mapping
#define MAP_ALL_MODES (0x3f | SELECTMODE | TERMINAL)
***************
*** 1317,1322 ****
--- 1319,1325 ----
EVENT_INSERTLEAVEPRE, // just before leaving Insert mode
EVENT_INSERTLEAVE, // just after leaving Insert mode
EVENT_MENUPOPUP, // just before popup menu is displayed
+ EVENT_MODECHANGED, // after changing the mode
EVENT_OPTIONSET, // option was set
EVENT_QUICKFIXCMDPOST, // after :make, :grep etc.
EVENT_QUICKFIXCMDPRE, // before :make, :grep etc.
*** ../vim-8.2.3429/src/version.c 2021-09-11 23:07:39.796286455 +0200
--- src/version.c 2021-09-12 13:32:33.235929337 +0200
***************
*** 757,758 ****
--- 757,760 ----
{ /* Add new patch number below this line */
+ /**/
+ 3430,
/**/
--
Far back in the mists of ancient time, in the great and glorious days of the
former Galactic Empire, life was wild, rich and largely tax free.
Mighty starships plied their way between exotic suns, seeking adventure and
reward among the furthest reaches of Galactic space. In those days, spirits
were brave, the stakes were high, men were real men, women were real women
and small furry creatures from Alpha Centauri were real small furry creatures
from Alpha Centauri. And all dared to brave unknown terrors, to do mighty
deeds, to boldly split infinitives that no man had split before -- and thus
was the Empire forged.
-- Douglas Adams, "The Hitchhiker's Guide to the Galaxy"
/// Bram Moolenaar -- Br...@Moolenaar.net --
http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features --
http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims --
http://ICCF-Holland.org ///