This patch adds asynchronous functions to vimscript. If you want to perform an
action in 700ms, simply:
let timeout_id = settimeout(700, 'echo("hello")')
To cancel the timeout before it's fired:
canceltimeout(timeout_id)
setinterval() also returns an id that can be used with canceltimeout.
The reason for this patch is simple: asynchronous functionality is needed to
implement real-time collaborative editing in Vim. This is one of the most
voted-for features (see http://www.vim.org/sponsor/vote_results.php).
Along with Matt Kaniaris, I founded Floobits to build real-time collaboration
into every editor. We wrote a plugin for Vim, but we had to use hacks to get
async behavior (abusing feedkeys or client-server). These methods had
side-effects such as breaking leaderkeys or other shortcuts. After a lot of
experimenting, we decided to try patching Vim.
Since Vim is character-driven, we had to munge some low-level input functions
to get the desired behavior. We changed gui_wait_for_chars() and mch_inchar()
so that call_timeouts() is run every ticktime milliseconds. The default
ticktime is 100ms.
This patch isn't finished yet, but it works on unix-based OSes. If the reaction
is positive, our intention is to change mch_inchar() (or something similar) in
other OS-specific files. That will get async functions working for everyone.
Even if our patch isn't the best approach, we'd love to help get async
functions in Vim. Doing so will open the door to a lot of cool plugins.
Oh, and this is the first time either myself or Matt have submitted a patch to
Vim, so please be gentle.
Sincerely,
Geoff Greer
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.
# HG changeset patch
# User Geoff Greer <[email protected]>
# Date 1378083352 25200
# Node ID 557016400d9bab434378969e3ceba1e2329af6eb
# Parent d17ef148ada4ada411ab7a88a3a678b6964d0fba
Add asynchronous functions to Vim: settimeout, setinterval, and canceltimeout.
diff -r d17ef148ada4 -r 557016400d9b Filelist
--- a/Filelist Fri Aug 30 17:29:16 2013 +0200
+++ b/Filelist Sun Sep 01 17:55:52 2013 -0700
@@ -7,6 +7,8 @@
src/arabic.c \
src/arabic.h \
src/ascii.h \
+ src/async.c \
+ src/async.h \
src/blowfish.c \
src/buffer.c \
src/charset.c \
diff -r d17ef148ada4 -r 557016400d9b src/Makefile
--- a/src/Makefile Fri Aug 30 17:29:16 2013 +0200
+++ b/src/Makefile Sun Sep 01 17:55:52 2013 -0700
@@ -1424,6 +1424,7 @@
TAGS_INCL = *.h
BASIC_SRC = \
+ async.c \
blowfish.c \
buffer.c \
charset.c \
@@ -1513,6 +1514,7 @@
#LINT_SRC = $(BASIC_SRC)
OBJ_COMMON = \
+ objects/async.o \
objects/buffer.o \
objects/blowfish.o \
objects/charset.o \
@@ -2484,6 +2486,9 @@
objects:
mkdir objects
+objects/async.o: async.c
+ $(CCC) -o $@ async.c
+
objects/blowfish.o: blowfish.c
$(CCC) -o $@ blowfish.c
diff -r d17ef148ada4 -r 557016400d9b src/async.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/async.c Sun Sep 01 17:55:52 2013 -0700
@@ -0,0 +1,49 @@
+#include "vim.h"
+
+#ifdef FEAT_ASYNC
+ void
+insert_timeout(timeout_T *to) {
+ timeout_T *cur = timeouts;
+ timeout_T *prev = NULL;
+
+ if (timeouts == NULL) {
+ timeouts = to;
+ return;
+ }
+ while (cur != NULL) {
+ if (cur->tm > to->tm) {
+ if (prev) {
+ prev->next = to;
+ } else {
+ timeouts = to;
+ }
+ to->next = cur;
+ return;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+}
+
+ void
+call_timeouts() {
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ unsigned long tm = now.tv_sec * 1000 + now.tv_usec/1000;
+ timeout_T *tmp;
+
+ while (timeouts != NULL && timeouts->tm < tm) {
+ call_func_retnr(timeouts->cmd, 0, 0, FALSE);
+ tmp = timeouts;
+ timeouts = timeouts->next;
+ if (tmp->interval == -1) {
+ free(tmp->cmd);
+ free(tmp);
+ } else {
+ tmp->tm = tm + tmp->interval;
+ insert_timeout(tmp);
+ }
+ }
+}
+
+#endif
diff -r d17ef148ada4 -r 557016400d9b src/async.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/async.h Sun Sep 01 17:55:52 2013 -0700
@@ -0,0 +1,5 @@
+
+EXTERN timeout_T *timeouts INIT(= NULL);
+
+void insert_timeout(timeout_T *to);
+void call_timeouts();
diff -r d17ef148ada4 -r 557016400d9b src/eval.c
--- a/src/eval.c Fri Aug 30 17:29:16 2013 +0200
+++ b/src/eval.c Sun Sep 01 17:55:52 2013 -0700
@@ -674,6 +674,11 @@
static void f_setloclist __ARGS((typval_T *argvars, typval_T *rettv));
static void f_setmatches __ARGS((typval_T *argvars, typval_T *rettv));
static void f_setpos __ARGS((typval_T *argvars, typval_T *rettv));
+#ifdef FEAT_ASYNC
+static void f_canceltimeout __ARGS((typval_T *argvars, typval_T *rettv));
+static void f_setinterval __ARGS((typval_T *argvars, typval_T *rettv));
+static void f_settimeout __ARGS((typval_T *argvars, typval_T *rettv));
+#endif
static void f_setqflist __ARGS((typval_T *argvars, typval_T *rettv));
static void f_setreg __ARGS((typval_T *argvars, typval_T *rettv));
static void f_settabvar __ARGS((typval_T *argvars, typval_T *rettv));
@@ -7861,6 +7866,9 @@
{"byte2line", 1, 1, f_byte2line},
{"byteidx", 2, 2, f_byteidx},
{"call", 2, 3, f_call},
+#ifdef FEAT_ASYNC
+ {"canceltimeout", 1, 1, f_canceltimeout},
+#endif
#ifdef FEAT_FLOAT
{"ceil", 1, 1, f_ceil},
#endif
@@ -8059,6 +8067,9 @@
{"serverlist", 0, 0, f_serverlist},
{"setbufvar", 3, 3, f_setbufvar},
{"setcmdpos", 1, 1, f_setcmdpos},
+#ifdef FEAT_ASYNC
+ {"setinterval", 2, 2, f_setinterval},
+#endif
{"setline", 2, 2, f_setline},
{"setloclist", 2, 3, f_setloclist},
{"setmatches", 1, 1, f_setmatches},
@@ -8067,6 +8078,9 @@
{"setreg", 2, 3, f_setreg},
{"settabvar", 3, 3, f_settabvar},
{"settabwinvar", 4, 4, f_settabwinvar},
+#ifdef FEAT_ASYNC
+ {"settimeout", 2, 2, f_settimeout},
+#endif
{"setwinvar", 3, 3, f_setwinvar},
#ifdef FEAT_CRYPT
{"sha256", 1, 1, f_sha256},
@@ -12416,6 +12430,9 @@
#ifdef FEAT_RELTIME
"reltime",
#endif
+#ifdef FEAT_ASYNC
+ "async",
+#endif
#ifdef FEAT_QUICKFIX
"quickfix",
#endif
@@ -16589,6 +16606,93 @@
#endif
}
+#ifdef FEAT_ASYNC
+static int timeout_id = 0;
+
+ static void
+set_timeout(argvars, rettv, interval)
+ typval_T *argvars;
+ typval_T *rettv;
+ int interval;
+{
+ long i = get_tv_number(&argvars[0]);
+ char_u *cmd = get_tv_string(&argvars[1]);
+ struct timeval now;
+ rettv->v_type = VAR_NUMBER;
+
+ if (i < 0) {
+ rettv->vval.v_number = -1;
+ EMSG2(_(e_invarg2), "Interval cannot be negative.");
+ return;
+ }
+
+ gettimeofday(&now, NULL);
+ timeout_T *to = malloc(sizeof(timeout_T));
+ to->id = timeout_id++;
+ rettv->vval.v_number = to->id;
+ to->tm = now.tv_sec * 1000 + now.tv_usec/1000 + i;
+ to->cmd = (char_u*)strdup((char*)cmd);
+ to->interval = interval ? i : -1;
+ to->next = NULL;
+
+ insert_timeout(to);
+}
+
+ static void
+f_setinterval(argvars, rettv)
+ typval_T *argvars;
+ typval_T *rettv;
+{
+ set_timeout(argvars, rettv, TRUE);
+}
+
+ static void
+f_settimeout(argvars, rettv)
+ typval_T *argvars;
+ typval_T *rettv;
+{
+ set_timeout(argvars, rettv, FALSE);
+}
+
+ static void
+f_canceltimeout(argvars, rettv)
+ typval_T *argvars;
+ typval_T *rettv;
+{
+ long id = get_tv_number(&argvars[0]);
+ if (id < 0) {
+ rettv->vval.v_number = -1;
+ EMSG2(_(e_invarg2), "Timeout id cannot be negative.");
+ return;
+ }
+
+ timeout_T *tmp = timeouts;
+ timeout_T *prev = NULL;
+ timeout_T *next;
+ while (tmp != NULL) {
+ next = tmp->next;
+ if (tmp->id == id) {
+ if (prev) {
+ prev->next = next;
+ } else {
+ timeouts = next;
+ }
+ free(tmp->cmd);
+ free(tmp);
+ rettv->vval.v_number = 0;
+ rettv->v_type = VAR_NUMBER;
+ return;
+ } else {
+ prev = tmp;
+ }
+ tmp = next;
+ }
+ rettv->vval.v_number = 1;
+ rettv->v_type = VAR_NUMBER;
+ EMSG2(_(e_invarg2), "Timeout id not found.");
+}
+#endif
+
/*
* "setpos()" function
*/
diff -r d17ef148ada4 -r 557016400d9b src/feature.h
--- a/src/feature.h Fri Aug 30 17:29:16 2013 +0200
+++ b/src/feature.h Sun Sep 01 17:55:52 2013 -0700
@@ -467,6 +467,13 @@
#endif
/*
+ * +async settimeout and setinterval functions.
+ */
+#if defined(FEAT_NORMAL)
+# define FEAT_ASYNC
+#endif
+
+/*
* +diff Displaying diffs in a nice way.
* Requires +windows and +autocmd.
*/
diff -r d17ef148ada4 -r 557016400d9b src/gui.c
--- a/src/gui.c Fri Aug 30 17:29:16 2013 +0200
+++ b/src/gui.c Sun Sep 01 17:55:52 2013 -0700
@@ -2898,18 +2898,31 @@
gui_mch_start_blink();
retval = FAIL;
+
+ int i = 0;
+ while (i < p_ut) {
+#ifdef FEAT_ASYNC
+ retval = gui_mch_wait_for_chars(p_tt);
+ call_timeouts();
+ i += p_tt;
+#else
+ retval = gui_mch_wait_for_chars(p_ut);
+ i += p_ut;
+#endif
+ if (retval == OK) {
+ break;
+ }
+ }
+
+#ifdef FEAT_AUTOCMD
/*
* We may want to trigger the CursorHold event. First wait for
* 'updatetime' and if nothing is typed within that time put the
* K_CURSORHOLD key in the input buffer.
*/
- if (gui_mch_wait_for_chars(p_ut) == OK)
- retval = OK;
-#ifdef FEAT_AUTOCMD
- else if (trigger_cursorhold())
+ if (retval == FAIL && trigger_cursorhold())
{
char_u buf[3];
-
/* Put K_CURSORHOLD in the input buffer. */
buf[0] = CSI;
buf[1] = KS_EXTRA;
@@ -2920,13 +2933,6 @@
}
#endif
- if (retval == FAIL)
- {
- /* Blocking wait. */
- before_blocking();
- retval = gui_mch_wait_for_chars(-1L);
- }
-
gui_mch_stop_blink();
return retval;
}
diff -r d17ef148ada4 -r 557016400d9b src/option.c
--- a/src/option.c Fri Aug 30 17:29:16 2013 +0200
+++ b/src/option.c Sun Sep 01 17:55:52 2013 -0700
@@ -2590,6 +2590,11 @@
(char_u *)NULL, PV_NONE,
#endif
{(char_u *)"", (char_u *)0L} SCRIPTID_INIT},
+#ifdef FEAT_ASYNC
+ {"ticktime", "tt", P_NUM|P_VI_DEF,
+ (char_u *)&p_tt, PV_NONE,
+ {(char_u *)100L, (char_u *)0L} SCRIPTID_INIT},
+#endif
{"tildeop", "top", P_BOOL|P_VI_DEF|P_VIM,
(char_u *)&p_to, PV_NONE,
{(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT},
diff -r d17ef148ada4 -r 557016400d9b src/option.h
--- a/src/option.h Fri Aug 30 17:29:16 2013 +0200
+++ b/src/option.h Sun Sep 01 17:55:52 2013 -0700
@@ -795,6 +795,9 @@
#ifdef FEAT_INS_EXPAND
EXTERN char_u *p_tsr; /* 'thesaurus' */
#endif
+#ifdef FEAT_ASYNC
+EXTERN long p_tt; /* 'ticktime' */
+#endif
EXTERN int p_ttimeout; /* 'ttimeout' */
EXTERN long p_ttm; /* 'ttimeoutlen' */
EXTERN int p_tbi; /* 'ttybuiltin' */
diff -r d17ef148ada4 -r 557016400d9b src/os_unix.c
--- a/src/os_unix.c Fri Aug 30 17:29:16 2013 +0200
+++ b/src/os_unix.c Sun Sep 01 17:55:52 2013 -0700
@@ -379,6 +379,7 @@
int tb_change_cnt;
{
int len;
+ int retval = FAIL;
#ifdef FEAT_NETBEANS_INTG
/* Process the queued netbeans messages. */
@@ -393,7 +394,8 @@
if (wtime >= 0)
{
while (WaitForChar(wtime) == 0) /* no character available */
- {
+ {
+ call_timeouts();
if (!do_resize) /* return if not interrupted by resize */
return 0;
handle_resize();
@@ -410,7 +412,22 @@
* flush all the swap files to disk.
* Also done when interrupted by SIGWINCH.
*/
- if (WaitForChar(p_ut) == 0)
+
+#ifdef FEAT_ASYNC
+ int t = 0;
+ while (t < p_ut) {
+ retval = WaitForChar(p_tt);
+ call_timeouts();
+ t += p_tt;
+ if (retval == OK) {
+ break;
+ }
+ }
+#else
+ retval = WaitForChar(p_ut);
+#endif
+
+ if (retval == FAIL)
{
#ifdef FEAT_AUTOCMD
if (trigger_cursorhold() && maxlen >= 3
@@ -440,12 +457,29 @@
* We want to be interrupted by the winch signal
* or by an event on the monitored file descriptors.
*/
+
+
+ #ifdef FEAT_ASYNC
+ while (TRUE) {
+ retval = WaitForChar(p_tt);
+ call_timeouts();
+ if (retval == OK) {
+ break;
+ }
+ if (do_resize) {
+ /* interrupted by SIGWINCH signal */
+ handle_resize();
+ return 0;
+ }
+ }
+ #else
if (WaitForChar(-1L) == 0)
{
if (do_resize) /* interrupted by SIGWINCH signal */
handle_resize();
return 0;
}
+ #endif
#endif
/* If input was put directly in typeahead buffer bail out here. */
diff -r d17ef148ada4 -r 557016400d9b src/structs.h
--- a/src/structs.h Fri Aug 30 17:29:16 2013 +0200
+++ b/src/structs.h Sun Sep 01 17:55:52 2013 -0700
@@ -2540,3 +2540,17 @@
UINT32_T state[8];
char_u buffer[64];
} context_sha256_T;
+
+#ifdef FEAT_ASYNC
+/*
+ * Used for async settimeout/interval.
+ */
+struct timeout_T {
+ int id; /* timeout/interval id */
+ int interval; /* interval period if interval, otherwise -1 */
+ unsigned long tm; /* time to fire (epoch milliseconds) */
+ char_u *cmd; /* vim command to run */
+ struct timeout_T *next; /* pointer to next timeout in linked list */
+};
+typedef struct timeout_T timeout_T;
+#endif
diff -r d17ef148ada4 -r 557016400d9b src/version.c
--- a/src/version.c Fri Aug 30 17:29:16 2013 +0200
+++ b/src/version.c Sun Sep 01 17:55:52 2013 -0700
@@ -77,6 +77,11 @@
#else
"-arabic",
#endif
+#ifdef FEAT_ASYNC
+ "+async",
+#else
+ "-async",
+#endif
#ifdef FEAT_AUTOCMD
"+autocmd",
#else
diff -r d17ef148ada4 -r 557016400d9b src/vim.h
--- a/src/vim.h Fri Aug 30 17:29:16 2013 +0200
+++ b/src/vim.h Sun Sep 01 17:55:52 2013 -0700
@@ -2123,6 +2123,10 @@
# endif
#endif
+#ifdef FEAT_ASYNC
+# include "async.h"
+#endif
+
#if defined(FEAT_BROWSE) && defined(GTK_CHECK_VERSION)
# if GTK_CHECK_VERSION(2,4,0)
# define USE_FILE_CHOOSER