changeset 13470:6faef782f50b v8.0.1609

patch 8.0.1609: shell commands in the GUI use a dumb terminal commit https://github.com/vim/vim/commit/135682517bc378cfdb63fe3a6e3553935f69f6ce Author: Bram Moolenaar <Bram@vim.org> Date: Fri Mar 16 20:46:58 2018 +0100 patch 8.0.1609: shell commands in the GUI use a dumb terminal Problem: Shell commands in the GUI use a dumb terminal. Solution: Add the "!" flag to 'guioptions' to execute system commands in a special terminal window. Only for Unix now.
author Christian Brabandt <cb@256bit.org>
date Fri, 16 Mar 2018 21:00:08 +0100
parents e9121e612cbc
children 198769450820
files runtime/doc/options.txt src/channel.c src/evalfunc.c src/option.h src/os_unix.c src/proto/channel.pro src/proto/terminal.pro src/terminal.c src/version.c src/vim.h
diffstat 10 files changed, 418 insertions(+), 166 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -3845,7 +3845,14 @@ A jump table for the options with a shor
 	To avoid problems with flags that are added in the future, use the
 	"+=" and "-=" feature of ":set" |add-option-flags|.
 
-	Valid letters are as follows:
+	Valid characters are as follows:
+								*'go-!'*
+	  '!'	External commands are executed in a terminal window.  Without
+		this flag the MS-Windows GUI will open a console window to
+		execute the command.  The Unix GUI will simulate a dumb
+		terminal to list the command output.
+		The terminal window will be positioned at the bottom, and grow
+		upwards as needed.
 							*guioptions_a* *'go-a'*
 	  'a'	Autoselect:  If present, then whenever VISUAL mode is started,
 		or the Visual area extended, Vim tries to become the owner of
--- a/src/channel.c
+++ b/src/channel.c
@@ -5383,11 +5383,13 @@ job_check_ended(void)
 
 /*
  * Create a job and return it.  Implements job_start().
+ * "argv_arg" is only for Unix.
+ * When "argv_arg" is NULL then "argvars" is used.
  * The returned job has a refcount of one.
  * Returns NULL when out of memory.
  */
     job_T *
