]> granicus.if.org Git - vim/commitdiff
patch 8.0.1000: cannot open a terminal without running a job in it v8.0.1000
authorBram Moolenaar <Bram@vim.org>
Sat, 26 Aug 2017 20:02:51 +0000 (22:02 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 26 Aug 2017 20:02:51 +0000 (22:02 +0200)
Problem:    Cannot open a terminal without running a job in it.
Solution:   Make ":terminal NONE" open a terminal with a pty.

src/channel.c
src/gui_gtk_x11.c
src/misc2.c
src/os_unix.c
src/proto/channel.pro
src/proto/os_unix.pro
src/structs.h
src/terminal.c
src/testdir/test_terminal.vim
src/version.c

index 1680d0dd1e17efb2c11dc24085edf2b2c17390cf..b31d95d41ceb64a769580472f8be6889256e3627 100644 (file)
@@ -503,6 +503,10 @@ channel_gui_register_one(channel_T *channel, ch_part_T part)
     if (!CH_HAS_GUI)
        return;
 
+    /* gets stuck in handling events for a not connected channel */
+    if (channel->ch_keep_open)
+       return;
+
 # ifdef FEAT_GUI_X11
     /* Tell notifier we are interested in being called
      * when there is input on the editor connection socket. */
@@ -548,9 +552,12 @@ channel_gui_register(channel_T *channel)
 {
     if (channel->CH_SOCK_FD != INVALID_FD)
        channel_gui_register_one(channel, PART_SOCK);
-    if (channel->CH_OUT_FD != INVALID_FD)
+    if (channel->CH_OUT_FD != INVALID_FD
+           && channel->CH_OUT_FD != channel->CH_SOCK_FD)
        channel_gui_register_one(channel, PART_OUT);
-    if (channel->CH_ERR_FD != INVALID_FD)
+    if (channel->CH_ERR_FD != INVALID_FD
+           && channel->CH_ERR_FD != channel->CH_SOCK_FD
+           && channel->CH_ERR_FD != channel->CH_OUT_FD)
        channel_gui_register_one(channel, PART_ERR);
 }
 
@@ -3247,11 +3254,13 @@ channel_read(channel_T *channel, ch_part_T part, char *func)
 
     /* Reading a disconnection (readlen == 0), or an error. */
     if (readlen <= 0)
-       ch_close_part_on_error(channel, part, (len < 0), func);
-
+    {
+       if (!channel->ch_keep_open)
+           ch_close_part_on_error(channel, part, (len < 0), func);
+    }
 #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)
+    else if (CH_HAS_GUI && gtk_main_level() > 0)
+       /* signal the main loop that there is something to read */
        gtk_main_quit();
 #endif
 }
@@ -3509,13 +3518,14 @@ channel_fd2channel(sock_T fd, ch_part_T *partp)
 }
 # endif
 
-# if defined(WIN32) || defined(PROTO)
+# if defined(WIN32) || defined(FEAT_GUI) || defined(PROTO)
 /*
  * Check the channels for anything that is ready to be read.
  * The data is put in the read queue.
+ * if "only_keep_open" is TRUE only check channels where ch_keep_open is set.
  */
     void
