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, ®len);
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