-job_start(typval_T *argvars, jobopt_T *opt_arg)
+job_start(typval_T *argvars, char **argv_arg, jobopt_T *opt_arg)
 {
     job_T	*job;
     char_u	*cmd = NULL;
@@ -5474,6 +5476,13 @@ job_start(typval_T *argvars, jobopt_T *o
 
     job_set_options(job, &opt);
 
+#ifdef USE_ARGV
+    if (argv_arg != NULL)
+    {
+	argv = argv_arg;
+    }
+    else
+#endif
     if (argvars[0].v_type == VAR_STRING)
     {
 	/* Command is a string. */
@@ -5551,7 +5560,8 @@ job_start(typval_T *argvars, jobopt_T *o
 
 theend:
 #ifdef USE_ARGV
-    vim_free(argv);
+    if (argv != argv_arg)
+	vim_free(argv);
 #else
     vim_free(ga.ga_data);
 #endif
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -7032,7 +7032,7 @@ f_job_start(typval_T *argvars, typval_T 
     rettv->v_type = VAR_JOB;
     if (check_restricted() || check_secure())
 	return;
-    rettv->vval.v_job = job_start(argvars, NULL);
+    rettv->vval.v_job = job_start(argvars, NULL, NULL);
 }
 
 /*
--- a/src/option.h
+++ b/src/option.h
@@ -214,6 +214,7 @@
 #define SHM_ALL		"rmfixlnwaWtToOsAIcqF" /* all possible flags for 'shm' */
 
 /* characters for p_go: */
+#define GO_TERMINAL	'!'		/* use terminal for system commands */
 #define GO_ASEL		'a'		/* autoselect */
 #define GO_ASELML	'A'		/* autoselect modeless selection */
 #define GO_BOT		'b'		/* use bottom scrollbar */
@@ -236,7 +237,7 @@
 #define GO_FOOTER	'F'		/* add footer */
 #define GO_VERTICAL	'v'		/* arrange dialog buttons vertically */
 #define GO_KEEPWINSIZE	'k'		/* keep GUI window size */
-#define GO_ALL		"aAbcefFghilmMprtTvk" /* all possible flags for 'go' */
+#define GO_ALL		"!aAbcefFghilmMprtTvk" /* all possible flags for 'go' */
 
 /* flags for 'comments' option */
 #define COM_NEST	'n'		/* comments strings nest */
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -4154,10 +4154,13 @@ wait4pid(pid_t child, waitstatus *status
     return wait_pid;
 }
 
-#if defined(FEAT_JOB_CHANNEL) || !defined(USE_SYSTEM) || defined(PROTO)
+#if defined(FEAT_JOB_CHANNEL) \
+	|| !defined(USE_SYSTEM) \
+	|| (defined(FEAT_GUI) && defined(FEAT_TERMINAL)) \
+	|| defined(PROTO)
 /*
  * Parse "cmd" and put the white-separated parts in "argv".
- * "argv" is an allocated array with "argc" entries.
+ * "argv" is an allocated array with "argc" entries and room for 4 more.
  * Returns FAIL when out of memory.
  */
     int
@@ -4359,8 +4362,121 @@ may_send_sigint(int c UNUSED, pid_t pid 
 # endif
 }
 
-    int
-mch_call_shell(
+#if !defined(USE_SYSTEM) || (defined(FEAT_GUI) && defined(FEAT_TERMINAL))
+
+    static int
+build_argv(
+	char_u *cmd,
+	char ***argvp,
+	char_u **sh_tofree,
+	char_u **shcf_tofree)
+{
+    char	**argv = NULL;
+    int		argc;
+
+    *sh_tofree = vim_strsave(p_sh);
+    if (*sh_tofree == NULL)		/* out of memory */
+	return FAIL;
+
+    if (mch_parse_cmd(*sh_tofree, TRUE, &argv, &argc) == FAIL)
+	return FAIL;
+    *argvp = argv;
+
+    if (cmd != NULL)
+    {
+	char_u	*s;
+	char_u	*p;
+
+	if (extra_shell_arg != NULL)
+	    argv[argc++] = (char *)extra_shell_arg;
+
+	/* Break 'shellcmdflag' into white separated parts.  This doesn't
+	 * handle quoted strings, they are very unlikely to appear. */
+	*shcf_tofree = alloc((unsigned)STRLEN(p_shcf) + 1);
+	if (*shcf_tofree == NULL)    /* out of memory */
+	    return FAIL;
+	s = *shcf_tofree;
+	p = p_shcf;
+	while (*p != NUL)
+	{
+	    argv[argc++] = (char *)s;
+	    while (*p && *p != ' ' && *p != TAB)
+		*s++ = *p++;
+	    *s++ = NUL;
+	    p = skipwhite(p);
+	}
+
+	argv[argc++] = (char *)cmd;
+    }
+    argv[argc] = NULL;
+    return OK;
+}
+#endif
+
+#if defined(FEAT_GUI) && defined(FEAT_TERMINAL)
+/*
+ * Use a terminal window to run a shell command in.
+ */
+    static int
+mch_call_shell_terminal(
+    char_u	*cmd,
+    int		options UNUSED)	/* SHELL_*, see vim.h */
+{
+    jobopt_T	opt;
+    char	**argv = NULL;
+    char_u	*tofree1 = NULL;
+    char_u	*tofree2 = NULL;
+    int		retval = -1;
+    buf_T	*buf;
+    aco_save_T	aco;
+    oparg_T	oa;		/* operator arguments */
+
+    if (build_argv(cmd, &argv, &tofree1, &tofree2) == FAIL)
+	goto theend;
+
+    init_job_options(&opt);
+    ch_log(NULL, "starting terminal for system command '%s'", cmd);
+    buf = term_start(NULL, argv, &opt, TERM_START_SYSTEM);
+
+    /* Find a window to make "buf" curbuf. */
+    aucmd_prepbuf(&aco, buf);
+
+    clear_oparg(&oa);
+    while (term_use_loop())
+    {
+	if (oa.op_type == OP_NOP && oa.regname == NUL && !VIsual_active)
+	{
+	    /* If terminal_loop() returns OK we got a key that is handled
+	     * in Normal model. We don't do redrawing anyway. */
+	    if (terminal_loop(TRUE) == OK)
+		normal_cmd(&oa, TRUE);
+	}
+	else
+	    normal_cmd(&oa, TRUE);
+    }
+    retval = 0;
+    ch_log(NULL, "system command finished");
+
+    /* restore curwin/curbuf and a few other things */
+    aucmd_restbuf(&aco);
+
+    wait_return(TRUE);
+    do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
+
+theend:
+    vim_free(argv);
+    vim_free(tofree1);
+    vim_free(tofree2);
+    return retval;
+}
+#endif
+
+#ifdef USE_SYSTEM
+/*
+ * Use system() to start the shell: simple but slow.
+ */
+    static int
+mch_call_shell_system(
     char_u	*cmd,
     int		options)	/* SHELL_*, see vim.h */
 {
@@ -4369,7 +4485,6 @@ mch_call_shell(
     char	*ofn = NULL;
 #endif
     int		tmode = cur_tmode;
-#ifdef USE_SYSTEM	/* use system() to start the shell: simple but slow */
     char_u	*newcmd;	/* only needed for unix */
     int		x;
 
@@ -4443,14 +4558,23 @@ mch_call_shell(
     restore_clipboard();
 # endif
     return x;
-
-#else /* USE_SYSTEM */	    /* don't use system(), use fork()/exec() */
+}
+
+#else /* USE_SYSTEM */
 
 # define EXEC_FAILED 122    /* Exit code when shell didn't execute.  Don't use
 			       127, some shells use that already */
 # define OPEN_NULL_FAILED 123 /* Exit code if /dev/null can't be opened */
 
-    char_u	*newcmd;
+/*
+ * Don't use system(), use fork()/exec().
+ */
+    static int
+mch_call_shell_fork(
+    char_u	*cmd,
+    int		options)	/* SHELL_*, see vim.h */
+{
+    int		tmode = cur_tmode;
     pid_t	pid;
     pid_t	wpid = 0;
     pid_t	wait_pid = 0;
@@ -4461,8 +4585,8 @@ mch_call_shell(
 # endif
     int		retval = -1;
     char	**argv = NULL;
-    int		argc;
-    char_u	*p_shcf_copy = NULL;
+    char_u	*tofree1 = NULL;
+    char_u	*tofree2 = NULL;
     int		i;
     char_u	*p;
     int		pty_master_fd = -1;	    /* for pty's */
@@ -4474,44 +4598,13 @@ mch_call_shell(
     int		pipe_error = FALSE;
     int		did_settmode = FALSE;	/* settmode(TMODE_RAW) called */
 
-    newcmd = vim_strsave(p_sh);
-    if (newcmd == NULL)		/* out of memory */
-	goto error;
-
     out_flush();
     if (options & SHELL_COOKED)
 	settmode(TMODE_COOK);		/* set to normal mode */
 
-    if (mch_parse_cmd(newcmd, TRUE, &argv, &argc) == FAIL)
+    if (build_argv(cmd, &argv, &tofree1, &tofree2) == FAIL)
 	goto error;
 
-    if (cmd != NULL)
-    {
-	char_u	*s;
-
-	if (extra_shell_arg != NULL)
-	    argv[argc++] = (char *)extra_shell_arg;
-
-	/* Break 'shellcmdflag' into white separated parts.  This doesn't
-	 * handle quoted strings, they are very unlikely to appear. */
-	p_shcf_copy = alloc((unsigned)STRLEN(p_shcf) + 1);
-	if (p_shcf_copy == NULL)    /* out of memory */
-	    goto error;
-	s = p_shcf_copy;
-	p = p_shcf;
-	while (*p != NUL)
-	{
-	    argv[argc++] = (char *)s;
-	    while (*p && *p != ' ' && *p != TAB)
-		*s++ = *p++;
-	    *s++ = NUL;
-	    p = skipwhite(p);
-	}
-
-	argv[argc++] = (char *)cmd;
-    }
-    argv[argc] = NULL;
-
     /*
      * For the GUI, when writing the output into the buffer and when reading
      * input from the buffer: Try using a pseudo-tty to get the stdin/stdout
@@ -5319,8 +5412,6 @@ finished:
 		MSG_PUTS(_("\nCommand terminated\n"));
 	}
     }
-    vim_free(argv);
-    vim_free(p_shcf_copy);
 
 error:
     if (!did_settmode)
@@ -5329,11 +5420,28 @@ error:
 # ifdef FEAT_TITLE
     resettitle();
 # endif
-    vim_free(newcmd);
+    vim_free(argv);
+    vim_free(tofree1);
+    vim_free(tofree2);
 
     return retval;
-
+}
 #endif /* USE_SYSTEM */
+
+    int
+mch_call_shell(
+    char_u	*cmd,
+    int		options)	/* SHELL_*, see vim.h */
+{
+#if defined(FEAT_GUI) && defined(FEAT_TERMINAL)
+    if (gui.in_use && vim_strchr(p_go, GO_TERMINAL) != NULL)
+	return mch_call_shell_terminal(cmd, options);
+#endif
+#ifdef USE_SYSTEM
+    return mch_call_shell_system(cmd, options);
+#else
+    return mch_call_shell_fork(cmd, options);
+#endif
 }
 
 #if defined(FEAT_JOB_CHANNEL) || defined(PROTO)
--- a/src/proto/channel.pro
+++ b/src/proto/channel.pro
@@ -66,7 +66,7 @@ void job_set_options(job_T *job, jobopt_
 void job_stop_on_exit(void);
 int has_pending_job(void);
 void job_check_ended(void);
-job_T *job_start(typval_T *argvars, jobopt_T *opt_arg);
+job_T *job_start(typval_T *argvars, char **argv_arg, jobopt_T *opt_arg);
 char *job_status(job_T *job);
 void job_info(job_T *job, dict_T *dict);
 int job_stop(job_T *job, typval_T *argvars, char *type);
--- a/src/proto/terminal.pro
+++ b/src/proto/terminal.pro
@@ -1,4 +1,6 @@
 /* terminal.c */
+void init_job_options(jobopt_T *opt);
+buf_T *term_start(typval_T *argvar, char **argv, jobopt_T *opt, int flags);
 void ex_terminal(exarg_T *eap);
 int term_write_session(FILE *fd, win_T *wp);
 int term_should_restore(buf_T *buf);
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -38,10 +38,14 @@
  * in tl_scrollback are no longer used.
  *
  * TODO:
- * - When using 'termguicolors' still use the 16 ANSI colors as-is.  Helps for
- * - In the GUI use a terminal emulator for :!cmd.  Make the height the same as
- *   the window and position it higher up when it gets filled, so it looks like
- *   the text scrolls up.
+ * - Make terminal close by default when started without a command.  Add
+ *   ++noclose argument.
+ * - Win32: In the GUI use a terminal emulator for :!cmd.
+ * - Add a way to set the 16 ANSI colors, to be used for 'termguicolors' and in
+ *   the GUI.
+ * - Some way for the job running in the terminal to send a :drop command back
+ *   to the Vim running the terminal.  Should be usable by a simple shell or
+ *   python script.
  * - implement term_setsize()
  * - Copy text in the vterm to the Vim buffer once in a while, so that
  *   completion works.
@@ -104,6 +108,10 @@ struct terminal_S {
     VTerm	*tl_vterm;
     job_T	*tl_job;
     buf_T	*tl_buffer;
+#if defined(FEAT_GUI)
+    int		tl_system;	/* when non-zero used for :!cmd output */
+    int		tl_toprow;	/* row with first line of system terminal */
+#endif
 
     /* Set when setting the size of a vterm, reset after redrawing. */
     int		tl_vterm_size_changed;
@@ -175,10 +183,13 @@ 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, typval_T *argvar, jobopt_T *opt);
+static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, 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);
+#ifdef FEAT_GUI
+static void update_system_term(term_T *term);
+#endif
 
 /* The character that we know (or assume) that the terminal expects for the
  * backspace key. */
@@ -209,6 +220,16 @@ static int	desired_cursor_blink = -1;
     static void
 set_term_and_win_size(term_T *term)
 {
+#ifdef FEAT_GUI
+    if (term->tl_system)
+    {
+	/* Use the whole screen for the system command.  However, it will start
+	 * at the command line and scroll up as needed, using tl_toprow. */
+	term->tl_rows = Rows;
+	term->tl_cols = Columns;
+    }
+    else
+#endif
     if (*curwin->w_p_tms != NUL)
     {
 	char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
@@ -236,7 +257,7 @@ set_term_and_win_size(term_T *term)
  * Initialize job options for a terminal job.
  * Caller may overrule some of them.
  */
-    static void
+    void
 init_job_options(jobopt_T *opt)
 {
     clear_job_options(opt);
@@ -301,12 +322,17 @@ term_close_buffer(buf_T *buf, buf_T *old
 
 /*
  * Start a terminal window and return its buffer.
- * When "without_job" is TRUE only create the buffer, b_term and open the
- * window.
+ * Use either "argvar" or "argv", the other must be NULL.
+ * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
+ * the window.
  * Returns NULL when failed.
  */
-    static buf_T *
-term_start(typval_T *argvar, jobopt_T *opt, int without_job, int forceit)
+    buf_T *
+term_start(
+	typval_T    *argvar,
+	char	    **argv,
+	jobopt_T    *opt,
+	int	    flags)
 {
     exarg_T	split_ea;
     win_T	*old_curwin = curwin;
@@ -334,26 +360,31 @@ term_start(typval_T *argvar, jobopt_T *o
     term->tl_cursor_visible = TRUE;
     term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
     term->tl_finish = opt->jo_term_finish;
+#ifdef FEAT_GUI
+    term->tl_system = (flags & TERM_START_SYSTEM);
+#endif
     ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
 
     vim_memset(&split_ea, 0, sizeof(split_ea));
     if (opt->jo_curwin)
     {
 	/* Create a new buffer in the current window. */
-	if (!can_abandon(curbuf, forceit))
+	if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
 	{
 	    no_write_message();
 	    vim_free(term);
 	    return NULL;
 	}
 	if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
-		     ECMD_HIDE + (forceit ? ECMD_FORCEIT : 0), curwin) == FAIL)
+		     ECMD_HIDE
+			   + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
+		     curwin) == FAIL)
 	{
 	    vim_free(term);
 	    return NULL;
 	}
     }
-    else if (opt->jo_hidden)
+    else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
     {
 	buf_T *buf;
 
@@ -418,6 +449,8 @@ term_start(typval_T *argvar, jobopt_T *o
 
     if (opt->jo_term_name != NULL)
 	curbuf->b_ffname = vim_strsave(opt->jo_term_name);
+    else if (argv != NULL)
+	curbuf->b_ffname = vim_strsave((char_u *)"!system");
     else
     {
 	int	i;
@@ -476,12 +509,12 @@ term_start(typval_T *argvar, jobopt_T *o
     set_term_and_win_size(term);
     setup_job_options(opt, term->tl_rows, term->tl_cols);
 
-    if (without_job)
+    if (flags & TERM_START_NOJOB)
 	return curbuf;
 
 #if defined(FEAT_SESSION)
     /* Remember the command for the session file. */
-    if (opt->jo_term_norestore)
+    if (opt->jo_term_norestore || argv != NULL)
     {
 	term->tl_command = vim_strsave((char_u *)"NONE");
     }
@@ -533,12 +566,13 @@ term_start(typval_T *argvar, jobopt_T *o
     }
 
     /* System dependent: setup the vterm and maybe start the job in it. */
-    if (argvar->v_type == VAR_STRING
+    if (argv == NULL
+	    && 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);
+	res = term_and_job_init(term, argvar, argv, opt);
 
     newbuf = curbuf;
     if (res == OK)
@@ -546,19 +580,26 @@ term_start(typval_T *argvar, jobopt_T *o
 	/* Get and remember the size we ended up with.  Update the pty. */
 	vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
 	term_report_winsize(term, term->tl_rows, term->tl_cols);
+#ifdef FEAT_GUI
+	if (term->tl_system)
+	{
+	    /* display first line below typed command */
+	    term->tl_toprow = msg_row + 1;
+	    term->tl_dirty_row_end = 0;
+	}
+#endif
 
 	/* Make sure we don't get stuck on sending keys to the job, it leads to
 	 * a deadlock if the job is waiting for Vim to read. */
 	channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
 
-	if (!opt->jo_hidden)
+	if (old_curbuf == NULL)
 	{
 	    ++curbuf->b_locked;
 	    apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
 	    --curbuf->b_locked;
 	}
-
-	if (old_curbuf != NULL)
+	else
 	{
 	    --curbuf->b_nwindows;
 	    curbuf = old_curbuf;
@@ -572,7 +613,7 @@ term_start(typval_T *argvar, jobopt_T *o
 	return NULL;
     }
 
-    apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, curbuf);
+    apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
     return newbuf;
 }
 
@@ -671,7 +712,7 @@ ex_terminal(exarg_T *eap)
     argvar[0].v_type = VAR_STRING;
     argvar[0].vval.v_string = cmd;
     argvar[1].v_type = VAR_UNKNOWN;
-    term_start(argvar, &opt, FALSE, eap->forceit);
+    term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
     vim_free(tofree);
 
 theend:
@@ -833,7 +874,13 @@ update_cursor(term_T *term, int redraw)
 {
     if (term->tl_normal_mode)
 	return;
-    setcursor();
+#ifdef FEAT_GUI
+    if (term->tl_system)
+	windgoto(term->tl_cursor_pos.row + term->tl_toprow,
+						      term->tl_cursor_pos.col);
+    else
+#endif
+	setcursor();
     if (redraw)
     {
 	if (term->tl_buffer == curbuf && term->tl_cursor_visible)
@@ -867,6 +914,15 @@ write_to_term(buf_T *buffer, char_u *msg
     ch_log(channel, "writing %d bytes to terminal", (int)len);
     term_write_job_output(term, msg, len);
 
+#ifdef FEAT_GUI
+    if (term->tl_system)
+    {
+	/* show system output, scrolling up the screen as needed */
+	update_system_term(term);
+	update_cursor(term, TRUE);
+    }
+    else
+#endif
     /* In Terminal-Normal mode we are displaying the buffer, not the terminal
      * contents, thus no screen update is needed. */
     if (!term->tl_normal_mode)
@@ -1905,11 +1961,15 @@ terminal_loop(int blocking)
 
     while (blocking || vpeekc_nomap() != NUL)
     {
-	/* TODO: skip screen update when handling a sequence of keys. */
-	/* Repeat redrawing in case a message is received while redrawing. */
-	while (must_redraw != 0)
-	    if (update_screen(0) == FAIL)
-		break;
+#ifdef FEAT_GUI
+	if (!curbuf->b_term->tl_system)
+#endif
+	    /* TODO: skip screen update when handling a sequence of keys. */
+	    /* Repeat redrawing in case a message is received while redrawing.
+	     */
+	    while (must_redraw != 0)
+		if (update_screen(0) == FAIL)
+		    break;
 	update_cursor(curbuf->b_term, FALSE);
 	restore_cursor = TRUE;
 
@@ -2586,6 +2646,139 @@ term_channel_closed(channel_T *ch)
 }
 
 /*
+ * Fill one screen line from a line of the terminal.
+ * Advances "pos" to past the last column.
+ */
+    static void
+term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
+{
+    int off = screen_get_current_line_off();
+
+    for (pos->col = 0; pos->col < max_col; )
+    {
+	VTermScreenCell cell;
+	int		c;
+
+	if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
+	    vim_memset(&cell, 0, sizeof(cell));
+
+	c = cell.chars[0];
+	if (c == NUL)
+	{
+	    ScreenLines[off] = ' ';
+	    if (enc_utf8)
+		ScreenLinesUC[off] = NUL;
+	}
+	else
+	{
+	    if (enc_utf8)
+	    {
+		int i;
+
+		/* composing chars */
+		for (i = 0; i < Screen_mco
+			      && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
+		{
+		    ScreenLinesC[i][off] = cell.chars[i + 1];
+		    if (cell.chars[i + 1] == 0)
+			break;
+		}
+		if (c >= 0x80 || (Screen_mco > 0
+					 && ScreenLinesC[0][off] != 0))
+		{
+		    ScreenLines[off] = ' ';
+		    ScreenLinesUC[off] = c;
+		}
+		else
+		{
+		    ScreenLines[off] = c;
+		    ScreenLinesUC[off] = NUL;
+		}
+	    }
+#ifdef WIN3264
+	    else if (has_mbyte && c >= 0x80)
+	    {
+		char_u	mb[MB_MAXBYTES+1];
+		WCHAR	wc = c;
+
+		if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
+					       (char*)mb, 2, 0, 0) > 1)
+		{
+		    ScreenLines[off] = mb[0];
+		    ScreenLines[off + 1] = mb[1];
+		    cell.width = mb_ptr2cells(mb);
+		}
+		else
+		    ScreenLines[off] = c;
+	    }
+#endif
+	    else
+		ScreenLines[off] = c;
+	}
+	ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
+
+	++pos->col;
+	++off;
+	if (cell.width == 2)
+	{
+	    if (enc_utf8)
+		ScreenLinesUC[off] = NUL;
+
+	    /* don't set the second byte to NUL for a DBCS encoding, it
+	     * has been set above */
+	    if (enc_utf8 || !has_mbyte)
+		ScreenLines[off] = NUL;
+
+	    ++pos->col;
+	    ++off;
+	}
+    }
+}
+
+    static void
+update_system_term(term_T *term)
+{
+    VTermPos	    pos;
+    VTermScreen	    *screen;
+
+    if (term->tl_vterm == NULL)
+	return;
+    screen = vterm_obtain_screen(term->tl_vterm);
+
+    /* Scroll up to make more room for terminal lines if needed. */
+    while (term->tl_toprow > 0
+			  && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
+    {
+	int save_p_more = p_more;
+
+	p_more = FALSE;
+	msg_row = Rows - 1;
+	msg_puts((char_u *)"\n");
+	p_more = save_p_more;
+	--term->tl_toprow;
+    }
+
+    for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
+						  && pos.row < Rows; ++pos.row)
+    {
+	if (pos.row < term->tl_rows)
+	{
+	    int max_col = MIN(Columns, term->tl_cols);
+
+	    term_line2screenline(screen, &pos, max_col);
+	}
+	else
+	    pos.col = 0;
+
+	screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
+    }
+
+    term->tl_dirty_row_start = MAX_ROW;
+    term->tl_dirty_row_end = 0;
+    update_cursor(term, TRUE);
+}
+
+/*
  * Called to update a window that contains an active terminal.
  * Returns FAIL when there is no terminal running in this window or in
  * Terminal-Normal mode.
@@ -2650,90 +2843,11 @@ term_update_window(win_T *wp)
     for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
 					  && pos.row < wp->w_height; ++pos.row)
     {
-	int off = screen_get_current_line_off();
-	int max_col = MIN(wp->w_width, term->tl_cols);
-
 	if (pos.row < term->tl_rows)
 	{
-	    for (pos.col = 0; pos.col < max_col; )
-	    {
-		VTermScreenCell cell;
-		int		c;
-
-		if (vterm_screen_get_cell(screen, pos, &cell) == 0)
-		    vim_memset(&cell, 0, sizeof(cell));
-
-		c = cell.chars[0];
-		if (c == NUL)
-		{
-		    ScreenLines[off] = ' ';
-		    if (enc_utf8)
-			ScreenLinesUC[off] = NUL;
-		}
-		else
-		{
-		    if (enc_utf8)
-		    {
-			int i;
-
-			/* composing chars */
-			for (i = 0; i < Screen_mco
-				      && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
-			{
-			    ScreenLinesC[i][off] = cell.chars[i + 1];
-			    if (cell.chars[i + 1] == 0)
-				break;
-			}
-			if (c >= 0x80 || (Screen_mco > 0
-						 && ScreenLinesC[0][off] != 0))
-			{
-			    ScreenLines[off] = ' ';
-			    ScreenLinesUC[off] = c;
-			}
-			else
-			{
-			    ScreenLines[off] = c;
-			    ScreenLinesUC[off] = NUL;
-			}
-		    }
-#ifdef WIN3264
-		    else if (has_mbyte && c >= 0x80)
-		    {
-			char_u	mb[MB_MAXBYTES+1];
-			WCHAR	wc = c;
-
-			if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
-						       (char*)mb, 2, 0, 0) > 1)
-			{
-			    ScreenLines[off] = mb[0];
-			    ScreenLines[off + 1] = mb[1];
-			    cell.width = mb_ptr2cells(mb);
-			}
-			else
-			    ScreenLines[off] = c;
-		    }
-#endif
-		    else
-			ScreenLines[off] = c;
-		}
-		ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
-
-		++pos.col;
-		++off;
-		if (cell.width == 2)
-		{
-		    if (enc_utf8)
-			ScreenLinesUC[off] = NUL;
-
-		    /* don't set the second byte to NUL for a DBCS encoding, it
-		     * has been set above */
-		    if (enc_utf8 || !has_mbyte)
-			ScreenLines[off] = NUL;
-
-		    ++pos.col;
-		    ++off;
-		}
-	    }
+	    int max_col = MIN(wp->w_width, term->tl_cols);
+
+	    term_line2screenline(screen, &pos, max_col);
 	}
 	else
 	    pos.col = 0;
@@ -3623,7 +3737,7 @@ term_load_dump(typval_T *argvars, typval
     /* TODO: use the file name arguments for the buffer name */
     opt.jo_term_name = (char_u *)"dump diff";
 
-    buf = term_start(&argvars[0], &opt, TRUE, FALSE);
+    buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
     if (buf != NULL && buf->b_term != NULL)
     {
 	int		i;
@@ -4396,7 +4510,7 @@ f_term_start(typval_T *argvars, typval_T
 
     if (opt.jo_vertical)
 	cmdmod.split = WSP_VERT;
-    buf = term_start(&argvars[0], &opt, FALSE, FALSE);
+    buf = term_start(&argvars[0], NULL, &opt, 0);
 
     if (buf != NULL && buf->b_term != NULL)
 	rettv->vval.v_number = buf->b_fnum;
@@ -4592,6 +4706,7 @@ dyn_winpty_init(int verbose)
 term_and_job_init(
 	term_T	    *term,
 	typval_T    *argvar,
+	char	    **argv UNUSED,
 	jobopt_T    *opt)
 {
     WCHAR	    *cmd_wchar = NULL;
@@ -4880,18 +4995,20 @@ terminal_enabled(void)
  * Create a new terminal of "rows" by "cols" cells.
  * Start job for "cmd".
  * Store the pointers in "term".
+ * When "argv" is not NULL then "argvar" is not used.
  * Return OK or FAIL.
  */
     static int
 term_and_job_init(
 	term_T	    *term,
 	typval_T    *argvar,
+	char	    **argv,
 	jobopt_T    *opt)
 {
     create_vterm(term, term->tl_rows, term->tl_cols);
 
-    /* This will change a string in "argvar". */
-    term->tl_job = job_start(argvar, opt);
+    /* This may change a string in "argvar". */
+    term->tl_job = job_start(argvar, argv, opt);
     if (term->tl_job != NULL)
 	++term->tl_job->jv_refcount;
 
--- a/src/version.c
+++ b/src/version.c
@@ -767,6 +767,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1609,
+/**/
     1608,
 /**/
     1607,
--- a/src/vim.h
+++ b/src/vim.h
@@ -2543,4 +2543,9 @@ typedef enum {
 #define REPLACE_CR_NCHAR    -1
 #define REPLACE_NL_NCHAR    -2
 
+/* flags for term_start() */
+#define TERM_START_NOJOB	1
+#define TERM_START_FORCEIT	2
+#define TERM_START_SYSTEM	4
+
 #endif /* VIM__H */