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