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