mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +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
|
// Set last_changedtick to avoid triggering a TextChanged autocommand right
|
||||||
// after it was added.
|
// after it was added.
|
||||||
curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
|
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);
|
curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf);
|
||||||
|
|
||||||
// require "!" to overwrite the file, because it wasn't read completely
|
// 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.
|
/// This is a dictionary item used to store b:changedtick.
|
||||||
ChangedtickDictItem changedtick_di;
|
ChangedtickDictItem changedtick_di;
|
||||||
|
|
||||||
varnumber_T b_last_changedtick; // b:changedtick when TextChanged or
|
varnumber_T b_last_changedtick; // b:changedtick when TextChanged was
|
||||||
// TextChangedI was last triggered.
|
|
||||||
varnumber_T b_last_changedtick_pum; // b:changedtick when TextChangedP was
|
|
||||||
// last triggered.
|
// 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
|
bool b_saving; // Set to true if we are in the middle of
|
||||||
// saving the buffer.
|
// saving the buffer.
|
||||||
|
@@ -1797,8 +1797,8 @@ restore_backup:
|
|||||||
unchanged(buf, true, false);
|
unchanged(buf, true, false);
|
||||||
const varnumber_T changedtick = buf_get_changedtick(buf);
|
const varnumber_T changedtick = buf_get_changedtick(buf);
|
||||||
if (buf->b_last_changedtick + 1 == changedtick) {
|
if (buf->b_last_changedtick + 1 == changedtick) {
|
||||||
// b:changedtick may be incremented in unchanged() but that
|
// b:changedtick may be incremented in unchanged() but that should not
|
||||||
// should not trigger a TextChanged event.
|
// trigger a TextChanged event.
|
||||||
buf->b_last_changedtick = changedtick;
|
buf->b_last_changedtick = changedtick;
|
||||||
}
|
}
|
||||||
u_unchanged(buf);
|
u_unchanged(buf);
|
||||||
|
@@ -1306,9 +1306,9 @@ void ins_redraw(bool ready)
|
|||||||
last_cursormoved = curwin->w_cursor;
|
last_cursormoved = curwin->w_cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger TextChangedI if changedtick differs.
|
// Trigger TextChangedI if changedtick_i differs.
|
||||||
if (ready && has_event(EVENT_TEXTCHANGEDI)
|
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()) {
|
&& !pum_visible()) {
|
||||||
aco_save_T aco;
|
aco_save_T aco;
|
||||||
varnumber_T tick = buf_get_changedtick(curbuf);
|
varnumber_T tick = buf_get_changedtick(curbuf);
|
||||||
@@ -1317,16 +1317,16 @@ void ins_redraw(bool ready)
|
|||||||
aucmd_prepbuf(&aco, curbuf);
|
aucmd_prepbuf(&aco, curbuf);
|
||||||
apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, false, curbuf);
|
apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, false, curbuf);
|
||||||
aucmd_restbuf(&aco);
|
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()
|
if (tick != buf_get_changedtick(curbuf)) { // see ins_apply_autocmds()
|
||||||
u_save(curwin->w_cursor.lnum,
|
u_save(curwin->w_cursor.lnum,
|
||||||
(linenr_T)(curwin->w_cursor.lnum + 1));
|
(linenr_T)(curwin->w_cursor.lnum + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger TextChangedP if changedtick differs. When the popupmenu closes
|
// Trigger TextChangedP if changedtick_pum differs. When the popupmenu
|
||||||
// TextChangedI will need to trigger for backwards compatibility, thus use
|
// closes TextChangedI will need to trigger for backwards compatibility,
|
||||||
// different b_last_changedtick* variables.
|
// thus use different b_last_changedtick* variables.
|
||||||
if (ready && has_event(EVENT_TEXTCHANGEDP)
|
if (ready && has_event(EVENT_TEXTCHANGEDP)
|
||||||
&& curbuf->b_last_changedtick_pum != buf_get_changedtick(curbuf)
|
&& curbuf->b_last_changedtick_pum != buf_get_changedtick(curbuf)
|
||||||
&& pum_visible()) {
|
&& 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)
|
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()
|
it('CompleteChanged autocommand', function()
|
||||||
curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar', ''})
|
curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar', ''})
|
||||||
source([[
|
source([[
|
||||||
|
@@ -2442,7 +2442,7 @@ endfunc
|
|||||||
|
|
||||||
" Test TextChangedI and TextChangedP
|
" Test TextChangedI and TextChangedP
|
||||||
func Test_ChangedP()
|
func Test_ChangedP()
|
||||||
throw 'Skipped: use test/functional/editor/completion_spec.lua'
|
throw 'Skipped: use test/functional/autocmd/textchanged_spec.lua'
|
||||||
new
|
new
|
||||||
call setline(1, ['foo', 'bar', 'foobar'])
|
call setline(1, ['foo', 'bar', 'foobar'])
|
||||||
call test_override("char_avail", 1)
|
call test_override("char_avail", 1)
|
||||||
@@ -2452,6 +2452,7 @@ func Test_ChangedP()
|
|||||||
let g:autocmd .= a:char
|
let g:autocmd .= a:char
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
" TextChanged will not be triggered, only check that it isn't.
|
||||||
au! TextChanged <buffer> :call TextChangedAutocmd('N')
|
au! TextChanged <buffer> :call TextChangedAutocmd('N')
|
||||||
au! TextChangedI <buffer> :call TextChangedAutocmd('I')
|
au! TextChangedI <buffer> :call TextChangedAutocmd('I')
|
||||||
au! TextChangedP <buffer> :call TextChangedAutocmd('P')
|
au! TextChangedP <buffer> :call TextChangedAutocmd('P')
|
||||||
@@ -2505,7 +2506,7 @@ func SetLineOne()
|
|||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_TextChangedI_with_setline()
|
func Test_TextChangedI_with_setline()
|
||||||
CheckFunction test_override
|
throw 'Skipped: use test/functional/autocmd/textchanged_spec.lua'
|
||||||
new
|
new
|
||||||
call test_override('char_avail', 1)
|
call test_override('char_avail', 1)
|
||||||
autocmd TextChangedI <buffer> call SetLineOne()
|
autocmd TextChangedI <buffer> call SetLineOne()
|
||||||
@@ -3433,6 +3434,45 @@ func Test_autocmd_vimgrep()
|
|||||||
augroup END
|
augroup END
|
||||||
endfunc
|
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()
|
func Test_closing_autocmd_window()
|
||||||
let lines =<< trim END
|
let lines =<< trim END
|
||||||
edit Xa.txt
|
edit Xa.txt
|
||||||
|
Reference in New Issue
Block a user