summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2018-05-12 15:38:26 +0200
committerBram Moolenaar <Bram@vim.org>2018-05-12 15:38:26 +0200
commitb0f42ba60d9e6d101d103421ba0c351811615c15 (patch)
tree083b82fcd12fc95a90d779898a6a2b9939ef7837
parentff3be4fe1e2e723de48b826cb992c798e296c41e (diff)
patch 8.0.1817: a timer may change v:count unexpectedlyv8.0.1817
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)
-rw-r--r--src/eval.c23
-rw-r--r--src/ex_cmds2.c5
-rw-r--r--src/proto/eval.pro2
-rw-r--r--src/structs.h6
-rw-r--r--src/testdir/test_timers.vim32
-rw-r--r--src/version.c2
6 files changed, 70 insertions, 0 deletions
diff --git a/src/eval.c b/src/eval.c
index 63f8bde329..b787474d10 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -6462,6 +6462,29 @@ set_vcount(
}
/*
+ * 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".
*/
void
diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c
index d27d0cdf59..01bc357f43 100644
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -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
diff --git a/src/proto/eval.pro b/src/proto/eval.pro
index 3bbe8154ac..a096156ae0 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -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);
diff --git a/src/structs.h b/src/structs.h
index 8f35a73871..5c4a74cac7 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -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;
diff --git a/src/testdir/test_timers.vim b/src/testdir/test_timers.vim
index 79a5ba58c1..bccd31b93d 100644
--- a/src/testdir/test_timers.vim
+++ b/src/testdir/test_timers.vim
@@ -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
diff --git a/src/version.c b/src/version.c
index f94090fb47..81f867b981 100644
--- a/src/version.c
+++ b/src/version.c
@@ -762,6 +762,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1817,
+/**/
1816,
/**/
1815,