# HG changeset patch # User Christian Brabandt # Date 1503778504 -7200 # Node ID 24abce52ad20444632e21b8e446abc45f7aa1f3a # Parent 658d961021028545acfaaf7744b831a567aee3da patch 8.0.1000: cannot open a terminal without running a job in it commit https://github.com/vim/vim/commit/13ebb03e7520c2c34f93444b0146640ca08e7424 Author: Bram Moolenaar Date: Sat Aug 26 22:02:51 2017 +0200 patch 8.0.1000: cannot open a terminal without running a job in it Problem: Cannot open a terminal without running a job in it. Solution: Make ":terminal NONE" open a terminal with a pty. diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -503,6 +503,10 @@ channel_gui_register_one(channel_T *chan 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 /* 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 } # 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) { diff --git a/src/gui_gtk_x11.c b/src/gui_gtk_x11.c --- a/src/gui_gtk_x11.c +++ b/src/gui_gtk_x11.c @@ -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; diff --git a/src/misc2.c b/src/misc2.c --- a/src/misc2.c +++ b/src/misc2.c @@ -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 diff --git a/src/os_unix.c b/src/os_unix.c --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5466,7 +5466,7 @@ mch_job_start(char **argv, job_T *job, j 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. diff --git a/src/proto/channel.pro b/src/proto/channel.pro --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -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); diff --git a/src/proto/os_unix.pro b/src/proto/os_unix.pro --- a/src/proto/os_unix.pro +++ b/src/proto/os_unix.pro @@ -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); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -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 diff --git a/src/terminal.c b/src/terminal.c --- a/src/terminal.c +++ b/src/terminal.c @@ -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 *o 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 *o 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 *o 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 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". */ diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim --- a/src/testdir/test_terminal.vim +++ b/src/testdir/test_terminal.vim @@ -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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -770,6 +770,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1000, +/**/ 999, /**/ 998,