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