mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +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