To: vim_dev@googlegroups.com Subject: Patch 7.4.1310 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 7.4.1310 Problem: Jobs don't open a channel. Solution: Create pipes and add them to the channel. Add ch_logfile(). Only Unix for now. Files: src/channel.c, src/eval.c, src/os_unix.c, src/structs.h, src/gui_w48.c, src/proto/channel.pro, src/testdir/test_channel.vim, src/testdir/test_channel_pipe.py, runtime/doc/eval.txt *** ../vim-7.4.1309/src/channel.c 2016-02-10 21:07:09.016869231 +0100 --- src/channel.c 2016-02-13 16:51:03.982964196 +0100 *************** *** 14,35 **** #if defined(FEAT_CHANNEL) || defined(PROTO) - /* - * Change the zero to 1 to enable debugging. - * This will write a file "channel_debug.log". - */ - #if 0 - # define CHERROR(fmt, arg) cherror(fmt, arg) - # define CHLOG(idx, send, buf) chlog(idx, send, buf) - # define CHFILE "channel_debug.log" - - static void cherror(char *fmt, char *arg); - static void chlog(int send, char_u *buf); - #else - # define CHERROR(fmt, arg) - # define CHLOG(idx, send, buf) - #endif - /* TRUE when netbeans is running with a GUI. */ #ifdef FEAT_GUI # define CH_HAS_GUI (gui.in_use || gui.starting) --- 14,19 ---- *************** *** 70,132 **** extern HWND s_hwnd; /* Gvim's Window handle */ #endif - struct readqueue - { - char_u *buffer; - struct readqueue *next; - struct readqueue *prev; - }; - typedef struct readqueue readq_T; - - struct jsonqueue - { - typval_T *value; - struct jsonqueue *next; - struct jsonqueue *prev; - }; - typedef struct jsonqueue jsonq_T; - - struct cbqueue - { - char_u *callback; - int seq_nr; - struct cbqueue *next; - struct cbqueue *prev; - }; - typedef struct cbqueue cbq_T; - - typedef struct { - sock_T ch_fd; /* the socket, -1 for a closed channel */ - int ch_idx; /* used by channel_poll_setup() */ - readq_T ch_head; /* dummy node, header for circular queue */ - - int ch_error; /* When TRUE an error was reported. Avoids giving - * pages full of error messages when the other side - * has exited, only mention the first error until the - * connection works again. */ - #ifdef FEAT_GUI_X11 - XtInputId ch_inputHandler; /* Cookie for input */ - #endif - #ifdef FEAT_GUI_GTK - gint ch_inputHandler; /* Cookie for input */ - #endif - #ifdef WIN32 - int ch_inputHandler; /* simply ret.value of WSAAsyncSelect() */ - #endif - - void (*ch_close_cb)(void); /* callback for when channel is closed */ - - int ch_block_id; /* ID that channel_read_json_block() is - waiting for */ - char_u *ch_callback; /* function to call when a msg is not handled */ - cbq_T ch_cb_head; /* dummy node for pre-request callbacks */ - - ch_mode_T ch_mode; - jsonq_T ch_json_head; /* dummy node, header for circular queue */ - - int ch_timeout; /* request timeout in msec */ - } channel_T; - /* * Information about all channels. * There can be gaps for closed channels, they will be reused later. --- 54,59 ---- *************** *** 134,143 **** static channel_T *channels = NULL; static int channel_count = 0; ! /* ! * TODO: open debug file when desired. ! */ ! FILE *debugfd = NULL; #ifdef _WIN32 # undef PERROR --- 61,167 ---- static channel_T *channels = NULL; static int channel_count = 0; ! /* Log file opened with ch_logfile(). */ ! static FILE *log_fd = NULL; ! ! void ! ch_logfile(FILE *file) ! { ! if (log_fd != NULL) ! fclose(log_fd); ! log_fd = file; ! if (log_fd != NULL) ! fprintf(log_fd, "==== start log session ====\n"); ! } ! ! static void ! ch_log_lead(char *what, int ch_idx) ! { ! if (log_fd != NULL) ! { ! if (ch_idx >= 0) ! fprintf(log_fd, "%son %d: ", what, ch_idx); ! else ! fprintf(log_fd, "%s: ", what); ! } ! } ! ! static void ! ch_log(int ch_idx, char *msg) ! { ! if (log_fd != NULL) ! { ! ch_log_lead("", ch_idx); ! fputs(msg, log_fd); ! fflush(log_fd); ! } ! } ! ! static void ! ch_logn(int ch_idx, char *msg, int nr) ! { ! if (log_fd != NULL) ! { ! ch_log_lead("", ch_idx); ! fprintf(log_fd, msg, nr); ! fflush(log_fd); ! } ! } ! ! static void ! ch_logs(int ch_idx, char *msg, char *name) ! { ! if (log_fd != NULL) ! { ! ch_log_lead("", ch_idx); ! fprintf(log_fd, msg, name); ! fflush(log_fd); ! } ! } ! ! static void ! ch_logsn(int ch_idx, char *msg, char *name, int nr) ! { ! if (log_fd != NULL) ! { ! ch_log_lead("", ch_idx); ! fprintf(log_fd, msg, name, nr); ! fflush(log_fd); ! } ! } ! ! static void ! ch_error(int ch_idx, char *msg) ! { ! if (log_fd != NULL) ! { ! ch_log_lead("ERR ", ch_idx); ! fputs(msg, log_fd); ! fflush(log_fd); ! } ! } ! ! static void ! ch_errorn(int ch_idx, char *msg, int nr) ! { ! if (log_fd != NULL) ! { ! ch_log_lead("ERR ", ch_idx); ! fprintf(log_fd, msg, nr); ! fflush(log_fd); ! } ! } ! ! static void ! ch_errors(int ch_idx, char *msg, char *arg) ! { ! if (log_fd != NULL) ! { ! ch_log_lead("ERR ", ch_idx); ! fprintf(log_fd, msg, arg); ! fflush(log_fd); ! } ! } #ifdef _WIN32 # undef PERROR *************** *** 181,218 **** } #endif ! /* ! * Add a new channel slot, return the index. ! * The channel isn't actually used into ch_fd is set >= 0; ! * Returns -1 if all channels are in use. ! */ ! static int ! add_channel(void) { - int idx; channel_T *ch; ! if (channels != NULL) ! { ! for (idx = 0; idx < channel_count; ++idx) ! if (channels[idx].ch_fd < 0) ! /* re-use a closed channel slot */ ! return idx; ! if (channel_count == MAX_OPEN_CHANNELS) ! return -1; ! } ! else ! { ! channels = (channel_T *)alloc((int)sizeof(channel_T) ! * MAX_OPEN_CHANNELS); ! if (channels == NULL) ! return -1; ! } ! ! ch = &channels[channel_count]; (void)vim_memset(ch, 0, sizeof(channel_T)); ! ch->ch_fd = (sock_T)-1; #ifdef FEAT_GUI_X11 ch->ch_inputHandler = (XtInputId)NULL; #endif --- 205,224 ---- } #endif ! static void ! init_channel(int ch_idx) { channel_T *ch; ! ch = &channels[ch_idx]; (void)vim_memset(ch, 0, sizeof(channel_T)); ! ch->ch_sock = (sock_T)-1; ! #ifdef CHANNEL_PIPES ! ch->ch_in = -1; ! ch->ch_out = -1; ! ch->ch_err = -1; ! #endif #ifdef FEAT_GUI_X11 ch->ch_inputHandler = (XtInputId)NULL; #endif *************** *** 231,237 **** --- 237,276 ---- ch->ch_json_head.prev = &ch->ch_json_head; ch->ch_timeout = 2000; + } + + /* + * Add a new channel slot, return the index. + * The channel isn't actually used into ch_sock is set >= 0; + * Returns -1 if all channels are in use. + */ + int + add_channel(void) + { + int ch_idx; + if (channels != NULL) + { + for (ch_idx = 0; ch_idx < channel_count; ++ch_idx) + if (!channel_is_open(ch_idx)) + { + /* re-use a closed channel slot */ + init_channel(ch_idx); + ch_log(ch_idx, "Opening channel (used before)\n"); + return ch_idx; + } + if (channel_count == MAX_OPEN_CHANNELS) + return -1; + } + else + { + channels = (channel_T *)alloc((int)sizeof(channel_T) + * MAX_OPEN_CHANNELS); + if (channels == NULL) + return -1; + } + init_channel(channel_count); + ch_log(channel_count, "Opening new channel\n"); return channel_count++; } *************** *** 245,251 **** int *unused1 UNUSED, XtInputId *unused2 UNUSED) { ! channel_read((int)(long)clientData); } #endif --- 284,290 ---- int *unused1 UNUSED, XtInputId *unused2 UNUSED) { ! channel_read((int)(long)clientData, FALSE, "messageFromNetbeans"); } #endif *************** *** 255,281 **** gint unused1 UNUSED, GdkInputCondition unused2 UNUSED) { ! channel_read((int)(long)clientData); } #endif static void ! channel_gui_register(int idx) { ! channel_T *channel = &channels[idx]; if (!CH_HAS_GUI) return; # ifdef FEAT_GUI_X11 /* tell notifier we are interested in being called * when there is input on the editor connection socket */ if (channel->ch_inputHandler == (XtInputId)NULL) channel->ch_inputHandler = ! XtAppAddInput((XtAppContext)app_context, channel->ch_fd, (XtPointer)(XtInputReadMask + XtInputExceptMask), ! messageFromNetbeans, (XtPointer)(long)idx); # else # ifdef FEAT_GUI_GTK /* --- 294,321 ---- gint unused1 UNUSED, GdkInputCondition unused2 UNUSED) { ! channel_read((int)(long)clientData, FALSE, "messageFromNetbeans"); } #endif static void ! channel_gui_register(int ch_idx) { ! channel_T *channel = &channels[ch_idx]; if (!CH_HAS_GUI) return; + /* TODO: pipes */ # ifdef FEAT_GUI_X11 /* tell notifier we are interested in being called * when there is input on the editor connection socket */ if (channel->ch_inputHandler == (XtInputId)NULL) channel->ch_inputHandler = ! XtAppAddInput((XtAppContext)app_context, channel->ch_sock, (XtPointer)(XtInputReadMask + XtInputExceptMask), ! messageFromNetbeans, (XtPointer)(long)ch_idx); # else # ifdef FEAT_GUI_GTK /* *************** *** 284,292 **** */ if (channel->ch_inputHandler == 0) channel->ch_inputHandler = ! gdk_input_add((gint)channel->ch_fd, (GdkInputCondition) ((int)GDK_INPUT_READ + (int)GDK_INPUT_EXCEPTION), ! messageFromNetbeans, (gpointer)(long)idx); # else # ifdef FEAT_GUI_W32 /* --- 324,332 ---- */ if (channel->ch_inputHandler == 0) channel->ch_inputHandler = ! gdk_input_add((gint)channel->ch_sock, (GdkInputCondition) ((int)GDK_INPUT_READ + (int)GDK_INPUT_EXCEPTION), ! messageFromNetbeans, (gpointer)(long)ch_idx); # else # ifdef FEAT_GUI_W32 /* *************** *** 295,301 **** */ if (channel->ch_inputHandler == -1) channel->ch_inputHandler = ! WSAAsyncSelect(channel->ch_fd, s_hwnd, WM_NETBEANS, FD_READ); # endif # endif # endif --- 335,341 ---- */ if (channel->ch_inputHandler == -1) channel->ch_inputHandler = ! WSAAsyncSelect(channel->ch_sock, s_hwnd, WM_NETBEANS, FD_READ); # endif # endif # endif *************** *** 311,325 **** int i; for (i = 0; i < channel_count; ++i) ! if (channels[i].ch_fd >= 0) channel_gui_register(i); } static void ! channel_gui_unregister(int idx) { ! channel_T *channel = &channels[idx]; # ifdef FEAT_GUI_X11 if (channel->ch_inputHandler != (XtInputId)NULL) { --- 351,367 ---- int i; for (i = 0; i < channel_count; ++i) ! /* TODO: pipes */ ! if (channels[i].ch_sock >= 0) channel_gui_register(i); } static void ! channel_gui_unregister(int ch_idx) { ! channel_T *channel = &channels[ch_idx]; + /* TODO: pipes */ # ifdef FEAT_GUI_X11 if (channel->ch_inputHandler != (XtInputId)NULL) { *************** *** 337,343 **** # ifdef FEAT_GUI_W32 if (channel->ch_inputHandler == 0) { ! WSAAsyncSelect(channel->ch_fd, s_hwnd, 0, 0); channel->ch_inputHandler = -1; } # endif --- 379,385 ---- # ifdef FEAT_GUI_W32 if (channel->ch_inputHandler == 0) { ! WSAAsyncSelect(channel->ch_sock, s_hwnd, 0, 0); channel->ch_inputHandler = -1; } # endif *************** *** 348,354 **** #endif /* ! * Open a channel to "hostname":"port". * Returns the channel number for success. * Returns a negative number for failure. */ --- 390,396 ---- #endif /* ! * Open a socket channel to "hostname":"port". * Returns the channel number for success. * Returns a negative number for failure. */ *************** *** 364,387 **** #else int port = port_in; #endif ! int idx; int ret; #ifdef WIN32 channel_init_winsock(); #endif ! idx = add_channel(); ! if (idx < 0) { ! CHERROR("All channels are in use\n", ""); EMSG(_("E897: All channels are in use")); return -1; } if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1) { ! CHERROR("error in socket() in channel_open()\n", ""); PERROR("E898: socket() in channel_open()"); return -1; } --- 406,429 ---- #else int port = port_in; #endif ! int ch_idx; int ret; #ifdef WIN32 channel_init_winsock(); #endif ! ch_idx = add_channel(); ! if (ch_idx < 0) { ! ch_error(-1, "All channels are in use.\n"); EMSG(_("E897: All channels are in use")); return -1; } if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1) { ! ch_error(-1, "in socket() in channel_open().\n"); PERROR("E898: socket() in channel_open()"); return -1; } *************** *** 393,399 **** server.sin_port = htons(port); if ((host = gethostbyname(hostname)) == NULL) { ! CHERROR("error in gethostbyname() in channel_open()\n", ""); PERROR("E901: gethostbyname() in channel_open()"); sock_close(sd); return -1; --- 435,441 ---- server.sin_port = htons(port); if ((host = gethostbyname(hostname)) == NULL) { ! ch_error(-1, "in gethostbyname() in channel_open()\n"); PERROR("E901: gethostbyname() in channel_open()"); sock_close(sd); return -1; *************** *** 412,432 **** ) { SOCK_ERRNO; ! CHERROR("channel_open: Connect failed with errno %d\n", errno); sock_close(sd); return -1; } } /* Try connecting to the server. */ ret = connect(sd, (struct sockaddr *)&server, sizeof(server)); SOCK_ERRNO; if (ret < 0) { ! if (errno != EWOULDBLOCK && errno != EINPROGRESS) { ! CHERROR("channel_open: Connect failed with errno %d\n", errno); ! CHERROR("Cannot connect to port\n", ""); PERROR(_("E902: Cannot connect to port")); sock_close(sd); return -1; --- 454,480 ---- ) { SOCK_ERRNO; ! ch_errorn(-1, "channel_open: Connect failed with errno %d\n", ! errno); sock_close(sd); return -1; } } /* Try connecting to the server. */ + ch_logsn(-1, "Connecting to %s port %d", hostname, port); ret = connect(sd, (struct sockaddr *)&server, sizeof(server)); SOCK_ERRNO; if (ret < 0) { ! if (errno != EWOULDBLOCK ! #ifdef EINPROGRESS ! && errno != EINPROGRESS ! #endif ! ) { ! ch_errorn(-1, "channel_open: Connect failed with errno %d\n", ! errno); PERROR(_("E902: Cannot connect to port")); sock_close(sd); return -1; *************** *** 446,453 **** if (ret < 0) { SOCK_ERRNO; ! CHERROR("channel_open: Connect failed with errno %d\n", errno); ! CHERROR("Cannot connect to port\n", ""); PERROR(_("E902: Cannot connect to port")); sock_close(sd); return -1; --- 494,501 ---- if (ret < 0) { SOCK_ERRNO; ! ch_errorn(-1, "channel_open: Connect failed with errno %d\n", ! errno); PERROR(_("E902: Cannot connect to port")); sock_close(sd); return -1; *************** *** 477,483 **** if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1) { SOCK_ERRNO; ! CHERROR("socket() retry in channel_open()\n", ""); PERROR("E900: socket() retry in channel_open()"); return -1; } --- 525,531 ---- if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1) { SOCK_ERRNO; ! ch_log(-1, "socket() retry in channel_open()\n"); PERROR("E900: socket() retry in channel_open()"); return -1; } *************** *** 490,496 **** while (retries-- && ((errno == ECONNREFUSED) || (errno == EINTR))) { ! CHERROR("retrying...\n", ""); mch_delay(3000L, TRUE); ui_breakcheck(); if (got_int) --- 538,544 ---- while (retries-- && ((errno == ECONNREFUSED) || (errno == EINTR))) { ! ch_log(-1, "retrying...\n"); mch_delay(3000L, TRUE); ui_breakcheck(); if (got_int) *************** *** 509,515 **** if (!success) { /* Get here when the server can't be found. */ ! CHERROR("Cannot connect to port after retry\n", ""); PERROR(_("E899: Cannot connect to port after retry2")); sock_close(sd); return -1; --- 557,563 ---- if (!success) { /* Get here when the server can't be found. */ ! ch_error(-1, "Cannot connect to port after retry\n"); PERROR(_("E899: Cannot connect to port after retry2")); sock_close(sd); return -1; *************** *** 517,567 **** } } ! channels[idx].ch_fd = sd; ! channels[idx].ch_close_cb = close_cb; #ifdef FEAT_GUI ! channel_gui_register(idx); #endif ! return idx; } /* ! * Set the json mode of channel "idx" to "ch_mode". */ void ! channel_set_json_mode(int idx, ch_mode_T ch_mode) { ! channels[idx].ch_mode = ch_mode; } /* ! * Set the read timeout of channel "idx". */ void ! channel_set_timeout(int idx, int timeout) { ! channels[idx].ch_timeout = timeout; } /* ! * Set the callback for channel "idx". */ void ! channel_set_callback(int idx, char_u *callback) { ! vim_free(channels[idx].ch_callback); ! channels[idx].ch_callback = vim_strsave(callback); } /* ! * Set the callback for channel "idx" for the response with "id". */ void ! channel_set_req_callback(int idx, char_u *callback, int id) { ! cbq_T *cbhead = &channels[idx].ch_cb_head; cbq_T *item = (cbq_T *)alloc((int)sizeof(cbq_T)); if (item != NULL) --- 565,633 ---- } } ! channels[ch_idx].ch_sock = sd; ! channels[ch_idx].ch_close_cb = close_cb; #ifdef FEAT_GUI ! channel_gui_register(ch_idx); #endif ! return ch_idx; ! } ! ! #if defined(CHANNEL_PIPES) || defined(PROTO) ! void ! channel_set_pipes(int ch_idx, int in, int out, int err) ! { ! channel_T *channel = &channels[ch_idx]; ! ! channel->ch_in = in; ! channel->ch_out = out; ! channel->ch_err = err; ! } ! #endif ! ! void ! channel_set_job(int ch_idx, job_T *job) ! { ! channels[ch_idx].ch_job = job; } /* ! * Set the json mode of channel "ch_idx" to "ch_mode". */ void ! channel_set_json_mode(int ch_idx, ch_mode_T ch_mode) { ! channels[ch_idx].ch_mode = ch_mode; } /* ! * Set the read timeout of channel "ch_idx". */ void ! channel_set_timeout(int ch_idx, int timeout) { ! channels[ch_idx].ch_timeout = timeout; } /* ! * Set the callback for channel "ch_idx". */ void ! channel_set_callback(int ch_idx, char_u *callback) { ! vim_free(channels[ch_idx].ch_callback); ! channels[ch_idx].ch_callback = vim_strsave(callback); } /* ! * Set the callback for channel "ch_idx" for the response with "id". */ void ! channel_set_req_callback(int ch_idx, char_u *callback, int id) { ! cbq_T *cbhead = &channels[ch_idx].ch_cb_head; cbq_T *item = (cbq_T *)alloc((int)sizeof(cbq_T)); if (item != NULL) *************** *** 576,591 **** } /* ! * Invoke the "callback" on channel "idx". */ static void ! invoke_callback(int idx, char_u *callback, typval_T *argv) { typval_T rettv; int dummy; argv[0].v_type = VAR_NUMBER; ! argv[0].vval.v_number = idx; call_func(callback, (int)STRLEN(callback), &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL); --- 642,657 ---- } /* ! * Invoke the "callback" on channel "ch_idx". */ static void ! invoke_callback(int ch_idx, char_u *callback, typval_T *argv) { typval_T rettv; int dummy; argv[0].v_type = VAR_NUMBER; ! argv[0].vval.v_number = ch_idx; call_func(callback, (int)STRLEN(callback), &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL); *************** *** 602,610 **** * Returns NULL if there is nothing. */ char_u * ! channel_get(int idx) { ! readq_T *head = &channels[idx].ch_head; readq_T *node; char_u *p; --- 668,676 ---- * Returns NULL if there is nothing. */ char_u * ! channel_get(int ch_idx) { ! readq_T *head = &channels[ch_idx].ch_head; readq_T *node; char_u *p; *************** *** 623,645 **** * Returns the whole buffer contents concatenated. */ static char_u * ! channel_get_all(int idx) { /* Concatenate everything into one buffer. * TODO: avoid multiple allocations. */ ! while (channel_collapse(idx) == OK) ; ! return channel_get(idx); } /* ! * Collapses the first and second buffer in the channel "idx". * Returns FAIL if that is not possible. */ int ! channel_collapse(int idx) { ! readq_T *head = &channels[idx].ch_head; readq_T *node = head->next; char_u *p; --- 689,711 ---- * Returns the whole buffer contents concatenated. */ static char_u * ! channel_get_all(int ch_idx) { /* Concatenate everything into one buffer. * TODO: avoid multiple allocations. */ ! while (channel_collapse(ch_idx) == OK) ; ! return channel_get(ch_idx); } /* ! * Collapses the first and second buffer in the channel "ch_idx". * Returns FAIL if that is not possible. */ int ! channel_collapse(int ch_idx) { ! readq_T *head = &channels[ch_idx].ch_head; readq_T *node = head->next; char_u *p; *************** *** 799,810 **** } /* ! * Execute a command received over channel "idx". * "cmd" is the command string, "arg2" the second argument. * "arg3" is the third argument, NULL if missing. */ static void ! channel_exe_cmd(int idx, char_u *cmd, typval_T *arg2, typval_T *arg3) { char_u *arg; --- 865,876 ---- } /* ! * Execute a command received over channel "ch_idx". * "cmd" is the command string, "arg2" the second argument. * "arg3" is the third argument, NULL if missing. */ static void ! channel_exe_cmd(int ch_idx, char_u *cmd, typval_T *arg2, typval_T *arg3) { char_u *arg; *************** *** 862,868 **** typval_T *tv; typval_T err_tv; char_u *json = NULL; ! channel_T *channel = &channels[idx]; int options = channel->ch_mode == MODE_JS ? JSON_JS : 0; /* Don't pollute the display with errors. */ --- 928,934 ---- typval_T *tv; typval_T err_tv; char_u *json = NULL; ! channel_T *channel = &channels[ch_idx]; int options = channel->ch_mode == MODE_JS ? JSON_JS : 0; /* Don't pollute the display with errors. */ *************** *** 885,891 **** } if (json != NULL) { ! channel_send(idx, json, "eval"); vim_free(json); } } --- 951,957 ---- } if (json != NULL) { ! channel_send(ch_idx, json, "eval"); vim_free(json); } } *************** *** 899,909 **** } /* ! * Invoke a callback for channel "idx" if needed. * Return OK when a message was handled, there might be another one. */ static int ! may_invoke_callback(int idx) { char_u *msg = NULL; typval_T *listtv = NULL; --- 965,975 ---- } /* ! * Invoke a callback for channel "ch_idx" if needed. * Return OK when a message was handled, there might be another one. */ static int ! may_invoke_callback(int ch_idx) { char_u *msg = NULL; typval_T *listtv = NULL; *************** *** 911,917 **** typval_T *typetv; typval_T argv[3]; int seq_nr = -1; ! channel_T *channel = &channels[idx]; ch_mode_T ch_mode = channel->ch_mode; if (channel->ch_close_cb != NULL) --- 977,983 ---- typval_T *typetv; typval_T argv[3]; int seq_nr = -1; ! channel_T *channel = &channels[ch_idx]; ch_mode_T ch_mode = channel->ch_mode; if (channel->ch_close_cb != NULL) *************** *** 921,931 **** if (ch_mode != MODE_RAW) { /* Get any json message in the queue. */ ! if (channel_get_json(idx, -1, &listtv) == FAIL) { /* Parse readahead, return when there is still no message. */ ! channel_parse_json(idx); ! if (channel_get_json(idx, -1, &listtv) == FAIL) return FALSE; } --- 987,997 ---- if (ch_mode != MODE_RAW) { /* Get any json message in the queue. */ ! if (channel_get_json(ch_idx, -1, &listtv) == FAIL) { /* Parse readahead, return when there is still no message. */ ! channel_parse_json(ch_idx); ! if (channel_get_json(ch_idx, -1, &listtv) == FAIL) return FALSE; } *************** *** 940,995 **** /* ["cmd", arg] or ["cmd", arg, arg] */ if (list->lv_len == 3) arg3 = &list->lv_last->li_tv; ! channel_exe_cmd(idx, cmd, &argv[1], arg3); clear_tv(listtv); return TRUE; } if (typetv->v_type != VAR_NUMBER) { ! /* TODO: give error */ clear_tv(listtv); return FALSE; } seq_nr = typetv->vval.v_number; } ! else if (channel_peek(idx) == NULL) { /* nothing to read on raw channel */ return FALSE; } else { /* For a raw channel we don't know where the message ends, just get * everything. */ ! msg = channel_get_all(idx); argv[1].v_type = VAR_STRING; argv[1].vval.v_string = msg; } if (seq_nr > 0) { ! cbq_T *cbhead = &channel->ch_cb_head; ! cbq_T *cbitem = cbhead->next; /* invoke the one-time callback with the matching nr */ while (cbitem != cbhead) { if (cbitem->seq_nr == seq_nr) { ! invoke_callback(idx, cbitem->callback, argv); remove_cb_node(cbitem); break; } cbitem = cbitem->next; } } else if (channel->ch_callback != NULL) { /* invoke the channel callback */ ! invoke_callback(idx, channel->ch_callback, argv); } ! /* else: drop the message TODO: give error */ if (listtv != NULL) clear_tv(listtv); --- 1006,1074 ---- /* ["cmd", arg] or ["cmd", arg, arg] */ if (list->lv_len == 3) arg3 = &list->lv_last->li_tv; ! ch_logs(ch_idx, "Executing %s command", (char *)cmd); ! channel_exe_cmd(ch_idx, cmd, &argv[1], arg3); clear_tv(listtv); return TRUE; } if (typetv->v_type != VAR_NUMBER) { ! ch_error(ch_idx, ! "Dropping message with invalid sequence number type\n"); clear_tv(listtv); return FALSE; } seq_nr = typetv->vval.v_number; } ! else if (channel_peek(ch_idx) == NULL) { /* nothing to read on raw channel */ return FALSE; } else { + /* If there is no callback, don't do anything. */ + if (channel->ch_callback == NULL) + return FALSE; + /* For a raw channel we don't know where the message ends, just get * everything. */ ! msg = channel_get_all(ch_idx); argv[1].v_type = VAR_STRING; argv[1].vval.v_string = msg; } if (seq_nr > 0) { ! cbq_T *cbhead = &channel->ch_cb_head; ! cbq_T *cbitem = cbhead->next; ! int done = FALSE; /* invoke the one-time callback with the matching nr */ while (cbitem != cbhead) { if (cbitem->seq_nr == seq_nr) { ! ch_log(ch_idx, "Invoking one-time callback\n"); ! invoke_callback(ch_idx, cbitem->callback, argv); remove_cb_node(cbitem); + done = TRUE; break; } cbitem = cbitem->next; } + if (!done) + ch_log(ch_idx, "Dropping message without callback\n"); } else if (channel->ch_callback != NULL) { /* invoke the channel callback */ ! ch_log(ch_idx, "Invoking channel callback\n"); ! invoke_callback(ch_idx, channel->ch_callback, argv); } ! else ! ch_log(ch_idx, "Dropping message\n"); if (listtv != NULL) clear_tv(listtv); *************** *** 999,1038 **** } /* ! * Return TRUE when channel "idx" is open. ! * Also returns FALSE or invalid "idx". */ int ! channel_is_open(int idx) { ! return idx >= 0 && idx < channel_count && channels[idx].ch_fd >= 0; } /* ! * Close channel "idx". * This does not trigger the close callback. */ void ! channel_close(int idx) { ! channel_T *channel = &channels[idx]; jsonq_T *jhead; cbq_T *cbhead; ! if (channel->ch_fd >= 0) { ! sock_close(channel->ch_fd); ! channel->ch_fd = -1; channel->ch_close_cb = NULL; #ifdef FEAT_GUI ! channel_gui_unregister(idx); #endif vim_free(channel->ch_callback); channel->ch_callback = NULL; channel->ch_timeout = 2000; ! while (channel_peek(idx) != NULL) ! vim_free(channel_get(idx)); cbhead = &channel->ch_cb_head; while (cbhead->next != cbhead) --- 1078,1139 ---- } /* ! * Return TRUE when channel "ch_idx" is open for writing to. ! * Also returns FALSE or invalid "ch_idx". */ int ! channel_can_write_to(int ch_idx) { ! return ch_idx >= 0 && ch_idx < channel_count ! && (channels[ch_idx].ch_sock >= 0 ! #ifdef CHANNEL_PIPES ! || channels[ch_idx].ch_in >= 0 ! #endif ! ); } /* ! * Return TRUE when channel "ch_idx" is open for reading or writing. ! * Also returns FALSE or invalid "ch_idx". ! */ ! int ! channel_is_open(int ch_idx) ! { ! return ch_idx >= 0 && ch_idx < channel_count ! && (channels[ch_idx].ch_sock >= 0 ! #ifdef CHANNEL_PIPES ! || channels[ch_idx].ch_in >= 0 ! || channels[ch_idx].ch_out >= 0 ! || channels[ch_idx].ch_err >= 0 ! #endif ! ); ! } ! ! /* ! * Close channel "ch_idx". * This does not trigger the close callback. */ void ! channel_close(int ch_idx) { ! channel_T *channel = &channels[ch_idx]; jsonq_T *jhead; cbq_T *cbhead; ! if (channel->ch_sock >= 0) { ! sock_close(channel->ch_sock); ! channel->ch_sock = -1; channel->ch_close_cb = NULL; #ifdef FEAT_GUI ! channel_gui_unregister(ch_idx); #endif vim_free(channel->ch_callback); channel->ch_callback = NULL; channel->ch_timeout = 2000; ! while (channel_peek(ch_idx) != NULL) ! vim_free(channel_get(ch_idx)); cbhead = &channel->ch_cb_head; while (cbhead->next != cbhead) *************** *** 1045,1061 **** remove_json_node(jhead->next); } } } /* ! * Store "buf[len]" on channel "idx". * Returns OK or FAIL. */ int ! channel_save(int idx, char_u *buf, int len) { readq_T *node; ! readq_T *head = &channels[idx].ch_head; node = (readq_T *)alloc(sizeof(readq_T)); if (node == NULL) --- 1146,1179 ---- remove_json_node(jhead->next); } } + #if defined(CHANNEL_PIPES) + if (channel->ch_in >= 0) + { + close(channel->ch_in); + channel->ch_in = -1; + } + if (channel->ch_out >= 0) + { + close(channel->ch_out); + channel->ch_out = -1; + } + if (channel->ch_err >= 0) + { + close(channel->ch_err); + channel->ch_err = -1; + } + #endif } /* ! * Store "buf[len]" on channel "ch_idx". * Returns OK or FAIL. */ int ! channel_save(int ch_idx, char_u *buf, int len) { readq_T *node; ! readq_T *head = &channels[ch_idx].ch_head; node = (readq_T *)alloc(sizeof(readq_T)); if (node == NULL) *************** *** 1075,1086 **** head->prev->next = node; head->prev = node; ! if (debugfd != NULL) { ! fprintf(debugfd, "RECV on %d: ", idx); ! if (fwrite(buf, len, 1, debugfd) != 1) return FAIL; ! fprintf(debugfd, "\n"); } return OK; } --- 1193,1205 ---- head->prev->next = node; head->prev = node; ! if (log_fd != NULL) { ! ch_log_lead("RECV ", ch_idx); ! fprintf(log_fd, "'"); ! if (fwrite(buf, len, 1, log_fd) != 1) return FAIL; ! fprintf(log_fd, "'\n"); } return OK; } *************** *** 1090,1098 **** * Returns NULL if there is nothing. */ char_u * ! channel_peek(int idx) { ! readq_T *head = &channels[idx].ch_head; if (head->next == head || head->next == NULL) return NULL; --- 1209,1217 ---- * Returns NULL if there is nothing. */ char_u * ! channel_peek(int ch_idx) { ! readq_T *head = &channels[ch_idx].ch_head; if (head->next == head || head->next == NULL) return NULL; *************** *** 1100,1111 **** } /* ! * Clear the read buffer on channel "idx". */ void ! channel_clear(int idx) { ! readq_T *head = &channels[idx].ch_head; readq_T *node = head->next; readq_T *next; --- 1219,1230 ---- } /* ! * Clear the read buffer on channel "ch_idx". */ void ! channel_clear(int ch_idx) { ! readq_T *head = &channels[ch_idx].ch_head; readq_T *node = head->next; readq_T *next; *************** *** 1136,1148 **** * Always returns OK for FEAT_GUI_W32. */ static int ! channel_wait(int fd, int timeout) { #if defined(HAVE_SELECT) && !defined(FEAT_GUI_W32) struct timeval tval; fd_set rfds; int ret; FD_ZERO(&rfds); FD_SET(fd, &rfds); tval.tv_sec = timeout / 1000; --- 1255,1269 ---- * Always returns OK for FEAT_GUI_W32. */ static int ! channel_wait(int ch_idx, int fd, int timeout) { #if defined(HAVE_SELECT) && !defined(FEAT_GUI_W32) struct timeval tval; fd_set rfds; int ret; + if (timeout > 0) + ch_logn(ch_idx, "Waiting for %d msec\n", timeout); FD_ZERO(&rfds); FD_SET(fd, &rfds); tval.tv_sec = timeout / 1000; *************** *** 1155,1171 **** --- 1276,1300 ---- continue; # endif if (ret <= 0) + { + ch_log(ch_idx, "Nothing to read\n"); return FAIL; + } break; } #else # ifdef HAVE_POLL struct pollfd fds; + if (timeout > 0) + ch_logn(ch_idx, "Waiting for %d msec\n", timeout); fds.fd = fd; fds.events = POLLIN; if (poll(&fds, 1, timeout) <= 0) + { + ch_log(ch_idx, "Nothing to read\n"); return FAIL; + } # endif #endif return OK; *************** *** 1183,1204 **** } /* ! * Read from channel "idx" for as long as there is something to read. * The data is put in the read queue. */ void ! channel_read(int idx) { static char_u *buf = NULL; int len = 0; int readlen = 0; ! channel_T *channel = &channels[idx]; ! if (channel->ch_fd < 0) ! { ! CHLOG(idx, FALSE, "channel_read() called while socket is closed\n"); return; ! } /* Allocate a buffer to read into. */ if (buf == NULL) --- 1312,1354 ---- } /* ! * Get the file descriptor to read from, either the socket or stdout. ! */ ! static int ! get_read_fd(int ch_idx, int use_stderr) ! { ! channel_T *channel = &channels[ch_idx]; ! ! if (channel->ch_sock >= 0) ! return channel->ch_sock; ! #if defined(CHANNEL_PIPES) ! if (!use_stderr && channel->ch_out >= 0) ! return channel->ch_out; ! if (use_stderr && channel->ch_err >= 0) ! return channel->ch_err; ! #endif ! ch_error(ch_idx, "channel_read() called while socket is closed\n"); ! return -1; ! } ! ! /* ! * Read from channel "ch_idx" for as long as there is something to read. * The data is put in the read queue. */ void ! channel_read(int ch_idx, int use_stderr, char *func) { + channel_T *channel = &channels[ch_idx]; static char_u *buf = NULL; int len = 0; int readlen = 0; ! int fd; ! int use_socket = FALSE; ! fd = get_read_fd(ch_idx, use_stderr); ! if (fd < 0) return; ! use_socket = channel->ch_sock >= 0; /* Allocate a buffer to read into. */ if (buf == NULL) *************** *** 1213,1232 **** * MAXMSGSIZE long. */ for (;;) { ! if (channel_wait(channel->ch_fd, 0) == FAIL) break; ! len = sock_read(channel->ch_fd, buf, MAXMSGSIZE); if (len <= 0) break; /* error or nothing more to read */ /* Store the read message in the queue. */ ! channel_save(idx, buf, len); readlen += len; if (len < MAXMSGSIZE) break; /* did read everything that's available */ } #ifdef FEAT_GUI_W32 ! if (len == SOCKET_ERROR) { /* For Win32 GUI channel_wait() always returns OK and we handle the * situation that there is nothing to read here. --- 1363,1385 ---- * MAXMSGSIZE long. */ for (;;) { ! if (channel_wait(ch_idx, fd, 0) == FAIL) break; ! if (use_socket) ! len = sock_read(fd, buf, MAXMSGSIZE); ! else ! len = read(fd, buf, MAXMSGSIZE); if (len <= 0) break; /* error or nothing more to read */ /* Store the read message in the queue. */ ! channel_save(ch_idx, buf, len); readlen += len; if (len < MAXMSGSIZE) break; /* did read everything that's available */ } #ifdef FEAT_GUI_W32 ! if (use_socket && len == SOCKET_ERROR) { /* For Win32 GUI channel_wait() always returns OK and we handle the * situation that there is nothing to read here. *************** *** 1249,1292 **** * -> gui event loop or select loop * -> channel_read() */ ! channel_save(idx, (char_u *)DETACH_MSG, (int)STRLEN(DETACH_MSG)); ! channel_close(idx); ! if (channel->ch_close_cb != NULL) ! (*channel->ch_close_cb)(); if (len < 0) { ! /* Todo: which channel? */ ! CHERROR("%s(): cannot from channel\n", "channel_read"); PERROR(_("E896: read from channel")); } } #if defined(CH_HAS_GUI) && defined(FEAT_GUI_GTK) if (CH_HAS_GUI && gtk_main_level() > 0) gtk_main_quit(); #endif } /* ! * Read from raw channel "idx". Blocks until there is something to read or * the timeout expires. * Returns what was read in allocated memory. * Returns NULL in case of error or timeout. */ char_u * ! channel_read_block(int idx) { ! if (channel_peek(idx) == NULL) { /* Wait for up to the channel timeout. */ ! if (channel_wait(channels[idx].ch_fd, channels[idx].ch_timeout) == FAIL) return NULL; ! channel_read(idx); } ! return channel_get_all(idx); } /* --- 1402,1463 ---- * -> gui event loop or select loop * -> channel_read() */ ! ch_errors(ch_idx, "%s(): Cannot read\n", func); ! channel_save(ch_idx, (char_u *)DETACH_MSG, (int)STRLEN(DETACH_MSG)); ! if (use_socket) ! { ! channel_close(ch_idx); ! if (channel->ch_close_cb != NULL) ! (*channel->ch_close_cb)(); ! } ! #if defined(CHANNEL_PIPES) ! else ! { ! close(fd); ! channel->ch_out = -1; ! } ! #endif if (len < 0) { ! ch_error(ch_idx, "channel_read(): cannot read from channel\n"); PERROR(_("E896: read from channel")); } } #if defined(CH_HAS_GUI) && defined(FEAT_GUI_GTK) + /* signal the main loop that there is something to read */ if (CH_HAS_GUI && gtk_main_level() > 0) gtk_main_quit(); #endif } /* ! * Read from raw channel "ch_idx". Blocks until there is something to read or * the timeout expires. * Returns what was read in allocated memory. * Returns NULL in case of error or timeout. */ char_u * ! channel_read_block(int ch_idx) { ! ch_log(ch_idx, "Reading raw\n"); ! if (channel_peek(ch_idx) == NULL) { + int fd = get_read_fd(ch_idx, FALSE); + + ch_log(ch_idx, "No readahead\n"); /* Wait for up to the channel timeout. */ ! if (fd < 0 || channel_wait(ch_idx, fd, ! channels[ch_idx].ch_timeout) == FAIL) return NULL; ! channel_read(ch_idx, FALSE, "channel_read_block"); } ! /* TODO: only get the first message */ ! ch_log(ch_idx, "Returning readahead\n"); ! return channel_get_all(ch_idx); } /* *************** *** 1299,1305 **** --- 1470,1478 ---- { int more; channel_T *channel = &channels[ch_idx]; + int fd; + ch_log(ch_idx, "Reading JSON\n"); channel->ch_block_id = id; for (;;) { *************** *** 1320,1329 **** continue; /* Wait for up to the channel timeout. */ ! if (channel->ch_fd < 0 || channel_wait(channel->ch_fd, ! channel->ch_timeout) == FAIL) break; ! channel_read(ch_idx); } } channel->ch_block_id = 0; --- 1493,1502 ---- continue; /* Wait for up to the channel timeout. */ ! fd = get_read_fd(ch_idx, FALSE); ! if (fd < 0 || channel_wait(ch_idx, fd, channel->ch_timeout) == FAIL) break; ! channel_read(ch_idx, FALSE, "channel_read_json_block"); } } channel->ch_block_id = 0; *************** *** 1336,1380 **** * Returns -1 when the socket isn't found. */ int ! channel_socket2idx(sock_T fd) { int i; if (fd >= 0) for (i = 0; i < channel_count; ++i) ! if (channels[i].ch_fd == fd) return i; return -1; } # endif /* ! * Write "buf" (NUL terminated string) to channel "idx". * When "fun" is not NULL an error message might be given. * Return FAIL or OK. */ int ! channel_send(int idx, char_u *buf, char *fun) { ! channel_T *channel = &channels[idx]; int len = (int)STRLEN(buf); ! if (channel->ch_fd < 0) { if (!channel->ch_error && fun != NULL) { ! CHERROR(" %s(): write while not connected\n", fun); EMSG2("E630: %s(): write while not connected", fun); } channel->ch_error = TRUE; return FAIL; } ! if (sock_write(channel->ch_fd, buf, len) != len) { if (!channel->ch_error && fun != NULL) { ! CHERROR(" %s(): write failed\n", fun); EMSG2("E631: %s(): write failed", fun); } channel->ch_error = TRUE; --- 1509,1583 ---- * Returns -1 when the socket isn't found. */ int ! channel_fd2idx(sock_T fd) { int i; if (fd >= 0) for (i = 0; i < channel_count; ++i) ! if (channels[i].ch_sock == fd ! # if defined(CHANNEL_PIPES) ! || channels[i].ch_out == fd ! || channels[i].ch_err == fd ! # endif ! ) return i; return -1; } # endif /* ! * Write "buf" (NUL terminated string) to channel "ch_idx". * When "fun" is not NULL an error message might be given. * Return FAIL or OK. */ int ! channel_send(int ch_idx, char_u *buf, char *fun) { ! channel_T *channel = &channels[ch_idx]; int len = (int)STRLEN(buf); + int res; + int fd; + int use_socket = FALSE; ! if (channel->ch_sock >= 0) ! { ! fd = channel->ch_sock; ! use_socket = TRUE; ! } ! #if defined(CHANNEL_PIPES) ! else if (channel->ch_in >= 0) ! fd = channel->ch_in; ! #endif ! if (fd < 0) { if (!channel->ch_error && fun != NULL) { ! ch_errors(ch_idx, "%s(): write while not connected\n", fun); EMSG2("E630: %s(): write while not connected", fun); } channel->ch_error = TRUE; return FAIL; } ! if (log_fd != NULL) ! { ! ch_log_lead("SEND ", ch_idx); ! fprintf(log_fd, "'"); ! ignored = fwrite(buf, len, 1, log_fd); ! fprintf(log_fd, "'\n"); ! fflush(log_fd); ! } ! ! if (use_socket) ! res = sock_write(fd, buf, len); ! else ! res = write(fd, buf, len); ! if (res != len) { if (!channel->ch_error && fun != NULL) { ! ch_errors(ch_idx, "%s(): write failed\n", fun); EMSG2("E631: %s(): write failed", fun); } channel->ch_error = TRUE; *************** *** 1399,1413 **** struct pollfd *fds = fds_in; for (i = 0; i < channel_count; ++i) ! if (channels[i].ch_fd >= 0) { ! channels[i].ch_idx = nfd; ! fds[nfd].fd = channels[i].ch_fd; fds[nfd].events = POLLIN; nfd++; } else ! channels[i].ch_idx = -1; return nfd; } --- 1602,1640 ---- struct pollfd *fds = fds_in; for (i = 0; i < channel_count; ++i) ! { ! if (channels[i].ch_sock >= 0) ! { ! channels[i].ch_sock_idx = nfd; ! fds[nfd].fd = channels[i].ch_sock; ! fds[nfd].events = POLLIN; ! nfd++; ! } ! else ! channels[i].ch_sock_idx = -1; ! ! # ifdef CHANNEL_PIPES ! if (channels[i].ch_out >= 0) ! { ! channels[i].ch_out_idx = nfd; ! fds[nfd].fd = channels[i].ch_out; ! fds[nfd].events = POLLIN; ! nfd++; ! } ! else ! channels[i].ch_out_idx = -1; ! ! if (channels[i].ch_err >= 0) { ! channels[i].ch_err_idx = nfd; ! fds[nfd].fd = channels[i].ch_err; fds[nfd].events = POLLIN; nfd++; } else ! channels[i].ch_err_idx = -1; ! # endif ! } return nfd; } *************** *** 1423,1434 **** struct pollfd *fds = fds_in; for (i = 0; i < channel_count; ++i) ! if (ret > 0 && channels[i].ch_idx != -1 ! && fds[channels[i].ch_idx].revents & POLLIN) { ! channel_read(i); --ret; } return ret; } --- 1650,1677 ---- struct pollfd *fds = fds_in; for (i = 0; i < channel_count; ++i) ! { ! if (ret > 0 && channels[i].ch_sock_idx != -1 ! && fds[channels[i].ch_sock_idx].revents & POLLIN) ! { ! channel_read(i, FALSE, "channel_poll_check"); ! --ret; ! } ! # ifdef CHANNEL_PIPES ! if (ret > 0 && channels[i].ch_out_idx != -1 ! && fds[channels[i].ch_out_idx].revents & POLLIN) { ! channel_read(i, FALSE, "channel_poll_check"); --ret; } + if (ret > 0 && channels[i].ch_err_idx != -1 + && fds[channels[i].ch_err_idx].revents & POLLIN) + { + channel_read(i, TRUE, "channel_poll_check"); + --ret; + } + # endif + } return ret; } *************** *** 1446,1457 **** fd_set *rfds = rfds_in; for (i = 0; i < channel_count; ++i) ! if (channels[i].ch_fd >= 0) { ! FD_SET(channels[i].ch_fd, rfds); ! if (maxfd < channels[i].ch_fd) ! maxfd = channels[i].ch_fd; } return maxfd; } --- 1689,1716 ---- fd_set *rfds = rfds_in; for (i = 0; i < channel_count; ++i) ! { ! if (channels[i].ch_sock >= 0) ! { ! FD_SET(channels[i].ch_sock, rfds); ! if (maxfd < channels[i].ch_sock) ! maxfd = channels[i].ch_sock; ! } ! # ifdef CHANNEL_PIPES ! if (channels[i].ch_out >= 0) ! { ! FD_SET(channels[i].ch_out, rfds); ! if (maxfd < channels[i].ch_out) ! maxfd = channels[i].ch_out; ! } ! if (channels[i].ch_err >= 0) { ! FD_SET(channels[i].ch_err, rfds); ! if (maxfd < channels[i].ch_err) ! maxfd = channels[i].ch_err; } + # endif + } return maxfd; } *************** *** 1467,1478 **** fd_set *rfds = rfds_in; for (i = 0; i < channel_count; ++i) ! if (ret > 0 && channels[i].ch_fd >= 0 ! && FD_ISSET(channels[i].ch_fd, rfds)) { ! channel_read(i); --ret; } return ret; } --- 1726,1753 ---- fd_set *rfds = rfds_in; for (i = 0; i < channel_count; ++i) ! { ! if (ret > 0 && channels[i].ch_sock >= 0 ! && FD_ISSET(channels[i].ch_sock, rfds)) ! { ! channel_read(i, FALSE, "channel_select_check"); ! --ret; ! } ! # ifdef CHANNEL_PIPES ! if (ret > 0 && channels[i].ch_out >= 0 ! && FD_ISSET(channels[i].ch_out, rfds)) { ! channel_read(i, FALSE, "channel_select_check"); --ret; } + if (ret > 0 && channels[i].ch_err >= 0 + && FD_ISSET(channels[i].ch_err, rfds)) + { + channel_read(i, TRUE, "channel_select_check"); + --ret; + } + # endif + } return ret; } *************** *** 1528,1542 **** } /* ! * Return the mode of channel "idx". ! * If "idx" is invalid returns MODE_JSON. */ ch_mode_T ! channel_get_mode(int idx) { ! if (idx < 0 || idx >= channel_count) return MODE_JSON; ! return channels[idx].ch_mode; } #endif /* FEAT_CHANNEL */ --- 1803,1817 ---- } /* ! * Return the mode of channel "ch_idx". ! * If "ch_idx" is invalid returns MODE_JSON. */ ch_mode_T ! channel_get_mode(int ch_idx) { ! if (ch_idx < 0 || ch_idx >= channel_count) return MODE_JSON; ! return channels[ch_idx].ch_mode; } #endif /* FEAT_CHANNEL */ *** ../vim-7.4.1309/src/eval.c 2016-02-12 19:30:20.349885799 +0100 --- src/eval.c 2016-02-13 15:38:01.864656937 +0100 *************** *** 503,510 **** static void f_ceil(typval_T *argvars, typval_T *rettv); #endif #ifdef FEAT_CHANNEL - static void f_ch_open(typval_T *argvars, typval_T *rettv); static void f_ch_close(typval_T *argvars, typval_T *rettv); static void f_ch_sendexpr(typval_T *argvars, typval_T *rettv); static void f_ch_sendraw(typval_T *argvars, typval_T *rettv); #endif --- 503,512 ---- static void f_ceil(typval_T *argvars, typval_T *rettv); #endif #ifdef FEAT_CHANNEL static void f_ch_close(typval_T *argvars, typval_T *rettv); + static void f_ch_logfile(typval_T *argvars, typval_T *rettv); + static void f_ch_open(typval_T *argvars, typval_T *rettv); + static void f_ch_readraw(typval_T *argvars, typval_T *rettv); static void f_ch_sendexpr(typval_T *argvars, typval_T *rettv); static void f_ch_sendraw(typval_T *argvars, typval_T *rettv); #endif *************** *** 624,629 **** --- 626,632 ---- static void f_islocked(typval_T *argvars, typval_T *rettv); static void f_items(typval_T *argvars, typval_T *rettv); #ifdef FEAT_JOB + static void f_job_getchannel(typval_T *argvars, typval_T *rettv); static void f_job_start(typval_T *argvars, typval_T *rettv); static void f_job_stop(typval_T *argvars, typval_T *rettv); static void f_job_status(typval_T *argvars, typval_T *rettv); *************** *** 7720,7725 **** --- 7723,7730 ---- static void job_free(job_T *job) { + if (job->jv_channel >= 0) + channel_close(job->jv_channel); mch_clear_job(job); vim_free(job); } *************** *** 8083,8089 **** --- 8088,8096 ---- #endif #ifdef FEAT_CHANNEL {"ch_close", 1, 1, f_ch_close}, + {"ch_logfile", 1, 2, f_ch_logfile}, {"ch_open", 1, 2, f_ch_open}, + {"ch_readraw", 1, 2, f_ch_readraw}, {"ch_sendexpr", 2, 3, f_ch_sendexpr}, {"ch_sendraw", 2, 3, f_ch_sendraw}, #endif *************** *** 8207,8212 **** --- 8214,8220 ---- {"islocked", 1, 1, f_islocked}, {"items", 1, 1, f_items}, #ifdef FEAT_JOB + {"job_getchannel", 1, 1, f_job_getchannel}, {"job_start", 1, 2, f_job_start}, {"job_status", 1, 1, f_job_status}, {"job_stop", 1, 2, f_job_stop}, *************** *** 9788,9794 **** } ch_idx = tv->vval.v_number; ! if (!channel_is_open(ch_idx)) { EMSGN(_("E906: not an open channel"), ch_idx); return -1; --- 9796,9802 ---- } ch_idx = tv->vval.v_number; ! if (!channel_can_write_to(ch_idx)) { EMSGN(_("E906: not an open channel"), ch_idx); return -1; *************** *** 9825,9830 **** --- 9833,9864 ---- } /* + * "ch_logfile()" function + */ + static void + f_ch_logfile(typval_T *argvars, typval_T *rettv UNUSED) + { + char_u *fname; + char_u *opt = (char_u *)""; + char_u buf[NUMBUFLEN]; + FILE *file = NULL; + + fname = get_tv_string(&argvars[0]); + if (argvars[1].v_type == VAR_STRING) + opt = get_tv_string_buf(&argvars[1], buf); + if (*fname != NUL) + { + file = fopen((char *)fname, *opt == 'w' ? "w" : "a"); + if (file == NULL) + { + EMSG2(_(e_notopen), fname); + return; + } + } + ch_logfile(file); + } + + /* * "ch_open()" function */ static void *************** *** 9914,9919 **** --- 9948,9974 ---- } /* + * "ch_readraw()" function + */ + static void + f_ch_readraw(typval_T *argvars, typval_T *rettv) + { + int ch_idx; + + /* return an empty string by default */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + ch_idx = get_channel_arg(&argvars[0]); + if (ch_idx < 0) + { + EMSG(_(e_invarg)); + return; + } + rettv->vval.v_string = channel_read_block(ch_idx); + } + + /* * common for "sendexpr()" and "sendraw()" * Returns the channel index if the caller should read the response. * Otherwise returns -1. *************** *** 14300,14305 **** --- 14355,14377 ---- #ifdef FEAT_JOB /* + * "job_getchannel()" function + */ + static void + f_job_getchannel(typval_T *argvars, typval_T *rettv) + { + if (argvars[0].v_type != VAR_JOB) + EMSG(_(e_invarg)); + else + { + job_T *job = argvars[0].vval.v_job; + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = job->jv_channel; + } + } + + /* * "job_start()" function */ static void *************** *** 14401,14407 **** * "job_status()" function */ static void ! f_job_status(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { char *result; --- 14473,14479 ---- * "job_status()" function */ static void ! f_job_status(typval_T *argvars, typval_T *rettv) { char *result; *** ../vim-7.4.1309/src/os_unix.c 2016-02-12 19:30:20.353885756 +0100 --- src/os_unix.c 2016-02-13 14:09:10.128543973 +0100 *************** *** 3984,3989 **** --- 3984,4025 ---- } #endif + #if !defined(USE_SYSTEM) || defined(FEAT_JOB) + static void + set_child_environment(void) + { + # ifdef HAVE_SETENV + char envbuf[50]; + # else + static char envbuf_Rows[20]; + static char envbuf_Columns[20]; + # endif + + /* Simulate to have a dumb terminal (for now) */ + # ifdef HAVE_SETENV + setenv("TERM", "dumb", 1); + sprintf((char *)envbuf, "%ld", Rows); + setenv("ROWS", (char *)envbuf, 1); + sprintf((char *)envbuf, "%ld", Rows); + setenv("LINES", (char *)envbuf, 1); + sprintf((char *)envbuf, "%ld", Columns); + setenv("COLUMNS", (char *)envbuf, 1); + # else + /* + * Putenv does not copy the string, it has to remain valid. + * Use a static array to avoid losing allocated memory. + */ + putenv("TERM=dumb"); + sprintf(envbuf_Rows, "ROWS=%ld", Rows); + putenv(envbuf_Rows); + sprintf(envbuf_Rows, "LINES=%ld", Rows); + putenv(envbuf_Rows); + sprintf(envbuf_Columns, "COLUMNS=%ld", Columns); + putenv(envbuf_Columns); + # endif + } + #endif + int mch_call_shell( char_u *cmd, *************** *** 4134,4145 **** int fd_toshell[2]; /* for pipes */ int fd_fromshell[2]; int pipe_error = FALSE; - # ifdef HAVE_SETENV - char envbuf[50]; - # else - static char envbuf_Rows[20]; - static char envbuf_Columns[20]; - # endif int did_settmode = FALSE; /* settmode(TMODE_RAW) called */ newcmd = vim_strsave(p_sh); --- 4170,4175 ---- *************** *** 4349,4376 **** # endif } # endif ! /* Simulate to have a dumb terminal (for now) */ ! # ifdef HAVE_SETENV ! setenv("TERM", "dumb", 1); ! sprintf((char *)envbuf, "%ld", Rows); ! setenv("ROWS", (char *)envbuf, 1); ! sprintf((char *)envbuf, "%ld", Rows); ! setenv("LINES", (char *)envbuf, 1); ! sprintf((char *)envbuf, "%ld", Columns); ! setenv("COLUMNS", (char *)envbuf, 1); ! # else ! /* ! * Putenv does not copy the string, it has to remain valid. ! * Use a static array to avoid losing allocated memory. ! */ ! putenv("TERM=dumb"); ! sprintf(envbuf_Rows, "ROWS=%ld", Rows); ! putenv(envbuf_Rows); ! sprintf(envbuf_Rows, "LINES=%ld", Rows); ! putenv(envbuf_Rows); ! sprintf(envbuf_Columns, "COLUMNS=%ld", Columns); ! putenv(envbuf_Columns); ! # endif /* * stderr is only redirected when using the GUI, so that a --- 4379,4385 ---- # endif } # endif ! set_child_environment(); /* * stderr is only redirected when using the GUI, so that a *************** *** 5030,5042 **** void mch_start_job(char **argv, job_T *job) { ! pid_t pid = fork(); ! if (pid == -1) /* maybe we should use vfork() */ { ! job->jv_status = JOB_FAILED; } ! else if (pid == 0) { /* child */ reset_signals(); /* handle signals normally */ --- 5039,5072 ---- void mch_start_job(char **argv, job_T *job) { ! pid_t pid; ! int fd_in[2]; /* for stdin */ ! int fd_out[2]; /* for stdout */ ! int fd_err[2]; /* for stderr */ ! int ch_idx; ! ! /* default is to fail */ ! job->jv_status = JOB_FAILED; ! fd_in[0] = -1; ! fd_out[0] = -1; ! fd_err[0] = -1; ! ! /* Open pipes for stdin, stdout, stderr. */ ! if ((pipe(fd_in) < 0) || (pipe(fd_out) < 0) ||(pipe(fd_err) < 0)) ! goto failed; ! ! ch_idx = add_channel(); ! if (ch_idx < 0) ! goto failed; ! pid = fork(); /* maybe we should use vfork() */ ! if (pid == -1) { ! /* failed to fork */ ! goto failed; } ! ! if (pid == 0) { /* child */ reset_signals(); /* handle signals normally */ *************** *** 5048,5064 **** (void)setsid(); # endif /* See above for type of argv. */ execvp(argv[0], argv); perror("executing job failed"); _exit(EXEC_FAILED); /* exec failed, return failure code */ } ! else { ! /* parent */ ! job->jv_pid = pid; ! job->jv_status = JOB_STARTED; } } --- 5078,5139 ---- (void)setsid(); # endif + set_child_environment(); + + /* set up stdin for the child */ + close(fd_in[1]); + close(0); + ignored = dup(fd_in[0]); + close(fd_in[0]); + + /* set up stdout for the child */ + close(fd_out[0]); + close(1); + ignored = dup(fd_out[1]); + close(fd_out[1]); + + /* set up stderr for the child */ + close(fd_err[0]); + close(2); + ignored = dup(fd_err[1]); + close(fd_err[1]); + /* See above for type of argv. */ execvp(argv[0], argv); perror("executing job failed"); _exit(EXEC_FAILED); /* exec failed, return failure code */ } ! ! /* parent */ ! job->jv_pid = pid; ! job->jv_status = JOB_STARTED; ! job->jv_channel = ch_idx; ! ! /* child stdin, stdout and stderr */ ! close(fd_in[0]); ! close(fd_out[1]); ! close(fd_err[1]); ! channel_set_pipes(ch_idx, fd_in[1], fd_out[0], fd_err[0]); ! channel_set_job(ch_idx, job); ! ! return; ! ! failed: ! if (fd_in[0] >= 0) ! { ! close(fd_in[0]); ! close(fd_in[1]); ! } ! if (fd_out[0] >= 0) ! { ! close(fd_out[0]); ! close(fd_out[1]); ! } ! if (fd_err[0] >= 0) { ! close(fd_err[0]); ! close(fd_err[1]); } } *************** *** 5104,5111 **** int mch_stop_job(job_T *job, char_u *how) { ! int sig = -1; ! pid_t job_pid; if (STRCMP(how, "hup") == 0) sig = SIGHUP; --- 5179,5186 ---- int mch_stop_job(job_T *job, char_u *how) { ! int sig = -1; ! pid_t job_pid; if (STRCMP(how, "hup") == 0) sig = SIGHUP; *** ../vim-7.4.1309/src/structs.h 2016-02-12 19:30:20.353885756 +0100 --- src/structs.h 2016-02-13 13:31:52.556086121 +0100 *************** *** 1110,1116 **** --- 1110,1121 ---- typedef struct listvar_S list_T; typedef struct dictvar_S dict_T; + typedef struct jobvar_S job_T; + typedef struct readq_S readq_T; + typedef struct jsonq_S jsonq_T; + typedef struct cbq_S cbq_T; + typedef struct channel_S channel_T; typedef enum { *************** *** 1255,1262 **** --- 1260,1351 ---- jobstatus_T jv_status; int jv_refcount; /* reference count */ + int jv_channel; /* channel for I/O */ + }; + + /* + * Structures to hold info about a Channel. + */ + struct readq_S + { + char_u *buffer; + readq_T *next; + readq_T *prev; + }; + + struct jsonq_S + { + typval_T *value; + jsonq_T *next; + jsonq_T *prev; + }; + + struct cbq_S + { + char_u *callback; + int seq_nr; + cbq_T *next; + cbq_T *prev; + }; + + /* mode for a channel */ + typedef enum + { + MODE_RAW = 0, + MODE_JSON, + MODE_JS + } ch_mode_T; + + struct channel_S { + sock_T ch_sock; /* the socket, -1 for a closed channel */ + + #ifdef UNIX + # define CHANNEL_PIPES + int ch_in; /* stdin of the job, -1 if not used */ + int ch_out; /* stdout of the job, -1 if not used */ + int ch_err; /* stderr of the job, -1 if not used */ + + # if defined(UNIX) && !defined(HAVE_SELECT) + int ch_sock_idx; /* used by channel_poll_setup() */ + int ch_in_idx; /* used by channel_poll_setup() */ + int ch_out_idx; /* used by channel_poll_setup() */ + int ch_err_idx; /* used by channel_poll_setup() */ + # endif + #endif + + readq_T ch_head; /* dummy node, header for circular queue */ + + int ch_error; /* When TRUE an error was reported. Avoids + * giving pages full of error messages when + * the other side has exited, only mention the + * first error until the connection works + * again. */ + #ifdef FEAT_GUI_X11 + XtInputId ch_inputHandler; /* Cookie for input */ + #endif + #ifdef FEAT_GUI_GTK + gint ch_inputHandler; /* Cookie for input */ + #endif + #ifdef WIN32 + int ch_inputHandler; /* simply ret.value of WSAAsyncSelect() */ + #endif + + void (*ch_close_cb)(void); /* callback for when channel is closed */ + + int ch_block_id; /* ID that channel_read_json_block() is + waiting for */ + char_u *ch_callback; /* function to call when a msg is not handled */ + cbq_T ch_cb_head; /* dummy node for pre-request callbacks */ + + ch_mode_T ch_mode; + jsonq_T ch_json_head; /* dummy node, header for circular queue */ + + int ch_timeout; /* request timeout in msec */ + + job_T *ch_job; /* job that uses this channel */ }; + /* structure used for explicit stack while garbage collecting hash tables */ typedef struct ht_stack_S { *************** *** 2729,2739 **** void *js_cookie; /* can be used by js_fill */ }; typedef struct js_reader js_read_T; - - /* mode for a channel */ - typedef enum - { - MODE_RAW = 0, - MODE_JSON, - MODE_JS - } ch_mode_T; --- 2818,2820 ---- *** ../vim-7.4.1309/src/gui_w48.c 2016-01-30 17:24:01.794502490 +0100 --- src/gui_w48.c 2016-02-13 15:50:48.476651452 +0100 *************** *** 1780,1789 **** #ifdef FEAT_CHANNEL if (msg.message == WM_NETBEANS) { ! int channel_idx = channel_socket2idx((sock_T)msg.wParam); if (channel_idx >= 0) ! channel_read(channel_idx); return; } #endif --- 1780,1789 ---- #ifdef FEAT_CHANNEL if (msg.message == WM_NETBEANS) { ! int channel_idx = channel_fd2idx((sock_T)msg.wParam); if (channel_idx >= 0) ! channel_read(channel_idx, FALSE, "process_message"); return; } #endif *** ../vim-7.4.1309/src/proto/channel.pro 2016-02-07 21:59:17.539916046 +0100 --- src/proto/channel.pro 2016-02-13 15:50:59.792533243 +0100 *************** *** 1,22 **** /* channel.c */ void channel_gui_register_all(void); int channel_open(char *hostname, int port_in, int waittime, void (*close_cb)(void)); void channel_set_json_mode(int idx, ch_mode_T ch_mode); void channel_set_timeout(int idx, int timeout); void channel_set_callback(int idx, char_u *callback); void channel_set_req_callback(int idx, char_u *callback, int id); char_u *channel_get(int idx); int channel_collapse(int idx); int channel_is_open(int idx); void channel_close(int idx); int channel_save(int idx, char_u *buf, int len); char_u *channel_peek(int idx); void channel_clear(int idx); int channel_get_id(void); ! void channel_read(int idx); char_u *channel_read_block(int idx); int channel_read_json_block(int ch_idx, int id, typval_T **rettv); ! int channel_socket2idx(sock_T fd); int channel_send(int idx, char_u *buf, char *fun); int channel_poll_setup(int nfd_in, void *fds_in); int channel_poll_check(int ret_in, void *fds_in); --- 1,27 ---- /* channel.c */ + void ch_logfile(FILE *file); + int add_channel(void); void channel_gui_register_all(void); int channel_open(char *hostname, int port_in, int waittime, void (*close_cb)(void)); + void channel_set_pipes(int idx, int in, int out, int err); + void channel_set_job(int idx, job_T *job); void channel_set_json_mode(int idx, ch_mode_T ch_mode); void channel_set_timeout(int idx, int timeout); void channel_set_callback(int idx, char_u *callback); void channel_set_req_callback(int idx, char_u *callback, int id); char_u *channel_get(int idx); int channel_collapse(int idx); + int channel_can_write_to(int idx); int channel_is_open(int idx); void channel_close(int idx); int channel_save(int idx, char_u *buf, int len); char_u *channel_peek(int idx); void channel_clear(int idx); int channel_get_id(void); ! void channel_read(int idx, int use_stderr, char *func); char_u *channel_read_block(int idx); int channel_read_json_block(int ch_idx, int id, typval_T **rettv); ! int channel_fd2idx(sock_T fd); int channel_send(int idx, char_u *buf, char *fun); int channel_poll_setup(int nfd_in, void *fds_in); int channel_poll_check(int ret_in, void *fds_in); *** ../vim-7.4.1309/src/testdir/test_channel.vim 2016-02-12 22:35:47.519872593 +0100 --- src/testdir/test_channel.vim 2016-02-13 16:43:35.355640338 +0100 *************** *** 273,275 **** --- 273,292 ---- call assert_true(reltimefloat(elapsed) < (has('unix') ? 1.0 : 3.0)) endif endfunc + + func Test_pipe() + if !has('job') || !has('unix') + return + endif + let job = job_start("python test_channel_pipe.py") + call assert_equal("run", job_status(job)) + try + let handle = job_getchannel(job) + call ch_sendraw(handle, "echo something\n", 0) + call assert_equal("something\n", ch_readraw(handle)) + let reply = ch_sendraw(handle, "quit\n") + call assert_equal("Goodbye!\n", reply) + finally + call job_stop(job) + endtry + endfunc *** ../vim-7.4.1309/src/testdir/test_channel_pipe.py 2016-02-13 16:57:13.367130042 +0100 --- src/testdir/test_channel_pipe.py 2016-02-13 16:22:31.128801099 +0100 *************** *** 0 **** --- 1,24 ---- + #!/usr/bin/python + # + # Server that will communicate over stdin/stderr + # + # This requires Python 2.6 or later. + + from __future__ import print_function + import sys + + if __name__ == "__main__": + + if len(sys.argv) > 1: + print(sys.argv[1]) + + while True: + typed = sys.stdin.readline() + if typed.startswith("quit"): + print("Goodbye!") + sys.stdout.flush() + break + if typed.startswith("echo"): + print(typed[5:-1]) + sys.stdout.flush() + *** ../vim-7.4.1309/runtime/doc/eval.txt 2016-02-11 21:08:27.544531244 +0100 --- runtime/doc/eval.txt 2016-02-13 15:36:56.061343826 +0100 *************** *** 1806,1812 **** --- 1816,1824 ---- any call {func} with arguments {arglist} ceil( {expr}) Float round {expr} up ch_close( {handle}) none close a channel + ch_logfile( {fname} [, {mode}]) none start logging channel activity ch_open( {address} [, {argdict})] Number open a channel to {address} + ch_readraw( {handle}) String read from channel {handle} ch_sendexpr( {handle}, {expr} [, {callback}]) any send {expr} over JSON channel {handle} ch_sendraw( {handle}, {string} [, {callback}]) *************** *** 2663,2670 **** don't fit, a vertical layout is used anyway. For some systems the horizontal layout is always used. ! ch_close({handle}) *ch_close()* Close channel {handle}. See |channel|. ch_open({address} [, {argdict}]) *ch_open()* Open a channel to {address}. See |channel|. --- 2678,2692 ---- don't fit, a vertical layout is used anyway. For some systems the horizontal layout is always used. ! ch_close({handle}) *ch_close()* Close channel {handle}. See |channel|. + {only available when compiled with the |+channel| feature} + + ch_logfile( {fname} [, {mode}]) *ch_logfile()* + Start logging channel activity to {fname}. + When {mode} is omitted or "a" append to the file. + When {mode} is "w" start with an empty file. + When {fname} is an empty string: stop logging. ch_open({address} [, {argdict}]) *ch_open()* Open a channel to {address}. See |channel|. *************** *** 2689,2695 **** Default: 2000. {only available when compiled with the |+channel| feature} ! ch_sendexpr({handle}, {expr} [, {callback}]) *ch_sendexpr()* Send {expr} over channel {handle}. The {expr} is encoded according to the type of channel. The function cannot be used with a raw channel. See |channel-use|. *E912* --- 2711,2723 ---- Default: 2000. {only available when compiled with the |+channel| feature} ! ch_readraw({handle}) *ch_readraw()* ! Read from channel {handle} and return the received message. ! This uses the channel timeout. When there is nothing to read ! within that time an empty string is returned. ! TODO: depends on channel mode. ! ! ch_sendexpr({handle}, {expr} [, {callback}]) *ch_sendexpr()* Send {expr} over channel {handle}. The {expr} is encoded according to the type of channel. The function cannot be used with a raw channel. See |channel-use|. *E912* *************** *** 4252,4273 **** Start a job and return a Job object. Unlike |system()| and |:!cmd| this does not wait for the job to finish. ! {command} can be a string. This works best on MS-Windows. On Unix it is split up in white-separated parts to be passed to execvp(). Arguments in double quotes can contain white space. ! {command} can be a list, where the first item is the executable and further items are the arguments. All items are converted to String. This works best on Unix. The command is executed directly, not through a shell, the 'shell' option is not used. To use the shell: > let job = job_start(["/bin/sh", "-c", "echo hello"]) < Or: > let job = job_start('/bin/sh -c "echo hello"') ! < However, the status of the job will now be the status of the ! shell, and stopping the job means stopping the shell and the ! command may continue to run. On Unix $PATH is used to search for the executable only when the command does not contain a slash. --- 4338,4362 ---- Start a job and return a Job object. Unlike |system()| and |:!cmd| this does not wait for the job to finish. ! {command} can be a String. This works best on MS-Windows. On Unix it is split up in white-separated parts to be passed to execvp(). Arguments in double quotes can contain white space. ! {command} can be a List, where the first item is the executable and further items are the arguments. All items are converted to String. This works best on Unix. + On MS-Windows, job_start() makes a GUI application hidden. If + want to show it, Use |:!start| instead. + The command is executed directly, not through a shell, the 'shell' option is not used. To use the shell: > let job = job_start(["/bin/sh", "-c", "echo hello"]) < Or: > let job = job_start('/bin/sh -c "echo hello"') ! < Note that this will start two processes, the shell and the ! command it executes. If you don't want this use the "exec" ! shell command. On Unix $PATH is used to search for the executable only when the command does not contain a slash. *************** *** 4280,4291 **** The returned Job object can be used to get the status with |job_status()| and stop the job with |job_stop()|. ! {options} must be a Dictionary. It can contain these optional ! items: ! killonexit When non-zero kill the job when Vim ! exits. (default: 0, don't kill) ! {only available when compiled with the |+channel| feature} job_status({job}) *job_status()* Returns a String with the status of {job}: --- 4369,4378 ---- The returned Job object can be used to get the status with |job_status()| and stop the job with |job_stop()|. ! {options} must be a Dictionary. It can contain many optional ! items, see |job-options|. ! {only available when compiled with the |+job| feature} job_status({job}) *job_status()* Returns a String with the status of {job}: *************** *** 4293,4319 **** "fail" job failed to start "dead" job died or was stopped after running ! {only available when compiled with the |+channel| feature} job_stop({job} [, {how}]) *job_stop()* Stop the {job}. This can also be used to signal the job. When {how} is omitted or is "term" the job will be terminated ! normally. For Unix SIGTERM is sent. ! Other values: "hup" Unix: SIGHUP "quit" Unix: SIGQUIT "kill" Unix: SIGKILL (strongest way to stop) number Unix: signal with that number The result is a Number: 1 if the operation could be executed, 0 if "how" is not supported on the system. Note that even when the operation was executed, whether the job was actually stopped needs to be checked with job_status(). ! The operation will even be done when the job wasn't running. ! {only available when compiled with the |+channel| feature} join({list} [, {sep}]) *join()* Join the items in {list} together into one String. --- 4380,4419 ---- "fail" job failed to start "dead" job died or was stopped after running ! {only available when compiled with the |+job| feature} job_stop({job} [, {how}]) *job_stop()* Stop the {job}. This can also be used to signal the job. When {how} is omitted or is "term" the job will be terminated ! normally. For Unix SIGTERM is sent. For MS-Windows ! CTRL_BREAK will be sent. This goes to the process group, thus ! children may also be affected. ! ! Other values for Unix: "hup" Unix: SIGHUP "quit" Unix: SIGQUIT "kill" Unix: SIGKILL (strongest way to stop) number Unix: signal with that number + Other values for MS-Windows: + "int" Windows: CTRL_C + "kill" Windows: terminate process forcedly + Others Windows: CTRL_BREAK + + On Unix the signal is sent to the process group. This means + that when the job is "sh -c command" it affects both the shell + and the command. + The result is a Number: 1 if the operation could be executed, 0 if "how" is not supported on the system. Note that even when the operation was executed, whether the job was actually stopped needs to be checked with job_status(). ! The status of the job isn't checked, the operation will even ! be done when Vim thinks the job isn't running. ! {only available when compiled with the |+job| feature} join({list} [, {sep}]) *join()* Join the items in {list} together into one String. *** ../vim-7.4.1309/src/version.c 2016-02-13 14:06:10.002442660 +0100 --- src/version.c 2016-02-13 16:57:23.163028341 +0100 *************** *** 749,750 **** --- 749,752 ---- { /* Add new patch number below this line */ + /**/ + 1310, /**/ -- Married is a three ring circus: First comes the engagement ring. Then comes the wedding ring. Then comes the suffering. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///