]> granicus.if.org Git - vim/commitdiff
patch 8.0.1817: a timer may change v:count unexpectedly v8.0.1817
authorBram Moolenaar <Bram@vim.org>
Sat, 12 May 2018 13:38:26 +0000 (15:38 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 12 May 2018 13:38:26 +0000 (15:38 +0200)
Problem:    A timer may change v:count unexpectedly.
Solution:   Save and restore v:count and similar variables when a timer
            callback is invoked. (closes #2897)

src/eval.c
src/ex_cmds2.c
src/proto/eval.pro
src/structs.h
src/testdir/test_timers.vim
src/version.c

index 63f8bde3294244cbf67a2d09203a5834eb120ecf..b787474d108e8bc8ada95dd2b828cf5aac00af27 100644 (file)
@@ -6461,6 +6461,29 @@ set_vcount(
     vimvars[VV_COUNT1].vv_nr = count1;
 }
 
+/*
+ * Save variables that might be changed as a side effect.  Used when executing
+ * a timer callback.
+ */
+    void
+save_vimvars(vimvars_save_T *vvsave)
+{
+    vvsave->vv_prevcount = vimvars[VV_PREVCOUNT].vv_nr;
+    vvsave->vv_count = vimvars[VV_COUNT].vv_nr;
+    vvsave->vv_count1 = vimvars[VV_COUNT1].vv_nr;
+}
+
+/*
+ * Restore variables saved by save_vimvars().
+ */
+    void
+restore_vimvars(vimvars_save_T *vvsave)
+{
+    vimvars[VV_PREVCOUNT].vv_nr = vvsave->vv_prevcount;
+    vimvars[VV_COUNT].vv_nr = vvsave->vv_count;
+    vimvars[VV_COUNT1].vv_nr = vvsave->vv_count1;
+}
+
 /*
  * Set string v: variable to a copy of "val".
  */
index d27d0cdf593ddbcb25a411ccd12c36e8d7c65f06..01bc357f436daf64fbf3e9272279a67f2c4130f9 100644 (file)
@@ -1336,6 +1336,8 @@ check_due_timer(void)
        this_due = proftime_time_left(&timer->tr_due, &now);
        if (this_due <= 1)
        {
+           /* Save and restore a lot of flags, because the timer fires while
+            * waiting for a character, which might be halfway a command. */
            int save_timer_busy = timer_busy;
            int save_vgetc_busy = vgetc_busy;
            int save_did_emsg = did_emsg;
@@ -1345,6 +1347,7 @@ check_due_timer(void)
            int save_did_throw = did_throw;
            int save_ex_pressedreturn = get_pressedreturn();
            except_T *save_current_exception = current_exception;
+           vimvars_save_T vvsave;
 
            /* Create a scope for running the timer callback, ignoring most of
             * the current scope, such as being inside a try/catch. */
@@ -1357,6 +1360,7 @@ check_due_timer(void)
            trylevel = 0;
            did_throw = FALSE;
            current_exception = NULL;
+           save_vimvars(&vvsave);
 
            timer->tr_firing = TRUE;
            timer_callback(timer);
@@ -1373,6 +1377,7 @@ check_due_timer(void)
            trylevel = save_trylevel;
            did_throw = save_did_throw;
            current_exception = save_current_exception;
+           restore_vimvars(&vvsave);
            if (must_redraw != 0)
                need_update_screen = TRUE;
            must_redraw = must_redraw > save_must_redraw
index 3bbe8154ac897fd40adc396ce112d1b5cafe6868..a096156ae06e6de3dc3edf82fd25a71f87ca1d6f 100644 (file)
@@ -67,6 +67,8 @@ list_T *get_vim_var_list(int idx);
 dict_T *get_vim_var_dict(int idx);
 void set_vim_var_char(int c);
 void set_vcount(long count, long count1, int set_prevcount);
+void save_vimvars(vimvars_save_T *vvsave);
+void restore_vimvars(vimvars_save_T *vvsave);
 void set_vim_var_string(int idx, char_u *val, int len);
 void set_vim_var_list(int idx, list_T *val);
 void set_vim_var_dict(int idx, dict_T *val);
index 8f35a7387110841cf3c06a606afcd91485ffc5a8..5c4a74cac7210250bb134ad41dc18eb0e0782c22 100644 (file)
@@ -3423,3 +3423,9 @@ typedef struct {
     int                save_opcount;
     tasave_T   tabuf;
 } save_state_T;
+
+typedef struct {
+    varnumber_T vv_prevcount;
+    varnumber_T vv_count;
+    varnumber_T vv_count1;
+} vimvars_save_T;
index 79a5ba58c1219838c60243486428033913b49176..bccd31b93daa00da24f2130458504052aa5b08e6 100644 (file)
@@ -5,6 +5,7 @@ if !has('timers')
 endif
 
 source shared.vim
+source screendump.vim
 
 func MyHandler(timer)
   let g:val += 1
@@ -260,4 +261,35 @@ func Test_ex_mode()
   call timer_stop(timer)
 endfunc
 
+func Test_restore_count()
+  if !CanRunVimInTerminal()
+    return
+  endif
+  " Check that v:count is saved and restored, not changed by a timer.
+  call writefile([
+        \ 'nnoremap <expr><silent> L v:count ? v:count . "l" : "l"',
+        \ 'func Doit(id)',
+        \ '  normal 3j',
+        \ 'endfunc',
+        \ 'call timer_start(100, "Doit")',
+       \ ], 'Xtrcscript')
+  call writefile([
+        \ '1-1234',
+        \ '2-1234',
+        \ '3-1234',
+       \ ], 'Xtrctext')
+  let buf = RunVimInTerminal('-S Xtrcscript Xtrctext', {})
+
+  " Wait for the timer to move the cursor to the third line.
+  call WaitForAssert({-> assert_equal(3, term_getcursor(buf)[0])})
+  call assert_equal(1, term_getcursor(buf)[1])
+  " Now check that v:count has not been set to 3
+  call term_sendkeys(buf, 'L')
+  call WaitForAssert({-> assert_equal(2, term_getcursor(buf)[1])})
+
+  call StopVimInTerminal(buf)
+  call delete('Xtrcscript')
+  call delete('Xtrctext')
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index f94090fb4713620d01174a877f77e4e14f022075..81f867b981c2442d1528e2bd146c5f2bc4afd34b 100644 (file)
@@ -761,6 +761,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1817,
 /**/
     1816,
 /**/