-channel_handle_events(void)
+channel_handle_events(int only_keep_open)
 {
     channel_T  *channel;
     ch_part_T  part;
@@ -3523,6 +3533,9 @@ channel_handle_events(void)
 
     for (channel = first_channel; channel != NULL; channel = channel->ch_next)
     {
+       if (only_keep_open && !channel->ch_keep_open)
+           continue;
+
        /* check the socket and pipes */
        for (part = PART_SOCK; part < PART_IN; ++part)
        {
index 480b8d3b8345c7d819dc328d7b6d04cc32e377bf..cfbb36ce933a80938bc73eb6dd112a85fbe71553 100644 (file)
@@ -6643,6 +6643,12 @@ gui_mch_wait_for_chars(long wtime)
            focus = gui.in_focus;
        }
 
+# if defined(FEAT_JOB_CHANNEL)
+       /* Using an event handler for a channel that may be disconnected does
+        * not work, it hangs.  Instead poll for messages. */
+       channel_handle_events(TRUE);
+# endif
+
 #ifdef MESSAGE_QUEUE
 # ifdef FEAT_TIMERS
        did_add_timer = FALSE;
index 17fa424bc89435978fe460c96c49c359c7e18d01..d431a94280143f785b9896fcd92edb502ba274bd 100644 (file)
@@ -6321,7 +6321,7 @@ parse_queued_messages(void)
 {
     /* For Win32 mch_breakcheck() does not check for input, do it here. */
 # if defined(WIN32) && defined(FEAT_JOB_CHANNEL)
-    channel_handle_events();
+    channel_handle_events(FALSE);
 # endif
 
 # ifdef FEAT_NETBEANS_INTG
index de0bb311dc2e24513fa32bcb73f4ac78ab63988c..f77debc8a36361ddb36970ee9b2688dcd9bf00bd 100644 (file)
@@ -5466,7 +5466,7 @@ mch_job_start(char **argv, job_T *job, jobopt_T *options)
     job->jv_channel = channel;  /* ch_refcount was set above */
 
     if (pty_master_fd >= 0)
-       close(pty_slave_fd); /* duped above */
+       close(pty_slave_fd); /* not used in the parent */
     /* close child stdin, stdout and stderr */
     if (!use_file_for_in && fd_in[0] >= 0)
        close(fd_in[0]);
@@ -5669,6 +5669,29 @@ mch_clear_job(job_T *job)
 }
 #endif
 
+#if defined(FEAT_TERMINAL) || defined(PROTO)
+    int
+mch_create_pty_channel(job_T *job, jobopt_T *options)
+{
+    int                pty_master_fd = -1;
+    int                pty_slave_fd = -1;
+    channel_T  *channel;
+
+    open_pty(&pty_master_fd, &pty_slave_fd, &job->jv_tty_name);
+    close(pty_slave_fd);
+
+    channel = add_channel();
+    if (channel == NULL)
+       return FAIL;
+    job->jv_channel = channel;  /* ch_refcount was set by add_channel() */
+    channel->ch_keep_open = TRUE;
+
+    channel_set_pipes(channel, pty_master_fd, pty_master_fd, pty_master_fd);
+    channel_set_job(channel, job, options);
+    return OK;
+}
+#endif
+
 /*
  * Check for CTRL-C typed by reading all available characters.
  * In cooked mode we should get SIGINT, no need to check.
index f9a70140987c6fb41ee62f436e93ba02623f2105..1b9282c1a8a888f411b1ea3f12a04f548dc4738d 100644 (file)
@@ -34,9 +34,9 @@ void channel_free_all(void);
 char_u *channel_read_block(channel_T *channel, ch_part_T part, int timeout);
 void common_channel_read(typval_T *argvars, typval_T *rettv, int raw);
 channel_T *channel_fd2channel(sock_T fd, ch_part_T *partp);
-void channel_handle_events(void);
+void channel_handle_events(int only_keep_open);
 void channel_set_nonblock(channel_T *channel, ch_part_T part);
-int channel_send(channel_T *channel, ch_part_T part, char_u *buf, int len, char *fun);
+int channel_send(channel_T *channel, ch_part_T part, char_u *buf_arg, int len_arg, char *fun);
 void ch_expr_common(typval_T *argvars, typval_T *rettv, int eval);
 void ch_raw_common(typval_T *argvars, typval_T *rettv, int eval);
 int channel_poll_setup(int nfd_in, void *fds_in);
index 80539d34ddab6587c9530a009170da55a0f442a8..bdbe17f770d5ba76e31fb45ad1782099ceb005d8 100644 (file)
@@ -66,6 +66,7 @@ char *mch_job_status(job_T *job);
 job_T *mch_detect_ended_job(job_T *job_list);
 int mch_signal_job(job_T *job, char_u *how);
 void mch_clear_job(job_T *job);
+int mch_create_pty_channel(job_T *job, jobopt_T *options);
 void mch_breakcheck(int force);
 int mch_expandpath(garray_T *gap, char_u *path, int flags);
 int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***file, int flags);
index 444b30df3d8fe14f9f9e2d4d521d87d45a75085a..4030f6d51fb16d35f9e42c732e6e56eaa1086617 100644 (file)
@@ -1656,6 +1656,7 @@ struct channel_S {
     char_u     *ch_close_cb;   /* call when channel is closed */
     partial_T  *ch_close_partial;
     int                ch_drop_never;
+    int                ch_keep_open;   /* do not close on read error */
 
     job_T      *ch_job;        /* Job that uses this channel; this does not
                                 * count as a reference to avoid a circular
index c6dc0d9956ef9ecfbd3be26fb05e3f37ec981306..c5226134c617a378013c49e6b66cd8d63d5bc651 100644 (file)
@@ -38,6 +38,7 @@
  * in tl_scrollback are no longer used.
  *
  * TODO:
+ * - ":term NONE" does not work in MS-Windows.
  * - better check for blinking - reply from Thomas Dickey Aug 22
  * - test for writing lines to terminal job does not work on MS-Windows
  * - implement term_setsize()
@@ -47,6 +48,7 @@
  * - do not set bufhidden to "hide"?  works like a buffer with changes.
  *   document that CTRL-W :hide can be used.
  * - GUI: when using tabs, focus in terminal, click on tab does not work.
+ * - When $HOME was set by Vim (MS-Windows), do not pass it to the job.
  * - GUI: when 'confirm' is set and trying to exit Vim, dialog offers to save
  *   changes to "!shell".
  *   (justrajdeep, 2017 Aug 22)
@@ -62,8 +64,6 @@
  *        shell writing stderr to a file or buffer
  * - For the GUI fill termios with default values, perhaps like pangoterm:
  *   http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
- * - support ":term NONE" to open a terminal with a pty but not running a job
- *   in it.  The pty can be passed to gdb to run the executable in.
  * - if the job in the terminal does not support the mouse, we can use the
  *   mouse in the Terminal window for copy/paste.
  * - when 'encoding' is not utf-8, or the job is using another encoding, setup
@@ -163,8 +163,8 @@ static term_T *in_terminal_loop = NULL;
 /*
  * Functions with separate implementation for MS-Windows and Unix-like systems.
  */
-static int term_and_job_init(term_T *term, int rows, int cols,
-                                             typval_T *argvar, jobopt_T *opt);
+static int term_and_job_init(term_T *term, typval_T *argvar, jobopt_T *opt);
+static int create_pty_only(term_T *term, jobopt_T *opt);
 static void term_report_winsize(term_T *term, int rows, int cols);
 static void term_free_vterm(term_T *term);
 
@@ -256,6 +256,7 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit)
     win_T      *old_curwin = curwin;
     term_T     *term;
     buf_T      *old_curbuf = NULL;
+    int                res;
 
     if (check_restricted() || check_secure())
        return;
@@ -355,7 +356,13 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit)
        char_u  *cmd, *p;
 
        if (argvar->v_type == VAR_STRING)
