mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	vim-patch:9.0.2075: TextChangedI may not always trigger (#25808)
Problem:  TextChangedI may not always trigger
Solution: trigger it in more cases: for insert/
          append/change operations, and when
          opening a new line,
fixes: vim/vim#13367
closes: vim/vim#13375
4bca4897a1
Co-authored-by: Christian Brabandt <cb@256bit.org>
			
			
This commit is contained in:
		| @@ -143,9 +143,6 @@ static void insert_enter(InsertState *s) | |||||||
|   update_Insstart_orig = true; |   update_Insstart_orig = true; | ||||||
|  |  | ||||||
|   ins_compl_clear();        // clear stuff for CTRL-X mode |   ins_compl_clear();        // clear stuff for CTRL-X mode | ||||||
|   // Reset Changedtick_i, so that TextChangedI will only be triggered for stuff |  | ||||||
|   // from insert mode |  | ||||||
|   curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf); |  | ||||||
|  |  | ||||||
|   // Trigger InsertEnter autocommands.  Do not do this for "r<CR>" or "grx". |   // Trigger InsertEnter autocommands.  Do not do this for "r<CR>" or "grx". | ||||||
|   if (s->cmdchar != 'r' && s->cmdchar != 'v') { |   if (s->cmdchar != 'r' && s->cmdchar != 'v') { | ||||||
|   | |||||||
| @@ -5724,6 +5724,8 @@ static void n_opencmd(cmdarg_T *cap) | |||||||
|     (void)hasFolding(curwin->w_cursor.lnum, |     (void)hasFolding(curwin->w_cursor.lnum, | ||||||
|                      NULL, &curwin->w_cursor.lnum); |                      NULL, &curwin->w_cursor.lnum); | ||||||
|   } |   } | ||||||
|  |   // trigger TextChangedI for the 'o/O' command | ||||||
|  |   curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf); | ||||||
|   if (u_save(curwin->w_cursor.lnum - (cap->cmdchar == 'O' ? 1 : 0), |   if (u_save(curwin->w_cursor.lnum - (cap->cmdchar == 'O' ? 1 : 0), | ||||||
|              curwin->w_cursor.lnum + (cap->cmdchar == 'o' ? 1 : 0)) |              curwin->w_cursor.lnum + (cap->cmdchar == 'o' ? 1 : 0)) | ||||||
|       && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, |       && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, | ||||||
| @@ -6265,6 +6267,11 @@ static void invoke_edit(cmdarg_T *cap, int repl, int cmd, int startln) | |||||||
|   // Always reset "restart_edit", this is not a restarted edit. |   // Always reset "restart_edit", this is not a restarted edit. | ||||||
|   restart_edit = 0; |   restart_edit = 0; | ||||||
|  |  | ||||||
|  |   // Reset Changedtick_i, so that TextChangedI will only be triggered for stuff | ||||||
|  |   // from insert mode, for 'o/O' this has already been done in n_opencmd | ||||||
|  |   if (cap->cmdchar != 'O' && cap->cmdchar != 'o') { | ||||||
|  |     curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf); | ||||||
|  |   } | ||||||
|   if (edit(cmd, startln, cap->count1)) { |   if (edit(cmd, startln, cap->count1)) { | ||||||
|     cap->retval |= CA_COMMAND_BUSY; |     cap->retval |= CA_COMMAND_BUSY; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -6226,6 +6226,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) | |||||||
|         // Restore linebreak, so that when the user edits it looks as before. |         // Restore linebreak, so that when the user edits it looks as before. | ||||||
|         restore_lbr(lbr_saved); |         restore_lbr(lbr_saved); | ||||||
|  |  | ||||||
|  |         // trigger TextChangedI | ||||||
|  |         curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf); | ||||||
|  |  | ||||||
|         if (op_change(oap)) {           // will call edit() |         if (op_change(oap)) {           // will call edit() | ||||||
|           cap->retval |= CA_COMMAND_BUSY; |           cap->retval |= CA_COMMAND_BUSY; | ||||||
|         } |         } | ||||||
| @@ -6324,6 +6327,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) | |||||||
|         // Restore linebreak, so that when the user edits it looks as before. |         // Restore linebreak, so that when the user edits it looks as before. | ||||||
|         restore_lbr(lbr_saved); |         restore_lbr(lbr_saved); | ||||||
|  |  | ||||||
|  |         // trigger TextChangedI | ||||||
|  |         curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf); | ||||||
|  |  | ||||||
|         op_insert(oap, cap->count1); |         op_insert(oap, cap->count1); | ||||||
|  |  | ||||||
|         // Reset linebreak, so that formatting works correctly. |         // Reset linebreak, so that formatting works correctly. | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ local exec = helpers.exec | |||||||
| local command = helpers.command | local command = helpers.command | ||||||
| local feed = helpers.feed | local feed = helpers.feed | ||||||
| local eq = helpers.eq | local eq = helpers.eq | ||||||
|  | local neq = helpers.neq | ||||||
| local eval = helpers.eval | local eval = helpers.eval | ||||||
| local poke_eventloop = helpers.poke_eventloop | local poke_eventloop = helpers.poke_eventloop | ||||||
|  |  | ||||||
| @@ -26,14 +27,14 @@ it('TextChangedI and TextChangedP autocommands', function() | |||||||
|   poke_eventloop() |   poke_eventloop() | ||||||
|   feed('<esc>') |   feed('<esc>') | ||||||
|   -- TextChangedI triggers only if text is actually changed in Insert mode |   -- TextChangedI triggers only if text is actually changed in Insert mode | ||||||
|   eq('', eval('g:autocmd')) |  | ||||||
|  |  | ||||||
|   command([[let g:autocmd = '']]) |  | ||||||
|   feed('S') |  | ||||||
|   poke_eventloop() |  | ||||||
|   feed('f') |  | ||||||
|   poke_eventloop() |  | ||||||
|   eq('I', eval('g:autocmd')) |   eq('I', eval('g:autocmd')) | ||||||
|  |  | ||||||
|  |   command([[let g:autocmd = '']]) | ||||||
|  |   feed('S') | ||||||
|  |   poke_eventloop() | ||||||
|  |   feed('f') | ||||||
|  |   poke_eventloop() | ||||||
|  |   eq('II', eval('g:autocmd')) | ||||||
|   feed('<esc>') |   feed('<esc>') | ||||||
|  |  | ||||||
|   command([[let g:autocmd = '']]) |   command([[let g:autocmd = '']]) | ||||||
| @@ -43,7 +44,7 @@ it('TextChangedI and TextChangedP autocommands', function() | |||||||
|   poke_eventloop() |   poke_eventloop() | ||||||
|   feed('<C-N>') |   feed('<C-N>') | ||||||
|   poke_eventloop() |   poke_eventloop() | ||||||
|   eq('IP', eval('g:autocmd')) |   eq('IIP', eval('g:autocmd')) | ||||||
|   feed('<esc>') |   feed('<esc>') | ||||||
|  |  | ||||||
|   command([[let g:autocmd = '']]) |   command([[let g:autocmd = '']]) | ||||||
| @@ -55,7 +56,7 @@ it('TextChangedI and TextChangedP autocommands', function() | |||||||
|   poke_eventloop() |   poke_eventloop() | ||||||
|   feed('<C-N>') |   feed('<C-N>') | ||||||
|   poke_eventloop() |   poke_eventloop() | ||||||
|   eq('IPP', eval('g:autocmd')) |   eq('IIPP', eval('g:autocmd')) | ||||||
|   feed('<esc>') |   feed('<esc>') | ||||||
|  |  | ||||||
|   command([[let g:autocmd = '']]) |   command([[let g:autocmd = '']]) | ||||||
| @@ -69,7 +70,7 @@ it('TextChangedI and TextChangedP autocommands', function() | |||||||
|   poke_eventloop() |   poke_eventloop() | ||||||
|   feed('<C-N>') |   feed('<C-N>') | ||||||
|   poke_eventloop() |   poke_eventloop() | ||||||
|   eq('IPPP', eval('g:autocmd')) |   eq('IIPPP', eval('g:autocmd')) | ||||||
|   feed('<esc>') |   feed('<esc>') | ||||||
|  |  | ||||||
|   command([[let g:autocmd = '']]) |   command([[let g:autocmd = '']]) | ||||||
| @@ -84,7 +85,7 @@ it('TextChangedI and TextChangedP autocommands', function() | |||||||
|   feed('<C-N>') |   feed('<C-N>') | ||||||
|   poke_eventloop() |   poke_eventloop() | ||||||
|   feed('<C-N>') |   feed('<C-N>') | ||||||
|   eq('IPPPP', eval('g:autocmd')) |   eq('IIPPPP', eval('g:autocmd')) | ||||||
|   feed('<esc>') |   feed('<esc>') | ||||||
|  |  | ||||||
|   eq({'foo', 'bar', 'foobar', 'foo'}, eval('getline(1, "$")')) |   eq({'foo', 'bar', 'foobar', 'foo'}, eval('getline(1, "$")')) | ||||||
| @@ -145,17 +146,37 @@ it('TextChangedI and TextChanged', function() | |||||||
|   eq('', eval('g:autocmd_n')) |   eq('', eval('g:autocmd_n')) | ||||||
|   eq('I5', eval('g:autocmd_i')) |   eq('I5', eval('g:autocmd_i')) | ||||||
|  |  | ||||||
|   command([[call feedkeys("yyp", 'tnix')]]) |   feed('yyp') | ||||||
|   eq('N6', eval('g:autocmd_n')) |   eq('N6', eval('g:autocmd_n')) | ||||||
|   eq('I5', eval('g:autocmd_i')) |   eq('I5', eval('g:autocmd_i')) | ||||||
|  |  | ||||||
|   -- TextChangedI should only trigger if change was done in Insert mode |   -- TextChangedI should only trigger if change was done in Insert mode | ||||||
|   command([[let g:autocmd_i = '']]) |   command([[let g:autocmd_i = '']]) | ||||||
|   command([[call feedkeys("yypi\<esc>", 'tnix')]]) |   feed('yypi<esc>') | ||||||
|   eq('', eval('g:autocmd_i')) |   eq('', eval('g:autocmd_i')) | ||||||
|  |  | ||||||
|   -- TextChanged should only trigger if change was done in Normal mode |   -- TextChanged should only trigger if change was done in Normal mode | ||||||
|   command([[let g:autocmd_n = '']]) |   command([[let g:autocmd_n = '']]) | ||||||
|   command([[call feedkeys("ibar\<esc>", 'tnix')]]) |   feed('ibar<esc>') | ||||||
|   eq('', eval('g:autocmd_n')) |   eq('', eval('g:autocmd_n')) | ||||||
|  |  | ||||||
|  |   local function validate_mixed_textchangedi(keys) | ||||||
|  |     feed('ifoo<esc>') | ||||||
|  |     command([[let g:autocmd_i = '']]) | ||||||
|  |     command([[let g:autocmd_n = '']]) | ||||||
|  |     for _, s in ipairs(keys) do | ||||||
|  |       feed(s) | ||||||
|  |       poke_eventloop() | ||||||
|  |     end | ||||||
|  |     neq('', eval('g:autocmd_i')) | ||||||
|  |     eq('', eval('g:autocmd_n')) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   validate_mixed_textchangedi({'o', '<esc>'}) | ||||||
|  |   validate_mixed_textchangedi({'O', '<esc>'}) | ||||||
|  |   validate_mixed_textchangedi({'ciw', '<esc>'}) | ||||||
|  |   validate_mixed_textchangedi({'cc', '<esc>'}) | ||||||
|  |   validate_mixed_textchangedi({'C', '<esc>'}) | ||||||
|  |   validate_mixed_textchangedi({'s', '<esc>'}) | ||||||
|  |   validate_mixed_textchangedi({'S', '<esc>'}) | ||||||
| end) | end) | ||||||
|   | |||||||
| @@ -2476,28 +2476,27 @@ func Test_ChangedP() | |||||||
|   call cursor(3, 1) |   call cursor(3, 1) | ||||||
|   let g:autocmd = '' |   let g:autocmd = '' | ||||||
|   call feedkeys("o\<esc>", 'tnix') |   call feedkeys("o\<esc>", 'tnix') | ||||||
|   " `TextChangedI` triggers only if text is actually changed in Insert mode |  | ||||||
|   call assert_equal('', g:autocmd) |  | ||||||
|  |  | ||||||
|   let g:autocmd = '' |  | ||||||
|   call feedkeys("Sf", 'tnix') |  | ||||||
|   call assert_equal('I', g:autocmd) |   call assert_equal('I', g:autocmd) | ||||||
|  |  | ||||||
|  |   let g:autocmd = '' | ||||||
|  |   call feedkeys("Sf", 'tnix') | ||||||
|  |   call assert_equal('II', g:autocmd) | ||||||
|  |  | ||||||
|   let g:autocmd = '' |   let g:autocmd = '' | ||||||
|   call feedkeys("Sf\<C-N>", 'tnix') |   call feedkeys("Sf\<C-N>", 'tnix') | ||||||
|   call assert_equal('IP', g:autocmd) |   call assert_equal('IIP', g:autocmd) | ||||||
|  |  | ||||||
|   let g:autocmd = '' |   let g:autocmd = '' | ||||||
|   call feedkeys("Sf\<C-N>\<C-N>", 'tnix') |   call feedkeys("Sf\<C-N>\<C-N>", 'tnix') | ||||||
|   call assert_equal('IPP', g:autocmd) |   call assert_equal('IIPP', g:autocmd) | ||||||
|  |  | ||||||
|   let g:autocmd = '' |   let g:autocmd = '' | ||||||
|   call feedkeys("Sf\<C-N>\<C-N>\<C-N>", 'tnix') |   call feedkeys("Sf\<C-N>\<C-N>\<C-N>", 'tnix') | ||||||
|   call assert_equal('IPPP', g:autocmd) |   call assert_equal('IIPPP', g:autocmd) | ||||||
|  |  | ||||||
|   let g:autocmd = '' |   let g:autocmd = '' | ||||||
|   call feedkeys("Sf\<C-N>\<C-N>\<C-N>\<C-N>", 'tnix') |   call feedkeys("Sf\<C-N>\<C-N>\<C-N>\<C-N>", 'tnix') | ||||||
|   call assert_equal('IPPPP', g:autocmd) |   call assert_equal('IIPPPP', g:autocmd) | ||||||
|  |  | ||||||
|   call assert_equal(['foo', 'bar', 'foobar', 'foo'], getline(1, '$')) |   call assert_equal(['foo', 'bar', 'foobar', 'foo'], getline(1, '$')) | ||||||
|   " TODO: how should it handle completeopt=noinsert,noselect? |   " TODO: how should it handle completeopt=noinsert,noselect? | ||||||
| @@ -3489,6 +3488,25 @@ func Test_Changed_ChangedI() | |||||||
|   call feedkeys("ibar\<esc>", 'tnix') |   call feedkeys("ibar\<esc>", 'tnix') | ||||||
|   call assert_equal('', g:autocmd_n) |   call assert_equal('', g:autocmd_n) | ||||||
|  |  | ||||||
|  |   " If change is a mix of Normal and Insert modes, TextChangedI should trigger | ||||||
|  |   func s:validate_mixed_textchangedi(keys) | ||||||
|  |     call feedkeys("ifoo\<esc>", 'tnix') | ||||||
|  |     let g:autocmd_i = '' | ||||||
|  |     let g:autocmd_n = '' | ||||||
|  |     call feedkeys(a:keys, 'tnix') | ||||||
|  |     call assert_notequal('', g:autocmd_i) | ||||||
|  |     call assert_equal('', g:autocmd_n) | ||||||
|  |   endfunc | ||||||
|  |  | ||||||
|  |   call s:validate_mixed_textchangedi("o\<esc>") | ||||||
|  |   call s:validate_mixed_textchangedi("O\<esc>") | ||||||
|  |   call s:validate_mixed_textchangedi("ciw\<esc>") | ||||||
|  |   call s:validate_mixed_textchangedi("cc\<esc>") | ||||||
|  |   call s:validate_mixed_textchangedi("C\<esc>") | ||||||
|  |   call s:validate_mixed_textchangedi("s\<esc>") | ||||||
|  |   call s:validate_mixed_textchangedi("S\<esc>") | ||||||
|  |  | ||||||
|  |  | ||||||
|   " CleanUp |   " CleanUp | ||||||
|   call test_override("char_avail", 0) |   call test_override("char_avail", 0) | ||||||
|   au! TextChanged  <buffer> |   au! TextChanged  <buffer> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 zeertzjq
					zeertzjq