vim-patch:8.0.1494: no autocmd triggered in Insert mode with visible popup menu

Problem:    No autocmd triggered in Insert mode with visible popup menu.
Solution:   Add TextChangedP. (Prabir Shrestha, Christian Brabandt,
            closes vim/vim#2372, closes vim/vim#1691)
            Fix that the TextChanged autocommands are not always triggered
            when sourcing a script.

5a09343719
This commit is contained in:
Shougo Matsushita
2018-02-11 22:37:14 +09:00
committed by chemzqm
parent 36b2e3f743
commit 021c5875c1
9 changed files with 178 additions and 18 deletions

View File

@@ -325,6 +325,9 @@ Name triggered by ~
|TextChanged| after a change was made to the text in Normal 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 |TextChangedI| after a change was made to the text in Insert mode
when popup menu is not visible
|TextChangedP| after a change was made to the text in Insert mode
when popup menu visible
|ColorScheme| after loading a color scheme |ColorScheme| after loading a color scheme
@@ -969,6 +972,11 @@ TextChangedI After a change was made to the text in the
current buffer in Insert mode. current buffer in Insert mode.
Not triggered when the popup menu is visible. Not triggered when the popup menu is visible.
Otherwise the same as TextChanged. Otherwise the same as TextChanged.
*TextChangedP*
TextChangedP After a change was made to the text in the
current buffer in Insert mode, only when the
popup menu is visible. Otherwise the same as
TextChanged.
*User* *User*
User Never executed automatically. To be used for User Never executed automatically. To be used for
autocommands that are only executed with autocommands that are only executed with

View File

@@ -85,7 +85,8 @@ return {
'TermOpen', -- after opening a terminal buffer 'TermOpen', -- after opening a terminal buffer
'TermResponse', -- after setting "v:termresponse" 'TermResponse', -- after setting "v:termresponse"
'TextChanged', -- text was modified 'TextChanged', -- text was modified
'TextChangedI', -- text was modified in Insert mode 'TextChangedI', -- text was modified in Insert mode(no popup)
'TextChangedP', -- text was modified in Insert mode(popup)
'TextYankPost', -- after a yank or delete was done (y, d, c) 'TextYankPost', -- after a yank or delete was done (y, d, c)
'User', -- user defined autocommand 'User', -- user defined autocommand
'VimEnter', -- after starting Vim 'VimEnter', -- after starting Vim

View File

@@ -484,6 +484,11 @@ struct file_buffer {
#define b_changedtick changedtick_di.di_tv.vval.v_number #define b_changedtick changedtick_di.di_tv.vval.v_number
ChangedtickDictItem changedtick_di; // b:changedtick dictionary item. ChangedtickDictItem changedtick_di; // b:changedtick dictionary item.
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
// last triggered.
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. */

View File

@@ -1387,13 +1387,20 @@ ins_redraw (
// Trigger TextChangedI if b_changedtick differs. // Trigger TextChangedI if b_changedtick differs.
if (ready && has_event(EVENT_TEXTCHANGEDI) if (ready && has_event(EVENT_TEXTCHANGEDI)
&& last_changedtick != curbuf->b_changedtick && curbuf->b_last_changedtick != curbuf->b_changedtick
&& !pum_visible()) { && !pum_visible()) {
if (last_changedtick_buf == curbuf) {
apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, false, curbuf);
} curbuf->b_last_changedtick = curbuf->b_changedtick;
last_changedtick_buf = curbuf; }
last_changedtick = curbuf->b_changedtick;
// Trigger TextChangedP if b_changedtick 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 != curbuf->b_changedtick
&& pum_visible()) {
apply_autocmds(EVENT_TEXTCHANGEDP, NULL, NULL, false, curbuf);
curbuf->b_last_changedtick_pum = curbuf->b_changedtick;
} }
if (must_redraw) if (must_redraw)
@@ -1415,6 +1422,7 @@ ins_redraw (
emsg_on_display = FALSE; /* may remove error message now */ emsg_on_display = FALSE; /* may remove error message now */
} }
/* /*
* Handle a CTRL-V or CTRL-Q typed in Insert mode. * Handle a CTRL-V or CTRL-Q typed in Insert mode.
*/ */

View File

@@ -3571,9 +3571,8 @@ restore_backup:
unchanged(buf, TRUE); unchanged(buf, TRUE);
/* buf->b_changedtick is always incremented in unchanged() but that /* buf->b_changedtick is always incremented in unchanged() but that
* should not trigger a TextChanged event. */ * should not trigger a TextChanged event. */
if (last_changedtick + 1 == buf->b_changedtick if (buf->b_last_changedtick + 1 == buf->b_changedtick) {
&& last_changedtick_buf == buf) { buf->b_last_changedtick = buf->b_changedtick;
last_changedtick = buf->b_changedtick;
} }
u_unchanged(buf); u_unchanged(buf);
u_update_save_nr(buf); u_update_save_nr(buf);

View File

@@ -868,9 +868,6 @@ EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd
// for CursorMoved event // for CursorMoved event
EXTERN pos_T last_cursormoved INIT(= INIT_POS_T(0, 0, 0)); EXTERN pos_T last_cursormoved INIT(= INIT_POS_T(0, 0, 0));
EXTERN varnumber_T last_changedtick INIT(= 0); // for TextChanged event
EXTERN buf_T *last_changedtick_buf INIT(= NULL);
EXTERN int postponed_split INIT(= 0); /* for CTRL-W CTRL-] command */ EXTERN int postponed_split INIT(= 0); /* for CTRL-W CTRL-] command */
EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */
EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */ EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */

View File

@@ -1215,13 +1215,9 @@ static void normal_check_text_changed(NormalState *s)
{ {
// Trigger TextChanged if b_changedtick differs. // Trigger TextChanged if b_changedtick differs.
if (!finish_op && has_event(EVENT_TEXTCHANGED) if (!finish_op && has_event(EVENT_TEXTCHANGED)
&& last_changedtick != curbuf->b_changedtick) { && curbuf->b_last_changedtick != curbuf->b_changedtick) {
if (last_changedtick_buf == curbuf) {
apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL, false, curbuf);
} curbuf->b_last_changedtick = curbuf->b_changedtick;
last_changedtick_buf = curbuf;
last_changedtick = curbuf->b_changedtick;
} }
} }