+       {
            cmd = argvar->vval.v_string;
+           if (cmd == NULL)
+               cmd = (char_u *)"";
+           else if (STRCMP(cmd, "NONE") == 0)
+               cmd = (char_u *)"pty";
+       }
        else if (argvar->v_type != VAR_LIST
                || argvar->vval.v_list == NULL
                || argvar->vval.v_list->lv_len < 1)
@@ -400,9 +407,15 @@ term_start(typval_T *argvar, jobopt_T *opt, int forceit)
     set_term_and_win_size(term);
     setup_job_options(opt, term->tl_rows, term->tl_cols);
 
-    /* System dependent: setup the vterm and start the job in it. */
-    if (term_and_job_init(term, term->tl_rows, term->tl_cols, argvar, opt)
-                                                                        == OK)
+    /* System dependent: setup the vterm and maybe start the job in it. */
+    if (argvar->v_type == VAR_STRING
+           && argvar->vval.v_string != NULL
+           && STRCMP(argvar->vval.v_string, "NONE") == 0)
+       res = create_pty_only(term, opt);
+    else
+       res = term_and_job_init(term, argvar, opt);
+
+    if (res == OK)
     {
        /* Get and remember the size we ended up with.  Update the pty. */
        vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
@@ -553,7 +566,8 @@ free_terminal(buf_T *buf)
     if (term->tl_job != NULL)
     {
        if (term->tl_job->jv_status != JOB_ENDED
-                                     && term->tl_job->jv_status != JOB_FAILED)
+               && term->tl_job->jv_status != JOB_FINISHED
+               && term->tl_job->jv_status != JOB_FAILED)
            job_stop(term->tl_job, NULL, "kill");
        job_unref(term->tl_job);
     }
@@ -839,8 +853,9 @@ term_job_running(term_T *term)
      * race condition when updating the title. */
     return term != NULL
        && term->tl_job != NULL
