From 5a093437199001a0d60d8e18e2b9539b99a7757c Mon Sep 17 00:00:00 2001
From: Bram Moolenaar <Bram@vim.org>
Date: Sat, 10 Feb 2018 18:15:19 +0100
Subject: [PATCH] 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 #2372, closes #1691)
            Fix that the TextChanged autocommands are not always triggered
            when sourcing a script.
---
 runtime/doc/autocmd.txt      | 10 ++++++-
 src/edit.c                   | 21 ++++++++++----
 src/fileio.c                 | 15 ++++++++--
 src/globals.h                |  2 --
 src/main.c                   |  9 ++----
 src/proto/fileio.pro         |  1 +
 src/structs.h                |  9 ++++++
 src/testdir/test_autocmd.vim | 55 ++++++++++++++++++++++++++++++++++++
 src/version.c                |  2 ++
 src/vim.h                    |  7 +++--
 10 files changed, 112 insertions(+), 19 deletions(-)

diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index ede536ed6..fe67774cf 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -1,4 +1,4 @@
-*autocmd.txt*   For Vim version 8.0.  Last change: 2018 Feb 09
+*autocmd.txt*   For Vim version 8.0.  Last change: 2018 Feb 10
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -332,6 +332,9 @@ Name			triggered by ~
 
 |TextChanged|		after a change was made to the text in Normal 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
 |TextYankPost|		after text is yanked or deleted
 
 |ColorScheme|		after loading a color scheme
@@ -976,6 +979,11 @@ TextChangedI			After a change was made to the text in the
 				current buffer in Insert mode.
 				Not triggered when the popup menu is visible.
 				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.
 							*TextYankPost*
 TextYankPost			After text has been yanked or deleted in the
 				current buffer.  The following values of
diff --git a/src/edit.c b/src/edit.c
index a9e6343a8..7074f2648 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -1682,17 +1682,28 @@ ins_redraw(
 #ifdef FEAT_AUTOCMD
     /* Trigger TextChangedI if b_changedtick differs. */
     if (ready && has_textchangedI()
-	    && last_changedtick != CHANGEDTICK(curbuf)
+	    && curbuf->b_last_changedtick != CHANGEDTICK(curbuf)
 # ifdef FEAT_INS_EXPAND
 	    && !pum_visible()
 # endif
 	    )
     {
-	if (last_changedtick_buf == curbuf)
-	    apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, FALSE, curbuf);
-	last_changedtick_buf = curbuf;
-	last_changedtick = CHANGEDTICK(curbuf);
+	apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, FALSE, curbuf);
+	curbuf->b_last_changedtick = CHANGEDTICK(curbuf);
     }
+
+# ifdef FEAT_INS_EXPAND
+    /* 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_textchangedP()
+	    && curbuf->b_last_changedtick_pum != CHANGEDTICK(curbuf)
+	    && pum_visible())
+    {
+	apply_autocmds(EVENT_TEXTCHANGEDP, NULL, NULL, FALSE, curbuf);
+	curbuf->b_last_changedtick_pum = CHANGEDTICK(curbuf);
+    }
+# endif
 #endif
 
     if (must_redraw)
diff --git a/src/fileio.c b/src/fileio.c
index ab216abbf..d4735f344 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -5037,9 +5037,8 @@ restore_backup:
 #ifdef FEAT_AUTOCMD
 	/* b:changedtick is always incremented in unchanged() but that
 	 * should not trigger a TextChanged event. */
-	if (last_changedtick + 1 == CHANGEDTICK(buf)
-					       && last_changedtick_buf == buf)
-	    last_changedtick = CHANGEDTICK(buf);
+	if (buf->b_last_changedtick + 1 == CHANGEDTICK(buf))
+	    buf->b_last_changedtick = CHANGEDTICK(buf);
 #endif
 	u_unchanged(buf);
 	u_update_save_nr(buf);
@@ -7851,6 +7850,7 @@ static struct event_name
     {"TermResponse",	EVENT_TERMRESPONSE},
     {"TextChanged",	EVENT_TEXTCHANGED},
     {"TextChangedI",	EVENT_TEXTCHANGEDI},
+    {"TextChangedP",	EVENT_TEXTCHANGEDP},
     {"User",		EVENT_USER},
     {"VimEnter",	EVENT_VIMENTER},
     {"VimLeave",	EVENT_VIMLEAVE},
@@ -9376,6 +9376,15 @@ has_textchangedI(void)
     return (first_autopat[(int)EVENT_TEXTCHANGEDI] != NULL);
 }
 