View File

@@ -1165,3 +1165,59 @@ func Test_nocatch_wipe_dummy_buffer()
call assert_fails('lv½ /x', 'E480') call assert_fails('lv½ /x', 'E480')
au! au!
endfunc endfunc
" Test TextChangedI and TextChangedP
func Test_ChangedP() abort
throw 'skipped: Nvim does not support test_override()'
new
call setline(1, ['foo', 'bar', 'foobar'])
call test_override("char_avail", 1)
set complete=. completeopt=menuone
func! TextChangedAutocmd(char)
let g:autocmd .= a:char
endfunc
au! TextChanged <buffer> :call TextChangedAutocmd('N')
au! TextChangedI <buffer> :call TextChangedAutocmd('I')
au! TextChangedP <buffer> :call TextChangedAutocmd('P')
call cursor(3, 1)
let g:autocmd = ''
call feedkeys("o\<esc>", 'tnix')
call assert_equal('I', g:autocmd)
let g:autocmd = ''
call feedkeys("Sf", 'tnix')
call assert_equal('II', g:autocmd)
let g:autocmd = ''
call feedkeys("Sf\<C-N>", 'tnix')
call assert_equal('IIP', g:autocmd)
let g:autocmd = ''
call feedkeys("Sf\<C-N>\<C-N>", 'tnix')
call assert_equal('IIPP', g:autocmd)
let g:autocmd = ''
call feedkeys("Sf\<C-N>\<C-N>\<C-N>", 'tnix')
call assert_equal('IIPPP', g:autocmd)
let g:autocmd = ''
call feedkeys("Sf\<C-N>\<C-N>\<C-N>\<C-N>", 'tnix')
call assert_equal('IIPPPP', g:autocmd)
call assert_equal(['foo', 'bar', 'foobar', 'foo'], getline(1, '$'))
" TODO: how should it handle completeopt=noinsert,noselect?
" CleanUp
call test_override("char_avail", 0)
au! TextChanged
au! TextChangedI
au! TextChangedP
delfu TextChangedAutocmd
unlet! g:autocmd
set complete&vim completeopt&vim
bw!
endfunc

View File

@@ -3,7 +3,10 @@ local Screen = require('test.functional.ui.screen')
local clear, feed = helpers.clear, helpers.feed local clear, feed = helpers.clear, helpers.feed
local eval, eq, neq = helpers.eval, helpers.eq, helpers.neq local eval, eq, neq = helpers.eval, helpers.eq, helpers.neq
local feed_command, source, expect = helpers.feed_command, helpers.source, helpers.expect local feed_command, source, expect = helpers.feed_command, helpers.source, helpers.expect
local curbufmeths = helpers.curbufmeths
local command = helpers.command
local meths = helpers.meths local meths = helpers.meths
local wait = helpers.wait
describe('completion', function() describe('completion', function()
local screen local screen
@@ -971,4 +974,91 @@ describe('ui/ext_popupmenu', function()
eq(nil, items) -- popupmenu was hidden eq(nil, items) -- popupmenu was hidden
end) end)
end) end)
describe('TextChangeP autocommand', function()
it('can trigger TextChangedP autocommand as expected',
function()
curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar'})
command('set complete=. completeopt=menuone')
command('let g:foo = []')
command('autocmd! TextChanged * :call add(g:foo, "N")')
command('autocmd! TextChangedI * :call add(g:foo, "I")')
command('autocmd! TextChangedP * :call add(g:foo, "P")')
command('call cursor(3, 1)')
command('let g:foo = []')
feed('o')
wait()
feed('<esc>')
assert.same({'I'}, eval('g:foo'))
command('let g:foo = []')
feed('S')
wait()
feed('f')
wait()
assert.same({'I', 'I'}, eval('g:foo'))
feed('<esc>')
command('let g:foo = []')
feed('S')
wait()
feed('f')
wait()
feed('<C-n>')
wait()
assert.same({'I', 'I', 'P'}, eval('g:foo'))
feed('<esc>')
command('let g:foo = []')
feed('S')
wait()
feed('f')
wait()
feed('<C-n>')
wait()
feed('<C-n>')
wait()
assert.same({'I', 'I', 'P', 'P'}, eval('g:foo'))
feed('<esc>')
command('let g:foo = []')
feed('S')
wait()
feed('f')
wait()
feed('<C-n>')
wait()
feed('<C-n>')
wait()
feed('<C-n>')
wait()
assert.same({'I', 'I', 'P', 'P', 'P'}, eval('g:foo'))
feed('<esc>')
command('let g:foo = []')
feed('S')
wait()
feed('f')
wait()
feed('<C-n>')
wait()
feed('<C-n>')
wait()
feed('<C-n>')
wait()
feed('<C-n>')
assert.same({'I', 'I', 'P', 'P', 'P', 'P'}, eval('g:foo'))
feed('<esc>')
assert.same({'foo', 'bar', 'foobar', 'foo'}, eval('getline(1, "$")'))
source([[
au! TextChanged
au! TextChangedI
au! TextChangedP
set complete&vim completeopt&vim
]])
end)
end)
end) end)