blob: 7a80636ad09836c823d46cc1e339e70d7d244e8e [file] [log] [blame]
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001/* 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 * TODO:
Bram Moolenaarb852c3e2018-03-11 16:55:36 +010041 * - if the job in the terminal does not support the mouse, we can use the
42 * mouse in the Terminal window for copy/paste and scrolling.
Bram Moolenaar46359e12017-11-29 22:33:38 +010043 * - When using 'termguicolors' still use the 16 ANSI colors as-is. Helps for
Bram Moolenaarb852c3e2018-03-11 16:55:36 +010044 * - In the GUI use a terminal emulator for :!cmd. Make the height the same as
45 * the window and position it higher up when it gets filled, so it looks like
46 * the text scrolls up.
47 * - implement term_setsize()
48 * - Copy text in the vterm to the Vim buffer once in a while, so that
49 * completion works.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +010050 * - Adding WinBar to terminal window doesn't display, text isn't shifted down.
Bram Moolenaar46359e12017-11-29 22:33:38 +010051 * a job that uses 16 colors while Vim is using > 256.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020052 * - in GUI vertical split causes problems. Cursor is flickering. (Hirohito
53 * Higashi, 2017 Sep 19)
Bram Moolenaar3a497e12017-09-30 20:40:27 +020054 * - after resizing windows overlap. (Boris Staletic, #2164)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020055 * - Redirecting output does not work on MS-Windows, Test_terminal_redir_file()
56 * is disabled.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020057 * - cursor blinks in terminal on widows with a timer. (xtal8, #2142)
Bram Moolenaarba6febd2017-10-30 21:56:23 +010058 * - Termdebug does not work when Vim build with mzscheme. gdb hangs.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020059 * - MS-Windows GUI: WinBar has tearoff item
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020060 * - MS-Windows GUI: still need to type a key after shell exits? #1924
Bram Moolenaar51b0f372017-11-18 18:52:04 +010061 * - After executing a shell command the status line isn't redraw.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020062 * - add test for giving error for invalid 'termsize' value.
63 * - support minimal size when 'termsize' is "rows*cols".
64 * - support minimal size when 'termsize' is empty?
65 * - GUI: when using tabs, focus in terminal, click on tab does not work.
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020066 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020067 * - For the GUI fill termios with default values, perhaps like pangoterm:
68 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020069 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
70 * conversions.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020071 * - add an optional limit for the scrollback size. When reaching it remove
72 * 10% at the start.
73 */
74
75#include "vim.h"
76
77#if defined(FEAT_TERMINAL) || defined(PROTO)
78
79#ifndef MIN
80# define MIN(x,y) ((x) < (y) ? (x) : (y))
81#endif
82#ifndef MAX
83# define MAX(x,y) ((x) > (y) ? (x) : (y))
84#endif
85
86#include "libvterm/include/vterm.h"
87
88/* This is VTermScreenCell without the characters, thus much smaller. */
89typedef struct {
90 VTermScreenCellAttrs attrs;
91 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010092 VTermColor fg;
93 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020094} cellattr_T;
95
96typedef struct sb_line_S {
97 int sb_cols; /* can differ per line */
98 cellattr_T *sb_cells; /* allocated */
99 cellattr_T sb_fill_attr; /* for short line */
100} sb_line_T;
101
102/* typedef term_T in structs.h */
103struct terminal_S {
104 term_T *tl_next;
105
106 VTerm *tl_vterm;
107 job_T *tl_job;
108 buf_T *tl_buffer;
109
110 /* Set when setting the size of a vterm, reset after redrawing. */
111 int tl_vterm_size_changed;
112
113 /* used when tl_job is NULL and only a pty was created */
114 int tl_tty_fd;
115 char_u *tl_tty_in;
116 char_u *tl_tty_out;
117
118 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
119 int tl_channel_closed;
120 int tl_finish; /* 'c' for ++close, 'o' for ++open */
121 char_u *tl_opencmd;
122 char_u *tl_eof_chars;
123
124#ifdef WIN3264
125 void *tl_winpty_config;
126 void *tl_winpty;
127#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100128#if defined(FEAT_SESSION)
129 char_u *tl_command;
130#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100131 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200132
133 /* last known vterm size */
134 int tl_rows;
135 int tl_cols;
136 /* vterm size does not follow window size */
137 int tl_rows_fixed;
138 int tl_cols_fixed;
139
140 char_u *tl_title; /* NULL or allocated */
141 char_u *tl_status_text; /* NULL or allocated */
142
143 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200144 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200145 int tl_dirty_row_end; /* row below last one to update */
146
147 garray_T tl_scrollback;
148 int tl_scrollback_scrolled;
149 cellattr_T tl_default_color;
150
Bram Moolenaard96ff162018-02-18 22:13:29 +0100151 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
152 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
153
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200154 VTermPos tl_cursor_pos;
155 int tl_cursor_visible;
156 int tl_cursor_blink;
157 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
158 char_u *tl_cursor_color; /* NULL or allocated */
159
160 int tl_using_altscreen;
161};
162
163#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
164#define TMODE_LOOP 2 /* CTRL-W N used */
165
166/*
167 * List of all active terminals.
168 */
169static term_T *first_term = NULL;
170
171/* Terminal active in terminal_loop(). */
172static term_T *in_terminal_loop = NULL;
173
174#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
175#define KEY_BUF_LEN 200
176
177/*
178 * Functions with separate implementation for MS-Windows and Unix-like systems.
179 */
180static int term_and_job_init(term_T *term, typval_T *argvar, jobopt_T *opt);
181static int create_pty_only(term_T *term, jobopt_T *opt);
182static void term_report_winsize(term_T *term, int rows, int cols);
183static void term_free_vterm(term_T *term);
184
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100185/* The character that we know (or assume) that the terminal expects for the
186 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200187static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200188
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100189/* "Terminal" highlight group colors. */
190static int term_default_cterm_fg = -1;
191static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200192
Bram Moolenaard317b382018-02-08 22:33:31 +0100193/* Store the last set and the desired cursor properties, so that we only update
194 * them when needed. Doing it unnecessary may result in flicker. */
195static char_u *last_set_cursor_color = (char_u *)"";
196static char_u *desired_cursor_color = (char_u *)"";
197static int last_set_cursor_shape = -1;
198static int desired_cursor_shape = -1;
199static int last_set_cursor_blink = -1;
200static int desired_cursor_blink = -1;
201
202
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200203/**************************************
204 * 1. Generic code for all systems.
205 */
206
207/*
208 * Determine the terminal size from 'termsize' and the current window.
209 * Assumes term->tl_rows and term->tl_cols are zero.
210 */
211 static void
212set_term_and_win_size(term_T *term)
213{
214 if (*curwin->w_p_tms != NUL)
215 {
216 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
217
218 term->tl_rows = atoi((char *)curwin->w_p_tms);
219 term->tl_cols = atoi((char *)p);
220 }
221 if (term->tl_rows == 0)
222 term->tl_rows = curwin->w_height;
223 else
224 {
225 win_setheight_win(term->tl_rows, curwin);
226 term->tl_rows_fixed = TRUE;
227 }
228 if (term->tl_cols == 0)
229 term->tl_cols = curwin->w_width;
230 else
231 {
232 win_setwidth_win(term->tl_cols, curwin);
233 term->tl_cols_fixed = TRUE;
234 }
235}
236
237/*
238 * Initialize job options for a terminal job.
239 * Caller may overrule some of them.
240 */
241 static void
242init_job_options(jobopt_T *opt)
243{
244 clear_job_options(opt);
245
246 opt->jo_mode = MODE_RAW;
247 opt->jo_out_mode = MODE_RAW;
248 opt->jo_err_mode = MODE_RAW;
249 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
250}
251
252/*
253 * Set job options mandatory for a terminal job.
254 */
255 static void
256setup_job_options(jobopt_T *opt, int rows, int cols)
257{
258 if (!(opt->jo_set & JO_OUT_IO))
259 {
260 /* Connect stdout to the terminal. */
261 opt->jo_io[PART_OUT] = JIO_BUFFER;
262 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
263 opt->jo_modifiable[PART_OUT] = 0;
264 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
265 }
266
267 if (!(opt->jo_set & JO_ERR_IO))
268 {
269 /* Connect stderr to the terminal. */
270 opt->jo_io[PART_ERR] = JIO_BUFFER;
271 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
272 opt->jo_modifiable[PART_ERR] = 0;
273 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
274 }
275
276 opt->jo_pty = TRUE;
277 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
278 opt->jo_term_rows = rows;
279 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
280 opt->jo_term_cols = cols;
281}
282
283/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100284 * Close a terminal buffer (and its window). Used when creating the terminal
285 * fails.
286 */
287 static void
288term_close_buffer(buf_T *buf, buf_T *old_curbuf)
289{
290 free_terminal(buf);
291 if (old_curbuf != NULL)
292 {
293 --curbuf->b_nwindows;
294 curbuf = old_curbuf;
295 curwin->w_buffer = curbuf;
296 ++curbuf->b_nwindows;
297 }
298
299 /* Wiping out the buffer will also close the window and call
300 * free_terminal(). */
301 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
302}
303
304/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200305 * Start a terminal window and return its buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +0100306 * When "without_job" is TRUE only create the buffer, b_term and open the
307 * window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200308 * Returns NULL when failed.
309 */
310 static buf_T *
Bram Moolenaard96ff162018-02-18 22:13:29 +0100311term_start(typval_T *argvar, jobopt_T *opt, int without_job, int forceit)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200312{
313 exarg_T split_ea;
314 win_T *old_curwin = curwin;
315 term_T *term;
316 buf_T *old_curbuf = NULL;
317 int res;
318 buf_T *newbuf;
319
320 if (check_restricted() || check_secure())
321 return NULL;
322
323 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
324 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
325 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
326 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
327 {
328 EMSG(_(e_invarg));
329 return NULL;
330 }
331
332 term = (term_T *)alloc_clear(sizeof(term_T));
333 if (term == NULL)
334 return NULL;
335 term->tl_dirty_row_end = MAX_ROW;
336 term->tl_cursor_visible = TRUE;
337 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
338 term->tl_finish = opt->jo_term_finish;
339 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
340
341 vim_memset(&split_ea, 0, sizeof(split_ea));
342 if (opt->jo_curwin)
343 {
344 /* Create a new buffer in the current window. */
345 if (!can_abandon(curbuf, forceit))
346 {
347 no_write_message();
348 vim_free(term);
349 return NULL;
350 }
351 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
352 ECMD_HIDE + (forceit ? ECMD_FORCEIT : 0), curwin) == FAIL)
353 {
354 vim_free(term);
355 return NULL;
356 }
357 }
358 else if (opt->jo_hidden)
359 {
360 buf_T *buf;
361
362 /* Create a new buffer without a window. Make it the current buffer for
363 * a moment to be able to do the initialisations. */
364 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
365 BLN_NEW | BLN_LISTED);
366 if (buf == NULL || ml_open(buf) == FAIL)
367 {
368 vim_free(term);
369 return NULL;
370 }
371 old_curbuf = curbuf;
372 --curbuf->b_nwindows;
373 curbuf = buf;
374 curwin->w_buffer = buf;
375 ++curbuf->b_nwindows;
376 }
377 else
378 {
379 /* Open a new window or tab. */
380 split_ea.cmdidx = CMD_new;
381 split_ea.cmd = (char_u *)"new";
382 split_ea.arg = (char_u *)"";
383 if (opt->jo_term_rows > 0 && !(cmdmod.split & WSP_VERT))
384 {
385 split_ea.line2 = opt->jo_term_rows;
386 split_ea.addr_count = 1;
387 }
388 if (opt->jo_term_cols > 0 && (cmdmod.split & WSP_VERT))
389 {
390 split_ea.line2 = opt->jo_term_cols;
391 split_ea.addr_count = 1;
392 }
393
394 ex_splitview(&split_ea);
395 if (curwin == old_curwin)
396 {
397 /* split failed */
398 vim_free(term);
399 return NULL;
400 }
401 }
402 term->tl_buffer = curbuf;
403 curbuf->b_term = term;
404
405 if (!opt->jo_hidden)
406 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100407 /* Only one size was taken care of with :new, do the other one. With
408 * "curwin" both need to be done. */
409 if (opt->jo_term_rows > 0 && (opt->jo_curwin
410 || (cmdmod.split & WSP_VERT)))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200411 win_setheight(opt->jo_term_rows);
Bram Moolenaarda650582018-02-20 15:51:40 +0100412 if (opt->jo_term_cols > 0 && (opt->jo_curwin
413 || !(cmdmod.split & WSP_VERT)))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200414 win_setwidth(opt->jo_term_cols);
415 }
416
417 /* Link the new terminal in the list of active terminals. */
418 term->tl_next = first_term;
419 first_term = term;
420
421 if (opt->jo_term_name != NULL)
422 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
423 else
424 {
425 int i;
426 size_t len;
427 char_u *cmd, *p;
428
429 if (argvar->v_type == VAR_STRING)
430 {
431 cmd = argvar->vval.v_string;
432 if (cmd == NULL)
433 cmd = (char_u *)"";
434 else if (STRCMP(cmd, "NONE") == 0)
435 cmd = (char_u *)"pty";
436 }
437 else if (argvar->v_type != VAR_LIST
438 || argvar->vval.v_list == NULL
439 || argvar->vval.v_list->lv_len < 1
440 || (cmd = get_tv_string_chk(
441 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
442 cmd = (char_u*)"";
443
444 len = STRLEN(cmd) + 10;
445 p = alloc((int)len);
446
447 for (i = 0; p != NULL; ++i)
448 {
449 /* Prepend a ! to the command name to avoid the buffer name equals
450 * the executable, otherwise ":w!" would overwrite it. */
451 if (i == 0)
452 vim_snprintf((char *)p, len, "!%s", cmd);
453 else
454 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
455 if (buflist_findname(p) == NULL)
456 {
457 vim_free(curbuf->b_ffname);
458 curbuf->b_ffname = p;
459 break;
460 }
461 }
462 }
463 curbuf->b_fname = curbuf->b_ffname;
464
465 if (opt->jo_term_opencmd != NULL)
466 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
467
468 if (opt->jo_eof_chars != NULL)
469 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
470
471 set_string_option_direct((char_u *)"buftype", -1,
472 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
473
474 /* Mark the buffer as not modifiable. It can only be made modifiable after
475 * the job finished. */
476 curbuf->b_p_ma = FALSE;
477
478 set_term_and_win_size(term);
479 setup_job_options(opt, term->tl_rows, term->tl_cols);
480
Bram Moolenaard96ff162018-02-18 22:13:29 +0100481 if (without_job)
482 return curbuf;
483
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100484#if defined(FEAT_SESSION)
485 /* Remember the command for the session file. */
486 if (opt->jo_term_norestore)
487 {
488 term->tl_command = vim_strsave((char_u *)"NONE");
489 }
490 else if (argvar->v_type == VAR_STRING)
491 {
492 char_u *cmd = argvar->vval.v_string;
493
494 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
495 term->tl_command = vim_strsave(cmd);
496 }
497 else if (argvar->v_type == VAR_LIST
498 && argvar->vval.v_list != NULL
499 && argvar->vval.v_list->lv_len > 0)
500 {
501 garray_T ga;
502 listitem_T *item;
503
504 ga_init2(&ga, 1, 100);
505 for (item = argvar->vval.v_list->lv_first;
506 item != NULL; item = item->li_next)
507 {
508 char_u *s = get_tv_string_chk(&item->li_tv);
509 char_u *p;
510
511 if (s == NULL)
512 break;
513 p = vim_strsave_fnameescape(s, FALSE);
514 if (p == NULL)
515 break;
516 ga_concat(&ga, p);
517 vim_free(p);
518 ga_append(&ga, ' ');
519 }
520 if (item == NULL)
521 {
522 ga_append(&ga, NUL);
523 term->tl_command = ga.ga_data;
524 }
525 else
526 ga_clear(&ga);
527 }
528#endif
529
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100530 if (opt->jo_term_kill != NULL)
531 {
532 char_u *p = skiptowhite(opt->jo_term_kill);
533
534 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
535 }
536
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200537 /* System dependent: setup the vterm and maybe start the job in it. */
538 if (argvar->v_type == VAR_STRING
539 && argvar->vval.v_string != NULL
540 && STRCMP(argvar->vval.v_string, "NONE") == 0)
541 res = create_pty_only(term, opt);
542 else
543 res = term_and_job_init(term, argvar, opt);
544
545 newbuf = curbuf;
546 if (res == OK)
547 {
548 /* Get and remember the size we ended up with. Update the pty. */
549 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
550 term_report_winsize(term, term->tl_rows, term->tl_cols);
551
552 /* Make sure we don't get stuck on sending keys to the job, it leads to
553 * a deadlock if the job is waiting for Vim to read. */
554 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
555
Bram Moolenaarab5e7c32018-02-13 14:07:18 +0100556 if (!opt->jo_hidden)
557 {
558 ++curbuf->b_locked;
559 apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
560 --curbuf->b_locked;
561 }
Bram Moolenaar8b21de32017-09-22 11:13:52 +0200562
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200563 if (old_curbuf != NULL)
564 {
565 --curbuf->b_nwindows;
566 curbuf = old_curbuf;
567 curwin->w_buffer = curbuf;
568 ++curbuf->b_nwindows;
569 }
570 }
571 else
572 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100573 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200574 return NULL;
575 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100576
577 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200578 return newbuf;
579}
580
581/*
582 * ":terminal": open a terminal window and execute a job in it.
583 */
584 void
585ex_terminal(exarg_T *eap)
586{
587 typval_T argvar[2];
588 jobopt_T opt;
589 char_u *cmd;
590 char_u *tofree = NULL;
591
592 init_job_options(&opt);
593
594 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100595 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200596 {
597 char_u *p, *ep;
598
599 cmd += 2;
600 p = skiptowhite(cmd);
601 ep = vim_strchr(cmd, '=');
602 if (ep != NULL && ep < p)
603 p = ep;
604
605 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
606 opt.jo_term_finish = 'c';
607 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
608 opt.jo_term_finish = 'o';
609 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
610 opt.jo_curwin = 1;
611 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
612 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100613 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
614 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100615 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
616 && ep != NULL)
617 {
618 opt.jo_set2 |= JO2_TERM_KILL;
619 opt.jo_term_kill = ep + 1;
620 p = skiptowhite(cmd);
621 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200622 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
623 && ep != NULL && isdigit(ep[1]))
624 {
625 opt.jo_set2 |= JO2_TERM_ROWS;
626 opt.jo_term_rows = atoi((char *)ep + 1);
627 p = skiptowhite(cmd);
628 }
629 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
630 && ep != NULL && isdigit(ep[1]))
631 {
632 opt.jo_set2 |= JO2_TERM_COLS;
633 opt.jo_term_cols = atoi((char *)ep + 1);
634 p = skiptowhite(cmd);
635 }
636 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
637 && ep != NULL)
638 {
639 char_u *buf = NULL;
640 char_u *keys;
641
642 p = skiptowhite(cmd);
643 *p = NUL;
644 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
645 opt.jo_set2 |= JO2_EOF_CHARS;
646 opt.jo_eof_chars = vim_strsave(keys);
647 vim_free(buf);
648 *p = ' ';
649 }
650 else
651 {
652 if (*p)
653 *p = NUL;
654 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100655 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200656 }
657 cmd = skipwhite(p);
658 }
659 if (*cmd == NUL)
660 /* Make a copy of 'shell', an autocommand may change the option. */
661 tofree = cmd = vim_strsave(p_sh);
662
663 if (eap->addr_count > 0)
664 {
665 /* Write lines from current buffer to the job. */
666 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
667 opt.jo_io[PART_IN] = JIO_BUFFER;
668 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
669 opt.jo_in_top = eap->line1;
670 opt.jo_in_bot = eap->line2;
671 }
672
673 argvar[0].v_type = VAR_STRING;
674 argvar[0].vval.v_string = cmd;
675 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaard96ff162018-02-18 22:13:29 +0100676 term_start(argvar, &opt, FALSE, eap->forceit);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200677 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100678
679theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200680 vim_free(opt.jo_eof_chars);
681}
682
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100683#if defined(FEAT_SESSION) || defined(PROTO)
684/*
685 * Write a :terminal command to the session file to restore the terminal in
686 * window "wp".
687 * Return FAIL if writing fails.
688 */
689 int
690term_write_session(FILE *fd, win_T *wp)
691{
692 term_T *term = wp->w_buffer->b_term;
693
694 /* Create the terminal and run the command. This is not without
695 * risk, but let's assume the user only creates a session when this
696 * will be OK. */
697 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
698 term->tl_cols, term->tl_rows) < 0)
699 return FAIL;
700 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
701 return FAIL;
702
703 return put_eol(fd);
704}
705
706/*
707 * Return TRUE if "buf" has a terminal that should be restored.
708 */
709 int
710term_should_restore(buf_T *buf)
711{
712 term_T *term = buf->b_term;
713
714 return term != NULL && (term->tl_command == NULL
715 || STRCMP(term->tl_command, "NONE") != 0);
716}
717#endif
718
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200719/*
720 * Free the scrollback buffer for "term".
721 */
722 static void
723free_scrollback(term_T *term)
724{
725 int i;
726
727 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
728 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
729 ga_clear(&term->tl_scrollback);
730}
731
732/*
733 * Free a terminal and everything it refers to.
734 * Kills the job if there is one.
735 * Called when wiping out a buffer.
736 */
737 void
738free_terminal(buf_T *buf)
739{
740 term_T *term = buf->b_term;
741 term_T *tp;
742
743 if (term == NULL)
744 return;
745 if (first_term == term)
746 first_term = term->tl_next;
747 else
748 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
749 if (tp->tl_next == term)
750 {
751 tp->tl_next = term->tl_next;
752 break;
753 }
754
755 if (term->tl_job != NULL)
756 {
757 if (term->tl_job->jv_status != JOB_ENDED
758 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100759 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200760 job_stop(term->tl_job, NULL, "kill");
761 job_unref(term->tl_job);
762 }
763
764 free_scrollback(term);
765
766 term_free_vterm(term);
767 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100768#ifdef FEAT_SESSION
769 vim_free(term->tl_command);
770#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100771 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200772 vim_free(term->tl_status_text);
773 vim_free(term->tl_opencmd);
774 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100775 if (desired_cursor_color == term->tl_cursor_color)
776 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200777 vim_free(term->tl_cursor_color);
778 vim_free(term);
779 buf->b_term = NULL;
780 if (in_terminal_loop == term)
781 in_terminal_loop = NULL;
782}
783
784/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100785 * Get the part that is connected to the tty. Normally this is PART_IN, but
786 * when writing buffer lines to the job it can be another. This makes it
787 * possible to do "1,5term vim -".
788 */
789 static ch_part_T
790get_tty_part(term_T *term)
791{
792#ifdef UNIX
793 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
794 int i;
795
796 for (i = 0; i < 3; ++i)
797 {
798 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
799
800 if (isatty(fd))
801 return parts[i];
802 }
803#endif
804 return PART_IN;
805}
806
807/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200808 * Write job output "msg[len]" to the vterm.
809 */
810 static void
811term_write_job_output(term_T *term, char_u *msg, size_t len)
812{
813 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100814 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200815
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100816 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200817
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100818 /* flush vterm buffer when vterm responded to control sequence */
819 if (prevlen != vterm_output_get_buffer_current(vterm))
820 {
821 char buf[KEY_BUF_LEN];
822 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
823
824 if (curlen > 0)
825 channel_send(term->tl_job->jv_channel, get_tty_part(term),
826 (char_u *)buf, (int)curlen, NULL);
827 }
828
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200829 /* this invokes the damage callbacks */
830 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
831}
832
833 static void
834update_cursor(term_T *term, int redraw)
835{
836 if (term->tl_normal_mode)
837 return;
838 setcursor();
839 if (redraw)
840 {
841 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
842 cursor_on();
843 out_flush();
844#ifdef FEAT_GUI
845 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100846 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200847 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100848 gui_mch_flush();
849 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200850#endif
851 }
852}
853
854/*
855 * Invoked when "msg" output from a job was received. Write it to the terminal
856 * of "buffer".
857 */
858 void
859write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
860{
861 size_t len = STRLEN(msg);
862 term_T *term = buffer->b_term;
863
864 if (term->tl_vterm == NULL)
865 {
866 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
867 return;
868 }
869 ch_log(channel, "writing %d bytes to terminal", (int)len);
870 term_write_job_output(term, msg, len);
871
872 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
873 * contents, thus no screen update is needed. */
874 if (!term->tl_normal_mode)
875 {
876 /* TODO: only update once in a while. */
877 ch_log(term->tl_job->jv_channel, "updating screen");
878 if (buffer == curbuf)
879 {
880 update_screen(0);
881 update_cursor(term, TRUE);
882 }
883 else
884 redraw_after_callback(TRUE);
885 }
886}
887
888/*
889 * Send a mouse position and click to the vterm
890 */
891 static int
892term_send_mouse(VTerm *vterm, int button, int pressed)
893{
894 VTermModifier mod = VTERM_MOD_NONE;
895
896 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200897 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100898 if (button != 0)
899 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200900 return TRUE;
901}
902
903/*
904 * Convert typed key "c" into bytes to send to the job.
905 * Return the number of bytes in "buf".
906 */
907 static int
908term_convert_key(term_T *term, int c, char *buf)
909{
910 VTerm *vterm = term->tl_vterm;
911 VTermKey key = VTERM_KEY_NONE;
912 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +0100913 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200914
915 switch (c)
916 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100917 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
918
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200919 /* don't use VTERM_KEY_BACKSPACE, it always
920 * becomes 0x7f DEL */
921 case K_BS: c = term_backspace_char; break;
922
923 case ESC: key = VTERM_KEY_ESCAPE; break;
924 case K_DEL: key = VTERM_KEY_DEL; break;
925 case K_DOWN: key = VTERM_KEY_DOWN; break;
926 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
927 key = VTERM_KEY_DOWN; break;
928 case K_END: key = VTERM_KEY_END; break;
929 case K_S_END: mod = VTERM_MOD_SHIFT;
930 key = VTERM_KEY_END; break;
931 case K_C_END: mod = VTERM_MOD_CTRL;
932 key = VTERM_KEY_END; break;
933 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
934 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
935 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
936 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
937 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
938 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
939 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
940 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
941 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
942 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
943 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
944 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
945 case K_HOME: key = VTERM_KEY_HOME; break;
946 case K_S_HOME: mod = VTERM_MOD_SHIFT;
947 key = VTERM_KEY_HOME; break;
948 case K_C_HOME: mod = VTERM_MOD_CTRL;
949 key = VTERM_KEY_HOME; break;
950 case K_INS: key = VTERM_KEY_INS; break;
951 case K_K0: key = VTERM_KEY_KP_0; break;
952 case K_K1: key = VTERM_KEY_KP_1; break;
953 case K_K2: key = VTERM_KEY_KP_2; break;
954 case K_K3: key = VTERM_KEY_KP_3; break;
955 case K_K4: key = VTERM_KEY_KP_4; break;
956 case K_K5: key = VTERM_KEY_KP_5; break;
957 case K_K6: key = VTERM_KEY_KP_6; break;
958 case K_K7: key = VTERM_KEY_KP_7; break;
959 case K_K8: key = VTERM_KEY_KP_8; break;
960 case K_K9: key = VTERM_KEY_KP_9; break;
961 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
962 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
963 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
964 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
965 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
966 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
967 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
968 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
969 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
970 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
971 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
972 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
973 case K_LEFT: key = VTERM_KEY_LEFT; break;
974 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
975 key = VTERM_KEY_LEFT; break;
976 case K_C_LEFT: mod = VTERM_MOD_CTRL;
977 key = VTERM_KEY_LEFT; break;
978 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
979 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
980 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
981 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
982 key = VTERM_KEY_RIGHT; break;
983 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
984 key = VTERM_KEY_RIGHT; break;
985 case K_UP: key = VTERM_KEY_UP; break;
986 case K_S_UP: mod = VTERM_MOD_SHIFT;
987 key = VTERM_KEY_UP; break;
988 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +0100989 case K_S_TAB: mod = VTERM_MOD_SHIFT;
990 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200991
Bram Moolenaara42ad572017-11-16 13:08:04 +0100992 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
993 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200994 case K_MOUSELEFT: /* TODO */ return 0;
995 case K_MOUSERIGHT: /* TODO */ return 0;
996
997 case K_LEFTMOUSE:
Bram Moolenaara42ad572017-11-16 13:08:04 +0100998 case K_LEFTMOUSE_NM: other = term_send_mouse(vterm, 1, 1); break;
999 case K_LEFTDRAG: other = term_send_mouse(vterm, 1, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001000 case K_LEFTRELEASE:
Bram Moolenaara42ad572017-11-16 13:08:04 +01001001 case K_LEFTRELEASE_NM: other = term_send_mouse(vterm, 1, 0); break;
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001002 case K_MOUSEMOVE: other = term_send_mouse(vterm, 0, 0); break;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001003 case K_MIDDLEMOUSE: other = term_send_mouse(vterm, 2, 1); break;
1004 case K_MIDDLEDRAG: other = term_send_mouse(vterm, 2, 1); break;
1005 case K_MIDDLERELEASE: other = term_send_mouse(vterm, 2, 0); break;
1006 case K_RIGHTMOUSE: other = term_send_mouse(vterm, 3, 1); break;
1007 case K_RIGHTDRAG: other = term_send_mouse(vterm, 3, 1); break;
1008 case K_RIGHTRELEASE: other = term_send_mouse(vterm, 3, 0); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001009 case K_X1MOUSE: /* TODO */ return 0;
1010 case K_X1DRAG: /* TODO */ return 0;
1011 case K_X1RELEASE: /* TODO */ return 0;
1012 case K_X2MOUSE: /* TODO */ return 0;
1013 case K_X2DRAG: /* TODO */ return 0;
1014 case K_X2RELEASE: /* TODO */ return 0;
1015
1016 case K_IGNORE: return 0;
1017 case K_NOP: return 0;
1018 case K_UNDO: return 0;
1019 case K_HELP: return 0;
1020 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1021 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1022 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1023 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1024 case K_SELECT: return 0;
1025#ifdef FEAT_GUI
1026 case K_VER_SCROLLBAR: return 0;
1027 case K_HOR_SCROLLBAR: return 0;
1028#endif
1029#ifdef FEAT_GUI_TABLINE
1030 case K_TABLINE: return 0;
1031 case K_TABMENU: return 0;
1032#endif
1033#ifdef FEAT_NETBEANS_INTG
1034 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1035#endif
1036#ifdef FEAT_DND
1037 case K_DROP: return 0;
1038#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001039 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001040 case K_PS: vterm_keyboard_start_paste(vterm);
1041 other = TRUE;
1042 break;
1043 case K_PE: vterm_keyboard_end_paste(vterm);
1044 other = TRUE;
1045 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001046 }
1047
1048 /*
1049 * Convert special keys to vterm keys:
1050 * - Write keys to vterm: vterm_keyboard_key()
1051 * - Write output to channel.
1052 * TODO: use mod_mask
1053 */
1054 if (key != VTERM_KEY_NONE)
1055 /* Special key, let vterm convert it. */
1056 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001057 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001058 /* Normal character, let vterm convert it. */
1059 vterm_keyboard_unichar(vterm, c, mod);
1060
1061 /* Read back the converted escape sequence. */
1062 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1063}
1064
1065/*
1066 * Return TRUE if the job for "term" is still running.
1067 */
1068 int
1069term_job_running(term_T *term)
1070{
1071 /* Also consider the job finished when the channel is closed, to avoid a
1072 * race condition when updating the title. */
1073 return term != NULL
1074 && term->tl_job != NULL
1075 && channel_is_open(term->tl_job->jv_channel)
1076 && (term->tl_job->jv_status == JOB_STARTED
1077 || term->tl_job->jv_channel->ch_keep_open);
1078}
1079
1080/*
1081 * Return TRUE if "term" has an active channel and used ":term NONE".
1082 */
1083 int
1084term_none_open(term_T *term)
1085{
1086 /* Also consider the job finished when the channel is closed, to avoid a
1087 * race condition when updating the title. */
1088 return term != NULL
1089 && term->tl_job != NULL
1090 && channel_is_open(term->tl_job->jv_channel)
1091 && term->tl_job->jv_channel->ch_keep_open;
1092}
1093
1094/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001095 * Used when exiting: kill the job in "buf" if so desired.
1096 * Return OK when the job finished.
1097 * Return FAIL when the job is still running.
1098 */
1099 int
1100term_try_stop_job(buf_T *buf)
1101{
1102 int count;
1103 char *how = (char *)buf->b_term->tl_kill;
1104
1105#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1106 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1107 {
1108 char_u buff[DIALOG_MSG_SIZE];
1109 int ret;
1110
1111 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1112 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1113 if (ret == VIM_YES)
1114 how = "kill";
1115 else if (ret == VIM_CANCEL)
1116 return FAIL;
1117 }
1118#endif
1119 if (how == NULL || *how == NUL)
1120 return FAIL;
1121
1122 job_stop(buf->b_term->tl_job, NULL, how);
1123
1124 /* wait for up to a second for the job to die */
1125 for (count = 0; count < 100; ++count)
1126 {
1127 /* buffer, terminal and job may be cleaned up while waiting */
1128 if (!buf_valid(buf)
1129 || buf->b_term == NULL
1130 || buf->b_term->tl_job == NULL)
1131 return OK;
1132
1133 /* call job_status() to update jv_status */
1134 job_status(buf->b_term->tl_job);
1135 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1136 return OK;
1137 ui_delay(10L, FALSE);
1138 mch_check_messages();
1139 parse_queued_messages();
1140 }
1141 return FAIL;
1142}
1143
1144/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001145 * Add the last line of the scrollback buffer to the buffer in the window.
1146 */
1147 static void
1148add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1149{
1150 buf_T *buf = term->tl_buffer;
1151 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1152 linenr_T lnum = buf->b_ml.ml_line_count;
1153
1154#ifdef WIN3264
1155 if (!enc_utf8 && enc_codepage > 0)
1156 {
1157 WCHAR *ret = NULL;
1158 int length = 0;
1159
1160 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1161 &ret, &length);
1162 if (ret != NULL)
1163 {
1164 WideCharToMultiByte_alloc(enc_codepage, 0,
1165 ret, length, (char **)&text, &len, 0, 0);
1166 vim_free(ret);
1167 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1168 vim_free(text);
1169 }
1170 }
1171 else
1172#endif
1173 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1174 if (empty)
1175 {
1176 /* Delete the empty line that was in the empty buffer. */
1177 curbuf = buf;
1178 ml_delete(1, FALSE);
1179 curbuf = curwin->w_buffer;
1180 }
1181}
1182
1183 static void
1184cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1185{
1186 attr->width = cell->width;
1187 attr->attrs = cell->attrs;
1188 attr->fg = cell->fg;
1189 attr->bg = cell->bg;
1190}
1191
1192 static int
1193equal_celattr(cellattr_T *a, cellattr_T *b)
1194{
1195 /* Comparing the colors should be sufficient. */
1196 return a->fg.red == b->fg.red
1197 && a->fg.green == b->fg.green
1198 && a->fg.blue == b->fg.blue
1199 && a->bg.red == b->bg.red
1200 && a->bg.green == b->bg.green
1201 && a->bg.blue == b->bg.blue;
1202}
1203
Bram Moolenaard96ff162018-02-18 22:13:29 +01001204/*
1205 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1206 * line at this position. Otherwise at the end.
1207 */
1208 static int
1209add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1210{
1211 if (ga_grow(&term->tl_scrollback, 1) == OK)
1212 {
1213 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1214 + term->tl_scrollback.ga_len;
1215
1216 if (lnum > 0)
1217 {
1218 int i;
1219
1220 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1221 {
1222 *line = *(line - 1);
1223 --line;
1224 }
1225 }
1226 line->sb_cols = 0;
1227 line->sb_cells = NULL;
1228 line->sb_fill_attr = *fill_attr;
1229 ++term->tl_scrollback.ga_len;
1230 return OK;
1231 }
1232 return FALSE;
1233}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001234
1235/*
1236 * Add the current lines of the terminal to scrollback and to the buffer.
1237 * Called after the job has ended and when switching to Terminal-Normal mode.
1238 */
1239 static void
1240move_terminal_to_buffer(term_T *term)
1241{
1242 win_T *wp;
1243 int len;
1244 int lines_skipped = 0;
1245 VTermPos pos;
1246 VTermScreenCell cell;
1247 cellattr_T fill_attr, new_fill_attr;
1248 cellattr_T *p;
1249 VTermScreen *screen;
1250
1251 if (term->tl_vterm == NULL)
1252 return;
1253 screen = vterm_obtain_screen(term->tl_vterm);
1254 fill_attr = new_fill_attr = term->tl_default_color;
1255
1256 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1257 {
1258 len = 0;
1259 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1260 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1261 && cell.chars[0] != NUL)
1262 {
1263 len = pos.col + 1;
1264 new_fill_attr = term->tl_default_color;
1265 }
1266 else
1267 /* Assume the last attr is the filler attr. */
1268 cell2cellattr(&cell, &new_fill_attr);
1269
1270 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1271 ++lines_skipped;
1272 else
1273 {
1274 while (lines_skipped > 0)
1275 {
1276 /* Line was skipped, add an empty line. */
1277 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001278 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001279 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001280 }
1281
1282 if (len == 0)
1283 p = NULL;
1284 else
1285 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1286 if ((p != NULL || len == 0)
1287 && ga_grow(&term->tl_scrollback, 1) == OK)
1288 {
1289 garray_T ga;
1290 int width;
1291 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1292 + term->tl_scrollback.ga_len;
1293
1294 ga_init2(&ga, 1, 100);
1295 for (pos.col = 0; pos.col < len; pos.col += width)
1296 {
1297 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1298 {
1299 width = 1;
1300 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1301 if (ga_grow(&ga, 1) == OK)
1302 ga.ga_len += utf_char2bytes(' ',
1303 (char_u *)ga.ga_data + ga.ga_len);
1304 }
1305 else
1306 {
1307 width = cell.width;
1308
1309 cell2cellattr(&cell, &p[pos.col]);
1310
1311 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1312 {
1313 int i;
1314 int c;
1315
1316 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1317 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1318 (char_u *)ga.ga_data + ga.ga_len);
1319 }
1320 }
1321 }
1322 line->sb_cols = len;
1323 line->sb_cells = p;
1324 line->sb_fill_attr = new_fill_attr;
1325 fill_attr = new_fill_attr;
1326 ++term->tl_scrollback.ga_len;
1327
1328 if (ga_grow(&ga, 1) == FAIL)
1329 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1330 else
1331 {
1332 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1333 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1334 }
1335 ga_clear(&ga);
1336 }
1337 else
1338 vim_free(p);
1339 }
1340 }
1341
1342 /* Obtain the current background color. */
1343 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1344 &term->tl_default_color.fg, &term->tl_default_color.bg);
1345
1346 FOR_ALL_WINDOWS(wp)
1347 {
1348 if (wp->w_buffer == term->tl_buffer)
1349 {
1350 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1351 wp->w_cursor.col = 0;
1352 wp->w_valid = 0;
1353 if (wp->w_cursor.lnum >= wp->w_height)
1354 {
1355 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1356
1357 if (wp->w_topline < min_topline)
1358 wp->w_topline = min_topline;
1359 }
1360 redraw_win_later(wp, NOT_VALID);
1361 }
1362 }
1363}
1364
1365 static void
1366set_terminal_mode(term_T *term, int normal_mode)
1367{
1368 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001369 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001370 if (term->tl_buffer == curbuf)
1371 maketitle();
1372}
1373
1374/*
1375 * Called after the job if finished and Terminal mode is not active:
1376 * Move the vterm contents into the scrollback buffer and free the vterm.
1377 */
1378 static void
1379cleanup_vterm(term_T *term)
1380{
1381 if (term->tl_finish != 'c')
1382 move_terminal_to_buffer(term);
1383 term_free_vterm(term);
1384 set_terminal_mode(term, FALSE);
1385}
1386
1387/*
1388 * Switch from Terminal-Job mode to Terminal-Normal mode.
1389 * Suspends updating the terminal window.
1390 */
1391 static void
1392term_enter_normal_mode(void)
1393{
1394 term_T *term = curbuf->b_term;
1395
1396 /* Append the current terminal contents to the buffer. */
1397 move_terminal_to_buffer(term);
1398
1399 set_terminal_mode(term, TRUE);
1400
1401 /* Move the window cursor to the position of the cursor in the
1402 * terminal. */
1403 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1404 + term->tl_cursor_pos.row + 1;
1405 check_cursor();
1406 coladvance(term->tl_cursor_pos.col);
1407
1408 /* Display the same lines as in the terminal. */
1409 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1410}
1411
1412/*
1413 * Returns TRUE if the current window contains a terminal and we are in
1414 * Terminal-Normal mode.
1415 */
1416 int
1417term_in_normal_mode(void)
1418{
1419 term_T *term = curbuf->b_term;
1420
1421 return term != NULL && term->tl_normal_mode;
1422}
1423
1424/*
1425 * Switch from Terminal-Normal mode to Terminal-Job mode.
1426 * Restores updating the terminal window.
1427 */
1428 void
1429term_enter_job_mode()
1430{
1431 term_T *term = curbuf->b_term;
1432 sb_line_T *line;
1433 garray_T *gap;
1434
1435 /* Remove the terminal contents from the scrollback and the buffer. */
1436 gap = &term->tl_scrollback;
1437 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1438 && gap->ga_len > 0)
1439 {
1440 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1441 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1442 vim_free(line->sb_cells);
1443 --gap->ga_len;
1444 }
1445 check_cursor();
1446
1447 set_terminal_mode(term, FALSE);
1448
1449 if (term->tl_channel_closed)
1450 cleanup_vterm(term);
1451 redraw_buf_and_status_later(curbuf, NOT_VALID);
1452}
1453
1454/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001455 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001456 * Note: while waiting a terminal may be closed and freed if the channel is
1457 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001458 */
1459 static int
1460term_vgetc()
1461{
1462 int c;
1463 int save_State = State;
1464
1465 State = TERMINAL;
1466 got_int = FALSE;
1467#ifdef WIN3264
1468 ctrl_break_was_pressed = FALSE;
1469#endif
1470 c = vgetc();
1471 got_int = FALSE;
1472 State = save_State;
1473 return c;
1474}
1475
1476/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001477 * Send keys to terminal.
1478 * Return FAIL when the key needs to be handled in Normal mode.
1479 * Return OK when the key was dropped or sent to the terminal.
1480 */
1481 int
1482send_keys_to_term(term_T *term, int c, int typed)
1483{
1484 char msg[KEY_BUF_LEN];
1485 size_t len;
1486 static int mouse_was_outside = FALSE;
1487 int dragging_outside = FALSE;
1488
1489 /* Catch keys that need to be handled as in Normal mode. */
1490 switch (c)
1491 {
1492 case NUL:
1493 case K_ZERO:
1494 if (typed)
1495 stuffcharReadbuff(c);
1496 return FAIL;
1497
1498 case K_IGNORE:
1499 return FAIL;
1500
1501 case K_LEFTDRAG:
1502 case K_MIDDLEDRAG:
1503 case K_RIGHTDRAG:
1504 case K_X1DRAG:
1505 case K_X2DRAG:
1506 dragging_outside = mouse_was_outside;
1507 /* FALLTHROUGH */
1508 case K_LEFTMOUSE:
1509 case K_LEFTMOUSE_NM:
1510 case K_LEFTRELEASE:
1511 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001512 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001513 case K_MIDDLEMOUSE:
1514 case K_MIDDLERELEASE:
1515 case K_RIGHTMOUSE:
1516 case K_RIGHTRELEASE:
1517 case K_X1MOUSE:
1518 case K_X1RELEASE:
1519 case K_X2MOUSE:
1520 case K_X2RELEASE:
1521
1522 case K_MOUSEUP:
1523 case K_MOUSEDOWN:
1524 case K_MOUSELEFT:
1525 case K_MOUSERIGHT:
1526 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001527 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001528 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001529 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001530 || dragging_outside)
1531 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001532 /* click or scroll outside the current window or on status line
1533 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001534 if (typed)
1535 {
1536 stuffcharReadbuff(c);
1537 mouse_was_outside = TRUE;
1538 }
1539 return FAIL;
1540 }
1541 }
1542 if (typed)
1543 mouse_was_outside = FALSE;
1544
1545 /* Convert the typed key to a sequence of bytes for the job. */
1546 len = term_convert_key(term, c, msg);
1547 if (len > 0)
1548 /* TODO: if FAIL is returned, stop? */
1549 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1550 (char_u *)msg, (int)len, NULL);
1551
1552 return OK;
1553}
1554
1555 static void
1556position_cursor(win_T *wp, VTermPos *pos)
1557{
1558 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1559 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1560 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1561}
1562
1563/*
1564 * Handle CTRL-W "": send register contents to the job.
1565 */
1566 static void
1567term_paste_register(int prev_c UNUSED)
1568{
1569 int c;
1570 list_T *l;
1571 listitem_T *item;
1572 long reglen = 0;
1573 int type;
1574
1575#ifdef FEAT_CMDL_INFO
1576 if (add_to_showcmd(prev_c))
1577 if (add_to_showcmd('"'))
1578 out_flush();
1579#endif
1580 c = term_vgetc();
1581#ifdef FEAT_CMDL_INFO
1582 clear_showcmd();
1583#endif
1584 if (!term_use_loop())
1585 /* job finished while waiting for a character */
1586 return;
1587
1588 /* CTRL-W "= prompt for expression to evaluate. */
1589 if (c == '=' && get_expr_register() != '=')
1590 return;
1591 if (!term_use_loop())
1592 /* job finished while waiting for a character */
1593 return;
1594
1595 l = (list_T *)get_reg_contents(c, GREG_LIST);
1596 if (l != NULL)
1597 {
1598 type = get_reg_type(c, &reglen);
1599 for (item = l->lv_first; item != NULL; item = item->li_next)
1600 {
1601 char_u *s = get_tv_string(&item->li_tv);
1602#ifdef WIN3264
1603 char_u *tmp = s;
1604
1605 if (!enc_utf8 && enc_codepage > 0)
1606 {
1607 WCHAR *ret = NULL;
1608 int length = 0;
1609
1610 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1611 (int)STRLEN(s), &ret, &length);
1612 if (ret != NULL)
1613 {
1614 WideCharToMultiByte_alloc(CP_UTF8, 0,
1615 ret, length, (char **)&s, &length, 0, 0);
1616 vim_free(ret);
1617 }
1618 }
1619#endif
1620 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1621 s, (int)STRLEN(s), NULL);
1622#ifdef WIN3264
1623 if (tmp != s)
1624 vim_free(s);
1625#endif
1626
1627 if (item->li_next != NULL || type == MLINE)
1628 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1629 (char_u *)"\r", 1, NULL);
1630 }
1631 list_free(l);
1632 }
1633}
1634
1635#if defined(FEAT_GUI) || defined(PROTO)
1636/*
1637 * Return TRUE when the cursor of the terminal should be displayed.
1638 */
1639 int
1640terminal_is_active()
1641{
1642 return in_terminal_loop != NULL;
1643}
1644
1645 cursorentry_T *
1646term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1647{
1648 term_T *term = in_terminal_loop;
1649 static cursorentry_T entry;
1650
1651 vim_memset(&entry, 0, sizeof(entry));
1652 entry.shape = entry.mshape =
1653 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1654 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1655 SHAPE_BLOCK;
1656 entry.percentage = 20;
1657 if (term->tl_cursor_blink)
1658 {
1659 entry.blinkwait = 700;
1660 entry.blinkon = 400;
1661 entry.blinkoff = 250;
1662 }
1663 *fg = gui.back_pixel;
1664 if (term->tl_cursor_color == NULL)
1665 *bg = gui.norm_pixel;
1666 else
1667 *bg = color_name2handle(term->tl_cursor_color);
1668 entry.name = "n";
1669 entry.used_for = SHAPE_CURSOR;
1670
1671 return &entry;
1672}
1673#endif
1674
Bram Moolenaard317b382018-02-08 22:33:31 +01001675 static void
1676may_output_cursor_props(void)
1677{
1678 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1679 || last_set_cursor_shape != desired_cursor_shape
1680 || last_set_cursor_blink != desired_cursor_blink)
1681 {
1682 last_set_cursor_color = desired_cursor_color;
1683 last_set_cursor_shape = desired_cursor_shape;
1684 last_set_cursor_blink = desired_cursor_blink;
1685 term_cursor_color(desired_cursor_color);
1686 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1687 /* this will restore the initial cursor style, if possible */
1688 ui_cursor_shape_forced(TRUE);
1689 else
1690 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1691 }
1692}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001693
Bram Moolenaard317b382018-02-08 22:33:31 +01001694/*
1695 * Set the cursor color and shape, if not last set to these.
1696 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001697 static void
1698may_set_cursor_props(term_T *term)
1699{
1700#ifdef FEAT_GUI
1701 /* For the GUI the cursor properties are obtained with
1702 * term_get_cursor_shape(). */
1703 if (gui.in_use)
1704 return;
1705#endif
1706 if (in_terminal_loop == term)
1707 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001708 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001709 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001710 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001711 desired_cursor_color = (char_u *)"";
1712 desired_cursor_shape = term->tl_cursor_shape;
1713 desired_cursor_blink = term->tl_cursor_blink;
1714 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001715 }
1716}
1717
Bram Moolenaard317b382018-02-08 22:33:31 +01001718/*
1719 * Reset the desired cursor properties and restore them when needed.
1720 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001721 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001722prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001723{
1724#ifdef FEAT_GUI
1725 if (gui.in_use)
1726 return;
1727#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001728 desired_cursor_color = (char_u *)"";
1729 desired_cursor_shape = -1;
1730 desired_cursor_blink = -1;
1731 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001732}
1733
1734/*
1735 * Returns TRUE if the current window contains a terminal and we are sending
1736 * keys to the job.
1737 */
1738 int
1739term_use_loop(void)
1740{
1741 term_T *term = curbuf->b_term;
1742
1743 return term != NULL
1744 && !term->tl_normal_mode
1745 && term->tl_vterm != NULL
1746 && term_job_running(term);
1747}
1748
1749/*
1750 * Wait for input and send it to the job.
1751 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1752 * when there is no more typahead.
1753 * Return when the start of a CTRL-W command is typed or anything else that
1754 * should be handled as a Normal mode command.
1755 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1756 * the terminal was closed.
1757 */
1758 int
1759terminal_loop(int blocking)
1760{
1761 int c;
1762 int termkey = 0;
1763 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001764#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001765 int tty_fd = curbuf->b_term->tl_job->jv_channel
1766 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001767#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001768 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001769
1770 /* Remember the terminal we are sending keys to. However, the terminal
1771 * might be closed while waiting for a character, e.g. typing "exit" in a
1772 * shell and ++close was used. Therefore use curbuf->b_term instead of a
1773 * stored reference. */
1774 in_terminal_loop = curbuf->b_term;
1775
1776 if (*curwin->w_p_tk != NUL)
1777 termkey = string_to_key(curwin->w_p_tk, TRUE);
1778 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
1779 may_set_cursor_props(curbuf->b_term);
1780
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001781 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001782 {
1783 /* TODO: skip screen update when handling a sequence of keys. */
1784 /* Repeat redrawing in case a message is received while redrawing. */
1785 while (must_redraw != 0)
1786 if (update_screen(0) == FAIL)
1787 break;
1788 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01001789 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001790
1791 c = term_vgetc();
1792 if (!term_use_loop())
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001793 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001794 /* Job finished while waiting for a character. Push back the
1795 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001796 if (c != K_IGNORE)
1797 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001798 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001799 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001800 if (c == K_IGNORE)
1801 continue;
1802
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001803#ifdef UNIX
1804 /*
1805 * The shell or another program may change the tty settings. Getting
1806 * them for every typed character is a bit of overhead, but it's needed
1807 * for the first character typed, e.g. when Vim starts in a shell.
1808 */
1809 if (isatty(tty_fd))
1810 {
1811 ttyinfo_T info;
1812
1813 /* Get the current backspace character of the pty. */
1814 if (get_tty_info(tty_fd, &info) == OK)
1815 term_backspace_char = info.backspace;
1816 }
1817#endif
1818
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001819#ifdef WIN3264
1820 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
1821 * Use CTRL-BREAK to kill the job. */
1822 if (ctrl_break_was_pressed)
1823 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
1824#endif
1825 /* Was either CTRL-W (termkey) or CTRL-\ pressed? */
1826 if (c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
1827 {
1828 int prev_c = c;
1829
1830#ifdef FEAT_CMDL_INFO
1831 if (add_to_showcmd(c))
1832 out_flush();
1833#endif
1834 c = term_vgetc();
1835#ifdef FEAT_CMDL_INFO
1836 clear_showcmd();
1837#endif
1838 if (!term_use_loop())
1839 /* job finished while waiting for a character */
1840 break;
1841
1842 if (prev_c == Ctrl_BSL)
1843 {
1844 if (c == Ctrl_N)
1845 {
1846 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
1847 term_enter_normal_mode();
1848 ret = FAIL;
1849 goto theend;
1850 }
1851 /* Send both keys to the terminal. */
1852 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
1853 }
1854 else if (c == Ctrl_C)
1855 {
1856 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
1857 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
1858 }
1859 else if (termkey == 0 && c == '.')
1860 {
1861 /* "CTRL-W .": send CTRL-W to the job */
1862 c = Ctrl_W;
1863 }
1864 else if (c == 'N')
1865 {
1866 /* CTRL-W N : go to Terminal-Normal mode. */
1867 term_enter_normal_mode();
1868 ret = FAIL;
1869 goto theend;
1870 }
1871 else if (c == '"')
1872 {
1873 term_paste_register(prev_c);
1874 continue;
1875 }
1876 else if (termkey == 0 || c != termkey)
1877 {
1878 stuffcharReadbuff(Ctrl_W);
1879 stuffcharReadbuff(c);
1880 ret = OK;
1881 goto theend;
1882 }
1883 }
1884# ifdef WIN3264
1885 if (!enc_utf8 && has_mbyte && c >= 0x80)
1886 {
1887 WCHAR wc;
1888 char_u mb[3];
1889
1890 mb[0] = (unsigned)c >> 8;
1891 mb[1] = c;
1892 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
1893 c = wc;
1894 }
1895# endif
1896 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
1897 {
Bram Moolenaard317b382018-02-08 22:33:31 +01001898 if (c == K_MOUSEMOVE)
1899 /* We are sure to come back here, don't reset the cursor color
1900 * and shape to avoid flickering. */
1901 restore_cursor = FALSE;
1902
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001903 ret = OK;
1904 goto theend;
1905 }
1906 }
1907 ret = FAIL;
1908
1909theend:
1910 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01001911 if (restore_cursor)
1912 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001913 return ret;
1914}
1915
1916/*
1917 * Called when a job has finished.
1918 * This updates the title and status, but does not close the vterm, because
1919 * there might still be pending output in the channel.
1920 */
1921 void
1922term_job_ended(job_T *job)
1923{
1924 term_T *term;
1925 int did_one = FALSE;
1926
1927 for (term = first_term; term != NULL; term = term->tl_next)
1928 if (term->tl_job == job)
1929 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01001930 VIM_CLEAR(term->tl_title);
1931 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001932 redraw_buf_and_status_later(term->tl_buffer, VALID);
1933 did_one = TRUE;
1934 }
1935 if (did_one)
1936 redraw_statuslines();
1937 if (curbuf->b_term != NULL)
1938 {
1939 if (curbuf->b_term->tl_job == job)
1940 maketitle();
1941 update_cursor(curbuf->b_term, TRUE);
1942 }
1943}
1944
1945 static void
1946may_toggle_cursor(term_T *term)
1947{
1948 if (in_terminal_loop == term)
1949 {
1950 if (term->tl_cursor_visible)
1951 cursor_on();
1952 else
1953 cursor_off();
1954 }
1955}
1956
1957/*
1958 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01001959 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001960 */
1961 static int
1962color2index(VTermColor *color, int fg, int *boldp)
1963{
1964 int red = color->red;
1965 int blue = color->blue;
1966 int green = color->green;
1967
Bram Moolenaar46359e12017-11-29 22:33:38 +01001968 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001969 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01001970 /* First 16 colors and default: use the ANSI index, because these
1971 * colors can be redefined. */
1972 if (t_colors >= 16)
1973 return color->ansi_index;
1974 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001975 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01001976 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01001977 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01001978 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
1979 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
1980 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
1981 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue*/
1982 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
1983 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
1984 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
1985 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
1986 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
1987 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
1988 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
1989 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
1990 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
1991 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
1992 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001993 }
1994 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01001995
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001996 if (t_colors >= 256)
1997 {
1998 if (red == blue && red == green)
1999 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002000 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002001 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002002 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2003 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2004 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002005 int i;
2006
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002007 if (red < 5)
2008 return 17; /* 00/00/00 */
2009 if (red > 245) /* ff/ff/ff */
2010 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002011 for (i = 0; i < 23; ++i)
2012 if (red < cutoff[i])
2013 return i + 233;
2014 return 256;
2015 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002016 {
2017 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2018 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002019
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002020 /* 216-color cube */
2021 for (ri = 0; ri < 5; ++ri)
2022 if (red < cutoff[ri])
2023 break;
2024 for (gi = 0; gi < 5; ++gi)
2025 if (green < cutoff[gi])
2026 break;
2027 for (bi = 0; bi < 5; ++bi)
2028 if (blue < cutoff[bi])
2029 break;
2030 return 17 + ri * 36 + gi * 6 + bi;
2031 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002032 }
2033 return 0;
2034}
2035
2036/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002037 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002038 */
2039 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002040vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002041{
2042 int attr = 0;
2043
2044 if (cellattrs.bold)
2045 attr |= HL_BOLD;
2046 if (cellattrs.underline)
2047 attr |= HL_UNDERLINE;
2048 if (cellattrs.italic)
2049 attr |= HL_ITALIC;
2050 if (cellattrs.strike)
2051 attr |= HL_STRIKETHROUGH;
2052 if (cellattrs.reverse)
2053 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002054 return attr;
2055}
2056
2057/*
2058 * Store Vterm attributes in "cell" from highlight flags.
2059 */
2060 static void
2061hl2vtermAttr(int attr, cellattr_T *cell)
2062{
2063 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2064 if (attr & HL_BOLD)
2065 cell->attrs.bold = 1;
2066 if (attr & HL_UNDERLINE)
2067 cell->attrs.underline = 1;
2068 if (attr & HL_ITALIC)
2069 cell->attrs.italic = 1;
2070 if (attr & HL_STRIKETHROUGH)
2071 cell->attrs.strike = 1;
2072 if (attr & HL_INVERSE)
2073 cell->attrs.reverse = 1;
2074}
2075
2076/*
2077 * Convert the attributes of a vterm cell into an attribute index.
2078 */
2079 static int
2080cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2081{
2082 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002083
2084#ifdef FEAT_GUI
2085 if (gui.in_use)
2086 {
2087 guicolor_T fg, bg;
2088
2089 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2090 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2091 return get_gui_attr_idx(attr, fg, bg);
2092 }
2093 else
2094#endif
2095#ifdef FEAT_TERMGUICOLORS
2096 if (p_tgc)
2097 {
2098 guicolor_T fg, bg;
2099
2100 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2101 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2102
2103 return get_tgc_attr_idx(attr, fg, bg);
2104 }
2105 else
2106#endif
2107 {
2108 int bold = MAYBE;
2109 int fg = color2index(&cellfg, TRUE, &bold);
2110 int bg = color2index(&cellbg, FALSE, &bold);
2111
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002112 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002113 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002114 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002115 if (fg == 0 && term_default_cterm_fg >= 0)
2116 fg = term_default_cterm_fg + 1;
2117 if (bg == 0 && term_default_cterm_bg >= 0)
2118 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002119 }
2120
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002121 /* with 8 colors set the bold attribute to get a bright foreground */
2122 if (bold == TRUE)
2123 attr |= HL_BOLD;
2124 return get_cterm_attr_idx(attr, fg, bg);
2125 }
2126 return 0;
2127}
2128
2129 static int
2130handle_damage(VTermRect rect, void *user)
2131{
2132 term_T *term = (term_T *)user;
2133
2134 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2135 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2136 redraw_buf_later(term->tl_buffer, NOT_VALID);
2137 return 1;
2138}
2139
2140 static int
2141handle_moverect(VTermRect dest, VTermRect src, void *user)
2142{
2143 term_T *term = (term_T *)user;
2144
2145 /* Scrolling up is done much more efficiently by deleting lines instead of
2146 * redrawing the text. */
2147 if (dest.start_col == src.start_col
2148 && dest.end_col == src.end_col
2149 && dest.start_row < src.start_row)
2150 {
2151 win_T *wp;
2152 VTermColor fg, bg;
2153 VTermScreenCellAttrs attr;
2154 int clear_attr;
2155
2156 /* Set the color to clear lines with. */
2157 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2158 &fg, &bg);
2159 vim_memset(&attr, 0, sizeof(attr));
2160 clear_attr = cell2attr(attr, fg, bg);
2161
2162 FOR_ALL_WINDOWS(wp)
2163 {
2164 if (wp->w_buffer == term->tl_buffer)
2165 win_del_lines(wp, dest.start_row,
2166 src.start_row - dest.start_row, FALSE, FALSE,
2167 clear_attr);
2168 }
2169 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002170
2171 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2172 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2173
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002174 redraw_buf_later(term->tl_buffer, NOT_VALID);
2175 return 1;
2176}
2177
2178 static int
2179handle_movecursor(
2180 VTermPos pos,
2181 VTermPos oldpos UNUSED,
2182 int visible,
2183 void *user)
2184{
2185 term_T *term = (term_T *)user;
2186 win_T *wp;
2187
2188 term->tl_cursor_pos = pos;
2189 term->tl_cursor_visible = visible;
2190
2191 FOR_ALL_WINDOWS(wp)
2192 {
2193 if (wp->w_buffer == term->tl_buffer)
2194 position_cursor(wp, &pos);
2195 }
2196 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2197 {
2198 may_toggle_cursor(term);
2199 update_cursor(term, term->tl_cursor_visible);
2200 }
2201
2202 return 1;
2203}
2204
2205 static int
2206handle_settermprop(
2207 VTermProp prop,
2208 VTermValue *value,
2209 void *user)
2210{
2211 term_T *term = (term_T *)user;
2212
2213 switch (prop)
2214 {
2215 case VTERM_PROP_TITLE:
2216 vim_free(term->tl_title);
2217 /* a blank title isn't useful, make it empty, so that "running" is
2218 * displayed */
2219 if (*skipwhite((char_u *)value->string) == NUL)
2220 term->tl_title = NULL;
2221#ifdef WIN3264
2222 else if (!enc_utf8 && enc_codepage > 0)
2223 {
2224 WCHAR *ret = NULL;
2225 int length = 0;
2226
2227 MultiByteToWideChar_alloc(CP_UTF8, 0,
2228 (char*)value->string, (int)STRLEN(value->string),
2229 &ret, &length);
2230 if (ret != NULL)
2231 {
2232 WideCharToMultiByte_alloc(enc_codepage, 0,
2233 ret, length, (char**)&term->tl_title,
2234 &length, 0, 0);
2235 vim_free(ret);
2236 }
2237 }
2238#endif
2239 else
2240 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002241 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002242 if (term == curbuf->b_term)
2243 maketitle();
2244 break;
2245
2246 case VTERM_PROP_CURSORVISIBLE:
2247 term->tl_cursor_visible = value->boolean;
2248 may_toggle_cursor(term);
2249 out_flush();
2250 break;
2251
2252 case VTERM_PROP_CURSORBLINK:
2253 term->tl_cursor_blink = value->boolean;
2254 may_set_cursor_props(term);
2255 break;
2256
2257 case VTERM_PROP_CURSORSHAPE:
2258 term->tl_cursor_shape = value->number;
2259 may_set_cursor_props(term);
2260 break;
2261
2262 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002263 if (desired_cursor_color == term->tl_cursor_color)
2264 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002265 vim_free(term->tl_cursor_color);
2266 if (*value->string == NUL)
2267 term->tl_cursor_color = NULL;
2268 else
2269 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2270 may_set_cursor_props(term);
2271 break;
2272
2273 case VTERM_PROP_ALTSCREEN:
2274 /* TODO: do anything else? */
2275 term->tl_using_altscreen = value->boolean;
2276 break;
2277
2278 default:
2279 break;
2280 }
2281 /* Always return 1, otherwise vterm doesn't store the value internally. */
2282 return 1;
2283}
2284
2285/*
2286 * The job running in the terminal resized the terminal.
2287 */
2288 static int
2289handle_resize(int rows, int cols, void *user)
2290{
2291 term_T *term = (term_T *)user;
2292 win_T *wp;
2293
2294 term->tl_rows = rows;
2295 term->tl_cols = cols;
2296 if (term->tl_vterm_size_changed)
2297 /* Size was set by vterm_set_size(), don't set the window size. */
2298 term->tl_vterm_size_changed = FALSE;
2299 else
2300 {
2301 FOR_ALL_WINDOWS(wp)
2302 {
2303 if (wp->w_buffer == term->tl_buffer)
2304 {
2305 win_setheight_win(rows, wp);
2306 win_setwidth_win(cols, wp);
2307 }
2308 }
2309 redraw_buf_later(term->tl_buffer, NOT_VALID);
2310 }
2311 return 1;
2312}
2313
2314/*
2315 * Handle a line that is pushed off the top of the screen.
2316 */
2317 static int
2318handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2319{
2320 term_T *term = (term_T *)user;
2321
2322 /* TODO: Limit the number of lines that are stored. */
2323 if (ga_grow(&term->tl_scrollback, 1) == OK)
2324 {
2325 cellattr_T *p = NULL;
2326 int len = 0;
2327 int i;
2328 int c;
2329 int col;
2330 sb_line_T *line;
2331 garray_T ga;
2332 cellattr_T fill_attr = term->tl_default_color;
2333
2334 /* do not store empty cells at the end */
2335 for (i = 0; i < cols; ++i)
2336 if (cells[i].chars[0] != 0)
2337 len = i + 1;
2338 else
2339 cell2cellattr(&cells[i], &fill_attr);
2340
2341 ga_init2(&ga, 1, 100);
2342 if (len > 0)
2343 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2344 if (p != NULL)
2345 {
2346 for (col = 0; col < len; col += cells[col].width)
2347 {
2348 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2349 {
2350 ga.ga_len = 0;
2351 break;
2352 }
2353 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2354 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2355 (char_u *)ga.ga_data + ga.ga_len);
2356 cell2cellattr(&cells[col], &p[col]);
2357 }
2358 }
2359 if (ga_grow(&ga, 1) == FAIL)
2360 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2361 else
2362 {
2363 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2364 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2365 }
2366 ga_clear(&ga);
2367
2368 line = (sb_line_T *)term->tl_scrollback.ga_data
2369 + term->tl_scrollback.ga_len;
2370 line->sb_cols = len;
2371 line->sb_cells = p;
2372 line->sb_fill_attr = fill_attr;
2373 ++term->tl_scrollback.ga_len;
2374 ++term->tl_scrollback_scrolled;
2375 }
2376 return 0; /* ignored */
2377}
2378
2379static VTermScreenCallbacks screen_callbacks = {
2380 handle_damage, /* damage */
2381 handle_moverect, /* moverect */
2382 handle_movecursor, /* movecursor */
2383 handle_settermprop, /* settermprop */
2384 NULL, /* bell */
2385 handle_resize, /* resize */
2386 handle_pushline, /* sb_pushline */
2387 NULL /* sb_popline */
2388};
2389
2390/*
2391 * Called when a channel has been closed.
2392 * If this was a channel for a terminal window then finish it up.
2393 */
2394 void
2395term_channel_closed(channel_T *ch)
2396{
2397 term_T *term;
2398 int did_one = FALSE;
2399
2400 for (term = first_term; term != NULL; term = term->tl_next)
2401 if (term->tl_job == ch->ch_job)
2402 {
2403 term->tl_channel_closed = TRUE;
2404 did_one = TRUE;
2405
Bram Moolenaard23a8232018-02-10 18:45:26 +01002406 VIM_CLEAR(term->tl_title);
2407 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002408
2409 /* Unless in Terminal-Normal mode: clear the vterm. */
2410 if (!term->tl_normal_mode)
2411 {
2412 int fnum = term->tl_buffer->b_fnum;
2413
2414 cleanup_vterm(term);
2415
2416 if (term->tl_finish == 'c')
2417 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002418 aco_save_T aco;
2419
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002420 /* ++close or term_finish == "close" */
2421 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002422 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002423 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002424 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002425 break;
2426 }
2427 if (term->tl_finish == 'o' && term->tl_buffer->b_nwindows == 0)
2428 {
2429 char buf[50];
2430
2431 /* TODO: use term_opencmd */
2432 ch_log(NULL, "terminal job finished, opening window");
2433 vim_snprintf(buf, sizeof(buf),
2434 term->tl_opencmd == NULL
2435 ? "botright sbuf %d"
2436 : (char *)term->tl_opencmd, fnum);
2437 do_cmdline_cmd((char_u *)buf);
2438 }
2439 else
2440 ch_log(NULL, "terminal job finished");
2441 }
2442
2443 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2444 }
2445 if (did_one)
2446 {
2447 redraw_statuslines();
2448
2449 /* Need to break out of vgetc(). */
2450 ins_char_typebuf(K_IGNORE);
2451 typebuf_was_filled = TRUE;
2452
2453 term = curbuf->b_term;
2454 if (term != NULL)
2455 {
2456 if (term->tl_job == ch->ch_job)
2457 maketitle();
2458 update_cursor(term, term->tl_cursor_visible);
2459 }
2460 }
2461}
2462
2463/*
2464 * Called to update a window that contains an active terminal.
2465 * Returns FAIL when there is no terminal running in this window or in
2466 * Terminal-Normal mode.
2467 */
2468 int
2469term_update_window(win_T *wp)
2470{
2471 term_T *term = wp->w_buffer->b_term;
2472 VTerm *vterm;
2473 VTermScreen *screen;
2474 VTermState *state;
2475 VTermPos pos;
2476
2477 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2478 return FAIL;
2479
2480 vterm = term->tl_vterm;
2481 screen = vterm_obtain_screen(vterm);
2482 state = vterm_obtain_state(vterm);
2483
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002484 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002485 {
2486 term->tl_dirty_row_start = 0;
2487 term->tl_dirty_row_end = MAX_ROW;
2488 }
2489
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002490 /*
2491 * If the window was resized a redraw will be triggered and we get here.
2492 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2493 */
2494 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
2495 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
2496 {
2497 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
2498 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
2499 win_T *twp;
2500
2501 FOR_ALL_WINDOWS(twp)
2502 {
2503 /* When more than one window shows the same terminal, use the
2504 * smallest size. */
2505 if (twp->w_buffer == term->tl_buffer)
2506 {
2507 if (!term->tl_rows_fixed && rows > twp->w_height)
2508 rows = twp->w_height;
2509 if (!term->tl_cols_fixed && cols > twp->w_width)
2510 cols = twp->w_width;
2511 }
2512 }
2513
2514 term->tl_vterm_size_changed = TRUE;
2515 vterm_set_size(vterm, rows, cols);
2516 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
2517 rows);
2518 term_report_winsize(term, rows, cols);
2519 }
2520
2521 /* The cursor may have been moved when resizing. */
2522 vterm_state_get_cursorpos(state, &pos);
2523 position_cursor(wp, &pos);
2524
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002525 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2526 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002527 {
2528 int off = screen_get_current_line_off();
2529 int max_col = MIN(wp->w_width, term->tl_cols);
2530
2531 if (pos.row < term->tl_rows)
2532 {
2533 for (pos.col = 0; pos.col < max_col; )
2534 {
2535 VTermScreenCell cell;
2536 int c;
2537
2538 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
2539 vim_memset(&cell, 0, sizeof(cell));
2540
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002541 c = cell.chars[0];
2542 if (c == NUL)
2543 {
2544 ScreenLines[off] = ' ';
2545 if (enc_utf8)
2546 ScreenLinesUC[off] = NUL;
2547 }
2548 else
2549 {
2550 if (enc_utf8)
2551 {
Bram Moolenaar6daeef12017-10-15 22:56:49 +02002552 int i;
2553
2554 /* composing chars */
2555 for (i = 0; i < Screen_mco
2556 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2557 {
2558 ScreenLinesC[i][off] = cell.chars[i + 1];
2559 if (cell.chars[i + 1] == 0)
2560 break;
2561 }
2562 if (c >= 0x80 || (Screen_mco > 0
2563 && ScreenLinesC[0][off] != 0))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002564 {
2565 ScreenLines[off] = ' ';
2566 ScreenLinesUC[off] = c;
2567 }
2568 else
2569 {
2570 ScreenLines[off] = c;
2571 ScreenLinesUC[off] = NUL;
2572 }
2573 }
2574#ifdef WIN3264
2575 else if (has_mbyte && c >= 0x80)
2576 {
2577 char_u mb[MB_MAXBYTES+1];
2578 WCHAR wc = c;
2579
2580 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2581 (char*)mb, 2, 0, 0) > 1)
2582 {
2583 ScreenLines[off] = mb[0];
2584 ScreenLines[off + 1] = mb[1];
2585 cell.width = mb_ptr2cells(mb);
2586 }
2587 else
2588 ScreenLines[off] = c;
2589 }
2590#endif
2591 else
2592 ScreenLines[off] = c;
2593 }
2594 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2595
2596 ++pos.col;
2597 ++off;
2598 if (cell.width == 2)
2599 {
2600 if (enc_utf8)
2601 ScreenLinesUC[off] = NUL;
2602
2603 /* don't set the second byte to NUL for a DBCS encoding, it
2604 * has been set above */
2605 if (enc_utf8 || !has_mbyte)
2606 ScreenLines[off] = NUL;
2607
2608 ++pos.col;
2609 ++off;
2610 }
2611 }
2612 }
2613 else
2614 pos.col = 0;
2615
Bram Moolenaar181ca992018-02-13 21:19:21 +01002616 screen_line(wp->w_winrow + pos.row + winbar_height(wp),
2617 wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002618 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002619 term->tl_dirty_row_start = MAX_ROW;
2620 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002621
2622 return OK;
2623}
2624
2625/*
2626 * Return TRUE if "wp" is a terminal window where the job has finished.
2627 */
2628 int
2629term_is_finished(buf_T *buf)
2630{
2631 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2632}
2633
2634/*
2635 * Return TRUE if "wp" is a terminal window where the job has finished or we
2636 * are in Terminal-Normal mode, thus we show the buffer contents.
2637 */
2638 int
2639term_show_buffer(buf_T *buf)
2640{
2641 term_T *term = buf->b_term;
2642
2643 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2644}
2645
2646/*
2647 * The current buffer is going to be changed. If there is terminal
2648 * highlighting remove it now.
2649 */
2650 void
2651term_change_in_curbuf(void)
2652{
2653 term_T *term = curbuf->b_term;
2654
2655 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2656 {
2657 free_scrollback(term);
2658 redraw_buf_later(term->tl_buffer, NOT_VALID);
2659
2660 /* The buffer is now like a normal buffer, it cannot be easily
2661 * abandoned when changed. */
2662 set_string_option_direct((char_u *)"buftype", -1,
2663 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2664 }
2665}
2666
2667/*
2668 * Get the screen attribute for a position in the buffer.
2669 * Use a negative "col" to get the filler background color.
2670 */
2671 int
2672term_get_attr(buf_T *buf, linenr_T lnum, int col)
2673{
2674 term_T *term = buf->b_term;
2675 sb_line_T *line;
2676 cellattr_T *cellattr;
2677
2678 if (lnum > term->tl_scrollback.ga_len)
2679 cellattr = &term->tl_default_color;
2680 else
2681 {
2682 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
2683 if (col < 0 || col >= line->sb_cols)
2684 cellattr = &line->sb_fill_attr;
2685 else
2686 cellattr = line->sb_cells + col;
2687 }
2688 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
2689}
2690
2691static VTermColor ansi_table[16] = {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002692 { 0, 0, 0, 1}, /* black */
2693 {224, 0, 0, 2}, /* dark red */
2694 { 0, 224, 0, 3}, /* dark green */
2695 {224, 224, 0, 4}, /* dark yellow / brown */
2696 { 0, 0, 224, 5}, /* dark blue */
2697 {224, 0, 224, 6}, /* dark magenta */
2698 { 0, 224, 224, 7}, /* dark cyan */
2699 {224, 224, 224, 8}, /* light grey */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002700
Bram Moolenaar46359e12017-11-29 22:33:38 +01002701 {128, 128, 128, 9}, /* dark grey */
2702 {255, 64, 64, 10}, /* light red */
2703 { 64, 255, 64, 11}, /* light green */
2704 {255, 255, 64, 12}, /* yellow */
2705 { 64, 64, 255, 13}, /* light blue */
2706 {255, 64, 255, 14}, /* light magenta */
2707 { 64, 255, 255, 15}, /* light cyan */
2708 {255, 255, 255, 16}, /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002709};
2710
2711static int cube_value[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002712 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002713};
2714
2715static int grey_ramp[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002716 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
2717 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002718};
2719
2720/*
2721 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002722 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002723 */
2724 static void
2725cterm_color2rgb(int nr, VTermColor *rgb)
2726{
2727 int idx;
2728
2729 if (nr < 16)
2730 {
2731 *rgb = ansi_table[nr];
2732 }
2733 else if (nr < 232)
2734 {
2735 /* 216 color cube */
2736 idx = nr - 16;
2737 rgb->blue = cube_value[idx % 6];
2738 rgb->green = cube_value[idx / 6 % 6];
2739 rgb->red = cube_value[idx / 36 % 6];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002740 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002741 }
2742 else if (nr < 256)
2743 {
2744 /* 24 grey scale ramp */
2745 idx = nr - 232;
2746 rgb->blue = grey_ramp[idx];
2747 rgb->green = grey_ramp[idx];
2748 rgb->red = grey_ramp[idx];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002749 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002750 }
2751}
2752
2753/*
2754 * Create a new vterm and initialize it.
2755 */
2756 static void
2757create_vterm(term_T *term, int rows, int cols)
2758{
2759 VTerm *vterm;
2760 VTermScreen *screen;
2761 VTermValue value;
2762 VTermColor *fg, *bg;
2763 int fgval, bgval;
2764 int id;
2765
2766 vterm = vterm_new(rows, cols);
2767 term->tl_vterm = vterm;
2768 screen = vterm_obtain_screen(vterm);
2769 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
2770 /* TODO: depends on 'encoding'. */
2771 vterm_set_utf8(vterm, 1);
2772
2773 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
2774 term->tl_default_color.width = 1;
2775 fg = &term->tl_default_color.fg;
2776 bg = &term->tl_default_color.bg;
2777
2778 /* Vterm uses a default black background. Set it to white when
2779 * 'background' is "light". */
2780 if (*p_bg == 'l')
2781 {
2782 fgval = 0;
2783 bgval = 255;
2784 }
2785 else
2786 {
2787 fgval = 255;
2788 bgval = 0;
2789 }
2790 fg->red = fg->green = fg->blue = fgval;
2791 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002792 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002793
2794 /* The "Terminal" highlight group overrules the defaults. */
2795 id = syn_name2id((char_u *)"Terminal");
2796
Bram Moolenaar46359e12017-11-29 22:33:38 +01002797 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002798#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
2799 if (0
2800# ifdef FEAT_GUI
2801 || gui.in_use
2802# endif
2803# ifdef FEAT_TERMGUICOLORS
2804 || p_tgc
2805# endif
2806 )
2807 {
2808 guicolor_T fg_rgb = INVALCOLOR;
2809 guicolor_T bg_rgb = INVALCOLOR;
2810
2811 if (id != 0)
2812 syn_id2colors(id, &fg_rgb, &bg_rgb);
2813
2814# ifdef FEAT_GUI
2815 if (gui.in_use)
2816 {
2817 if (fg_rgb == INVALCOLOR)
2818 fg_rgb = gui.norm_pixel;
2819 if (bg_rgb == INVALCOLOR)
2820 bg_rgb = gui.back_pixel;
2821 }
2822# ifdef FEAT_TERMGUICOLORS
2823 else
2824# endif
2825# endif
2826# ifdef FEAT_TERMGUICOLORS
2827 {
2828 if (fg_rgb == INVALCOLOR)
2829 fg_rgb = cterm_normal_fg_gui_color;
2830 if (bg_rgb == INVALCOLOR)
2831 bg_rgb = cterm_normal_bg_gui_color;
2832 }
2833# endif
2834 if (fg_rgb != INVALCOLOR)
2835 {
2836 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
2837
2838 fg->red = (unsigned)(rgb >> 16);
2839 fg->green = (unsigned)(rgb >> 8) & 255;
2840 fg->blue = (unsigned)rgb & 255;
2841 }
2842 if (bg_rgb != INVALCOLOR)
2843 {
2844 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
2845
2846 bg->red = (unsigned)(rgb >> 16);
2847 bg->green = (unsigned)(rgb >> 8) & 255;
2848 bg->blue = (unsigned)rgb & 255;
2849 }
2850 }
2851 else
2852#endif
2853 if (id != 0 && t_colors >= 16)
2854 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002855 if (term_default_cterm_fg >= 0)
2856 cterm_color2rgb(term_default_cterm_fg, fg);
2857 if (term_default_cterm_bg >= 0)
2858 cterm_color2rgb(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002859 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002860 else
2861 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002862#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002863 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002864#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002865
2866 /* In an MS-Windows console we know the normal colors. */
2867 if (cterm_normal_fg_color > 0)
2868 {
2869 cterm_color2rgb(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002870# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002871 tmp = fg->red;
2872 fg->red = fg->blue;
2873 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002874# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002875 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02002876# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002877 else
2878 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02002879# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002880
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002881 if (cterm_normal_bg_color > 0)
2882 {
2883 cterm_color2rgb(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002884# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002885 tmp = bg->red;
2886 bg->red = bg->blue;
2887 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002888# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002889 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02002890# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002891 else
2892 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02002893# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002894 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002895
2896 vterm_state_set_default_colors(vterm_obtain_state(vterm), fg, bg);
2897
2898 /* Required to initialize most things. */
2899 vterm_screen_reset(screen, 1 /* hard */);
2900
2901 /* Allow using alternate screen. */
2902 vterm_screen_enable_altscreen(screen, 1);
2903
2904 /* For unix do not use a blinking cursor. In an xterm this causes the
2905 * cursor to blink if it's blinking in the xterm.
2906 * For Windows we respect the system wide setting. */
2907#ifdef WIN3264
2908 if (GetCaretBlinkTime() == INFINITE)
2909 value.boolean = 0;
2910 else
2911 value.boolean = 1;
2912#else
2913 value.boolean = 0;
2914#endif
2915 vterm_state_set_termprop(vterm_obtain_state(vterm),
2916 VTERM_PROP_CURSORBLINK, &value);
2917}
2918
2919/*
2920 * Return the text to show for the buffer name and status.
2921 */
2922 char_u *
2923term_get_status_text(term_T *term)
2924{
2925 if (term->tl_status_text == NULL)
2926 {
2927 char_u *txt;
2928 size_t len;
2929
2930 if (term->tl_normal_mode)
2931 {
2932 if (term_job_running(term))
2933 txt = (char_u *)_("Terminal");
2934 else
2935 txt = (char_u *)_("Terminal-finished");
2936 }
2937 else if (term->tl_title != NULL)
2938 txt = term->tl_title;
2939 else if (term_none_open(term))
2940 txt = (char_u *)_("active");
2941 else if (term_job_running(term))
2942 txt = (char_u *)_("running");
2943 else
2944 txt = (char_u *)_("finished");
2945 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
2946 term->tl_status_text = alloc((int)len);
2947 if (term->tl_status_text != NULL)
2948 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
2949 term->tl_buffer->b_fname, txt);
2950 }
2951 return term->tl_status_text;
2952}
2953
2954/*
2955 * Mark references in jobs of terminals.
2956 */
2957 int
2958set_ref_in_term(int copyID)
2959{
2960 int abort = FALSE;
2961 term_T *term;
2962 typval_T tv;
2963
2964 for (term = first_term; term != NULL; term = term->tl_next)
2965 if (term->tl_job != NULL)
2966 {
2967 tv.v_type = VAR_JOB;
2968 tv.vval.v_job = term->tl_job;
2969 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
2970 }
2971 return abort;
2972}
2973
2974/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002975 * Cache "Terminal" highlight group colors.
2976 */
2977 void
2978set_terminal_default_colors(int cterm_fg, int cterm_bg)
2979{
2980 term_default_cterm_fg = cterm_fg - 1;
2981 term_default_cterm_bg = cterm_bg - 1;
2982}
2983
2984/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002985 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01002986 * Returns NULL when the buffer is not for a terminal window and logs a message
2987 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002988 */
2989 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01002990term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002991{
2992 buf_T *buf;
2993
2994 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
2995 ++emsg_off;
2996 buf = get_buf_tv(&argvars[0], FALSE);
2997 --emsg_off;
2998 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01002999 {
3000 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003001 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003002 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003003 return buf;
3004}
3005
Bram Moolenaard96ff162018-02-18 22:13:29 +01003006 static int
3007same_color(VTermColor *a, VTermColor *b)
3008{
3009 return a->red == b->red
3010 && a->green == b->green
3011 && a->blue == b->blue
3012 && a->ansi_index == b->ansi_index;
3013}
3014
3015 static void
3016dump_term_color(FILE *fd, VTermColor *color)
3017{
3018 fprintf(fd, "%02x%02x%02x%d",
3019 (int)color->red, (int)color->green, (int)color->blue,
3020 (int)color->ansi_index);
3021}
3022
3023/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003024 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003025 *
3026 * Each screen cell in full is:
3027 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3028 * {characters} is a space for an empty cell
3029 * For a double-width character "+" is changed to "*" and the next cell is
3030 * skipped.
3031 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3032 * when "&" use the same as the previous cell.
3033 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3034 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3035 * {color-idx} is a number from 0 to 255
3036 *
3037 * Screen cell with same width, attributes and color as the previous one:
3038 * |{characters}
3039 *
3040 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3041 *
3042 * Repeating the previous screen cell:
3043 * @{count}
3044 */
3045 void
3046f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3047{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003048 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003049 term_T *term;
3050 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003051 int max_height = 0;
3052 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003053 stat_T st;
3054 FILE *fd;
3055 VTermPos pos;
3056 VTermScreen *screen;
3057 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003058 VTermState *state;
3059 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003060
3061 if (check_restricted() || check_secure())
3062 return;
3063 if (buf == NULL)
3064 return;
3065 term = buf->b_term;
3066
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003067 if (argvars[2].v_type != VAR_UNKNOWN)
3068 {
3069 dict_T *d;
3070
3071 if (argvars[2].v_type != VAR_DICT)
3072 {
3073 EMSG(_(e_dictreq));
3074 return;
3075 }
3076 d = argvars[2].vval.v_dict;
3077 if (d != NULL)
3078 {
3079 max_height = get_dict_number(d, (char_u *)"rows");
3080 max_width = get_dict_number(d, (char_u *)"columns");
3081 }
3082 }
3083
Bram Moolenaard96ff162018-02-18 22:13:29 +01003084 fname = get_tv_string_chk(&argvars[1]);
3085 if (fname == NULL)
3086 return;
3087 if (mch_stat((char *)fname, &st) >= 0)
3088 {
3089 EMSG2(_("E953: File exists: %s"), fname);
3090 return;
3091 }
3092
Bram Moolenaard96ff162018-02-18 22:13:29 +01003093 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3094 {
3095 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3096 return;
3097 }
3098
3099 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3100
3101 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003102 state = vterm_obtain_state(term->tl_vterm);
3103 vterm_state_get_cursorpos(state, &cursor_pos);
3104
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003105 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3106 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003107 {
3108 int repeat = 0;
3109
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003110 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3111 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003112 {
3113 VTermScreenCell cell;
3114 int same_attr;
3115 int same_chars = TRUE;
3116 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003117 int is_cursor_pos = (pos.col == cursor_pos.col
3118 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003119
3120 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3121 vim_memset(&cell, 0, sizeof(cell));
3122
3123 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3124 {
3125 if (cell.chars[i] != prev_cell.chars[i])
3126 same_chars = FALSE;
3127 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3128 break;
3129 }
3130 same_attr = vtermAttr2hl(cell.attrs)
3131 == vtermAttr2hl(prev_cell.attrs)
3132 && same_color(&cell.fg, &prev_cell.fg)
3133 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003134 if (same_chars && cell.width == prev_cell.width && same_attr
3135 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003136 {
3137 ++repeat;
3138 }
3139 else
3140 {
3141 if (repeat > 0)
3142 {
3143 fprintf(fd, "@%d", repeat);
3144 repeat = 0;
3145 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003146 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003147
3148 if (cell.chars[0] == NUL)
3149 fputs(" ", fd);
3150 else
3151 {
3152 char_u charbuf[10];
3153 int len;
3154
3155 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3156 && cell.chars[i] != NUL; ++i)
3157 {
3158 len = utf_char2bytes(cell.chars[0], charbuf);
3159 fwrite(charbuf, len, 1, fd);
3160 }
3161 }
3162
3163 /* When only the characters differ we don't write anything, the
3164 * following "|", "@" or NL will indicate using the same
3165 * attributes. */
3166 if (cell.width != prev_cell.width || !same_attr)
3167 {
3168 if (cell.width == 2)
3169 {
3170 fputs("*", fd);
3171 ++pos.col;
3172 }
3173 else
3174 fputs("+", fd);
3175
3176 if (same_attr)
3177 {
3178 fputs("&", fd);
3179 }
3180 else
3181 {
3182 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3183 if (same_color(&cell.fg, &prev_cell.fg))
3184 fputs("&", fd);
3185 else
3186 {
3187 fputs("#", fd);
3188 dump_term_color(fd, &cell.fg);
3189 }
3190 if (same_color(&cell.bg, &prev_cell.bg))
3191 fputs("&", fd);
3192 else
3193 {
3194 fputs("#", fd);
3195 dump_term_color(fd, &cell.bg);
3196 }
3197 }
3198 }
3199
3200 prev_cell = cell;
3201 }
3202 }
3203 if (repeat > 0)
3204 fprintf(fd, "@%d", repeat);
3205 fputs("\n", fd);
3206 }
3207
3208 fclose(fd);
3209}
3210
3211/*
3212 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3213 */
3214 static void
3215dump_is_corrupt(garray_T *gap)
3216{
3217 ga_concat(gap, (char_u *)"CORRUPT");
3218}
3219
3220 static void
3221append_cell(garray_T *gap, cellattr_T *cell)
3222{
3223 if (ga_grow(gap, 1) == OK)
3224 {
3225 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3226 ++gap->ga_len;
3227 }
3228}
3229
3230/*
3231 * Read the dump file from "fd" and append lines to the current buffer.
3232 * Return the cell width of the longest line.
3233 */
3234 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003235read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003236{
3237 int c;
3238 garray_T ga_text;
3239 garray_T ga_cell;
3240 char_u *prev_char = NULL;
3241 int attr = 0;
3242 cellattr_T cell;
3243 term_T *term = curbuf->b_term;
3244 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003245 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003246
3247 ga_init2(&ga_text, 1, 90);
3248 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3249 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003250 cursor_pos->row = -1;
3251 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003252
3253 c = fgetc(fd);
3254 for (;;)
3255 {
3256 if (c == EOF)
3257 break;
3258 if (c == '\n')
3259 {
3260 /* End of a line: append it to the buffer. */
3261 if (ga_text.ga_data == NULL)
3262 dump_is_corrupt(&ga_text);
3263 if (ga_grow(&term->tl_scrollback, 1) == OK)
3264 {
3265 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3266 + term->tl_scrollback.ga_len;
3267
3268 if (max_cells < ga_cell.ga_len)
3269 max_cells = ga_cell.ga_len;
3270 line->sb_cols = ga_cell.ga_len;
3271 line->sb_cells = ga_cell.ga_data;
3272 line->sb_fill_attr = term->tl_default_color;
3273 ++term->tl_scrollback.ga_len;
3274 ga_init(&ga_cell);
3275
3276 ga_append(&ga_text, NUL);
3277 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3278 ga_text.ga_len, FALSE);
3279 }
3280 else
3281 ga_clear(&ga_cell);
3282 ga_text.ga_len = 0;
3283
3284 c = fgetc(fd);
3285 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003286 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003287 {
3288 int prev_len = ga_text.ga_len;
3289
Bram Moolenaar9271d052018-02-25 21:39:46 +01003290 if (c == '>')
3291 {
3292 if (cursor_pos->row != -1)
3293 dump_is_corrupt(&ga_text); /* duplicate cursor */
3294 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3295 cursor_pos->col = ga_cell.ga_len;
3296 }
3297
Bram Moolenaard96ff162018-02-18 22:13:29 +01003298 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3299 c = fgetc(fd);
3300 if (c != EOF)
3301 ga_append(&ga_text, c);
3302 for (;;)
3303 {
3304 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003305 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003306 || c == EOF || c == '\n')
3307 break;
3308 ga_append(&ga_text, c);
3309 }
3310
3311 /* save the character for repeating it */
3312 vim_free(prev_char);
3313 if (ga_text.ga_data != NULL)
3314 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3315 ga_text.ga_len - prev_len);
3316
Bram Moolenaar9271d052018-02-25 21:39:46 +01003317 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003318 {
3319 /* use all attributes from previous cell */
3320 }
3321 else if (c == '+' || c == '*')
3322 {
3323 int is_bg;
3324
3325 cell.width = c == '+' ? 1 : 2;
3326
3327 c = fgetc(fd);
3328 if (c == '&')
3329 {
3330 /* use same attr as previous cell */
3331 c = fgetc(fd);
3332 }
3333 else if (isdigit(c))
3334 {
3335 /* get the decimal attribute */
3336 attr = 0;
3337 while (isdigit(c))
3338 {
3339 attr = attr * 10 + (c - '0');
3340 c = fgetc(fd);
3341 }
3342 hl2vtermAttr(attr, &cell);
3343 }
3344 else
3345 dump_is_corrupt(&ga_text);
3346
3347 /* is_bg == 0: fg, is_bg == 1: bg */
3348 for (is_bg = 0; is_bg <= 1; ++is_bg)
3349 {
3350 if (c == '&')
3351 {
3352 /* use same color as previous cell */
3353 c = fgetc(fd);
3354 }
3355 else if (c == '#')
3356 {
3357 int red, green, blue, index = 0;
3358
3359 c = fgetc(fd);
3360 red = hex2nr(c);
3361 c = fgetc(fd);
3362 red = (red << 4) + hex2nr(c);
3363 c = fgetc(fd);
3364 green = hex2nr(c);
3365 c = fgetc(fd);
3366 green = (green << 4) + hex2nr(c);
3367 c = fgetc(fd);
3368 blue = hex2nr(c);
3369 c = fgetc(fd);
3370 blue = (blue << 4) + hex2nr(c);
3371 c = fgetc(fd);
3372 if (!isdigit(c))
3373 dump_is_corrupt(&ga_text);
3374 while (isdigit(c))
3375 {
3376 index = index * 10 + (c - '0');
3377 c = fgetc(fd);
3378 }
3379
3380 if (is_bg)
3381 {
3382 cell.bg.red = red;
3383 cell.bg.green = green;
3384 cell.bg.blue = blue;
3385 cell.bg.ansi_index = index;
3386 }
3387 else
3388 {
3389 cell.fg.red = red;
3390 cell.fg.green = green;
3391 cell.fg.blue = blue;
3392 cell.fg.ansi_index = index;
3393 }
3394 }
3395 else
3396 dump_is_corrupt(&ga_text);
3397 }
3398 }
3399 else
3400 dump_is_corrupt(&ga_text);
3401
3402 append_cell(&ga_cell, &cell);
3403 }
3404 else if (c == '@')
3405 {
3406 if (prev_char == NULL)
3407 dump_is_corrupt(&ga_text);
3408 else
3409 {
3410 int count = 0;
3411
3412 /* repeat previous character, get the count */
3413 for (;;)
3414 {
3415 c = fgetc(fd);
3416 if (!isdigit(c))
3417 break;
3418 count = count * 10 + (c - '0');
3419 }
3420
3421 while (count-- > 0)
3422 {
3423 ga_concat(&ga_text, prev_char);
3424 append_cell(&ga_cell, &cell);
3425 }
3426 }
3427 }
3428 else
3429 {
3430 dump_is_corrupt(&ga_text);
3431 c = fgetc(fd);
3432 }
3433 }
3434
3435 if (ga_text.ga_len > 0)
3436 {
3437 /* trailing characters after last NL */
3438 dump_is_corrupt(&ga_text);
3439 ga_append(&ga_text, NUL);
3440 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3441 ga_text.ga_len, FALSE);
3442 }
3443
3444 ga_clear(&ga_text);
3445 vim_free(prev_char);
3446
3447 return max_cells;
3448}
3449
3450/*
3451 * Common for "term_dumpdiff()" and "term_dumpload()".
3452 */
3453 static void
3454term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
3455{
3456 jobopt_T opt;
3457 buf_T *buf;
3458 char_u buf1[NUMBUFLEN];
3459 char_u buf2[NUMBUFLEN];
3460 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003461 char_u *fname2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003462 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003463 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003464 char_u *textline = NULL;
3465
3466 /* First open the files. If this fails bail out. */
3467 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
3468 if (do_diff)
3469 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
3470 if (fname1 == NULL || (do_diff && fname2 == NULL))
3471 {
3472 EMSG(_(e_invarg));
3473 return;
3474 }
3475 fd1 = mch_fopen((char *)fname1, READBIN);
3476 if (fd1 == NULL)
3477 {
3478 EMSG2(_(e_notread), fname1);
3479 return;
3480 }
3481 if (do_diff)
3482 {
3483 fd2 = mch_fopen((char *)fname2, READBIN);
3484 if (fd2 == NULL)
3485 {
3486 fclose(fd1);
3487 EMSG2(_(e_notread), fname2);
3488 return;
3489 }
3490 }
3491
3492 init_job_options(&opt);
3493 /* TODO: use the {options} argument */
3494
3495 /* TODO: use the file name arguments for the buffer name */
3496 opt.jo_term_name = (char_u *)"dump diff";
3497
3498 buf = term_start(&argvars[0], &opt, TRUE, FALSE);
3499 if (buf != NULL && buf->b_term != NULL)
3500 {
3501 int i;
3502 linenr_T bot_lnum;
3503 linenr_T lnum;
3504 term_T *term = buf->b_term;
3505 int width;
3506 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003507 VTermPos cursor_pos1;
3508 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003509
3510 rettv->vval.v_number = buf->b_fnum;
3511
3512 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01003513 width = read_dump_file(fd1, &cursor_pos1);
3514
3515 /* position the cursor */
3516 if (cursor_pos1.row >= 0)
3517 {
3518 curwin->w_cursor.lnum = cursor_pos1.row + 1;
3519 coladvance(cursor_pos1.col);
3520 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003521
3522 /* Delete the empty line that was in the empty buffer. */
3523 ml_delete(1, FALSE);
3524
3525 /* For term_dumpload() we are done here. */
3526 if (!do_diff)
3527 goto theend;
3528
3529 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
3530
3531 textline = alloc(width + 1);
3532 if (textline == NULL)
3533 goto theend;
3534 for (i = 0; i < width; ++i)
3535 textline[i] = '=';
3536 textline[width] = NUL;
3537 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3538 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3539 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3540 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3541
3542 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003543 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003544 if (width2 > width)
3545 {
3546 vim_free(textline);
3547 textline = alloc(width2 + 1);
3548 if (textline == NULL)
3549 goto theend;
3550 width = width2;
3551 textline[width] = NUL;
3552 }
3553 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
3554
3555 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
3556 {
3557 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
3558 {
3559 /* bottom part has fewer rows, fill with "-" */
3560 for (i = 0; i < width; ++i)
3561 textline[i] = '-';
3562 }
3563 else
3564 {
3565 char_u *line1;
3566 char_u *line2;
3567 char_u *p1;
3568 char_u *p2;
3569 int col;
3570 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3571 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
3572 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
3573 ->sb_cells;
3574
3575 /* Make a copy, getting the second line will invalidate it. */
3576 line1 = vim_strsave(ml_get(lnum));
3577 if (line1 == NULL)
3578 break;
3579 p1 = line1;
3580
3581 line2 = ml_get(lnum + bot_lnum);
3582 p2 = line2;
3583 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
3584 {
3585 int len1 = utfc_ptr2len(p1);
3586 int len2 = utfc_ptr2len(p2);
3587
3588 textline[col] = ' ';
3589 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01003590 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01003591 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01003592 else if (lnum == cursor_pos1.row + 1
3593 && col == cursor_pos1.col
3594 && (cursor_pos1.row != cursor_pos2.row
3595 || cursor_pos1.col != cursor_pos2.col))
3596 /* cursor in first but not in second */
3597 textline[col] = '>';
3598 else if (lnum == cursor_pos2.row + 1
3599 && col == cursor_pos2.col
3600 && (cursor_pos1.row != cursor_pos2.row
3601 || cursor_pos1.col != cursor_pos2.col))
3602 /* cursor in second but not in first */
3603 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01003604 else if (cellattr1 != NULL && cellattr2 != NULL)
3605 {
3606 if ((cellattr1 + col)->width
3607 != (cellattr2 + col)->width)
3608 textline[col] = 'w';
3609 else if (!same_color(&(cellattr1 + col)->fg,
3610 &(cellattr2 + col)->fg))
3611 textline[col] = 'f';
3612 else if (!same_color(&(cellattr1 + col)->bg,
3613 &(cellattr2 + col)->bg))
3614 textline[col] = 'b';
3615 else if (vtermAttr2hl((cellattr1 + col)->attrs)
3616 != vtermAttr2hl(((cellattr2 + col)->attrs)))
3617 textline[col] = 'a';
3618 }
3619 p1 += len1;
3620 p2 += len2;
3621 /* TODO: handle different width */
3622 }
3623 vim_free(line1);
3624
3625 while (col < width)
3626 {
3627 if (*p1 == NUL && *p2 == NUL)
3628 textline[col] = '?';
3629 else if (*p1 == NUL)
3630 {
3631 textline[col] = '+';
3632 p2 += utfc_ptr2len(p2);
3633 }
3634 else
3635 {
3636 textline[col] = '-';
3637 p1 += utfc_ptr2len(p1);
3638 }
3639 ++col;
3640 }
3641 }
3642 if (add_empty_scrollback(term, &term->tl_default_color,
3643 term->tl_top_diff_rows) == OK)
3644 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3645 ++bot_lnum;
3646 }
3647
3648 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
3649 {
3650 /* bottom part has more rows, fill with "+" */
3651 for (i = 0; i < width; ++i)
3652 textline[i] = '+';
3653 if (add_empty_scrollback(term, &term->tl_default_color,
3654 term->tl_top_diff_rows) == OK)
3655 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3656 ++lnum;
3657 ++bot_lnum;
3658 }
3659
3660 term->tl_cols = width;
3661 }
3662
3663theend:
3664 vim_free(textline);
3665 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003666 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003667 fclose(fd2);
3668}
3669
3670/*
3671 * If the current buffer shows the output of term_dumpdiff(), swap the top and
3672 * bottom files.
3673 * Return FAIL when this is not possible.
3674 */
3675 int
3676term_swap_diff()
3677{
3678 term_T *term = curbuf->b_term;
3679 linenr_T line_count;
3680 linenr_T top_rows;
3681 linenr_T bot_rows;
3682 linenr_T bot_start;
3683 linenr_T lnum;
3684 char_u *p;
3685 sb_line_T *sb_line;
3686
3687 if (term == NULL
3688 || !term_is_finished(curbuf)
3689 || term->tl_top_diff_rows == 0
3690 || term->tl_scrollback.ga_len == 0)
3691 return FAIL;
3692
3693 line_count = curbuf->b_ml.ml_line_count;
3694 top_rows = term->tl_top_diff_rows;
3695 bot_rows = term->tl_bot_diff_rows;
3696 bot_start = line_count - bot_rows;
3697 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3698
3699 /* move lines from top to above the bottom part */
3700 for (lnum = 1; lnum <= top_rows; ++lnum)
3701 {
3702 p = vim_strsave(ml_get(1));
3703 if (p == NULL)
3704 return OK;
3705 ml_append(bot_start, p, 0, FALSE);
3706 ml_delete(1, FALSE);
3707 vim_free(p);
3708 }
3709
3710 /* move lines from bottom to the top */
3711 for (lnum = 1; lnum <= bot_rows; ++lnum)
3712 {
3713 p = vim_strsave(ml_get(bot_start + lnum));
3714 if (p == NULL)
3715 return OK;
3716 ml_delete(bot_start + lnum, FALSE);
3717 ml_append(lnum - 1, p, 0, FALSE);
3718 vim_free(p);
3719 }
3720
3721 if (top_rows == bot_rows)
3722 {
3723 /* rows counts are equal, can swap cell properties */
3724 for (lnum = 0; lnum < top_rows; ++lnum)
3725 {
3726 sb_line_T temp;
3727
3728 temp = *(sb_line + lnum);
3729 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
3730 *(sb_line + bot_start + lnum) = temp;
3731 }
3732 }
3733 else
3734 {
3735 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
3736 sb_line_T *temp = (sb_line_T *)alloc((int)size);
3737
3738 /* need to copy cell properties into temp memory */
3739 if (temp != NULL)
3740 {
3741 mch_memmove(temp, term->tl_scrollback.ga_data, size);
3742 mch_memmove(term->tl_scrollback.ga_data,
3743 temp + bot_start,
3744 sizeof(sb_line_T) * bot_rows);
3745 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
3746 temp + top_rows,
3747 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
3748 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
3749 + line_count - top_rows,
3750 temp,
3751 sizeof(sb_line_T) * top_rows);
3752 vim_free(temp);
3753 }
3754 }
3755
3756 term->tl_top_diff_rows = bot_rows;
3757 term->tl_bot_diff_rows = top_rows;
3758
3759 update_screen(NOT_VALID);
3760 return OK;
3761}
3762
3763/*
3764 * "term_dumpdiff(filename, filename, options)" function
3765 */
3766 void
3767f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
3768{
3769 term_load_dump(argvars, rettv, TRUE);
3770}
3771
3772/*
3773 * "term_dumpload(filename, options)" function
3774 */
3775 void
3776f_term_dumpload(typval_T *argvars, typval_T *rettv)
3777{
3778 term_load_dump(argvars, rettv, FALSE);
3779}
3780
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003781/*
3782 * "term_getaltscreen(buf)" function
3783 */
3784 void
3785f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
3786{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003787 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003788
3789 if (buf == NULL)
3790 return;
3791 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
3792}
3793
3794/*
3795 * "term_getattr(attr, name)" function
3796 */
3797 void
3798f_term_getattr(typval_T *argvars, typval_T *rettv)
3799{
3800 int attr;
3801 size_t i;
3802 char_u *name;
3803
3804 static struct {
3805 char *name;
3806 int attr;
3807 } attrs[] = {
3808 {"bold", HL_BOLD},
3809 {"italic", HL_ITALIC},
3810 {"underline", HL_UNDERLINE},
3811 {"strike", HL_STRIKETHROUGH},
3812 {"reverse", HL_INVERSE},
3813 };
3814
3815 attr = get_tv_number(&argvars[0]);
3816 name = get_tv_string_chk(&argvars[1]);
3817 if (name == NULL)
3818 return;
3819
3820 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
3821 if (STRCMP(name, attrs[i].name) == 0)
3822 {
3823 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
3824 break;
3825 }
3826}
3827
3828/*
3829 * "term_getcursor(buf)" function
3830 */
3831 void
3832f_term_getcursor(typval_T *argvars, typval_T *rettv)
3833{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003834 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003835 term_T *term;
3836 list_T *l;
3837 dict_T *d;
3838
3839 if (rettv_list_alloc(rettv) == FAIL)
3840 return;
3841 if (buf == NULL)
3842 return;
3843 term = buf->b_term;
3844
3845 l = rettv->vval.v_list;
3846 list_append_number(l, term->tl_cursor_pos.row + 1);
3847 list_append_number(l, term->tl_cursor_pos.col + 1);
3848
3849 d = dict_alloc();
3850 if (d != NULL)
3851 {
3852 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
3853 dict_add_nr_str(d, "blink", blink_state_is_inverted()
3854 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
3855 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
3856 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
3857 ? (char_u *)"" : term->tl_cursor_color);
3858 list_append_dict(l, d);
3859 }
3860}
3861
3862/*
3863 * "term_getjob(buf)" function
3864 */
3865 void
3866f_term_getjob(typval_T *argvars, typval_T *rettv)
3867{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003868 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003869
3870 rettv->v_type = VAR_JOB;
3871 rettv->vval.v_job = NULL;
3872 if (buf == NULL)
3873 return;
3874
3875 rettv->vval.v_job = buf->b_term->tl_job;
3876 if (rettv->vval.v_job != NULL)
3877 ++rettv->vval.v_job->jv_refcount;
3878}
3879
3880 static int
3881get_row_number(typval_T *tv, term_T *term)
3882{
3883 if (tv->v_type == VAR_STRING
3884 && tv->vval.v_string != NULL
3885 && STRCMP(tv->vval.v_string, ".") == 0)
3886 return term->tl_cursor_pos.row;
3887 return (int)get_tv_number(tv) - 1;
3888}
3889
3890/*
3891 * "term_getline(buf, row)" function
3892 */
3893 void
3894f_term_getline(typval_T *argvars, typval_T *rettv)
3895{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003896 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003897 term_T *term;
3898 int row;
3899
3900 rettv->v_type = VAR_STRING;
3901 if (buf == NULL)
3902 return;
3903 term = buf->b_term;
3904 row = get_row_number(&argvars[1], term);
3905
3906 if (term->tl_vterm == NULL)
3907 {
3908 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
3909
3910 /* vterm is finished, get the text from the buffer */
3911 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
3912 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
3913 }
3914 else
3915 {
3916 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
3917 VTermRect rect;
3918 int len;
3919 char_u *p;
3920
3921 if (row < 0 || row >= term->tl_rows)
3922 return;
3923 len = term->tl_cols * MB_MAXBYTES + 1;
3924 p = alloc(len);
3925 if (p == NULL)
3926 return;
3927 rettv->vval.v_string = p;
3928
3929 rect.start_col = 0;
3930 rect.end_col = term->tl_cols;
3931 rect.start_row = row;
3932 rect.end_row = row + 1;
3933 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
3934 }
3935}
3936
3937/*
3938 * "term_getscrolled(buf)" function
3939 */
3940 void
3941f_term_getscrolled(typval_T *argvars, typval_T *rettv)
3942{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003943 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003944
3945 if (buf == NULL)
3946 return;
3947 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
3948}
3949
3950/*
3951 * "term_getsize(buf)" function
3952 */
3953 void
3954f_term_getsize(typval_T *argvars, typval_T *rettv)
3955{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003956 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003957 list_T *l;
3958
3959 if (rettv_list_alloc(rettv) == FAIL)
3960 return;
3961 if (buf == NULL)
3962 return;
3963
3964 l = rettv->vval.v_list;
3965 list_append_number(l, buf->b_term->tl_rows);
3966 list_append_number(l, buf->b_term->tl_cols);
3967}
3968
3969/*
3970 * "term_getstatus(buf)" function
3971 */
3972 void
3973f_term_getstatus(typval_T *argvars, typval_T *rettv)
3974{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003975 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003976 term_T *term;
3977 char_u val[100];
3978
3979 rettv->v_type = VAR_STRING;
3980 if (buf == NULL)
3981 return;
3982 term = buf->b_term;
3983
3984 if (term_job_running(term))
3985 STRCPY(val, "running");
3986 else
3987 STRCPY(val, "finished");
3988 if (term->tl_normal_mode)
3989 STRCAT(val, ",normal");
3990 rettv->vval.v_string = vim_strsave(val);
3991}
3992
3993/*
3994 * "term_gettitle(buf)" function
3995 */
3996 void
3997f_term_gettitle(typval_T *argvars, typval_T *rettv)
3998{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003999 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004000
4001 rettv->v_type = VAR_STRING;
4002 if (buf == NULL)
4003 return;
4004
4005 if (buf->b_term->tl_title != NULL)
4006 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4007}
4008
4009/*
4010 * "term_gettty(buf)" function
4011 */
4012 void
4013f_term_gettty(typval_T *argvars, typval_T *rettv)
4014{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004015 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004016 char_u *p;
4017 int num = 0;
4018
4019 rettv->v_type = VAR_STRING;
4020 if (buf == NULL)
4021 return;
4022 if (argvars[1].v_type != VAR_UNKNOWN)
4023 num = get_tv_number(&argvars[1]);
4024
4025 switch (num)
4026 {
4027 case 0:
4028 if (buf->b_term->tl_job != NULL)
4029 p = buf->b_term->tl_job->jv_tty_out;
4030 else
4031 p = buf->b_term->tl_tty_out;
4032 break;
4033 case 1:
4034 if (buf->b_term->tl_job != NULL)
4035 p = buf->b_term->tl_job->jv_tty_in;
4036 else
4037 p = buf->b_term->tl_tty_in;
4038 break;
4039 default:
4040 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4041 return;
4042 }
4043 if (p != NULL)
4044 rettv->vval.v_string = vim_strsave(p);
4045}
4046
4047/*
4048 * "term_list()" function
4049 */
4050 void
4051f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4052{
4053 term_T *tp;
4054 list_T *l;
4055
4056 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4057 return;
4058
4059 l = rettv->vval.v_list;
4060 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4061 if (tp != NULL && tp->tl_buffer != NULL)
4062 if (list_append_number(l,
4063 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4064 return;
4065}
4066
4067/*
4068 * "term_scrape(buf, row)" function
4069 */
4070 void
4071f_term_scrape(typval_T *argvars, typval_T *rettv)
4072{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004073 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004074 VTermScreen *screen = NULL;
4075 VTermPos pos;
4076 list_T *l;
4077 term_T *term;
4078 char_u *p;
4079 sb_line_T *line;
4080
4081 if (rettv_list_alloc(rettv) == FAIL)
4082 return;
4083 if (buf == NULL)
4084 return;
4085 term = buf->b_term;
4086
4087 l = rettv->vval.v_list;
4088 pos.row = get_row_number(&argvars[1], term);
4089
4090 if (term->tl_vterm != NULL)
4091 {
4092 screen = vterm_obtain_screen(term->tl_vterm);
4093 p = NULL;
4094 line = NULL;
4095 }
4096 else
4097 {
4098 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4099
4100 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4101 return;
4102 p = ml_get_buf(buf, lnum + 1, FALSE);
4103 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4104 }
4105
4106 for (pos.col = 0; pos.col < term->tl_cols; )
4107 {
4108 dict_T *dcell;
4109 int width;
4110 VTermScreenCellAttrs attrs;
4111 VTermColor fg, bg;
4112 char_u rgb[8];
4113 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4114 int off = 0;
4115 int i;
4116
4117 if (screen == NULL)
4118 {
4119 cellattr_T *cellattr;
4120 int len;
4121
4122 /* vterm has finished, get the cell from scrollback */
4123 if (pos.col >= line->sb_cols)
4124 break;
4125 cellattr = line->sb_cells + pos.col;
4126 width = cellattr->width;
4127 attrs = cellattr->attrs;
4128 fg = cellattr->fg;
4129 bg = cellattr->bg;
4130 len = MB_PTR2LEN(p);
4131 mch_memmove(mbs, p, len);
4132 mbs[len] = NUL;
4133 p += len;
4134 }
4135 else
4136 {
4137 VTermScreenCell cell;
4138 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4139 break;
4140 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4141 {
4142 if (cell.chars[i] == 0)
4143 break;
4144 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4145 }
4146 mbs[off] = NUL;
4147 width = cell.width;
4148 attrs = cell.attrs;
4149 fg = cell.fg;
4150 bg = cell.bg;
4151 }
4152 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004153 if (dcell == NULL)
4154 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004155 list_append_dict(l, dcell);
4156
4157 dict_add_nr_str(dcell, "chars", 0, mbs);
4158
4159 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4160 fg.red, fg.green, fg.blue);
4161 dict_add_nr_str(dcell, "fg", 0, rgb);
4162 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4163 bg.red, bg.green, bg.blue);
4164 dict_add_nr_str(dcell, "bg", 0, rgb);
4165
4166 dict_add_nr_str(dcell, "attr",
4167 cell2attr(attrs, fg, bg), NULL);
4168 dict_add_nr_str(dcell, "width", width, NULL);
4169
4170 ++pos.col;
4171 if (width == 2)
4172 ++pos.col;
4173 }
4174}
4175
4176/*
4177 * "term_sendkeys(buf, keys)" function
4178 */
4179 void
4180f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4181{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004182 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004183 char_u *msg;
4184 term_T *term;
4185
4186 rettv->v_type = VAR_UNKNOWN;
4187 if (buf == NULL)
4188 return;
4189
4190 msg = get_tv_string_chk(&argvars[1]);
4191 if (msg == NULL)
4192 return;
4193 term = buf->b_term;
4194 if (term->tl_vterm == NULL)
4195 return;
4196
4197 while (*msg != NUL)
4198 {
4199 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004200 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004201 }
4202}
4203
4204/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004205 * "term_setrestore(buf, command)" function
4206 */
4207 void
4208f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4209{
4210#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004211 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004212 term_T *term;
4213 char_u *cmd;
4214
4215 if (buf == NULL)
4216 return;
4217 term = buf->b_term;
4218 vim_free(term->tl_command);
4219 cmd = get_tv_string_chk(&argvars[1]);
4220 if (cmd != NULL)
4221 term->tl_command = vim_strsave(cmd);
4222 else
4223 term->tl_command = NULL;
4224#endif
4225}
4226
4227/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004228 * "term_setkill(buf, how)" function
4229 */
4230 void
4231f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4232{
4233 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4234 term_T *term;
4235 char_u *how;
4236
4237 if (buf == NULL)
4238 return;
4239 term = buf->b_term;
4240 vim_free(term->tl_kill);
4241 how = get_tv_string_chk(&argvars[1]);
4242 if (how != NULL)
4243 term->tl_kill = vim_strsave(how);
4244 else
4245 term->tl_kill = NULL;
4246}
4247
4248/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004249 * "term_start(command, options)" function
4250 */
4251 void
4252f_term_start(typval_T *argvars, typval_T *rettv)
4253{
4254 jobopt_T opt;
4255 buf_T *buf;
4256
4257 init_job_options(&opt);
4258 if (argvars[1].v_type != VAR_UNKNOWN
4259 && get_job_options(&argvars[1], &opt,
4260 JO_TIMEOUT_ALL + JO_STOPONEXIT
4261 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
4262 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
4263 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
4264 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004265 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004266 + JO2_NORESTORE + JO2_TERM_KILL) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004267 return;
4268
4269 if (opt.jo_vertical)
4270 cmdmod.split = WSP_VERT;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004271 buf = term_start(&argvars[0], &opt, FALSE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004272
4273 if (buf != NULL && buf->b_term != NULL)
4274 rettv->vval.v_number = buf->b_fnum;
4275}
4276
4277/*
4278 * "term_wait" function
4279 */
4280 void
4281f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
4282{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004283 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004284
4285 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004286 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004287 if (buf->b_term->tl_job == NULL)
4288 {
4289 ch_log(NULL, "term_wait(): no job to wait for");
4290 return;
4291 }
4292 if (buf->b_term->tl_job->jv_channel == NULL)
4293 /* channel is closed, nothing to do */
4294 return;
4295
4296 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01004297 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004298 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
4299 {
4300 /* The job is dead, keep reading channel I/O until the channel is
4301 * closed. buf->b_term may become NULL if the terminal was closed while
4302 * waiting. */
4303 ch_log(NULL, "term_wait(): waiting for channel to close");
4304 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
4305 {
4306 mch_check_messages();
4307 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01004308 if (!buf_valid(buf))
4309 /* If the terminal is closed when the channel is closed the
4310 * buffer disappears. */
4311 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004312 ui_delay(10L, FALSE);
4313 }
4314 mch_check_messages();
4315 parse_queued_messages();
4316 }
4317 else
4318 {
4319 long wait = 10L;
4320
4321 mch_check_messages();
4322 parse_queued_messages();
4323
4324 /* Wait for some time for any channel I/O. */
4325 if (argvars[1].v_type != VAR_UNKNOWN)
4326 wait = get_tv_number(&argvars[1]);
4327 ui_delay(wait, TRUE);
4328 mch_check_messages();
4329
4330 /* Flushing messages on channels is hopefully sufficient.
4331 * TODO: is there a better way? */
4332 parse_queued_messages();
4333 }
4334}
4335
4336/*
4337 * Called when a channel has sent all the lines to a terminal.
4338 * Send a CTRL-D to mark the end of the text.
4339 */
4340 void
4341term_send_eof(channel_T *ch)
4342{
4343 term_T *term;
4344
4345 for (term = first_term; term != NULL; term = term->tl_next)
4346 if (term->tl_job == ch->ch_job)
4347 {
4348 if (term->tl_eof_chars != NULL)
4349 {
4350 channel_send(ch, PART_IN, term->tl_eof_chars,
4351 (int)STRLEN(term->tl_eof_chars), NULL);
4352 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
4353 }
4354# ifdef WIN3264
4355 else
4356 /* Default: CTRL-D */
4357 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
4358# endif
4359 }
4360}
4361
4362# if defined(WIN3264) || defined(PROTO)
4363
4364/**************************************
4365 * 2. MS-Windows implementation.
4366 */
4367
4368# ifndef PROTO
4369
4370#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
4371#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01004372#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004373
4374void* (*winpty_config_new)(UINT64, void*);
4375void* (*winpty_open)(void*, void*);
4376void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
4377BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
4378void (*winpty_config_set_mouse_mode)(void*, int);
4379void (*winpty_config_set_initial_size)(void*, int, int);
4380LPCWSTR (*winpty_conin_name)(void*);
4381LPCWSTR (*winpty_conout_name)(void*);
4382LPCWSTR (*winpty_conerr_name)(void*);
4383void (*winpty_free)(void*);
4384void (*winpty_config_free)(void*);
4385void (*winpty_spawn_config_free)(void*);
4386void (*winpty_error_free)(void*);
4387LPCWSTR (*winpty_error_msg)(void*);
4388BOOL (*winpty_set_size)(void*, int, int, void*);
4389HANDLE (*winpty_agent_process)(void*);
4390
4391#define WINPTY_DLL "winpty.dll"
4392
4393static HINSTANCE hWinPtyDLL = NULL;
4394# endif
4395
4396 static int
4397dyn_winpty_init(int verbose)
4398{
4399 int i;
4400 static struct
4401 {
4402 char *name;
4403 FARPROC *ptr;
4404 } winpty_entry[] =
4405 {
4406 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
4407 {"winpty_config_free", (FARPROC*)&winpty_config_free},
4408 {"winpty_config_new", (FARPROC*)&winpty_config_new},
4409 {"winpty_config_set_mouse_mode",
4410 (FARPROC*)&winpty_config_set_mouse_mode},
4411 {"winpty_config_set_initial_size",
4412 (FARPROC*)&winpty_config_set_initial_size},
4413 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
4414 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
4415 {"winpty_error_free", (FARPROC*)&winpty_error_free},
4416 {"winpty_free", (FARPROC*)&winpty_free},
4417 {"winpty_open", (FARPROC*)&winpty_open},
4418 {"winpty_spawn", (FARPROC*)&winpty_spawn},
4419 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
4420 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
4421 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
4422 {"winpty_set_size", (FARPROC*)&winpty_set_size},
4423 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
4424 {NULL, NULL}
4425 };
4426
4427 /* No need to initialize twice. */
4428 if (hWinPtyDLL)
4429 return OK;
4430 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
4431 * winpty.dll. */
4432 if (*p_winptydll != NUL)
4433 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
4434 if (!hWinPtyDLL)
4435 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
4436 if (!hWinPtyDLL)
4437 {
4438 if (verbose)
4439 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
4440 : (char_u *)WINPTY_DLL);
4441 return FAIL;
4442 }
4443 for (i = 0; winpty_entry[i].name != NULL
4444 && winpty_entry[i].ptr != NULL; ++i)
4445 {
4446 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
4447 winpty_entry[i].name)) == NULL)
4448 {
4449 if (verbose)
4450 EMSG2(_(e_loadfunc), winpty_entry[i].name);
4451 return FAIL;
4452 }
4453 }
4454
4455 return OK;
4456}
4457
4458/*
4459 * Create a new terminal of "rows" by "cols" cells.
4460 * Store a reference in "term".
4461 * Return OK or FAIL.
4462 */
4463 static int
4464term_and_job_init(
4465 term_T *term,
4466 typval_T *argvar,
4467 jobopt_T *opt)
4468{
4469 WCHAR *cmd_wchar = NULL;
4470 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004471 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004472 channel_T *channel = NULL;
4473 job_T *job = NULL;
4474 DWORD error;
4475 HANDLE jo = NULL;
4476 HANDLE child_process_handle;
4477 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01004478 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004479 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004480 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004481 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004482
4483 if (dyn_winpty_init(TRUE) == FAIL)
4484 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004485 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
4486 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004487
4488 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004489 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004490 cmd = argvar->vval.v_string;
4491 }
4492 else if (argvar->v_type == VAR_LIST)
4493 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004494 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004495 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004496 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004497 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004498 if (cmd == NULL || *cmd == NUL)
4499 {
4500 EMSG(_(e_invarg));
4501 goto failed;
4502 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004503
4504 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004505 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004506 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004507 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004508 if (opt->jo_cwd != NULL)
4509 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004510
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004511 win32_build_env(opt->jo_env, &ga_env, TRUE);
4512 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004513
4514 job = job_alloc();
4515 if (job == NULL)
4516 goto failed;
4517
4518 channel = add_channel();
4519 if (channel == NULL)
4520 goto failed;
4521
4522 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
4523 if (term->tl_winpty_config == NULL)
4524 goto failed;
4525
4526 winpty_config_set_mouse_mode(term->tl_winpty_config,
4527 WINPTY_MOUSE_MODE_FORCE);
4528 winpty_config_set_initial_size(term->tl_winpty_config,
4529 term->tl_cols, term->tl_rows);
4530 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
4531 if (term->tl_winpty == NULL)
4532 goto failed;
4533
4534 spawn_config = winpty_spawn_config_new(
4535 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
4536 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
4537 NULL,
4538 cmd_wchar,
4539 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004540 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004541 &winpty_err);
4542 if (spawn_config == NULL)
4543 goto failed;
4544
4545 channel = add_channel();
4546 if (channel == NULL)
4547 goto failed;
4548
4549 job = job_alloc();
4550 if (job == NULL)
4551 goto failed;
4552
4553 if (opt->jo_set & JO_IN_BUF)
4554 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
4555
4556 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
4557 &child_thread_handle, &error, &winpty_err))
4558 goto failed;
4559
4560 channel_set_pipes(channel,
4561 (sock_T)CreateFileW(
4562 winpty_conin_name(term->tl_winpty),
4563 GENERIC_WRITE, 0, NULL,
4564 OPEN_EXISTING, 0, NULL),
4565 (sock_T)CreateFileW(
4566 winpty_conout_name(term->tl_winpty),
4567 GENERIC_READ, 0, NULL,
4568 OPEN_EXISTING, 0, NULL),
4569 (sock_T)CreateFileW(
4570 winpty_conerr_name(term->tl_winpty),
4571 GENERIC_READ, 0, NULL,
4572 OPEN_EXISTING, 0, NULL));
4573
4574 /* Write lines with CR instead of NL. */
4575 channel->ch_write_text_mode = TRUE;
4576
4577 jo = CreateJobObject(NULL, NULL);
4578 if (jo == NULL)
4579 goto failed;
4580
4581 if (!AssignProcessToJobObject(jo, child_process_handle))
4582 {
4583 /* Failed, switch the way to terminate process with TerminateProcess. */
4584 CloseHandle(jo);
4585 jo = NULL;
4586 }
4587
4588 winpty_spawn_config_free(spawn_config);
4589 vim_free(cmd_wchar);
4590 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004591 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004592
4593 create_vterm(term, term->tl_rows, term->tl_cols);
4594
4595 channel_set_job(channel, job, opt);
4596 job_set_options(job, opt);
4597
4598 job->jv_channel = channel;
4599 job->jv_proc_info.hProcess = child_process_handle;
4600 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
4601 job->jv_job_object = jo;
4602 job->jv_status = JOB_STARTED;
4603 job->jv_tty_in = utf16_to_enc(
4604 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
4605 job->jv_tty_out = utf16_to_enc(
4606 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
4607 ++job->jv_refcount;
4608 term->tl_job = job;
4609
4610 return OK;
4611
4612failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004613 ga_clear(&ga_cmd);
4614 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004615 vim_free(cmd_wchar);
4616 vim_free(cwd_wchar);
4617 if (spawn_config != NULL)
4618 winpty_spawn_config_free(spawn_config);
4619 if (channel != NULL)
4620 channel_clear(channel);
4621 if (job != NULL)
4622 {
4623 job->jv_channel = NULL;
4624 job_cleanup(job);
4625 }
4626 term->tl_job = NULL;
4627 if (jo != NULL)
4628 CloseHandle(jo);
4629 if (term->tl_winpty != NULL)
4630 winpty_free(term->tl_winpty);
4631 term->tl_winpty = NULL;
4632 if (term->tl_winpty_config != NULL)
4633 winpty_config_free(term->tl_winpty_config);
4634 term->tl_winpty_config = NULL;
4635 if (winpty_err != NULL)
4636 {
4637 char_u *msg = utf16_to_enc(
4638 (short_u *)winpty_error_msg(winpty_err), NULL);
4639
4640 EMSG(msg);
4641 winpty_error_free(winpty_err);
4642 }
4643 return FAIL;
4644}
4645
4646 static int
4647create_pty_only(term_T *term, jobopt_T *options)
4648{
4649 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
4650 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
4651 char in_name[80], out_name[80];
4652 channel_T *channel = NULL;
4653
4654 create_vterm(term, term->tl_rows, term->tl_cols);
4655
4656 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
4657 GetCurrentProcessId(),
4658 curbuf->b_fnum);
4659 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
4660 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4661 PIPE_UNLIMITED_INSTANCES,
4662 0, 0, NMPWAIT_NOWAIT, NULL);
4663 if (hPipeIn == INVALID_HANDLE_VALUE)
4664 goto failed;
4665
4666 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
4667 GetCurrentProcessId(),
4668 curbuf->b_fnum);
4669 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
4670 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4671 PIPE_UNLIMITED_INSTANCES,
4672 0, 0, 0, NULL);
4673 if (hPipeOut == INVALID_HANDLE_VALUE)
4674 goto failed;
4675
4676 ConnectNamedPipe(hPipeIn, NULL);
4677 ConnectNamedPipe(hPipeOut, NULL);
4678
4679 term->tl_job = job_alloc();
4680 if (term->tl_job == NULL)
4681 goto failed;
4682 ++term->tl_job->jv_refcount;
4683
4684 /* behave like the job is already finished */
4685 term->tl_job->jv_status = JOB_FINISHED;
4686
4687 channel = add_channel();
4688 if (channel == NULL)
4689 goto failed;
4690 term->tl_job->jv_channel = channel;
4691 channel->ch_keep_open = TRUE;
4692 channel->ch_named_pipe = TRUE;
4693
4694 channel_set_pipes(channel,
4695 (sock_T)hPipeIn,
4696 (sock_T)hPipeOut,
4697 (sock_T)hPipeOut);
4698 channel_set_job(channel, term->tl_job, options);
4699 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
4700 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
4701
4702 return OK;
4703
4704failed:
4705 if (hPipeIn != NULL)
4706 CloseHandle(hPipeIn);
4707 if (hPipeOut != NULL)
4708 CloseHandle(hPipeOut);
4709 return FAIL;
4710}
4711
4712/*
4713 * Free the terminal emulator part of "term".
4714 */
4715 static void
4716term_free_vterm(term_T *term)
4717{
4718 if (term->tl_winpty != NULL)
4719 winpty_free(term->tl_winpty);
4720 term->tl_winpty = NULL;
4721 if (term->tl_winpty_config != NULL)
4722 winpty_config_free(term->tl_winpty_config);
4723 term->tl_winpty_config = NULL;
4724 if (term->tl_vterm != NULL)
4725 vterm_free(term->tl_vterm);
4726 term->tl_vterm = NULL;
4727}
4728
4729/*
4730 * Request size to terminal.
4731 */
4732 static void
4733term_report_winsize(term_T *term, int rows, int cols)
4734{
4735 if (term->tl_winpty)
4736 winpty_set_size(term->tl_winpty, cols, rows, NULL);
4737}
4738
4739 int
4740terminal_enabled(void)
4741{
4742 return dyn_winpty_init(FALSE) == OK;
4743}
4744
4745# else
4746
4747/**************************************
4748 * 3. Unix-like implementation.
4749 */
4750
4751/*
4752 * Create a new terminal of "rows" by "cols" cells.
4753 * Start job for "cmd".
4754 * Store the pointers in "term".
4755 * Return OK or FAIL.
4756 */
4757 static int
4758term_and_job_init(
4759 term_T *term,
4760 typval_T *argvar,
4761 jobopt_T *opt)
4762{
4763 create_vterm(term, term->tl_rows, term->tl_cols);
4764
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004765 /* This will change a string in "argvar". */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004766 term->tl_job = job_start(argvar, opt);
4767 if (term->tl_job != NULL)
4768 ++term->tl_job->jv_refcount;
4769
4770 return term->tl_job != NULL
4771 && term->tl_job->jv_channel != NULL
4772 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
4773}
4774
4775 static int
4776create_pty_only(term_T *term, jobopt_T *opt)
4777{
4778 create_vterm(term, term->tl_rows, term->tl_cols);
4779
4780 term->tl_job = job_alloc();
4781 if (term->tl_job == NULL)
4782 return FAIL;
4783 ++term->tl_job->jv_refcount;
4784
4785 /* behave like the job is already finished */
4786 term->tl_job->jv_status = JOB_FINISHED;
4787
4788 return mch_create_pty_channel(term->tl_job, opt);
4789}
4790
4791/*
4792 * Free the terminal emulator part of "term".
4793 */
4794 static void
4795term_free_vterm(term_T *term)
4796{
4797 if (term->tl_vterm != NULL)
4798 vterm_free(term->tl_vterm);
4799 term->tl_vterm = NULL;
4800}
4801
4802/*
4803 * Request size to terminal.
4804 */
4805 static void
4806term_report_winsize(term_T *term, int rows, int cols)
4807{
4808 /* Use an ioctl() to report the new window size to the job. */
4809 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
4810 {
4811 int fd = -1;
4812 int part;
4813
4814 for (part = PART_OUT; part < PART_COUNT; ++part)
4815 {
4816 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
4817 if (isatty(fd))
4818 break;
4819 }
4820 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
4821 mch_signal_job(term->tl_job, (char_u *)"winch");
4822 }
4823}
4824
4825# endif
4826
4827#endif /* FEAT_TERMINAL */