-       && term->tl_job->jv_status == JOB_STARTED
-       && channel_is_open(term->tl_job->jv_channel);
+       && channel_is_open(term->tl_job->jv_channel)
+       && (term->tl_job->jv_status == JOB_STARTED
+               || term->tl_job->jv_channel->ch_keep_open);
 }
 
 /*
@@ -2842,9 +2857,14 @@ f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
        ch_log(NULL, "term_wait(): no job to wait for");
        return;
     }
+    if (buf->b_term->tl_job->jv_channel == NULL)
+       /* channel is closed, nothing to do */
+       return;
 
     /* Get the job status, this will detect a job that finished. */
-    if (STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
+    if ((buf->b_term->tl_job->jv_channel == NULL
+                            || !buf->b_term->tl_job->jv_channel->ch_keep_open)
+           && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
     {
        /* The job is dead, keep reading channel I/O until the channel is
         * closed. */
@@ -2976,8 +2996,6 @@ dyn_winpty_init(int verbose)
     static int
 term_and_job_init(
        term_T      *term,
-       int         rows,
-       int         cols,
        typval_T    *argvar,
        jobopt_T    *opt)
 {
@@ -3023,7 +3041,8 @@ term_and_job_init(
     if (term->tl_winpty_config == NULL)
        goto failed;
 
-    winpty_config_set_initial_size(term->tl_winpty_config, cols, rows);
+    winpty_config_set_initial_size(term->tl_winpty_config,
+                                                term->tl_cols, term->tl_rows);
     term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
     if (term->tl_winpty == NULL)
        goto failed;
@@ -3085,7 +3104,7 @@ term_and_job_init(
     winpty_spawn_config_free(spawn_config);
     vim_free(cmd_wchar);
 
-    create_vterm(term, rows, cols);
+    create_vterm(term, term->tl_rows, term->tl_cols);
 
     channel_set_job(channel, job, opt);
     job_set_options(job, opt);
@@ -3137,6 +3156,13 @@ failed:
     return FAIL;
 }
 
+    static int
+create_pty_only(term_T *term, jobopt_T *opt)
+{
+    /* TODO: implement this */
+    return FAIL;
+}
+
 /*
  * Free the terminal emulator part of "term".
  */
@@ -3185,12 +3211,10 @@ terminal_enabled(void)
     static int
 term_and_job_init(
        term_T      *term,
-       int         rows,
-       int         cols,
        typval_T    *argvar,
        jobopt_T    *opt)
 {
-    create_vterm(term, rows, cols);
+    create_vterm(term, term->tl_rows, term->tl_cols);
 
     /* TODO: if the command is "NONE" only create a pty. */
     term->tl_job = job_start(argvar, opt);
@@ -3202,6 +3226,26 @@ term_and_job_init(
        && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
 }
 
+    static int
+create_pty_only(term_T *term, jobopt_T *opt)
+{
+    int ret;
+
+    create_vterm(term, term->tl_rows, term->tl_cols);
+
+    term->tl_job = job_alloc();
+    if (term->tl_job == NULL)
+       return FAIL;
+    ++term->tl_job->jv_refcount;
+
+    /* behave like the job is already finished */
+    term->tl_job->jv_status = JOB_FINISHED;
+
+    ret = mch_create_pty_channel(term->tl_job, opt);
+
+    return ret;
+}
+
 /*
  * Free the terminal emulator part of "term".
  */
index 2356f2c538055207ec8adf483cd9ddab31dc8242..d651d0bbaa5b6956a89d0ceb1f98da487ca6bcfe 100644 (file)
@@ -505,3 +505,23 @@ func Test_terminal_write_stdin()
 
   bwipe!
 endfunc
+
+func Test_terminal_no_cmd()
+  " Todo: make this work on all systems.
+  if !has('unix')
+    return
+  endif
+  " Todo: make this work in the GUI
+  if !has('gui_running')
+    return
+  endif
+  let buf = term_start('NONE', {})
+  call assert_notequal(0, buf)
+
+  let pty = job_info(term_getjob(buf))['tty']
+  call assert_notequal('', pty)
+  call system('echo "look here" > ' . pty)
+  call term_wait(buf)
+  call assert_equal('look here', term_getline(buf, 1))
+  bwipe!
+endfunc
index e62f6a1bbbcc2405c942f4c5f93a04980b431a2e..e229f41775da0113ec30519e5e61197f83215586 100644 (file)
@@ -769,6 +769,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1000,
 /**/
     999,
 /**/