mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	vim-patch:8.2.3517: TextChanged does not trigger after TextChangedI (#25384)
Problem:    TextChanged does not trigger after TextChangedI.
Solution:   Store the tick separately for TextChangedI. (Christian Brabandt,
            closes vim/vim#8968, closes vim/vim#8932)
db3b44640d
Co-authored-by: Christian Brabandt <cb@256bit.org>
			
			
This commit is contained in:
		| @@ -354,6 +354,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg) | ||||
|   // Set last_changedtick to avoid triggering a TextChanged autocommand right | ||||
|   // after it was added. | ||||
|   curbuf->b_last_changedtick = buf_get_changedtick(curbuf); | ||||
|   curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf); | ||||
|   curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf); | ||||
|  | ||||
|   // require "!" to overwrite the file, because it wasn't read completely | ||||
|   | ||||
| @@ -418,10 +418,10 @@ struct file_buffer { | ||||
|   /// This is a dictionary item used to store b:changedtick. | ||||
|   ChangedtickDictItem changedtick_di; | ||||
|  | ||||
|   varnumber_T b_last_changedtick;       // b:changedtick when TextChanged or | ||||
|                                         // TextChangedI was last triggered. | ||||
|   varnumber_T b_last_changedtick_pum;   // b:changedtick when TextChangedP was | ||||
|   varnumber_T b_last_changedtick;       // b:changedtick when TextChanged was | ||||
|                                         // last triggered. | ||||
|   varnumber_T b_last_changedtick_i;     // b:changedtick for TextChangedI | ||||
|   varnumber_T b_last_changedtick_pum;   // b:changedtick for TextChangedP | ||||
|  | ||||
|   bool b_saving;                // Set to true if we are in the middle of | ||||
|                                 // saving the buffer. | ||||
|   | ||||
| @@ -1797,8 +1797,8 @@ restore_backup: | ||||
|     unchanged(buf, true, false); | ||||
|     const varnumber_T changedtick = buf_get_changedtick(buf); | ||||
|     if (buf->b_last_changedtick + 1 == changedtick) { | ||||
|       // b:changedtick may be incremented in unchanged() but that | ||||
|       // should not trigger a TextChanged event. | ||||
|       // b:changedtick may be incremented in unchanged() but that should not | ||||
|       // trigger a TextChanged event. | ||||
|       buf->b_last_changedtick = changedtick; | ||||
|     } | ||||
|     u_unchanged(buf); | ||||
|   | ||||
| @@ -1306,9 +1306,9 @@ void ins_redraw(bool ready) | ||||
|     last_cursormoved = curwin->w_cursor; | ||||
|   } | ||||
|  | ||||
|   // Trigger TextChangedI if changedtick differs. | ||||
|   // Trigger TextChangedI if changedtick_i differs. | ||||
|   if (ready && has_event(EVENT_TEXTCHANGEDI) | ||||
|       && curbuf->b_last_changedtick != buf_get_changedtick(curbuf) | ||||
|       && curbuf->b_last_changedtick_i != buf_get_changedtick(curbuf) | ||||
|       && !pum_visible()) { | ||||
|     aco_save_T aco; | ||||
|     varnumber_T tick = buf_get_changedtick(curbuf); | ||||
| @@ -1317,16 +1317,16 @@ void ins_redraw(bool ready) | ||||
|     aucmd_prepbuf(&aco, curbuf); | ||||
|     apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, false, curbuf); | ||||
|     aucmd_restbuf(&aco); | ||||
|     curbuf->b_last_changedtick = buf_get_changedtick(curbuf); | ||||
|     curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf); | ||||
|     if (tick != buf_get_changedtick(curbuf)) {  // see ins_apply_autocmds() | ||||
|       u_save(curwin->w_cursor.lnum, | ||||
|              (linenr_T)(curwin->w_cursor.lnum + 1)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Trigger TextChangedP if changedtick differs. When the popupmenu closes | ||||
|   // TextChangedI will need to trigger for backwards compatibility, thus use | ||||
|   // different b_last_changedtick* variables. | ||||
|   // Trigger TextChangedP if changedtick_pum differs. When the popupmenu | ||||
|   // closes TextChangedI will need to trigger for backwards compatibility, | ||||
|   // thus use different b_last_changedtick* variables. | ||||
|   if (ready && has_event(EVENT_TEXTCHANGEDP) | ||||
|       && curbuf->b_last_changedtick_pum != buf_get_changedtick(curbuf) | ||||
|       && pum_visible()) { | ||||
|   | ||||
							
								
								
									
										150
									
								
								test/functional/autocmd/textchanged_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								test/functional/autocmd/textchanged_spec.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| local helpers = require('test.functional.helpers')(after_each) | ||||
| local clear = helpers.clear | ||||
| local exec = helpers.exec | ||||
| local command = helpers.command | ||||
| local feed = helpers.feed | ||||
| local eq = helpers.eq | ||||
| local eval = helpers.eval | ||||
| local poke_eventloop = helpers.poke_eventloop | ||||
|  | ||||
| before_each(clear) | ||||
|  | ||||
| -- oldtest: Test_ChangedP() | ||||
| it('TextChangedI and TextChangedP autocommands', function() | ||||
|   -- The oldtest uses feedkeys() with 'x' flag, which never triggers TextChanged. | ||||
|   -- So don't add TextChanged autocommand here. | ||||
|   exec([[ | ||||
|     call setline(1, ['foo', 'bar', 'foobar']) | ||||
|     set complete=. completeopt=menuone | ||||
|     au! TextChangedI <buffer> let g:autocmd ..= 'I' | ||||
|     au! TextChangedP <buffer> let g:autocmd ..= 'P' | ||||
|     call cursor(3, 1) | ||||
|   ]]) | ||||
|  | ||||
|   command([[let g:autocmd = '']]) | ||||
|   feed('o') | ||||
|   poke_eventloop() | ||||
|   feed('<esc>') | ||||
|   eq('I', eval('g:autocmd')) | ||||
|  | ||||
|   command([[let g:autocmd = '']]) | ||||
|   feed('S') | ||||
|   poke_eventloop() | ||||
|   feed('f') | ||||
|   poke_eventloop() | ||||
|   eq('II', eval('g:autocmd')) | ||||
|   feed('<esc>') | ||||
|  | ||||
|   command([[let g:autocmd = '']]) | ||||
|   feed('S') | ||||
|   poke_eventloop() | ||||
|   feed('f') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   poke_eventloop() | ||||
|   eq('IIP', eval('g:autocmd')) | ||||
|   feed('<esc>') | ||||
|  | ||||
|   command([[let g:autocmd = '']]) | ||||
|   feed('S') | ||||
|   poke_eventloop() | ||||
|   feed('f') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   poke_eventloop() | ||||
|   eq('IIPP', eval('g:autocmd')) | ||||
|   feed('<esc>') | ||||
|  | ||||
|   command([[let g:autocmd = '']]) | ||||
|   feed('S') | ||||
|   poke_eventloop() | ||||
|   feed('f') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   poke_eventloop() | ||||
|   eq('IIPPP', eval('g:autocmd')) | ||||
|   feed('<esc>') | ||||
|  | ||||
|   command([[let g:autocmd = '']]) | ||||
|   feed('S') | ||||
|   poke_eventloop() | ||||
|   feed('f') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   poke_eventloop() | ||||
|   feed('<C-N>') | ||||
|   eq('IIPPPP', eval('g:autocmd')) | ||||
|   feed('<esc>') | ||||
|  | ||||
|   eq({'foo', 'bar', 'foobar', 'foo'}, eval('getline(1, "$")')) | ||||
| end) | ||||
|  | ||||
| -- oldtest: Test_TextChangedI_with_setline() | ||||
| it('TextChangedI with setline()', function() | ||||
|   exec([[ | ||||
|     let g:setline_handled = v:false | ||||
|     func SetLineOne() | ||||
|       if !g:setline_handled | ||||
|         call setline(1, "(x)") | ||||
|         let g:setline_handled = v:true | ||||
|       endif | ||||
|     endfunc | ||||
|     autocmd TextChangedI <buffer> call SetLineOne() | ||||
|   ]]) | ||||
|  | ||||
|   feed('i') | ||||
|   poke_eventloop() | ||||
|   feed('(') | ||||
|   poke_eventloop() | ||||
|   feed('<CR>') | ||||
|   poke_eventloop() | ||||
|   feed('<Esc>') | ||||
|   eq('(', eval('getline(1)')) | ||||
|   eq('x)', eval('getline(2)')) | ||||
|   command('undo') | ||||
|   eq('', eval('getline(1)')) | ||||
|   eq('', eval('getline(2)')) | ||||
| end) | ||||
|  | ||||
| -- oldtest: Test_Changed_ChangedI() | ||||
| it('TextChanged is triggerd after TextChangedI', function() | ||||
|   exec([[ | ||||
|     let [g:autocmd_i, g:autocmd_n] = ['',''] | ||||
|  | ||||
|     func! TextChangedAutocmdI(char) | ||||
|       let g:autocmd_{tolower(a:char)} = a:char .. b:changedtick | ||||
|     endfunc | ||||
|  | ||||
|     augroup Test_TextChanged | ||||
|       au! | ||||
|       au TextChanged  <buffer> :call TextChangedAutocmdI('N') | ||||
|       au TextChangedI <buffer> :call TextChangedAutocmdI('I') | ||||
|     augroup END | ||||
|   ]]) | ||||
|  | ||||
|   feed('i') | ||||
|   poke_eventloop() | ||||
|   feed('f') | ||||
|   poke_eventloop() | ||||
|   feed('o') | ||||
|   poke_eventloop() | ||||
|   feed('o') | ||||
|   poke_eventloop() | ||||
|   feed('<esc>') | ||||
|   eq('N5', eval('g:autocmd_n')) | ||||
|   eq('I5', eval('g:autocmd_i')) | ||||
|  | ||||
|   command([[call feedkeys("yyp", 'tnix')]]) | ||||
|   eq('N6', eval('g:autocmd_n')) | ||||
|   eq('I5', eval('g:autocmd_i')) | ||||
| end) | ||||
| @@ -1032,93 +1032,6 @@ describe('completion', function() | ||||
|     ]]) | ||||
|   end) | ||||
|  | ||||
|   -- oldtest: Test_ChangedP() | ||||
|   it('TextChangedI and TextChangedP autocommands', function() | ||||
|     curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar'}) | ||||
|     source([[ | ||||
|       set complete=. completeopt=menuone | ||||
|       let g:foo = [] | ||||
|       autocmd! TextChanged * :call add(g:foo, "N") | ||||
|       autocmd! TextChangedI * :call add(g:foo, "I") | ||||
|       autocmd! TextChangedP * :call add(g:foo, "P") | ||||
|       call cursor(3, 1) | ||||
|     ]]) | ||||
|  | ||||
|     command('let g:foo = []') | ||||
|     feed('o') | ||||
|     poke_eventloop() | ||||
|     feed('<esc>') | ||||
|     eq({'I'}, eval('g:foo')) | ||||
|  | ||||
|     command('let g:foo = []') | ||||
|     feed('S') | ||||
|     poke_eventloop() | ||||
|     feed('f') | ||||
|     poke_eventloop() | ||||
|     eq({'I', 'I'}, eval('g:foo')) | ||||
|     feed('<esc>') | ||||
|  | ||||
|     command('let g:foo = []') | ||||
|     feed('S') | ||||
|     poke_eventloop() | ||||
|     feed('f') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     poke_eventloop() | ||||
|     eq({'I', 'I', 'P'}, eval('g:foo')) | ||||
|     feed('<esc>') | ||||
|  | ||||
|     command('let g:foo = []') | ||||
|     feed('S') | ||||
|     poke_eventloop() | ||||
|     feed('f') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     poke_eventloop() | ||||
|     eq({'I', 'I', 'P', 'P'}, eval('g:foo')) | ||||
|     feed('<esc>') | ||||
|  | ||||
|     command('let g:foo = []') | ||||
|     feed('S') | ||||
|     poke_eventloop() | ||||
|     feed('f') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     poke_eventloop() | ||||
|     eq({'I', 'I', 'P', 'P', 'P'}, eval('g:foo')) | ||||
|     feed('<esc>') | ||||
|  | ||||
|     command('let g:foo = []') | ||||
|     feed('S') | ||||
|     poke_eventloop() | ||||
|     feed('f') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     poke_eventloop() | ||||
|     feed('<C-N>') | ||||
|     eq({'I', 'I', 'P', 'P', 'P', 'P'}, eval('g:foo')) | ||||
|     feed('<esc>') | ||||
|  | ||||
|     eq({'foo', 'bar', 'foobar', 'foo'}, eval('getline(1, "$")')) | ||||
|  | ||||
|     source([[ | ||||
|       au! TextChanged | ||||
|       au! TextChangedI | ||||
|       au! TextChangedP | ||||
|       set complete&vim completeopt&vim | ||||
|     ]]) | ||||
|   end) | ||||
|  | ||||
|   it('CompleteChanged autocommand', function() | ||||
|     curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar', ''}) | ||||
|     source([[ | ||||
|   | ||||
| @@ -2442,7 +2442,7 @@ endfunc | ||||
|  | ||||
| " Test TextChangedI and TextChangedP | ||||
| func Test_ChangedP() | ||||
|   throw 'Skipped: use test/functional/editor/completion_spec.lua' | ||||
|   throw 'Skipped: use test/functional/autocmd/textchanged_spec.lua' | ||||
|   new | ||||
|   call setline(1, ['foo', 'bar', 'foobar']) | ||||
|   call test_override("char_avail", 1) | ||||
| @@ -2452,6 +2452,7 @@ func Test_ChangedP() | ||||
|     let g:autocmd .= a:char | ||||
|   endfunc | ||||
|  | ||||
|   " TextChanged will not be triggered, only check that it isn't. | ||||
|   au! TextChanged <buffer> :call TextChangedAutocmd('N') | ||||
|   au! TextChangedI <buffer> :call TextChangedAutocmd('I') | ||||
|   au! TextChangedP <buffer> :call TextChangedAutocmd('P') | ||||
| @@ -2505,7 +2506,7 @@ func SetLineOne() | ||||
| endfunc | ||||
|  | ||||
| func Test_TextChangedI_with_setline() | ||||
|   CheckFunction test_override | ||||
|   throw 'Skipped: use test/functional/autocmd/textchanged_spec.lua' | ||||
|   new | ||||
|   call test_override('char_avail', 1) | ||||
|   autocmd TextChangedI <buffer> call SetLineOne() | ||||
| @@ -3433,6 +3434,45 @@ func Test_autocmd_vimgrep() | ||||
|   augroup END | ||||
| endfunc | ||||
|  | ||||
| " Test TextChangedI and TextChanged | ||||
| func Test_Changed_ChangedI() | ||||
|   throw 'Skipped: use test/functional/autocmd/textchanged_spec.lua' | ||||
|   new | ||||
|   call test_override("char_avail", 1) | ||||
|   let [g:autocmd_i, g:autocmd_n] = ['',''] | ||||
|  | ||||
|   func! TextChangedAutocmdI(char) | ||||
|     let g:autocmd_{tolower(a:char)} = a:char .. b:changedtick | ||||
|   endfunc | ||||
|  | ||||
|   augroup Test_TextChanged | ||||
|     au! | ||||
|     au TextChanged  <buffer> :call TextChangedAutocmdI('N') | ||||
|     au TextChangedI <buffer> :call TextChangedAutocmdI('I') | ||||
|   augroup END | ||||
|  | ||||
|   call feedkeys("ifoo\<esc>", 'tnix') | ||||
|   " TODO: Test test does not seem to trigger TextChanged autocommand, this | ||||
|   " requires running Vim in a terminal window. | ||||
|   " call assert_equal('N3', g:autocmd_n) | ||||
|   call assert_equal('I3', g:autocmd_i) | ||||
|  | ||||
|   call feedkeys("yyp", 'tnix') | ||||
|   " TODO: Test test does not seem to trigger TextChanged autocommand. | ||||
|   " call assert_equal('N4', g:autocmd_n) | ||||
|   call assert_equal('I3', g:autocmd_i) | ||||
|  | ||||
|   " CleanUp | ||||
|   call test_override("char_avail", 0) | ||||
|   au! TextChanged  <buffer> | ||||
|   au! TextChangedI <buffer> | ||||
|   augroup! Test_TextChanged | ||||
|   delfu TextChangedAutocmdI | ||||
|   unlet! g:autocmd_i g:autocmd_n | ||||
|  | ||||
|   bw! | ||||
| endfunc | ||||
|  | ||||
| func Test_closing_autocmd_window() | ||||
|   let lines =<< trim END | ||||
|       edit Xa.txt | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 zeertzjq
					zeertzjq