blob: 89a354175fc06c7fea345483984464438f559d08 [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 Moolenaar13568252018-03-16 20:46:58 +010041 * - Make terminal close by default when started without a command. Add
42 * ++noclose argument.
43 * - Win32: In the GUI use a terminal emulator for :!cmd.
44 * - Add a way to set the 16 ANSI colors, to be used for 'termguicolors' and in
45 * the GUI.
46 * - Some way for the job running in the terminal to send a :drop command back
47 * to the Vim running the terminal. Should be usable by a simple shell or
48 * python script.
Bram Moolenaarb852c3e2018-03-11 16:55:36 +010049 * - implement term_setsize()
50 * - Copy text in the vterm to the Vim buffer once in a while, so that
51 * completion works.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +010052 * - Adding WinBar to terminal window doesn't display, text isn't shifted down.
Bram Moolenaar46359e12017-11-29 22:33:38 +010053 * a job that uses 16 colors while Vim is using > 256.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020054 * - in GUI vertical split causes problems. Cursor is flickering. (Hirohito
55 * Higashi, 2017 Sep 19)
Bram Moolenaar3a497e12017-09-30 20:40:27 +020056 * - after resizing windows overlap. (Boris Staletic, #2164)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020057 * - Redirecting output does not work on MS-Windows, Test_terminal_redir_file()
58 * is disabled.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020059 * - cursor blinks in terminal on widows with a timer. (xtal8, #2142)
Bram Moolenaarba6febd2017-10-30 21:56:23 +010060 * - Termdebug does not work when Vim build with mzscheme. gdb hangs.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020061 * - MS-Windows GUI: WinBar has tearoff item
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020062 * - MS-Windows GUI: still need to type a key after shell exits? #1924
Bram Moolenaar51b0f372017-11-18 18:52:04 +010063 * - After executing a shell command the status line isn't redraw.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020064 * - add test for giving error for invalid 'termsize' value.
65 * - support minimal size when 'termsize' is "rows*cols".
66 * - support minimal size when 'termsize' is empty?
67 * - GUI: when using tabs, focus in terminal, click on tab does not work.
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020068 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020069 * - For the GUI fill termios with default values, perhaps like pangoterm:
70 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020071 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
72 * conversions.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020073 * - add an optional limit for the scrollback size. When reaching it remove
74 * 10% at the start.
75 */
76
77#include "vim.h"
78
79#if defined(FEAT_TERMINAL) || defined(PROTO)
80
81#ifndef MIN
82# define MIN(x,y) ((x) < (y) ? (x) : (y))
83#endif
84#ifndef MAX
85# define MAX(x,y) ((x) > (y) ? (x) : (y))
86#endif
87
88#include "libvterm/include/vterm.h"
89
90/* This is VTermScreenCell without the characters, thus much smaller. */
91typedef struct {
92 VTermScreenCellAttrs attrs;
93 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010094 VTermColor fg;
95 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020096} cellattr_T;
97
98typedef struct sb_line_S {
99 int sb_cols; /* can differ per line */
100 cellattr_T *sb_cells; /* allocated */
101 cellattr_T sb_fill_attr; /* for short line */
102} sb_line_T;
103
104/* typedef term_T in structs.h */
105struct terminal_S {
106 term_T *tl_next;
107
108 VTerm *tl_vterm;
109 job_T *tl_job;
110 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +0100111#if defined(FEAT_GUI)
112 int tl_system; /* when non-zero used for :!cmd output */
113 int tl_toprow; /* row with first line of system terminal */
114#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200115
116 /* Set when setting the size of a vterm, reset after redrawing. */
117 int tl_vterm_size_changed;
118
119 /* used when tl_job is NULL and only a pty was created */
120 int tl_tty_fd;
121 char_u *tl_tty_in;
122 char_u *tl_tty_out;
123
124 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
125 int tl_channel_closed;
126 int tl_finish; /* 'c' for ++close, 'o' for ++open */
127 char_u *tl_opencmd;
128 char_u *tl_eof_chars;
129
130#ifdef WIN3264
131 void *tl_winpty_config;
132 void *tl_winpty;
133#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100134#if defined(FEAT_SESSION)
135 char_u *tl_command;
136#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100137 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200138
139 /* last known vterm size */
140 int tl_rows;
141 int tl_cols;
142 /* vterm size does not follow window size */
143 int tl_rows_fixed;
144 int tl_cols_fixed;
145
146 char_u *tl_title; /* NULL or allocated */
147 char_u *tl_status_text; /* NULL or allocated */
148
149 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200150 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200151 int tl_dirty_row_end; /* row below last one to update */
152
153 garray_T tl_scrollback;
154 int tl_scrollback_scrolled;
155 cellattr_T tl_default_color;
156
Bram Moolenaard96ff162018-02-18 22:13:29 +0100157 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
158 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
159
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200160 VTermPos tl_cursor_pos;
161 int tl_cursor_visible;
162 int tl_cursor_blink;
163 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
164 char_u *tl_cursor_color; /* NULL or allocated */
165
166 int tl_using_altscreen;
167};
168
169#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
170#define TMODE_LOOP 2 /* CTRL-W N used */
171
172/*
173 * List of all active terminals.
174 */
175static term_T *first_term = NULL;
176
177/* Terminal active in terminal_loop(). */
178static term_T *in_terminal_loop = NULL;
179
180#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
181#define KEY_BUF_LEN 200
182
183/*
184 * Functions with separate implementation for MS-Windows and Unix-like systems.
185 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100186static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200187static int create_pty_only(term_T *term, jobopt_T *opt);
188static void term_report_winsize(term_T *term, int rows, int cols);
189static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100190#ifdef FEAT_GUI
191static void update_system_term(term_T *term);
192#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200193
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{
Bram Moolenaar13568252018-03-16 20:46:58 +0100223#ifdef FEAT_GUI
224 if (term->tl_system)
225 {
226 /* Use the whole screen for the system command. However, it will start
227 * at the command line and scroll up as needed, using tl_toprow. */
228 term->tl_rows = Rows;
229 term->tl_cols = Columns;
230 }
231 else
232#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200233 if (*curwin->w_p_tms != NUL)
234 {
235 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
236
237 term->tl_rows = atoi((char *)curwin->w_p_tms);
238 term->tl_cols = atoi((char *)p);
239 }
240 if (term->tl_rows == 0)
241 term->tl_rows = curwin->w_height;
242 else
243 {
244 win_setheight_win(term->tl_rows, curwin);
245 term->tl_rows_fixed = TRUE;
246 }
247 if (term->tl_cols == 0)
248 term->tl_cols = curwin->w_width;
249 else
250 {
251 win_setwidth_win(term->tl_cols, curwin);
252 term->tl_cols_fixed = TRUE;
253 }
254}
255
256/*
257 * Initialize job options for a terminal job.
258 * Caller may overrule some of them.
259 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100260 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200261init_job_options(jobopt_T *opt)
262{
263 clear_job_options(opt);
264
265 opt->jo_mode = MODE_RAW;
266 opt->jo_out_mode = MODE_RAW;
267 opt->jo_err_mode = MODE_RAW;
268 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
269}
270
271/*
272 * Set job options mandatory for a terminal job.
273 */
274 static void
275setup_job_options(jobopt_T *opt, int rows, int cols)
276{
277 if (!(opt->jo_set & JO_OUT_IO))
278 {
279 /* Connect stdout to the terminal. */
280 opt->jo_io[PART_OUT] = JIO_BUFFER;
281 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
282 opt->jo_modifiable[PART_OUT] = 0;
283 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
284 }
285
286 if (!(opt->jo_set & JO_ERR_IO))
287 {
288 /* Connect stderr to the terminal. */
289 opt->jo_io[PART_ERR] = JIO_BUFFER;
290 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
291 opt->jo_modifiable[PART_ERR] = 0;
292 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
293 }
294
295 opt->jo_pty = TRUE;
296 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
297 opt->jo_term_rows = rows;
298 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
299 opt->jo_term_cols = cols;
300}
301
302/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100303 * Close a terminal buffer (and its window). Used when creating the terminal
304 * fails.
305 */
306 static void
307term_close_buffer(buf_T *buf, buf_T *old_curbuf)
308{
309 free_terminal(buf);
310 if (old_curbuf != NULL)
311 {
312 --curbuf->b_nwindows;
313 curbuf = old_curbuf;
314 curwin->w_buffer = curbuf;
315 ++curbuf->b_nwindows;
316 }
317
318 /* Wiping out the buffer will also close the window and call
319 * free_terminal(). */
320 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
321}
322
323/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200324 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100325 * Use either "argvar" or "argv", the other must be NULL.
326 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
327 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200328 * Returns NULL when failed.
329 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100330 buf_T *
331term_start(
332 typval_T *argvar,
333 char **argv,
334 jobopt_T *opt,
335 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200336{
337 exarg_T split_ea;
338 win_T *old_curwin = curwin;
339 term_T *term;
340 buf_T *old_curbuf = NULL;
341 int res;
342 buf_T *newbuf;
343
344 if (check_restricted() || check_secure())
345 return NULL;
346
347 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
348 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
349 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
350 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
351 {
352 EMSG(_(e_invarg));
353 return NULL;
354 }
355
356 term = (term_T *)alloc_clear(sizeof(term_T));
357 if (term == NULL)
358 return NULL;
359 term->tl_dirty_row_end = MAX_ROW;
360 term->tl_cursor_visible = TRUE;
361 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
362 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100363#ifdef FEAT_GUI
364 term->tl_system = (flags & TERM_START_SYSTEM);
365#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200366 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
367
368 vim_memset(&split_ea, 0, sizeof(split_ea));
369 if (opt->jo_curwin)
370 {
371 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100372 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200373 {
374 no_write_message();
375 vim_free(term);
376 return NULL;
377 }
378 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100379 ECMD_HIDE
380 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
381 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200382 {
383 vim_free(term);
384 return NULL;
385 }
386 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100387 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200388 {
389 buf_T *buf;
390
391 /* Create a new buffer without a window. Make it the current buffer for
392 * a moment to be able to do the initialisations. */
393 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
394 BLN_NEW | BLN_LISTED);
395 if (buf == NULL || ml_open(buf) == FAIL)
396 {
397 vim_free(term);
398 return NULL;
399 }
400 old_curbuf = curbuf;
401 --curbuf->b_nwindows;
402 curbuf = buf;
403 curwin->w_buffer = buf;
404 ++curbuf->b_nwindows;
405 }
406 else
407 {
408 /* Open a new window or tab. */
409 split_ea.cmdidx = CMD_new;
410 split_ea.cmd = (char_u *)"new";
411 split_ea.arg = (char_u *)"";
412 if (opt->jo_term_rows > 0 && !(cmdmod.split & WSP_VERT))
413 {
414 split_ea.line2 = opt->jo_term_rows;
415 split_ea.addr_count = 1;
416 }
417 if (opt->jo_term_cols > 0 && (cmdmod.split & WSP_VERT))
418 {
419 split_ea.line2 = opt->jo_term_cols;
420 split_ea.addr_count = 1;
421 }
422
423 ex_splitview(&split_ea);
424 if (curwin == old_curwin)
425 {
426 /* split failed */
427 vim_free(term);
428 return NULL;
429 }
430 }
431 term->tl_buffer = curbuf;
432 curbuf->b_term = term;
433
434 if (!opt->jo_hidden)
435 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100436 /* Only one size was taken care of with :new, do the other one. With
437 * "curwin" both need to be done. */
438 if (opt->jo_term_rows > 0 && (opt->jo_curwin
439 || (cmdmod.split & WSP_VERT)))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200440 win_setheight(opt->jo_term_rows);
Bram Moolenaarda650582018-02-20 15:51:40 +0100441 if (opt->jo_term_cols > 0 && (opt->jo_curwin
442 || !(cmdmod.split & WSP_VERT)))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200443 win_setwidth(opt->jo_term_cols);
444 }
445
446 /* Link the new terminal in the list of active terminals. */
447 term->tl_next = first_term;
448 first_term = term;
449
450 if (opt->jo_term_name != NULL)
451 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100452 else if (argv != NULL)
453 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200454 else
455 {
456 int i;
457 size_t len;
458 char_u *cmd, *p;
459
460 if (argvar->v_type == VAR_STRING)
461 {
462 cmd = argvar->vval.v_string;
463 if (cmd == NULL)
464 cmd = (char_u *)"";
465 else if (STRCMP(cmd, "NONE") == 0)
466 cmd = (char_u *)"pty";
467 }
468 else if (argvar->v_type != VAR_LIST
469 || argvar->vval.v_list == NULL
470 || argvar->vval.v_list->lv_len < 1
471 || (cmd = get_tv_string_chk(
472 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
473 cmd = (char_u*)"";
474
475 len = STRLEN(cmd) + 10;
476 p = alloc((int)len);
477
478 for (i = 0; p != NULL; ++i)
479 {
480 /* Prepend a ! to the command name to avoid the buffer name equals
481 * the executable, otherwise ":w!" would overwrite it. */
482 if (i == 0)
483 vim_snprintf((char *)p, len, "!%s", cmd);
484 else
485 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
486 if (buflist_findname(p) == NULL)
487 {
488 vim_free(curbuf->b_ffname);
489 curbuf->b_ffname = p;
490 break;
491 }
492 }
493 }
494 curbuf->b_fname = curbuf->b_ffname;
495
496 if (opt->jo_term_opencmd != NULL)
497 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
498
499 if (opt->jo_eof_chars != NULL)
500 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
501
502 set_string_option_direct((char_u *)"buftype", -1,
503 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
504
505 /* Mark the buffer as not modifiable. It can only be made modifiable after
506 * the job finished. */
507 curbuf->b_p_ma = FALSE;
508
509 set_term_and_win_size(term);
510 setup_job_options(opt, term->tl_rows, term->tl_cols);
511
Bram Moolenaar13568252018-03-16 20:46:58 +0100512 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100513 return curbuf;
514
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100515#if defined(FEAT_SESSION)
516 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100517 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100518 {
519 term->tl_command = vim_strsave((char_u *)"NONE");
520 }
521 else if (argvar->v_type == VAR_STRING)
522 {
523 char_u *cmd = argvar->vval.v_string;
524
525 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
526 term->tl_command = vim_strsave(cmd);
527 }
528 else if (argvar->v_type == VAR_LIST
529 && argvar->vval.v_list != NULL
530 && argvar->vval.v_list->lv_len > 0)
531 {
532 garray_T ga;
533 listitem_T *item;
534
535 ga_init2(&ga, 1, 100);
536 for (item = argvar->vval.v_list->lv_first;
537 item != NULL; item = item->li_next)
538 {
539 char_u *s = get_tv_string_chk(&item->li_tv);
540 char_u *p;
541
542 if (s == NULL)
543 break;
544 p = vim_strsave_fnameescape(s, FALSE);
545 if (p == NULL)
546 break;
547 ga_concat(&ga, p);
548 vim_free(p);
549 ga_append(&ga, ' ');
550 }
551 if (item == NULL)
552 {
553 ga_append(&ga, NUL);
554 term->tl_command = ga.ga_data;
555 }
556 else
557 ga_clear(&ga);
558 }
559#endif
560
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100561 if (opt->jo_term_kill != NULL)
562 {
563 char_u *p = skiptowhite(opt->jo_term_kill);
564
565 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
566 }
567
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200568 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100569 if (argv == NULL
570 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200571 && argvar->vval.v_string != NULL
572 && STRCMP(argvar->vval.v_string, "NONE") == 0)
573 res = create_pty_only(term, opt);
574 else
Bram Moolenaar13568252018-03-16 20:46:58 +0100575 res = term_and_job_init(term, argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200576
577 newbuf = curbuf;
578 if (res == OK)
579 {
580 /* Get and remember the size we ended up with. Update the pty. */
581 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
582 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100583#ifdef FEAT_GUI
584 if (term->tl_system)
585 {
586 /* display first line below typed command */
587 term->tl_toprow = msg_row + 1;
588 term->tl_dirty_row_end = 0;
589 }
590#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200591
592 /* Make sure we don't get stuck on sending keys to the job, it leads to
593 * a deadlock if the job is waiting for Vim to read. */
594 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
595
Bram Moolenaar13568252018-03-16 20:46:58 +0100596 if (old_curbuf == NULL)
Bram Moolenaarab5e7c32018-02-13 14:07:18 +0100597 {
598 ++curbuf->b_locked;
599 apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
600 --curbuf->b_locked;
601 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100602 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200603 {
604 --curbuf->b_nwindows;
605 curbuf = old_curbuf;
606 curwin->w_buffer = curbuf;
607 ++curbuf->b_nwindows;
608 }
609 }
610 else
611 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100612 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200613 return NULL;
614 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100615
Bram Moolenaar13568252018-03-16 20:46:58 +0100616 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200617 return newbuf;
618}
619
620/*
621 * ":terminal": open a terminal window and execute a job in it.
622 */
623 void
624ex_terminal(exarg_T *eap)
625{
626 typval_T argvar[2];
627 jobopt_T opt;
628 char_u *cmd;
629 char_u *tofree = NULL;
630
631 init_job_options(&opt);
632
633 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100634 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200635 {
636 char_u *p, *ep;
637
638 cmd += 2;
639 p = skiptowhite(cmd);
640 ep = vim_strchr(cmd, '=');
641 if (ep != NULL && ep < p)
642 p = ep;
643
644 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
645 opt.jo_term_finish = 'c';
646 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
647 opt.jo_term_finish = 'o';
648 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
649 opt.jo_curwin = 1;
650 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
651 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100652 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
653 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100654 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
655 && ep != NULL)
656 {
657 opt.jo_set2 |= JO2_TERM_KILL;
658 opt.jo_term_kill = ep + 1;
659 p = skiptowhite(cmd);
660 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200661 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
662 && ep != NULL && isdigit(ep[1]))
663 {
664 opt.jo_set2 |= JO2_TERM_ROWS;
665 opt.jo_term_rows = atoi((char *)ep + 1);
666 p = skiptowhite(cmd);
667 }
668 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
669 && ep != NULL && isdigit(ep[1]))
670 {
671 opt.jo_set2 |= JO2_TERM_COLS;
672 opt.jo_term_cols = atoi((char *)ep + 1);
673 p = skiptowhite(cmd);
674 }
675 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
676 && ep != NULL)
677 {
678 char_u *buf = NULL;
679 char_u *keys;
680
681 p = skiptowhite(cmd);
682 *p = NUL;
683 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
684 opt.jo_set2 |= JO2_EOF_CHARS;
685 opt.jo_eof_chars = vim_strsave(keys);
686 vim_free(buf);
687 *p = ' ';
688 }
689 else
690 {
691 if (*p)
692 *p = NUL;
693 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100694 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200695 }
696 cmd = skipwhite(p);
697 }
698 if (*cmd == NUL)
699 /* Make a copy of 'shell', an autocommand may change the option. */
700 tofree = cmd = vim_strsave(p_sh);
701
702 if (eap->addr_count > 0)
703 {
704 /* Write lines from current buffer to the job. */
705 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
706 opt.jo_io[PART_IN] = JIO_BUFFER;
707 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
708 opt.jo_in_top = eap->line1;
709 opt.jo_in_bot = eap->line2;
710 }
711
712 argvar[0].v_type = VAR_STRING;
713 argvar[0].vval.v_string = cmd;
714 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100715 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200716 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100717
718theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200719 vim_free(opt.jo_eof_chars);
720}
721
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100722#if defined(FEAT_SESSION) || defined(PROTO)
723/*
724 * Write a :terminal command to the session file to restore the terminal in
725 * window "wp".
726 * Return FAIL if writing fails.
727 */
728 int
729term_write_session(FILE *fd, win_T *wp)
730{
731 term_T *term = wp->w_buffer->b_term;
732
733 /* Create the terminal and run the command. This is not without
734 * risk, but let's assume the user only creates a session when this
735 * will be OK. */
736 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
737 term->tl_cols, term->tl_rows) < 0)
738 return FAIL;
739 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
740 return FAIL;
741
742 return put_eol(fd);
743}
744
745/*
746 * Return TRUE if "buf" has a terminal that should be restored.
747 */
748 int
749term_should_restore(buf_T *buf)
750{
751 term_T *term = buf->b_term;
752
753 return term != NULL && (term->tl_command == NULL
754 || STRCMP(term->tl_command, "NONE") != 0);
755}
756#endif
757
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200758/*
759 * Free the scrollback buffer for "term".
760 */
761 static void
762free_scrollback(term_T *term)
763{
764 int i;
765
766 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
767 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
768 ga_clear(&term->tl_scrollback);
769}
770
771/*
772 * Free a terminal and everything it refers to.
773 * Kills the job if there is one.
774 * Called when wiping out a buffer.
775 */
776 void
777free_terminal(buf_T *buf)
778{
779 term_T *term = buf->b_term;
780 term_T *tp;
781
782 if (term == NULL)
783 return;
784 if (first_term == term)
785 first_term = term->tl_next;
786 else
787 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
788 if (tp->tl_next == term)
789 {
790 tp->tl_next = term->tl_next;
791 break;
792 }
793
794 if (term->tl_job != NULL)
795 {
796 if (term->tl_job->jv_status != JOB_ENDED
797 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100798 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200799 job_stop(term->tl_job, NULL, "kill");
800 job_unref(term->tl_job);
801 }
802
803 free_scrollback(term);
804
805 term_free_vterm(term);
806 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100807#ifdef FEAT_SESSION
808 vim_free(term->tl_command);
809#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100810 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200811 vim_free(term->tl_status_text);
812 vim_free(term->tl_opencmd);
813 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100814 if (desired_cursor_color == term->tl_cursor_color)
815 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200816 vim_free(term->tl_cursor_color);
817 vim_free(term);
818 buf->b_term = NULL;
819 if (in_terminal_loop == term)
820 in_terminal_loop = NULL;
821}
822
823/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100824 * Get the part that is connected to the tty. Normally this is PART_IN, but
825 * when writing buffer lines to the job it can be another. This makes it
826 * possible to do "1,5term vim -".
827 */
828 static ch_part_T
829get_tty_part(term_T *term)
830{
831#ifdef UNIX
832 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
833 int i;
834
835 for (i = 0; i < 3; ++i)
836 {
837 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
838
839 if (isatty(fd))
840 return parts[i];
841 }
842#endif
843 return PART_IN;
844}
845
846/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200847 * Write job output "msg[len]" to the vterm.
848 */
849 static void
850term_write_job_output(term_T *term, char_u *msg, size_t len)
851{
852 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100853 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200854
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100855 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200856
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100857 /* flush vterm buffer when vterm responded to control sequence */
858 if (prevlen != vterm_output_get_buffer_current(vterm))
859 {
860 char buf[KEY_BUF_LEN];
861 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
862
863 if (curlen > 0)
864 channel_send(term->tl_job->jv_channel, get_tty_part(term),
865 (char_u *)buf, (int)curlen, NULL);
866 }
867
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200868 /* this invokes the damage callbacks */
869 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
870}
871
872 static void
873update_cursor(term_T *term, int redraw)
874{
875 if (term->tl_normal_mode)
876 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100877#ifdef FEAT_GUI
878 if (term->tl_system)
879 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
880 term->tl_cursor_pos.col);
881 else
882#endif
883 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200884 if (redraw)
885 {
886 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
887 cursor_on();
888 out_flush();
889#ifdef FEAT_GUI
890 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100891 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200892 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100893 gui_mch_flush();
894 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200895#endif
896 }
897}
898
899/*
900 * Invoked when "msg" output from a job was received. Write it to the terminal
901 * of "buffer".
902 */
903 void
904write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
905{
906 size_t len = STRLEN(msg);
907 term_T *term = buffer->b_term;
908
909 if (term->tl_vterm == NULL)
910 {
911 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
912 return;
913 }
914 ch_log(channel, "writing %d bytes to terminal", (int)len);
915 term_write_job_output(term, msg, len);
916
Bram Moolenaar13568252018-03-16 20:46:58 +0100917#ifdef FEAT_GUI
918 if (term->tl_system)
919 {
920 /* show system output, scrolling up the screen as needed */
921 update_system_term(term);
922 update_cursor(term, TRUE);
923 }
924 else
925#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200926 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
927 * contents, thus no screen update is needed. */
928 if (!term->tl_normal_mode)
929 {
930 /* TODO: only update once in a while. */
931 ch_log(term->tl_job->jv_channel, "updating screen");
932 if (buffer == curbuf)
933 {
934 update_screen(0);
935 update_cursor(term, TRUE);
936 }
937 else
938 redraw_after_callback(TRUE);
939 }
940}
941
942/*
943 * Send a mouse position and click to the vterm
944 */
945 static int
946term_send_mouse(VTerm *vterm, int button, int pressed)
947{
948 VTermModifier mod = VTERM_MOD_NONE;
949
950 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200951 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100952 if (button != 0)
953 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200954 return TRUE;
955}
956
Bram Moolenaarc48369c2018-03-11 19:30:45 +0100957static int enter_mouse_col = -1;
958static int enter_mouse_row = -1;
959
960/*
961 * Handle a mouse click, drag or release.
962 * Return TRUE when a mouse event is sent to the terminal.
963 */
964 static int
965term_mouse_click(VTerm *vterm, int key)
966{
967#if defined(FEAT_CLIPBOARD)
968 /* For modeless selection mouse drag and release events are ignored, unless
969 * they are preceded with a mouse down event */
970 static int ignore_drag_release = TRUE;
971 VTermMouseState mouse_state;
972
973 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
974 if (mouse_state.flags == 0)
975 {
976 /* Terminal is not using the mouse, use modeless selection. */
977 switch (key)
978 {
979 case K_LEFTDRAG:
980 case K_LEFTRELEASE:
981 case K_RIGHTDRAG:
982 case K_RIGHTRELEASE:
983 /* Ignore drag and release events when the button-down wasn't
984 * seen before. */
985 if (ignore_drag_release)
986 {
987 int save_mouse_col, save_mouse_row;
988
989 if (enter_mouse_col < 0)
990 break;
991
992 /* mouse click in the window gave us focus, handle that
993 * click now */
994 save_mouse_col = mouse_col;
995 save_mouse_row = mouse_row;
996 mouse_col = enter_mouse_col;
997 mouse_row = enter_mouse_row;
998 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
999 mouse_col = save_mouse_col;
1000 mouse_row = save_mouse_row;
1001 }
1002 /* FALLTHROUGH */
1003 case K_LEFTMOUSE:
1004 case K_RIGHTMOUSE:
1005 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1006 ignore_drag_release = TRUE;
1007 else
1008 ignore_drag_release = FALSE;
1009 /* Should we call mouse_has() here? */
1010 if (clip_star.available)
1011 {
1012 int button, is_click, is_drag;
1013
1014 button = get_mouse_button(KEY2TERMCAP1(key),
1015 &is_click, &is_drag);
1016 if (mouse_model_popup() && button == MOUSE_LEFT
1017 && (mod_mask & MOD_MASK_SHIFT))
1018 {
1019 /* Translate shift-left to right button. */
1020 button = MOUSE_RIGHT;
1021 mod_mask &= ~MOD_MASK_SHIFT;
1022 }
1023 clip_modeless(button, is_click, is_drag);
1024 }
1025 break;
1026
1027 case K_MIDDLEMOUSE:
1028 if (clip_star.available)
1029 insert_reg('*', TRUE);
1030 break;
1031 }
1032 enter_mouse_col = -1;
1033 return FALSE;
1034 }
1035#endif
1036 enter_mouse_col = -1;
1037
1038 switch (key)
1039 {
1040 case K_LEFTMOUSE:
1041 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1042 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1043 case K_LEFTRELEASE:
1044 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1045 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1046 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1047 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1048 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1049 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1050 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1051 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1052 }
1053 return TRUE;
1054}
1055
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001056/*
1057 * Convert typed key "c" into bytes to send to the job.
1058 * Return the number of bytes in "buf".
1059 */
1060 static int
1061term_convert_key(term_T *term, int c, char *buf)
1062{
1063 VTerm *vterm = term->tl_vterm;
1064 VTermKey key = VTERM_KEY_NONE;
1065 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001066 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001067
1068 switch (c)
1069 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001070 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1071
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001072 /* don't use VTERM_KEY_BACKSPACE, it always
1073 * becomes 0x7f DEL */
1074 case K_BS: c = term_backspace_char; break;
1075
1076 case ESC: key = VTERM_KEY_ESCAPE; break;
1077 case K_DEL: key = VTERM_KEY_DEL; break;
1078 case K_DOWN: key = VTERM_KEY_DOWN; break;
1079 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1080 key = VTERM_KEY_DOWN; break;
1081 case K_END: key = VTERM_KEY_END; break;
1082 case K_S_END: mod = VTERM_MOD_SHIFT;
1083 key = VTERM_KEY_END; break;
1084 case K_C_END: mod = VTERM_MOD_CTRL;
1085 key = VTERM_KEY_END; break;
1086 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1087 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1088 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1089 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1090 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1091 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1092 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1093 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1094 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1095 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1096 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1097 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1098 case K_HOME: key = VTERM_KEY_HOME; break;
1099 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1100 key = VTERM_KEY_HOME; break;
1101 case K_C_HOME: mod = VTERM_MOD_CTRL;
1102 key = VTERM_KEY_HOME; break;
1103 case K_INS: key = VTERM_KEY_INS; break;
1104 case K_K0: key = VTERM_KEY_KP_0; break;
1105 case K_K1: key = VTERM_KEY_KP_1; break;
1106 case K_K2: key = VTERM_KEY_KP_2; break;
1107 case K_K3: key = VTERM_KEY_KP_3; break;
1108 case K_K4: key = VTERM_KEY_KP_4; break;
1109 case K_K5: key = VTERM_KEY_KP_5; break;
1110 case K_K6: key = VTERM_KEY_KP_6; break;
1111 case K_K7: key = VTERM_KEY_KP_7; break;
1112 case K_K8: key = VTERM_KEY_KP_8; break;
1113 case K_K9: key = VTERM_KEY_KP_9; break;
1114 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1115 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1116 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1117 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1118 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1119 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1120 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1121 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1122 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1123 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1124 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1125 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1126 case K_LEFT: key = VTERM_KEY_LEFT; break;
1127 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1128 key = VTERM_KEY_LEFT; break;
1129 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1130 key = VTERM_KEY_LEFT; break;
1131 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1132 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1133 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1134 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1135 key = VTERM_KEY_RIGHT; break;
1136 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1137 key = VTERM_KEY_RIGHT; break;
1138 case K_UP: key = VTERM_KEY_UP; break;
1139 case K_S_UP: mod = VTERM_MOD_SHIFT;
1140 key = VTERM_KEY_UP; break;
1141 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001142 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1143 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001144
Bram Moolenaara42ad572017-11-16 13:08:04 +01001145 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1146 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001147 case K_MOUSELEFT: /* TODO */ return 0;
1148 case K_MOUSERIGHT: /* TODO */ return 0;
1149
1150 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001151 case K_LEFTMOUSE_NM:
1152 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001153 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001154 case K_LEFTRELEASE_NM:
1155 case K_MOUSEMOVE:
1156 case K_MIDDLEMOUSE:
1157 case K_MIDDLEDRAG:
1158 case K_MIDDLERELEASE:
1159 case K_RIGHTMOUSE:
1160 case K_RIGHTDRAG:
1161 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1162 return 0;
1163 other = TRUE;
1164 break;
1165
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001166 case K_X1MOUSE: /* TODO */ return 0;
1167 case K_X1DRAG: /* TODO */ return 0;
1168 case K_X1RELEASE: /* TODO */ return 0;
1169 case K_X2MOUSE: /* TODO */ return 0;
1170 case K_X2DRAG: /* TODO */ return 0;
1171 case K_X2RELEASE: /* TODO */ return 0;
1172
1173 case K_IGNORE: return 0;
1174 case K_NOP: return 0;
1175 case K_UNDO: return 0;
1176 case K_HELP: return 0;
1177 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1178 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1179 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1180 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1181 case K_SELECT: return 0;
1182#ifdef FEAT_GUI
1183 case K_VER_SCROLLBAR: return 0;
1184 case K_HOR_SCROLLBAR: return 0;
1185#endif
1186#ifdef FEAT_GUI_TABLINE
1187 case K_TABLINE: return 0;
1188 case K_TABMENU: return 0;
1189#endif
1190#ifdef FEAT_NETBEANS_INTG
1191 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1192#endif
1193#ifdef FEAT_DND
1194 case K_DROP: return 0;
1195#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001196 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001197 case K_PS: vterm_keyboard_start_paste(vterm);
1198 other = TRUE;
1199 break;
1200 case K_PE: vterm_keyboard_end_paste(vterm);
1201 other = TRUE;
1202 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001203 }
1204
1205 /*
1206 * Convert special keys to vterm keys:
1207 * - Write keys to vterm: vterm_keyboard_key()
1208 * - Write output to channel.
1209 * TODO: use mod_mask
1210 */
1211 if (key != VTERM_KEY_NONE)
1212 /* Special key, let vterm convert it. */
1213 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001214 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001215 /* Normal character, let vterm convert it. */
1216 vterm_keyboard_unichar(vterm, c, mod);
1217
1218 /* Read back the converted escape sequence. */
1219 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1220}
1221
1222/*
1223 * Return TRUE if the job for "term" is still running.
1224 */
1225 int
1226term_job_running(term_T *term)
1227{
1228 /* Also consider the job finished when the channel is closed, to avoid a
1229 * race condition when updating the title. */
1230 return term != NULL
1231 && term->tl_job != NULL
1232 && channel_is_open(term->tl_job->jv_channel)
1233 && (term->tl_job->jv_status == JOB_STARTED
1234 || term->tl_job->jv_channel->ch_keep_open);
1235}
1236
1237/*
1238 * Return TRUE if "term" has an active channel and used ":term NONE".
1239 */
1240 int
1241term_none_open(term_T *term)
1242{
1243 /* Also consider the job finished when the channel is closed, to avoid a
1244 * race condition when updating the title. */
1245 return term != NULL
1246 && term->tl_job != NULL
1247 && channel_is_open(term->tl_job->jv_channel)
1248 && term->tl_job->jv_channel->ch_keep_open;
1249}
1250
1251/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001252 * Used when exiting: kill the job in "buf" if so desired.
1253 * Return OK when the job finished.
1254 * Return FAIL when the job is still running.
1255 */
1256 int
1257term_try_stop_job(buf_T *buf)
1258{
1259 int count;
1260 char *how = (char *)buf->b_term->tl_kill;
1261
1262#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1263 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1264 {
1265 char_u buff[DIALOG_MSG_SIZE];
1266 int ret;
1267
1268 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1269 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1270 if (ret == VIM_YES)
1271 how = "kill";
1272 else if (ret == VIM_CANCEL)
1273 return FAIL;
1274 }
1275#endif
1276 if (how == NULL || *how == NUL)
1277 return FAIL;
1278
1279 job_stop(buf->b_term->tl_job, NULL, how);
1280
1281 /* wait for up to a second for the job to die */
1282 for (count = 0; count < 100; ++count)
1283 {
1284 /* buffer, terminal and job may be cleaned up while waiting */
1285 if (!buf_valid(buf)
1286 || buf->b_term == NULL
1287 || buf->b_term->tl_job == NULL)
1288 return OK;
1289
1290 /* call job_status() to update jv_status */
1291 job_status(buf->b_term->tl_job);
1292 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1293 return OK;
1294 ui_delay(10L, FALSE);
1295 mch_check_messages();
1296 parse_queued_messages();
1297 }
1298 return FAIL;
1299}
1300
1301/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001302 * Add the last line of the scrollback buffer to the buffer in the window.
1303 */
1304 static void
1305add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1306{
1307 buf_T *buf = term->tl_buffer;
1308 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1309 linenr_T lnum = buf->b_ml.ml_line_count;
1310
1311#ifdef WIN3264
1312 if (!enc_utf8 && enc_codepage > 0)
1313 {
1314 WCHAR *ret = NULL;
1315 int length = 0;
1316
1317 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1318 &ret, &length);
1319 if (ret != NULL)
1320 {
1321 WideCharToMultiByte_alloc(enc_codepage, 0,
1322 ret, length, (char **)&text, &len, 0, 0);
1323 vim_free(ret);
1324 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1325 vim_free(text);
1326 }
1327 }
1328 else
1329#endif
1330 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1331 if (empty)
1332 {
1333 /* Delete the empty line that was in the empty buffer. */
1334 curbuf = buf;
1335 ml_delete(1, FALSE);
1336 curbuf = curwin->w_buffer;
1337 }
1338}
1339
1340 static void
1341cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1342{
1343 attr->width = cell->width;
1344 attr->attrs = cell->attrs;
1345 attr->fg = cell->fg;
1346 attr->bg = cell->bg;
1347}
1348
1349 static int
1350equal_celattr(cellattr_T *a, cellattr_T *b)
1351{
1352 /* Comparing the colors should be sufficient. */
1353 return a->fg.red == b->fg.red
1354 && a->fg.green == b->fg.green
1355 && a->fg.blue == b->fg.blue
1356 && a->bg.red == b->bg.red
1357 && a->bg.green == b->bg.green
1358 && a->bg.blue == b->bg.blue;
1359}
1360
Bram Moolenaard96ff162018-02-18 22:13:29 +01001361/*
1362 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1363 * line at this position. Otherwise at the end.
1364 */
1365 static int
1366add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1367{
1368 if (ga_grow(&term->tl_scrollback, 1) == OK)
1369 {
1370 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1371 + term->tl_scrollback.ga_len;
1372
1373 if (lnum > 0)
1374 {
1375 int i;
1376
1377 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1378 {
1379 *line = *(line - 1);
1380 --line;
1381 }
1382 }
1383 line->sb_cols = 0;
1384 line->sb_cells = NULL;
1385 line->sb_fill_attr = *fill_attr;
1386 ++term->tl_scrollback.ga_len;
1387 return OK;
1388 }
1389 return FALSE;
1390}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001391
1392/*
1393 * Add the current lines of the terminal to scrollback and to the buffer.
1394 * Called after the job has ended and when switching to Terminal-Normal mode.
1395 */
1396 static void
1397move_terminal_to_buffer(term_T *term)
1398{
1399 win_T *wp;
1400 int len;
1401 int lines_skipped = 0;
1402 VTermPos pos;
1403 VTermScreenCell cell;
1404 cellattr_T fill_attr, new_fill_attr;
1405 cellattr_T *p;
1406 VTermScreen *screen;
1407
1408 if (term->tl_vterm == NULL)
1409 return;
1410 screen = vterm_obtain_screen(term->tl_vterm);
1411 fill_attr = new_fill_attr = term->tl_default_color;
1412
1413 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1414 {
1415 len = 0;
1416 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1417 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1418 && cell.chars[0] != NUL)
1419 {
1420 len = pos.col + 1;
1421 new_fill_attr = term->tl_default_color;
1422 }
1423 else
1424 /* Assume the last attr is the filler attr. */
1425 cell2cellattr(&cell, &new_fill_attr);
1426
1427 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1428 ++lines_skipped;
1429 else
1430 {
1431 while (lines_skipped > 0)
1432 {
1433 /* Line was skipped, add an empty line. */
1434 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001435 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001436 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001437 }
1438
1439 if (len == 0)
1440 p = NULL;
1441 else
1442 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1443 if ((p != NULL || len == 0)
1444 && ga_grow(&term->tl_scrollback, 1) == OK)
1445 {
1446 garray_T ga;
1447 int width;
1448 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1449 + term->tl_scrollback.ga_len;
1450
1451 ga_init2(&ga, 1, 100);
1452 for (pos.col = 0; pos.col < len; pos.col += width)
1453 {
1454 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1455 {
1456 width = 1;
1457 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1458 if (ga_grow(&ga, 1) == OK)
1459 ga.ga_len += utf_char2bytes(' ',
1460 (char_u *)ga.ga_data + ga.ga_len);
1461 }
1462 else
1463 {
1464 width = cell.width;
1465
1466 cell2cellattr(&cell, &p[pos.col]);
1467
1468 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1469 {
1470 int i;
1471 int c;
1472
1473 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1474 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1475 (char_u *)ga.ga_data + ga.ga_len);
1476 }
1477 }
1478 }
1479 line->sb_cols = len;
1480 line->sb_cells = p;
1481 line->sb_fill_attr = new_fill_attr;
1482 fill_attr = new_fill_attr;
1483 ++term->tl_scrollback.ga_len;
1484
1485 if (ga_grow(&ga, 1) == FAIL)
1486 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1487 else
1488 {
1489 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1490 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1491 }
1492 ga_clear(&ga);
1493 }
1494 else
1495 vim_free(p);
1496 }
1497 }
1498
1499 /* Obtain the current background color. */
1500 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1501 &term->tl_default_color.fg, &term->tl_default_color.bg);
1502
1503 FOR_ALL_WINDOWS(wp)
1504 {
1505 if (wp->w_buffer == term->tl_buffer)
1506 {
1507 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1508 wp->w_cursor.col = 0;
1509 wp->w_valid = 0;
1510 if (wp->w_cursor.lnum >= wp->w_height)
1511 {
1512 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1513
1514 if (wp->w_topline < min_topline)
1515 wp->w_topline = min_topline;
1516 }
1517 redraw_win_later(wp, NOT_VALID);
1518 }
1519 }
1520}
1521
1522 static void
1523set_terminal_mode(term_T *term, int normal_mode)
1524{
1525 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001526 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001527 if (term->tl_buffer == curbuf)
1528 maketitle();
1529}
1530
1531/*
1532 * Called after the job if finished and Terminal mode is not active:
1533 * Move the vterm contents into the scrollback buffer and free the vterm.
1534 */
1535 static void
1536cleanup_vterm(term_T *term)
1537{
1538 if (term->tl_finish != 'c')
1539 move_terminal_to_buffer(term);
1540 term_free_vterm(term);
1541 set_terminal_mode(term, FALSE);
1542}
1543
1544/*
1545 * Switch from Terminal-Job mode to Terminal-Normal mode.
1546 * Suspends updating the terminal window.
1547 */
1548 static void
1549term_enter_normal_mode(void)
1550{
1551 term_T *term = curbuf->b_term;
1552
1553 /* Append the current terminal contents to the buffer. */
1554 move_terminal_to_buffer(term);
1555
1556 set_terminal_mode(term, TRUE);
1557
1558 /* Move the window cursor to the position of the cursor in the
1559 * terminal. */
1560 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1561 + term->tl_cursor_pos.row + 1;
1562 check_cursor();
1563 coladvance(term->tl_cursor_pos.col);
1564
1565 /* Display the same lines as in the terminal. */
1566 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1567}
1568
1569/*
1570 * Returns TRUE if the current window contains a terminal and we are in
1571 * Terminal-Normal mode.
1572 */
1573 int
1574term_in_normal_mode(void)
1575{
1576 term_T *term = curbuf->b_term;
1577
1578 return term != NULL && term->tl_normal_mode;
1579}
1580
1581/*
1582 * Switch from Terminal-Normal mode to Terminal-Job mode.
1583 * Restores updating the terminal window.
1584 */
1585 void
1586term_enter_job_mode()
1587{
1588 term_T *term = curbuf->b_term;
1589 sb_line_T *line;
1590 garray_T *gap;
1591
1592 /* Remove the terminal contents from the scrollback and the buffer. */
1593 gap = &term->tl_scrollback;
1594 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1595 && gap->ga_len > 0)
1596 {
1597 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1598 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1599 vim_free(line->sb_cells);
1600 --gap->ga_len;
1601 }
1602 check_cursor();
1603
1604 set_terminal_mode(term, FALSE);
1605
1606 if (term->tl_channel_closed)
1607 cleanup_vterm(term);
1608 redraw_buf_and_status_later(curbuf, NOT_VALID);
1609}
1610
1611/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001612 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001613 * Note: while waiting a terminal may be closed and freed if the channel is
1614 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001615 */
1616 static int
1617term_vgetc()
1618{
1619 int c;
1620 int save_State = State;
1621
1622 State = TERMINAL;
1623 got_int = FALSE;
1624#ifdef WIN3264
1625 ctrl_break_was_pressed = FALSE;
1626#endif
1627 c = vgetc();
1628 got_int = FALSE;
1629 State = save_State;
1630 return c;
1631}
1632
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001633static int mouse_was_outside = FALSE;
1634
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001635/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001636 * Send keys to terminal.
1637 * Return FAIL when the key needs to be handled in Normal mode.
1638 * Return OK when the key was dropped or sent to the terminal.
1639 */
1640 int
1641send_keys_to_term(term_T *term, int c, int typed)
1642{
1643 char msg[KEY_BUF_LEN];
1644 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001645 int dragging_outside = FALSE;
1646
1647 /* Catch keys that need to be handled as in Normal mode. */
1648 switch (c)
1649 {
1650 case NUL:
1651 case K_ZERO:
1652 if (typed)
1653 stuffcharReadbuff(c);
1654 return FAIL;
1655
1656 case K_IGNORE:
1657 return FAIL;
1658
1659 case K_LEFTDRAG:
1660 case K_MIDDLEDRAG:
1661 case K_RIGHTDRAG:
1662 case K_X1DRAG:
1663 case K_X2DRAG:
1664 dragging_outside = mouse_was_outside;
1665 /* FALLTHROUGH */
1666 case K_LEFTMOUSE:
1667 case K_LEFTMOUSE_NM:
1668 case K_LEFTRELEASE:
1669 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001670 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001671 case K_MIDDLEMOUSE:
1672 case K_MIDDLERELEASE:
1673 case K_RIGHTMOUSE:
1674 case K_RIGHTRELEASE:
1675 case K_X1MOUSE:
1676 case K_X1RELEASE:
1677 case K_X2MOUSE:
1678 case K_X2RELEASE:
1679
1680 case K_MOUSEUP:
1681 case K_MOUSEDOWN:
1682 case K_MOUSELEFT:
1683 case K_MOUSERIGHT:
1684 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001685 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001686 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001687 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001688 || dragging_outside)
1689 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001690 /* click or scroll outside the current window or on status line
1691 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001692 if (typed)
1693 {
1694 stuffcharReadbuff(c);
1695 mouse_was_outside = TRUE;
1696 }
1697 return FAIL;
1698 }
1699 }
1700 if (typed)
1701 mouse_was_outside = FALSE;
1702
1703 /* Convert the typed key to a sequence of bytes for the job. */
1704 len = term_convert_key(term, c, msg);
1705 if (len > 0)
1706 /* TODO: if FAIL is returned, stop? */
1707 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1708 (char_u *)msg, (int)len, NULL);
1709
1710 return OK;
1711}
1712
1713 static void
1714position_cursor(win_T *wp, VTermPos *pos)
1715{
1716 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1717 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1718 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1719}
1720
1721/*
1722 * Handle CTRL-W "": send register contents to the job.
1723 */
1724 static void
1725term_paste_register(int prev_c UNUSED)
1726{
1727 int c;
1728 list_T *l;
1729 listitem_T *item;
1730 long reglen = 0;
1731 int type;
1732
1733#ifdef FEAT_CMDL_INFO
1734 if (add_to_showcmd(prev_c))
1735 if (add_to_showcmd('"'))
1736 out_flush();
1737#endif
1738 c = term_vgetc();
1739#ifdef FEAT_CMDL_INFO
1740 clear_showcmd();
1741#endif
1742 if (!term_use_loop())
1743 /* job finished while waiting for a character */
1744 return;
1745
1746 /* CTRL-W "= prompt for expression to evaluate. */
1747 if (c == '=' && get_expr_register() != '=')
1748 return;
1749 if (!term_use_loop())
1750 /* job finished while waiting for a character */
1751 return;
1752
1753 l = (list_T *)get_reg_contents(c, GREG_LIST);
1754 if (l != NULL)
1755 {
1756 type = get_reg_type(c, &reglen);
1757 for (item = l->lv_first; item != NULL; item = item->li_next)
1758 {
1759 char_u *s = get_tv_string(&item->li_tv);
1760#ifdef WIN3264
1761 char_u *tmp = s;
1762
1763 if (!enc_utf8 && enc_codepage > 0)
1764 {
1765 WCHAR *ret = NULL;
1766 int length = 0;
1767
1768 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1769 (int)STRLEN(s), &ret, &length);
1770 if (ret != NULL)
1771 {
1772 WideCharToMultiByte_alloc(CP_UTF8, 0,
1773 ret, length, (char **)&s, &length, 0, 0);
1774 vim_free(ret);
1775 }
1776 }
1777#endif
1778 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1779 s, (int)STRLEN(s), NULL);
1780#ifdef WIN3264
1781 if (tmp != s)
1782 vim_free(s);
1783#endif
1784
1785 if (item->li_next != NULL || type == MLINE)
1786 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1787 (char_u *)"\r", 1, NULL);
1788 }
1789 list_free(l);
1790 }
1791}
1792
1793#if defined(FEAT_GUI) || defined(PROTO)
1794/*
1795 * Return TRUE when the cursor of the terminal should be displayed.
1796 */
1797 int
1798terminal_is_active()
1799{
1800 return in_terminal_loop != NULL;
1801}
1802
1803 cursorentry_T *
1804term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1805{
1806 term_T *term = in_terminal_loop;
1807 static cursorentry_T entry;
1808
1809 vim_memset(&entry, 0, sizeof(entry));
1810 entry.shape = entry.mshape =
1811 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1812 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1813 SHAPE_BLOCK;
1814 entry.percentage = 20;
1815 if (term->tl_cursor_blink)
1816 {
1817 entry.blinkwait = 700;
1818 entry.blinkon = 400;
1819 entry.blinkoff = 250;
1820 }
1821 *fg = gui.back_pixel;
1822 if (term->tl_cursor_color == NULL)
1823 *bg = gui.norm_pixel;
1824 else
1825 *bg = color_name2handle(term->tl_cursor_color);
1826 entry.name = "n";
1827 entry.used_for = SHAPE_CURSOR;
1828
1829 return &entry;
1830}
1831#endif
1832
Bram Moolenaard317b382018-02-08 22:33:31 +01001833 static void
1834may_output_cursor_props(void)
1835{
1836 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1837 || last_set_cursor_shape != desired_cursor_shape
1838 || last_set_cursor_blink != desired_cursor_blink)
1839 {
1840 last_set_cursor_color = desired_cursor_color;
1841 last_set_cursor_shape = desired_cursor_shape;
1842 last_set_cursor_blink = desired_cursor_blink;
1843 term_cursor_color(desired_cursor_color);
1844 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1845 /* this will restore the initial cursor style, if possible */
1846 ui_cursor_shape_forced(TRUE);
1847 else
1848 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1849 }
1850}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001851
Bram Moolenaard317b382018-02-08 22:33:31 +01001852/*
1853 * Set the cursor color and shape, if not last set to these.
1854 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001855 static void
1856may_set_cursor_props(term_T *term)
1857{
1858#ifdef FEAT_GUI
1859 /* For the GUI the cursor properties are obtained with
1860 * term_get_cursor_shape(). */
1861 if (gui.in_use)
1862 return;
1863#endif
1864 if (in_terminal_loop == term)
1865 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001866 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001867 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001868 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001869 desired_cursor_color = (char_u *)"";
1870 desired_cursor_shape = term->tl_cursor_shape;
1871 desired_cursor_blink = term->tl_cursor_blink;
1872 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001873 }
1874}
1875
Bram Moolenaard317b382018-02-08 22:33:31 +01001876/*
1877 * Reset the desired cursor properties and restore them when needed.
1878 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001879 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001880prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001881{
1882#ifdef FEAT_GUI
1883 if (gui.in_use)
1884 return;
1885#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001886 desired_cursor_color = (char_u *)"";
1887 desired_cursor_shape = -1;
1888 desired_cursor_blink = -1;
1889 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001890}
1891
1892/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001893 * Called when entering a window with the mouse. If this is a terminal window
1894 * we may want to change state.
1895 */
1896 void
1897term_win_entered()
1898{
1899 term_T *term = curbuf->b_term;
1900
1901 if (term != NULL)
1902 {
1903 if (term_use_loop())
1904 {
1905 reset_VIsual_and_resel();
1906 if (State & INSERT)
1907 stop_insert_mode = TRUE;
1908 }
1909 mouse_was_outside = FALSE;
1910 enter_mouse_col = mouse_col;
1911 enter_mouse_row = mouse_row;
1912 }
1913}
1914
1915/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001916 * Returns TRUE if the current window contains a terminal and we are sending
1917 * keys to the job.
1918 */
1919 int
1920term_use_loop(void)
1921{
1922 term_T *term = curbuf->b_term;
1923
1924 return term != NULL
1925 && !term->tl_normal_mode
1926 && term->tl_vterm != NULL
1927 && term_job_running(term);
1928}
1929
1930/*
1931 * Wait for input and send it to the job.
1932 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1933 * when there is no more typahead.
1934 * Return when the start of a CTRL-W command is typed or anything else that
1935 * should be handled as a Normal mode command.
1936 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1937 * the terminal was closed.
1938 */
1939 int
1940terminal_loop(int blocking)
1941{
1942 int c;
1943 int termkey = 0;
1944 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001945#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001946 int tty_fd = curbuf->b_term->tl_job->jv_channel
1947 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001948#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001949 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001950
1951 /* Remember the terminal we are sending keys to. However, the terminal
1952 * might be closed while waiting for a character, e.g. typing "exit" in a
1953 * shell and ++close was used. Therefore use curbuf->b_term instead of a
1954 * stored reference. */
1955 in_terminal_loop = curbuf->b_term;
1956
1957 if (*curwin->w_p_tk != NUL)
1958 termkey = string_to_key(curwin->w_p_tk, TRUE);
1959 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
1960 may_set_cursor_props(curbuf->b_term);
1961
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001962 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001963 {
Bram Moolenaar13568252018-03-16 20:46:58 +01001964#ifdef FEAT_GUI
1965 if (!curbuf->b_term->tl_system)
1966#endif
1967 /* TODO: skip screen update when handling a sequence of keys. */
1968 /* Repeat redrawing in case a message is received while redrawing.
1969 */
1970 while (must_redraw != 0)
1971 if (update_screen(0) == FAIL)
1972 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001973 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01001974 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001975
1976 c = term_vgetc();
1977 if (!term_use_loop())
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001978 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001979 /* Job finished while waiting for a character. Push back the
1980 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001981 if (c != K_IGNORE)
1982 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001983 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001984 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001985 if (c == K_IGNORE)
1986 continue;
1987
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001988#ifdef UNIX
1989 /*
1990 * The shell or another program may change the tty settings. Getting
1991 * them for every typed character is a bit of overhead, but it's needed
1992 * for the first character typed, e.g. when Vim starts in a shell.
1993 */
1994 if (isatty(tty_fd))
1995 {
1996 ttyinfo_T info;
1997
1998 /* Get the current backspace character of the pty. */
1999 if (get_tty_info(tty_fd, &info) == OK)
2000 term_backspace_char = info.backspace;
2001 }
2002#endif
2003
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002004#ifdef WIN3264
2005 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2006 * Use CTRL-BREAK to kill the job. */
2007 if (ctrl_break_was_pressed)
2008 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2009#endif
2010 /* Was either CTRL-W (termkey) or CTRL-\ pressed? */
2011 if (c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
2012 {
2013 int prev_c = c;
2014
2015#ifdef FEAT_CMDL_INFO
2016 if (add_to_showcmd(c))
2017 out_flush();
2018#endif
2019 c = term_vgetc();
2020#ifdef FEAT_CMDL_INFO
2021 clear_showcmd();
2022#endif
2023 if (!term_use_loop())
2024 /* job finished while waiting for a character */
2025 break;
2026
2027 if (prev_c == Ctrl_BSL)
2028 {
2029 if (c == Ctrl_N)
2030 {
2031 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2032 term_enter_normal_mode();
2033 ret = FAIL;
2034 goto theend;
2035 }
2036 /* Send both keys to the terminal. */
2037 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2038 }
2039 else if (c == Ctrl_C)
2040 {
2041 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
2042 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2043 }
2044 else if (termkey == 0 && c == '.')
2045 {
2046 /* "CTRL-W .": send CTRL-W to the job */
2047 c = Ctrl_W;
2048 }
2049 else if (c == 'N')
2050 {
2051 /* CTRL-W N : go to Terminal-Normal mode. */
2052 term_enter_normal_mode();
2053 ret = FAIL;
2054 goto theend;
2055 }
2056 else if (c == '"')
2057 {
2058 term_paste_register(prev_c);
2059 continue;
2060 }
2061 else if (termkey == 0 || c != termkey)
2062 {
2063 stuffcharReadbuff(Ctrl_W);
2064 stuffcharReadbuff(c);
2065 ret = OK;
2066 goto theend;
2067 }
2068 }
2069# ifdef WIN3264
2070 if (!enc_utf8 && has_mbyte && c >= 0x80)
2071 {
2072 WCHAR wc;
2073 char_u mb[3];
2074
2075 mb[0] = (unsigned)c >> 8;
2076 mb[1] = c;
2077 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2078 c = wc;
2079 }
2080# endif
2081 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2082 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002083 if (c == K_MOUSEMOVE)
2084 /* We are sure to come back here, don't reset the cursor color
2085 * and shape to avoid flickering. */
2086 restore_cursor = FALSE;
2087
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002088 ret = OK;
2089 goto theend;
2090 }
2091 }
2092 ret = FAIL;
2093
2094theend:
2095 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002096 if (restore_cursor)
2097 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002098 return ret;
2099}
2100
2101/*
2102 * Called when a job has finished.
2103 * This updates the title and status, but does not close the vterm, because
2104 * there might still be pending output in the channel.
2105 */
2106 void
2107term_job_ended(job_T *job)
2108{
2109 term_T *term;
2110 int did_one = FALSE;
2111
2112 for (term = first_term; term != NULL; term = term->tl_next)
2113 if (term->tl_job == job)
2114 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002115 VIM_CLEAR(term->tl_title);
2116 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002117 redraw_buf_and_status_later(term->tl_buffer, VALID);
2118 did_one = TRUE;
2119 }
2120 if (did_one)
2121 redraw_statuslines();
2122 if (curbuf->b_term != NULL)
2123 {
2124 if (curbuf->b_term->tl_job == job)
2125 maketitle();
2126 update_cursor(curbuf->b_term, TRUE);
2127 }
2128}
2129
2130 static void
2131may_toggle_cursor(term_T *term)
2132{
2133 if (in_terminal_loop == term)
2134 {
2135 if (term->tl_cursor_visible)
2136 cursor_on();
2137 else
2138 cursor_off();
2139 }
2140}
2141
2142/*
2143 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002144 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002145 */
2146 static int
2147color2index(VTermColor *color, int fg, int *boldp)
2148{
2149 int red = color->red;
2150 int blue = color->blue;
2151 int green = color->green;
2152
Bram Moolenaar46359e12017-11-29 22:33:38 +01002153 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002154 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002155 /* First 16 colors and default: use the ANSI index, because these
2156 * colors can be redefined. */
2157 if (t_colors >= 16)
2158 return color->ansi_index;
2159 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002160 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002161 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002162 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002163 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2164 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2165 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
2166 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue*/
2167 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2168 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2169 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2170 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2171 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2172 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2173 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2174 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2175 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2176 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2177 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002178 }
2179 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002180
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002181 if (t_colors >= 256)
2182 {
2183 if (red == blue && red == green)
2184 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002185 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002186 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002187 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2188 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2189 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002190 int i;
2191
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002192 if (red < 5)
2193 return 17; /* 00/00/00 */
2194 if (red > 245) /* ff/ff/ff */
2195 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002196 for (i = 0; i < 23; ++i)
2197 if (red < cutoff[i])
2198 return i + 233;
2199 return 256;
2200 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002201 {
2202 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2203 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002204
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002205 /* 216-color cube */
2206 for (ri = 0; ri < 5; ++ri)
2207 if (red < cutoff[ri])
2208 break;
2209 for (gi = 0; gi < 5; ++gi)
2210 if (green < cutoff[gi])
2211 break;
2212 for (bi = 0; bi < 5; ++bi)
2213 if (blue < cutoff[bi])
2214 break;
2215 return 17 + ri * 36 + gi * 6 + bi;
2216 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002217 }
2218 return 0;
2219}
2220
2221/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002222 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002223 */
2224 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002225vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002226{
2227 int attr = 0;
2228
2229 if (cellattrs.bold)
2230 attr |= HL_BOLD;
2231 if (cellattrs.underline)
2232 attr |= HL_UNDERLINE;
2233 if (cellattrs.italic)
2234 attr |= HL_ITALIC;
2235 if (cellattrs.strike)
2236 attr |= HL_STRIKETHROUGH;
2237 if (cellattrs.reverse)
2238 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002239 return attr;
2240}
2241
2242/*
2243 * Store Vterm attributes in "cell" from highlight flags.
2244 */
2245 static void
2246hl2vtermAttr(int attr, cellattr_T *cell)
2247{
2248 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2249 if (attr & HL_BOLD)
2250 cell->attrs.bold = 1;
2251 if (attr & HL_UNDERLINE)
2252 cell->attrs.underline = 1;
2253 if (attr & HL_ITALIC)
2254 cell->attrs.italic = 1;
2255 if (attr & HL_STRIKETHROUGH)
2256 cell->attrs.strike = 1;
2257 if (attr & HL_INVERSE)
2258 cell->attrs.reverse = 1;
2259}
2260
2261/*
2262 * Convert the attributes of a vterm cell into an attribute index.
2263 */
2264 static int
2265cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2266{
2267 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002268
2269#ifdef FEAT_GUI
2270 if (gui.in_use)
2271 {
2272 guicolor_T fg, bg;
2273
2274 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2275 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2276 return get_gui_attr_idx(attr, fg, bg);
2277 }
2278 else
2279#endif
2280#ifdef FEAT_TERMGUICOLORS
2281 if (p_tgc)
2282 {
2283 guicolor_T fg, bg;
2284
2285 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2286 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2287
2288 return get_tgc_attr_idx(attr, fg, bg);
2289 }
2290 else
2291#endif
2292 {
2293 int bold = MAYBE;
2294 int fg = color2index(&cellfg, TRUE, &bold);
2295 int bg = color2index(&cellbg, FALSE, &bold);
2296
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002297 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002298 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002299 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002300 if (fg == 0 && term_default_cterm_fg >= 0)
2301 fg = term_default_cterm_fg + 1;
2302 if (bg == 0 && term_default_cterm_bg >= 0)
2303 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002304 }
2305
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002306 /* with 8 colors set the bold attribute to get a bright foreground */
2307 if (bold == TRUE)
2308 attr |= HL_BOLD;
2309 return get_cterm_attr_idx(attr, fg, bg);
2310 }
2311 return 0;
2312}
2313
2314 static int
2315handle_damage(VTermRect rect, void *user)
2316{
2317 term_T *term = (term_T *)user;
2318
2319 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2320 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2321 redraw_buf_later(term->tl_buffer, NOT_VALID);
2322 return 1;
2323}
2324
2325 static int
2326handle_moverect(VTermRect dest, VTermRect src, void *user)
2327{
2328 term_T *term = (term_T *)user;
2329
2330 /* Scrolling up is done much more efficiently by deleting lines instead of
2331 * redrawing the text. */
2332 if (dest.start_col == src.start_col
2333 && dest.end_col == src.end_col
2334 && dest.start_row < src.start_row)
2335 {
2336 win_T *wp;
2337 VTermColor fg, bg;
2338 VTermScreenCellAttrs attr;
2339 int clear_attr;
2340
2341 /* Set the color to clear lines with. */
2342 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2343 &fg, &bg);
2344 vim_memset(&attr, 0, sizeof(attr));
2345 clear_attr = cell2attr(attr, fg, bg);
2346
2347 FOR_ALL_WINDOWS(wp)
2348 {
2349 if (wp->w_buffer == term->tl_buffer)
2350 win_del_lines(wp, dest.start_row,
2351 src.start_row - dest.start_row, FALSE, FALSE,
2352 clear_attr);
2353 }
2354 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002355
2356 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2357 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2358
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002359 redraw_buf_later(term->tl_buffer, NOT_VALID);
2360 return 1;
2361}
2362
2363 static int
2364handle_movecursor(
2365 VTermPos pos,
2366 VTermPos oldpos UNUSED,
2367 int visible,
2368 void *user)
2369{
2370 term_T *term = (term_T *)user;
2371 win_T *wp;
2372
2373 term->tl_cursor_pos = pos;
2374 term->tl_cursor_visible = visible;
2375
2376 FOR_ALL_WINDOWS(wp)
2377 {
2378 if (wp->w_buffer == term->tl_buffer)
2379 position_cursor(wp, &pos);
2380 }
2381 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2382 {
2383 may_toggle_cursor(term);
2384 update_cursor(term, term->tl_cursor_visible);
2385 }
2386
2387 return 1;
2388}
2389
2390 static int
2391handle_settermprop(
2392 VTermProp prop,
2393 VTermValue *value,
2394 void *user)
2395{
2396 term_T *term = (term_T *)user;
2397
2398 switch (prop)
2399 {
2400 case VTERM_PROP_TITLE:
2401 vim_free(term->tl_title);
2402 /* a blank title isn't useful, make it empty, so that "running" is
2403 * displayed */
2404 if (*skipwhite((char_u *)value->string) == NUL)
2405 term->tl_title = NULL;
2406#ifdef WIN3264
2407 else if (!enc_utf8 && enc_codepage > 0)
2408 {
2409 WCHAR *ret = NULL;
2410 int length = 0;
2411
2412 MultiByteToWideChar_alloc(CP_UTF8, 0,
2413 (char*)value->string, (int)STRLEN(value->string),
2414 &ret, &length);
2415 if (ret != NULL)
2416 {
2417 WideCharToMultiByte_alloc(enc_codepage, 0,
2418 ret, length, (char**)&term->tl_title,
2419 &length, 0, 0);
2420 vim_free(ret);
2421 }
2422 }
2423#endif
2424 else
2425 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002426 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002427 if (term == curbuf->b_term)
2428 maketitle();
2429 break;
2430
2431 case VTERM_PROP_CURSORVISIBLE:
2432 term->tl_cursor_visible = value->boolean;
2433 may_toggle_cursor(term);
2434 out_flush();
2435 break;
2436
2437 case VTERM_PROP_CURSORBLINK:
2438 term->tl_cursor_blink = value->boolean;
2439 may_set_cursor_props(term);
2440 break;
2441
2442 case VTERM_PROP_CURSORSHAPE:
2443 term->tl_cursor_shape = value->number;
2444 may_set_cursor_props(term);
2445 break;
2446
2447 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002448 if (desired_cursor_color == term->tl_cursor_color)
2449 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002450 vim_free(term->tl_cursor_color);
2451 if (*value->string == NUL)
2452 term->tl_cursor_color = NULL;
2453 else
2454 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2455 may_set_cursor_props(term);
2456 break;
2457
2458 case VTERM_PROP_ALTSCREEN:
2459 /* TODO: do anything else? */
2460 term->tl_using_altscreen = value->boolean;
2461 break;
2462
2463 default:
2464 break;
2465 }
2466 /* Always return 1, otherwise vterm doesn't store the value internally. */
2467 return 1;
2468}
2469
2470/*
2471 * The job running in the terminal resized the terminal.
2472 */
2473 static int
2474handle_resize(int rows, int cols, void *user)
2475{
2476 term_T *term = (term_T *)user;
2477 win_T *wp;
2478
2479 term->tl_rows = rows;
2480 term->tl_cols = cols;
2481 if (term->tl_vterm_size_changed)
2482 /* Size was set by vterm_set_size(), don't set the window size. */
2483 term->tl_vterm_size_changed = FALSE;
2484 else
2485 {
2486 FOR_ALL_WINDOWS(wp)
2487 {
2488 if (wp->w_buffer == term->tl_buffer)
2489 {
2490 win_setheight_win(rows, wp);
2491 win_setwidth_win(cols, wp);
2492 }
2493 }
2494 redraw_buf_later(term->tl_buffer, NOT_VALID);
2495 }
2496 return 1;
2497}
2498
2499/*
2500 * Handle a line that is pushed off the top of the screen.
2501 */
2502 static int
2503handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2504{
2505 term_T *term = (term_T *)user;
2506
2507 /* TODO: Limit the number of lines that are stored. */
2508 if (ga_grow(&term->tl_scrollback, 1) == OK)
2509 {
2510 cellattr_T *p = NULL;
2511 int len = 0;
2512 int i;
2513 int c;
2514 int col;
2515 sb_line_T *line;
2516 garray_T ga;
2517 cellattr_T fill_attr = term->tl_default_color;
2518
2519 /* do not store empty cells at the end */
2520 for (i = 0; i < cols; ++i)
2521 if (cells[i].chars[0] != 0)
2522 len = i + 1;
2523 else
2524 cell2cellattr(&cells[i], &fill_attr);
2525
2526 ga_init2(&ga, 1, 100);
2527 if (len > 0)
2528 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2529 if (p != NULL)
2530 {
2531 for (col = 0; col < len; col += cells[col].width)
2532 {
2533 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2534 {
2535 ga.ga_len = 0;
2536 break;
2537 }
2538 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2539 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2540 (char_u *)ga.ga_data + ga.ga_len);
2541 cell2cellattr(&cells[col], &p[col]);
2542 }
2543 }
2544 if (ga_grow(&ga, 1) == FAIL)
2545 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2546 else
2547 {
2548 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2549 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2550 }
2551 ga_clear(&ga);
2552
2553 line = (sb_line_T *)term->tl_scrollback.ga_data
2554 + term->tl_scrollback.ga_len;
2555 line->sb_cols = len;
2556 line->sb_cells = p;
2557 line->sb_fill_attr = fill_attr;
2558 ++term->tl_scrollback.ga_len;
2559 ++term->tl_scrollback_scrolled;
2560 }
2561 return 0; /* ignored */
2562}
2563
2564static VTermScreenCallbacks screen_callbacks = {
2565 handle_damage, /* damage */
2566 handle_moverect, /* moverect */
2567 handle_movecursor, /* movecursor */
2568 handle_settermprop, /* settermprop */
2569 NULL, /* bell */
2570 handle_resize, /* resize */
2571 handle_pushline, /* sb_pushline */
2572 NULL /* sb_popline */
2573};
2574
2575/*
2576 * Called when a channel has been closed.
2577 * If this was a channel for a terminal window then finish it up.
2578 */
2579 void
2580term_channel_closed(channel_T *ch)
2581{
2582 term_T *term;
2583 int did_one = FALSE;
2584
2585 for (term = first_term; term != NULL; term = term->tl_next)
2586 if (term->tl_job == ch->ch_job)
2587 {
2588 term->tl_channel_closed = TRUE;
2589 did_one = TRUE;
2590
Bram Moolenaard23a8232018-02-10 18:45:26 +01002591 VIM_CLEAR(term->tl_title);
2592 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002593
2594 /* Unless in Terminal-Normal mode: clear the vterm. */
2595 if (!term->tl_normal_mode)
2596 {
2597 int fnum = term->tl_buffer->b_fnum;
2598
2599 cleanup_vterm(term);
2600
2601 if (term->tl_finish == 'c')
2602 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002603 aco_save_T aco;
2604
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002605 /* ++close or term_finish == "close" */
2606 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002607 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002608 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002609 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002610 break;
2611 }
2612 if (term->tl_finish == 'o' && term->tl_buffer->b_nwindows == 0)
2613 {
2614 char buf[50];
2615
2616 /* TODO: use term_opencmd */
2617 ch_log(NULL, "terminal job finished, opening window");
2618 vim_snprintf(buf, sizeof(buf),
2619 term->tl_opencmd == NULL
2620 ? "botright sbuf %d"
2621 : (char *)term->tl_opencmd, fnum);
2622 do_cmdline_cmd((char_u *)buf);
2623 }
2624 else
2625 ch_log(NULL, "terminal job finished");
2626 }
2627
2628 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2629 }
2630 if (did_one)
2631 {
2632 redraw_statuslines();
2633
2634 /* Need to break out of vgetc(). */
2635 ins_char_typebuf(K_IGNORE);
2636 typebuf_was_filled = TRUE;
2637
2638 term = curbuf->b_term;
2639 if (term != NULL)
2640 {
2641 if (term->tl_job == ch->ch_job)
2642 maketitle();
2643 update_cursor(term, term->tl_cursor_visible);
2644 }
2645 }
2646}
2647
2648/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002649 * Fill one screen line from a line of the terminal.
2650 * Advances "pos" to past the last column.
2651 */
2652 static void
2653term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2654{
2655 int off = screen_get_current_line_off();
2656
2657 for (pos->col = 0; pos->col < max_col; )
2658 {
2659 VTermScreenCell cell;
2660 int c;
2661
2662 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2663 vim_memset(&cell, 0, sizeof(cell));
2664
2665 c = cell.chars[0];
2666 if (c == NUL)
2667 {
2668 ScreenLines[off] = ' ';
2669 if (enc_utf8)
2670 ScreenLinesUC[off] = NUL;
2671 }
2672 else
2673 {
2674 if (enc_utf8)
2675 {
2676 int i;
2677
2678 /* composing chars */
2679 for (i = 0; i < Screen_mco
2680 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2681 {
2682 ScreenLinesC[i][off] = cell.chars[i + 1];
2683 if (cell.chars[i + 1] == 0)
2684 break;
2685 }
2686 if (c >= 0x80 || (Screen_mco > 0
2687 && ScreenLinesC[0][off] != 0))
2688 {
2689 ScreenLines[off] = ' ';
2690 ScreenLinesUC[off] = c;
2691 }
2692 else
2693 {
2694 ScreenLines[off] = c;
2695 ScreenLinesUC[off] = NUL;
2696 }
2697 }
2698#ifdef WIN3264
2699 else if (has_mbyte && c >= 0x80)
2700 {
2701 char_u mb[MB_MAXBYTES+1];
2702 WCHAR wc = c;
2703
2704 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2705 (char*)mb, 2, 0, 0) > 1)
2706 {
2707 ScreenLines[off] = mb[0];
2708 ScreenLines[off + 1] = mb[1];
2709 cell.width = mb_ptr2cells(mb);
2710 }
2711 else
2712 ScreenLines[off] = c;
2713 }
2714#endif
2715 else
2716 ScreenLines[off] = c;
2717 }
2718 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2719
2720 ++pos->col;
2721 ++off;
2722 if (cell.width == 2)
2723 {
2724 if (enc_utf8)
2725 ScreenLinesUC[off] = NUL;
2726
2727 /* don't set the second byte to NUL for a DBCS encoding, it
2728 * has been set above */
2729 if (enc_utf8 || !has_mbyte)
2730 ScreenLines[off] = NUL;
2731
2732 ++pos->col;
2733 ++off;
2734 }
2735 }
2736}
2737
2738 static void
2739update_system_term(term_T *term)
2740{
2741 VTermPos pos;
2742 VTermScreen *screen;
2743
2744 if (term->tl_vterm == NULL)
2745 return;
2746 screen = vterm_obtain_screen(term->tl_vterm);
2747
2748 /* Scroll up to make more room for terminal lines if needed. */
2749 while (term->tl_toprow > 0
2750 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2751 {
2752 int save_p_more = p_more;
2753
2754 p_more = FALSE;
2755 msg_row = Rows - 1;
2756 msg_puts((char_u *)"\n");
2757 p_more = save_p_more;
2758 --term->tl_toprow;
2759 }
2760
2761 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2762 && pos.row < Rows; ++pos.row)
2763 {
2764 if (pos.row < term->tl_rows)
2765 {
2766 int max_col = MIN(Columns, term->tl_cols);
2767
2768 term_line2screenline(screen, &pos, max_col);
2769 }
2770 else
2771 pos.col = 0;
2772
2773 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2774 }
2775
2776 term->tl_dirty_row_start = MAX_ROW;
2777 term->tl_dirty_row_end = 0;
2778 update_cursor(term, TRUE);
2779}
2780
2781/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002782 * Called to update a window that contains an active terminal.
2783 * Returns FAIL when there is no terminal running in this window or in
2784 * Terminal-Normal mode.
2785 */
2786 int
2787term_update_window(win_T *wp)
2788{
2789 term_T *term = wp->w_buffer->b_term;
2790 VTerm *vterm;
2791 VTermScreen *screen;
2792 VTermState *state;
2793 VTermPos pos;
2794
2795 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2796 return FAIL;
2797
2798 vterm = term->tl_vterm;
2799 screen = vterm_obtain_screen(vterm);
2800 state = vterm_obtain_state(vterm);
2801
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002802 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002803 {
2804 term->tl_dirty_row_start = 0;
2805 term->tl_dirty_row_end = MAX_ROW;
2806 }
2807
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002808 /*
2809 * If the window was resized a redraw will be triggered and we get here.
2810 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2811 */
2812 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
2813 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
2814 {
2815 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
2816 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
2817 win_T *twp;
2818
2819 FOR_ALL_WINDOWS(twp)
2820 {
2821 /* When more than one window shows the same terminal, use the
2822 * smallest size. */
2823 if (twp->w_buffer == term->tl_buffer)
2824 {
2825 if (!term->tl_rows_fixed && rows > twp->w_height)
2826 rows = twp->w_height;
2827 if (!term->tl_cols_fixed && cols > twp->w_width)
2828 cols = twp->w_width;
2829 }
2830 }
2831
2832 term->tl_vterm_size_changed = TRUE;
2833 vterm_set_size(vterm, rows, cols);
2834 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
2835 rows);
2836 term_report_winsize(term, rows, cols);
2837 }
2838
2839 /* The cursor may have been moved when resizing. */
2840 vterm_state_get_cursorpos(state, &pos);
2841 position_cursor(wp, &pos);
2842
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002843 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2844 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002845 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002846 if (pos.row < term->tl_rows)
2847 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002848 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002849
Bram Moolenaar13568252018-03-16 20:46:58 +01002850 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002851 }
2852 else
2853 pos.col = 0;
2854
Bram Moolenaarf118d482018-03-13 13:14:00 +01002855 screen_line(wp->w_winrow + pos.row
2856#ifdef FEAT_MENU
2857 + winbar_height(wp)
2858#endif
2859 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002860 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002861 term->tl_dirty_row_start = MAX_ROW;
2862 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002863
2864 return OK;
2865}
2866
2867/*
2868 * Return TRUE if "wp" is a terminal window where the job has finished.
2869 */
2870 int
2871term_is_finished(buf_T *buf)
2872{
2873 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2874}
2875
2876/*
2877 * Return TRUE if "wp" is a terminal window where the job has finished or we
2878 * are in Terminal-Normal mode, thus we show the buffer contents.
2879 */
2880 int
2881term_show_buffer(buf_T *buf)
2882{
2883 term_T *term = buf->b_term;
2884
2885 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2886}
2887
2888/*
2889 * The current buffer is going to be changed. If there is terminal
2890 * highlighting remove it now.
2891 */
2892 void
2893term_change_in_curbuf(void)
2894{
2895 term_T *term = curbuf->b_term;
2896
2897 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2898 {
2899 free_scrollback(term);
2900 redraw_buf_later(term->tl_buffer, NOT_VALID);
2901
2902 /* The buffer is now like a normal buffer, it cannot be easily
2903 * abandoned when changed. */
2904 set_string_option_direct((char_u *)"buftype", -1,
2905 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2906 }
2907}
2908
2909/*
2910 * Get the screen attribute for a position in the buffer.
2911 * Use a negative "col" to get the filler background color.
2912 */
2913 int
2914term_get_attr(buf_T *buf, linenr_T lnum, int col)
2915{
2916 term_T *term = buf->b_term;
2917 sb_line_T *line;
2918 cellattr_T *cellattr;
2919
2920 if (lnum > term->tl_scrollback.ga_len)
2921 cellattr = &term->tl_default_color;
2922 else
2923 {
2924 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
2925 if (col < 0 || col >= line->sb_cols)
2926 cellattr = &line->sb_fill_attr;
2927 else
2928 cellattr = line->sb_cells + col;
2929 }
2930 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
2931}
2932
2933static VTermColor ansi_table[16] = {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002934 { 0, 0, 0, 1}, /* black */
2935 {224, 0, 0, 2}, /* dark red */
2936 { 0, 224, 0, 3}, /* dark green */
2937 {224, 224, 0, 4}, /* dark yellow / brown */
2938 { 0, 0, 224, 5}, /* dark blue */
2939 {224, 0, 224, 6}, /* dark magenta */
2940 { 0, 224, 224, 7}, /* dark cyan */
2941 {224, 224, 224, 8}, /* light grey */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002942
Bram Moolenaar46359e12017-11-29 22:33:38 +01002943 {128, 128, 128, 9}, /* dark grey */
2944 {255, 64, 64, 10}, /* light red */
2945 { 64, 255, 64, 11}, /* light green */
2946 {255, 255, 64, 12}, /* yellow */
2947 { 64, 64, 255, 13}, /* light blue */
2948 {255, 64, 255, 14}, /* light magenta */
2949 { 64, 255, 255, 15}, /* light cyan */
2950 {255, 255, 255, 16}, /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002951};
2952
2953static int cube_value[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002954 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002955};
2956
2957static int grey_ramp[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002958 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
2959 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002960};
2961
2962/*
2963 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002964 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002965 */
2966 static void
2967cterm_color2rgb(int nr, VTermColor *rgb)
2968{
2969 int idx;
2970
2971 if (nr < 16)
2972 {
2973 *rgb = ansi_table[nr];
2974 }
2975 else if (nr < 232)
2976 {
2977 /* 216 color cube */
2978 idx = nr - 16;
2979 rgb->blue = cube_value[idx % 6];
2980 rgb->green = cube_value[idx / 6 % 6];
2981 rgb->red = cube_value[idx / 36 % 6];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002982 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002983 }
2984 else if (nr < 256)
2985 {
2986 /* 24 grey scale ramp */
2987 idx = nr - 232;
2988 rgb->blue = grey_ramp[idx];
2989 rgb->green = grey_ramp[idx];
2990 rgb->red = grey_ramp[idx];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002991 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002992 }
2993}
2994
2995/*
2996 * Create a new vterm and initialize it.
2997 */
2998 static void
2999create_vterm(term_T *term, int rows, int cols)
3000{
3001 VTerm *vterm;
3002 VTermScreen *screen;
3003 VTermValue value;
3004 VTermColor *fg, *bg;
3005 int fgval, bgval;
3006 int id;
3007
3008 vterm = vterm_new(rows, cols);
3009 term->tl_vterm = vterm;
3010 screen = vterm_obtain_screen(vterm);
3011 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3012 /* TODO: depends on 'encoding'. */
3013 vterm_set_utf8(vterm, 1);
3014
3015 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3016 term->tl_default_color.width = 1;
3017 fg = &term->tl_default_color.fg;
3018 bg = &term->tl_default_color.bg;
3019
3020 /* Vterm uses a default black background. Set it to white when
3021 * 'background' is "light". */
3022 if (*p_bg == 'l')
3023 {
3024 fgval = 0;
3025 bgval = 255;
3026 }
3027 else
3028 {
3029 fgval = 255;
3030 bgval = 0;
3031 }
3032 fg->red = fg->green = fg->blue = fgval;
3033 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003034 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003035
3036 /* The "Terminal" highlight group overrules the defaults. */
3037 id = syn_name2id((char_u *)"Terminal");
3038
Bram Moolenaar46359e12017-11-29 22:33:38 +01003039 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003040#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3041 if (0
3042# ifdef FEAT_GUI
3043 || gui.in_use
3044# endif
3045# ifdef FEAT_TERMGUICOLORS
3046 || p_tgc
3047# endif
3048 )
3049 {
3050 guicolor_T fg_rgb = INVALCOLOR;
3051 guicolor_T bg_rgb = INVALCOLOR;
3052
3053 if (id != 0)
3054 syn_id2colors(id, &fg_rgb, &bg_rgb);
3055
3056# ifdef FEAT_GUI
3057 if (gui.in_use)
3058 {
3059 if (fg_rgb == INVALCOLOR)
3060 fg_rgb = gui.norm_pixel;
3061 if (bg_rgb == INVALCOLOR)
3062 bg_rgb = gui.back_pixel;
3063 }
3064# ifdef FEAT_TERMGUICOLORS
3065 else
3066# endif
3067# endif
3068# ifdef FEAT_TERMGUICOLORS
3069 {
3070 if (fg_rgb == INVALCOLOR)
3071 fg_rgb = cterm_normal_fg_gui_color;
3072 if (bg_rgb == INVALCOLOR)
3073 bg_rgb = cterm_normal_bg_gui_color;
3074 }
3075# endif
3076 if (fg_rgb != INVALCOLOR)
3077 {
3078 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3079
3080 fg->red = (unsigned)(rgb >> 16);
3081 fg->green = (unsigned)(rgb >> 8) & 255;
3082 fg->blue = (unsigned)rgb & 255;
3083 }
3084 if (bg_rgb != INVALCOLOR)
3085 {
3086 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3087
3088 bg->red = (unsigned)(rgb >> 16);
3089 bg->green = (unsigned)(rgb >> 8) & 255;
3090 bg->blue = (unsigned)rgb & 255;
3091 }
3092 }
3093 else
3094#endif
3095 if (id != 0 && t_colors >= 16)
3096 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003097 if (term_default_cterm_fg >= 0)
3098 cterm_color2rgb(term_default_cterm_fg, fg);
3099 if (term_default_cterm_bg >= 0)
3100 cterm_color2rgb(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003101 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003102 else
3103 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003104#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003105 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003106#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003107
3108 /* In an MS-Windows console we know the normal colors. */
3109 if (cterm_normal_fg_color > 0)
3110 {
3111 cterm_color2rgb(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003112# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003113 tmp = fg->red;
3114 fg->red = fg->blue;
3115 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003116# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003117 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003118# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003119 else
3120 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003121# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003122
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003123 if (cterm_normal_bg_color > 0)
3124 {
3125 cterm_color2rgb(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003126# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003127 tmp = bg->red;
3128 bg->red = bg->blue;
3129 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003130# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003131 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003132# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003133 else
3134 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003135# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003136 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003137
3138 vterm_state_set_default_colors(vterm_obtain_state(vterm), fg, bg);
3139
3140 /* Required to initialize most things. */
3141 vterm_screen_reset(screen, 1 /* hard */);
3142
3143 /* Allow using alternate screen. */
3144 vterm_screen_enable_altscreen(screen, 1);
3145
3146 /* For unix do not use a blinking cursor. In an xterm this causes the
3147 * cursor to blink if it's blinking in the xterm.
3148 * For Windows we respect the system wide setting. */
3149#ifdef WIN3264
3150 if (GetCaretBlinkTime() == INFINITE)
3151 value.boolean = 0;
3152 else
3153 value.boolean = 1;
3154#else
3155 value.boolean = 0;
3156#endif
3157 vterm_state_set_termprop(vterm_obtain_state(vterm),
3158 VTERM_PROP_CURSORBLINK, &value);
3159}
3160
3161/*
3162 * Return the text to show for the buffer name and status.
3163 */
3164 char_u *
3165term_get_status_text(term_T *term)
3166{
3167 if (term->tl_status_text == NULL)
3168 {
3169 char_u *txt;
3170 size_t len;
3171
3172 if (term->tl_normal_mode)
3173 {
3174 if (term_job_running(term))
3175 txt = (char_u *)_("Terminal");
3176 else
3177 txt = (char_u *)_("Terminal-finished");
3178 }
3179 else if (term->tl_title != NULL)
3180 txt = term->tl_title;
3181 else if (term_none_open(term))
3182 txt = (char_u *)_("active");
3183 else if (term_job_running(term))
3184 txt = (char_u *)_("running");
3185 else
3186 txt = (char_u *)_("finished");
3187 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3188 term->tl_status_text = alloc((int)len);
3189 if (term->tl_status_text != NULL)
3190 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3191 term->tl_buffer->b_fname, txt);
3192 }
3193 return term->tl_status_text;
3194}
3195
3196/*
3197 * Mark references in jobs of terminals.
3198 */
3199 int
3200set_ref_in_term(int copyID)
3201{
3202 int abort = FALSE;
3203 term_T *term;
3204 typval_T tv;
3205
3206 for (term = first_term; term != NULL; term = term->tl_next)
3207 if (term->tl_job != NULL)
3208 {
3209 tv.v_type = VAR_JOB;
3210 tv.vval.v_job = term->tl_job;
3211 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3212 }
3213 return abort;
3214}
3215
3216/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003217 * Cache "Terminal" highlight group colors.
3218 */
3219 void
3220set_terminal_default_colors(int cterm_fg, int cterm_bg)
3221{
3222 term_default_cterm_fg = cterm_fg - 1;
3223 term_default_cterm_bg = cterm_bg - 1;
3224}
3225
3226/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003227 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003228 * Returns NULL when the buffer is not for a terminal window and logs a message
3229 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003230 */
3231 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003232term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003233{
3234 buf_T *buf;
3235
3236 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3237 ++emsg_off;
3238 buf = get_buf_tv(&argvars[0], FALSE);
3239 --emsg_off;
3240 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003241 {
3242 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003243 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003244 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003245 return buf;
3246}
3247
Bram Moolenaard96ff162018-02-18 22:13:29 +01003248 static int
3249same_color(VTermColor *a, VTermColor *b)
3250{
3251 return a->red == b->red
3252 && a->green == b->green
3253 && a->blue == b->blue
3254 && a->ansi_index == b->ansi_index;
3255}
3256
3257 static void
3258dump_term_color(FILE *fd, VTermColor *color)
3259{
3260 fprintf(fd, "%02x%02x%02x%d",
3261 (int)color->red, (int)color->green, (int)color->blue,
3262 (int)color->ansi_index);
3263}
3264
3265/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003266 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003267 *
3268 * Each screen cell in full is:
3269 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3270 * {characters} is a space for an empty cell
3271 * For a double-width character "+" is changed to "*" and the next cell is
3272 * skipped.
3273 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3274 * when "&" use the same as the previous cell.
3275 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3276 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3277 * {color-idx} is a number from 0 to 255
3278 *
3279 * Screen cell with same width, attributes and color as the previous one:
3280 * |{characters}
3281 *
3282 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3283 *
3284 * Repeating the previous screen cell:
3285 * @{count}
3286 */
3287 void
3288f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3289{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003290 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003291 term_T *term;
3292 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003293 int max_height = 0;
3294 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003295 stat_T st;
3296 FILE *fd;
3297 VTermPos pos;
3298 VTermScreen *screen;
3299 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003300 VTermState *state;
3301 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003302
3303 if (check_restricted() || check_secure())
3304 return;
3305 if (buf == NULL)
3306 return;
3307 term = buf->b_term;
3308
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003309 if (argvars[2].v_type != VAR_UNKNOWN)
3310 {
3311 dict_T *d;
3312
3313 if (argvars[2].v_type != VAR_DICT)
3314 {
3315 EMSG(_(e_dictreq));
3316 return;
3317 }
3318 d = argvars[2].vval.v_dict;
3319 if (d != NULL)
3320 {
3321 max_height = get_dict_number(d, (char_u *)"rows");
3322 max_width = get_dict_number(d, (char_u *)"columns");
3323 }
3324 }
3325
Bram Moolenaard96ff162018-02-18 22:13:29 +01003326 fname = get_tv_string_chk(&argvars[1]);
3327 if (fname == NULL)
3328 return;
3329 if (mch_stat((char *)fname, &st) >= 0)
3330 {
3331 EMSG2(_("E953: File exists: %s"), fname);
3332 return;
3333 }
3334
Bram Moolenaard96ff162018-02-18 22:13:29 +01003335 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3336 {
3337 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3338 return;
3339 }
3340
3341 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3342
3343 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003344 state = vterm_obtain_state(term->tl_vterm);
3345 vterm_state_get_cursorpos(state, &cursor_pos);
3346
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003347 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3348 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003349 {
3350 int repeat = 0;
3351
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003352 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3353 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003354 {
3355 VTermScreenCell cell;
3356 int same_attr;
3357 int same_chars = TRUE;
3358 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003359 int is_cursor_pos = (pos.col == cursor_pos.col
3360 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003361
3362 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3363 vim_memset(&cell, 0, sizeof(cell));
3364
3365 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3366 {
3367 if (cell.chars[i] != prev_cell.chars[i])
3368 same_chars = FALSE;
3369 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3370 break;
3371 }
3372 same_attr = vtermAttr2hl(cell.attrs)
3373 == vtermAttr2hl(prev_cell.attrs)
3374 && same_color(&cell.fg, &prev_cell.fg)
3375 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003376 if (same_chars && cell.width == prev_cell.width && same_attr
3377 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003378 {
3379 ++repeat;
3380 }
3381 else
3382 {
3383 if (repeat > 0)
3384 {
3385 fprintf(fd, "@%d", repeat);
3386 repeat = 0;
3387 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003388 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003389
3390 if (cell.chars[0] == NUL)
3391 fputs(" ", fd);
3392 else
3393 {
3394 char_u charbuf[10];
3395 int len;
3396
3397 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3398 && cell.chars[i] != NUL; ++i)
3399 {
3400 len = utf_char2bytes(cell.chars[0], charbuf);
3401 fwrite(charbuf, len, 1, fd);
3402 }
3403 }
3404
3405 /* When only the characters differ we don't write anything, the
3406 * following "|", "@" or NL will indicate using the same
3407 * attributes. */
3408 if (cell.width != prev_cell.width || !same_attr)
3409 {
3410 if (cell.width == 2)
3411 {
3412 fputs("*", fd);
3413 ++pos.col;
3414 }
3415 else
3416 fputs("+", fd);
3417
3418 if (same_attr)
3419 {
3420 fputs("&", fd);
3421 }
3422 else
3423 {
3424 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3425 if (same_color(&cell.fg, &prev_cell.fg))
3426 fputs("&", fd);
3427 else
3428 {
3429 fputs("#", fd);
3430 dump_term_color(fd, &cell.fg);
3431 }
3432 if (same_color(&cell.bg, &prev_cell.bg))
3433 fputs("&", fd);
3434 else
3435 {
3436 fputs("#", fd);
3437 dump_term_color(fd, &cell.bg);
3438 }
3439 }
3440 }
3441
3442 prev_cell = cell;
3443 }
3444 }
3445 if (repeat > 0)
3446 fprintf(fd, "@%d", repeat);
3447 fputs("\n", fd);
3448 }
3449
3450 fclose(fd);
3451}
3452
3453/*
3454 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3455 */
3456 static void
3457dump_is_corrupt(garray_T *gap)
3458{
3459 ga_concat(gap, (char_u *)"CORRUPT");
3460}
3461
3462 static void
3463append_cell(garray_T *gap, cellattr_T *cell)
3464{
3465 if (ga_grow(gap, 1) == OK)
3466 {
3467 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3468 ++gap->ga_len;
3469 }
3470}
3471
3472/*
3473 * Read the dump file from "fd" and append lines to the current buffer.
3474 * Return the cell width of the longest line.
3475 */
3476 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003477read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003478{
3479 int c;
3480 garray_T ga_text;
3481 garray_T ga_cell;
3482 char_u *prev_char = NULL;
3483 int attr = 0;
3484 cellattr_T cell;
3485 term_T *term = curbuf->b_term;
3486 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003487 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003488
3489 ga_init2(&ga_text, 1, 90);
3490 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3491 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003492 cursor_pos->row = -1;
3493 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003494
3495 c = fgetc(fd);
3496 for (;;)
3497 {
3498 if (c == EOF)
3499 break;
3500 if (c == '\n')
3501 {
3502 /* End of a line: append it to the buffer. */
3503 if (ga_text.ga_data == NULL)
3504 dump_is_corrupt(&ga_text);
3505 if (ga_grow(&term->tl_scrollback, 1) == OK)
3506 {
3507 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3508 + term->tl_scrollback.ga_len;
3509
3510 if (max_cells < ga_cell.ga_len)
3511 max_cells = ga_cell.ga_len;
3512 line->sb_cols = ga_cell.ga_len;
3513 line->sb_cells = ga_cell.ga_data;
3514 line->sb_fill_attr = term->tl_default_color;
3515 ++term->tl_scrollback.ga_len;
3516 ga_init(&ga_cell);
3517
3518 ga_append(&ga_text, NUL);
3519 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3520 ga_text.ga_len, FALSE);
3521 }
3522 else
3523 ga_clear(&ga_cell);
3524 ga_text.ga_len = 0;
3525
3526 c = fgetc(fd);
3527 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003528 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003529 {
3530 int prev_len = ga_text.ga_len;
3531
Bram Moolenaar9271d052018-02-25 21:39:46 +01003532 if (c == '>')
3533 {
3534 if (cursor_pos->row != -1)
3535 dump_is_corrupt(&ga_text); /* duplicate cursor */
3536 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3537 cursor_pos->col = ga_cell.ga_len;
3538 }
3539
Bram Moolenaard96ff162018-02-18 22:13:29 +01003540 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3541 c = fgetc(fd);
3542 if (c != EOF)
3543 ga_append(&ga_text, c);
3544 for (;;)
3545 {
3546 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003547 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003548 || c == EOF || c == '\n')
3549 break;
3550 ga_append(&ga_text, c);
3551 }
3552
3553 /* save the character for repeating it */
3554 vim_free(prev_char);
3555 if (ga_text.ga_data != NULL)
3556 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3557 ga_text.ga_len - prev_len);
3558
Bram Moolenaar9271d052018-02-25 21:39:46 +01003559 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003560 {
3561 /* use all attributes from previous cell */
3562 }
3563 else if (c == '+' || c == '*')
3564 {
3565 int is_bg;
3566
3567 cell.width = c == '+' ? 1 : 2;
3568
3569 c = fgetc(fd);
3570 if (c == '&')
3571 {
3572 /* use same attr as previous cell */
3573 c = fgetc(fd);
3574 }
3575 else if (isdigit(c))
3576 {
3577 /* get the decimal attribute */
3578 attr = 0;
3579 while (isdigit(c))
3580 {
3581 attr = attr * 10 + (c - '0');
3582 c = fgetc(fd);
3583 }
3584 hl2vtermAttr(attr, &cell);
3585 }
3586 else
3587 dump_is_corrupt(&ga_text);
3588
3589 /* is_bg == 0: fg, is_bg == 1: bg */
3590 for (is_bg = 0; is_bg <= 1; ++is_bg)
3591 {
3592 if (c == '&')
3593 {
3594 /* use same color as previous cell */
3595 c = fgetc(fd);
3596 }
3597 else if (c == '#')
3598 {
3599 int red, green, blue, index = 0;
3600
3601 c = fgetc(fd);
3602 red = hex2nr(c);
3603 c = fgetc(fd);
3604 red = (red << 4) + hex2nr(c);
3605 c = fgetc(fd);
3606 green = hex2nr(c);
3607 c = fgetc(fd);
3608 green = (green << 4) + hex2nr(c);
3609 c = fgetc(fd);
3610 blue = hex2nr(c);
3611 c = fgetc(fd);
3612 blue = (blue << 4) + hex2nr(c);
3613 c = fgetc(fd);
3614 if (!isdigit(c))
3615 dump_is_corrupt(&ga_text);
3616 while (isdigit(c))
3617 {
3618 index = index * 10 + (c - '0');
3619 c = fgetc(fd);
3620 }
3621
3622 if (is_bg)
3623 {
3624 cell.bg.red = red;
3625 cell.bg.green = green;
3626 cell.bg.blue = blue;
3627 cell.bg.ansi_index = index;
3628 }
3629 else
3630 {
3631 cell.fg.red = red;
3632 cell.fg.green = green;
3633 cell.fg.blue = blue;
3634 cell.fg.ansi_index = index;
3635 }
3636 }
3637 else
3638 dump_is_corrupt(&ga_text);
3639 }
3640 }
3641 else
3642 dump_is_corrupt(&ga_text);
3643
3644 append_cell(&ga_cell, &cell);
3645 }
3646 else if (c == '@')
3647 {
3648 if (prev_char == NULL)
3649 dump_is_corrupt(&ga_text);
3650 else
3651 {
3652 int count = 0;
3653
3654 /* repeat previous character, get the count */
3655 for (;;)
3656 {
3657 c = fgetc(fd);
3658 if (!isdigit(c))
3659 break;
3660 count = count * 10 + (c - '0');
3661 }
3662
3663 while (count-- > 0)
3664 {
3665 ga_concat(&ga_text, prev_char);
3666 append_cell(&ga_cell, &cell);
3667 }
3668 }
3669 }
3670 else
3671 {
3672 dump_is_corrupt(&ga_text);
3673 c = fgetc(fd);
3674 }
3675 }
3676
3677 if (ga_text.ga_len > 0)
3678 {
3679 /* trailing characters after last NL */
3680 dump_is_corrupt(&ga_text);
3681 ga_append(&ga_text, NUL);
3682 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3683 ga_text.ga_len, FALSE);
3684 }
3685
3686 ga_clear(&ga_text);
3687 vim_free(prev_char);
3688
3689 return max_cells;
3690}
3691
3692/*
3693 * Common for "term_dumpdiff()" and "term_dumpload()".
3694 */
3695 static void
3696term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
3697{
3698 jobopt_T opt;
3699 buf_T *buf;
3700 char_u buf1[NUMBUFLEN];
3701 char_u buf2[NUMBUFLEN];
3702 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003703 char_u *fname2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003704 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003705 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003706 char_u *textline = NULL;
3707
3708 /* First open the files. If this fails bail out. */
3709 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
3710 if (do_diff)
3711 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
3712 if (fname1 == NULL || (do_diff && fname2 == NULL))
3713 {
3714 EMSG(_(e_invarg));
3715 return;
3716 }
3717 fd1 = mch_fopen((char *)fname1, READBIN);
3718 if (fd1 == NULL)
3719 {
3720 EMSG2(_(e_notread), fname1);
3721 return;
3722 }
3723 if (do_diff)
3724 {
3725 fd2 = mch_fopen((char *)fname2, READBIN);
3726 if (fd2 == NULL)
3727 {
3728 fclose(fd1);
3729 EMSG2(_(e_notread), fname2);
3730 return;
3731 }
3732 }
3733
3734 init_job_options(&opt);
3735 /* TODO: use the {options} argument */
3736
3737 /* TODO: use the file name arguments for the buffer name */
3738 opt.jo_term_name = (char_u *)"dump diff";
3739
Bram Moolenaar13568252018-03-16 20:46:58 +01003740 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003741 if (buf != NULL && buf->b_term != NULL)
3742 {
3743 int i;
3744 linenr_T bot_lnum;
3745 linenr_T lnum;
3746 term_T *term = buf->b_term;
3747 int width;
3748 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003749 VTermPos cursor_pos1;
3750 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003751
3752 rettv->vval.v_number = buf->b_fnum;
3753
3754 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01003755 width = read_dump_file(fd1, &cursor_pos1);
3756
3757 /* position the cursor */
3758 if (cursor_pos1.row >= 0)
3759 {
3760 curwin->w_cursor.lnum = cursor_pos1.row + 1;
3761 coladvance(cursor_pos1.col);
3762 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003763
3764 /* Delete the empty line that was in the empty buffer. */
3765 ml_delete(1, FALSE);
3766
3767 /* For term_dumpload() we are done here. */
3768 if (!do_diff)
3769 goto theend;
3770
3771 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
3772
3773 textline = alloc(width + 1);
3774 if (textline == NULL)
3775 goto theend;
3776 for (i = 0; i < width; ++i)
3777 textline[i] = '=';
3778 textline[width] = NUL;
3779 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3780 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3781 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3782 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3783
3784 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003785 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003786 if (width2 > width)
3787 {
3788 vim_free(textline);
3789 textline = alloc(width2 + 1);
3790 if (textline == NULL)
3791 goto theend;
3792 width = width2;
3793 textline[width] = NUL;
3794 }
3795 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
3796
3797 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
3798 {
3799 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
3800 {
3801 /* bottom part has fewer rows, fill with "-" */
3802 for (i = 0; i < width; ++i)
3803 textline[i] = '-';
3804 }
3805 else
3806 {
3807 char_u *line1;
3808 char_u *line2;
3809 char_u *p1;
3810 char_u *p2;
3811 int col;
3812 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3813 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
3814 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
3815 ->sb_cells;
3816
3817 /* Make a copy, getting the second line will invalidate it. */
3818 line1 = vim_strsave(ml_get(lnum));
3819 if (line1 == NULL)
3820 break;
3821 p1 = line1;
3822
3823 line2 = ml_get(lnum + bot_lnum);
3824 p2 = line2;
3825 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
3826 {
3827 int len1 = utfc_ptr2len(p1);
3828 int len2 = utfc_ptr2len(p2);
3829
3830 textline[col] = ' ';
3831 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01003832 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01003833 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01003834 else if (lnum == cursor_pos1.row + 1
3835 && col == cursor_pos1.col
3836 && (cursor_pos1.row != cursor_pos2.row
3837 || cursor_pos1.col != cursor_pos2.col))
3838 /* cursor in first but not in second */
3839 textline[col] = '>';
3840 else if (lnum == cursor_pos2.row + 1
3841 && col == cursor_pos2.col
3842 && (cursor_pos1.row != cursor_pos2.row
3843 || cursor_pos1.col != cursor_pos2.col))
3844 /* cursor in second but not in first */
3845 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01003846 else if (cellattr1 != NULL && cellattr2 != NULL)
3847 {
3848 if ((cellattr1 + col)->width
3849 != (cellattr2 + col)->width)
3850 textline[col] = 'w';
3851 else if (!same_color(&(cellattr1 + col)->fg,
3852 &(cellattr2 + col)->fg))
3853 textline[col] = 'f';
3854 else if (!same_color(&(cellattr1 + col)->bg,
3855 &(cellattr2 + col)->bg))
3856 textline[col] = 'b';
3857 else if (vtermAttr2hl((cellattr1 + col)->attrs)
3858 != vtermAttr2hl(((cellattr2 + col)->attrs)))
3859 textline[col] = 'a';
3860 }
3861 p1 += len1;
3862 p2 += len2;
3863 /* TODO: handle different width */
3864 }
3865 vim_free(line1);
3866
3867 while (col < width)
3868 {
3869 if (*p1 == NUL && *p2 == NUL)
3870 textline[col] = '?';
3871 else if (*p1 == NUL)
3872 {
3873 textline[col] = '+';
3874 p2 += utfc_ptr2len(p2);
3875 }
3876 else
3877 {
3878 textline[col] = '-';
3879 p1 += utfc_ptr2len(p1);
3880 }
3881 ++col;
3882 }
3883 }
3884 if (add_empty_scrollback(term, &term->tl_default_color,
3885 term->tl_top_diff_rows) == OK)
3886 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3887 ++bot_lnum;
3888 }
3889
3890 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
3891 {
3892 /* bottom part has more rows, fill with "+" */
3893 for (i = 0; i < width; ++i)
3894 textline[i] = '+';
3895 if (add_empty_scrollback(term, &term->tl_default_color,
3896 term->tl_top_diff_rows) == OK)
3897 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3898 ++lnum;
3899 ++bot_lnum;
3900 }
3901
3902 term->tl_cols = width;
3903 }
3904
3905theend:
3906 vim_free(textline);
3907 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003908 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003909 fclose(fd2);
3910}
3911
3912/*
3913 * If the current buffer shows the output of term_dumpdiff(), swap the top and
3914 * bottom files.
3915 * Return FAIL when this is not possible.
3916 */
3917 int
3918term_swap_diff()
3919{
3920 term_T *term = curbuf->b_term;
3921 linenr_T line_count;
3922 linenr_T top_rows;
3923 linenr_T bot_rows;
3924 linenr_T bot_start;
3925 linenr_T lnum;
3926 char_u *p;
3927 sb_line_T *sb_line;
3928
3929 if (term == NULL
3930 || !term_is_finished(curbuf)
3931 || term->tl_top_diff_rows == 0
3932 || term->tl_scrollback.ga_len == 0)
3933 return FAIL;
3934
3935 line_count = curbuf->b_ml.ml_line_count;
3936 top_rows = term->tl_top_diff_rows;
3937 bot_rows = term->tl_bot_diff_rows;
3938 bot_start = line_count - bot_rows;
3939 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3940
3941 /* move lines from top to above the bottom part */
3942 for (lnum = 1; lnum <= top_rows; ++lnum)
3943 {
3944 p = vim_strsave(ml_get(1));
3945 if (p == NULL)
3946 return OK;
3947 ml_append(bot_start, p, 0, FALSE);
3948 ml_delete(1, FALSE);
3949 vim_free(p);
3950 }
3951
3952 /* move lines from bottom to the top */
3953 for (lnum = 1; lnum <= bot_rows; ++lnum)
3954 {
3955 p = vim_strsave(ml_get(bot_start + lnum));
3956 if (p == NULL)
3957 return OK;
3958 ml_delete(bot_start + lnum, FALSE);
3959 ml_append(lnum - 1, p, 0, FALSE);
3960 vim_free(p);
3961 }
3962
3963 if (top_rows == bot_rows)
3964 {
3965 /* rows counts are equal, can swap cell properties */
3966 for (lnum = 0; lnum < top_rows; ++lnum)
3967 {
3968 sb_line_T temp;
3969
3970 temp = *(sb_line + lnum);
3971 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
3972 *(sb_line + bot_start + lnum) = temp;
3973 }
3974 }
3975 else
3976 {
3977 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
3978 sb_line_T *temp = (sb_line_T *)alloc((int)size);
3979
3980 /* need to copy cell properties into temp memory */
3981 if (temp != NULL)
3982 {
3983 mch_memmove(temp, term->tl_scrollback.ga_data, size);
3984 mch_memmove(term->tl_scrollback.ga_data,
3985 temp + bot_start,
3986 sizeof(sb_line_T) * bot_rows);
3987 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
3988 temp + top_rows,
3989 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
3990 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
3991 + line_count - top_rows,
3992 temp,
3993 sizeof(sb_line_T) * top_rows);
3994 vim_free(temp);
3995 }
3996 }
3997
3998 term->tl_top_diff_rows = bot_rows;
3999 term->tl_bot_diff_rows = top_rows;
4000
4001 update_screen(NOT_VALID);
4002 return OK;
4003}
4004
4005/*
4006 * "term_dumpdiff(filename, filename, options)" function
4007 */
4008 void
4009f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4010{
4011 term_load_dump(argvars, rettv, TRUE);
4012}
4013
4014/*
4015 * "term_dumpload(filename, options)" function
4016 */
4017 void
4018f_term_dumpload(typval_T *argvars, typval_T *rettv)
4019{
4020 term_load_dump(argvars, rettv, FALSE);
4021}
4022
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004023/*
4024 * "term_getaltscreen(buf)" function
4025 */
4026 void
4027f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4028{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004029 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004030
4031 if (buf == NULL)
4032 return;
4033 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4034}
4035
4036/*
4037 * "term_getattr(attr, name)" function
4038 */
4039 void
4040f_term_getattr(typval_T *argvars, typval_T *rettv)
4041{
4042 int attr;
4043 size_t i;
4044 char_u *name;
4045
4046 static struct {
4047 char *name;
4048 int attr;
4049 } attrs[] = {
4050 {"bold", HL_BOLD},
4051 {"italic", HL_ITALIC},
4052 {"underline", HL_UNDERLINE},
4053 {"strike", HL_STRIKETHROUGH},
4054 {"reverse", HL_INVERSE},
4055 };
4056
4057 attr = get_tv_number(&argvars[0]);
4058 name = get_tv_string_chk(&argvars[1]);
4059 if (name == NULL)
4060 return;
4061
4062 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4063 if (STRCMP(name, attrs[i].name) == 0)
4064 {
4065 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4066 break;
4067 }
4068}
4069
4070/*
4071 * "term_getcursor(buf)" function
4072 */
4073 void
4074f_term_getcursor(typval_T *argvars, typval_T *rettv)
4075{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004076 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004077 term_T *term;
4078 list_T *l;
4079 dict_T *d;
4080
4081 if (rettv_list_alloc(rettv) == FAIL)
4082 return;
4083 if (buf == NULL)
4084 return;
4085 term = buf->b_term;
4086
4087 l = rettv->vval.v_list;
4088 list_append_number(l, term->tl_cursor_pos.row + 1);
4089 list_append_number(l, term->tl_cursor_pos.col + 1);
4090
4091 d = dict_alloc();
4092 if (d != NULL)
4093 {
4094 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4095 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4096 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4097 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4098 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4099 ? (char_u *)"" : term->tl_cursor_color);
4100 list_append_dict(l, d);
4101 }
4102}
4103
4104/*
4105 * "term_getjob(buf)" function
4106 */
4107 void
4108f_term_getjob(typval_T *argvars, typval_T *rettv)
4109{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004110 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004111
4112 rettv->v_type = VAR_JOB;
4113 rettv->vval.v_job = NULL;
4114 if (buf == NULL)
4115 return;
4116
4117 rettv->vval.v_job = buf->b_term->tl_job;
4118 if (rettv->vval.v_job != NULL)
4119 ++rettv->vval.v_job->jv_refcount;
4120}
4121
4122 static int
4123get_row_number(typval_T *tv, term_T *term)
4124{
4125 if (tv->v_type == VAR_STRING
4126 && tv->vval.v_string != NULL
4127 && STRCMP(tv->vval.v_string, ".") == 0)
4128 return term->tl_cursor_pos.row;
4129 return (int)get_tv_number(tv) - 1;
4130}
4131
4132/*
4133 * "term_getline(buf, row)" function
4134 */
4135 void
4136f_term_getline(typval_T *argvars, typval_T *rettv)
4137{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004138 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004139 term_T *term;
4140 int row;
4141
4142 rettv->v_type = VAR_STRING;
4143 if (buf == NULL)
4144 return;
4145 term = buf->b_term;
4146 row = get_row_number(&argvars[1], term);
4147
4148 if (term->tl_vterm == NULL)
4149 {
4150 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4151
4152 /* vterm is finished, get the text from the buffer */
4153 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4154 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4155 }
4156 else
4157 {
4158 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4159 VTermRect rect;
4160 int len;
4161 char_u *p;
4162
4163 if (row < 0 || row >= term->tl_rows)
4164 return;
4165 len = term->tl_cols * MB_MAXBYTES + 1;
4166 p = alloc(len);
4167 if (p == NULL)
4168 return;
4169 rettv->vval.v_string = p;
4170
4171 rect.start_col = 0;
4172 rect.end_col = term->tl_cols;
4173 rect.start_row = row;
4174 rect.end_row = row + 1;
4175 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4176 }
4177}
4178
4179/*
4180 * "term_getscrolled(buf)" function
4181 */
4182 void
4183f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4184{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004185 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004186
4187 if (buf == NULL)
4188 return;
4189 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4190}
4191
4192/*
4193 * "term_getsize(buf)" function
4194 */
4195 void
4196f_term_getsize(typval_T *argvars, typval_T *rettv)
4197{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004198 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004199 list_T *l;
4200
4201 if (rettv_list_alloc(rettv) == FAIL)
4202 return;
4203 if (buf == NULL)
4204 return;
4205
4206 l = rettv->vval.v_list;
4207 list_append_number(l, buf->b_term->tl_rows);
4208 list_append_number(l, buf->b_term->tl_cols);
4209}
4210
4211/*
4212 * "term_getstatus(buf)" function
4213 */
4214 void
4215f_term_getstatus(typval_T *argvars, typval_T *rettv)
4216{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004217 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004218 term_T *term;
4219 char_u val[100];
4220
4221 rettv->v_type = VAR_STRING;
4222 if (buf == NULL)
4223 return;
4224 term = buf->b_term;
4225
4226 if (term_job_running(term))
4227 STRCPY(val, "running");
4228 else
4229 STRCPY(val, "finished");
4230 if (term->tl_normal_mode)
4231 STRCAT(val, ",normal");
4232 rettv->vval.v_string = vim_strsave(val);
4233}
4234
4235/*
4236 * "term_gettitle(buf)" function
4237 */
4238 void
4239f_term_gettitle(typval_T *argvars, typval_T *rettv)
4240{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004241 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004242
4243 rettv->v_type = VAR_STRING;
4244 if (buf == NULL)
4245 return;
4246
4247 if (buf->b_term->tl_title != NULL)
4248 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4249}
4250
4251/*
4252 * "term_gettty(buf)" function
4253 */
4254 void
4255f_term_gettty(typval_T *argvars, typval_T *rettv)
4256{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004257 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004258 char_u *p;
4259 int num = 0;
4260
4261 rettv->v_type = VAR_STRING;
4262 if (buf == NULL)
4263 return;
4264 if (argvars[1].v_type != VAR_UNKNOWN)
4265 num = get_tv_number(&argvars[1]);
4266
4267 switch (num)
4268 {
4269 case 0:
4270 if (buf->b_term->tl_job != NULL)
4271 p = buf->b_term->tl_job->jv_tty_out;
4272 else
4273 p = buf->b_term->tl_tty_out;
4274 break;
4275 case 1:
4276 if (buf->b_term->tl_job != NULL)
4277 p = buf->b_term->tl_job->jv_tty_in;
4278 else
4279 p = buf->b_term->tl_tty_in;
4280 break;
4281 default:
4282 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4283 return;
4284 }
4285 if (p != NULL)
4286 rettv->vval.v_string = vim_strsave(p);
4287}
4288
4289/*
4290 * "term_list()" function
4291 */
4292 void
4293f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4294{
4295 term_T *tp;
4296 list_T *l;
4297
4298 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4299 return;
4300
4301 l = rettv->vval.v_list;
4302 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4303 if (tp != NULL && tp->tl_buffer != NULL)
4304 if (list_append_number(l,
4305 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4306 return;
4307}
4308
4309/*
4310 * "term_scrape(buf, row)" function
4311 */
4312 void
4313f_term_scrape(typval_T *argvars, typval_T *rettv)
4314{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004315 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004316 VTermScreen *screen = NULL;
4317 VTermPos pos;
4318 list_T *l;
4319 term_T *term;
4320 char_u *p;
4321 sb_line_T *line;
4322
4323 if (rettv_list_alloc(rettv) == FAIL)
4324 return;
4325 if (buf == NULL)
4326 return;
4327 term = buf->b_term;
4328
4329 l = rettv->vval.v_list;
4330 pos.row = get_row_number(&argvars[1], term);
4331
4332 if (term->tl_vterm != NULL)
4333 {
4334 screen = vterm_obtain_screen(term->tl_vterm);
4335 p = NULL;
4336 line = NULL;
4337 }
4338 else
4339 {
4340 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4341
4342 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4343 return;
4344 p = ml_get_buf(buf, lnum + 1, FALSE);
4345 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4346 }
4347
4348 for (pos.col = 0; pos.col < term->tl_cols; )
4349 {
4350 dict_T *dcell;
4351 int width;
4352 VTermScreenCellAttrs attrs;
4353 VTermColor fg, bg;
4354 char_u rgb[8];
4355 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4356 int off = 0;
4357 int i;
4358
4359 if (screen == NULL)
4360 {
4361 cellattr_T *cellattr;
4362 int len;
4363
4364 /* vterm has finished, get the cell from scrollback */
4365 if (pos.col >= line->sb_cols)
4366 break;
4367 cellattr = line->sb_cells + pos.col;
4368 width = cellattr->width;
4369 attrs = cellattr->attrs;
4370 fg = cellattr->fg;
4371 bg = cellattr->bg;
4372 len = MB_PTR2LEN(p);
4373 mch_memmove(mbs, p, len);
4374 mbs[len] = NUL;
4375 p += len;
4376 }
4377 else
4378 {
4379 VTermScreenCell cell;
4380 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4381 break;
4382 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4383 {
4384 if (cell.chars[i] == 0)
4385 break;
4386 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4387 }
4388 mbs[off] = NUL;
4389 width = cell.width;
4390 attrs = cell.attrs;
4391 fg = cell.fg;
4392 bg = cell.bg;
4393 }
4394 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004395 if (dcell == NULL)
4396 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004397 list_append_dict(l, dcell);
4398
4399 dict_add_nr_str(dcell, "chars", 0, mbs);
4400
4401 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4402 fg.red, fg.green, fg.blue);
4403 dict_add_nr_str(dcell, "fg", 0, rgb);
4404 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4405 bg.red, bg.green, bg.blue);
4406 dict_add_nr_str(dcell, "bg", 0, rgb);
4407
4408 dict_add_nr_str(dcell, "attr",
4409 cell2attr(attrs, fg, bg), NULL);
4410 dict_add_nr_str(dcell, "width", width, NULL);
4411
4412 ++pos.col;
4413 if (width == 2)
4414 ++pos.col;
4415 }
4416}
4417
4418/*
4419 * "term_sendkeys(buf, keys)" function
4420 */
4421 void
4422f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4423{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004424 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004425 char_u *msg;
4426 term_T *term;
4427
4428 rettv->v_type = VAR_UNKNOWN;
4429 if (buf == NULL)
4430 return;
4431
4432 msg = get_tv_string_chk(&argvars[1]);
4433 if (msg == NULL)
4434 return;
4435 term = buf->b_term;
4436 if (term->tl_vterm == NULL)
4437 return;
4438
4439 while (*msg != NUL)
4440 {
4441 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004442 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004443 }
4444}
4445
4446/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004447 * "term_setrestore(buf, command)" function
4448 */
4449 void
4450f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4451{
4452#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004453 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004454 term_T *term;
4455 char_u *cmd;
4456
4457 if (buf == NULL)
4458 return;
4459 term = buf->b_term;
4460 vim_free(term->tl_command);
4461 cmd = get_tv_string_chk(&argvars[1]);
4462 if (cmd != NULL)
4463 term->tl_command = vim_strsave(cmd);
4464 else
4465 term->tl_command = NULL;
4466#endif
4467}
4468
4469/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004470 * "term_setkill(buf, how)" function
4471 */
4472 void
4473f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4474{
4475 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4476 term_T *term;
4477 char_u *how;
4478
4479 if (buf == NULL)
4480 return;
4481 term = buf->b_term;
4482 vim_free(term->tl_kill);
4483 how = get_tv_string_chk(&argvars[1]);
4484 if (how != NULL)
4485 term->tl_kill = vim_strsave(how);
4486 else
4487 term->tl_kill = NULL;
4488}
4489
4490/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004491 * "term_start(command, options)" function
4492 */
4493 void
4494f_term_start(typval_T *argvars, typval_T *rettv)
4495{
4496 jobopt_T opt;
4497 buf_T *buf;
4498
4499 init_job_options(&opt);
4500 if (argvars[1].v_type != VAR_UNKNOWN
4501 && get_job_options(&argvars[1], &opt,
4502 JO_TIMEOUT_ALL + JO_STOPONEXIT
4503 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
4504 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
4505 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
4506 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004507 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004508 + JO2_NORESTORE + JO2_TERM_KILL) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004509 return;
4510
4511 if (opt.jo_vertical)
4512 cmdmod.split = WSP_VERT;
Bram Moolenaar13568252018-03-16 20:46:58 +01004513 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004514
4515 if (buf != NULL && buf->b_term != NULL)
4516 rettv->vval.v_number = buf->b_fnum;
4517}
4518
4519/*
4520 * "term_wait" function
4521 */
4522 void
4523f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
4524{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004525 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004526
4527 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004528 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004529 if (buf->b_term->tl_job == NULL)
4530 {
4531 ch_log(NULL, "term_wait(): no job to wait for");
4532 return;
4533 }
4534 if (buf->b_term->tl_job->jv_channel == NULL)
4535 /* channel is closed, nothing to do */
4536 return;
4537
4538 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01004539 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004540 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
4541 {
4542 /* The job is dead, keep reading channel I/O until the channel is
4543 * closed. buf->b_term may become NULL if the terminal was closed while
4544 * waiting. */
4545 ch_log(NULL, "term_wait(): waiting for channel to close");
4546 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
4547 {
4548 mch_check_messages();
4549 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01004550 if (!buf_valid(buf))
4551 /* If the terminal is closed when the channel is closed the
4552 * buffer disappears. */
4553 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004554 ui_delay(10L, FALSE);
4555 }
4556 mch_check_messages();
4557 parse_queued_messages();
4558 }
4559 else
4560 {
4561 long wait = 10L;
4562
4563 mch_check_messages();
4564 parse_queued_messages();
4565
4566 /* Wait for some time for any channel I/O. */
4567 if (argvars[1].v_type != VAR_UNKNOWN)
4568 wait = get_tv_number(&argvars[1]);
4569 ui_delay(wait, TRUE);
4570 mch_check_messages();
4571
4572 /* Flushing messages on channels is hopefully sufficient.
4573 * TODO: is there a better way? */
4574 parse_queued_messages();
4575 }
4576}
4577
4578/*
4579 * Called when a channel has sent all the lines to a terminal.
4580 * Send a CTRL-D to mark the end of the text.
4581 */
4582 void
4583term_send_eof(channel_T *ch)
4584{
4585 term_T *term;
4586
4587 for (term = first_term; term != NULL; term = term->tl_next)
4588 if (term->tl_job == ch->ch_job)
4589 {
4590 if (term->tl_eof_chars != NULL)
4591 {
4592 channel_send(ch, PART_IN, term->tl_eof_chars,
4593 (int)STRLEN(term->tl_eof_chars), NULL);
4594 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
4595 }
4596# ifdef WIN3264
4597 else
4598 /* Default: CTRL-D */
4599 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
4600# endif
4601 }
4602}
4603
4604# if defined(WIN3264) || defined(PROTO)
4605
4606/**************************************
4607 * 2. MS-Windows implementation.
4608 */
4609
4610# ifndef PROTO
4611
4612#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
4613#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01004614#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004615
4616void* (*winpty_config_new)(UINT64, void*);
4617void* (*winpty_open)(void*, void*);
4618void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
4619BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
4620void (*winpty_config_set_mouse_mode)(void*, int);
4621void (*winpty_config_set_initial_size)(void*, int, int);
4622LPCWSTR (*winpty_conin_name)(void*);
4623LPCWSTR (*winpty_conout_name)(void*);
4624LPCWSTR (*winpty_conerr_name)(void*);
4625void (*winpty_free)(void*);
4626void (*winpty_config_free)(void*);
4627void (*winpty_spawn_config_free)(void*);
4628void (*winpty_error_free)(void*);
4629LPCWSTR (*winpty_error_msg)(void*);
4630BOOL (*winpty_set_size)(void*, int, int, void*);
4631HANDLE (*winpty_agent_process)(void*);
4632
4633#define WINPTY_DLL "winpty.dll"
4634
4635static HINSTANCE hWinPtyDLL = NULL;
4636# endif
4637
4638 static int
4639dyn_winpty_init(int verbose)
4640{
4641 int i;
4642 static struct
4643 {
4644 char *name;
4645 FARPROC *ptr;
4646 } winpty_entry[] =
4647 {
4648 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
4649 {"winpty_config_free", (FARPROC*)&winpty_config_free},
4650 {"winpty_config_new", (FARPROC*)&winpty_config_new},
4651 {"winpty_config_set_mouse_mode",
4652 (FARPROC*)&winpty_config_set_mouse_mode},
4653 {"winpty_config_set_initial_size",
4654 (FARPROC*)&winpty_config_set_initial_size},
4655 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
4656 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
4657 {"winpty_error_free", (FARPROC*)&winpty_error_free},
4658 {"winpty_free", (FARPROC*)&winpty_free},
4659 {"winpty_open", (FARPROC*)&winpty_open},
4660 {"winpty_spawn", (FARPROC*)&winpty_spawn},
4661 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
4662 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
4663 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
4664 {"winpty_set_size", (FARPROC*)&winpty_set_size},
4665 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
4666 {NULL, NULL}
4667 };
4668
4669 /* No need to initialize twice. */
4670 if (hWinPtyDLL)
4671 return OK;
4672 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
4673 * winpty.dll. */
4674 if (*p_winptydll != NUL)
4675 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
4676 if (!hWinPtyDLL)
4677 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
4678 if (!hWinPtyDLL)
4679 {
4680 if (verbose)
4681 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
4682 : (char_u *)WINPTY_DLL);
4683 return FAIL;
4684 }
4685 for (i = 0; winpty_entry[i].name != NULL
4686 && winpty_entry[i].ptr != NULL; ++i)
4687 {
4688 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
4689 winpty_entry[i].name)) == NULL)
4690 {
4691 if (verbose)
4692 EMSG2(_(e_loadfunc), winpty_entry[i].name);
4693 return FAIL;
4694 }
4695 }
4696
4697 return OK;
4698}
4699
4700/*
4701 * Create a new terminal of "rows" by "cols" cells.
4702 * Store a reference in "term".
4703 * Return OK or FAIL.
4704 */
4705 static int
4706term_and_job_init(
4707 term_T *term,
4708 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01004709 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004710 jobopt_T *opt)
4711{
4712 WCHAR *cmd_wchar = NULL;
4713 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004714 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004715 channel_T *channel = NULL;
4716 job_T *job = NULL;
4717 DWORD error;
4718 HANDLE jo = NULL;
4719 HANDLE child_process_handle;
4720 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01004721 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004722 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004723 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004724 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004725
4726 if (dyn_winpty_init(TRUE) == FAIL)
4727 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004728 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
4729 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004730
4731 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004732 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004733 cmd = argvar->vval.v_string;
4734 }
4735 else if (argvar->v_type == VAR_LIST)
4736 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004737 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004738 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004739 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004740 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004741 if (cmd == NULL || *cmd == NUL)
4742 {
4743 EMSG(_(e_invarg));
4744 goto failed;
4745 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004746
4747 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004748 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004749 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004750 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004751 if (opt->jo_cwd != NULL)
4752 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004753
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004754 win32_build_env(opt->jo_env, &ga_env, TRUE);
4755 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004756
4757 job = job_alloc();
4758 if (job == NULL)
4759 goto failed;
4760
4761 channel = add_channel();
4762 if (channel == NULL)
4763 goto failed;
4764
4765 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
4766 if (term->tl_winpty_config == NULL)
4767 goto failed;
4768
4769 winpty_config_set_mouse_mode(term->tl_winpty_config,
4770 WINPTY_MOUSE_MODE_FORCE);
4771 winpty_config_set_initial_size(term->tl_winpty_config,
4772 term->tl_cols, term->tl_rows);
4773 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
4774 if (term->tl_winpty == NULL)
4775 goto failed;
4776
4777 spawn_config = winpty_spawn_config_new(
4778 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
4779 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
4780 NULL,
4781 cmd_wchar,
4782 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004783 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004784 &winpty_err);
4785 if (spawn_config == NULL)
4786 goto failed;
4787
4788 channel = add_channel();
4789 if (channel == NULL)
4790 goto failed;
4791
4792 job = job_alloc();
4793 if (job == NULL)
4794 goto failed;
4795
4796 if (opt->jo_set & JO_IN_BUF)
4797 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
4798
4799 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
4800 &child_thread_handle, &error, &winpty_err))
4801 goto failed;
4802
4803 channel_set_pipes(channel,
4804 (sock_T)CreateFileW(
4805 winpty_conin_name(term->tl_winpty),
4806 GENERIC_WRITE, 0, NULL,
4807 OPEN_EXISTING, 0, NULL),
4808 (sock_T)CreateFileW(
4809 winpty_conout_name(term->tl_winpty),
4810 GENERIC_READ, 0, NULL,
4811 OPEN_EXISTING, 0, NULL),
4812 (sock_T)CreateFileW(
4813 winpty_conerr_name(term->tl_winpty),
4814 GENERIC_READ, 0, NULL,
4815 OPEN_EXISTING, 0, NULL));
4816
4817 /* Write lines with CR instead of NL. */
4818 channel->ch_write_text_mode = TRUE;
4819
4820 jo = CreateJobObject(NULL, NULL);
4821 if (jo == NULL)
4822 goto failed;
4823
4824 if (!AssignProcessToJobObject(jo, child_process_handle))
4825 {
4826 /* Failed, switch the way to terminate process with TerminateProcess. */
4827 CloseHandle(jo);
4828 jo = NULL;
4829 }
4830
4831 winpty_spawn_config_free(spawn_config);
4832 vim_free(cmd_wchar);
4833 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004834 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004835
4836 create_vterm(term, term->tl_rows, term->tl_cols);
4837
4838 channel_set_job(channel, job, opt);
4839 job_set_options(job, opt);
4840
4841 job->jv_channel = channel;
4842 job->jv_proc_info.hProcess = child_process_handle;
4843 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
4844 job->jv_job_object = jo;
4845 job->jv_status = JOB_STARTED;
4846 job->jv_tty_in = utf16_to_enc(
4847 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
4848 job->jv_tty_out = utf16_to_enc(
4849 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
4850 ++job->jv_refcount;
4851 term->tl_job = job;
4852
4853 return OK;
4854
4855failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004856 ga_clear(&ga_cmd);
4857 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004858 vim_free(cmd_wchar);
4859 vim_free(cwd_wchar);
4860 if (spawn_config != NULL)
4861 winpty_spawn_config_free(spawn_config);
4862 if (channel != NULL)
4863 channel_clear(channel);
4864 if (job != NULL)
4865 {
4866 job->jv_channel = NULL;
4867 job_cleanup(job);
4868 }
4869 term->tl_job = NULL;
4870 if (jo != NULL)
4871 CloseHandle(jo);
4872 if (term->tl_winpty != NULL)
4873 winpty_free(term->tl_winpty);
4874 term->tl_winpty = NULL;
4875 if (term->tl_winpty_config != NULL)
4876 winpty_config_free(term->tl_winpty_config);
4877 term->tl_winpty_config = NULL;
4878 if (winpty_err != NULL)
4879 {
4880 char_u *msg = utf16_to_enc(
4881 (short_u *)winpty_error_msg(winpty_err), NULL);
4882
4883 EMSG(msg);
4884 winpty_error_free(winpty_err);
4885 }
4886 return FAIL;
4887}
4888
4889 static int
4890create_pty_only(term_T *term, jobopt_T *options)
4891{
4892 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
4893 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
4894 char in_name[80], out_name[80];
4895 channel_T *channel = NULL;
4896
4897 create_vterm(term, term->tl_rows, term->tl_cols);
4898
4899 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
4900 GetCurrentProcessId(),
4901 curbuf->b_fnum);
4902 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
4903 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4904 PIPE_UNLIMITED_INSTANCES,
4905 0, 0, NMPWAIT_NOWAIT, NULL);
4906 if (hPipeIn == INVALID_HANDLE_VALUE)
4907 goto failed;
4908
4909 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
4910 GetCurrentProcessId(),
4911 curbuf->b_fnum);
4912 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
4913 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4914 PIPE_UNLIMITED_INSTANCES,
4915 0, 0, 0, NULL);
4916 if (hPipeOut == INVALID_HANDLE_VALUE)
4917 goto failed;
4918
4919 ConnectNamedPipe(hPipeIn, NULL);
4920 ConnectNamedPipe(hPipeOut, NULL);
4921
4922 term->tl_job = job_alloc();
4923 if (term->tl_job == NULL)
4924 goto failed;
4925 ++term->tl_job->jv_refcount;
4926
4927 /* behave like the job is already finished */
4928 term->tl_job->jv_status = JOB_FINISHED;
4929
4930 channel = add_channel();
4931 if (channel == NULL)
4932 goto failed;
4933 term->tl_job->jv_channel = channel;
4934 channel->ch_keep_open = TRUE;
4935 channel->ch_named_pipe = TRUE;
4936
4937 channel_set_pipes(channel,
4938 (sock_T)hPipeIn,
4939 (sock_T)hPipeOut,
4940 (sock_T)hPipeOut);
4941 channel_set_job(channel, term->tl_job, options);
4942 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
4943 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
4944
4945 return OK;
4946
4947failed:
4948 if (hPipeIn != NULL)
4949 CloseHandle(hPipeIn);
4950 if (hPipeOut != NULL)
4951 CloseHandle(hPipeOut);
4952 return FAIL;
4953}
4954
4955/*
4956 * Free the terminal emulator part of "term".
4957 */
4958 static void
4959term_free_vterm(term_T *term)
4960{
4961 if (term->tl_winpty != NULL)
4962 winpty_free(term->tl_winpty);
4963 term->tl_winpty = NULL;
4964 if (term->tl_winpty_config != NULL)
4965 winpty_config_free(term->tl_winpty_config);
4966 term->tl_winpty_config = NULL;
4967 if (term->tl_vterm != NULL)
4968 vterm_free(term->tl_vterm);
4969 term->tl_vterm = NULL;
4970}
4971
4972/*
4973 * Request size to terminal.
4974 */
4975 static void
4976term_report_winsize(term_T *term, int rows, int cols)
4977{
4978 if (term->tl_winpty)
4979 winpty_set_size(term->tl_winpty, cols, rows, NULL);
4980}
4981
4982 int
4983terminal_enabled(void)
4984{
4985 return dyn_winpty_init(FALSE) == OK;
4986}
4987
4988# else
4989
4990/**************************************
4991 * 3. Unix-like implementation.
4992 */
4993
4994/*
4995 * Create a new terminal of "rows" by "cols" cells.
4996 * Start job for "cmd".
4997 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01004998 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004999 * Return OK or FAIL.
5000 */
5001 static int
5002term_and_job_init(
5003 term_T *term,
5004 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005005 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005006 jobopt_T *opt)
5007{
5008 create_vterm(term, term->tl_rows, term->tl_cols);
5009
Bram Moolenaar13568252018-03-16 20:46:58 +01005010 /* This may change a string in "argvar". */
5011 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005012 if (term->tl_job != NULL)
5013 ++term->tl_job->jv_refcount;
5014
5015 return term->tl_job != NULL
5016 && term->tl_job->jv_channel != NULL
5017 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5018}
5019
5020 static int
5021create_pty_only(term_T *term, jobopt_T *opt)
5022{
5023 create_vterm(term, term->tl_rows, term->tl_cols);
5024
5025 term->tl_job = job_alloc();
5026 if (term->tl_job == NULL)
5027 return FAIL;
5028 ++term->tl_job->jv_refcount;
5029
5030 /* behave like the job is already finished */
5031 term->tl_job->jv_status = JOB_FINISHED;
5032
5033 return mch_create_pty_channel(term->tl_job, opt);
5034}
5035
5036/*
5037 * Free the terminal emulator part of "term".
5038 */
5039 static void
5040term_free_vterm(term_T *term)
5041{
5042 if (term->tl_vterm != NULL)
5043 vterm_free(term->tl_vterm);
5044 term->tl_vterm = NULL;
5045}
5046
5047/*
5048 * Request size to terminal.
5049 */
5050 static void
5051term_report_winsize(term_T *term, int rows, int cols)
5052{
5053 /* Use an ioctl() to report the new window size to the job. */
5054 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5055 {
5056 int fd = -1;
5057 int part;
5058
5059 for (part = PART_OUT; part < PART_COUNT; ++part)
5060 {
5061 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5062 if (isatty(fd))
5063 break;
5064 }
5065 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5066 mch_signal_job(term->tl_job, (char_u *)"winch");
5067 }
5068}
5069
5070# endif
5071
5072#endif /* FEAT_TERMINAL */