xref: /vim-8.2.3635/src/terminal.c (revision 94688b8a)
1 /* vi:set ts=8 sts=4 sw=4 noet:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  * See README.txt for an overview of the Vim source code.
8  */
9 
10 /*
11  * Terminal window support, see ":help :terminal".
12  *
13  * There are three parts:
14  * 1. Generic code for all systems.
15  *    Uses libvterm for the terminal emulator.
16  * 2. The MS-Windows implementation.
17  *    Uses winpty.
18  * 3. The Unix-like implementation.
19  *    Uses pseudo-tty's (pty's).
20  *
21  * For each terminal one VTerm is constructed.  This uses libvterm.  A copy of
22  * this library is in the libvterm directory.
23  *
24  * When a terminal window is opened, a job is started that will be connected to
25  * the terminal emulator.
26  *
27  * If the terminal window has keyboard focus, typed keys are converted to the
28  * terminal encoding and writing to the job over a channel.
29  *
30  * If the job produces output, it is written to the terminal emulator.  The
31  * terminal emulator invokes callbacks when its screen content changes.  The
32  * line range is stored in tl_dirty_row_start and tl_dirty_row_end.  Once in a
33  * while, if the terminal window is visible, the screen contents is drawn.
34  *
35  * When the job ends the text is put in a buffer.  Redrawing then happens from
36  * that buffer, attributes come from the scrollback buffer tl_scrollback.
37  * When the buffer is changed it is turned into a normal buffer, the attributes
38  * in tl_scrollback are no longer used.
39  */
40 
41 #include "vim.h"
42 
43 #if defined(FEAT_TERMINAL) || defined(PROTO)
44 
45 #ifndef MIN
46 # define MIN(x,y) ((x) < (y) ? (x) : (y))
47 #endif
48 #ifndef MAX
49 # define MAX(x,y) ((x) > (y) ? (x) : (y))
50 #endif
51 
52 #include "libvterm/include/vterm.h"
53 
54 /* This is VTermScreenCell without the characters, thus much smaller. */
55 typedef struct {
56   VTermScreenCellAttrs	attrs;
57   char			width;
58   VTermColor		fg;
59   VTermColor		bg;
60 } cellattr_T;
61 
62 typedef struct sb_line_S {
63     int		sb_cols;	/* can differ per line */
64     cellattr_T	*sb_cells;	/* allocated */
65     cellattr_T	sb_fill_attr;	/* for short line */
66 } sb_line_T;
67 
68 #ifdef WIN3264
69 # ifndef HPCON
70 #  define HPCON VOID*
71 # endif
72 # ifndef EXTENDED_STARTUPINFO_PRESENT
73 #  define EXTENDED_STARTUPINFO_PRESENT 0x00080000
74 # endif
75 # ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
76 #  define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
77 # endif
78 typedef struct _DYN_STARTUPINFOEXW
79 {
80     STARTUPINFOW StartupInfo;
81     LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
82 } DYN_STARTUPINFOEXW, *PDYN_STARTUPINFOEXW;
83 #endif
84 
85 /* typedef term_T in structs.h */
86 struct terminal_S {
87     term_T	*tl_next;
88 
89     VTerm	*tl_vterm;
90     job_T	*tl_job;
91     buf_T	*tl_buffer;
92 #if defined(FEAT_GUI)
93     int		tl_system;	/* when non-zero used for :!cmd output */
94     int		tl_toprow;	/* row with first line of system terminal */
95 #endif
96 
97     /* Set when setting the size of a vterm, reset after redrawing. */
98     int		tl_vterm_size_changed;
99 
100     int		tl_normal_mode; /* TRUE: Terminal-Normal mode */
101     int		tl_channel_closed;
102     int		tl_channel_recently_closed; // still need to handle tl_finish
103 
104     int		tl_finish;
105 #define TL_FINISH_UNSET	    NUL
106 #define TL_FINISH_CLOSE	    'c'	/* ++close or :terminal without argument */
107 #define TL_FINISH_NOCLOSE   'n'	/* ++noclose */
108 #define TL_FINISH_OPEN	    'o'	/* ++open */
109     char_u	*tl_opencmd;
110     char_u	*tl_eof_chars;
111 
112     char_u	*tl_arg0_cmd;	// To format the status bar
113 
114 #ifdef WIN3264
115     void	*tl_winpty_config;
116     void	*tl_winpty;
117 
118     HPCON	tl_conpty;
119     DYN_STARTUPINFOEXW tl_siex;	// Structure that always needs to be hold
120 
121     FILE	*tl_out_fd;
122 #endif
123 #if defined(FEAT_SESSION)
124     char_u	*tl_command;
125 #endif
126     char_u	*tl_kill;
127 
128     /* last known vterm size */
129     int		tl_rows;
130     int		tl_cols;
131 
132     char_u	*tl_title; /* NULL or allocated */
133     char_u	*tl_status_text; /* NULL or allocated */
134 
135     /* Range of screen rows to update.  Zero based. */
136     int		tl_dirty_row_start; /* MAX_ROW if nothing dirty */
137     int		tl_dirty_row_end;   /* row below last one to update */
138     int		tl_dirty_snapshot;  /* text updated after making snapshot */
139 #ifdef FEAT_TIMERS
140     int		tl_timer_set;
141     proftime_T	tl_timer_due;
142 #endif
143     int		tl_postponed_scroll;	/* to be scrolled up */
144 
145     garray_T	tl_scrollback;
146     int		tl_scrollback_scrolled;
147     cellattr_T	tl_default_color;
148 
149     linenr_T	tl_top_diff_rows;   /* rows of top diff file or zero */
150     linenr_T	tl_bot_diff_rows;   /* rows of bottom diff file */
151 
152     VTermPos	tl_cursor_pos;
153     int		tl_cursor_visible;
154     int		tl_cursor_blink;
155     int		tl_cursor_shape;  /* 1: block, 2: underline, 3: bar */
156     char_u	*tl_cursor_color; /* NULL or allocated */
157 
158     int		tl_using_altscreen;
159 };
160 
161 #define TMODE_ONCE 1	    /* CTRL-\ CTRL-N used */
162 #define TMODE_LOOP 2	    /* CTRL-W N used */
163 
164 /*
165  * List of all active terminals.
166  */
167 static term_T *first_term = NULL;
168 
169 /* Terminal active in terminal_loop(). */
170 static term_T *in_terminal_loop = NULL;
171 
172 #ifdef WIN3264
173 static BOOL has_winpty = FALSE;
174 static BOOL has_conpty = FALSE;
175 #endif
176 
177 #define MAX_ROW 999999	    /* used for tl_dirty_row_end to update all rows */
178 #define KEY_BUF_LEN 200
179 
180 /*
181  * Functions with separate implementation for MS-Windows and Unix-like systems.
182  */
183 static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt, jobopt_T *orig_opt);
184 static int create_pty_only(term_T *term, jobopt_T *opt);
185 static void term_report_winsize(term_T *term, int rows, int cols);
186 static void term_free_vterm(term_T *term);
187 #ifdef FEAT_GUI
188 static void update_system_term(term_T *term);
189 #endif
190 
191 /* The character that we know (or assume) that the terminal expects for the
192  * backspace key. */
193 static int term_backspace_char = BS;
194 
195 /* "Terminal" highlight group colors. */
196 static int term_default_cterm_fg = -1;
197 static int term_default_cterm_bg = -1;
198 
199 /* Store the last set and the desired cursor properties, so that we only update
200  * them when needed.  Doing it unnecessary may result in flicker. */
201 static char_u	*last_set_cursor_color = NULL;
202 static char_u	*desired_cursor_color = NULL;
203 static int	last_set_cursor_shape = -1;
204 static int	desired_cursor_shape = -1;
205 static int	last_set_cursor_blink = -1;
206 static int	desired_cursor_blink = -1;
207 
208 
209 /**************************************
210  * 1. Generic code for all systems.
211  */
212 
213     static int
214 cursor_color_equal(char_u *lhs_color, char_u *rhs_color)
215 {
216     if (lhs_color != NULL && rhs_color != NULL)
217 	return STRCMP(lhs_color, rhs_color) == 0;
218     return lhs_color == NULL && rhs_color == NULL;
219 }
220 
221     static void
222 cursor_color_copy(char_u **to_color, char_u *from_color)
223 {
224     // Avoid a free & alloc if the value is already right.
225     if (cursor_color_equal(*to_color, from_color))
226 	return;
227     vim_free(*to_color);
228     *to_color = (from_color == NULL) ? NULL : vim_strsave(from_color);
229 }
230 
231     static char_u *
232 cursor_color_get(char_u *color)
233 {
234     return (color == NULL) ? (char_u *)"" : color;
235 }
236 
237 
238 /*
239  * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
240  * current window.
241  * Sets "rows" and/or "cols" to zero when it should follow the window size.
242  * Return TRUE if the size is the minimum size: "24*80".
243  */
244     static int
245 parse_termwinsize(win_T *wp, int *rows, int *cols)
246 {
247     int	minsize = FALSE;
248 
249     *rows = 0;
250     *cols = 0;
251 
252     if (*wp->w_p_tws != NUL)
253     {
254 	char_u *p = vim_strchr(wp->w_p_tws, 'x');
255 
256 	/* Syntax of value was already checked when it's set. */
257 	if (p == NULL)
258 	{
259 	    minsize = TRUE;
260 	    p = vim_strchr(wp->w_p_tws, '*');
261 	}
262 	*rows = atoi((char *)wp->w_p_tws);
263 	*cols = atoi((char *)p + 1);
264     }
265     return minsize;
266 }
267 
268 /*
269  * Determine the terminal size from 'termwinsize' and the current window.
270  */
271     static void
272 set_term_and_win_size(term_T *term)
273 {
274 #ifdef FEAT_GUI
275     if (term->tl_system)
276     {
277 	/* Use the whole screen for the system command.  However, it will start
278 	 * at the command line and scroll up as needed, using tl_toprow. */
279 	term->tl_rows = Rows;
280 	term->tl_cols = Columns;
281 	return;
282     }
283 #endif
284     if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols))
285     {
286 	if (term->tl_rows != 0)
287 	    term->tl_rows = MAX(term->tl_rows, curwin->w_height);
288 	if (term->tl_cols != 0)
289 	    term->tl_cols = MAX(term->tl_cols, curwin->w_width);
290     }
291     if (term->tl_rows == 0)
292 	term->tl_rows = curwin->w_height;
293     else
294 	win_setheight_win(term->tl_rows, curwin);
295     if (term->tl_cols == 0)
296 	term->tl_cols = curwin->w_width;
297     else
298 	win_setwidth_win(term->tl_cols, curwin);
299 }
300 
301 /*
302  * Initialize job options for a terminal job.
303  * Caller may overrule some of them.
304  */
305     void
306 init_job_options(jobopt_T *opt)
307 {
308     clear_job_options(opt);
309 
310     opt->jo_mode = MODE_RAW;
311     opt->jo_out_mode = MODE_RAW;
312     opt->jo_err_mode = MODE_RAW;
313     opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
314 }
315 
316 /*
317  * Set job options mandatory for a terminal job.
318  */
319     static void
320 setup_job_options(jobopt_T *opt, int rows, int cols)
321 {
322 #ifndef WIN3264
323     /* Win32: Redirecting the job output won't work, thus always connect stdout
324      * here. */
325     if (!(opt->jo_set & JO_OUT_IO))
326 #endif
327     {
328 	/* Connect stdout to the terminal. */
329 	opt->jo_io[PART_OUT] = JIO_BUFFER;
330 	opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
331 	opt->jo_modifiable[PART_OUT] = 0;
332 	opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
333     }
334 
335 #ifndef WIN3264
336     /* Win32: Redirecting the job output won't work, thus always connect stderr
337      * here. */
338     if (!(opt->jo_set & JO_ERR_IO))
339 #endif
340     {
341 	/* Connect stderr to the terminal. */
342 	opt->jo_io[PART_ERR] = JIO_BUFFER;
343 	opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
344 	opt->jo_modifiable[PART_ERR] = 0;
345 	opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
346     }
347 
348     opt->jo_pty = TRUE;
349     if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
350 	opt->jo_term_rows = rows;
351     if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
352 	opt->jo_term_cols = cols;
353 }
354 
355 /*
356  * Close a terminal buffer (and its window).  Used when creating the terminal
357  * fails.
358  */
359     static void
360 term_close_buffer(buf_T *buf, buf_T *old_curbuf)
361 {
362     free_terminal(buf);
363     if (old_curbuf != NULL)
364     {
365 	--curbuf->b_nwindows;
366 	curbuf = old_curbuf;
367 	curwin->w_buffer = curbuf;
368 	++curbuf->b_nwindows;
369     }
370 
371     /* Wiping out the buffer will also close the window and call
372      * free_terminal(). */
373     do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
374 }
375 
376 /*
377  * Start a terminal window and return its buffer.
378  * Use either "argvar" or "argv", the other must be NULL.
379  * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
380  * the window.
381  * Returns NULL when failed.
382  */
383     buf_T *
384 term_start(
385 	typval_T    *argvar,
386 	char	    **argv,
387 	jobopt_T    *opt,
388 	int	    flags)
389 {
390     exarg_T	split_ea;
391     win_T	*old_curwin = curwin;
392     term_T	*term;
393     buf_T	*old_curbuf = NULL;
394     int		res;
395     buf_T	*newbuf;
396     int		vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
397     jobopt_T	orig_opt;  // only partly filled
398 
399     if (check_restricted() || check_secure())
400 	return NULL;
401 
402     if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
403 					 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
404 	|| (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
405 	|| (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
406     {
407 	emsg(_(e_invarg));
408 	return NULL;
409     }
410 
411     term = (term_T *)alloc_clear(sizeof(term_T));
412     if (term == NULL)
413 	return NULL;
414     term->tl_dirty_row_end = MAX_ROW;
415     term->tl_cursor_visible = TRUE;
416     term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
417     term->tl_finish = opt->jo_term_finish;
418 #ifdef FEAT_GUI
419     term->tl_system = (flags & TERM_START_SYSTEM);
420 #endif
421     ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
422 
423     vim_memset(&split_ea, 0, sizeof(split_ea));
424     if (opt->jo_curwin)
425     {
426 	/* Create a new buffer in the current window. */
427 	if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
428 	{
429 	    no_write_message();
430 	    vim_free(term);
431 	    return NULL;
432 	}
433 	if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
434 		     ECMD_HIDE
435 			   + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
436 		     curwin) == FAIL)
437 	{
438 	    vim_free(term);
439 	    return NULL;
440 	}
441     }
442     else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
443     {
444 	buf_T *buf;
445 
446 	/* Create a new buffer without a window. Make it the current buffer for
447 	 * a moment to be able to do the initialisations. */
448 	buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
449 							 BLN_NEW | BLN_LISTED);
450 	if (buf == NULL || ml_open(buf) == FAIL)
451 	{
452 	    vim_free(term);
453 	    return NULL;
454 	}
455 	old_curbuf = curbuf;
456 	--curbuf->b_nwindows;
457 	curbuf = buf;
458 	curwin->w_buffer = buf;
459 	++curbuf->b_nwindows;
460     }
461     else
462     {
463 	/* Open a new window or tab. */
464 	split_ea.cmdidx = CMD_new;
465 	split_ea.cmd = (char_u *)"new";
466 	split_ea.arg = (char_u *)"";
467 	if (opt->jo_term_rows > 0 && !vertical)
468 	{
469 	    split_ea.line2 = opt->jo_term_rows;
470 	    split_ea.addr_count = 1;
471 	}
472 	if (opt->jo_term_cols > 0 && vertical)
473 	{
474 	    split_ea.line2 = opt->jo_term_cols;
475 	    split_ea.addr_count = 1;
476 	}
477 
478 	if (vertical)
479 	    cmdmod.split |= WSP_VERT;
480 	ex_splitview(&split_ea);
481 	if (curwin == old_curwin)
482 	{
483 	    /* split failed */
484 	    vim_free(term);
485 	    return NULL;
486 	}
487     }
488     term->tl_buffer = curbuf;
489     curbuf->b_term = term;
490 
491     if (!opt->jo_hidden)
492     {
493 	/* Only one size was taken care of with :new, do the other one.  With
494 	 * "curwin" both need to be done. */
495 	if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
496 	    win_setheight(opt->jo_term_rows);
497 	if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
498 	    win_setwidth(opt->jo_term_cols);
499     }
500 
501     /* Link the new terminal in the list of active terminals. */
502     term->tl_next = first_term;
503     first_term = term;
504 
505     if (opt->jo_term_name != NULL)
506 	curbuf->b_ffname = vim_strsave(opt->jo_term_name);
507     else if (argv != NULL)
508 	curbuf->b_ffname = vim_strsave((char_u *)"!system");
509     else
510     {
511 	int	i;
512 	size_t	len;
513 	char_u	*cmd, *p;
514 
515 	if (argvar->v_type == VAR_STRING)
516 	{
517 	    cmd = argvar->vval.v_string;
518 	    if (cmd == NULL)
519 		cmd = (char_u *)"";
520 	    else if (STRCMP(cmd, "NONE") == 0)
521 		cmd = (char_u *)"pty";
522 	}
523 	else if (argvar->v_type != VAR_LIST
524 		|| argvar->vval.v_list == NULL
525 		|| argvar->vval.v_list->lv_len < 1
526 		|| (cmd = tv_get_string_chk(
527 			       &argvar->vval.v_list->lv_first->li_tv)) == NULL)
528 	    cmd = (char_u*)"";
529 
530 	len = STRLEN(cmd) + 10;
531 	p = alloc((int)len);
532 
533 	for (i = 0; p != NULL; ++i)
534 	{
535 	    /* Prepend a ! to the command name to avoid the buffer name equals
536 	     * the executable, otherwise ":w!" would overwrite it. */
537 	    if (i == 0)
538 		vim_snprintf((char *)p, len, "!%s", cmd);
539 	    else
540 		vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
541 	    if (buflist_findname(p) == NULL)
542 	    {
543 		vim_free(curbuf->b_ffname);
544 		curbuf->b_ffname = p;
545 		break;
546 	    }
547 	}
548     }
549     curbuf->b_fname = curbuf->b_ffname;
550 
551     if (opt->jo_term_opencmd != NULL)
552 	term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
553 
554     if (opt->jo_eof_chars != NULL)
555 	term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
556 
557     set_string_option_direct((char_u *)"buftype", -1,
558 				  (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
559     // Avoid that 'buftype' is reset when this buffer is entered.
560     curbuf->b_p_initialized = TRUE;
561 
562     /* Mark the buffer as not modifiable. It can only be made modifiable after
563      * the job finished. */
564     curbuf->b_p_ma = FALSE;
565 
566     set_term_and_win_size(term);
567 #ifdef WIN3264
568     mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
569 #endif
570     setup_job_options(opt, term->tl_rows, term->tl_cols);
571 
572     if (flags & TERM_START_NOJOB)
573 	return curbuf;
574 
575 #if defined(FEAT_SESSION)
576     /* Remember the command for the session file. */
577     if (opt->jo_term_norestore || argv != NULL)
578     {
579 	term->tl_command = vim_strsave((char_u *)"NONE");
580     }
581     else if (argvar->v_type == VAR_STRING)
582     {
583 	char_u	*cmd = argvar->vval.v_string;
584 
585 	if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
586 	    term->tl_command = vim_strsave(cmd);
587     }
588     else if (argvar->v_type == VAR_LIST
589 	    && argvar->vval.v_list != NULL
590 	    && argvar->vval.v_list->lv_len > 0)
591     {
592 	garray_T	ga;
593 	listitem_T	*item;
594 
595 	ga_init2(&ga, 1, 100);
596 	for (item = argvar->vval.v_list->lv_first;
597 					item != NULL; item = item->li_next)
598 	{
599 	    char_u *s = tv_get_string_chk(&item->li_tv);
600 	    char_u *p;
601 
602 	    if (s == NULL)
603 		break;
604 	    p = vim_strsave_fnameescape(s, FALSE);
605 	    if (p == NULL)
606 		break;
607 	    ga_concat(&ga, p);
608 	    vim_free(p);
609 	    ga_append(&ga, ' ');
610 	}
611 	if (item == NULL)
612 	{
613 	    ga_append(&ga, NUL);
614 	    term->tl_command = ga.ga_data;
615 	}
616 	else
617 	    ga_clear(&ga);
618     }
619 #endif
620 
621     if (opt->jo_term_kill != NULL)
622     {
623 	char_u *p = skiptowhite(opt->jo_term_kill);
624 
625 	term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
626     }
627 
628     /* System dependent: setup the vterm and maybe start the job in it. */
629     if (argv == NULL
630 	    && argvar->v_type == VAR_STRING
631 	    && argvar->vval.v_string != NULL
632 	    && STRCMP(argvar->vval.v_string, "NONE") == 0)
633 	res = create_pty_only(term, opt);
634     else
635 	res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
636 
637     newbuf = curbuf;
638     if (res == OK)
639     {
640 	/* Get and remember the size we ended up with.  Update the pty. */
641 	vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
642 	term_report_winsize(term, term->tl_rows, term->tl_cols);
643 #ifdef FEAT_GUI
644 	if (term->tl_system)
645 	{
646 	    /* display first line below typed command */
647 	    term->tl_toprow = msg_row + 1;
648 	    term->tl_dirty_row_end = 0;
649 	}
650 #endif
651 
652 	/* Make sure we don't get stuck on sending keys to the job, it leads to
653 	 * a deadlock if the job is waiting for Vim to read. */
654 	channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
655 
656 	if (old_curbuf != NULL)
657 	{
658 	    --curbuf->b_nwindows;
659 	    curbuf = old_curbuf;
660 	    curwin->w_buffer = curbuf;
661 	    ++curbuf->b_nwindows;
662 	}
663     }
664     else
665     {
666 	term_close_buffer(curbuf, old_curbuf);
667 	return NULL;
668     }
669 
670     apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
671     return newbuf;
672 }
673 
674 /*
675  * ":terminal": open a terminal window and execute a job in it.
676  */
677     void
678 ex_terminal(exarg_T *eap)
679 {
680     typval_T	argvar[2];
681     jobopt_T	opt;
682     char_u	*cmd;
683     char_u	*tofree = NULL;
684 
685     init_job_options(&opt);
686 
687     cmd = eap->arg;
688     while (*cmd == '+' && *(cmd + 1) == '+')
689     {
690 	char_u  *p, *ep;
691 
692 	cmd += 2;
693 	p = skiptowhite(cmd);
694 	ep = vim_strchr(cmd, '=');
695 	if (ep != NULL && ep < p)
696 	    p = ep;
697 
698 	if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
699 	    opt.jo_term_finish = 'c';
700 	else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
701 	    opt.jo_term_finish = 'n';
702 	else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
703 	    opt.jo_term_finish = 'o';
704 	else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
705 	    opt.jo_curwin = 1;
706 	else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
707 	    opt.jo_hidden = 1;
708 	else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
709 	    opt.jo_term_norestore = 1;
710 	else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
711 		&& ep != NULL)
712 	{
713 	    opt.jo_set2 |= JO2_TERM_KILL;
714 	    opt.jo_term_kill = ep + 1;
715 	    p = skiptowhite(cmd);
716 	}
717 	else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
718 		&& ep != NULL && isdigit(ep[1]))
719 	{
720 	    opt.jo_set2 |= JO2_TERM_ROWS;
721 	    opt.jo_term_rows = atoi((char *)ep + 1);
722 	    p = skiptowhite(cmd);
723 	}
724 	else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
725 		&& ep != NULL && isdigit(ep[1]))
726 	{
727 	    opt.jo_set2 |= JO2_TERM_COLS;
728 	    opt.jo_term_cols = atoi((char *)ep + 1);
729 	    p = skiptowhite(cmd);
730 	}
731 	else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
732 								 && ep != NULL)
733 	{
734 	    char_u *buf = NULL;
735 	    char_u *keys;
736 
737 	    p = skiptowhite(cmd);
738 	    *p = NUL;
739 	    keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
740 	    opt.jo_set2 |= JO2_EOF_CHARS;
741 	    opt.jo_eof_chars = vim_strsave(keys);
742 	    vim_free(buf);
743 	    *p = ' ';
744 	}
745 	else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "winpty", 6) == 0)
746 	{
747 	    opt.jo_set2 |= JO2_TERM_MODE;
748 	    opt.jo_term_mode = 'w';
749 	}
750 	else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "conpty", 6) == 0)
751 	{
752 	    opt.jo_set2 |= JO2_TERM_MODE;
753 	    opt.jo_term_mode = 'c';
754 	}
755 	else
756 	{
757 	    if (*p)
758 		*p = NUL;
759 	    semsg(_("E181: Invalid attribute: %s"), cmd);
760 	    goto theend;
761 	}
762 	cmd = skipwhite(p);
763     }
764     if (*cmd == NUL)
765     {
766 	/* Make a copy of 'shell', an autocommand may change the option. */
767 	tofree = cmd = vim_strsave(p_sh);
768 
769 	/* default to close when the shell exits */
770 	if (opt.jo_term_finish == NUL)
771 	    opt.jo_term_finish = 'c';
772     }
773 
774     if (eap->addr_count > 0)
775     {
776 	/* Write lines from current buffer to the job. */
777 	opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
778 	opt.jo_io[PART_IN] = JIO_BUFFER;
779 	opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
780 	opt.jo_in_top = eap->line1;
781 	opt.jo_in_bot = eap->line2;
782     }
783 
784     argvar[0].v_type = VAR_STRING;
785     argvar[0].vval.v_string = cmd;
786     argvar[1].v_type = VAR_UNKNOWN;
787     term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
788     vim_free(tofree);
789 
790 theend:
791     vim_free(opt.jo_eof_chars);
792 }
793 
794 #if defined(FEAT_SESSION) || defined(PROTO)
795 /*
796  * Write a :terminal command to the session file to restore the terminal in
797  * window "wp".
798  * Return FAIL if writing fails.
799  */
800     int
801 term_write_session(FILE *fd, win_T *wp)
802 {
803     term_T *term = wp->w_buffer->b_term;
804 
805     /* Create the terminal and run the command.  This is not without
806      * risk, but let's assume the user only creates a session when this
807      * will be OK. */
808     if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
809 		term->tl_cols, term->tl_rows) < 0)
810 	return FAIL;
811 #ifdef WIN3264
812     if (*wp->w_p_tmod != NUL)
813 	if (fprintf(fd, "++%s ", wp->w_p_tmod) < 0)
814 	    return FAIL;
815 #endif
816     if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
817 	return FAIL;
818 
819     return put_eol(fd);
820 }
821 
822 /*
823  * Return TRUE if "buf" has a terminal that should be restored.
824  */
825     int
826 term_should_restore(buf_T *buf)
827 {
828     term_T	*term = buf->b_term;
829 
830     return term != NULL && (term->tl_command == NULL
831 				     || STRCMP(term->tl_command, "NONE") != 0);
832 }
833 #endif
834 
835 /*
836  * Free the scrollback buffer for "term".
837  */
838     static void
839 free_scrollback(term_T *term)
840 {
841     int i;
842 
843     for (i = 0; i < term->tl_scrollback.ga_len; ++i)
844 	vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
845     ga_clear(&term->tl_scrollback);
846 }
847 
848 
849 // Terminals that need to be freed soon.
850 term_T	*terminals_to_free = NULL;
851 
852 /*
853  * Free a terminal and everything it refers to.
854  * Kills the job if there is one.
855  * Called when wiping out a buffer.
856  * The actual terminal structure is freed later in free_unused_terminals(),
857  * because callbacks may wipe out a buffer while the terminal is still
858  * referenced.
859  */
860     void
861 free_terminal(buf_T *buf)
862 {
863     term_T	*term = buf->b_term;
864     term_T	*tp;
865 
866     if (term == NULL)
867 	return;
868 
869     // Unlink the terminal form the list of terminals.
870     if (first_term == term)
871 	first_term = term->tl_next;
872     else
873 	for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
874 	    if (tp->tl_next == term)
875 	    {
876 		tp->tl_next = term->tl_next;
877 		break;
878 	    }
879 
880     if (term->tl_job != NULL)
881     {
882 	if (term->tl_job->jv_status != JOB_ENDED
883 		&& term->tl_job->jv_status != JOB_FINISHED
884 		&& term->tl_job->jv_status != JOB_FAILED)
885 	    job_stop(term->tl_job, NULL, "kill");
886 	job_unref(term->tl_job);
887     }
888     term->tl_next = terminals_to_free;
889     terminals_to_free = term;
890 
891     buf->b_term = NULL;
892     if (in_terminal_loop == term)
893 	in_terminal_loop = NULL;
894 }
895 
896     void
897 free_unused_terminals()
898 {
899     while (terminals_to_free != NULL)
900     {
901 	term_T	    *term = terminals_to_free;
902 
903 	terminals_to_free = term->tl_next;
904 
905 	free_scrollback(term);
906 
907 	term_free_vterm(term);
908 	vim_free(term->tl_title);
909 #ifdef FEAT_SESSION
910 	vim_free(term->tl_command);
911 #endif
912 	vim_free(term->tl_kill);
913 	vim_free(term->tl_status_text);
914 	vim_free(term->tl_opencmd);
915 	vim_free(term->tl_eof_chars);
916 	vim_free(term->tl_arg0_cmd);
917 #ifdef WIN3264
918 	if (term->tl_out_fd != NULL)
919 	    fclose(term->tl_out_fd);
920 #endif
921 	vim_free(term->tl_cursor_color);
922 	vim_free(term);
923     }
924 }
925 
926 /*
927  * Get the part that is connected to the tty. Normally this is PART_IN, but
928  * when writing buffer lines to the job it can be another.  This makes it
929  * possible to do "1,5term vim -".
930  */
931     static ch_part_T
932 get_tty_part(term_T *term)
933 {
934 #ifdef UNIX
935     ch_part_T	parts[3] = {PART_IN, PART_OUT, PART_ERR};
936     int		i;
937 
938     for (i = 0; i < 3; ++i)
939     {
940 	int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
941 
942 	if (mch_isatty(fd))
943 	    return parts[i];
944     }
945 #endif
946     return PART_IN;
947 }
948 
949 /*
950  * Write job output "msg[len]" to the vterm.
951  */
952     static void
953 term_write_job_output(term_T *term, char_u *msg, size_t len)
954 {
955     VTerm	*vterm = term->tl_vterm;
956     size_t	prevlen = vterm_output_get_buffer_current(vterm);
957 
958     vterm_input_write(vterm, (char *)msg, len);
959 
960     /* flush vterm buffer when vterm responded to control sequence */
961     if (prevlen != vterm_output_get_buffer_current(vterm))
962     {
963 	char   buf[KEY_BUF_LEN];
964 	size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
965 
966 	if (curlen > 0)
967 	    channel_send(term->tl_job->jv_channel, get_tty_part(term),
968 					     (char_u *)buf, (int)curlen, NULL);
969     }
970 
971     /* this invokes the damage callbacks */
972     vterm_screen_flush_damage(vterm_obtain_screen(vterm));
973 }
974 
975     static void
976 update_cursor(term_T *term, int redraw)
977 {
978     if (term->tl_normal_mode)
979 	return;
980 #ifdef FEAT_GUI
981     if (term->tl_system)
982 	windgoto(term->tl_cursor_pos.row + term->tl_toprow,
983 						      term->tl_cursor_pos.col);
984     else
985 #endif
986 	setcursor();
987     if (redraw)
988     {
989 	if (term->tl_buffer == curbuf && term->tl_cursor_visible)
990 	    cursor_on();
991 	out_flush();
992 #ifdef FEAT_GUI
993 	if (gui.in_use)
994 	{
995 	    gui_update_cursor(FALSE, FALSE);
996 	    gui_mch_flush();
997 	}
998 #endif
999     }
1000 }
1001 
1002 /*
1003  * Invoked when "msg" output from a job was received.  Write it to the terminal
1004  * of "buffer".
1005  */
1006     void
1007 write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
1008 {
1009     size_t	len = STRLEN(msg);
1010     term_T	*term = buffer->b_term;
1011 
1012 #ifdef WIN3264
1013     /* Win32: Cannot redirect output of the job, intercept it here and write to
1014      * the file. */
1015     if (term->tl_out_fd != NULL)
1016     {
1017 	ch_log(channel, "Writing %d bytes to output file", (int)len);
1018 	fwrite(msg, len, 1, term->tl_out_fd);
1019 	return;
1020     }
1021 #endif
1022 
1023     if (term->tl_vterm == NULL)
1024     {
1025 	ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
1026 	return;
1027     }
1028     ch_log(channel, "writing %d bytes to terminal", (int)len);
1029     term_write_job_output(term, msg, len);
1030 
1031 #ifdef FEAT_GUI
1032     if (term->tl_system)
1033     {
1034 	/* show system output, scrolling up the screen as needed */
1035 	update_system_term(term);
1036 	update_cursor(term, TRUE);
1037     }
1038     else
1039 #endif
1040     /* In Terminal-Normal mode we are displaying the buffer, not the terminal
1041      * contents, thus no screen update is needed. */
1042     if (!term->tl_normal_mode)
1043     {
1044 	// Don't use update_screen() when editing the command line, it gets
1045 	// cleared.
1046 	// TODO: only update once in a while.
1047 	ch_log(term->tl_job->jv_channel, "updating screen");
1048 	if (buffer == curbuf && (State & CMDLINE) == 0)
1049 	{
1050 	    update_screen(VALID_NO_UPDATE);
1051 	    /* update_screen() can be slow, check the terminal wasn't closed
1052 	     * already */
1053 	    if (buffer == curbuf && curbuf->b_term != NULL)
1054 		update_cursor(curbuf->b_term, TRUE);
1055 	}
1056 	else
1057 	    redraw_after_callback(TRUE);
1058     }
1059 }
1060 
1061 /*
1062  * Send a mouse position and click to the vterm
1063  */
1064     static int
1065 term_send_mouse(VTerm *vterm, int button, int pressed)
1066 {
1067     VTermModifier   mod = VTERM_MOD_NONE;
1068 
1069     vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
1070 					    mouse_col - curwin->w_wincol, mod);
1071     if (button != 0)
1072 	vterm_mouse_button(vterm, button, pressed, mod);
1073     return TRUE;
1074 }
1075 
1076 static int enter_mouse_col = -1;
1077 static int enter_mouse_row = -1;
1078 
1079 /*
1080  * Handle a mouse click, drag or release.
1081  * Return TRUE when a mouse event is sent to the terminal.
1082  */
1083     static int
1084 term_mouse_click(VTerm *vterm, int key)
1085 {
1086 #if defined(FEAT_CLIPBOARD)
1087     /* For modeless selection mouse drag and release events are ignored, unless
1088      * they are preceded with a mouse down event */
1089     static int	    ignore_drag_release = TRUE;
1090     VTermMouseState mouse_state;
1091 
1092     vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1093     if (mouse_state.flags == 0)
1094     {
1095 	/* Terminal is not using the mouse, use modeless selection. */
1096 	switch (key)
1097 	{
1098 	case K_LEFTDRAG:
1099 	case K_LEFTRELEASE:
1100 	case K_RIGHTDRAG:
1101 	case K_RIGHTRELEASE:
1102 		/* Ignore drag and release events when the button-down wasn't
1103 		 * seen before. */
1104 		if (ignore_drag_release)
1105 		{
1106 		    int save_mouse_col, save_mouse_row;
1107 
1108 		    if (enter_mouse_col < 0)
1109 			break;
1110 
1111 		    /* mouse click in the window gave us focus, handle that
1112 		     * click now */
1113 		    save_mouse_col = mouse_col;
1114 		    save_mouse_row = mouse_row;
1115 		    mouse_col = enter_mouse_col;
1116 		    mouse_row = enter_mouse_row;
1117 		    clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1118 		    mouse_col = save_mouse_col;
1119 		    mouse_row = save_mouse_row;
1120 		}
1121 		/* FALLTHROUGH */
1122 	case K_LEFTMOUSE:
1123 	case K_RIGHTMOUSE:
1124 		if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1125 		    ignore_drag_release = TRUE;
1126 		else
1127 		    ignore_drag_release = FALSE;
1128 		/* Should we call mouse_has() here? */
1129 		if (clip_star.available)
1130 		{
1131 		    int	    button, is_click, is_drag;
1132 
1133 		    button = get_mouse_button(KEY2TERMCAP1(key),
1134 							 &is_click, &is_drag);
1135 		    if (mouse_model_popup() && button == MOUSE_LEFT
1136 					       && (mod_mask & MOD_MASK_SHIFT))
1137 		    {
1138 			/* Translate shift-left to right button. */
1139 			button = MOUSE_RIGHT;
1140 			mod_mask &= ~MOD_MASK_SHIFT;
1141 		    }
1142 		    clip_modeless(button, is_click, is_drag);
1143 		}
1144 		break;
1145 
1146 	case K_MIDDLEMOUSE:
1147 		if (clip_star.available)
1148 		    insert_reg('*', TRUE);
1149 		break;
1150 	}
1151 	enter_mouse_col = -1;
1152 	return FALSE;
1153     }
1154 #endif
1155     enter_mouse_col = -1;
1156 
1157     switch (key)
1158     {
1159 	case K_LEFTMOUSE:
1160 	case K_LEFTMOUSE_NM:	term_send_mouse(vterm, 1, 1); break;
1161 	case K_LEFTDRAG:	term_send_mouse(vterm, 1, 1); break;
1162 	case K_LEFTRELEASE:
1163 	case K_LEFTRELEASE_NM:	term_send_mouse(vterm, 1, 0); break;
1164 	case K_MOUSEMOVE:	term_send_mouse(vterm, 0, 0); break;
1165 	case K_MIDDLEMOUSE:	term_send_mouse(vterm, 2, 1); break;
1166 	case K_MIDDLEDRAG:	term_send_mouse(vterm, 2, 1); break;
1167 	case K_MIDDLERELEASE:	term_send_mouse(vterm, 2, 0); break;
1168 	case K_RIGHTMOUSE:	term_send_mouse(vterm, 3, 1); break;
1169 	case K_RIGHTDRAG:	term_send_mouse(vterm, 3, 1); break;
1170 	case K_RIGHTRELEASE:	term_send_mouse(vterm, 3, 0); break;
1171     }
1172     return TRUE;
1173 }
1174 
1175 /*
1176  * Convert typed key "c" into bytes to send to the job.
1177  * Return the number of bytes in "buf".
1178  */
1179     static int
1180 term_convert_key(term_T *term, int c, char *buf)
1181 {
1182     VTerm	    *vterm = term->tl_vterm;
1183     VTermKey	    key = VTERM_KEY_NONE;
1184     VTermModifier   mod = VTERM_MOD_NONE;
1185     int		    other = FALSE;
1186 
1187     switch (c)
1188     {
1189 	/* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1190 
1191 				/* don't use VTERM_KEY_BACKSPACE, it always
1192 				 * becomes 0x7f DEL */
1193 	case K_BS:		c = term_backspace_char; break;
1194 
1195 	case ESC:		key = VTERM_KEY_ESCAPE; break;
1196 	case K_DEL:		key = VTERM_KEY_DEL; break;
1197 	case K_DOWN:		key = VTERM_KEY_DOWN; break;
1198 	case K_S_DOWN:		mod = VTERM_MOD_SHIFT;
1199 				key = VTERM_KEY_DOWN; break;
1200 	case K_END:		key = VTERM_KEY_END; break;
1201 	case K_S_END:		mod = VTERM_MOD_SHIFT;
1202 				key = VTERM_KEY_END; break;
1203 	case K_C_END:		mod = VTERM_MOD_CTRL;
1204 				key = VTERM_KEY_END; break;
1205 	case K_F10:		key = VTERM_KEY_FUNCTION(10); break;
1206 	case K_F11:		key = VTERM_KEY_FUNCTION(11); break;
1207 	case K_F12:		key = VTERM_KEY_FUNCTION(12); break;
1208 	case K_F1:		key = VTERM_KEY_FUNCTION(1); break;
1209 	case K_F2:		key = VTERM_KEY_FUNCTION(2); break;
1210 	case K_F3:		key = VTERM_KEY_FUNCTION(3); break;
1211 	case K_F4:		key = VTERM_KEY_FUNCTION(4); break;
1212 	case K_F5:		key = VTERM_KEY_FUNCTION(5); break;
1213 	case K_F6:		key = VTERM_KEY_FUNCTION(6); break;
1214 	case K_F7:		key = VTERM_KEY_FUNCTION(7); break;
1215 	case K_F8:		key = VTERM_KEY_FUNCTION(8); break;
1216 	case K_F9:		key = VTERM_KEY_FUNCTION(9); break;
1217 	case K_HOME:		key = VTERM_KEY_HOME; break;
1218 	case K_S_HOME:		mod = VTERM_MOD_SHIFT;
1219 				key = VTERM_KEY_HOME; break;
1220 	case K_C_HOME:		mod = VTERM_MOD_CTRL;
1221 				key = VTERM_KEY_HOME; break;
1222 	case K_INS:		key = VTERM_KEY_INS; break;
1223 	case K_K0:		key = VTERM_KEY_KP_0; break;
1224 	case K_K1:		key = VTERM_KEY_KP_1; break;
1225 	case K_K2:		key = VTERM_KEY_KP_2; break;
1226 	case K_K3:		key = VTERM_KEY_KP_3; break;
1227 	case K_K4:		key = VTERM_KEY_KP_4; break;
1228 	case K_K5:		key = VTERM_KEY_KP_5; break;
1229 	case K_K6:		key = VTERM_KEY_KP_6; break;
1230 	case K_K7:		key = VTERM_KEY_KP_7; break;
1231 	case K_K8:		key = VTERM_KEY_KP_8; break;
1232 	case K_K9:		key = VTERM_KEY_KP_9; break;
1233 	case K_KDEL:		key = VTERM_KEY_DEL; break; /* TODO */
1234 	case K_KDIVIDE:		key = VTERM_KEY_KP_DIVIDE; break;
1235 	case K_KEND:		key = VTERM_KEY_KP_1; break; /* TODO */
1236 	case K_KENTER:		key = VTERM_KEY_KP_ENTER; break;
1237 	case K_KHOME:		key = VTERM_KEY_KP_7; break; /* TODO */
1238 	case K_KINS:		key = VTERM_KEY_KP_0; break; /* TODO */
1239 	case K_KMINUS:		key = VTERM_KEY_KP_MINUS; break;
1240 	case K_KMULTIPLY:	key = VTERM_KEY_KP_MULT; break;
1241 	case K_KPAGEDOWN:	key = VTERM_KEY_KP_3; break; /* TODO */
1242 	case K_KPAGEUP:		key = VTERM_KEY_KP_9; break; /* TODO */
1243 	case K_KPLUS:		key = VTERM_KEY_KP_PLUS; break;
1244 	case K_KPOINT:		key = VTERM_KEY_KP_PERIOD; break;
1245 	case K_LEFT:		key = VTERM_KEY_LEFT; break;
1246 	case K_S_LEFT:		mod = VTERM_MOD_SHIFT;
1247 				key = VTERM_KEY_LEFT; break;
1248 	case K_C_LEFT:		mod = VTERM_MOD_CTRL;
1249 				key = VTERM_KEY_LEFT; break;
1250 	case K_PAGEDOWN:	key = VTERM_KEY_PAGEDOWN; break;
1251 	case K_PAGEUP:		key = VTERM_KEY_PAGEUP; break;
1252 	case K_RIGHT:		key = VTERM_KEY_RIGHT; break;
1253 	case K_S_RIGHT:		mod = VTERM_MOD_SHIFT;
1254 				key = VTERM_KEY_RIGHT; break;
1255 	case K_C_RIGHT:		mod = VTERM_MOD_CTRL;
1256 				key = VTERM_KEY_RIGHT; break;
1257 	case K_UP:		key = VTERM_KEY_UP; break;
1258 	case K_S_UP:		mod = VTERM_MOD_SHIFT;
1259 				key = VTERM_KEY_UP; break;
1260 	case TAB:		key = VTERM_KEY_TAB; break;
1261 	case K_S_TAB:		mod = VTERM_MOD_SHIFT;
1262 				key = VTERM_KEY_TAB; break;
1263 
1264 	case K_MOUSEUP:		other = term_send_mouse(vterm, 5, 1); break;
1265 	case K_MOUSEDOWN:	other = term_send_mouse(vterm, 4, 1); break;
1266 	case K_MOUSELEFT:	/* TODO */ return 0;
1267 	case K_MOUSERIGHT:	/* TODO */ return 0;
1268 
1269 	case K_LEFTMOUSE:
1270 	case K_LEFTMOUSE_NM:
1271 	case K_LEFTDRAG:
1272 	case K_LEFTRELEASE:
1273 	case K_LEFTRELEASE_NM:
1274 	case K_MOUSEMOVE:
1275 	case K_MIDDLEMOUSE:
1276 	case K_MIDDLEDRAG:
1277 	case K_MIDDLERELEASE:
1278 	case K_RIGHTMOUSE:
1279 	case K_RIGHTDRAG:
1280 	case K_RIGHTRELEASE:	if (!term_mouse_click(vterm, c))
1281 				    return 0;
1282 				other = TRUE;
1283 				break;
1284 
1285 	case K_X1MOUSE:		/* TODO */ return 0;
1286 	case K_X1DRAG:		/* TODO */ return 0;
1287 	case K_X1RELEASE:	/* TODO */ return 0;
1288 	case K_X2MOUSE:		/* TODO */ return 0;
1289 	case K_X2DRAG:		/* TODO */ return 0;
1290 	case K_X2RELEASE:	/* TODO */ return 0;
1291 
1292 	case K_IGNORE:		return 0;
1293 	case K_NOP:		return 0;
1294 	case K_UNDO:		return 0;
1295 	case K_HELP:		return 0;
1296 	case K_XF1:		key = VTERM_KEY_FUNCTION(1); break;
1297 	case K_XF2:		key = VTERM_KEY_FUNCTION(2); break;
1298 	case K_XF3:		key = VTERM_KEY_FUNCTION(3); break;
1299 	case K_XF4:		key = VTERM_KEY_FUNCTION(4); break;
1300 	case K_SELECT:		return 0;
1301 #ifdef FEAT_GUI
1302 	case K_VER_SCROLLBAR:	return 0;
1303 	case K_HOR_SCROLLBAR:	return 0;
1304 #endif
1305 #ifdef FEAT_GUI_TABLINE
1306 	case K_TABLINE:		return 0;
1307 	case K_TABMENU:		return 0;
1308 #endif
1309 #ifdef FEAT_NETBEANS_INTG
1310 	case K_F21:		key = VTERM_KEY_FUNCTION(21); break;
1311 #endif
1312 #ifdef FEAT_DND
1313 	case K_DROP:		return 0;
1314 #endif
1315 	case K_CURSORHOLD:	return 0;
1316 	case K_PS:		vterm_keyboard_start_paste(vterm);
1317 				other = TRUE;
1318 				break;
1319 	case K_PE:		vterm_keyboard_end_paste(vterm);
1320 				other = TRUE;
1321 				break;
1322     }
1323 
1324     /*
1325      * Convert special keys to vterm keys:
1326      * - Write keys to vterm: vterm_keyboard_key()
1327      * - Write output to channel.
1328      * TODO: use mod_mask
1329      */
1330     if (key != VTERM_KEY_NONE)
1331 	/* Special key, let vterm convert it. */
1332 	vterm_keyboard_key(vterm, key, mod);
1333     else if (!other)
1334 	/* Normal character, let vterm convert it. */
1335 	vterm_keyboard_unichar(vterm, c, mod);
1336 
1337     /* Read back the converted escape sequence. */
1338     return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1339 }
1340 
1341 /*
1342  * Return TRUE if the job for "term" is still running.
1343  * If "check_job_status" is TRUE update the job status.
1344  * NOTE: "term" may be freed by callbacks.
1345  */
1346     static int
1347 term_job_running_check(term_T *term, int check_job_status)
1348 {
1349     /* Also consider the job finished when the channel is closed, to avoid a
1350      * race condition when updating the title. */
1351     if (term != NULL
1352 	&& term->tl_job != NULL
1353 	&& channel_is_open(term->tl_job->jv_channel))
1354     {
1355 	job_T *job = term->tl_job;
1356 
1357 	// Careful: Checking the job status may invoked callbacks, which close
1358 	// the buffer and terminate "term".  However, "job" will not be freed
1359 	// yet.
1360 	if (check_job_status)
1361 	    job_status(job);
1362 	return (job->jv_status == JOB_STARTED
1363 		|| (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
1364     }
1365     return FALSE;
1366 }
1367 
1368 /*
1369  * Return TRUE if the job for "term" is still running.
1370  */
1371     int
1372 term_job_running(term_T *term)
1373 {
1374     return term_job_running_check(term, FALSE);
1375 }
1376 
1377 /*
1378  * Return TRUE if "term" has an active channel and used ":term NONE".
1379  */
1380     int
1381 term_none_open(term_T *term)
1382 {
1383     /* Also consider the job finished when the channel is closed, to avoid a
1384      * race condition when updating the title. */
1385     return term != NULL
1386 	&& term->tl_job != NULL
1387 	&& channel_is_open(term->tl_job->jv_channel)
1388 	&& term->tl_job->jv_channel->ch_keep_open;
1389 }
1390 
1391 /*
1392  * Used when exiting: kill the job in "buf" if so desired.
1393  * Return OK when the job finished.
1394  * Return FAIL when the job is still running.
1395  */
1396     int
1397 term_try_stop_job(buf_T *buf)
1398 {
1399     int	    count;
1400     char    *how = (char *)buf->b_term->tl_kill;
1401 
1402 #if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1403     if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1404     {
1405 	char_u	buff[DIALOG_MSG_SIZE];
1406 	int	ret;
1407 
1408 	dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1409 	ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1410 	if (ret == VIM_YES)
1411 	    how = "kill";
1412 	else if (ret == VIM_CANCEL)
1413 	    return FAIL;
1414     }
1415 #endif
1416     if (how == NULL || *how == NUL)
1417 	return FAIL;
1418 
1419     job_stop(buf->b_term->tl_job, NULL, how);
1420 
1421     // wait for up to a second for the job to die
1422     for (count = 0; count < 100; ++count)
1423     {
1424 	job_T *job;
1425 
1426 	// buffer, terminal and job may be cleaned up while waiting
1427 	if (!buf_valid(buf)
1428 		|| buf->b_term == NULL
1429 		|| buf->b_term->tl_job == NULL)
1430 	    return OK;
1431 	job = buf->b_term->tl_job;
1432 
1433 	// Call job_status() to update jv_status. It may cause the job to be
1434 	// cleaned up but it won't be freed.
1435 	job_status(job);
1436 	if (job->jv_status >= JOB_ENDED)
1437 	    return OK;
1438 
1439 	ui_delay(10L, FALSE);
1440 	mch_check_messages();
1441 	parse_queued_messages();
1442     }
1443     return FAIL;
1444 }
1445 
1446 /*
1447  * Add the last line of the scrollback buffer to the buffer in the window.
1448  */
1449     static void
1450 add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1451 {
1452     buf_T	*buf = term->tl_buffer;
1453     int		empty = (buf->b_ml.ml_flags & ML_EMPTY);
1454     linenr_T	lnum = buf->b_ml.ml_line_count;
1455 
1456 #ifdef WIN3264
1457     if (!enc_utf8 && enc_codepage > 0)
1458     {
1459 	WCHAR   *ret = NULL;
1460 	int	length = 0;
1461 
1462 	MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1463 							   &ret, &length);
1464 	if (ret != NULL)
1465 	{
1466 	    WideCharToMultiByte_alloc(enc_codepage, 0,
1467 				      ret, length, (char **)&text, &len, 0, 0);
1468 	    vim_free(ret);
1469 	    ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1470 	    vim_free(text);
1471 	}
1472     }
1473     else
1474 #endif
1475 	ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1476     if (empty)
1477     {
1478 	/* Delete the empty line that was in the empty buffer. */
1479 	curbuf = buf;
1480 	ml_delete(1, FALSE);
1481 	curbuf = curwin->w_buffer;
1482     }
1483 }
1484 
1485     static void
1486 cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1487 {
1488     attr->width = cell->width;
1489     attr->attrs = cell->attrs;
1490     attr->fg = cell->fg;
1491     attr->bg = cell->bg;
1492 }
1493 
1494     static int
1495 equal_celattr(cellattr_T *a, cellattr_T *b)
1496 {
1497     /* Comparing the colors should be sufficient. */
1498     return a->fg.red == b->fg.red
1499 	&& a->fg.green == b->fg.green
1500 	&& a->fg.blue == b->fg.blue
1501 	&& a->bg.red == b->bg.red
1502 	&& a->bg.green == b->bg.green
1503 	&& a->bg.blue == b->bg.blue;
1504 }
1505 
1506 /*
1507  * Add an empty scrollback line to "term".  When "lnum" is not zero, add the
1508  * line at this position.  Otherwise at the end.
1509  */
1510     static int
1511 add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1512 {
1513     if (ga_grow(&term->tl_scrollback, 1) == OK)
1514     {
1515 	sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1516 				      + term->tl_scrollback.ga_len;
1517 
1518 	if (lnum > 0)
1519 	{
1520 	    int i;
1521 
1522 	    for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1523 	    {
1524 		*line = *(line - 1);
1525 		--line;
1526 	    }
1527 	}
1528 	line->sb_cols = 0;
1529 	line->sb_cells = NULL;
1530 	line->sb_fill_attr = *fill_attr;
1531 	++term->tl_scrollback.ga_len;
1532 	return OK;
1533     }
1534     return FALSE;
1535 }
1536 
1537 /*
1538  * Remove the terminal contents from the scrollback and the buffer.
1539  * Used before adding a new scrollback line or updating the buffer for lines
1540  * displayed in the terminal.
1541  */
1542     static void
1543 cleanup_scrollback(term_T *term)
1544 {
1545     sb_line_T	*line;
1546     garray_T	*gap;
1547 
1548     curbuf = term->tl_buffer;
1549     gap = &term->tl_scrollback;
1550     while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1551 							    && gap->ga_len > 0)
1552     {
1553 	ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1554 	line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1555 	vim_free(line->sb_cells);
1556 	--gap->ga_len;
1557     }
1558     curbuf = curwin->w_buffer;
1559     if (curbuf == term->tl_buffer)
1560 	check_cursor();
1561 }
1562 
1563 /*
1564  * Add the current lines of the terminal to scrollback and to the buffer.
1565  */
1566     static void
1567 update_snapshot(term_T *term)
1568 {
1569     VTermScreen	    *screen;
1570     int		    len;
1571     int		    lines_skipped = 0;
1572     VTermPos	    pos;
1573     VTermScreenCell cell;
1574     cellattr_T	    fill_attr, new_fill_attr;
1575     cellattr_T	    *p;
1576 
1577     ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1578 				  "Adding terminal window snapshot to buffer");
1579 
1580     /* First remove the lines that were appended before, they might be
1581      * outdated. */
1582     cleanup_scrollback(term);
1583 
1584     screen = vterm_obtain_screen(term->tl_vterm);
1585     fill_attr = new_fill_attr = term->tl_default_color;
1586     for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1587     {
1588 	len = 0;
1589 	for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1590 	    if (vterm_screen_get_cell(screen, pos, &cell) != 0
1591 						       && cell.chars[0] != NUL)
1592 	    {
1593 		len = pos.col + 1;
1594 		new_fill_attr = term->tl_default_color;
1595 	    }
1596 	    else
1597 		/* Assume the last attr is the filler attr. */
1598 		cell2cellattr(&cell, &new_fill_attr);
1599 
1600 	if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1601 	    ++lines_skipped;
1602 	else
1603 	{
1604 	    while (lines_skipped > 0)
1605 	    {
1606 		/* Line was skipped, add an empty line. */
1607 		--lines_skipped;
1608 		if (add_empty_scrollback(term, &fill_attr, 0) == OK)
1609 		    add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1610 	    }
1611 
1612 	    if (len == 0)
1613 		p = NULL;
1614 	    else
1615 		p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1616 	    if ((p != NULL || len == 0)
1617 				     && ga_grow(&term->tl_scrollback, 1) == OK)
1618 	    {
1619 		garray_T    ga;
1620 		int	    width;
1621 		sb_line_T   *line = (sb_line_T *)term->tl_scrollback.ga_data
1622 						  + term->tl_scrollback.ga_len;
1623 
1624 		ga_init2(&ga, 1, 100);
1625 		for (pos.col = 0; pos.col < len; pos.col += width)
1626 		{
1627 		    if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1628 		    {
1629 			width = 1;
1630 			vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1631 			if (ga_grow(&ga, 1) == OK)
1632 			    ga.ga_len += utf_char2bytes(' ',
1633 					     (char_u *)ga.ga_data + ga.ga_len);
1634 		    }
1635 		    else
1636 		    {
1637 			width = cell.width;
1638 
1639 			cell2cellattr(&cell, &p[pos.col]);
1640 
1641 			// Each character can be up to 6 bytes.
1642 			if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK)
1643 			{
1644 			    int	    i;
1645 			    int	    c;
1646 
1647 			    for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1648 				ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1649 					     (char_u *)ga.ga_data + ga.ga_len);
1650 			}
1651 		    }
1652 		}
1653 		line->sb_cols = len;
1654 		line->sb_cells = p;
1655 		line->sb_fill_attr = new_fill_attr;
1656 		fill_attr = new_fill_attr;
1657 		++term->tl_scrollback.ga_len;
1658 
1659 		if (ga_grow(&ga, 1) == FAIL)
1660 		    add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1661 		else
1662 		{
1663 		    *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1664 		    add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1665 		}
1666 		ga_clear(&ga);
1667 	    }
1668 	    else
1669 		vim_free(p);
1670 	}
1671     }
1672 
1673     // Add trailing empty lines.
1674     for (pos.row = term->tl_scrollback.ga_len;
1675 	    pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row;
1676 	    ++pos.row)
1677     {
1678 	if (add_empty_scrollback(term, &fill_attr, 0) == OK)
1679 	    add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1680     }
1681 
1682     term->tl_dirty_snapshot = FALSE;
1683 #ifdef FEAT_TIMERS
1684     term->tl_timer_set = FALSE;
1685 #endif
1686 }
1687 
1688 /*
1689  * If needed, add the current lines of the terminal to scrollback and to the
1690  * buffer.  Called after the job has ended and when switching to
1691  * Terminal-Normal mode.
1692  * When "redraw" is TRUE redraw the windows that show the terminal.
1693  */
1694     static void
1695 may_move_terminal_to_buffer(term_T *term, int redraw)
1696 {
1697     win_T	    *wp;
1698 
1699     if (term->tl_vterm == NULL)
1700 	return;
1701 
1702     /* Update the snapshot only if something changes or the buffer does not
1703      * have all the lines. */
1704     if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
1705 					       <= term->tl_scrollback_scrolled)
1706 	update_snapshot(term);
1707 
1708     /* Obtain the current background color. */
1709     vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1710 		       &term->tl_default_color.fg, &term->tl_default_color.bg);
1711 
1712     if (redraw)
1713 	FOR_ALL_WINDOWS(wp)
1714 	{
1715 	    if (wp->w_buffer == term->tl_buffer)
1716 	    {
1717 		wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1718 		wp->w_cursor.col = 0;
1719 		wp->w_valid = 0;
1720 		if (wp->w_cursor.lnum >= wp->w_height)
1721 		{
1722 		    linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1723 
1724 		    if (wp->w_topline < min_topline)
1725 			wp->w_topline = min_topline;
1726 		}
1727 		redraw_win_later(wp, NOT_VALID);
1728 	    }
1729 	}
1730 }
1731 
1732 #if defined(FEAT_TIMERS) || defined(PROTO)
1733 /*
1734  * Check if any terminal timer expired.  If so, copy text from the terminal to
1735  * the buffer.
1736  * Return the time until the next timer will expire.
1737  */
1738     int
1739 term_check_timers(int next_due_arg, proftime_T *now)
1740 {
1741     term_T  *term;
1742     int	    next_due = next_due_arg;
1743 
1744     for (term = first_term; term != NULL; term = term->tl_next)
1745     {
1746 	if (term->tl_timer_set && !term->tl_normal_mode)
1747 	{
1748 	    long    this_due = proftime_time_left(&term->tl_timer_due, now);
1749 
1750 	    if (this_due <= 1)
1751 	    {
1752 		term->tl_timer_set = FALSE;
1753 		may_move_terminal_to_buffer(term, FALSE);
1754 	    }
1755 	    else if (next_due == -1 || next_due > this_due)
1756 		next_due = this_due;
1757 	}
1758     }
1759 
1760     return next_due;
1761 }
1762 #endif
1763 
1764     static void
1765 set_terminal_mode(term_T *term, int normal_mode)
1766 {
1767     term->tl_normal_mode = normal_mode;
1768     VIM_CLEAR(term->tl_status_text);
1769     if (term->tl_buffer == curbuf)
1770 	maketitle();
1771 }
1772 
1773 /*
1774  * Called after the job if finished and Terminal mode is not active:
1775  * Move the vterm contents into the scrollback buffer and free the vterm.
1776  */
1777     static void
1778 cleanup_vterm(term_T *term)
1779 {
1780     if (term->tl_finish != TL_FINISH_CLOSE)
1781 	may_move_terminal_to_buffer(term, TRUE);
1782     term_free_vterm(term);
1783     set_terminal_mode(term, FALSE);
1784 }
1785 
1786 /*
1787  * Switch from Terminal-Job mode to Terminal-Normal mode.
1788  * Suspends updating the terminal window.
1789  */
1790     static void
1791 term_enter_normal_mode(void)
1792 {
1793     term_T *term = curbuf->b_term;
1794 
1795     set_terminal_mode(term, TRUE);
1796 
1797     /* Append the current terminal contents to the buffer. */
1798     may_move_terminal_to_buffer(term, TRUE);
1799 
1800     /* Move the window cursor to the position of the cursor in the
1801      * terminal. */
1802     curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1803 					     + term->tl_cursor_pos.row + 1;
1804     check_cursor();
1805     if (coladvance(term->tl_cursor_pos.col) == FAIL)
1806 	coladvance(MAXCOL);
1807 
1808     /* Display the same lines as in the terminal. */
1809     curwin->w_topline = term->tl_scrollback_scrolled + 1;
1810 }
1811 
1812 /*
1813  * Returns TRUE if the current window contains a terminal and we are in
1814  * Terminal-Normal mode.
1815  */
1816     int
1817 term_in_normal_mode(void)
1818 {
1819     term_T *term = curbuf->b_term;
1820 
1821     return term != NULL && term->tl_normal_mode;
1822 }
1823 
1824 /*
1825  * Switch from Terminal-Normal mode to Terminal-Job mode.
1826  * Restores updating the terminal window.
1827  */
1828     void
1829 term_enter_job_mode()
1830 {
1831     term_T	*term = curbuf->b_term;
1832 
1833     set_terminal_mode(term, FALSE);
1834 
1835     if (term->tl_channel_closed)
1836 	cleanup_vterm(term);
1837     redraw_buf_and_status_later(curbuf, NOT_VALID);
1838 }
1839 
1840 /*
1841  * Get a key from the user with terminal mode mappings.
1842  * Note: while waiting a terminal may be closed and freed if the channel is
1843  * closed and ++close was used.
1844  */
1845     static int
1846 term_vgetc()
1847 {
1848     int c;
1849     int save_State = State;
1850 
1851     State = TERMINAL;
1852     got_int = FALSE;
1853 #ifdef WIN3264
1854     ctrl_break_was_pressed = FALSE;
1855 #endif
1856     c = vgetc();
1857     got_int = FALSE;
1858     State = save_State;
1859     return c;
1860 }
1861 
1862 static int	mouse_was_outside = FALSE;
1863 
1864 /*
1865  * Send keys to terminal.
1866  * Return FAIL when the key needs to be handled in Normal mode.
1867  * Return OK when the key was dropped or sent to the terminal.
1868  */
1869     int
1870 send_keys_to_term(term_T *term, int c, int typed)
1871 {
1872     char	msg[KEY_BUF_LEN];
1873     size_t	len;
1874     int		dragging_outside = FALSE;
1875 
1876     /* Catch keys that need to be handled as in Normal mode. */
1877     switch (c)
1878     {
1879 	case NUL:
1880 	case K_ZERO:
1881 	    if (typed)
1882 		stuffcharReadbuff(c);
1883 	    return FAIL;
1884 
1885 	case K_TABLINE:
1886 	    stuffcharReadbuff(c);
1887 	    return FAIL;
1888 
1889 	case K_IGNORE:
1890 	case K_CANCEL:  // used for :normal when running out of chars
1891 	    return FAIL;
1892 
1893 	case K_LEFTDRAG:
1894 	case K_MIDDLEDRAG:
1895 	case K_RIGHTDRAG:
1896 	case K_X1DRAG:
1897 	case K_X2DRAG:
1898 	    dragging_outside = mouse_was_outside;
1899 	    /* FALLTHROUGH */
1900 	case K_LEFTMOUSE:
1901 	case K_LEFTMOUSE_NM:
1902 	case K_LEFTRELEASE:
1903 	case K_LEFTRELEASE_NM:
1904 	case K_MOUSEMOVE:
1905 	case K_MIDDLEMOUSE:
1906 	case K_MIDDLERELEASE:
1907 	case K_RIGHTMOUSE:
1908 	case K_RIGHTRELEASE:
1909 	case K_X1MOUSE:
1910 	case K_X1RELEASE:
1911 	case K_X2MOUSE:
1912 	case K_X2RELEASE:
1913 
1914 	case K_MOUSEUP:
1915 	case K_MOUSEDOWN:
1916 	case K_MOUSELEFT:
1917 	case K_MOUSERIGHT:
1918 	    if (mouse_row < W_WINROW(curwin)
1919 		    || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
1920 		    || mouse_col < curwin->w_wincol
1921 		    || mouse_col >= W_ENDCOL(curwin)
1922 		    || dragging_outside)
1923 	    {
1924 		/* click or scroll outside the current window or on status line
1925 		 * or vertical separator */
1926 		if (typed)
1927 		{
1928 		    stuffcharReadbuff(c);
1929 		    mouse_was_outside = TRUE;
1930 		}
1931 		return FAIL;
1932 	    }
1933     }
1934     if (typed)
1935 	mouse_was_outside = FALSE;
1936 
1937     /* Convert the typed key to a sequence of bytes for the job. */
1938     len = term_convert_key(term, c, msg);
1939     if (len > 0)
1940 	/* TODO: if FAIL is returned, stop? */
1941 	channel_send(term->tl_job->jv_channel, get_tty_part(term),
1942 						(char_u *)msg, (int)len, NULL);
1943 
1944     return OK;
1945 }
1946 
1947     static void
1948 position_cursor(win_T *wp, VTermPos *pos)
1949 {
1950     wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1951     wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1952     wp->w_valid |= (VALID_WCOL|VALID_WROW);
1953 }
1954 
1955 /*
1956  * Handle CTRL-W "": send register contents to the job.
1957  */
1958     static void
1959 term_paste_register(int prev_c UNUSED)
1960 {
1961     int		c;
1962     list_T	*l;
1963     listitem_T	*item;
1964     long	reglen = 0;
1965     int		type;
1966 
1967 #ifdef FEAT_CMDL_INFO
1968     if (add_to_showcmd(prev_c))
1969     if (add_to_showcmd('"'))
1970 	out_flush();
1971 #endif
1972     c = term_vgetc();
1973 #ifdef FEAT_CMDL_INFO
1974     clear_showcmd();
1975 #endif
1976     if (!term_use_loop())
1977 	/* job finished while waiting for a character */
1978 	return;
1979 
1980     /* CTRL-W "= prompt for expression to evaluate. */
1981     if (c == '=' && get_expr_register() != '=')
1982 	return;
1983     if (!term_use_loop())
1984 	/* job finished while waiting for a character */
1985 	return;
1986 
1987     l = (list_T *)get_reg_contents(c, GREG_LIST);
1988     if (l != NULL)
1989     {
1990 	type = get_reg_type(c, &reglen);
1991 	for (item = l->lv_first; item != NULL; item = item->li_next)
1992 	{
1993 	    char_u *s = tv_get_string(&item->li_tv);
1994 #ifdef WIN3264
1995 	    char_u *tmp = s;
1996 
1997 	    if (!enc_utf8 && enc_codepage > 0)
1998 	    {
1999 		WCHAR   *ret = NULL;
2000 		int	length = 0;
2001 
2002 		MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
2003 						(int)STRLEN(s), &ret, &length);
2004 		if (ret != NULL)
2005 		{
2006 		    WideCharToMultiByte_alloc(CP_UTF8, 0,
2007 				    ret, length, (char **)&s, &length, 0, 0);
2008 		    vim_free(ret);
2009 		}
2010 	    }
2011 #endif
2012 	    channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2013 						      s, (int)STRLEN(s), NULL);
2014 #ifdef WIN3264
2015 	    if (tmp != s)
2016 		vim_free(s);
2017 #endif
2018 
2019 	    if (item->li_next != NULL || type == MLINE)
2020 		channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2021 						      (char_u *)"\r", 1, NULL);
2022 	}
2023 	list_free(l);
2024     }
2025 }
2026 
2027 /*
2028  * Return TRUE when waiting for a character in the terminal, the cursor of the
2029  * terminal should be displayed.
2030  */
2031     int
2032 terminal_is_active()
2033 {
2034     return in_terminal_loop != NULL;
2035 }
2036 
2037 #if defined(FEAT_GUI) || defined(PROTO)
2038     cursorentry_T *
2039 term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
2040 {
2041     term_T		 *term = in_terminal_loop;
2042     static cursorentry_T entry;
2043     int			 id;
2044     guicolor_T		term_fg, term_bg;
2045 
2046     vim_memset(&entry, 0, sizeof(entry));
2047     entry.shape = entry.mshape =
2048 	term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
2049 	term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
2050 	SHAPE_BLOCK;
2051     entry.percentage = 20;
2052     if (term->tl_cursor_blink)
2053     {
2054 	entry.blinkwait = 700;
2055 	entry.blinkon = 400;
2056 	entry.blinkoff = 250;
2057     }
2058 
2059     /* The "Terminal" highlight group overrules the defaults. */
2060     id = syn_name2id((char_u *)"Terminal");
2061     if (id != 0)
2062     {
2063 	syn_id2colors(id, &term_fg, &term_bg);
2064 	*fg = term_bg;
2065     }
2066     else
2067 	*fg = gui.back_pixel;
2068 
2069     if (term->tl_cursor_color == NULL)
2070     {
2071 	if (id != 0)
2072 	    *bg = term_fg;
2073 	else
2074 	    *bg = gui.norm_pixel;
2075     }
2076     else
2077 	*bg = color_name2handle(term->tl_cursor_color);
2078     entry.name = "n";
2079     entry.used_for = SHAPE_CURSOR;
2080 
2081     return &entry;
2082 }
2083 #endif
2084 
2085     static void
2086 may_output_cursor_props(void)
2087 {
2088     if (!cursor_color_equal(last_set_cursor_color, desired_cursor_color)
2089 	    || last_set_cursor_shape != desired_cursor_shape
2090 	    || last_set_cursor_blink != desired_cursor_blink)
2091     {
2092 	cursor_color_copy(&last_set_cursor_color, desired_cursor_color);
2093 	last_set_cursor_shape = desired_cursor_shape;
2094 	last_set_cursor_blink = desired_cursor_blink;
2095 	term_cursor_color(cursor_color_get(desired_cursor_color));
2096 	if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
2097 	    /* this will restore the initial cursor style, if possible */
2098 	    ui_cursor_shape_forced(TRUE);
2099 	else
2100 	    term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
2101     }
2102 }
2103 
2104 /*
2105  * Set the cursor color and shape, if not last set to these.
2106  */
2107     static void
2108 may_set_cursor_props(term_T *term)
2109 {
2110 #ifdef FEAT_GUI
2111     /* For the GUI the cursor properties are obtained with
2112      * term_get_cursor_shape(). */
2113     if (gui.in_use)
2114 	return;
2115 #endif
2116     if (in_terminal_loop == term)
2117     {
2118 	cursor_color_copy(&desired_cursor_color, term->tl_cursor_color);
2119 	desired_cursor_shape = term->tl_cursor_shape;
2120 	desired_cursor_blink = term->tl_cursor_blink;
2121 	may_output_cursor_props();
2122     }
2123 }
2124 
2125 /*
2126  * Reset the desired cursor properties and restore them when needed.
2127  */
2128     static void
2129 prepare_restore_cursor_props(void)
2130 {
2131 #ifdef FEAT_GUI
2132     if (gui.in_use)
2133 	return;
2134 #endif
2135     cursor_color_copy(&desired_cursor_color, NULL);
2136     desired_cursor_shape = -1;
2137     desired_cursor_blink = -1;
2138     may_output_cursor_props();
2139 }
2140 
2141 /*
2142  * Returns TRUE if the current window contains a terminal and we are sending
2143  * keys to the job.
2144  * If "check_job_status" is TRUE update the job status.
2145  */
2146     static int
2147 term_use_loop_check(int check_job_status)
2148 {
2149     term_T *term = curbuf->b_term;
2150 
2151     return term != NULL
2152 	&& !term->tl_normal_mode
2153 	&& term->tl_vterm != NULL
2154 	&& term_job_running_check(term, check_job_status);
2155 }
2156 
2157 /*
2158  * Returns TRUE if the current window contains a terminal and we are sending
2159  * keys to the job.
2160  */
2161     int
2162 term_use_loop(void)
2163 {
2164     return term_use_loop_check(FALSE);
2165 }
2166 
2167 /*
2168  * Called when entering a window with the mouse.  If this is a terminal window
2169  * we may want to change state.
2170  */
2171     void
2172 term_win_entered()
2173 {
2174     term_T *term = curbuf->b_term;
2175 
2176     if (term != NULL)
2177     {
2178 	if (term_use_loop_check(TRUE))
2179 	{
2180 	    reset_VIsual_and_resel();
2181 	    if (State & INSERT)
2182 		stop_insert_mode = TRUE;
2183 	}
2184 	mouse_was_outside = FALSE;
2185 	enter_mouse_col = mouse_col;
2186 	enter_mouse_row = mouse_row;
2187     }
2188 }
2189 
2190 /*
2191  * Wait for input and send it to the job.
2192  * When "blocking" is TRUE wait for a character to be typed.  Otherwise return
2193  * when there is no more typahead.
2194  * Return when the start of a CTRL-W command is typed or anything else that
2195  * should be handled as a Normal mode command.
2196  * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2197  * the terminal was closed.
2198  */
2199     int
2200 terminal_loop(int blocking)
2201 {
2202     int		c;
2203     int		termwinkey = 0;
2204     int		ret;
2205 #ifdef UNIX
2206     int		tty_fd = curbuf->b_term->tl_job->jv_channel
2207 				 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
2208 #endif
2209     int		restore_cursor = FALSE;
2210 
2211     /* Remember the terminal we are sending keys to.  However, the terminal
2212      * might be closed while waiting for a character, e.g. typing "exit" in a
2213      * shell and ++close was used.  Therefore use curbuf->b_term instead of a
2214      * stored reference. */
2215     in_terminal_loop = curbuf->b_term;
2216 
2217     if (*curwin->w_p_twk != NUL)
2218     {
2219 	termwinkey = string_to_key(curwin->w_p_twk, TRUE);
2220 	if (termwinkey == Ctrl_W)
2221 	    termwinkey = 0;
2222     }
2223     position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2224     may_set_cursor_props(curbuf->b_term);
2225 
2226     while (blocking || vpeekc_nomap() != NUL)
2227     {
2228 #ifdef FEAT_GUI
2229 	if (!curbuf->b_term->tl_system)
2230 #endif
2231 	    // TODO: skip screen update when handling a sequence of keys.
2232 	    // Repeat redrawing in case a message is received while redrawing.
2233 	    while (must_redraw != 0)
2234 		if (update_screen(0) == FAIL)
2235 		    break;
2236 	if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
2237 	    /* job finished while redrawing */
2238 	    break;
2239 
2240 	update_cursor(curbuf->b_term, FALSE);
2241 	restore_cursor = TRUE;
2242 
2243 	c = term_vgetc();
2244 	if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
2245 	{
2246 	    /* Job finished while waiting for a character.  Push back the
2247 	     * received character. */
2248 	    if (c != K_IGNORE)
2249 		vungetc(c);
2250 	    break;
2251 	}
2252 	if (c == K_IGNORE)
2253 	    continue;
2254 
2255 #ifdef UNIX
2256 	/*
2257 	 * The shell or another program may change the tty settings.  Getting
2258 	 * them for every typed character is a bit of overhead, but it's needed
2259 	 * for the first character typed, e.g. when Vim starts in a shell.
2260 	 */
2261 	if (mch_isatty(tty_fd))
2262 	{
2263 	    ttyinfo_T info;
2264 
2265 	    /* Get the current backspace character of the pty. */
2266 	    if (get_tty_info(tty_fd, &info) == OK)
2267 		term_backspace_char = info.backspace;
2268 	}
2269 #endif
2270 
2271 #ifdef WIN3264
2272 	/* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2273 	 * Use CTRL-BREAK to kill the job. */
2274 	if (ctrl_break_was_pressed)
2275 	    mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2276 #endif
2277 	/* Was either CTRL-W (termwinkey) or CTRL-\ pressed?
2278 	 * Not in a system terminal. */
2279 	if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
2280 #ifdef FEAT_GUI
2281 		&& !curbuf->b_term->tl_system
2282 #endif
2283 		)
2284 	{
2285 	    int	    prev_c = c;
2286 
2287 #ifdef FEAT_CMDL_INFO
2288 	    if (add_to_showcmd(c))
2289 		out_flush();
2290 #endif
2291 	    c = term_vgetc();
2292 #ifdef FEAT_CMDL_INFO
2293 	    clear_showcmd();
2294 #endif
2295 	    if (!term_use_loop_check(TRUE)
2296 					 || in_terminal_loop != curbuf->b_term)
2297 		/* job finished while waiting for a character */
2298 		break;
2299 
2300 	    if (prev_c == Ctrl_BSL)
2301 	    {
2302 		if (c == Ctrl_N)
2303 		{
2304 		    /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2305 		    term_enter_normal_mode();
2306 		    ret = FAIL;
2307 		    goto theend;
2308 		}
2309 		/* Send both keys to the terminal. */
2310 		send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2311 	    }
2312 	    else if (c == Ctrl_C)
2313 	    {
2314 		/* "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job */
2315 		mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2316 	    }
2317 	    else if (c == '.')
2318 	    {
2319 		/* "CTRL-W .": send CTRL-W to the job */
2320 		/* "'termwinkey' .": send 'termwinkey' to the job */
2321 		c = termwinkey == 0 ? Ctrl_W : termwinkey;
2322 	    }
2323 	    else if (c == Ctrl_BSL)
2324 	    {
2325 		/* "CTRL-W CTRL-\": send CTRL-\ to the job */
2326 		c = Ctrl_BSL;
2327 	    }
2328 	    else if (c == 'N')
2329 	    {
2330 		/* CTRL-W N : go to Terminal-Normal mode. */
2331 		term_enter_normal_mode();
2332 		ret = FAIL;
2333 		goto theend;
2334 	    }
2335 	    else if (c == '"')
2336 	    {
2337 		term_paste_register(prev_c);
2338 		continue;
2339 	    }
2340 	    else if (termwinkey == 0 || c != termwinkey)
2341 	    {
2342 		stuffcharReadbuff(Ctrl_W);
2343 		stuffcharReadbuff(c);
2344 		ret = OK;
2345 		goto theend;
2346 	    }
2347 	}
2348 # ifdef WIN3264
2349 	if (!enc_utf8 && has_mbyte && c >= 0x80)
2350 	{
2351 	    WCHAR   wc;
2352 	    char_u  mb[3];
2353 
2354 	    mb[0] = (unsigned)c >> 8;
2355 	    mb[1] = c;
2356 	    if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2357 		c = wc;
2358 	}
2359 # endif
2360 	if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2361 	{
2362 	    if (c == K_MOUSEMOVE)
2363 		/* We are sure to come back here, don't reset the cursor color
2364 		 * and shape to avoid flickering. */
2365 		restore_cursor = FALSE;
2366 
2367 	    ret = OK;
2368 	    goto theend;
2369 	}
2370     }
2371     ret = FAIL;
2372 
2373 theend:
2374     in_terminal_loop = NULL;
2375     if (restore_cursor)
2376 	prepare_restore_cursor_props();
2377 
2378     /* Move a snapshot of the screen contents to the buffer, so that completion
2379      * works in other buffers. */
2380     if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode)
2381 	may_move_terminal_to_buffer(curbuf->b_term, FALSE);
2382 
2383     return ret;
2384 }
2385 
2386     static void
2387 may_toggle_cursor(term_T *term)
2388 {
2389     if (in_terminal_loop == term)
2390     {
2391 	if (term->tl_cursor_visible)
2392 	    cursor_on();
2393 	else
2394 	    cursor_off();
2395     }
2396 }
2397 
2398 /*
2399  * Reverse engineer the RGB value into a cterm color index.
2400  * First color is 1.  Return 0 if no match found (default color).
2401  */
2402     static int
2403 color2index(VTermColor *color, int fg, int *boldp)
2404 {
2405     int red = color->red;
2406     int blue = color->blue;
2407     int green = color->green;
2408 
2409     if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
2410     {
2411 	/* First 16 colors and default: use the ANSI index, because these
2412 	 * colors can be redefined. */
2413 	if (t_colors >= 16)
2414 	    return color->ansi_index;
2415 	switch (color->ansi_index)
2416 	{
2417 	    case  0: return 0;
2418 	    case  1: return lookup_color( 0, fg, boldp) + 1; /* black */
2419 	    case  2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2420 	    case  3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2421 	    case  4: return lookup_color( 6, fg, boldp) + 1; /* brown */
2422 	    case  5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
2423 	    case  6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2424 	    case  7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2425 	    case  8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2426 	    case  9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2427 	    case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2428 	    case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2429 	    case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2430 	    case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2431 	    case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2432 	    case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2433 	    case 16: return lookup_color(26, fg, boldp) + 1; /* white */
2434 	}
2435     }
2436 
2437     if (t_colors >= 256)
2438     {
2439 	if (red == blue && red == green)
2440 	{
2441 	    /* 24-color greyscale plus white and black */
2442 	    static int cutoff[23] = {
2443 		    0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2444 		    0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2445 		    0xD5, 0xDF, 0xE9};
2446 	    int i;
2447 
2448 	    if (red < 5)
2449 		return 17; /* 00/00/00 */
2450 	    if (red > 245) /* ff/ff/ff */
2451 		return 232;
2452 	    for (i = 0; i < 23; ++i)
2453 		if (red < cutoff[i])
2454 		    return i + 233;
2455 	    return 256;
2456 	}
2457 	{
2458 	    static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2459 	    int ri, gi, bi;
2460 
2461 	    /* 216-color cube */
2462 	    for (ri = 0; ri < 5; ++ri)
2463 		if (red < cutoff[ri])
2464 		    break;
2465 	    for (gi = 0; gi < 5; ++gi)
2466 		if (green < cutoff[gi])
2467 		    break;
2468 	    for (bi = 0; bi < 5; ++bi)
2469 		if (blue < cutoff[bi])
2470 		    break;
2471 	    return 17 + ri * 36 + gi * 6 + bi;
2472 	}
2473     }
2474     return 0;
2475 }
2476 
2477 /*
2478  * Convert Vterm attributes to highlight flags.
2479  */
2480     static int
2481 vtermAttr2hl(VTermScreenCellAttrs cellattrs)
2482 {
2483     int attr = 0;
2484 
2485     if (cellattrs.bold)
2486 	attr |= HL_BOLD;
2487     if (cellattrs.underline)
2488 	attr |= HL_UNDERLINE;
2489     if (cellattrs.italic)
2490 	attr |= HL_ITALIC;
2491     if (cellattrs.strike)
2492 	attr |= HL_STRIKETHROUGH;
2493     if (cellattrs.reverse)
2494 	attr |= HL_INVERSE;
2495     return attr;
2496 }
2497 
2498 /*
2499  * Store Vterm attributes in "cell" from highlight flags.
2500  */
2501     static void
2502 hl2vtermAttr(int attr, cellattr_T *cell)
2503 {
2504     vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2505     if (attr & HL_BOLD)
2506 	cell->attrs.bold = 1;
2507     if (attr & HL_UNDERLINE)
2508 	cell->attrs.underline = 1;
2509     if (attr & HL_ITALIC)
2510 	cell->attrs.italic = 1;
2511     if (attr & HL_STRIKETHROUGH)
2512 	cell->attrs.strike = 1;
2513     if (attr & HL_INVERSE)
2514 	cell->attrs.reverse = 1;
2515 }
2516 
2517 /*
2518  * Convert the attributes of a vterm cell into an attribute index.
2519  */
2520     static int
2521 cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2522 {
2523     int attr = vtermAttr2hl(cellattrs);
2524 
2525 #ifdef FEAT_GUI
2526     if (gui.in_use)
2527     {
2528 	guicolor_T fg, bg;
2529 
2530 	fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2531 	bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2532 	return get_gui_attr_idx(attr, fg, bg);
2533     }
2534     else
2535 #endif
2536 #ifdef FEAT_TERMGUICOLORS
2537     if (p_tgc)
2538     {
2539 	guicolor_T fg, bg;
2540 
2541 	fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2542 	bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2543 
2544 	return get_tgc_attr_idx(attr, fg, bg);
2545     }
2546     else
2547 #endif
2548     {
2549 	int bold = MAYBE;
2550 	int fg = color2index(&cellfg, TRUE, &bold);
2551 	int bg = color2index(&cellbg, FALSE, &bold);
2552 
2553 	/* Use the "Terminal" highlighting for the default colors. */
2554 	if ((fg == 0 || bg == 0) && t_colors >= 16)
2555 	{
2556 	    if (fg == 0 && term_default_cterm_fg >= 0)
2557 		fg = term_default_cterm_fg + 1;
2558 	    if (bg == 0 && term_default_cterm_bg >= 0)
2559 		bg = term_default_cterm_bg + 1;
2560 	}
2561 
2562 	/* with 8 colors set the bold attribute to get a bright foreground */
2563 	if (bold == TRUE)
2564 	    attr |= HL_BOLD;
2565 	return get_cterm_attr_idx(attr, fg, bg);
2566     }
2567     return 0;
2568 }
2569 
2570     static void
2571 set_dirty_snapshot(term_T *term)
2572 {
2573     term->tl_dirty_snapshot = TRUE;
2574 #ifdef FEAT_TIMERS
2575     if (!term->tl_normal_mode)
2576     {
2577 	/* Update the snapshot after 100 msec of not getting updates. */
2578 	profile_setlimit(100L, &term->tl_timer_due);
2579 	term->tl_timer_set = TRUE;
2580     }
2581 #endif
2582 }
2583 
2584     static int
2585 handle_damage(VTermRect rect, void *user)
2586 {
2587     term_T *term = (term_T *)user;
2588 
2589     term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2590     term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2591     set_dirty_snapshot(term);
2592     redraw_buf_later(term->tl_buffer, SOME_VALID);
2593     return 1;
2594 }
2595 
2596     static void
2597 term_scroll_up(term_T *term, int start_row, int count)
2598 {
2599     win_T		 *wp;
2600     VTermColor		 fg, bg;
2601     VTermScreenCellAttrs attr;
2602     int			 clear_attr;
2603 
2604     /* Set the color to clear lines with. */
2605     vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2606 								     &fg, &bg);
2607     vim_memset(&attr, 0, sizeof(attr));
2608     clear_attr = cell2attr(attr, fg, bg);
2609 
2610     FOR_ALL_WINDOWS(wp)
2611     {
2612 	if (wp->w_buffer == term->tl_buffer)
2613 	    win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
2614     }
2615 }
2616 
2617     static int
2618 handle_moverect(VTermRect dest, VTermRect src, void *user)
2619 {
2620     term_T	*term = (term_T *)user;
2621     int		count = src.start_row - dest.start_row;
2622 
2623     /* Scrolling up is done much more efficiently by deleting lines instead of
2624      * redrawing the text. But avoid doing this multiple times, postpone until
2625      * the redraw happens. */
2626     if (dest.start_col == src.start_col
2627 	    && dest.end_col == src.end_col
2628 	    && dest.start_row < src.start_row)
2629     {
2630 	if (dest.start_row == 0)
2631 	    term->tl_postponed_scroll += count;
2632 	else
2633 	    term_scroll_up(term, dest.start_row, count);
2634     }
2635 
2636     term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2637     term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2638     set_dirty_snapshot(term);
2639 
2640     /* Note sure if the scrolling will work correctly, let's do a complete
2641      * redraw later. */
2642     redraw_buf_later(term->tl_buffer, NOT_VALID);
2643     return 1;
2644 }
2645 
2646     static int
2647 handle_movecursor(
2648 	VTermPos pos,
2649 	VTermPos oldpos UNUSED,
2650 	int visible,
2651 	void *user)
2652 {
2653     term_T	*term = (term_T *)user;
2654     win_T	*wp;
2655 
2656     term->tl_cursor_pos = pos;
2657     term->tl_cursor_visible = visible;
2658 
2659     FOR_ALL_WINDOWS(wp)
2660     {
2661 	if (wp->w_buffer == term->tl_buffer)
2662 	    position_cursor(wp, &pos);
2663     }
2664     if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2665     {
2666 	may_toggle_cursor(term);
2667 	update_cursor(term, term->tl_cursor_visible);
2668     }
2669 
2670     return 1;
2671 }
2672 
2673     static int
2674 handle_settermprop(
2675 	VTermProp prop,
2676 	VTermValue *value,
2677 	void *user)
2678 {
2679     term_T	*term = (term_T *)user;
2680 
2681     switch (prop)
2682     {
2683 	case VTERM_PROP_TITLE:
2684 	    vim_free(term->tl_title);
2685 	    // a blank title isn't useful, make it empty, so that "running" is
2686 	    // displayed
2687 	    if (*skipwhite((char_u *)value->string) == NUL)
2688 		term->tl_title = NULL;
2689 	    // Same as blank
2690 	    else if (term->tl_arg0_cmd != NULL
2691 		    && STRNCMP(term->tl_arg0_cmd, (char_u *)value->string,
2692 					  (int)STRLEN(term->tl_arg0_cmd)) == 0)
2693 		term->tl_title = NULL;
2694 	    // Empty corrupted data of winpty
2695 	    else if (STRNCMP("  - ", (char_u *)value->string, 4) == 0)
2696 		term->tl_title = NULL;
2697 #ifdef WIN3264
2698 	    else if (!enc_utf8 && enc_codepage > 0)
2699 	    {
2700 		WCHAR   *ret = NULL;
2701 		int	length = 0;
2702 
2703 		MultiByteToWideChar_alloc(CP_UTF8, 0,
2704 			(char*)value->string, (int)STRLEN(value->string),
2705 								&ret, &length);
2706 		if (ret != NULL)
2707 		{
2708 		    WideCharToMultiByte_alloc(enc_codepage, 0,
2709 					ret, length, (char**)&term->tl_title,
2710 					&length, 0, 0);
2711 		    vim_free(ret);
2712 		}
2713 	    }
2714 #endif
2715 	    else
2716 		term->tl_title = vim_strsave((char_u *)value->string);
2717 	    VIM_CLEAR(term->tl_status_text);
2718 	    if (term == curbuf->b_term)
2719 		maketitle();
2720 	    break;
2721 
2722 	case VTERM_PROP_CURSORVISIBLE:
2723 	    term->tl_cursor_visible = value->boolean;
2724 	    may_toggle_cursor(term);
2725 	    out_flush();
2726 	    break;
2727 
2728 	case VTERM_PROP_CURSORBLINK:
2729 	    term->tl_cursor_blink = value->boolean;
2730 	    may_set_cursor_props(term);
2731 	    break;
2732 
2733 	case VTERM_PROP_CURSORSHAPE:
2734 	    term->tl_cursor_shape = value->number;
2735 	    may_set_cursor_props(term);
2736 	    break;
2737 
2738 	case VTERM_PROP_CURSORCOLOR:
2739 	    cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string);
2740 	    may_set_cursor_props(term);
2741 	    break;
2742 
2743 	case VTERM_PROP_ALTSCREEN:
2744 	    /* TODO: do anything else? */
2745 	    term->tl_using_altscreen = value->boolean;
2746 	    break;
2747 
2748 	default:
2749 	    break;
2750     }
2751     /* Always return 1, otherwise vterm doesn't store the value internally. */
2752     return 1;
2753 }
2754 
2755 /*
2756  * The job running in the terminal resized the terminal.
2757  */
2758     static int
2759 handle_resize(int rows, int cols, void *user)
2760 {
2761     term_T	*term = (term_T *)user;
2762     win_T	*wp;
2763 
2764     term->tl_rows = rows;
2765     term->tl_cols = cols;
2766     if (term->tl_vterm_size_changed)
2767 	/* Size was set by vterm_set_size(), don't set the window size. */
2768 	term->tl_vterm_size_changed = FALSE;
2769     else
2770     {
2771 	FOR_ALL_WINDOWS(wp)
2772 	{
2773 	    if (wp->w_buffer == term->tl_buffer)
2774 	    {
2775 		win_setheight_win(rows, wp);
2776 		win_setwidth_win(cols, wp);
2777 	    }
2778 	}
2779 	redraw_buf_later(term->tl_buffer, NOT_VALID);
2780     }
2781     return 1;
2782 }
2783 
2784 /*
2785  * Handle a line that is pushed off the top of the screen.
2786  */
2787     static int
2788 handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2789 {
2790     term_T	*term = (term_T *)user;
2791 
2792     /* First remove the lines that were appended before, the pushed line goes
2793      * above it. */
2794     cleanup_scrollback(term);
2795 
2796     /* If the number of lines that are stored goes over 'termscrollback' then
2797      * delete the first 10%. */
2798     if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
2799     {
2800 	int	todo = term->tl_buffer->b_p_twsl / 10;
2801 	int	i;
2802 
2803 	curbuf = term->tl_buffer;
2804 	for (i = 0; i < todo; ++i)
2805 	{
2806 	    vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2807 	    ml_delete(1, FALSE);
2808 	}
2809 	curbuf = curwin->w_buffer;
2810 
2811 	term->tl_scrollback.ga_len -= todo;
2812 	mch_memmove(term->tl_scrollback.ga_data,
2813 	    (sb_line_T *)term->tl_scrollback.ga_data + todo,
2814 	    sizeof(sb_line_T) * term->tl_scrollback.ga_len);
2815 	term->tl_scrollback_scrolled -= todo;
2816     }
2817 
2818     if (ga_grow(&term->tl_scrollback, 1) == OK)
2819     {
2820 	cellattr_T	*p = NULL;
2821 	int		len = 0;
2822 	int		i;
2823 	int		c;
2824 	int		col;
2825 	sb_line_T	*line;
2826 	garray_T	ga;
2827 	cellattr_T	fill_attr = term->tl_default_color;
2828 
2829 	/* do not store empty cells at the end */
2830 	for (i = 0; i < cols; ++i)
2831 	    if (cells[i].chars[0] != 0)
2832 		len = i + 1;
2833 	    else
2834 		cell2cellattr(&cells[i], &fill_attr);
2835 
2836 	ga_init2(&ga, 1, 100);
2837 	if (len > 0)
2838 	    p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2839 	if (p != NULL)
2840 	{
2841 	    for (col = 0; col < len; col += cells[col].width)
2842 	    {
2843 		if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2844 		{
2845 		    ga.ga_len = 0;
2846 		    break;
2847 		}
2848 		for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2849 		    ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2850 					     (char_u *)ga.ga_data + ga.ga_len);
2851 		cell2cellattr(&cells[col], &p[col]);
2852 	    }
2853 	}
2854 	if (ga_grow(&ga, 1) == FAIL)
2855 	    add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2856 	else
2857 	{
2858 	    *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2859 	    add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2860 	}
2861 	ga_clear(&ga);
2862 
2863 	line = (sb_line_T *)term->tl_scrollback.ga_data
2864 						  + term->tl_scrollback.ga_len;
2865 	line->sb_cols = len;
2866 	line->sb_cells = p;
2867 	line->sb_fill_attr = fill_attr;
2868 	++term->tl_scrollback.ga_len;
2869 	++term->tl_scrollback_scrolled;
2870     }
2871     return 0; /* ignored */
2872 }
2873 
2874 static VTermScreenCallbacks screen_callbacks = {
2875   handle_damage,	/* damage */
2876   handle_moverect,	/* moverect */
2877   handle_movecursor,	/* movecursor */
2878   handle_settermprop,	/* settermprop */
2879   NULL,			/* bell */
2880   handle_resize,	/* resize */
2881   handle_pushline,	/* sb_pushline */
2882   NULL			/* sb_popline */
2883 };
2884 
2885 /*
2886  * Do the work after the channel of a terminal was closed.
2887  * Must be called only when updating_screen is FALSE.
2888  * Returns TRUE when a buffer was closed (list of terminals may have changed).
2889  */
2890     static int
2891 term_after_channel_closed(term_T *term)
2892 {
2893     /* Unless in Terminal-Normal mode: clear the vterm. */
2894     if (!term->tl_normal_mode)
2895     {
2896 	int	fnum = term->tl_buffer->b_fnum;
2897 
2898 	cleanup_vterm(term);
2899 
2900 	if (term->tl_finish == TL_FINISH_CLOSE)
2901 	{
2902 	    aco_save_T	aco;
2903 	    int		do_set_w_closing = term->tl_buffer->b_nwindows == 0;
2904 
2905 	    // ++close or term_finish == "close"
2906 	    ch_log(NULL, "terminal job finished, closing window");
2907 	    aucmd_prepbuf(&aco, term->tl_buffer);
2908 	    // Avoid closing the window if we temporarily use it.
2909 	    if (do_set_w_closing)
2910 		curwin->w_closing = TRUE;
2911 	    do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
2912 	    if (do_set_w_closing)
2913 		curwin->w_closing = FALSE;
2914 	    aucmd_restbuf(&aco);
2915 	    return TRUE;
2916 	}
2917 	if (term->tl_finish == TL_FINISH_OPEN
2918 				   && term->tl_buffer->b_nwindows == 0)
2919 	{
2920 	    char buf[50];
2921 
2922 	    /* TODO: use term_opencmd */
2923 	    ch_log(NULL, "terminal job finished, opening window");
2924 	    vim_snprintf(buf, sizeof(buf),
2925 		    term->tl_opencmd == NULL
2926 			    ? "botright sbuf %d"
2927 			    : (char *)term->tl_opencmd, fnum);
2928 	    do_cmdline_cmd((char_u *)buf);
2929 	}
2930 	else
2931 	    ch_log(NULL, "terminal job finished");
2932     }
2933 
2934     redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2935     return FALSE;
2936 }
2937 
2938 /*
2939  * Called when a channel has been closed.
2940  * If this was a channel for a terminal window then finish it up.
2941  */
2942     void
2943 term_channel_closed(channel_T *ch)
2944 {
2945     term_T *term;
2946     term_T *next_term;
2947     int	    did_one = FALSE;
2948 
2949     for (term = first_term; term != NULL; term = next_term)
2950     {
2951 	next_term = term->tl_next;
2952 	if (term->tl_job == ch->ch_job)
2953 	{
2954 	    term->tl_channel_closed = TRUE;
2955 	    did_one = TRUE;
2956 
2957 	    VIM_CLEAR(term->tl_title);
2958 	    VIM_CLEAR(term->tl_status_text);
2959 #ifdef WIN3264
2960 	    if (term->tl_out_fd != NULL)
2961 	    {
2962 		fclose(term->tl_out_fd);
2963 		term->tl_out_fd = NULL;
2964 	    }
2965 #endif
2966 
2967 	    if (updating_screen)
2968 	    {
2969 		/* Cannot open or close windows now.  Can happen when
2970 		 * 'lazyredraw' is set. */
2971 		term->tl_channel_recently_closed = TRUE;
2972 		continue;
2973 	    }
2974 
2975 	    if (term_after_channel_closed(term))
2976 		next_term = first_term;
2977 	}
2978     }
2979 
2980     if (did_one)
2981     {
2982 	redraw_statuslines();
2983 
2984 	/* Need to break out of vgetc(). */
2985 	ins_char_typebuf(K_IGNORE);
2986 	typebuf_was_filled = TRUE;
2987 
2988 	term = curbuf->b_term;
2989 	if (term != NULL)
2990 	{
2991 	    if (term->tl_job == ch->ch_job)
2992 		maketitle();
2993 	    update_cursor(term, term->tl_cursor_visible);
2994 	}
2995     }
2996 }
2997 
2998 /*
2999  * To be called after resetting updating_screen: handle any terminal where the
3000  * channel was closed.
3001  */
3002     void
3003 term_check_channel_closed_recently()
3004 {
3005     term_T *term;
3006     term_T *next_term;
3007 
3008     for (term = first_term; term != NULL; term = next_term)
3009     {
3010 	next_term = term->tl_next;
3011 	if (term->tl_channel_recently_closed)
3012 	{
3013 	    term->tl_channel_recently_closed = FALSE;
3014 	    if (term_after_channel_closed(term))
3015 		// start over, the list may have changed
3016 		next_term = first_term;
3017 	}
3018     }
3019 }
3020 
3021 /*
3022  * Fill one screen line from a line of the terminal.
3023  * Advances "pos" to past the last column.
3024  */
3025     static void
3026 term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
3027 {
3028     int off = screen_get_current_line_off();
3029 
3030     for (pos->col = 0; pos->col < max_col; )
3031     {
3032 	VTermScreenCell cell;
3033 	int		c;
3034 
3035 	if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
3036 	    vim_memset(&cell, 0, sizeof(cell));
3037 
3038 	c = cell.chars[0];
3039 	if (c == NUL)
3040 	{
3041 	    ScreenLines[off] = ' ';
3042 	    if (enc_utf8)
3043 		ScreenLinesUC[off] = NUL;
3044 	}
3045 	else
3046 	{
3047 	    if (enc_utf8)
3048 	    {
3049 		int i;
3050 
3051 		/* composing chars */
3052 		for (i = 0; i < Screen_mco
3053 			      && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
3054 		{
3055 		    ScreenLinesC[i][off] = cell.chars[i + 1];
3056 		    if (cell.chars[i + 1] == 0)
3057 			break;
3058 		}
3059 		if (c >= 0x80 || (Screen_mco > 0
3060 					 && ScreenLinesC[0][off] != 0))
3061 		{
3062 		    ScreenLines[off] = ' ';
3063 		    ScreenLinesUC[off] = c;
3064 		}
3065 		else
3066 		{
3067 		    ScreenLines[off] = c;
3068 		    ScreenLinesUC[off] = NUL;
3069 		}
3070 	    }
3071 #ifdef WIN3264
3072 	    else if (has_mbyte && c >= 0x80)
3073 	    {
3074 		char_u	mb[MB_MAXBYTES+1];
3075 		WCHAR	wc = c;
3076 
3077 		if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
3078 					       (char*)mb, 2, 0, 0) > 1)
3079 		{
3080 		    ScreenLines[off] = mb[0];
3081 		    ScreenLines[off + 1] = mb[1];
3082 		    cell.width = mb_ptr2cells(mb);
3083 		}
3084 		else
3085 		    ScreenLines[off] = c;
3086 	    }
3087 #endif
3088 	    else
3089 		ScreenLines[off] = c;
3090 	}
3091 	ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
3092 
3093 	++pos->col;
3094 	++off;
3095 	if (cell.width == 2)
3096 	{
3097 	    if (enc_utf8)
3098 		ScreenLinesUC[off] = NUL;
3099 
3100 	    /* don't set the second byte to NUL for a DBCS encoding, it
3101 	     * has been set above */
3102 	    if (enc_utf8 || !has_mbyte)
3103 		ScreenLines[off] = NUL;
3104 
3105 	    ++pos->col;
3106 	    ++off;
3107 	}
3108     }
3109 }
3110 
3111 #if defined(FEAT_GUI)
3112     static void
3113 update_system_term(term_T *term)
3114 {
3115     VTermPos	    pos;
3116     VTermScreen	    *screen;
3117 
3118     if (term->tl_vterm == NULL)
3119 	return;
3120     screen = vterm_obtain_screen(term->tl_vterm);
3121 
3122     /* Scroll up to make more room for terminal lines if needed. */
3123     while (term->tl_toprow > 0
3124 			  && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3125     {
3126 	int save_p_more = p_more;
3127 
3128 	p_more = FALSE;
3129 	msg_row = Rows - 1;
3130 	msg_puts("\n");
3131 	p_more = save_p_more;
3132 	--term->tl_toprow;
3133     }
3134 
3135     for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3136 						  && pos.row < Rows; ++pos.row)
3137     {
3138 	if (pos.row < term->tl_rows)
3139 	{
3140 	    int max_col = MIN(Columns, term->tl_cols);
3141 
3142 	    term_line2screenline(screen, &pos, max_col);
3143 	}
3144 	else
3145 	    pos.col = 0;
3146 
3147 	screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
3148     }
3149 
3150     term->tl_dirty_row_start = MAX_ROW;
3151     term->tl_dirty_row_end = 0;
3152     update_cursor(term, TRUE);
3153 }
3154 #endif
3155 
3156 /*
3157  * Return TRUE if window "wp" is to be redrawn with term_update_window().
3158  * Returns FALSE when there is no terminal running in this window or it is in
3159  * Terminal-Normal mode.
3160  */
3161     int
3162 term_do_update_window(win_T *wp)
3163 {
3164     term_T	*term = wp->w_buffer->b_term;
3165 
3166     return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3167 }
3168 
3169 /*
3170  * Called to update a window that contains an active terminal.
3171  */
3172     void
3173 term_update_window(win_T *wp)
3174 {
3175     term_T	*term = wp->w_buffer->b_term;
3176     VTerm	*vterm;
3177     VTermScreen *screen;
3178     VTermState	*state;
3179     VTermPos	pos;
3180     int		rows, cols;
3181     int		newrows, newcols;
3182     int		minsize;
3183     win_T	*twp;
3184 
3185     vterm = term->tl_vterm;
3186     screen = vterm_obtain_screen(vterm);
3187     state = vterm_obtain_state(vterm);
3188 
3189     /* We use NOT_VALID on a resize or scroll, redraw everything then.  With
3190      * SOME_VALID only redraw what was marked dirty. */
3191     if (wp->w_redr_type > SOME_VALID)
3192     {
3193 	term->tl_dirty_row_start = 0;
3194 	term->tl_dirty_row_end = MAX_ROW;
3195 
3196 	if (term->tl_postponed_scroll > 0
3197 			      && term->tl_postponed_scroll < term->tl_rows / 3)
3198 	    /* Scrolling is usually faster than redrawing, when there are only
3199 	     * a few lines to scroll. */
3200 	    term_scroll_up(term, 0, term->tl_postponed_scroll);
3201 	term->tl_postponed_scroll = 0;
3202     }
3203 
3204     /*
3205      * If the window was resized a redraw will be triggered and we get here.
3206      * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
3207      */
3208     minsize = parse_termwinsize(wp, &rows, &cols);
3209 
3210     newrows = 99999;
3211     newcols = 99999;
3212     FOR_ALL_WINDOWS(twp)
3213     {
3214 	/* When more than one window shows the same terminal, use the
3215 	 * smallest size. */
3216 	if (twp->w_buffer == term->tl_buffer)
3217 	{
3218 	    newrows = MIN(newrows, twp->w_height);
3219 	    newcols = MIN(newcols, twp->w_width);
3220 	}
3221     }
3222     newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3223     newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3224 
3225     if (term->tl_rows != newrows || term->tl_cols != newcols)
3226     {
3227 	term->tl_vterm_size_changed = TRUE;
3228 	vterm_set_size(vterm, newrows, newcols);
3229 	ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
3230 								      newrows);
3231 	term_report_winsize(term, newrows, newcols);
3232 
3233 	// Updating the terminal size will cause the snapshot to be cleared.
3234 	// When not in terminal_loop() we need to restore it.
3235 	if (term != in_terminal_loop)
3236 	    may_move_terminal_to_buffer(term, FALSE);
3237     }
3238 
3239     /* The cursor may have been moved when resizing. */
3240     vterm_state_get_cursorpos(state, &pos);
3241     position_cursor(wp, &pos);
3242 
3243     for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3244 					  && pos.row < wp->w_height; ++pos.row)
3245     {
3246 	if (pos.row < term->tl_rows)
3247 	{
3248 	    int max_col = MIN(wp->w_width, term->tl_cols);
3249 
3250 	    term_line2screenline(screen, &pos, max_col);
3251 	}
3252 	else
3253 	    pos.col = 0;
3254 
3255 	screen_line(wp->w_winrow + pos.row
3256 #ifdef FEAT_MENU
3257 				+ winbar_height(wp)
3258 #endif
3259 				, wp->w_wincol, pos.col, wp->w_width, FALSE);
3260     }
3261     term->tl_dirty_row_start = MAX_ROW;
3262     term->tl_dirty_row_end = 0;
3263 }
3264 
3265 /*
3266  * Return TRUE if "wp" is a terminal window where the job has finished.
3267  */
3268     int
3269 term_is_finished(buf_T *buf)
3270 {
3271     return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3272 }
3273 
3274 /*
3275  * Return TRUE if "wp" is a terminal window where the job has finished or we
3276  * are in Terminal-Normal mode, thus we show the buffer contents.
3277  */
3278     int
3279 term_show_buffer(buf_T *buf)
3280 {
3281     term_T *term = buf->b_term;
3282 
3283     return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3284 }
3285 
3286 /*
3287  * The current buffer is going to be changed.  If there is terminal
3288  * highlighting remove it now.
3289  */
3290     void
3291 term_change_in_curbuf(void)
3292 {
3293     term_T *term = curbuf->b_term;
3294 
3295     if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3296     {
3297 	free_scrollback(term);
3298 	redraw_buf_later(term->tl_buffer, NOT_VALID);
3299 
3300 	/* The buffer is now like a normal buffer, it cannot be easily
3301 	 * abandoned when changed. */
3302 	set_string_option_direct((char_u *)"buftype", -1,
3303 					  (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3304     }
3305 }
3306 
3307 /*
3308  * Get the screen attribute for a position in the buffer.
3309  * Use a negative "col" to get the filler background color.
3310  */
3311     int
3312 term_get_attr(buf_T *buf, linenr_T lnum, int col)
3313 {
3314     term_T	*term = buf->b_term;
3315     sb_line_T	*line;
3316     cellattr_T	*cellattr;
3317 
3318     if (lnum > term->tl_scrollback.ga_len)
3319 	cellattr = &term->tl_default_color;
3320     else
3321     {
3322 	line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3323 	if (col < 0 || col >= line->sb_cols)
3324 	    cellattr = &line->sb_fill_attr;
3325 	else
3326 	    cellattr = line->sb_cells + col;
3327     }
3328     return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3329 }
3330 
3331 /*
3332  * Convert a cterm color number 0 - 255 to RGB.
3333  * This is compatible with xterm.
3334  */
3335     static void
3336 cterm_color2vterm(int nr, VTermColor *rgb)
3337 {
3338     cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
3339 }
3340 
3341 /*
3342  * Initialize term->tl_default_color from the environment.
3343  */
3344     static void
3345 init_default_colors(term_T *term)
3346 {
3347     VTermColor	    *fg, *bg;
3348     int		    fgval, bgval;
3349     int		    id;
3350 
3351     vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3352     term->tl_default_color.width = 1;
3353     fg = &term->tl_default_color.fg;
3354     bg = &term->tl_default_color.bg;
3355 
3356     /* Vterm uses a default black background.  Set it to white when
3357      * 'background' is "light". */
3358     if (*p_bg == 'l')
3359     {
3360 	fgval = 0;
3361 	bgval = 255;
3362     }
3363     else
3364     {
3365 	fgval = 255;
3366 	bgval = 0;
3367     }
3368     fg->red = fg->green = fg->blue = fgval;
3369     bg->red = bg->green = bg->blue = bgval;
3370     fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
3371 
3372     /* The "Terminal" highlight group overrules the defaults. */
3373     id = syn_name2id((char_u *)"Terminal");
3374 
3375     /* Use the actual color for the GUI and when 'termguicolors' is set. */
3376 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3377     if (0
3378 # ifdef FEAT_GUI
3379 	    || gui.in_use
3380 # endif
3381 # ifdef FEAT_TERMGUICOLORS
3382 	    || p_tgc
3383 #  ifdef FEAT_VTP
3384 	    /* Finally get INVALCOLOR on this execution path */
3385 	    || (!p_tgc && t_colors >= 256)
3386 #  endif
3387 # endif
3388        )
3389     {
3390 	guicolor_T	fg_rgb = INVALCOLOR;
3391 	guicolor_T	bg_rgb = INVALCOLOR;
3392 
3393 	if (id != 0)
3394 	    syn_id2colors(id, &fg_rgb, &bg_rgb);
3395 
3396 # ifdef FEAT_GUI
3397 	if (gui.in_use)
3398 	{
3399 	    if (fg_rgb == INVALCOLOR)
3400 		fg_rgb = gui.norm_pixel;
3401 	    if (bg_rgb == INVALCOLOR)
3402 		bg_rgb = gui.back_pixel;
3403 	}
3404 #  ifdef FEAT_TERMGUICOLORS
3405 	else
3406 #  endif
3407 # endif
3408 # ifdef FEAT_TERMGUICOLORS
3409 	{
3410 	    if (fg_rgb == INVALCOLOR)
3411 		fg_rgb = cterm_normal_fg_gui_color;
3412 	    if (bg_rgb == INVALCOLOR)
3413 		bg_rgb = cterm_normal_bg_gui_color;
3414 	}
3415 # endif
3416 	if (fg_rgb != INVALCOLOR)
3417 	{
3418 	    long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3419 
3420 	    fg->red = (unsigned)(rgb >> 16);
3421 	    fg->green = (unsigned)(rgb >> 8) & 255;
3422 	    fg->blue = (unsigned)rgb & 255;
3423 	}
3424 	if (bg_rgb != INVALCOLOR)
3425 	{
3426 	    long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3427 
3428 	    bg->red = (unsigned)(rgb >> 16);
3429 	    bg->green = (unsigned)(rgb >> 8) & 255;
3430 	    bg->blue = (unsigned)rgb & 255;
3431 	}
3432     }
3433     else
3434 #endif
3435     if (id != 0 && t_colors >= 16)
3436     {
3437 	if (term_default_cterm_fg >= 0)
3438 	    cterm_color2vterm(term_default_cterm_fg, fg);
3439 	if (term_default_cterm_bg >= 0)
3440 	    cterm_color2vterm(term_default_cterm_bg, bg);
3441     }
3442     else
3443     {
3444 #if defined(WIN3264) && !defined(FEAT_GUI_W32)
3445 	int tmp;
3446 #endif
3447 
3448 	/* In an MS-Windows console we know the normal colors. */
3449 	if (cterm_normal_fg_color > 0)
3450 	{
3451 	    cterm_color2vterm(cterm_normal_fg_color - 1, fg);
3452 # if defined(WIN3264) && !defined(FEAT_GUI_W32)
3453 	    tmp = fg->red;
3454 	    fg->red = fg->blue;
3455 	    fg->blue = tmp;
3456 # endif
3457 	}
3458 # ifdef FEAT_TERMRESPONSE
3459 	else
3460 	    term_get_fg_color(&fg->red, &fg->green, &fg->blue);
3461 # endif
3462 
3463 	if (cterm_normal_bg_color > 0)
3464 	{
3465 	    cterm_color2vterm(cterm_normal_bg_color - 1, bg);
3466 # if defined(WIN3264) && !defined(FEAT_GUI_W32)
3467 	    tmp = bg->red;
3468 	    bg->red = bg->blue;
3469 	    bg->blue = tmp;
3470 # endif
3471 	}
3472 # ifdef FEAT_TERMRESPONSE
3473 	else
3474 	    term_get_bg_color(&bg->red, &bg->green, &bg->blue);
3475 # endif
3476     }
3477 }
3478 
3479 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3480 /*
3481  * Set the 16 ANSI colors from array of RGB values
3482  */
3483     static void
3484 set_vterm_palette(VTerm *vterm, long_u *rgb)
3485 {
3486     int		index = 0;
3487     VTermState	*state = vterm_obtain_state(vterm);
3488 
3489     for (; index < 16; index++)
3490     {
3491 	VTermColor	color;
3492 	color.red = (unsigned)(rgb[index] >> 16);
3493 	color.green = (unsigned)(rgb[index] >> 8) & 255;
3494 	color.blue = (unsigned)rgb[index] & 255;
3495 	vterm_state_set_palette_color(state, index, &color);
3496     }
3497 }
3498 
3499 /*
3500  * Set the ANSI color palette from a list of colors
3501  */
3502     static int
3503 set_ansi_colors_list(VTerm *vterm, list_T *list)
3504 {
3505     int		n = 0;
3506     long_u	rgb[16];
3507     listitem_T	*li = list->lv_first;
3508 
3509     for (; li != NULL && n < 16; li = li->li_next, n++)
3510     {
3511 	char_u		*color_name;
3512 	guicolor_T	guicolor;
3513 
3514 	color_name = tv_get_string_chk(&li->li_tv);
3515 	if (color_name == NULL)
3516 	    return FAIL;
3517 
3518 	guicolor = GUI_GET_COLOR(color_name);
3519 	if (guicolor == INVALCOLOR)
3520 	    return FAIL;
3521 
3522 	rgb[n] = GUI_MCH_GET_RGB(guicolor);
3523     }
3524 
3525     if (n != 16 || li != NULL)
3526 	return FAIL;
3527 
3528     set_vterm_palette(vterm, rgb);
3529 
3530     return OK;
3531 }
3532 
3533 /*
3534  * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3535  */
3536     static void
3537 init_vterm_ansi_colors(VTerm *vterm)
3538 {
3539     dictitem_T	*var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3540 
3541     if (var != NULL
3542 	    && (var->di_tv.v_type != VAR_LIST
3543 		|| var->di_tv.vval.v_list == NULL
3544 		|| set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
3545 	semsg(_(e_invarg2), "g:terminal_ansi_colors");
3546 }
3547 #endif
3548 
3549 /*
3550  * Handles a "drop" command from the job in the terminal.
3551  * "item" is the file name, "item->li_next" may have options.
3552  */
3553     static void
3554 handle_drop_command(listitem_T *item)
3555 {
3556     char_u	*fname = tv_get_string(&item->li_tv);
3557     listitem_T	*opt_item = item->li_next;
3558     int		bufnr;
3559     win_T	*wp;
3560     tabpage_T   *tp;
3561     exarg_T	ea;
3562     char_u	*tofree = NULL;
3563 
3564     bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3565     FOR_ALL_TAB_WINDOWS(tp, wp)
3566     {
3567 	if (wp->w_buffer->b_fnum == bufnr)
3568 	{
3569 	    /* buffer is in a window already, go there */
3570 	    goto_tabpage_win(tp, wp);
3571 	    return;
3572 	}
3573     }
3574 
3575     vim_memset(&ea, 0, sizeof(ea));
3576 
3577     if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3578 					&& opt_item->li_tv.vval.v_dict != NULL)
3579     {
3580 	dict_T *dict = opt_item->li_tv.vval.v_dict;
3581 	char_u *p;
3582 
3583 	p = dict_get_string(dict, (char_u *)"ff", FALSE);
3584 	if (p == NULL)
3585 	    p = dict_get_string(dict, (char_u *)"fileformat", FALSE);
3586 	if (p != NULL)
3587 	{
3588 	    if (check_ff_value(p) == FAIL)
3589 		ch_log(NULL, "Invalid ff argument to drop: %s", p);
3590 	    else
3591 		ea.force_ff = *p;
3592 	}
3593 	p = dict_get_string(dict, (char_u *)"enc", FALSE);
3594 	if (p == NULL)
3595 	    p = dict_get_string(dict, (char_u *)"encoding", FALSE);
3596 	if (p != NULL)
3597 	{
3598 	    ea.cmd = alloc((int)STRLEN(p) + 12);
3599 	    if (ea.cmd != NULL)
3600 	    {
3601 		sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3602 		ea.force_enc = 11;
3603 		tofree = ea.cmd;
3604 	    }
3605 	}
3606 
3607 	p = dict_get_string(dict, (char_u *)"bad", FALSE);
3608 	if (p != NULL)
3609 	    get_bad_opt(p, &ea);
3610 
3611 	if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3612 	    ea.force_bin = FORCE_BIN;
3613 	if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3614 	    ea.force_bin = FORCE_BIN;
3615 	if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3616 	    ea.force_bin = FORCE_NOBIN;
3617 	if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3618 	    ea.force_bin = FORCE_NOBIN;
3619     }
3620 
3621     /* open in new window, like ":split fname" */
3622     if (ea.cmd == NULL)
3623 	ea.cmd = (char_u *)"split";
3624     ea.arg = fname;
3625     ea.cmdidx = CMD_split;
3626     ex_splitview(&ea);
3627 
3628     vim_free(tofree);
3629 }
3630 
3631 /*
3632  * Handles a function call from the job running in a terminal.
3633  * "item" is the function name, "item->li_next" has the arguments.
3634  */
3635     static void
3636 handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3637 {
3638     char_u	*func;
3639     typval_T	argvars[2];
3640     typval_T	rettv;
3641     int		doesrange;
3642 
3643     if (item->li_next == NULL)
3644     {
3645 	ch_log(channel, "Missing function arguments for call");
3646 	return;
3647     }
3648     func = tv_get_string(&item->li_tv);
3649 
3650     if (STRNCMP(func, "Tapi_", 5) != 0)
3651     {
3652 	ch_log(channel, "Invalid function name: %s", func);
3653 	return;
3654     }
3655 
3656     argvars[0].v_type = VAR_NUMBER;
3657     argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3658     argvars[1] = item->li_next->li_tv;
3659     if (call_func(func, (int)STRLEN(func), &rettv,
3660 		2, argvars, /* argv_func */ NULL,
3661 		/* firstline */ 1, /* lastline */ 1,
3662 		&doesrange, /* evaluate */ TRUE,
3663 		/* partial */ NULL, /* selfdict */ NULL) == OK)
3664     {
3665 	clear_tv(&rettv);
3666 	ch_log(channel, "Function %s called", func);
3667     }
3668     else
3669 	ch_log(channel, "Calling function %s failed", func);
3670 }
3671 
3672 /*
3673  * Called by libvterm when it cannot recognize an OSC sequence.
3674  * We recognize a terminal API command.
3675  */
3676     static int
3677 parse_osc(const char *command, size_t cmdlen, void *user)
3678 {
3679     term_T	*term = (term_T *)user;
3680     js_read_T	reader;
3681     typval_T	tv;
3682     channel_T	*channel = term->tl_job == NULL ? NULL
3683 						    : term->tl_job->jv_channel;
3684 
3685     /* We recognize only OSC 5 1 ; {command} */
3686     if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3687 	return 0; /* not handled */
3688 
3689     reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
3690     if (reader.js_buf == NULL)
3691 	return 1;
3692     reader.js_fill = NULL;
3693     reader.js_used = 0;
3694     if (json_decode(&reader, &tv, 0) == OK
3695 	    && tv.v_type == VAR_LIST
3696 	    && tv.vval.v_list != NULL)
3697     {
3698 	listitem_T *item = tv.vval.v_list->lv_first;
3699 
3700 	if (item == NULL)
3701 	    ch_log(channel, "Missing command");
3702 	else
3703 	{
3704 	    char_u	*cmd = tv_get_string(&item->li_tv);
3705 
3706 	    /* Make sure an invoked command doesn't delete the buffer (and the
3707 	     * terminal) under our fingers. */
3708 	    ++term->tl_buffer->b_locked;
3709 
3710 	    item = item->li_next;
3711 	    if (item == NULL)
3712 		ch_log(channel, "Missing argument for %s", cmd);
3713 	    else if (STRCMP(cmd, "drop") == 0)
3714 		handle_drop_command(item);
3715 	    else if (STRCMP(cmd, "call") == 0)
3716 		handle_call_command(term, channel, item);
3717 	    else
3718 		ch_log(channel, "Invalid command received: %s", cmd);
3719 	    --term->tl_buffer->b_locked;
3720 	}
3721     }
3722     else
3723 	ch_log(channel, "Invalid JSON received");
3724 
3725     vim_free(reader.js_buf);
3726     clear_tv(&tv);
3727     return 1;
3728 }
3729 
3730 static VTermParserCallbacks parser_fallbacks = {
3731   NULL,		/* text */
3732   NULL,		/* control */
3733   NULL,		/* escape */
3734   NULL,		/* csi */
3735   parse_osc,	/* osc */
3736   NULL,		/* dcs */
3737   NULL		/* resize */
3738 };
3739 
3740 /*
3741  * Use Vim's allocation functions for vterm so profiling works.
3742  */
3743     static void *
3744 vterm_malloc(size_t size, void *data UNUSED)
3745 {
3746     return alloc_clear((unsigned) size);
3747 }
3748 
3749     static void
3750 vterm_memfree(void *ptr, void *data UNUSED)
3751 {
3752     vim_free(ptr);
3753 }
3754 
3755 static VTermAllocatorFunctions vterm_allocator = {
3756   &vterm_malloc,
3757   &vterm_memfree
3758 };
3759 
3760 /*
3761  * Create a new vterm and initialize it.
3762  * Return FAIL when out of memory.
3763  */
3764     static int
3765 create_vterm(term_T *term, int rows, int cols)
3766 {
3767     VTerm	    *vterm;
3768     VTermScreen	    *screen;
3769     VTermState	    *state;
3770     VTermValue	    value;
3771 
3772     vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
3773     term->tl_vterm = vterm;
3774     if (vterm == NULL)
3775 	return FAIL;
3776 
3777     // Allocate screen and state here, so we can bail out if that fails.
3778     state = vterm_obtain_state(vterm);
3779     screen = vterm_obtain_screen(vterm);
3780     if (state == NULL || screen == NULL)
3781     {
3782 	vterm_free(vterm);
3783 	return FAIL;
3784     }
3785 
3786     vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3787     /* TODO: depends on 'encoding'. */
3788     vterm_set_utf8(vterm, 1);
3789 
3790     init_default_colors(term);
3791 
3792     vterm_state_set_default_colors(
3793 	    state,
3794 	    &term->tl_default_color.fg,
3795 	    &term->tl_default_color.bg);
3796 
3797     if (t_colors >= 16)
3798 	vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3799 
3800     /* Required to initialize most things. */
3801     vterm_screen_reset(screen, 1 /* hard */);
3802 
3803     /* Allow using alternate screen. */
3804     vterm_screen_enable_altscreen(screen, 1);
3805 
3806     /* For unix do not use a blinking cursor.  In an xterm this causes the
3807      * cursor to blink if it's blinking in the xterm.
3808      * For Windows we respect the system wide setting. */
3809 #ifdef WIN3264
3810     if (GetCaretBlinkTime() == INFINITE)
3811 	value.boolean = 0;
3812     else
3813 	value.boolean = 1;
3814 #else
3815     value.boolean = 0;
3816 #endif
3817     vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3818     vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
3819 
3820     return OK;
3821 }
3822 
3823 /*
3824  * Return the text to show for the buffer name and status.
3825  */
3826     char_u *
3827 term_get_status_text(term_T *term)
3828 {
3829     if (term->tl_status_text == NULL)
3830     {
3831 	char_u *txt;
3832 	size_t len;
3833 
3834 	if (term->tl_normal_mode)
3835 	{
3836 	    if (term_job_running(term))
3837 		txt = (char_u *)_("Terminal");
3838 	    else
3839 		txt = (char_u *)_("Terminal-finished");
3840 	}
3841 	else if (term->tl_title != NULL)
3842 	    txt = term->tl_title;
3843 	else if (term_none_open(term))
3844 	    txt = (char_u *)_("active");
3845 	else if (term_job_running(term))
3846 	    txt = (char_u *)_("running");
3847 	else
3848 	    txt = (char_u *)_("finished");
3849 	len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3850 	term->tl_status_text = alloc((int)len);
3851 	if (term->tl_status_text != NULL)
3852 	    vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3853 						term->tl_buffer->b_fname, txt);
3854     }
3855     return term->tl_status_text;
3856 }
3857 
3858 /*
3859  * Mark references in jobs of terminals.
3860  */
3861     int
3862 set_ref_in_term(int copyID)
3863 {
3864     int		abort = FALSE;
3865     term_T	*term;
3866     typval_T	tv;
3867 
3868     for (term = first_term; term != NULL; term = term->tl_next)
3869 	if (term->tl_job != NULL)
3870 	{
3871 	    tv.v_type = VAR_JOB;
3872 	    tv.vval.v_job = term->tl_job;
3873 	    abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3874 	}
3875     return abort;
3876 }
3877 
3878 /*
3879  * Cache "Terminal" highlight group colors.
3880  */
3881     void
3882 set_terminal_default_colors(int cterm_fg, int cterm_bg)
3883 {
3884     term_default_cterm_fg = cterm_fg - 1;
3885     term_default_cterm_bg = cterm_bg - 1;
3886 }
3887 
3888 /*
3889  * Get the buffer from the first argument in "argvars".
3890  * Returns NULL when the buffer is not for a terminal window and logs a message
3891  * with "where".
3892  */
3893     static buf_T *
3894 term_get_buf(typval_T *argvars, char *where)
3895 {
3896     buf_T *buf;
3897 
3898     (void)tv_get_number(&argvars[0]);	    /* issue errmsg if type error */
3899     ++emsg_off;
3900     buf = tv_get_buf(&argvars[0], FALSE);
3901     --emsg_off;
3902     if (buf == NULL || buf->b_term == NULL)
3903     {
3904 	ch_log(NULL, "%s: invalid buffer argument", where);
3905 	return NULL;
3906     }
3907     return buf;
3908 }
3909 
3910     static int
3911 same_color(VTermColor *a, VTermColor *b)
3912 {
3913     return a->red == b->red
3914 	&& a->green == b->green
3915 	&& a->blue == b->blue
3916 	&& a->ansi_index == b->ansi_index;
3917 }
3918 
3919     static void
3920 dump_term_color(FILE *fd, VTermColor *color)
3921 {
3922     fprintf(fd, "%02x%02x%02x%d",
3923 	    (int)color->red, (int)color->green, (int)color->blue,
3924 	    (int)color->ansi_index);
3925 }
3926 
3927 /*
3928  * "term_dumpwrite(buf, filename, options)" function
3929  *
3930  * Each screen cell in full is:
3931  *    |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3932  * {characters} is a space for an empty cell
3933  * For a double-width character "+" is changed to "*" and the next cell is
3934  * skipped.
3935  * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3936  *			  when "&" use the same as the previous cell.
3937  * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3938  * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3939  * {color-idx} is a number from 0 to 255
3940  *
3941  * Screen cell with same width, attributes and color as the previous one:
3942  *    |{characters}
3943  *
3944  * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3945  *
3946  * Repeating the previous screen cell:
3947  *    @{count}
3948  */
3949     void
3950 f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3951 {
3952     buf_T	*buf = term_get_buf(argvars, "term_dumpwrite()");
3953     term_T	*term;
3954     char_u	*fname;
3955     int		max_height = 0;
3956     int		max_width = 0;
3957     stat_T	st;
3958     FILE	*fd;
3959     VTermPos	pos;
3960     VTermScreen *screen;
3961     VTermScreenCell prev_cell;
3962     VTermState	*state;
3963     VTermPos	cursor_pos;
3964 
3965     if (check_restricted() || check_secure())
3966 	return;
3967     if (buf == NULL)
3968 	return;
3969     term = buf->b_term;
3970     if (term->tl_vterm == NULL)
3971     {
3972 	emsg(_("E958: Job already finished"));
3973 	return;
3974     }
3975 
3976     if (argvars[2].v_type != VAR_UNKNOWN)
3977     {
3978 	dict_T *d;
3979 
3980 	if (argvars[2].v_type != VAR_DICT)
3981 	{
3982 	    emsg(_(e_dictreq));
3983 	    return;
3984 	}
3985 	d = argvars[2].vval.v_dict;
3986 	if (d != NULL)
3987 	{
3988 	    max_height = dict_get_number(d, (char_u *)"rows");
3989 	    max_width = dict_get_number(d, (char_u *)"columns");
3990 	}
3991     }
3992 
3993     fname = tv_get_string_chk(&argvars[1]);
3994     if (fname == NULL)
3995 	return;
3996     if (mch_stat((char *)fname, &st) >= 0)
3997     {
3998 	semsg(_("E953: File exists: %s"), fname);
3999 	return;
4000     }
4001 
4002     if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
4003     {
4004 	semsg(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
4005 	return;
4006     }
4007 
4008     vim_memset(&prev_cell, 0, sizeof(prev_cell));
4009 
4010     screen = vterm_obtain_screen(term->tl_vterm);
4011     state = vterm_obtain_state(term->tl_vterm);
4012     vterm_state_get_cursorpos(state, &cursor_pos);
4013 
4014     for (pos.row = 0; (max_height == 0 || pos.row < max_height)
4015 					 && pos.row < term->tl_rows; ++pos.row)
4016     {
4017 	int		repeat = 0;
4018 
4019 	for (pos.col = 0; (max_width == 0 || pos.col < max_width)
4020 					 && pos.col < term->tl_cols; ++pos.col)
4021 	{
4022 	    VTermScreenCell cell;
4023 	    int		    same_attr;
4024 	    int		    same_chars = TRUE;
4025 	    int		    i;
4026 	    int		    is_cursor_pos = (pos.col == cursor_pos.col
4027 						 && pos.row == cursor_pos.row);
4028 
4029 	    if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4030 		vim_memset(&cell, 0, sizeof(cell));
4031 
4032 	    for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4033 	    {
4034 		int c = cell.chars[i];
4035 		int pc = prev_cell.chars[i];
4036 
4037 		/* For the first character NUL is the same as space. */
4038 		if (i == 0)
4039 		{
4040 		    c = (c == NUL) ? ' ' : c;
4041 		    pc = (pc == NUL) ? ' ' : pc;
4042 		}
4043 		if (c != pc)
4044 		    same_chars = FALSE;
4045 		if (c == NUL || pc == NUL)
4046 		    break;
4047 	    }
4048 	    same_attr = vtermAttr2hl(cell.attrs)
4049 					       == vtermAttr2hl(prev_cell.attrs)
4050 			&& same_color(&cell.fg, &prev_cell.fg)
4051 			&& same_color(&cell.bg, &prev_cell.bg);
4052 	    if (same_chars && cell.width == prev_cell.width && same_attr
4053 							     && !is_cursor_pos)
4054 	    {
4055 		++repeat;
4056 	    }
4057 	    else
4058 	    {
4059 		if (repeat > 0)
4060 		{
4061 		    fprintf(fd, "@%d", repeat);
4062 		    repeat = 0;
4063 		}
4064 		fputs(is_cursor_pos ? ">" : "|", fd);
4065 
4066 		if (cell.chars[0] == NUL)
4067 		    fputs(" ", fd);
4068 		else
4069 		{
4070 		    char_u	charbuf[10];
4071 		    int		len;
4072 
4073 		    for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
4074 						  && cell.chars[i] != NUL; ++i)
4075 		    {
4076 			len = utf_char2bytes(cell.chars[i], charbuf);
4077 			fwrite(charbuf, len, 1, fd);
4078 		    }
4079 		}
4080 
4081 		/* When only the characters differ we don't write anything, the
4082 		 * following "|", "@" or NL will indicate using the same
4083 		 * attributes. */
4084 		if (cell.width != prev_cell.width || !same_attr)
4085 		{
4086 		    if (cell.width == 2)
4087 		    {
4088 			fputs("*", fd);
4089 		    }
4090 		    else
4091 			fputs("+", fd);
4092 
4093 		    if (same_attr)
4094 		    {
4095 			fputs("&", fd);
4096 		    }
4097 		    else
4098 		    {
4099 			fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
4100 			if (same_color(&cell.fg, &prev_cell.fg))
4101 			    fputs("&", fd);
4102 			else
4103 			{
4104 			    fputs("#", fd);
4105 			    dump_term_color(fd, &cell.fg);
4106 			}
4107 			if (same_color(&cell.bg, &prev_cell.bg))
4108 			    fputs("&", fd);
4109 			else
4110 			{
4111 			    fputs("#", fd);
4112 			    dump_term_color(fd, &cell.bg);
4113 			}
4114 		    }
4115 		}
4116 
4117 		prev_cell = cell;
4118 	    }
4119 
4120 	    if (cell.width == 2)
4121 		++pos.col;
4122 	}
4123 	if (repeat > 0)
4124 	    fprintf(fd, "@%d", repeat);
4125 	fputs("\n", fd);
4126     }
4127 
4128     fclose(fd);
4129 }
4130 
4131 /*
4132  * Called when a dump is corrupted.  Put a breakpoint here when debugging.
4133  */
4134     static void
4135 dump_is_corrupt(garray_T *gap)
4136 {
4137     ga_concat(gap, (char_u *)"CORRUPT");
4138 }
4139 
4140     static void
4141 append_cell(garray_T *gap, cellattr_T *cell)
4142 {
4143     if (ga_grow(gap, 1) == OK)
4144     {
4145 	*(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
4146 	++gap->ga_len;
4147     }
4148 }
4149 
4150 /*
4151  * Read the dump file from "fd" and append lines to the current buffer.
4152  * Return the cell width of the longest line.
4153  */
4154     static int
4155 read_dump_file(FILE *fd, VTermPos *cursor_pos)
4156 {
4157     int		    c;
4158     garray_T	    ga_text;
4159     garray_T	    ga_cell;
4160     char_u	    *prev_char = NULL;
4161     int		    attr = 0;
4162     cellattr_T	    cell;
4163     cellattr_T	    empty_cell;
4164     term_T	    *term = curbuf->b_term;
4165     int		    max_cells = 0;
4166     int		    start_row = term->tl_scrollback.ga_len;
4167 
4168     ga_init2(&ga_text, 1, 90);
4169     ga_init2(&ga_cell, sizeof(cellattr_T), 90);
4170     vim_memset(&cell, 0, sizeof(cell));
4171     vim_memset(&empty_cell, 0, sizeof(empty_cell));
4172     cursor_pos->row = -1;
4173     cursor_pos->col = -1;
4174 
4175     c = fgetc(fd);
4176     for (;;)
4177     {
4178 	if (c == EOF)
4179 	    break;
4180 	if (c == '\r')
4181 	{
4182 	    // DOS line endings?  Ignore.
4183 	    c = fgetc(fd);
4184 	}
4185 	else if (c == '\n')
4186 	{
4187 	    /* End of a line: append it to the buffer. */
4188 	    if (ga_text.ga_data == NULL)
4189 		dump_is_corrupt(&ga_text);
4190 	    if (ga_grow(&term->tl_scrollback, 1) == OK)
4191 	    {
4192 		sb_line_T   *line = (sb_line_T *)term->tl_scrollback.ga_data
4193 						  + term->tl_scrollback.ga_len;
4194 
4195 		if (max_cells < ga_cell.ga_len)
4196 		    max_cells = ga_cell.ga_len;
4197 		line->sb_cols = ga_cell.ga_len;
4198 		line->sb_cells = ga_cell.ga_data;
4199 		line->sb_fill_attr = term->tl_default_color;
4200 		++term->tl_scrollback.ga_len;
4201 		ga_init(&ga_cell);
4202 
4203 		ga_append(&ga_text, NUL);
4204 		ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4205 							ga_text.ga_len, FALSE);
4206 	    }
4207 	    else
4208 		ga_clear(&ga_cell);
4209 	    ga_text.ga_len = 0;
4210 
4211 	    c = fgetc(fd);
4212 	}
4213 	else if (c == '|' || c == '>')
4214 	{
4215 	    int prev_len = ga_text.ga_len;
4216 
4217 	    if (c == '>')
4218 	    {
4219 		if (cursor_pos->row != -1)
4220 		    dump_is_corrupt(&ga_text);	/* duplicate cursor */
4221 		cursor_pos->row = term->tl_scrollback.ga_len - start_row;
4222 		cursor_pos->col = ga_cell.ga_len;
4223 	    }
4224 
4225 	    /* normal character(s) followed by "+", "*", "|", "@" or NL */
4226 	    c = fgetc(fd);
4227 	    if (c != EOF)
4228 		ga_append(&ga_text, c);
4229 	    for (;;)
4230 	    {
4231 		c = fgetc(fd);
4232 		if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
4233 						      || c == EOF || c == '\n')
4234 		    break;
4235 		ga_append(&ga_text, c);
4236 	    }
4237 
4238 	    /* save the character for repeating it */
4239 	    vim_free(prev_char);
4240 	    if (ga_text.ga_data != NULL)
4241 		prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
4242 						    ga_text.ga_len - prev_len);
4243 
4244 	    if (c == '@' || c == '|' || c == '>' || c == '\n')
4245 	    {
4246 		/* use all attributes from previous cell */
4247 	    }
4248 	    else if (c == '+' || c == '*')
4249 	    {
4250 		int is_bg;
4251 
4252 		cell.width = c == '+' ? 1 : 2;
4253 
4254 		c = fgetc(fd);
4255 		if (c == '&')
4256 		{
4257 		    /* use same attr as previous cell */
4258 		    c = fgetc(fd);
4259 		}
4260 		else if (isdigit(c))
4261 		{
4262 		    /* get the decimal attribute */
4263 		    attr = 0;
4264 		    while (isdigit(c))
4265 		    {
4266 			attr = attr * 10 + (c - '0');
4267 			c = fgetc(fd);
4268 		    }
4269 		    hl2vtermAttr(attr, &cell);
4270 
4271 		    /* is_bg == 0: fg, is_bg == 1: bg */
4272 		    for (is_bg = 0; is_bg <= 1; ++is_bg)
4273 		    {
4274 			if (c == '&')
4275 			{
4276 			    /* use same color as previous cell */
4277 			    c = fgetc(fd);
4278 			}
4279 			else if (c == '#')
4280 			{
4281 			    int red, green, blue, index = 0;
4282 
4283 			    c = fgetc(fd);
4284 			    red = hex2nr(c);
4285 			    c = fgetc(fd);
4286 			    red = (red << 4) + hex2nr(c);
4287 			    c = fgetc(fd);
4288 			    green = hex2nr(c);
4289 			    c = fgetc(fd);
4290 			    green = (green << 4) + hex2nr(c);
4291 			    c = fgetc(fd);
4292 			    blue = hex2nr(c);
4293 			    c = fgetc(fd);
4294 			    blue = (blue << 4) + hex2nr(c);
4295 			    c = fgetc(fd);
4296 			    if (!isdigit(c))
4297 				dump_is_corrupt(&ga_text);
4298 			    while (isdigit(c))
4299 			    {
4300 				index = index * 10 + (c - '0');
4301 				c = fgetc(fd);
4302 			    }
4303 
4304 			    if (is_bg)
4305 			    {
4306 				cell.bg.red = red;
4307 				cell.bg.green = green;
4308 				cell.bg.blue = blue;
4309 				cell.bg.ansi_index = index;
4310 			    }
4311 			    else
4312 			    {
4313 				cell.fg.red = red;
4314 				cell.fg.green = green;
4315 				cell.fg.blue = blue;
4316 				cell.fg.ansi_index = index;
4317 			    }
4318 			}
4319 			else
4320 			    dump_is_corrupt(&ga_text);
4321 		    }
4322 		}
4323 		else
4324 		    dump_is_corrupt(&ga_text);
4325 	    }
4326 	    else
4327 		dump_is_corrupt(&ga_text);
4328 
4329 	    append_cell(&ga_cell, &cell);
4330 	    if (cell.width == 2)
4331 		append_cell(&ga_cell, &empty_cell);
4332 	}
4333 	else if (c == '@')
4334 	{
4335 	    if (prev_char == NULL)
4336 		dump_is_corrupt(&ga_text);
4337 	    else
4338 	    {
4339 		int count = 0;
4340 
4341 		/* repeat previous character, get the count */
4342 		for (;;)
4343 		{
4344 		    c = fgetc(fd);
4345 		    if (!isdigit(c))
4346 			break;
4347 		    count = count * 10 + (c - '0');
4348 		}
4349 
4350 		while (count-- > 0)
4351 		{
4352 		    ga_concat(&ga_text, prev_char);
4353 		    append_cell(&ga_cell, &cell);
4354 		}
4355 	    }
4356 	}
4357 	else
4358 	{
4359 	    dump_is_corrupt(&ga_text);
4360 	    c = fgetc(fd);
4361 	}
4362     }
4363 
4364     if (ga_text.ga_len > 0)
4365     {
4366 	/* trailing characters after last NL */
4367 	dump_is_corrupt(&ga_text);
4368 	ga_append(&ga_text, NUL);
4369 	ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4370 							ga_text.ga_len, FALSE);
4371     }
4372 
4373     ga_clear(&ga_text);
4374     vim_free(prev_char);
4375 
4376     return max_cells;
4377 }
4378 
4379 /*
4380  * Return an allocated string with at least "text_width" "=" characters and
4381  * "fname" inserted in the middle.
4382  */
4383     static char_u *
4384 get_separator(int text_width, char_u *fname)
4385 {
4386     int	    width = MAX(text_width, curwin->w_width);
4387     char_u  *textline;
4388     int	    fname_size;
4389     char_u  *p = fname;
4390     int	    i;
4391     size_t  off;
4392 
4393     textline = alloc(width + (int)STRLEN(fname) + 1);
4394     if (textline == NULL)
4395 	return NULL;
4396 
4397     fname_size = vim_strsize(fname);
4398     if (fname_size < width - 8)
4399     {
4400 	/* enough room, don't use the full window width */
4401 	width = MAX(text_width, fname_size + 8);
4402     }
4403     else if (fname_size > width - 8)
4404     {
4405 	/* full name doesn't fit, use only the tail */
4406 	p = gettail(fname);
4407 	fname_size = vim_strsize(p);
4408     }
4409     /* skip characters until the name fits */
4410     while (fname_size > width - 8)
4411     {
4412 	p += (*mb_ptr2len)(p);
4413 	fname_size = vim_strsize(p);
4414     }
4415 
4416     for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4417 	textline[i] = '=';
4418     textline[i++] = ' ';
4419 
4420     STRCPY(textline + i, p);
4421     off = STRLEN(textline);
4422     textline[off] = ' ';
4423     for (i = 1; i < (width - fname_size) / 2; ++i)
4424 	textline[off + i] = '=';
4425     textline[off + i] = NUL;
4426 
4427     return textline;
4428 }
4429 
4430 /*
4431  * Common for "term_dumpdiff()" and "term_dumpload()".
4432  */
4433     static void
4434 term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4435 {
4436     jobopt_T	opt;
4437     buf_T	*buf;
4438     char_u	buf1[NUMBUFLEN];
4439     char_u	buf2[NUMBUFLEN];
4440     char_u	*fname1;
4441     char_u	*fname2 = NULL;
4442     char_u	*fname_tofree = NULL;
4443     FILE	*fd1;
4444     FILE	*fd2 = NULL;
4445     char_u	*textline = NULL;
4446 
4447     /* First open the files.  If this fails bail out. */
4448     fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
4449     if (do_diff)
4450 	fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
4451     if (fname1 == NULL || (do_diff && fname2 == NULL))
4452     {
4453 	emsg(_(e_invarg));
4454 	return;
4455     }
4456     fd1 = mch_fopen((char *)fname1, READBIN);
4457     if (fd1 == NULL)
4458     {
4459 	semsg(_(e_notread), fname1);
4460 	return;
4461     }
4462     if (do_diff)
4463     {
4464 	fd2 = mch_fopen((char *)fname2, READBIN);
4465 	if (fd2 == NULL)
4466 	{
4467 	    fclose(fd1);
4468 	    semsg(_(e_notread), fname2);
4469 	    return;
4470 	}
4471     }
4472 
4473     init_job_options(&opt);
4474     if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4475 	    && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4476 		    JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4477 		    + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4478 	goto theend;
4479 
4480     if (opt.jo_term_name == NULL)
4481     {
4482 	size_t len = STRLEN(fname1) + 12;
4483 
4484 	fname_tofree = alloc((int)len);
4485 	if (fname_tofree != NULL)
4486 	{
4487 	    vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4488 	    opt.jo_term_name = fname_tofree;
4489 	}
4490     }
4491 
4492     buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
4493     if (buf != NULL && buf->b_term != NULL)
4494     {
4495 	int		i;
4496 	linenr_T	bot_lnum;
4497 	linenr_T	lnum;
4498 	term_T		*term = buf->b_term;
4499 	int		width;
4500 	int		width2;
4501 	VTermPos	cursor_pos1;
4502 	VTermPos	cursor_pos2;
4503 
4504 	init_default_colors(term);
4505 
4506 	rettv->vval.v_number = buf->b_fnum;
4507 
4508 	/* read the files, fill the buffer with the diff */
4509 	width = read_dump_file(fd1, &cursor_pos1);
4510 
4511 	/* position the cursor */
4512 	if (cursor_pos1.row >= 0)
4513 	{
4514 	    curwin->w_cursor.lnum = cursor_pos1.row + 1;
4515 	    coladvance(cursor_pos1.col);
4516 	}
4517 
4518 	/* Delete the empty line that was in the empty buffer. */
4519 	ml_delete(1, FALSE);
4520 
4521 	/* For term_dumpload() we are done here. */
4522 	if (!do_diff)
4523 	    goto theend;
4524 
4525 	term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4526 
4527 	textline = get_separator(width, fname1);
4528 	if (textline == NULL)
4529 	    goto theend;
4530 	if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4531 	    ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4532 	vim_free(textline);
4533 
4534 	textline = get_separator(width, fname2);
4535 	if (textline == NULL)
4536 	    goto theend;
4537 	if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4538 	    ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4539 	textline[width] = NUL;
4540 
4541 	bot_lnum = curbuf->b_ml.ml_line_count;
4542 	width2 = read_dump_file(fd2, &cursor_pos2);
4543 	if (width2 > width)
4544 	{
4545 	    vim_free(textline);
4546 	    textline = alloc(width2 + 1);
4547 	    if (textline == NULL)
4548 		goto theend;
4549 	    width = width2;
4550 	    textline[width] = NUL;
4551 	}
4552 	term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4553 
4554 	for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4555 	{
4556 	    if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4557 	    {
4558 		/* bottom part has fewer rows, fill with "-" */
4559 		for (i = 0; i < width; ++i)
4560 		    textline[i] = '-';
4561 	    }
4562 	    else
4563 	    {
4564 		char_u *line1;
4565 		char_u *line2;
4566 		char_u *p1;
4567 		char_u *p2;
4568 		int	col;
4569 		sb_line_T   *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4570 		cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4571 		cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4572 								    ->sb_cells;
4573 
4574 		/* Make a copy, getting the second line will invalidate it. */
4575 		line1 = vim_strsave(ml_get(lnum));
4576 		if (line1 == NULL)
4577 		    break;
4578 		p1 = line1;
4579 
4580 		line2 = ml_get(lnum + bot_lnum);
4581 		p2 = line2;
4582 		for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4583 		{
4584 		    int len1 = utfc_ptr2len(p1);
4585 		    int len2 = utfc_ptr2len(p2);
4586 
4587 		    textline[col] = ' ';
4588 		    if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
4589 			/* text differs */
4590 			textline[col] = 'X';
4591 		    else if (lnum == cursor_pos1.row + 1
4592 			    && col == cursor_pos1.col
4593 			    && (cursor_pos1.row != cursor_pos2.row
4594 					|| cursor_pos1.col != cursor_pos2.col))
4595 			/* cursor in first but not in second */
4596 			textline[col] = '>';
4597 		    else if (lnum == cursor_pos2.row + 1
4598 			    && col == cursor_pos2.col
4599 			    && (cursor_pos1.row != cursor_pos2.row
4600 					|| cursor_pos1.col != cursor_pos2.col))
4601 			/* cursor in second but not in first */
4602 			textline[col] = '<';
4603 		    else if (cellattr1 != NULL && cellattr2 != NULL)
4604 		    {
4605 			if ((cellattr1 + col)->width
4606 						   != (cellattr2 + col)->width)
4607 			    textline[col] = 'w';
4608 			else if (!same_color(&(cellattr1 + col)->fg,
4609 						   &(cellattr2 + col)->fg))
4610 			    textline[col] = 'f';
4611 			else if (!same_color(&(cellattr1 + col)->bg,
4612 						   &(cellattr2 + col)->bg))
4613 			    textline[col] = 'b';
4614 			else if (vtermAttr2hl((cellattr1 + col)->attrs)
4615 				   != vtermAttr2hl(((cellattr2 + col)->attrs)))
4616 			    textline[col] = 'a';
4617 		    }
4618 		    p1 += len1;
4619 		    p2 += len2;
4620 		    /* TODO: handle different width */
4621 		}
4622 		vim_free(line1);
4623 
4624 		while (col < width)
4625 		{
4626 		    if (*p1 == NUL && *p2 == NUL)
4627 			textline[col] = '?';
4628 		    else if (*p1 == NUL)
4629 		    {
4630 			textline[col] = '+';
4631 			p2 += utfc_ptr2len(p2);
4632 		    }
4633 		    else
4634 		    {
4635 			textline[col] = '-';
4636 			p1 += utfc_ptr2len(p1);
4637 		    }
4638 		    ++col;
4639 		}
4640 	    }
4641 	    if (add_empty_scrollback(term, &term->tl_default_color,
4642 						 term->tl_top_diff_rows) == OK)
4643 		ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4644 	    ++bot_lnum;
4645 	}
4646 
4647 	while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4648 	{
4649 	    /* bottom part has more rows, fill with "+" */
4650 	    for (i = 0; i < width; ++i)
4651 		textline[i] = '+';
4652 	    if (add_empty_scrollback(term, &term->tl_default_color,
4653 						 term->tl_top_diff_rows) == OK)
4654 		ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4655 	    ++lnum;
4656 	    ++bot_lnum;
4657 	}
4658 
4659 	term->tl_cols = width;
4660 
4661 	/* looks better without wrapping */
4662 	curwin->w_p_wrap = 0;
4663     }
4664 
4665 theend:
4666     vim_free(textline);
4667     vim_free(fname_tofree);
4668     fclose(fd1);
4669     if (fd2 != NULL)
4670 	fclose(fd2);
4671 }
4672 
4673 /*
4674  * If the current buffer shows the output of term_dumpdiff(), swap the top and
4675  * bottom files.
4676  * Return FAIL when this is not possible.
4677  */
4678     int
4679 term_swap_diff()
4680 {
4681     term_T	*term = curbuf->b_term;
4682     linenr_T	line_count;
4683     linenr_T	top_rows;
4684     linenr_T	bot_rows;
4685     linenr_T	bot_start;
4686     linenr_T	lnum;
4687     char_u	*p;
4688     sb_line_T	*sb_line;
4689 
4690     if (term == NULL
4691 	    || !term_is_finished(curbuf)
4692 	    || term->tl_top_diff_rows == 0
4693 	    || term->tl_scrollback.ga_len == 0)
4694 	return FAIL;
4695 
4696     line_count = curbuf->b_ml.ml_line_count;
4697     top_rows = term->tl_top_diff_rows;
4698     bot_rows = term->tl_bot_diff_rows;
4699     bot_start = line_count - bot_rows;
4700     sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4701 
4702     /* move lines from top to above the bottom part */
4703     for (lnum = 1; lnum <= top_rows; ++lnum)
4704     {
4705 	p = vim_strsave(ml_get(1));
4706 	if (p == NULL)
4707 	    return OK;
4708 	ml_append(bot_start, p, 0, FALSE);
4709 	ml_delete(1, FALSE);
4710 	vim_free(p);
4711     }
4712 
4713     /* move lines from bottom to the top */
4714     for (lnum = 1; lnum <= bot_rows; ++lnum)
4715     {
4716 	p = vim_strsave(ml_get(bot_start + lnum));
4717 	if (p == NULL)
4718 	    return OK;
4719 	ml_delete(bot_start + lnum, FALSE);
4720 	ml_append(lnum - 1, p, 0, FALSE);
4721 	vim_free(p);
4722     }
4723 
4724     if (top_rows == bot_rows)
4725     {
4726 	/* rows counts are equal, can swap cell properties */
4727 	for (lnum = 0; lnum < top_rows; ++lnum)
4728 	{
4729 	    sb_line_T	temp;
4730 
4731 	    temp = *(sb_line + lnum);
4732 	    *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4733 	    *(sb_line + bot_start + lnum) = temp;
4734 	}
4735     }
4736     else
4737     {
4738 	size_t		size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4739 	sb_line_T	*temp = (sb_line_T *)alloc((int)size);
4740 
4741 	/* need to copy cell properties into temp memory */
4742 	if (temp != NULL)
4743 	{
4744 	    mch_memmove(temp, term->tl_scrollback.ga_data, size);
4745 	    mch_memmove(term->tl_scrollback.ga_data,
4746 		    temp + bot_start,
4747 		    sizeof(sb_line_T) * bot_rows);
4748 	    mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4749 		    temp + top_rows,
4750 		    sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4751 	    mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4752 						       + line_count - top_rows,
4753 		    temp,
4754 		    sizeof(sb_line_T) * top_rows);
4755 	    vim_free(temp);
4756 	}
4757     }
4758 
4759     term->tl_top_diff_rows = bot_rows;
4760     term->tl_bot_diff_rows = top_rows;
4761 
4762     update_screen(NOT_VALID);
4763     return OK;
4764 }
4765 
4766 /*
4767  * "term_dumpdiff(filename, filename, options)" function
4768  */
4769     void
4770 f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4771 {
4772     term_load_dump(argvars, rettv, TRUE);
4773 }
4774 
4775 /*
4776  * "term_dumpload(filename, options)" function
4777  */
4778     void
4779 f_term_dumpload(typval_T *argvars, typval_T *rettv)
4780 {
4781     term_load_dump(argvars, rettv, FALSE);
4782 }
4783 
4784 /*
4785  * "term_getaltscreen(buf)" function
4786  */
4787     void
4788 f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4789 {
4790     buf_T	*buf = term_get_buf(argvars, "term_getaltscreen()");
4791 
4792     if (buf == NULL)
4793 	return;
4794     rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4795 }
4796 
4797 /*
4798  * "term_getattr(attr, name)" function
4799  */
4800     void
4801 f_term_getattr(typval_T *argvars, typval_T *rettv)
4802 {
4803     int	    attr;
4804     size_t  i;
4805     char_u  *name;
4806 
4807     static struct {
4808 	char	    *name;
4809 	int	    attr;
4810     } attrs[] = {
4811 	{"bold",      HL_BOLD},
4812 	{"italic",    HL_ITALIC},
4813 	{"underline", HL_UNDERLINE},
4814 	{"strike",    HL_STRIKETHROUGH},
4815 	{"reverse",   HL_INVERSE},
4816     };
4817 
4818     attr = tv_get_number(&argvars[0]);
4819     name = tv_get_string_chk(&argvars[1]);
4820     if (name == NULL)
4821 	return;
4822 
4823     for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4824 	if (STRCMP(name, attrs[i].name) == 0)
4825 	{
4826 	    rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4827 	    break;
4828 	}
4829 }
4830 
4831 /*
4832  * "term_getcursor(buf)" function
4833  */
4834     void
4835 f_term_getcursor(typval_T *argvars, typval_T *rettv)
4836 {
4837     buf_T	*buf = term_get_buf(argvars, "term_getcursor()");
4838     term_T	*term;
4839     list_T	*l;
4840     dict_T	*d;
4841 
4842     if (rettv_list_alloc(rettv) == FAIL)
4843 	return;
4844     if (buf == NULL)
4845 	return;
4846     term = buf->b_term;
4847 
4848     l = rettv->vval.v_list;
4849     list_append_number(l, term->tl_cursor_pos.row + 1);
4850     list_append_number(l, term->tl_cursor_pos.col + 1);
4851 
4852     d = dict_alloc();
4853     if (d != NULL)
4854     {
4855 	dict_add_number(d, "visible", term->tl_cursor_visible);
4856 	dict_add_number(d, "blink", blink_state_is_inverted()
4857 			    ? !term->tl_cursor_blink : term->tl_cursor_blink);
4858 	dict_add_number(d, "shape", term->tl_cursor_shape);
4859 	dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color));
4860 	list_append_dict(l, d);
4861     }
4862 }
4863 
4864 /*
4865  * "term_getjob(buf)" function
4866  */
4867     void
4868 f_term_getjob(typval_T *argvars, typval_T *rettv)
4869 {
4870     buf_T	*buf = term_get_buf(argvars, "term_getjob()");
4871 
4872     if (buf == NULL)
4873     {
4874 	rettv->v_type = VAR_SPECIAL;
4875 	rettv->vval.v_number = VVAL_NULL;
4876 	return;
4877     }
4878 
4879     rettv->v_type = VAR_JOB;
4880     rettv->vval.v_job = buf->b_term->tl_job;
4881     if (rettv->vval.v_job != NULL)
4882 	++rettv->vval.v_job->jv_refcount;
4883 }
4884 
4885     static int
4886 get_row_number(typval_T *tv, term_T *term)
4887 {
4888     if (tv->v_type == VAR_STRING
4889 	    && tv->vval.v_string != NULL
4890 	    && STRCMP(tv->vval.v_string, ".") == 0)
4891 	return term->tl_cursor_pos.row;
4892     return (int)tv_get_number(tv) - 1;
4893 }
4894 
4895 /*
4896  * "term_getline(buf, row)" function
4897  */
4898     void
4899 f_term_getline(typval_T *argvars, typval_T *rettv)
4900 {
4901     buf_T	    *buf = term_get_buf(argvars, "term_getline()");
4902     term_T	    *term;
4903     int		    row;
4904 
4905     rettv->v_type = VAR_STRING;
4906     if (buf == NULL)
4907 	return;
4908     term = buf->b_term;
4909     row = get_row_number(&argvars[1], term);
4910 
4911     if (term->tl_vterm == NULL)
4912     {
4913 	linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4914 
4915 	/* vterm is finished, get the text from the buffer */
4916 	if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4917 	    rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4918     }
4919     else
4920     {
4921 	VTermScreen	*screen = vterm_obtain_screen(term->tl_vterm);
4922 	VTermRect	rect;
4923 	int		len;
4924 	char_u		*p;
4925 
4926 	if (row < 0 || row >= term->tl_rows)
4927 	    return;
4928 	len = term->tl_cols * MB_MAXBYTES + 1;
4929 	p = alloc(len);
4930 	if (p == NULL)
4931 	    return;
4932 	rettv->vval.v_string = p;
4933 
4934 	rect.start_col = 0;
4935 	rect.end_col = term->tl_cols;
4936 	rect.start_row = row;
4937 	rect.end_row = row + 1;
4938 	p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4939     }
4940 }
4941 
4942 /*
4943  * "term_getscrolled(buf)" function
4944  */
4945     void
4946 f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4947 {
4948     buf_T	*buf = term_get_buf(argvars, "term_getscrolled()");
4949 
4950     if (buf == NULL)
4951 	return;
4952     rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4953 }
4954 
4955 /*
4956  * "term_getsize(buf)" function
4957  */
4958     void
4959 f_term_getsize(typval_T *argvars, typval_T *rettv)
4960 {
4961     buf_T	*buf = term_get_buf(argvars, "term_getsize()");
4962     list_T	*l;
4963 
4964     if (rettv_list_alloc(rettv) == FAIL)
4965 	return;
4966     if (buf == NULL)
4967 	return;
4968 
4969     l = rettv->vval.v_list;
4970     list_append_number(l, buf->b_term->tl_rows);
4971     list_append_number(l, buf->b_term->tl_cols);
4972 }
4973 
4974 /*
4975  * "term_setsize(buf, rows, cols)" function
4976  */
4977     void
4978 f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4979 {
4980     buf_T	*buf = term_get_buf(argvars, "term_setsize()");
4981     term_T	*term;
4982     varnumber_T rows, cols;
4983 
4984     if (buf == NULL)
4985     {
4986 	emsg(_("E955: Not a terminal buffer"));
4987 	return;
4988     }
4989     if (buf->b_term->tl_vterm == NULL)
4990 	return;
4991     term = buf->b_term;
4992     rows = tv_get_number(&argvars[1]);
4993     rows = rows <= 0 ? term->tl_rows : rows;
4994     cols = tv_get_number(&argvars[2]);
4995     cols = cols <= 0 ? term->tl_cols : cols;
4996     vterm_set_size(term->tl_vterm, rows, cols);
4997     /* handle_resize() will resize the windows */
4998 
4999     /* Get and remember the size we ended up with.  Update the pty. */
5000     vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
5001     term_report_winsize(term, term->tl_rows, term->tl_cols);
5002 }
5003 
5004 /*
5005  * "term_getstatus(buf)" function
5006  */
5007     void
5008 f_term_getstatus(typval_T *argvars, typval_T *rettv)
5009 {
5010     buf_T	*buf = term_get_buf(argvars, "term_getstatus()");
5011     term_T	*term;
5012     char_u	val[100];
5013 
5014     rettv->v_type = VAR_STRING;
5015     if (buf == NULL)
5016 	return;
5017     term = buf->b_term;
5018 
5019     if (term_job_running(term))
5020 	STRCPY(val, "running");
5021     else
5022 	STRCPY(val, "finished");
5023     if (term->tl_normal_mode)
5024 	STRCAT(val, ",normal");
5025     rettv->vval.v_string = vim_strsave(val);
5026 }
5027 
5028 /*
5029  * "term_gettitle(buf)" function
5030  */
5031     void
5032 f_term_gettitle(typval_T *argvars, typval_T *rettv)
5033 {
5034     buf_T	*buf = term_get_buf(argvars, "term_gettitle()");
5035 
5036     rettv->v_type = VAR_STRING;
5037     if (buf == NULL)
5038 	return;
5039 
5040     if (buf->b_term->tl_title != NULL)
5041 	rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
5042 }
5043 
5044 /*
5045  * "term_gettty(buf)" function
5046  */
5047     void
5048 f_term_gettty(typval_T *argvars, typval_T *rettv)
5049 {
5050     buf_T	*buf = term_get_buf(argvars, "term_gettty()");
5051     char_u	*p = NULL;
5052     int		num = 0;
5053 
5054     rettv->v_type = VAR_STRING;
5055     if (buf == NULL)
5056 	return;
5057     if (argvars[1].v_type != VAR_UNKNOWN)
5058 	num = tv_get_number(&argvars[1]);
5059 
5060     switch (num)
5061     {
5062 	case 0:
5063 	    if (buf->b_term->tl_job != NULL)
5064 		p = buf->b_term->tl_job->jv_tty_out;
5065 	    break;
5066 	case 1:
5067 	    if (buf->b_term->tl_job != NULL)
5068 		p = buf->b_term->tl_job->jv_tty_in;
5069 	    break;
5070 	default:
5071 	    semsg(_(e_invarg2), tv_get_string(&argvars[1]));
5072 	    return;
5073     }
5074     if (p != NULL)
5075 	rettv->vval.v_string = vim_strsave(p);
5076 }
5077 
5078 /*
5079  * "term_list()" function
5080  */
5081     void
5082 f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
5083 {
5084     term_T	*tp;
5085     list_T	*l;
5086 
5087     if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
5088 	return;
5089 
5090     l = rettv->vval.v_list;
5091     for (tp = first_term; tp != NULL; tp = tp->tl_next)
5092 	if (tp != NULL && tp->tl_buffer != NULL)
5093 	    if (list_append_number(l,
5094 				   (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
5095 		return;
5096 }
5097 
5098 /*
5099  * "term_scrape(buf, row)" function
5100  */
5101     void
5102 f_term_scrape(typval_T *argvars, typval_T *rettv)
5103 {
5104     buf_T	    *buf = term_get_buf(argvars, "term_scrape()");
5105     VTermScreen	    *screen = NULL;
5106     VTermPos	    pos;
5107     list_T	    *l;
5108     term_T	    *term;
5109     char_u	    *p;
5110     sb_line_T	    *line;
5111 
5112     if (rettv_list_alloc(rettv) == FAIL)
5113 	return;
5114     if (buf == NULL)
5115 	return;
5116     term = buf->b_term;
5117 
5118     l = rettv->vval.v_list;
5119     pos.row = get_row_number(&argvars[1], term);
5120 
5121     if (term->tl_vterm != NULL)
5122     {
5123 	screen = vterm_obtain_screen(term->tl_vterm);
5124 	if (screen == NULL)  // can't really happen
5125 	    return;
5126 	p = NULL;
5127 	line = NULL;
5128     }
5129     else
5130     {
5131 	linenr_T	lnum = pos.row + term->tl_scrollback_scrolled;
5132 
5133 	if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
5134 	    return;
5135 	p = ml_get_buf(buf, lnum + 1, FALSE);
5136 	line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
5137     }
5138 
5139     for (pos.col = 0; pos.col < term->tl_cols; )
5140     {
5141 	dict_T		*dcell;
5142 	int		width;
5143 	VTermScreenCellAttrs attrs;
5144 	VTermColor	fg, bg;
5145 	char_u		rgb[8];
5146 	char_u		mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
5147 	int		off = 0;
5148 	int		i;
5149 
5150 	if (screen == NULL)
5151 	{
5152 	    cellattr_T	*cellattr;
5153 	    int		len;
5154 
5155 	    /* vterm has finished, get the cell from scrollback */
5156 	    if (pos.col >= line->sb_cols)
5157 		break;
5158 	    cellattr = line->sb_cells + pos.col;
5159 	    width = cellattr->width;
5160 	    attrs = cellattr->attrs;
5161 	    fg = cellattr->fg;
5162 	    bg = cellattr->bg;
5163 	    len = MB_PTR2LEN(p);
5164 	    mch_memmove(mbs, p, len);
5165 	    mbs[len] = NUL;
5166 	    p += len;
5167 	}
5168 	else
5169 	{
5170 	    VTermScreenCell cell;
5171 	    if (vterm_screen_get_cell(screen, pos, &cell) == 0)
5172 		break;
5173 	    for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5174 	    {
5175 		if (cell.chars[i] == 0)
5176 		    break;
5177 		off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
5178 	    }
5179 	    mbs[off] = NUL;
5180 	    width = cell.width;
5181 	    attrs = cell.attrs;
5182 	    fg = cell.fg;
5183 	    bg = cell.bg;
5184 	}
5185 	dcell = dict_alloc();
5186 	if (dcell == NULL)
5187 	    break;
5188 	list_append_dict(l, dcell);
5189 
5190 	dict_add_string(dcell, "chars", mbs);
5191 
5192 	vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5193 				     fg.red, fg.green, fg.blue);
5194 	dict_add_string(dcell, "fg", rgb);
5195 	vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5196 				     bg.red, bg.green, bg.blue);
5197 	dict_add_string(dcell, "bg", rgb);
5198 
5199 	dict_add_number(dcell, "attr", cell2attr(attrs, fg, bg));
5200 	dict_add_number(dcell, "width", width);
5201 
5202 	++pos.col;
5203 	if (width == 2)
5204 	    ++pos.col;
5205     }
5206 }
5207 
5208 /*
5209  * "term_sendkeys(buf, keys)" function
5210  */
5211     void
5212 f_term_sendkeys(typval_T *argvars, typval_T *rettv)
5213 {
5214     buf_T	*buf = term_get_buf(argvars, "term_sendkeys()");
5215     char_u	*msg;
5216     term_T	*term;
5217 
5218     rettv->v_type = VAR_UNKNOWN;
5219     if (buf == NULL)
5220 	return;
5221 
5222     msg = tv_get_string_chk(&argvars[1]);
5223     if (msg == NULL)
5224 	return;
5225     term = buf->b_term;
5226     if (term->tl_vterm == NULL)
5227 	return;
5228 
5229     while (*msg != NUL)
5230     {
5231 	int c;
5232 
5233 	if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL)
5234 	{
5235 	    c = TO_SPECIAL(msg[1], msg[2]);
5236 	    msg += 3;
5237 	}
5238 	else
5239 	{
5240 	    c = PTR2CHAR(msg);
5241 	    msg += MB_CPTR2LEN(msg);
5242 	}
5243 	send_keys_to_term(term, c, FALSE);
5244     }
5245 }
5246 
5247 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
5248 /*
5249  * "term_getansicolors(buf)" function
5250  */
5251     void
5252 f_term_getansicolors(typval_T *argvars, typval_T *rettv)
5253 {
5254     buf_T	*buf = term_get_buf(argvars, "term_getansicolors()");
5255     term_T	*term;
5256     VTermState	*state;
5257     VTermColor  color;
5258     char_u	hexbuf[10];
5259     int		index;
5260     list_T	*list;
5261 
5262     if (rettv_list_alloc(rettv) == FAIL)
5263 	return;
5264 
5265     if (buf == NULL)
5266 	return;
5267     term = buf->b_term;
5268     if (term->tl_vterm == NULL)
5269 	return;
5270 
5271     list = rettv->vval.v_list;
5272     state = vterm_obtain_state(term->tl_vterm);
5273     for (index = 0; index < 16; index++)
5274     {
5275 	vterm_state_get_palette_color(state, index, &color);
5276 	sprintf((char *)hexbuf, "#%02x%02x%02x",
5277 		color.red, color.green, color.blue);
5278 	if (list_append_string(list, hexbuf, 7) == FAIL)
5279 	    return;
5280     }
5281 }
5282 
5283 /*
5284  * "term_setansicolors(buf, list)" function
5285  */
5286     void
5287 f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
5288 {
5289     buf_T	*buf = term_get_buf(argvars, "term_setansicolors()");
5290     term_T	*term;
5291 
5292     if (buf == NULL)
5293 	return;
5294     term = buf->b_term;
5295     if (term->tl_vterm == NULL)
5296 	return;
5297 
5298     if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
5299     {
5300 	emsg(_(e_listreq));
5301 	return;
5302     }
5303 
5304     if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
5305 	emsg(_(e_invarg));
5306 }
5307 #endif
5308 
5309 /*
5310  * "term_setrestore(buf, command)" function
5311  */
5312     void
5313 f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5314 {
5315 #if defined(FEAT_SESSION)
5316     buf_T	*buf = term_get_buf(argvars, "term_setrestore()");
5317     term_T	*term;
5318     char_u	*cmd;
5319 
5320     if (buf == NULL)
5321 	return;
5322     term = buf->b_term;
5323     vim_free(term->tl_command);
5324     cmd = tv_get_string_chk(&argvars[1]);
5325     if (cmd != NULL)
5326 	term->tl_command = vim_strsave(cmd);
5327     else
5328 	term->tl_command = NULL;
5329 #endif
5330 }
5331 
5332 /*
5333  * "term_setkill(buf, how)" function
5334  */
5335     void
5336 f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5337 {
5338     buf_T	*buf = term_get_buf(argvars, "term_setkill()");
5339     term_T	*term;
5340     char_u	*how;
5341 
5342     if (buf == NULL)
5343 	return;
5344     term = buf->b_term;
5345     vim_free(term->tl_kill);
5346     how = tv_get_string_chk(&argvars[1]);
5347     if (how != NULL)
5348 	term->tl_kill = vim_strsave(how);
5349     else
5350 	term->tl_kill = NULL;
5351 }
5352 
5353 /*
5354  * "term_start(command, options)" function
5355  */
5356     void
5357 f_term_start(typval_T *argvars, typval_T *rettv)
5358 {
5359     jobopt_T	opt;
5360     buf_T	*buf;
5361 
5362     init_job_options(&opt);
5363     if (argvars[1].v_type != VAR_UNKNOWN
5364 	    && get_job_options(&argvars[1], &opt,
5365 		JO_TIMEOUT_ALL + JO_STOPONEXIT
5366 		    + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5367 		    + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5368 		JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5369 		    + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
5370 		    + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
5371 		    + JO2_NORESTORE + JO2_TERM_KILL
5372 		    + JO2_ANSI_COLORS + JO2_TERM_MODE) == FAIL)
5373 	return;
5374 
5375     buf = term_start(&argvars[0], NULL, &opt, 0);
5376 
5377     if (buf != NULL && buf->b_term != NULL)
5378 	rettv->vval.v_number = buf->b_fnum;
5379 }
5380 
5381 /*
5382  * "term_wait" function
5383  */
5384     void
5385 f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5386 {
5387     buf_T	*buf = term_get_buf(argvars, "term_wait()");
5388 
5389     if (buf == NULL)
5390 	return;
5391     if (buf->b_term->tl_job == NULL)
5392     {
5393 	ch_log(NULL, "term_wait(): no job to wait for");
5394 	return;
5395     }
5396     if (buf->b_term->tl_job->jv_channel == NULL)
5397 	/* channel is closed, nothing to do */
5398 	return;
5399 
5400     /* Get the job status, this will detect a job that finished. */
5401     if (!buf->b_term->tl_job->jv_channel->ch_keep_open
5402 	    && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5403     {
5404 	/* The job is dead, keep reading channel I/O until the channel is
5405 	 * closed. buf->b_term may become NULL if the terminal was closed while
5406 	 * waiting. */
5407 	ch_log(NULL, "term_wait(): waiting for channel to close");
5408 	while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5409 	{
5410 	    mch_check_messages();
5411 	    parse_queued_messages();
5412 	    ui_delay(10L, FALSE);
5413 	    if (!buf_valid(buf))
5414 		/* If the terminal is closed when the channel is closed the
5415 		 * buffer disappears. */
5416 		break;
5417 	}
5418 	mch_check_messages();
5419 	parse_queued_messages();
5420     }
5421     else
5422     {
5423 	long wait = 10L;
5424 
5425 	mch_check_messages();
5426 	parse_queued_messages();
5427 
5428 	/* Wait for some time for any channel I/O. */
5429 	if (argvars[1].v_type != VAR_UNKNOWN)
5430 	    wait = tv_get_number(&argvars[1]);
5431 	ui_delay(wait, TRUE);
5432 	mch_check_messages();
5433 
5434 	/* Flushing messages on channels is hopefully sufficient.
5435 	 * TODO: is there a better way? */
5436 	parse_queued_messages();
5437     }
5438 }
5439 
5440 /*
5441  * Called when a channel has sent all the lines to a terminal.
5442  * Send a CTRL-D to mark the end of the text.
5443  */
5444     void
5445 term_send_eof(channel_T *ch)
5446 {
5447     term_T	*term;
5448 
5449     for (term = first_term; term != NULL; term = term->tl_next)
5450 	if (term->tl_job == ch->ch_job)
5451 	{
5452 	    if (term->tl_eof_chars != NULL)
5453 	    {
5454 		channel_send(ch, PART_IN, term->tl_eof_chars,
5455 					(int)STRLEN(term->tl_eof_chars), NULL);
5456 		channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5457 	    }
5458 # ifdef WIN3264
5459 	    else
5460 		/* Default: CTRL-D */
5461 		channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5462 # endif
5463 	}
5464 }
5465 
5466 #if defined(FEAT_GUI) || defined(PROTO)
5467     job_T *
5468 term_getjob(term_T *term)
5469 {
5470     return term != NULL ? term->tl_job : NULL;
5471 }
5472 #endif
5473 
5474 # if defined(WIN3264) || defined(PROTO)
5475 
5476 /**************************************
5477  * 2. MS-Windows implementation.
5478  */
5479 
5480 HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
5481 HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
5482 HRESULT (WINAPI *pClosePseudoConsole)(HPCON);
5483 BOOL (*pInitializeProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
5484 BOOL (*pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
5485 void (*pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST);
5486 
5487     static int
5488 dyn_conpty_init(int verbose)
5489 {
5490     static BOOL	handled = FALSE;
5491     static int	result;
5492     HMODULE	hKerneldll;
5493     int		i;
5494     static struct
5495     {
5496 	char	*name;
5497 	FARPROC	*ptr;
5498     } conpty_entry[] =
5499     {
5500 	{"CreatePseudoConsole", (FARPROC*)&pCreatePseudoConsole},
5501 	{"ResizePseudoConsole", (FARPROC*)&pResizePseudoConsole},
5502 	{"ClosePseudoConsole", (FARPROC*)&pClosePseudoConsole},
5503 	{"InitializeProcThreadAttributeList",
5504 				(FARPROC*)&pInitializeProcThreadAttributeList},
5505 	{"UpdateProcThreadAttribute",
5506 				(FARPROC*)&pUpdateProcThreadAttribute},
5507 	{"DeleteProcThreadAttributeList",
5508 				(FARPROC*)&pDeleteProcThreadAttributeList},
5509 	{NULL, NULL}
5510     };
5511 
5512     if (handled)
5513 	return result;
5514 
5515     if (!has_vtp_working())
5516     {
5517 	handled = TRUE;
5518 	result = FAIL;
5519 	return FAIL;
5520     }
5521 
5522     hKerneldll = vimLoadLib("kernel32.dll");
5523     for (i = 0; conpty_entry[i].name != NULL
5524 					&& conpty_entry[i].ptr != NULL; ++i)
5525     {
5526 	if ((*conpty_entry[i].ptr = (FARPROC)GetProcAddress(hKerneldll,
5527 						conpty_entry[i].name)) == NULL)
5528 	{
5529 	    if (verbose)
5530 		semsg(_(e_loadfunc), conpty_entry[i].name);
5531 	    return FAIL;
5532 	}
5533     }
5534 
5535     handled = TRUE;
5536     result = OK;
5537     return OK;
5538 }
5539 
5540     static int
5541 conpty_term_and_job_init(
5542 	term_T	    *term,
5543 	typval_T    *argvar,
5544 	char	    **argv,
5545 	jobopt_T    *opt,
5546 	jobopt_T    *orig_opt)
5547 {
5548     WCHAR	    *cmd_wchar = NULL;
5549     WCHAR	    *cmd_wchar_copy = NULL;
5550     WCHAR	    *cwd_wchar = NULL;
5551     WCHAR	    *env_wchar = NULL;
5552     channel_T	    *channel = NULL;
5553     job_T	    *job = NULL;
5554     HANDLE	    jo = NULL;
5555     garray_T	    ga_cmd, ga_env;
5556     char_u	    *cmd = NULL;
5557     HRESULT	    hr;
5558     COORD	    consize;
5559     SIZE_T	    breq;
5560     PROCESS_INFORMATION proc_info;
5561     HANDLE	    i_theirs = NULL;
5562     HANDLE	    o_theirs = NULL;
5563     HANDLE	    i_ours = NULL;
5564     HANDLE	    o_ours = NULL;
5565 
5566     ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5567     ga_init2(&ga_env, (int)sizeof(char*), 20);
5568 
5569     if (argvar->v_type == VAR_STRING)
5570     {
5571 	cmd = argvar->vval.v_string;
5572     }
5573     else if (argvar->v_type == VAR_LIST)
5574     {
5575 	if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
5576 	    goto failed;
5577 	cmd = ga_cmd.ga_data;
5578     }
5579     if (cmd == NULL || *cmd == NUL)
5580     {
5581 	emsg(_(e_invarg));
5582 	goto failed;
5583     }
5584 
5585     term->tl_arg0_cmd = vim_strsave(cmd);
5586 
5587     cmd_wchar = enc_to_utf16(cmd, NULL);
5588 
5589     if (cmd_wchar != NULL)
5590     {
5591 	/* Request by CreateProcessW */
5592 	breq = wcslen(cmd_wchar) + 1 + 1;	/* Addition of NUL by API */
5593 	cmd_wchar_copy = (PWSTR)alloc((int)(breq * sizeof(WCHAR)));
5594 	wcsncpy(cmd_wchar_copy, cmd_wchar, breq - 1);
5595     }
5596 
5597     ga_clear(&ga_cmd);
5598     if (cmd_wchar == NULL)
5599 	goto failed;
5600     if (opt->jo_cwd != NULL)
5601 	cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
5602 
5603     win32_build_env(opt->jo_env, &ga_env, TRUE);
5604     env_wchar = ga_env.ga_data;
5605 
5606     if (!CreatePipe(&i_theirs, &i_ours, NULL, 0))
5607 	goto failed;
5608     if (!CreatePipe(&o_ours, &o_theirs, NULL, 0))
5609 	goto failed;
5610 
5611     consize.X = term->tl_cols;
5612     consize.Y = term->tl_rows;
5613     hr = pCreatePseudoConsole(consize, i_theirs, o_theirs, 0,
5614 							     &term->tl_conpty);
5615     if (FAILED(hr))
5616 	goto failed;
5617 
5618     term->tl_siex.StartupInfo.cb = sizeof(term->tl_siex);
5619 
5620     /* Set up pipe inheritance safely: Vista or later. */
5621     pInitializeProcThreadAttributeList(NULL, 1, 0, &breq);
5622     term->tl_siex.lpAttributeList =
5623 	    (PPROC_THREAD_ATTRIBUTE_LIST)alloc((int)breq);
5624     if (!term->tl_siex.lpAttributeList)
5625 	goto failed;
5626     if (!pInitializeProcThreadAttributeList(term->tl_siex.lpAttributeList, 1,
5627 								     0, &breq))
5628 	goto failed;
5629     if (!pUpdateProcThreadAttribute(
5630 	    term->tl_siex.lpAttributeList, 0,
5631 	    PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, term->tl_conpty,
5632 	    sizeof(HPCON), NULL, NULL))
5633 	goto failed;
5634 
5635     channel = add_channel();
5636     if (channel == NULL)
5637 	goto failed;
5638 
5639     job = job_alloc();
5640     if (job == NULL)
5641 	goto failed;
5642     if (argvar->v_type == VAR_STRING)
5643     {
5644 	int argc;
5645 
5646 	build_argv_from_string(cmd, &job->jv_argv, &argc);
5647     }
5648     else
5649     {
5650 	int argc;
5651 
5652 	build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5653     }
5654 
5655     if (opt->jo_set & JO_IN_BUF)
5656 	job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5657 
5658     if (!CreateProcessW(NULL, cmd_wchar_copy, NULL, NULL, FALSE,
5659 	    EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT
5660 	    | CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP
5661 	    | CREATE_DEFAULT_ERROR_MODE,
5662 	    env_wchar, cwd_wchar,
5663 	    &term->tl_siex.StartupInfo, &proc_info))
5664 	goto failed;
5665 
5666     CloseHandle(i_theirs);
5667     CloseHandle(o_theirs);
5668 
5669     channel_set_pipes(channel,
5670 	    (sock_T)i_ours,
5671 	    (sock_T)o_ours,
5672 	    (sock_T)o_ours);
5673 
5674     /* Write lines with CR instead of NL. */
5675     channel->ch_write_text_mode = TRUE;
5676 
5677     /* Use to explicitly delete anonymous pipe handle. */
5678     channel->ch_anonymous_pipe = TRUE;
5679 
5680     jo = CreateJobObject(NULL, NULL);
5681     if (jo == NULL)
5682 	goto failed;
5683 
5684     if (!AssignProcessToJobObject(jo, proc_info.hProcess))
5685     {
5686 	/* Failed, switch the way to terminate process with TerminateProcess. */
5687 	CloseHandle(jo);
5688 	jo = NULL;
5689     }
5690 
5691     ResumeThread(proc_info.hThread);
5692     CloseHandle(proc_info.hThread);
5693 
5694     vim_free(cmd_wchar);
5695     vim_free(cmd_wchar_copy);
5696     vim_free(cwd_wchar);
5697     vim_free(env_wchar);
5698 
5699     if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
5700 	goto failed;
5701 
5702 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5703     if (opt->jo_set2 & JO2_ANSI_COLORS)
5704 	set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5705     else
5706 	init_vterm_ansi_colors(term->tl_vterm);
5707 #endif
5708 
5709     channel_set_job(channel, job, opt);
5710     job_set_options(job, opt);
5711 
5712     job->jv_channel = channel;
5713     job->jv_proc_info = proc_info;
5714     job->jv_job_object = jo;
5715     job->jv_status = JOB_STARTED;
5716     ++job->jv_refcount;
5717     term->tl_job = job;
5718 
5719     /* Redirecting stdout and stderr doesn't work at the job level.  Instead
5720      * open the file here and handle it in.  opt->jo_io was changed in
5721      * setup_job_options(), use the original flags here. */
5722     if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
5723     {
5724 	char_u *fname = opt->jo_io_name[PART_OUT];
5725 
5726 	ch_log(channel, "Opening output file %s", fname);
5727 	term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
5728 	if (term->tl_out_fd == NULL)
5729 	    semsg(_(e_notopen), fname);
5730     }
5731 
5732     return OK;
5733 
5734 failed:
5735     ga_clear(&ga_cmd);
5736     ga_clear(&ga_env);
5737     vim_free(cmd_wchar);
5738     vim_free(cmd_wchar_copy);
5739     vim_free(cwd_wchar);
5740     if (channel != NULL)
5741 	channel_clear(channel);
5742     if (job != NULL)
5743     {
5744 	job->jv_channel = NULL;
5745 	job_cleanup(job);
5746     }
5747     term->tl_job = NULL;
5748     if (jo != NULL)
5749 	CloseHandle(jo);
5750 
5751     if (term->tl_siex.lpAttributeList != NULL)
5752     {
5753 	pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
5754 	vim_free(term->tl_siex.lpAttributeList);
5755     }
5756     term->tl_siex.lpAttributeList = NULL;
5757     if (o_theirs != NULL)
5758 	CloseHandle(o_theirs);
5759     if (o_ours != NULL)
5760 	CloseHandle(o_ours);
5761     if (i_ours != NULL)
5762 	CloseHandle(i_ours);
5763     if (i_theirs != NULL)
5764 	CloseHandle(i_theirs);
5765     if (term->tl_conpty != NULL)
5766 	pClosePseudoConsole(term->tl_conpty);
5767     term->tl_conpty = NULL;
5768     return FAIL;
5769 }
5770 
5771     static void
5772 conpty_term_report_winsize(term_T *term, int rows, int cols)
5773 {
5774     COORD consize;
5775 
5776     consize.X = cols;
5777     consize.Y = rows;
5778     pResizePseudoConsole(term->tl_conpty, consize);
5779 }
5780 
5781     void
5782 term_free_conpty(term_T *term)
5783 {
5784     if (term->tl_siex.lpAttributeList != NULL)
5785     {
5786 	pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
5787 	vim_free(term->tl_siex.lpAttributeList);
5788     }
5789     term->tl_siex.lpAttributeList = NULL;
5790     if (term->tl_conpty != NULL)
5791 	pClosePseudoConsole(term->tl_conpty);
5792     term->tl_conpty = NULL;
5793 }
5794 
5795     int
5796 use_conpty(void)
5797 {
5798     return has_conpty;
5799 }
5800 
5801 #  ifndef PROTO
5802 
5803 #define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5804 #define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
5805 #define WINPTY_MOUSE_MODE_FORCE		2
5806 
5807 void* (*winpty_config_new)(UINT64, void*);
5808 void* (*winpty_open)(void*, void*);
5809 void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5810 BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5811 void (*winpty_config_set_mouse_mode)(void*, int);
5812 void (*winpty_config_set_initial_size)(void*, int, int);
5813 LPCWSTR (*winpty_conin_name)(void*);
5814 LPCWSTR (*winpty_conout_name)(void*);
5815 LPCWSTR (*winpty_conerr_name)(void*);
5816 void (*winpty_free)(void*);
5817 void (*winpty_config_free)(void*);
5818 void (*winpty_spawn_config_free)(void*);
5819 void (*winpty_error_free)(void*);
5820 LPCWSTR (*winpty_error_msg)(void*);
5821 BOOL (*winpty_set_size)(void*, int, int, void*);
5822 HANDLE (*winpty_agent_process)(void*);
5823 
5824 #define WINPTY_DLL "winpty.dll"
5825 
5826 static HINSTANCE hWinPtyDLL = NULL;
5827 #  endif
5828 
5829     static int
5830 dyn_winpty_init(int verbose)
5831 {
5832     int i;
5833     static struct
5834     {
5835 	char	    *name;
5836 	FARPROC	    *ptr;
5837     } winpty_entry[] =
5838     {
5839 	{"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5840 	{"winpty_config_free", (FARPROC*)&winpty_config_free},
5841 	{"winpty_config_new", (FARPROC*)&winpty_config_new},
5842 	{"winpty_config_set_mouse_mode",
5843 				      (FARPROC*)&winpty_config_set_mouse_mode},
5844 	{"winpty_config_set_initial_size",
5845 				    (FARPROC*)&winpty_config_set_initial_size},
5846 	{"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5847 	{"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5848 	{"winpty_error_free", (FARPROC*)&winpty_error_free},
5849 	{"winpty_free", (FARPROC*)&winpty_free},
5850 	{"winpty_open", (FARPROC*)&winpty_open},
5851 	{"winpty_spawn", (FARPROC*)&winpty_spawn},
5852 	{"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5853 	{"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5854 	{"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5855 	{"winpty_set_size", (FARPROC*)&winpty_set_size},
5856 	{"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5857 	{NULL, NULL}
5858     };
5859 
5860     /* No need to initialize twice. */
5861     if (hWinPtyDLL)
5862 	return OK;
5863     /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5864      * winpty.dll. */
5865     if (*p_winptydll != NUL)
5866 	hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5867     if (!hWinPtyDLL)
5868 	hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5869     if (!hWinPtyDLL)
5870     {
5871 	if (verbose)
5872 	    semsg(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
5873 						       : (char_u *)WINPTY_DLL);
5874 	return FAIL;
5875     }
5876     for (i = 0; winpty_entry[i].name != NULL
5877 					 && winpty_entry[i].ptr != NULL; ++i)
5878     {
5879 	if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5880 					      winpty_entry[i].name)) == NULL)
5881 	{
5882 	    if (verbose)
5883 		semsg(_(e_loadfunc), winpty_entry[i].name);
5884 	    return FAIL;
5885 	}
5886     }
5887 
5888     return OK;
5889 }
5890 
5891     static int
5892 winpty_term_and_job_init(
5893 	term_T	    *term,
5894 	typval_T    *argvar,
5895 	char	    **argv,
5896 	jobopt_T    *opt,
5897 	jobopt_T    *orig_opt)
5898 {
5899     WCHAR	    *cmd_wchar = NULL;
5900     WCHAR	    *cwd_wchar = NULL;
5901     WCHAR	    *env_wchar = NULL;
5902     channel_T	    *channel = NULL;
5903     job_T	    *job = NULL;
5904     DWORD	    error;
5905     HANDLE	    jo = NULL;
5906     HANDLE	    child_process_handle;
5907     HANDLE	    child_thread_handle;
5908     void	    *winpty_err = NULL;
5909     void	    *spawn_config = NULL;
5910     garray_T	    ga_cmd, ga_env;
5911     char_u	    *cmd = NULL;
5912 
5913     ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5914     ga_init2(&ga_env, (int)sizeof(char*), 20);
5915 
5916     if (argvar->v_type == VAR_STRING)
5917     {
5918 	cmd = argvar->vval.v_string;
5919     }
5920     else if (argvar->v_type == VAR_LIST)
5921     {
5922 	if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
5923 	    goto failed;
5924 	cmd = ga_cmd.ga_data;
5925     }
5926     if (cmd == NULL || *cmd == NUL)
5927     {
5928 	emsg(_(e_invarg));
5929 	goto failed;
5930     }
5931 
5932     term->tl_arg0_cmd = vim_strsave(cmd);
5933 
5934     cmd_wchar = enc_to_utf16(cmd, NULL);
5935     ga_clear(&ga_cmd);
5936     if (cmd_wchar == NULL)
5937 	goto failed;
5938     if (opt->jo_cwd != NULL)
5939 	cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
5940 
5941     win32_build_env(opt->jo_env, &ga_env, TRUE);
5942     env_wchar = ga_env.ga_data;
5943 
5944     term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5945     if (term->tl_winpty_config == NULL)
5946 	goto failed;
5947 
5948     winpty_config_set_mouse_mode(term->tl_winpty_config,
5949 						    WINPTY_MOUSE_MODE_FORCE);
5950     winpty_config_set_initial_size(term->tl_winpty_config,
5951 						 term->tl_cols, term->tl_rows);
5952     term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5953     if (term->tl_winpty == NULL)
5954 	goto failed;
5955 
5956     spawn_config = winpty_spawn_config_new(
5957 	    WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5958 		WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5959 	    NULL,
5960 	    cmd_wchar,
5961 	    cwd_wchar,
5962 	    env_wchar,
5963 	    &winpty_err);
5964     if (spawn_config == NULL)
5965 	goto failed;
5966 
5967     channel = add_channel();
5968     if (channel == NULL)
5969 	goto failed;
5970 
5971     job = job_alloc();
5972     if (job == NULL)
5973 	goto failed;
5974     if (argvar->v_type == VAR_STRING)
5975     {
5976 	int argc;
5977 
5978 	build_argv_from_string(cmd, &job->jv_argv, &argc);
5979     }
5980     else
5981     {
5982 	int argc;
5983 
5984 	build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5985     }
5986 
5987     if (opt->jo_set & JO_IN_BUF)
5988 	job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5989 
5990     if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5991 	    &child_thread_handle, &error, &winpty_err))
5992 	goto failed;
5993 
5994     channel_set_pipes(channel,
5995 	(sock_T)CreateFileW(
5996 	    winpty_conin_name(term->tl_winpty),
5997 	    GENERIC_WRITE, 0, NULL,
5998 	    OPEN_EXISTING, 0, NULL),
5999 	(sock_T)CreateFileW(
6000 	    winpty_conout_name(term->tl_winpty),
6001 	    GENERIC_READ, 0, NULL,
6002 	    OPEN_EXISTING, 0, NULL),
6003 	(sock_T)CreateFileW(
6004 	    winpty_conerr_name(term->tl_winpty),
6005 	    GENERIC_READ, 0, NULL,
6006 	    OPEN_EXISTING, 0, NULL));
6007 
6008     /* Write lines with CR instead of NL. */
6009     channel->ch_write_text_mode = TRUE;
6010 
6011     jo = CreateJobObject(NULL, NULL);
6012     if (jo == NULL)
6013 	goto failed;
6014 
6015     if (!AssignProcessToJobObject(jo, child_process_handle))
6016     {
6017 	/* Failed, switch the way to terminate process with TerminateProcess. */
6018 	CloseHandle(jo);
6019 	jo = NULL;
6020     }
6021 
6022     winpty_spawn_config_free(spawn_config);
6023     vim_free(cmd_wchar);
6024     vim_free(cwd_wchar);
6025     vim_free(env_wchar);
6026 
6027     if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6028 	goto failed;
6029 
6030 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6031     if (opt->jo_set2 & JO2_ANSI_COLORS)
6032 	set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6033     else
6034 	init_vterm_ansi_colors(term->tl_vterm);
6035 #endif
6036 
6037     channel_set_job(channel, job, opt);
6038     job_set_options(job, opt);
6039 
6040     job->jv_channel = channel;
6041     job->jv_proc_info.hProcess = child_process_handle;
6042     job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
6043     job->jv_job_object = jo;
6044     job->jv_status = JOB_STARTED;
6045     job->jv_tty_in = utf16_to_enc(
6046 	    (short_u *)winpty_conin_name(term->tl_winpty), NULL);
6047     job->jv_tty_out = utf16_to_enc(
6048 	    (short_u *)winpty_conout_name(term->tl_winpty), NULL);
6049     ++job->jv_refcount;
6050     term->tl_job = job;
6051 
6052     /* Redirecting stdout and stderr doesn't work at the job level.  Instead
6053      * open the file here and handle it in.  opt->jo_io was changed in
6054      * setup_job_options(), use the original flags here. */
6055     if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6056     {
6057 	char_u *fname = opt->jo_io_name[PART_OUT];
6058 
6059 	ch_log(channel, "Opening output file %s", fname);
6060 	term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6061 	if (term->tl_out_fd == NULL)
6062 	    semsg(_(e_notopen), fname);
6063     }
6064 
6065     return OK;
6066 
6067 failed:
6068     ga_clear(&ga_cmd);
6069     ga_clear(&ga_env);
6070     vim_free(cmd_wchar);
6071     vim_free(cwd_wchar);
6072     if (spawn_config != NULL)
6073 	winpty_spawn_config_free(spawn_config);
6074     if (channel != NULL)
6075 	channel_clear(channel);
6076     if (job != NULL)
6077     {
6078 	job->jv_channel = NULL;
6079 	job_cleanup(job);
6080     }
6081     term->tl_job = NULL;
6082     if (jo != NULL)
6083 	CloseHandle(jo);
6084     if (term->tl_winpty != NULL)
6085 	winpty_free(term->tl_winpty);
6086     term->tl_winpty = NULL;
6087     if (term->tl_winpty_config != NULL)
6088 	winpty_config_free(term->tl_winpty_config);
6089     term->tl_winpty_config = NULL;
6090     if (winpty_err != NULL)
6091     {
6092 	char *msg = (char *)utf16_to_enc(
6093 				(short_u *)winpty_error_msg(winpty_err), NULL);
6094 
6095 	emsg(msg);
6096 	winpty_error_free(winpty_err);
6097     }
6098     return FAIL;
6099 }
6100 
6101 /*
6102  * Create a new terminal of "rows" by "cols" cells.
6103  * Store a reference in "term".
6104  * Return OK or FAIL.
6105  */
6106     static int
6107 term_and_job_init(
6108 	term_T	    *term,
6109 	typval_T    *argvar,
6110 	char	    **argv UNUSED,
6111 	jobopt_T    *opt,
6112 	jobopt_T    *orig_opt)
6113 {
6114     int		    use_winpty = FALSE;
6115     int		    use_conpty = FALSE;
6116 
6117     has_winpty = dyn_winpty_init(FALSE) != FAIL ? TRUE : FALSE;
6118     has_conpty = dyn_conpty_init(FALSE) != FAIL ? TRUE : FALSE;
6119 
6120     if (!has_winpty && !has_conpty)
6121 	// If neither is available give the errors for winpty, since when
6122 	// conpty is not available it can't be installed either.
6123 	return dyn_winpty_init(TRUE);
6124 
6125     if (opt->jo_term_mode == 'w')
6126 	set_string_option_direct((char_u *)"tmod", -1, (char_u *)"winpty",
6127 							OPT_FREE|OPT_LOCAL, 0);
6128     if (opt->jo_term_mode == 'c')
6129 	set_string_option_direct((char_u *)"tmod", -1, (char_u *)"conpty",
6130 							OPT_FREE|OPT_LOCAL, 0);
6131 
6132     if (curwin->w_p_tmod == NULL || *curwin->w_p_tmod == NUL)
6133     {
6134 	if (has_conpty)
6135 	    use_conpty = TRUE;
6136 	else if (has_winpty)
6137 	    use_winpty = TRUE;
6138 	// else: error
6139     }
6140     else if (STRICMP(curwin->w_p_tmod, "winpty") == 0)
6141     {
6142 	if (has_winpty)
6143 	    use_winpty = TRUE;
6144     }
6145     else if (STRICMP(curwin->w_p_tmod, "conpty") == 0)
6146     {
6147 	if (has_conpty)
6148 	    use_conpty = TRUE;
6149 	else
6150 	    return dyn_conpty_init(TRUE);
6151     }
6152 
6153     if (use_conpty)
6154     {
6155 	set_string_option_direct((char_u *)"tmod", -1, (char_u *)"conpty",
6156 							OPT_FREE|OPT_LOCAL, 0);
6157 	return conpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
6158     }
6159 
6160     if (use_winpty)
6161     {
6162 	set_string_option_direct((char_u *)"tmod", -1, (char_u *)"winpty",
6163 							OPT_FREE|OPT_LOCAL, 0);
6164 	return winpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
6165     }
6166 
6167     // error
6168     return dyn_winpty_init(TRUE);
6169 }
6170 
6171     static int
6172 create_pty_only(term_T *term, jobopt_T *options)
6173 {
6174     HANDLE	    hPipeIn = INVALID_HANDLE_VALUE;
6175     HANDLE	    hPipeOut = INVALID_HANDLE_VALUE;
6176     char	    in_name[80], out_name[80];
6177     channel_T	    *channel = NULL;
6178 
6179     if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6180 	return FAIL;
6181 
6182     vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
6183 	    GetCurrentProcessId(),
6184 	    curbuf->b_fnum);
6185     hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
6186 	    PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6187 	    PIPE_UNLIMITED_INSTANCES,
6188 	    0, 0, NMPWAIT_NOWAIT, NULL);
6189     if (hPipeIn == INVALID_HANDLE_VALUE)
6190 	goto failed;
6191 
6192     vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
6193 	    GetCurrentProcessId(),
6194 	    curbuf->b_fnum);
6195     hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
6196 	    PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6197 	    PIPE_UNLIMITED_INSTANCES,
6198 	    0, 0, 0, NULL);
6199     if (hPipeOut == INVALID_HANDLE_VALUE)
6200 	goto failed;
6201 
6202     ConnectNamedPipe(hPipeIn, NULL);
6203     ConnectNamedPipe(hPipeOut, NULL);
6204 
6205     term->tl_job = job_alloc();
6206     if (term->tl_job == NULL)
6207 	goto failed;
6208     ++term->tl_job->jv_refcount;
6209 
6210     /* behave like the job is already finished */
6211     term->tl_job->jv_status = JOB_FINISHED;
6212 
6213     channel = add_channel();
6214     if (channel == NULL)
6215 	goto failed;
6216     term->tl_job->jv_channel = channel;
6217     channel->ch_keep_open = TRUE;
6218     channel->ch_named_pipe = TRUE;
6219 
6220     channel_set_pipes(channel,
6221 	(sock_T)hPipeIn,
6222 	(sock_T)hPipeOut,
6223 	(sock_T)hPipeOut);
6224     channel_set_job(channel, term->tl_job, options);
6225     term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
6226     term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
6227 
6228     return OK;
6229 
6230 failed:
6231     if (hPipeIn != NULL)
6232 	CloseHandle(hPipeIn);
6233     if (hPipeOut != NULL)
6234 	CloseHandle(hPipeOut);
6235     return FAIL;
6236 }
6237 
6238 /*
6239  * Free the terminal emulator part of "term".
6240  */
6241     static void
6242 term_free_vterm(term_T *term)
6243 {
6244     term_free_conpty(term);
6245     if (term->tl_winpty != NULL)
6246 	winpty_free(term->tl_winpty);
6247     term->tl_winpty = NULL;
6248     if (term->tl_winpty_config != NULL)
6249 	winpty_config_free(term->tl_winpty_config);
6250     term->tl_winpty_config = NULL;
6251     if (term->tl_vterm != NULL)
6252 	vterm_free(term->tl_vterm);
6253     term->tl_vterm = NULL;
6254 }
6255 
6256 /*
6257  * Report the size to the terminal.
6258  */
6259     static void
6260 term_report_winsize(term_T *term, int rows, int cols)
6261 {
6262     if (term->tl_conpty)
6263 	conpty_term_report_winsize(term, rows, cols);
6264     if (term->tl_winpty)
6265 	winpty_set_size(term->tl_winpty, cols, rows, NULL);
6266 }
6267 
6268     int
6269 terminal_enabled(void)
6270 {
6271     return dyn_winpty_init(FALSE) == OK || dyn_conpty_init(FALSE) == OK;
6272 }
6273 
6274 # else
6275 
6276 /**************************************
6277  * 3. Unix-like implementation.
6278  */
6279 
6280 /*
6281  * Create a new terminal of "rows" by "cols" cells.
6282  * Start job for "cmd".
6283  * Store the pointers in "term".
6284  * When "argv" is not NULL then "argvar" is not used.
6285  * Return OK or FAIL.
6286  */
6287     static int
6288 term_and_job_init(
6289 	term_T	    *term,
6290 	typval_T    *argvar,
6291 	char	    **argv,
6292 	jobopt_T    *opt,
6293 	jobopt_T    *orig_opt UNUSED)
6294 {
6295     term->tl_arg0_cmd = NULL;
6296 
6297     if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6298 	return FAIL;
6299 
6300 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6301     if (opt->jo_set2 & JO2_ANSI_COLORS)
6302 	set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6303     else
6304 	init_vterm_ansi_colors(term->tl_vterm);
6305 #endif
6306 
6307     /* This may change a string in "argvar". */
6308     term->tl_job = job_start(argvar, argv, opt, TRUE);
6309     if (term->tl_job != NULL)
6310 	++term->tl_job->jv_refcount;
6311 
6312     return term->tl_job != NULL
6313 	&& term->tl_job->jv_channel != NULL
6314 	&& term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
6315 }
6316 
6317     static int
6318 create_pty_only(term_T *term, jobopt_T *opt)
6319 {
6320     if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6321 	return FAIL;
6322 
6323     term->tl_job = job_alloc();
6324     if (term->tl_job == NULL)
6325 	return FAIL;
6326     ++term->tl_job->jv_refcount;
6327 
6328     /* behave like the job is already finished */
6329     term->tl_job->jv_status = JOB_FINISHED;
6330 
6331     return mch_create_pty_channel(term->tl_job, opt);
6332 }
6333 
6334 /*
6335  * Free the terminal emulator part of "term".
6336  */
6337     static void
6338 term_free_vterm(term_T *term)
6339 {
6340     if (term->tl_vterm != NULL)
6341 	vterm_free(term->tl_vterm);
6342     term->tl_vterm = NULL;
6343 }
6344 
6345 /*
6346  * Report the size to the terminal.
6347  */
6348     static void
6349 term_report_winsize(term_T *term, int rows, int cols)
6350 {
6351     /* Use an ioctl() to report the new window size to the job. */
6352     if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
6353     {
6354 	int fd = -1;
6355 	int part;
6356 
6357 	for (part = PART_OUT; part < PART_COUNT; ++part)
6358 	{
6359 	    fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
6360 	    if (mch_isatty(fd))
6361 		break;
6362 	}
6363 	if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
6364 	    mch_signal_job(term->tl_job, (char_u *)"winch");
6365     }
6366 }
6367 
6368 # endif
6369 
6370 #endif /* FEAT_TERMINAL */
6371