+/*
+ * Return TRUE when there is a TextChangedP autocommand defined.
+ */
+    int
+has_textchangedP(void)
+{
+    return (first_autopat[(int)EVENT_TEXTCHANGEDP] != NULL);
+}
+
 /*
  * Return TRUE when there is an InsertCharPre autocommand defined.
  */
diff --git a/src/globals.h b/src/globals.h
index 943bb3130..1b2b1e8bf 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -1085,8 +1085,6 @@ EXTERN pos_T	last_cursormoved	      /* for CursorMoved event */
 			= INIT_POS_T(0, 0, 0)
 # endif
 			;
-EXTERN varnumber_T last_changedtick INIT(= 0);   /* for TextChanged event */
-EXTERN buf_T	*last_changedtick_buf INIT(= NULL);
 #endif
 
 EXTERN int	postponed_split INIT(= 0);  /* for CTRL-W CTRL-] command */
diff --git a/src/main.c b/src/main.c
index 9f443aef2..971590aab 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1201,13 +1201,10 @@ main_loop(
 #ifdef FEAT_AUTOCMD
 	    /* Trigger TextChanged if b:changedtick differs. */
 	    if (!finish_op && has_textchanged()
-		    && last_changedtick != CHANGEDTICK(curbuf))
+		    && curbuf->b_last_changedtick != CHANGEDTICK(curbuf))
 	    {
-		if (last_changedtick_buf == curbuf)
-		    apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL,
-							       FALSE, curbuf);
-		last_changedtick_buf = curbuf;
-		last_changedtick = CHANGEDTICK(curbuf);
+		apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL, FALSE, curbuf);
+		curbuf->b_last_changedtick = CHANGEDTICK(curbuf);
 	    }
 #endif
 
diff --git a/src/proto/fileio.pro b/src/proto/fileio.pro
index 757963113..12d5c14ff 100644
--- a/src/proto/fileio.pro
+++ b/src/proto/fileio.pro
@@ -48,6 +48,7 @@ int has_cursormoved(void);
 int has_cursormovedI(void);
 int has_textchanged(void);
 int has_textchangedI(void);
+int has_textchangedP(void);
 int has_insertcharpre(void);
 int has_cmdundefined(void);
 int has_funcundefined(void);
diff --git a/src/structs.h b/src/structs.h
index abfb73176..3efd6ab3d 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1983,6 +1983,15 @@ struct file_buffer
 				   incremented for each change, also for undo */
 #define CHANGEDTICK(buf) ((buf)->b_ct_di.di_tv.vval.v_number)
 
+#ifdef FEAT_AUTOCMD
+    varnumber_T	b_last_changedtick; /* b:changedtick when TextChanged or
+				       TextChangedI was last triggered. */
+# ifdef FEAT_INS_EXPAND
+    varnumber_T	b_last_changedtick_pum; /* b:changedtick when TextChangedP was
+					   last triggered. */
+# endif
+#endif
+
     int		b_saving;	/* Set to TRUE if we are in the middle of
 				   saving the buffer. */
 
diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim
index 065ac4e29..d1ce56197 100644
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -1249,3 +1249,58 @@ function Test_dirchanged_auto()
   bwipe!
   call s:After_test_dirchanged()
 endfunc
+
+" Test TextChangedI and TextChangedP
+func Test_ChangedP()
+  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
diff --git a/src/version.c b/src/version.c
index 261e3d7c6..f5772a8c2 100644
--- a/src/version.c
+++ b/src/version.c
@@ -771,6 +771,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1494,
 /**/
     1493,
 /**/
diff --git a/src/vim.h b/src/vim.h
index bd8ac0872..5e9f7d6d1 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1337,8 +1337,11 @@ enum auto_event
     EVENT_TABCLOSED,		/* after closing a tab page */
     EVENT_SHELLCMDPOST,		/* after ":!cmd" */
     EVENT_SHELLFILTERPOST,	/* after ":1,2!cmd", ":w !cmd", ":r !cmd". */
-    EVENT_TEXTCHANGED,		/* text was modified */
-    EVENT_TEXTCHANGEDI,		/* text was modified in Insert mode*/
+    EVENT_TEXTCHANGED,		/* text was modified not in Insert mode */
+    EVENT_TEXTCHANGEDI,         /* text was modified in Insert mode without
+				   popup menu visible */
+    EVENT_TEXTCHANGEDP,         /* text was modified in Insert mode with popup
+				   menu visible */
     EVENT_CMDUNDEFINED,		/* command undefined */
     EVENT_OPTIONSET,		/* option was set */
     EVENT_TEXTYANKPOST,		/* after some text was yanked */
-- 
2.40.0