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