blob: 67972f14b55ea2dcbf757630199cbef3adb4f9e1 [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
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002010 /* Was either CTRL-W (termkey) or CTRL-\ pressed?
2011 * Not in a system terminal. */
2012 if ((c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
2013#ifdef FEAT_GUI
2014 && !curbuf->b_term->tl_system
2015#endif
2016 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002017 {
2018 int prev_c = c;
2019
2020#ifdef FEAT_CMDL_INFO
2021 if (add_to_showcmd(c))
2022 out_flush();
2023#endif
2024 c = term_vgetc();
2025#ifdef FEAT_CMDL_INFO
2026 clear_showcmd();
2027#endif
2028 if (!term_use_loop())
2029 /* job finished while waiting for a character */
2030 break;
2031
2032 if (prev_c == Ctrl_BSL)
2033 {
2034 if (c == Ctrl_N)
2035 {
2036 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2037 term_enter_normal_mode();
2038 ret = FAIL;
2039 goto theend;
2040 }
2041 /* Send both keys to the terminal. */
2042 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2043 }
2044 else if (c == Ctrl_C)
2045 {
2046 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
2047 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2048 }
2049 else if (termkey == 0 && c == '.')
2050 {
2051 /* "CTRL-W .": send CTRL-W to the job */
2052 c = Ctrl_W;
2053 }
2054 else if (c == 'N')
2055 {
2056 /* CTRL-W N : go to Terminal-Normal mode. */
2057 term_enter_normal_mode();
2058 ret = FAIL;
2059 goto theend;
2060 }
2061 else if (c == '"')
2062 {
2063 term_paste_register(prev_c);
2064 continue;
2065 }
2066 else if (termkey == 0 || c != termkey)
2067 {
2068 stuffcharReadbuff(Ctrl_W);
2069 stuffcharReadbuff(c);
2070 ret = OK;
2071 goto theend;
2072 }
2073 }
2074# ifdef WIN3264
2075 if (!enc_utf8 && has_mbyte && c >= 0x80)
2076 {
2077 WCHAR wc;
2078 char_u mb[3];
2079
2080 mb[0] = (unsigned)c >> 8;
2081 mb[1] = c;
2082 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2083 c = wc;
2084 }
2085# endif
2086 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2087 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002088 if (c == K_MOUSEMOVE)
2089 /* We are sure to come back here, don't reset the cursor color
2090 * and shape to avoid flickering. */
2091 restore_cursor = FALSE;
2092
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002093 ret = OK;
2094 goto theend;
2095 }
2096 }
2097 ret = FAIL;
2098
2099theend:
2100 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002101 if (restore_cursor)
2102 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002103 return ret;
2104}
2105
2106/*
2107 * Called when a job has finished.
2108 * This updates the title and status, but does not close the vterm, because
2109 * there might still be pending output in the channel.
2110 */
2111 void
2112term_job_ended(job_T *job)
2113{
2114 term_T *term;
2115 int did_one = FALSE;
2116
2117 for (term = first_term; term != NULL; term = term->tl_next)
2118 if (term->tl_job == job)
2119 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002120 VIM_CLEAR(term->tl_title);
2121 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002122 redraw_buf_and_status_later(term->tl_buffer, VALID);
2123 did_one = TRUE;
2124 }
2125 if (did_one)
2126 redraw_statuslines();
2127 if (curbuf->b_term != NULL)
2128 {
2129 if (curbuf->b_term->tl_job == job)
2130 maketitle();
2131 update_cursor(curbuf->b_term, TRUE);
2132 }
2133}
2134
2135 static void
2136may_toggle_cursor(term_T *term)
2137{
2138 if (in_terminal_loop == term)
2139 {
2140 if (term->tl_cursor_visible)
2141 cursor_on();
2142 else
2143 cursor_off();
2144 }
2145}
2146
2147/*
2148 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002149 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002150 */
2151 static int
2152color2index(VTermColor *color, int fg, int *boldp)
2153{
2154 int red = color->red;
2155 int blue = color->blue;
2156 int green = color->green;
2157
Bram Moolenaar46359e12017-11-29 22:33:38 +01002158 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002159 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002160 /* First 16 colors and default: use the ANSI index, because these
2161 * colors can be redefined. */
2162 if (t_colors >= 16)
2163 return color->ansi_index;
2164 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002165 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002166 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002167 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002168 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2169 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2170 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
2171 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue*/
2172 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2173 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2174 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2175 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2176 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2177 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2178 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2179 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2180 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2181 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2182 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002183 }
2184 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002185
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002186 if (t_colors >= 256)
2187 {
2188 if (red == blue && red == green)
2189 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002190 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002191 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002192 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2193 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2194 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002195 int i;
2196
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002197 if (red < 5)
2198 return 17; /* 00/00/00 */
2199 if (red > 245) /* ff/ff/ff */
2200 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002201 for (i = 0; i < 23; ++i)
2202 if (red < cutoff[i])
2203 return i + 233;
2204 return 256;
2205 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002206 {
2207 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2208 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002209
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002210 /* 216-color cube */
2211 for (ri = 0; ri < 5; ++ri)
2212 if (red < cutoff[ri])
2213 break;
2214 for (gi = 0; gi < 5; ++gi)
2215 if (green < cutoff[gi])
2216 break;
2217 for (bi = 0; bi < 5; ++bi)
2218 if (blue < cutoff[bi])
2219 break;
2220 return 17 + ri * 36 + gi * 6 + bi;
2221 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002222 }
2223 return 0;
2224}
2225
2226/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002227 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002228 */
2229 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002230vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002231{
2232 int attr = 0;
2233
2234 if (cellattrs.bold)
2235 attr |= HL_BOLD;
2236 if (cellattrs.underline)
2237 attr |= HL_UNDERLINE;
2238 if (cellattrs.italic)
2239 attr |= HL_ITALIC;
2240 if (cellattrs.strike)
2241 attr |= HL_STRIKETHROUGH;
2242 if (cellattrs.reverse)
2243 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002244 return attr;
2245}
2246
2247/*
2248 * Store Vterm attributes in "cell" from highlight flags.
2249 */
2250 static void
2251hl2vtermAttr(int attr, cellattr_T *cell)
2252{
2253 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2254 if (attr & HL_BOLD)
2255 cell->attrs.bold = 1;
2256 if (attr & HL_UNDERLINE)
2257 cell->attrs.underline = 1;
2258 if (attr & HL_ITALIC)
2259 cell->attrs.italic = 1;
2260 if (attr & HL_STRIKETHROUGH)
2261 cell->attrs.strike = 1;
2262 if (attr & HL_INVERSE)
2263 cell->attrs.reverse = 1;
2264}
2265
2266/*
2267 * Convert the attributes of a vterm cell into an attribute index.
2268 */
2269 static int
2270cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2271{
2272 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002273
2274#ifdef FEAT_GUI
2275 if (gui.in_use)
2276 {
2277 guicolor_T fg, bg;
2278
2279 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2280 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2281 return get_gui_attr_idx(attr, fg, bg);
2282 }
2283 else
2284#endif
2285#ifdef FEAT_TERMGUICOLORS
2286 if (p_tgc)
2287 {
2288 guicolor_T fg, bg;
2289
2290 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2291 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2292
2293 return get_tgc_attr_idx(attr, fg, bg);
2294 }
2295 else
2296#endif
2297 {
2298 int bold = MAYBE;
2299 int fg = color2index(&cellfg, TRUE, &bold);
2300 int bg = color2index(&cellbg, FALSE, &bold);
2301
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002302 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002303 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002304 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002305 if (fg == 0 && term_default_cterm_fg >= 0)
2306 fg = term_default_cterm_fg + 1;
2307 if (bg == 0 && term_default_cterm_bg >= 0)
2308 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002309 }
2310
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002311 /* with 8 colors set the bold attribute to get a bright foreground */
2312 if (bold == TRUE)
2313 attr |= HL_BOLD;
2314 return get_cterm_attr_idx(attr, fg, bg);
2315 }
2316 return 0;
2317}
2318
2319 static int
2320handle_damage(VTermRect rect, void *user)
2321{
2322 term_T *term = (term_T *)user;
2323
2324 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2325 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2326 redraw_buf_later(term->tl_buffer, NOT_VALID);
2327 return 1;
2328}
2329
2330 static int
2331handle_moverect(VTermRect dest, VTermRect src, void *user)
2332{
2333 term_T *term = (term_T *)user;
2334
2335 /* Scrolling up is done much more efficiently by deleting lines instead of
2336 * redrawing the text. */
2337 if (dest.start_col == src.start_col
2338 && dest.end_col == src.end_col
2339 && dest.start_row < src.start_row)
2340 {
2341 win_T *wp;
2342 VTermColor fg, bg;
2343 VTermScreenCellAttrs attr;
2344 int clear_attr;
2345
2346 /* Set the color to clear lines with. */
2347 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2348 &fg, &bg);
2349 vim_memset(&attr, 0, sizeof(attr));
2350 clear_attr = cell2attr(attr, fg, bg);
2351
2352 FOR_ALL_WINDOWS(wp)
2353 {
2354 if (wp->w_buffer == term->tl_buffer)
2355 win_del_lines(wp, dest.start_row,
2356 src.start_row - dest.start_row, FALSE, FALSE,
2357 clear_attr);
2358 }
2359 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002360
2361 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2362 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2363
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002364 redraw_buf_later(term->tl_buffer, NOT_VALID);
2365 return 1;
2366}
2367
2368 static int
2369handle_movecursor(
2370 VTermPos pos,
2371 VTermPos oldpos UNUSED,
2372 int visible,
2373 void *user)
2374{
2375 term_T *term = (term_T *)user;
2376 win_T *wp;
2377
2378 term->tl_cursor_pos = pos;
2379 term->tl_cursor_visible = visible;
2380
2381 FOR_ALL_WINDOWS(wp)
2382 {
2383 if (wp->w_buffer == term->tl_buffer)
2384 position_cursor(wp, &pos);
2385 }
2386 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2387 {
2388 may_toggle_cursor(term);
2389 update_cursor(term, term->tl_cursor_visible);
2390 }
2391
2392 return 1;
2393}
2394
2395 static int
2396handle_settermprop(
2397 VTermProp prop,
2398 VTermValue *value,
2399 void *user)
2400{
2401 term_T *term = (term_T *)user;
2402
2403 switch (prop)
2404 {
2405 case VTERM_PROP_TITLE:
2406 vim_free(term->tl_title);
2407 /* a blank title isn't useful, make it empty, so that "running" is
2408 * displayed */
2409 if (*skipwhite((char_u *)value->string) == NUL)
2410 term->tl_title = NULL;
2411#ifdef WIN3264
2412 else if (!enc_utf8 && enc_codepage > 0)
2413 {
2414 WCHAR *ret = NULL;
2415 int length = 0;
2416
2417 MultiByteToWideChar_alloc(CP_UTF8, 0,
2418 (char*)value->string, (int)STRLEN(value->string),
2419 &ret, &length);
2420 if (ret != NULL)
2421 {
2422 WideCharToMultiByte_alloc(enc_codepage, 0,
2423 ret, length, (char**)&term->tl_title,
2424 &length, 0, 0);
2425 vim_free(ret);
2426 }
2427 }
2428#endif
2429 else
2430 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002431 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002432 if (term == curbuf->b_term)
2433 maketitle();
2434 break;
2435
2436 case VTERM_PROP_CURSORVISIBLE:
2437 term->tl_cursor_visible = value->boolean;
2438 may_toggle_cursor(term);
2439 out_flush();
2440 break;
2441
2442 case VTERM_PROP_CURSORBLINK:
2443 term->tl_cursor_blink = value->boolean;
2444 may_set_cursor_props(term);
2445 break;
2446
2447 case VTERM_PROP_CURSORSHAPE:
2448 term->tl_cursor_shape = value->number;
2449 may_set_cursor_props(term);
2450 break;
2451
2452 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002453 if (desired_cursor_color == term->tl_cursor_color)
2454 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002455 vim_free(term->tl_cursor_color);
2456 if (*value->string == NUL)
2457 term->tl_cursor_color = NULL;
2458 else
2459 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2460 may_set_cursor_props(term);
2461 break;
2462
2463 case VTERM_PROP_ALTSCREEN:
2464 /* TODO: do anything else? */
2465 term->tl_using_altscreen = value->boolean;
2466 break;
2467
2468 default:
2469 break;
2470 }
2471 /* Always return 1, otherwise vterm doesn't store the value internally. */
2472 return 1;
2473}
2474
2475/*
2476 * The job running in the terminal resized the terminal.
2477 */
2478 static int
2479handle_resize(int rows, int cols, void *user)
2480{
2481 term_T *term = (term_T *)user;
2482 win_T *wp;
2483
2484 term->tl_rows = rows;
2485 term->tl_cols = cols;
2486 if (term->tl_vterm_size_changed)
2487 /* Size was set by vterm_set_size(), don't set the window size. */
2488 term->tl_vterm_size_changed = FALSE;
2489 else
2490 {
2491 FOR_ALL_WINDOWS(wp)
2492 {
2493 if (wp->w_buffer == term->tl_buffer)
2494 {
2495 win_setheight_win(rows, wp);
2496 win_setwidth_win(cols, wp);
2497 }
2498 }
2499 redraw_buf_later(term->tl_buffer, NOT_VALID);
2500 }
2501 return 1;
2502}
2503
2504/*
2505 * Handle a line that is pushed off the top of the screen.
2506 */
2507 static int
2508handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2509{
2510 term_T *term = (term_T *)user;
2511
2512 /* TODO: Limit the number of lines that are stored. */
2513 if (ga_grow(&term->tl_scrollback, 1) == OK)
2514 {
2515 cellattr_T *p = NULL;
2516 int len = 0;
2517 int i;
2518 int c;
2519 int col;
2520 sb_line_T *line;
2521 garray_T ga;
2522 cellattr_T fill_attr = term->tl_default_color;
2523
2524 /* do not store empty cells at the end */
2525 for (i = 0; i < cols; ++i)
2526 if (cells[i].chars[0] != 0)
2527 len = i + 1;
2528 else
2529 cell2cellattr(&cells[i], &fill_attr);
2530
2531 ga_init2(&ga, 1, 100);
2532 if (len > 0)
2533 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2534 if (p != NULL)
2535 {
2536 for (col = 0; col < len; col += cells[col].width)
2537 {
2538 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2539 {
2540 ga.ga_len = 0;
2541 break;
2542 }
2543 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2544 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2545 (char_u *)ga.ga_data + ga.ga_len);
2546 cell2cellattr(&cells[col], &p[col]);
2547 }
2548 }
2549 if (ga_grow(&ga, 1) == FAIL)
2550 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2551 else
2552 {
2553 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2554 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2555 }
2556 ga_clear(&ga);
2557
2558 line = (sb_line_T *)term->tl_scrollback.ga_data
2559 + term->tl_scrollback.ga_len;
2560 line->sb_cols = len;
2561 line->sb_cells = p;
2562 line->sb_fill_attr = fill_attr;
2563 ++term->tl_scrollback.ga_len;
2564 ++term->tl_scrollback_scrolled;
2565 }
2566 return 0; /* ignored */
2567}
2568
2569static VTermScreenCallbacks screen_callbacks = {
2570 handle_damage, /* damage */
2571 handle_moverect, /* moverect */
2572 handle_movecursor, /* movecursor */
2573 handle_settermprop, /* settermprop */
2574 NULL, /* bell */
2575 handle_resize, /* resize */
2576 handle_pushline, /* sb_pushline */
2577 NULL /* sb_popline */
2578};
2579
2580/*
2581 * Called when a channel has been closed.
2582 * If this was a channel for a terminal window then finish it up.
2583 */
2584 void
2585term_channel_closed(channel_T *ch)
2586{
2587 term_T *term;
2588 int did_one = FALSE;
2589
2590 for (term = first_term; term != NULL; term = term->tl_next)
2591 if (term->tl_job == ch->ch_job)
2592 {
2593 term->tl_channel_closed = TRUE;
2594 did_one = TRUE;
2595
Bram Moolenaard23a8232018-02-10 18:45:26 +01002596 VIM_CLEAR(term->tl_title);
2597 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002598
2599 /* Unless in Terminal-Normal mode: clear the vterm. */
2600 if (!term->tl_normal_mode)
2601 {
2602 int fnum = term->tl_buffer->b_fnum;
2603
2604 cleanup_vterm(term);
2605
2606 if (term->tl_finish == 'c')
2607 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002608 aco_save_T aco;
2609
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002610 /* ++close or term_finish == "close" */
2611 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002612 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002613 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002614 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002615 break;
2616 }
2617 if (term->tl_finish == 'o' && term->tl_buffer->b_nwindows == 0)
2618 {
2619 char buf[50];
2620
2621 /* TODO: use term_opencmd */
2622 ch_log(NULL, "terminal job finished, opening window");
2623 vim_snprintf(buf, sizeof(buf),
2624 term->tl_opencmd == NULL
2625 ? "botright sbuf %d"
2626 : (char *)term->tl_opencmd, fnum);
2627 do_cmdline_cmd((char_u *)buf);
2628 }
2629 else
2630 ch_log(NULL, "terminal job finished");
2631 }
2632
2633 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2634 }
2635 if (did_one)
2636 {
2637 redraw_statuslines();
2638
2639 /* Need to break out of vgetc(). */
2640 ins_char_typebuf(K_IGNORE);
2641 typebuf_was_filled = TRUE;
2642
2643 term = curbuf->b_term;
2644 if (term != NULL)
2645 {
2646 if (term->tl_job == ch->ch_job)
2647 maketitle();
2648 update_cursor(term, term->tl_cursor_visible);
2649 }
2650 }
2651}
2652
2653/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002654 * Fill one screen line from a line of the terminal.
2655 * Advances "pos" to past the last column.
2656 */
2657 static void
2658term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2659{
2660 int off = screen_get_current_line_off();
2661
2662 for (pos->col = 0; pos->col < max_col; )
2663 {
2664 VTermScreenCell cell;
2665 int c;
2666
2667 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2668 vim_memset(&cell, 0, sizeof(cell));
2669
2670 c = cell.chars[0];
2671 if (c == NUL)
2672 {
2673 ScreenLines[off] = ' ';
2674 if (enc_utf8)
2675 ScreenLinesUC[off] = NUL;
2676 }
2677 else
2678 {
2679 if (enc_utf8)
2680 {
2681 int i;
2682
2683 /* composing chars */
2684 for (i = 0; i < Screen_mco
2685 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2686 {
2687 ScreenLinesC[i][off] = cell.chars[i + 1];
2688 if (cell.chars[i + 1] == 0)
2689 break;
2690 }
2691 if (c >= 0x80 || (Screen_mco > 0
2692 && ScreenLinesC[0][off] != 0))
2693 {
2694 ScreenLines[off] = ' ';
2695 ScreenLinesUC[off] = c;
2696 }
2697 else
2698 {
2699 ScreenLines[off] = c;
2700 ScreenLinesUC[off] = NUL;
2701 }
2702 }
2703#ifdef WIN3264
2704 else if (has_mbyte && c >= 0x80)
2705 {
2706 char_u mb[MB_MAXBYTES+1];
2707 WCHAR wc = c;
2708
2709 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2710 (char*)mb, 2, 0, 0) > 1)
2711 {
2712 ScreenLines[off] = mb[0];
2713 ScreenLines[off + 1] = mb[1];
2714 cell.width = mb_ptr2cells(mb);
2715 }
2716 else
2717 ScreenLines[off] = c;
2718 }
2719#endif
2720 else
2721 ScreenLines[off] = c;
2722 }
2723 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2724
2725 ++pos->col;
2726 ++off;
2727 if (cell.width == 2)
2728 {
2729 if (enc_utf8)
2730 ScreenLinesUC[off] = NUL;
2731
2732 /* don't set the second byte to NUL for a DBCS encoding, it
2733 * has been set above */
2734 if (enc_utf8 || !has_mbyte)
2735 ScreenLines[off] = NUL;
2736
2737 ++pos->col;
2738 ++off;
2739 }
2740 }
2741}
2742
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002743#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002744 static void
2745update_system_term(term_T *term)
2746{
2747 VTermPos pos;
2748 VTermScreen *screen;
2749
2750 if (term->tl_vterm == NULL)
2751 return;
2752 screen = vterm_obtain_screen(term->tl_vterm);
2753
2754 /* Scroll up to make more room for terminal lines if needed. */
2755 while (term->tl_toprow > 0
2756 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2757 {
2758 int save_p_more = p_more;
2759
2760 p_more = FALSE;
2761 msg_row = Rows - 1;
2762 msg_puts((char_u *)"\n");
2763 p_more = save_p_more;
2764 --term->tl_toprow;
2765 }
2766
2767 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2768 && pos.row < Rows; ++pos.row)
2769 {
2770 if (pos.row < term->tl_rows)
2771 {
2772 int max_col = MIN(Columns, term->tl_cols);
2773
2774 term_line2screenline(screen, &pos, max_col);
2775 }
2776 else
2777 pos.col = 0;
2778
2779 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2780 }
2781
2782 term->tl_dirty_row_start = MAX_ROW;
2783 term->tl_dirty_row_end = 0;
2784 update_cursor(term, TRUE);
2785}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002786#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002787
2788/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002789 * Called to update a window that contains an active terminal.
2790 * Returns FAIL when there is no terminal running in this window or in
2791 * Terminal-Normal mode.
2792 */
2793 int
2794term_update_window(win_T *wp)
2795{
2796 term_T *term = wp->w_buffer->b_term;
2797 VTerm *vterm;
2798 VTermScreen *screen;
2799 VTermState *state;
2800 VTermPos pos;
2801
2802 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2803 return FAIL;
2804
2805 vterm = term->tl_vterm;
2806 screen = vterm_obtain_screen(vterm);
2807 state = vterm_obtain_state(vterm);
2808
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002809 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002810 {
2811 term->tl_dirty_row_start = 0;
2812 term->tl_dirty_row_end = MAX_ROW;
2813 }
2814
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002815 /*
2816 * If the window was resized a redraw will be triggered and we get here.
2817 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2818 */
2819 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
2820 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
2821 {
2822 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
2823 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
2824 win_T *twp;
2825
2826 FOR_ALL_WINDOWS(twp)
2827 {
2828 /* When more than one window shows the same terminal, use the
2829 * smallest size. */
2830 if (twp->w_buffer == term->tl_buffer)
2831 {
2832 if (!term->tl_rows_fixed && rows > twp->w_height)
2833 rows = twp->w_height;
2834 if (!term->tl_cols_fixed && cols > twp->w_width)
2835 cols = twp->w_width;
2836 }
2837 }
2838
2839 term->tl_vterm_size_changed = TRUE;
2840 vterm_set_size(vterm, rows, cols);
2841 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
2842 rows);
2843 term_report_winsize(term, rows, cols);
2844 }
2845
2846 /* The cursor may have been moved when resizing. */
2847 vterm_state_get_cursorpos(state, &pos);
2848 position_cursor(wp, &pos);
2849
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002850 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2851 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002852 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002853 if (pos.row < term->tl_rows)
2854 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002855 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002856
Bram Moolenaar13568252018-03-16 20:46:58 +01002857 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002858 }
2859 else
2860 pos.col = 0;
2861
Bram Moolenaarf118d482018-03-13 13:14:00 +01002862 screen_line(wp->w_winrow + pos.row
2863#ifdef FEAT_MENU
2864 + winbar_height(wp)
2865#endif
2866 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002867 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002868 term->tl_dirty_row_start = MAX_ROW;
2869 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002870
2871 return OK;
2872}
2873
2874/*
2875 * Return TRUE if "wp" is a terminal window where the job has finished.
2876 */
2877 int
2878term_is_finished(buf_T *buf)
2879{
2880 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2881}
2882
2883/*
2884 * Return TRUE if "wp" is a terminal window where the job has finished or we
2885 * are in Terminal-Normal mode, thus we show the buffer contents.
2886 */
2887 int
2888term_show_buffer(buf_T *buf)
2889{
2890 term_T *term = buf->b_term;
2891
2892 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2893}
2894
2895/*
2896 * The current buffer is going to be changed. If there is terminal
2897 * highlighting remove it now.
2898 */
2899 void
2900term_change_in_curbuf(void)
2901{
2902 term_T *term = curbuf->b_term;
2903
2904 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2905 {
2906 free_scrollback(term);
2907 redraw_buf_later(term->tl_buffer, NOT_VALID);
2908
2909 /* The buffer is now like a normal buffer, it cannot be easily
2910 * abandoned when changed. */
2911 set_string_option_direct((char_u *)"buftype", -1,
2912 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2913 }
2914}
2915
2916/*
2917 * Get the screen attribute for a position in the buffer.
2918 * Use a negative "col" to get the filler background color.
2919 */
2920 int
2921term_get_attr(buf_T *buf, linenr_T lnum, int col)
2922{
2923 term_T *term = buf->b_term;
2924 sb_line_T *line;
2925 cellattr_T *cellattr;
2926
2927 if (lnum > term->tl_scrollback.ga_len)
2928 cellattr = &term->tl_default_color;
2929 else
2930 {
2931 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
2932 if (col < 0 || col >= line->sb_cols)
2933 cellattr = &line->sb_fill_attr;
2934 else
2935 cellattr = line->sb_cells + col;
2936 }
2937 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
2938}
2939
2940static VTermColor ansi_table[16] = {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002941 { 0, 0, 0, 1}, /* black */
2942 {224, 0, 0, 2}, /* dark red */
2943 { 0, 224, 0, 3}, /* dark green */
2944 {224, 224, 0, 4}, /* dark yellow / brown */
2945 { 0, 0, 224, 5}, /* dark blue */
2946 {224, 0, 224, 6}, /* dark magenta */
2947 { 0, 224, 224, 7}, /* dark cyan */
2948 {224, 224, 224, 8}, /* light grey */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002949
Bram Moolenaar46359e12017-11-29 22:33:38 +01002950 {128, 128, 128, 9}, /* dark grey */
2951 {255, 64, 64, 10}, /* light red */
2952 { 64, 255, 64, 11}, /* light green */
2953 {255, 255, 64, 12}, /* yellow */
2954 { 64, 64, 255, 13}, /* light blue */
2955 {255, 64, 255, 14}, /* light magenta */
2956 { 64, 255, 255, 15}, /* light cyan */
2957 {255, 255, 255, 16}, /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002958};
2959
2960static int cube_value[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002961 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002962};
2963
2964static int grey_ramp[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002965 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
2966 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002967};
2968
2969/*
2970 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002971 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002972 */
2973 static void
2974cterm_color2rgb(int nr, VTermColor *rgb)
2975{
2976 int idx;
2977
2978 if (nr < 16)
2979 {
2980 *rgb = ansi_table[nr];
2981 }
2982 else if (nr < 232)
2983 {
2984 /* 216 color cube */
2985 idx = nr - 16;
2986 rgb->blue = cube_value[idx % 6];
2987 rgb->green = cube_value[idx / 6 % 6];
2988 rgb->red = cube_value[idx / 36 % 6];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002989 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002990 }
2991 else if (nr < 256)
2992 {
2993 /* 24 grey scale ramp */
2994 idx = nr - 232;
2995 rgb->blue = grey_ramp[idx];
2996 rgb->green = grey_ramp[idx];
2997 rgb->red = grey_ramp[idx];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002998 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002999 }
3000}
3001
3002/*
3003 * Create a new vterm and initialize it.
3004 */
3005 static void
3006create_vterm(term_T *term, int rows, int cols)
3007{
3008 VTerm *vterm;
3009 VTermScreen *screen;
3010 VTermValue value;
3011 VTermColor *fg, *bg;
3012 int fgval, bgval;
3013 int id;
3014
3015 vterm = vterm_new(rows, cols);
3016 term->tl_vterm = vterm;
3017 screen = vterm_obtain_screen(vterm);
3018 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3019 /* TODO: depends on 'encoding'. */
3020 vterm_set_utf8(vterm, 1);
3021
3022 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3023 term->tl_default_color.width = 1;
3024 fg = &term->tl_default_color.fg;
3025 bg = &term->tl_default_color.bg;
3026
3027 /* Vterm uses a default black background. Set it to white when
3028 * 'background' is "light". */
3029 if (*p_bg == 'l')
3030 {
3031 fgval = 0;
3032 bgval = 255;
3033 }
3034 else
3035 {
3036 fgval = 255;
3037 bgval = 0;
3038 }
3039 fg->red = fg->green = fg->blue = fgval;
3040 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003041 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003042
3043 /* The "Terminal" highlight group overrules the defaults. */
3044 id = syn_name2id((char_u *)"Terminal");
3045
Bram Moolenaar46359e12017-11-29 22:33:38 +01003046 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003047#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3048 if (0
3049# ifdef FEAT_GUI
3050 || gui.in_use
3051# endif
3052# ifdef FEAT_TERMGUICOLORS
3053 || p_tgc
3054# endif
3055 )
3056 {
3057 guicolor_T fg_rgb = INVALCOLOR;
3058 guicolor_T bg_rgb = INVALCOLOR;
3059
3060 if (id != 0)
3061 syn_id2colors(id, &fg_rgb, &bg_rgb);
3062
3063# ifdef FEAT_GUI
3064 if (gui.in_use)
3065 {
3066 if (fg_rgb == INVALCOLOR)
3067 fg_rgb = gui.norm_pixel;
3068 if (bg_rgb == INVALCOLOR)
3069 bg_rgb = gui.back_pixel;
3070 }
3071# ifdef FEAT_TERMGUICOLORS
3072 else
3073# endif
3074# endif
3075# ifdef FEAT_TERMGUICOLORS
3076 {
3077 if (fg_rgb == INVALCOLOR)
3078 fg_rgb = cterm_normal_fg_gui_color;
3079 if (bg_rgb == INVALCOLOR)
3080 bg_rgb = cterm_normal_bg_gui_color;
3081 }
3082# endif
3083 if (fg_rgb != INVALCOLOR)
3084 {
3085 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3086
3087 fg->red = (unsigned)(rgb >> 16);
3088 fg->green = (unsigned)(rgb >> 8) & 255;
3089 fg->blue = (unsigned)rgb & 255;
3090 }
3091 if (bg_rgb != INVALCOLOR)
3092 {
3093 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3094
3095 bg->red = (unsigned)(rgb >> 16);
3096 bg->green = (unsigned)(rgb >> 8) & 255;
3097 bg->blue = (unsigned)rgb & 255;
3098 }
3099 }
3100 else
3101#endif
3102 if (id != 0 && t_colors >= 16)
3103 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003104 if (term_default_cterm_fg >= 0)
3105 cterm_color2rgb(term_default_cterm_fg, fg);
3106 if (term_default_cterm_bg >= 0)
3107 cterm_color2rgb(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003108 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003109 else
3110 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003111#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003112 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003113#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003114
3115 /* In an MS-Windows console we know the normal colors. */
3116 if (cterm_normal_fg_color > 0)
3117 {
3118 cterm_color2rgb(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003119# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003120 tmp = fg->red;
3121 fg->red = fg->blue;
3122 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003123# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003124 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003125# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003126 else
3127 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003128# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003129
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003130 if (cterm_normal_bg_color > 0)
3131 {
3132 cterm_color2rgb(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003133# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003134 tmp = bg->red;
3135 bg->red = bg->blue;
3136 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003137# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003138 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003139# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003140 else
3141 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003142# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003143 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003144
3145 vterm_state_set_default_colors(vterm_obtain_state(vterm), fg, bg);
3146
3147 /* Required to initialize most things. */
3148 vterm_screen_reset(screen, 1 /* hard */);
3149
3150 /* Allow using alternate screen. */
3151 vterm_screen_enable_altscreen(screen, 1);
3152
3153 /* For unix do not use a blinking cursor. In an xterm this causes the
3154 * cursor to blink if it's blinking in the xterm.
3155 * For Windows we respect the system wide setting. */
3156#ifdef WIN3264
3157 if (GetCaretBlinkTime() == INFINITE)
3158 value.boolean = 0;
3159 else
3160 value.boolean = 1;
3161#else
3162 value.boolean = 0;
3163#endif
3164 vterm_state_set_termprop(vterm_obtain_state(vterm),
3165 VTERM_PROP_CURSORBLINK, &value);
3166}
3167
3168/*
3169 * Return the text to show for the buffer name and status.
3170 */
3171 char_u *
3172term_get_status_text(term_T *term)
3173{
3174 if (term->tl_status_text == NULL)
3175 {
3176 char_u *txt;
3177 size_t len;
3178
3179 if (term->tl_normal_mode)
3180 {
3181 if (term_job_running(term))
3182 txt = (char_u *)_("Terminal");
3183 else
3184 txt = (char_u *)_("Terminal-finished");
3185 }
3186 else if (term->tl_title != NULL)
3187 txt = term->tl_title;
3188 else if (term_none_open(term))
3189 txt = (char_u *)_("active");
3190 else if (term_job_running(term))
3191 txt = (char_u *)_("running");
3192 else
3193 txt = (char_u *)_("finished");
3194 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3195 term->tl_status_text = alloc((int)len);
3196 if (term->tl_status_text != NULL)
3197 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3198 term->tl_buffer->b_fname, txt);
3199 }
3200 return term->tl_status_text;
3201}
3202
3203/*
3204 * Mark references in jobs of terminals.
3205 */
3206 int
3207set_ref_in_term(int copyID)
3208{
3209 int abort = FALSE;
3210 term_T *term;
3211 typval_T tv;
3212
3213 for (term = first_term; term != NULL; term = term->tl_next)
3214 if (term->tl_job != NULL)
3215 {
3216 tv.v_type = VAR_JOB;
3217 tv.vval.v_job = term->tl_job;
3218 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3219 }
3220 return abort;
3221}
3222
3223/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003224 * Cache "Terminal" highlight group colors.
3225 */
3226 void
3227set_terminal_default_colors(int cterm_fg, int cterm_bg)
3228{
3229 term_default_cterm_fg = cterm_fg - 1;
3230 term_default_cterm_bg = cterm_bg - 1;
3231}
3232
3233/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003234 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003235 * Returns NULL when the buffer is not for a terminal window and logs a message
3236 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003237 */
3238 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003239term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003240{
3241 buf_T *buf;
3242
3243 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3244 ++emsg_off;
3245 buf = get_buf_tv(&argvars[0], FALSE);
3246 --emsg_off;
3247 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003248 {
3249 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003250 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003251 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003252 return buf;
3253}
3254
Bram Moolenaard96ff162018-02-18 22:13:29 +01003255 static int
3256same_color(VTermColor *a, VTermColor *b)
3257{
3258 return a->red == b->red
3259 && a->green == b->green
3260 && a->blue == b->blue
3261 && a->ansi_index == b->ansi_index;
3262}
3263
3264 static void
3265dump_term_color(FILE *fd, VTermColor *color)
3266{
3267 fprintf(fd, "%02x%02x%02x%d",
3268 (int)color->red, (int)color->green, (int)color->blue,
3269 (int)color->ansi_index);
3270}
3271
3272/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003273 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003274 *
3275 * Each screen cell in full is:
3276 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3277 * {characters} is a space for an empty cell
3278 * For a double-width character "+" is changed to "*" and the next cell is
3279 * skipped.
3280 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3281 * when "&" use the same as the previous cell.
3282 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3283 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3284 * {color-idx} is a number from 0 to 255
3285 *
3286 * Screen cell with same width, attributes and color as the previous one:
3287 * |{characters}
3288 *
3289 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3290 *
3291 * Repeating the previous screen cell:
3292 * @{count}
3293 */
3294 void
3295f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3296{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003297 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003298 term_T *term;
3299 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003300 int max_height = 0;
3301 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003302 stat_T st;
3303 FILE *fd;
3304 VTermPos pos;
3305 VTermScreen *screen;
3306 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003307 VTermState *state;
3308 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003309
3310 if (check_restricted() || check_secure())
3311 return;
3312 if (buf == NULL)
3313 return;
3314 term = buf->b_term;
3315
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003316 if (argvars[2].v_type != VAR_UNKNOWN)
3317 {
3318 dict_T *d;
3319
3320 if (argvars[2].v_type != VAR_DICT)
3321 {
3322 EMSG(_(e_dictreq));
3323 return;
3324 }
3325 d = argvars[2].vval.v_dict;
3326 if (d != NULL)
3327 {
3328 max_height = get_dict_number(d, (char_u *)"rows");
3329 max_width = get_dict_number(d, (char_u *)"columns");
3330 }
3331 }
3332
Bram Moolenaard96ff162018-02-18 22:13:29 +01003333 fname = get_tv_string_chk(&argvars[1]);
3334 if (fname == NULL)
3335 return;
3336 if (mch_stat((char *)fname, &st) >= 0)
3337 {
3338 EMSG2(_("E953: File exists: %s"), fname);
3339 return;
3340 }
3341
Bram Moolenaard96ff162018-02-18 22:13:29 +01003342 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3343 {
3344 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3345 return;
3346 }
3347
3348 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3349
3350 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003351 state = vterm_obtain_state(term->tl_vterm);
3352 vterm_state_get_cursorpos(state, &cursor_pos);
3353
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003354 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3355 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003356 {
3357 int repeat = 0;
3358
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003359 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3360 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003361 {
3362 VTermScreenCell cell;
3363 int same_attr;
3364 int same_chars = TRUE;
3365 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003366 int is_cursor_pos = (pos.col == cursor_pos.col
3367 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003368
3369 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3370 vim_memset(&cell, 0, sizeof(cell));
3371
3372 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3373 {
3374 if (cell.chars[i] != prev_cell.chars[i])
3375 same_chars = FALSE;
3376 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3377 break;
3378 }
3379 same_attr = vtermAttr2hl(cell.attrs)
3380 == vtermAttr2hl(prev_cell.attrs)
3381 && same_color(&cell.fg, &prev_cell.fg)
3382 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003383 if (same_chars && cell.width == prev_cell.width && same_attr
3384 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003385 {
3386 ++repeat;
3387 }
3388 else
3389 {
3390 if (repeat > 0)
3391 {
3392 fprintf(fd, "@%d", repeat);
3393 repeat = 0;
3394 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003395 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003396
3397 if (cell.chars[0] == NUL)
3398 fputs(" ", fd);
3399 else
3400 {
3401 char_u charbuf[10];
3402 int len;
3403
3404 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3405 && cell.chars[i] != NUL; ++i)
3406 {
3407 len = utf_char2bytes(cell.chars[0], charbuf);
3408 fwrite(charbuf, len, 1, fd);
3409 }
3410 }
3411
3412 /* When only the characters differ we don't write anything, the
3413 * following "|", "@" or NL will indicate using the same
3414 * attributes. */
3415 if (cell.width != prev_cell.width || !same_attr)
3416 {
3417 if (cell.width == 2)
3418 {
3419 fputs("*", fd);
3420 ++pos.col;
3421 }
3422 else
3423 fputs("+", fd);
3424
3425 if (same_attr)
3426 {
3427 fputs("&", fd);
3428 }
3429 else
3430 {
3431 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3432 if (same_color(&cell.fg, &prev_cell.fg))
3433 fputs("&", fd);
3434 else
3435 {
3436 fputs("#", fd);
3437 dump_term_color(fd, &cell.fg);
3438 }
3439 if (same_color(&cell.bg, &prev_cell.bg))
3440 fputs("&", fd);
3441 else
3442 {
3443 fputs("#", fd);
3444 dump_term_color(fd, &cell.bg);
3445 }
3446 }
3447 }
3448
3449 prev_cell = cell;
3450 }
3451 }
3452 if (repeat > 0)
3453 fprintf(fd, "@%d", repeat);
3454 fputs("\n", fd);
3455 }
3456
3457 fclose(fd);
3458}
3459
3460/*
3461 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3462 */
3463 static void
3464dump_is_corrupt(garray_T *gap)
3465{
3466 ga_concat(gap, (char_u *)"CORRUPT");
3467}
3468
3469 static void
3470append_cell(garray_T *gap, cellattr_T *cell)
3471{
3472 if (ga_grow(gap, 1) == OK)
3473 {
3474 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3475 ++gap->ga_len;
3476 }
3477}
3478
3479/*
3480 * Read the dump file from "fd" and append lines to the current buffer.
3481 * Return the cell width of the longest line.
3482 */
3483 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003484read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003485{
3486 int c;
3487 garray_T ga_text;
3488 garray_T ga_cell;
3489 char_u *prev_char = NULL;
3490 int attr = 0;
3491 cellattr_T cell;
3492 term_T *term = curbuf->b_term;
3493 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003494 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003495
3496 ga_init2(&ga_text, 1, 90);
3497 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3498 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003499 cursor_pos->row = -1;
3500 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003501
3502 c = fgetc(fd);
3503 for (;;)
3504 {
3505 if (c == EOF)
3506 break;
3507 if (c == '\n')
3508 {
3509 /* End of a line: append it to the buffer. */
3510 if (ga_text.ga_data == NULL)
3511 dump_is_corrupt(&ga_text);
3512 if (ga_grow(&term->tl_scrollback, 1) == OK)
3513 {
3514 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3515 + term->tl_scrollback.ga_len;
3516
3517 if (max_cells < ga_cell.ga_len)
3518 max_cells = ga_cell.ga_len;
3519 line->sb_cols = ga_cell.ga_len;
3520 line->sb_cells = ga_cell.ga_data;
3521 line->sb_fill_attr = term->tl_default_color;
3522 ++term->tl_scrollback.ga_len;
3523 ga_init(&ga_cell);
3524
3525 ga_append(&ga_text, NUL);
3526 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3527 ga_text.ga_len, FALSE);
3528 }
3529 else
3530 ga_clear(&ga_cell);
3531 ga_text.ga_len = 0;
3532
3533 c = fgetc(fd);
3534 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003535 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003536 {
3537 int prev_len = ga_text.ga_len;
3538
Bram Moolenaar9271d052018-02-25 21:39:46 +01003539 if (c == '>')
3540 {
3541 if (cursor_pos->row != -1)
3542 dump_is_corrupt(&ga_text); /* duplicate cursor */
3543 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3544 cursor_pos->col = ga_cell.ga_len;
3545 }
3546
Bram Moolenaard96ff162018-02-18 22:13:29 +01003547 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3548 c = fgetc(fd);
3549 if (c != EOF)
3550 ga_append(&ga_text, c);
3551 for (;;)
3552 {
3553 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003554 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003555 || c == EOF || c == '\n')
3556 break;
3557 ga_append(&ga_text, c);
3558 }
3559
3560 /* save the character for repeating it */
3561 vim_free(prev_char);
3562 if (ga_text.ga_data != NULL)
3563 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3564 ga_text.ga_len - prev_len);
3565
Bram Moolenaar9271d052018-02-25 21:39:46 +01003566 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003567 {
3568 /* use all attributes from previous cell */
3569 }
3570 else if (c == '+' || c == '*')
3571 {
3572 int is_bg;
3573
3574 cell.width = c == '+' ? 1 : 2;
3575
3576 c = fgetc(fd);
3577 if (c == '&')
3578 {
3579 /* use same attr as previous cell */
3580 c = fgetc(fd);
3581 }
3582 else if (isdigit(c))
3583 {
3584 /* get the decimal attribute */
3585 attr = 0;
3586 while (isdigit(c))
3587 {
3588 attr = attr * 10 + (c - '0');
3589 c = fgetc(fd);
3590 }
3591 hl2vtermAttr(attr, &cell);
3592 }
3593 else
3594 dump_is_corrupt(&ga_text);
3595
3596 /* is_bg == 0: fg, is_bg == 1: bg */
3597 for (is_bg = 0; is_bg <= 1; ++is_bg)
3598 {
3599 if (c == '&')
3600 {
3601 /* use same color as previous cell */
3602 c = fgetc(fd);
3603 }
3604 else if (c == '#')
3605 {
3606 int red, green, blue, index = 0;
3607
3608 c = fgetc(fd);
3609 red = hex2nr(c);
3610 c = fgetc(fd);
3611 red = (red << 4) + hex2nr(c);
3612 c = fgetc(fd);
3613 green = hex2nr(c);
3614 c = fgetc(fd);
3615 green = (green << 4) + hex2nr(c);
3616 c = fgetc(fd);
3617 blue = hex2nr(c);
3618 c = fgetc(fd);
3619 blue = (blue << 4) + hex2nr(c);
3620 c = fgetc(fd);
3621 if (!isdigit(c))
3622 dump_is_corrupt(&ga_text);
3623 while (isdigit(c))
3624 {
3625 index = index * 10 + (c - '0');
3626 c = fgetc(fd);
3627 }
3628
3629 if (is_bg)
3630 {
3631 cell.bg.red = red;
3632 cell.bg.green = green;
3633 cell.bg.blue = blue;
3634 cell.bg.ansi_index = index;
3635 }
3636 else
3637 {
3638 cell.fg.red = red;
3639 cell.fg.green = green;
3640 cell.fg.blue = blue;
3641 cell.fg.ansi_index = index;
3642 }
3643 }
3644 else
3645 dump_is_corrupt(&ga_text);
3646 }
3647 }
3648 else
3649 dump_is_corrupt(&ga_text);
3650
3651 append_cell(&ga_cell, &cell);
3652 }
3653 else if (c == '@')
3654 {
3655 if (prev_char == NULL)
3656 dump_is_corrupt(&ga_text);
3657 else
3658 {
3659 int count = 0;
3660
3661 /* repeat previous character, get the count */
3662 for (;;)
3663 {
3664 c = fgetc(fd);
3665 if (!isdigit(c))
3666 break;
3667 count = count * 10 + (c - '0');
3668 }
3669
3670 while (count-- > 0)
3671 {
3672 ga_concat(&ga_text, prev_char);
3673 append_cell(&ga_cell, &cell);
3674 }
3675 }
3676 }
3677 else
3678 {
3679 dump_is_corrupt(&ga_text);
3680 c = fgetc(fd);
3681 }
3682 }
3683
3684 if (ga_text.ga_len > 0)
3685 {
3686 /* trailing characters after last NL */
3687 dump_is_corrupt(&ga_text);
3688 ga_append(&ga_text, NUL);
3689 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3690 ga_text.ga_len, FALSE);
3691 }
3692
3693 ga_clear(&ga_text);
3694 vim_free(prev_char);
3695
3696 return max_cells;
3697}
3698
3699/*
3700 * Common for "term_dumpdiff()" and "term_dumpload()".
3701 */
3702 static void
3703term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
3704{
3705 jobopt_T opt;
3706 buf_T *buf;
3707 char_u buf1[NUMBUFLEN];
3708 char_u buf2[NUMBUFLEN];
3709 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003710 char_u *fname2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003711 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003712 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003713 char_u *textline = NULL;
3714
3715 /* First open the files. If this fails bail out. */
3716 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
3717 if (do_diff)
3718 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
3719 if (fname1 == NULL || (do_diff && fname2 == NULL))
3720 {
3721 EMSG(_(e_invarg));
3722 return;
3723 }
3724 fd1 = mch_fopen((char *)fname1, READBIN);
3725 if (fd1 == NULL)
3726 {
3727 EMSG2(_(e_notread), fname1);
3728 return;
3729 }
3730 if (do_diff)
3731 {
3732 fd2 = mch_fopen((char *)fname2, READBIN);
3733 if (fd2 == NULL)
3734 {
3735 fclose(fd1);
3736 EMSG2(_(e_notread), fname2);
3737 return;
3738 }
3739 }
3740
3741 init_job_options(&opt);
3742 /* TODO: use the {options} argument */
3743
3744 /* TODO: use the file name arguments for the buffer name */
3745 opt.jo_term_name = (char_u *)"dump diff";
3746
Bram Moolenaar13568252018-03-16 20:46:58 +01003747 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003748 if (buf != NULL && buf->b_term != NULL)
3749 {
3750 int i;
3751 linenr_T bot_lnum;
3752 linenr_T lnum;
3753 term_T *term = buf->b_term;
3754 int width;
3755 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003756 VTermPos cursor_pos1;
3757 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003758
3759 rettv->vval.v_number = buf->b_fnum;
3760
3761 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01003762 width = read_dump_file(fd1, &cursor_pos1);
3763
3764 /* position the cursor */
3765 if (cursor_pos1.row >= 0)
3766 {
3767 curwin->w_cursor.lnum = cursor_pos1.row + 1;
3768 coladvance(cursor_pos1.col);
3769 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003770
3771 /* Delete the empty line that was in the empty buffer. */
3772 ml_delete(1, FALSE);
3773
3774 /* For term_dumpload() we are done here. */
3775 if (!do_diff)
3776 goto theend;
3777
3778 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
3779
3780 textline = alloc(width + 1);
3781 if (textline == NULL)
3782 goto theend;
3783 for (i = 0; i < width; ++i)
3784 textline[i] = '=';
3785 textline[width] = NUL;
3786 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3787 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3788 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3789 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3790
3791 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003792 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003793 if (width2 > width)
3794 {
3795 vim_free(textline);
3796 textline = alloc(width2 + 1);
3797 if (textline == NULL)
3798 goto theend;
3799 width = width2;
3800 textline[width] = NUL;
3801 }
3802 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
3803
3804 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
3805 {
3806 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
3807 {
3808 /* bottom part has fewer rows, fill with "-" */
3809 for (i = 0; i < width; ++i)
3810 textline[i] = '-';
3811 }
3812 else
3813 {
3814 char_u *line1;
3815 char_u *line2;
3816 char_u *p1;
3817 char_u *p2;
3818 int col;
3819 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3820 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
3821 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
3822 ->sb_cells;
3823
3824 /* Make a copy, getting the second line will invalidate it. */
3825 line1 = vim_strsave(ml_get(lnum));
3826 if (line1 == NULL)
3827 break;
3828 p1 = line1;
3829
3830 line2 = ml_get(lnum + bot_lnum);
3831 p2 = line2;
3832 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
3833 {
3834 int len1 = utfc_ptr2len(p1);
3835 int len2 = utfc_ptr2len(p2);
3836
3837 textline[col] = ' ';
3838 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01003839 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01003840 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01003841 else if (lnum == cursor_pos1.row + 1
3842 && col == cursor_pos1.col
3843 && (cursor_pos1.row != cursor_pos2.row
3844 || cursor_pos1.col != cursor_pos2.col))
3845 /* cursor in first but not in second */
3846 textline[col] = '>';
3847 else if (lnum == cursor_pos2.row + 1
3848 && col == cursor_pos2.col
3849 && (cursor_pos1.row != cursor_pos2.row
3850 || cursor_pos1.col != cursor_pos2.col))
3851 /* cursor in second but not in first */
3852 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01003853 else if (cellattr1 != NULL && cellattr2 != NULL)
3854 {
3855 if ((cellattr1 + col)->width
3856 != (cellattr2 + col)->width)
3857 textline[col] = 'w';
3858 else if (!same_color(&(cellattr1 + col)->fg,
3859 &(cellattr2 + col)->fg))
3860 textline[col] = 'f';
3861 else if (!same_color(&(cellattr1 + col)->bg,
3862 &(cellattr2 + col)->bg))
3863 textline[col] = 'b';
3864 else if (vtermAttr2hl((cellattr1 + col)->attrs)
3865 != vtermAttr2hl(((cellattr2 + col)->attrs)))
3866 textline[col] = 'a';
3867 }
3868 p1 += len1;
3869 p2 += len2;
3870 /* TODO: handle different width */
3871 }
3872 vim_free(line1);
3873
3874 while (col < width)
3875 {
3876 if (*p1 == NUL && *p2 == NUL)
3877 textline[col] = '?';
3878 else if (*p1 == NUL)
3879 {
3880 textline[col] = '+';
3881 p2 += utfc_ptr2len(p2);
3882 }
3883 else
3884 {
3885 textline[col] = '-';
3886 p1 += utfc_ptr2len(p1);
3887 }
3888 ++col;
3889 }
3890 }
3891 if (add_empty_scrollback(term, &term->tl_default_color,
3892 term->tl_top_diff_rows) == OK)
3893 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3894 ++bot_lnum;
3895 }
3896
3897 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
3898 {
3899 /* bottom part has more rows, fill with "+" */
3900 for (i = 0; i < width; ++i)
3901 textline[i] = '+';
3902 if (add_empty_scrollback(term, &term->tl_default_color,
3903 term->tl_top_diff_rows) == OK)
3904 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3905 ++lnum;
3906 ++bot_lnum;
3907 }
3908
3909 term->tl_cols = width;
3910 }
3911
3912theend:
3913 vim_free(textline);
3914 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003915 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003916 fclose(fd2);
3917}
3918
3919/*
3920 * If the current buffer shows the output of term_dumpdiff(), swap the top and
3921 * bottom files.
3922 * Return FAIL when this is not possible.
3923 */
3924 int
3925term_swap_diff()
3926{
3927 term_T *term = curbuf->b_term;
3928 linenr_T line_count;
3929 linenr_T top_rows;
3930 linenr_T bot_rows;
3931 linenr_T bot_start;
3932 linenr_T lnum;
3933 char_u *p;
3934 sb_line_T *sb_line;
3935
3936 if (term == NULL
3937 || !term_is_finished(curbuf)
3938 || term->tl_top_diff_rows == 0
3939 || term->tl_scrollback.ga_len == 0)
3940 return FAIL;
3941
3942 line_count = curbuf->b_ml.ml_line_count;
3943 top_rows = term->tl_top_diff_rows;
3944 bot_rows = term->tl_bot_diff_rows;
3945 bot_start = line_count - bot_rows;
3946 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3947
3948 /* move lines from top to above the bottom part */
3949 for (lnum = 1; lnum <= top_rows; ++lnum)
3950 {
3951 p = vim_strsave(ml_get(1));
3952 if (p == NULL)
3953 return OK;
3954 ml_append(bot_start, p, 0, FALSE);
3955 ml_delete(1, FALSE);
3956 vim_free(p);
3957 }
3958
3959 /* move lines from bottom to the top */
3960 for (lnum = 1; lnum <= bot_rows; ++lnum)
3961 {
3962 p = vim_strsave(ml_get(bot_start + lnum));
3963 if (p == NULL)
3964 return OK;
3965 ml_delete(bot_start + lnum, FALSE);
3966 ml_append(lnum - 1, p, 0, FALSE);
3967 vim_free(p);
3968 }
3969
3970 if (top_rows == bot_rows)
3971 {
3972 /* rows counts are equal, can swap cell properties */
3973 for (lnum = 0; lnum < top_rows; ++lnum)
3974 {
3975 sb_line_T temp;
3976
3977 temp = *(sb_line + lnum);
3978 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
3979 *(sb_line + bot_start + lnum) = temp;
3980 }
3981 }
3982 else
3983 {
3984 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
3985 sb_line_T *temp = (sb_line_T *)alloc((int)size);
3986
3987 /* need to copy cell properties into temp memory */
3988 if (temp != NULL)
3989 {
3990 mch_memmove(temp, term->tl_scrollback.ga_data, size);
3991 mch_memmove(term->tl_scrollback.ga_data,
3992 temp + bot_start,
3993 sizeof(sb_line_T) * bot_rows);
3994 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
3995 temp + top_rows,
3996 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
3997 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
3998 + line_count - top_rows,
3999 temp,
4000 sizeof(sb_line_T) * top_rows);
4001 vim_free(temp);
4002 }
4003 }
4004
4005 term->tl_top_diff_rows = bot_rows;
4006 term->tl_bot_diff_rows = top_rows;
4007
4008 update_screen(NOT_VALID);
4009 return OK;
4010}
4011
4012/*
4013 * "term_dumpdiff(filename, filename, options)" function
4014 */
4015 void
4016f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4017{
4018 term_load_dump(argvars, rettv, TRUE);
4019}
4020
4021/*
4022 * "term_dumpload(filename, options)" function
4023 */
4024 void
4025f_term_dumpload(typval_T *argvars, typval_T *rettv)
4026{
4027 term_load_dump(argvars, rettv, FALSE);
4028}
4029
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004030/*
4031 * "term_getaltscreen(buf)" function
4032 */
4033 void
4034f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4035{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004036 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004037
4038 if (buf == NULL)
4039 return;
4040 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4041}
4042
4043/*
4044 * "term_getattr(attr, name)" function
4045 */
4046 void
4047f_term_getattr(typval_T *argvars, typval_T *rettv)
4048{
4049 int attr;
4050 size_t i;
4051 char_u *name;
4052
4053 static struct {
4054 char *name;
4055 int attr;
4056 } attrs[] = {
4057 {"bold", HL_BOLD},
4058 {"italic", HL_ITALIC},
4059 {"underline", HL_UNDERLINE},
4060 {"strike", HL_STRIKETHROUGH},
4061 {"reverse", HL_INVERSE},
4062 };
4063
4064 attr = get_tv_number(&argvars[0]);
4065 name = get_tv_string_chk(&argvars[1]);
4066 if (name == NULL)
4067 return;
4068
4069 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4070 if (STRCMP(name, attrs[i].name) == 0)
4071 {
4072 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4073 break;
4074 }
4075}
4076
4077/*
4078 * "term_getcursor(buf)" function
4079 */
4080 void
4081f_term_getcursor(typval_T *argvars, typval_T *rettv)
4082{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004083 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004084 term_T *term;
4085 list_T *l;
4086 dict_T *d;
4087
4088 if (rettv_list_alloc(rettv) == FAIL)
4089 return;
4090 if (buf == NULL)
4091 return;
4092 term = buf->b_term;
4093
4094 l = rettv->vval.v_list;
4095 list_append_number(l, term->tl_cursor_pos.row + 1);
4096 list_append_number(l, term->tl_cursor_pos.col + 1);
4097
4098 d = dict_alloc();
4099 if (d != NULL)
4100 {
4101 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4102 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4103 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4104 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4105 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4106 ? (char_u *)"" : term->tl_cursor_color);
4107 list_append_dict(l, d);
4108 }
4109}
4110
4111/*
4112 * "term_getjob(buf)" function
4113 */
4114 void
4115f_term_getjob(typval_T *argvars, typval_T *rettv)
4116{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004117 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004118
4119 rettv->v_type = VAR_JOB;
4120 rettv->vval.v_job = NULL;
4121 if (buf == NULL)
4122 return;
4123
4124 rettv->vval.v_job = buf->b_term->tl_job;
4125 if (rettv->vval.v_job != NULL)
4126 ++rettv->vval.v_job->jv_refcount;
4127}
4128
4129 static int
4130get_row_number(typval_T *tv, term_T *term)
4131{
4132 if (tv->v_type == VAR_STRING
4133 && tv->vval.v_string != NULL
4134 && STRCMP(tv->vval.v_string, ".") == 0)
4135 return term->tl_cursor_pos.row;
4136 return (int)get_tv_number(tv) - 1;
4137}
4138
4139/*
4140 * "term_getline(buf, row)" function
4141 */
4142 void
4143f_term_getline(typval_T *argvars, typval_T *rettv)
4144{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004145 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004146 term_T *term;
4147 int row;
4148
4149 rettv->v_type = VAR_STRING;
4150 if (buf == NULL)
4151 return;
4152 term = buf->b_term;
4153 row = get_row_number(&argvars[1], term);
4154
4155 if (term->tl_vterm == NULL)
4156 {
4157 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4158
4159 /* vterm is finished, get the text from the buffer */
4160 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4161 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4162 }
4163 else
4164 {
4165 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4166 VTermRect rect;
4167 int len;
4168 char_u *p;
4169
4170 if (row < 0 || row >= term->tl_rows)
4171 return;
4172 len = term->tl_cols * MB_MAXBYTES + 1;
4173 p = alloc(len);
4174 if (p == NULL)
4175 return;
4176 rettv->vval.v_string = p;
4177
4178 rect.start_col = 0;
4179 rect.end_col = term->tl_cols;
4180 rect.start_row = row;
4181 rect.end_row = row + 1;
4182 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4183 }
4184}
4185
4186/*
4187 * "term_getscrolled(buf)" function
4188 */
4189 void
4190f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4191{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004192 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004193
4194 if (buf == NULL)
4195 return;
4196 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4197}
4198
4199/*
4200 * "term_getsize(buf)" function
4201 */
4202 void
4203f_term_getsize(typval_T *argvars, typval_T *rettv)
4204{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004205 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004206 list_T *l;
4207
4208 if (rettv_list_alloc(rettv) == FAIL)
4209 return;
4210 if (buf == NULL)
4211 return;
4212
4213 l = rettv->vval.v_list;
4214 list_append_number(l, buf->b_term->tl_rows);
4215 list_append_number(l, buf->b_term->tl_cols);
4216}
4217
4218/*
4219 * "term_getstatus(buf)" function
4220 */
4221 void
4222f_term_getstatus(typval_T *argvars, typval_T *rettv)
4223{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004224 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004225 term_T *term;
4226 char_u val[100];
4227
4228 rettv->v_type = VAR_STRING;
4229 if (buf == NULL)
4230 return;
4231 term = buf->b_term;
4232
4233 if (term_job_running(term))
4234 STRCPY(val, "running");
4235 else
4236 STRCPY(val, "finished");
4237 if (term->tl_normal_mode)
4238 STRCAT(val, ",normal");
4239 rettv->vval.v_string = vim_strsave(val);
4240}
4241
4242/*
4243 * "term_gettitle(buf)" function
4244 */
4245 void
4246f_term_gettitle(typval_T *argvars, typval_T *rettv)
4247{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004248 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004249
4250 rettv->v_type = VAR_STRING;
4251 if (buf == NULL)
4252 return;
4253
4254 if (buf->b_term->tl_title != NULL)
4255 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4256}
4257
4258/*
4259 * "term_gettty(buf)" function
4260 */
4261 void
4262f_term_gettty(typval_T *argvars, typval_T *rettv)
4263{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004264 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004265 char_u *p;
4266 int num = 0;
4267
4268 rettv->v_type = VAR_STRING;
4269 if (buf == NULL)
4270 return;
4271 if (argvars[1].v_type != VAR_UNKNOWN)
4272 num = get_tv_number(&argvars[1]);
4273
4274 switch (num)
4275 {
4276 case 0:
4277 if (buf->b_term->tl_job != NULL)
4278 p = buf->b_term->tl_job->jv_tty_out;
4279 else
4280 p = buf->b_term->tl_tty_out;
4281 break;
4282 case 1:
4283 if (buf->b_term->tl_job != NULL)
4284 p = buf->b_term->tl_job->jv_tty_in;
4285 else
4286 p = buf->b_term->tl_tty_in;
4287 break;
4288 default:
4289 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4290 return;
4291 }
4292 if (p != NULL)
4293 rettv->vval.v_string = vim_strsave(p);
4294}
4295
4296/*
4297 * "term_list()" function
4298 */
4299 void
4300f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4301{
4302 term_T *tp;
4303 list_T *l;
4304
4305 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4306 return;
4307
4308 l = rettv->vval.v_list;
4309 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4310 if (tp != NULL && tp->tl_buffer != NULL)
4311 if (list_append_number(l,
4312 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4313 return;
4314}
4315
4316/*
4317 * "term_scrape(buf, row)" function
4318 */
4319 void
4320f_term_scrape(typval_T *argvars, typval_T *rettv)
4321{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004322 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004323 VTermScreen *screen = NULL;
4324 VTermPos pos;
4325 list_T *l;
4326 term_T *term;
4327 char_u *p;
4328 sb_line_T *line;
4329
4330 if (rettv_list_alloc(rettv) == FAIL)
4331 return;
4332 if (buf == NULL)
4333 return;
4334 term = buf->b_term;
4335
4336 l = rettv->vval.v_list;
4337 pos.row = get_row_number(&argvars[1], term);
4338
4339 if (term->tl_vterm != NULL)
4340 {
4341 screen = vterm_obtain_screen(term->tl_vterm);
4342 p = NULL;
4343 line = NULL;
4344 }
4345 else
4346 {
4347 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4348
4349 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4350 return;
4351 p = ml_get_buf(buf, lnum + 1, FALSE);
4352 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4353 }
4354
4355 for (pos.col = 0; pos.col < term->tl_cols; )
4356 {
4357 dict_T *dcell;
4358 int width;
4359 VTermScreenCellAttrs attrs;
4360 VTermColor fg, bg;
4361 char_u rgb[8];
4362 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4363 int off = 0;
4364 int i;
4365
4366 if (screen == NULL)
4367 {
4368 cellattr_T *cellattr;
4369 int len;
4370
4371 /* vterm has finished, get the cell from scrollback */
4372 if (pos.col >= line->sb_cols)
4373 break;
4374 cellattr = line->sb_cells + pos.col;
4375 width = cellattr->width;
4376 attrs = cellattr->attrs;
4377 fg = cellattr->fg;
4378 bg = cellattr->bg;
4379 len = MB_PTR2LEN(p);
4380 mch_memmove(mbs, p, len);
4381 mbs[len] = NUL;
4382 p += len;
4383 }
4384 else
4385 {
4386 VTermScreenCell cell;
4387 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4388 break;
4389 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4390 {
4391 if (cell.chars[i] == 0)
4392 break;
4393 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4394 }
4395 mbs[off] = NUL;
4396 width = cell.width;
4397 attrs = cell.attrs;
4398 fg = cell.fg;
4399 bg = cell.bg;
4400 }
4401 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004402 if (dcell == NULL)
4403 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004404 list_append_dict(l, dcell);
4405
4406 dict_add_nr_str(dcell, "chars", 0, mbs);
4407
4408 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4409 fg.red, fg.green, fg.blue);
4410 dict_add_nr_str(dcell, "fg", 0, rgb);
4411 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4412 bg.red, bg.green, bg.blue);
4413 dict_add_nr_str(dcell, "bg", 0, rgb);
4414
4415 dict_add_nr_str(dcell, "attr",
4416 cell2attr(attrs, fg, bg), NULL);
4417 dict_add_nr_str(dcell, "width", width, NULL);
4418
4419 ++pos.col;
4420 if (width == 2)
4421 ++pos.col;
4422 }
4423}
4424
4425/*
4426 * "term_sendkeys(buf, keys)" function
4427 */
4428 void
4429f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4430{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004431 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004432 char_u *msg;
4433 term_T *term;
4434
4435 rettv->v_type = VAR_UNKNOWN;
4436 if (buf == NULL)
4437 return;
4438
4439 msg = get_tv_string_chk(&argvars[1]);
4440 if (msg == NULL)
4441 return;
4442 term = buf->b_term;
4443 if (term->tl_vterm == NULL)
4444 return;
4445
4446 while (*msg != NUL)
4447 {
4448 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004449 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004450 }
4451}
4452
4453/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004454 * "term_setrestore(buf, command)" function
4455 */
4456 void
4457f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4458{
4459#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004460 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004461 term_T *term;
4462 char_u *cmd;
4463
4464 if (buf == NULL)
4465 return;
4466 term = buf->b_term;
4467 vim_free(term->tl_command);
4468 cmd = get_tv_string_chk(&argvars[1]);
4469 if (cmd != NULL)
4470 term->tl_command = vim_strsave(cmd);
4471 else
4472 term->tl_command = NULL;
4473#endif
4474}
4475
4476/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004477 * "term_setkill(buf, how)" function
4478 */
4479 void
4480f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4481{
4482 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4483 term_T *term;
4484 char_u *how;
4485
4486 if (buf == NULL)
4487 return;
4488 term = buf->b_term;
4489 vim_free(term->tl_kill);
4490 how = get_tv_string_chk(&argvars[1]);
4491 if (how != NULL)
4492 term->tl_kill = vim_strsave(how);
4493 else
4494 term->tl_kill = NULL;
4495}
4496
4497/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004498 * "term_start(command, options)" function
4499 */
4500 void
4501f_term_start(typval_T *argvars, typval_T *rettv)
4502{
4503 jobopt_T opt;
4504 buf_T *buf;
4505
4506 init_job_options(&opt);
4507 if (argvars[1].v_type != VAR_UNKNOWN
4508 && get_job_options(&argvars[1], &opt,
4509 JO_TIMEOUT_ALL + JO_STOPONEXIT
4510 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
4511 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
4512 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
4513 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004514 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004515 + JO2_NORESTORE + JO2_TERM_KILL) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004516 return;
4517
4518 if (opt.jo_vertical)
4519 cmdmod.split = WSP_VERT;
Bram Moolenaar13568252018-03-16 20:46:58 +01004520 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004521
4522 if (buf != NULL && buf->b_term != NULL)
4523 rettv->vval.v_number = buf->b_fnum;
4524}
4525
4526/*
4527 * "term_wait" function
4528 */
4529 void
4530f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
4531{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004532 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004533
4534 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004535 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004536 if (buf->b_term->tl_job == NULL)
4537 {
4538 ch_log(NULL, "term_wait(): no job to wait for");
4539 return;
4540 }
4541 if (buf->b_term->tl_job->jv_channel == NULL)
4542 /* channel is closed, nothing to do */
4543 return;
4544
4545 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01004546 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004547 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
4548 {
4549 /* The job is dead, keep reading channel I/O until the channel is
4550 * closed. buf->b_term may become NULL if the terminal was closed while
4551 * waiting. */
4552 ch_log(NULL, "term_wait(): waiting for channel to close");
4553 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
4554 {
4555 mch_check_messages();
4556 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01004557 if (!buf_valid(buf))
4558 /* If the terminal is closed when the channel is closed the
4559 * buffer disappears. */
4560 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004561 ui_delay(10L, FALSE);
4562 }
4563 mch_check_messages();
4564 parse_queued_messages();
4565 }
4566 else
4567 {
4568 long wait = 10L;
4569
4570 mch_check_messages();
4571 parse_queued_messages();
4572
4573 /* Wait for some time for any channel I/O. */
4574 if (argvars[1].v_type != VAR_UNKNOWN)
4575 wait = get_tv_number(&argvars[1]);
4576 ui_delay(wait, TRUE);
4577 mch_check_messages();
4578
4579 /* Flushing messages on channels is hopefully sufficient.
4580 * TODO: is there a better way? */
4581 parse_queued_messages();
4582 }
4583}
4584
4585/*
4586 * Called when a channel has sent all the lines to a terminal.
4587 * Send a CTRL-D to mark the end of the text.
4588 */
4589 void
4590term_send_eof(channel_T *ch)
4591{
4592 term_T *term;
4593
4594 for (term = first_term; term != NULL; term = term->tl_next)
4595 if (term->tl_job == ch->ch_job)
4596 {
4597 if (term->tl_eof_chars != NULL)
4598 {
4599 channel_send(ch, PART_IN, term->tl_eof_chars,
4600 (int)STRLEN(term->tl_eof_chars), NULL);
4601 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
4602 }
4603# ifdef WIN3264
4604 else
4605 /* Default: CTRL-D */
4606 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
4607# endif
4608 }
4609}
4610
4611# if defined(WIN3264) || defined(PROTO)
4612
4613/**************************************
4614 * 2. MS-Windows implementation.
4615 */
4616
4617# ifndef PROTO
4618
4619#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
4620#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01004621#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004622
4623void* (*winpty_config_new)(UINT64, void*);
4624void* (*winpty_open)(void*, void*);
4625void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
4626BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
4627void (*winpty_config_set_mouse_mode)(void*, int);
4628void (*winpty_config_set_initial_size)(void*, int, int);
4629LPCWSTR (*winpty_conin_name)(void*);
4630LPCWSTR (*winpty_conout_name)(void*);
4631LPCWSTR (*winpty_conerr_name)(void*);
4632void (*winpty_free)(void*);
4633void (*winpty_config_free)(void*);
4634void (*winpty_spawn_config_free)(void*);
4635void (*winpty_error_free)(void*);
4636LPCWSTR (*winpty_error_msg)(void*);
4637BOOL (*winpty_set_size)(void*, int, int, void*);
4638HANDLE (*winpty_agent_process)(void*);
4639
4640#define WINPTY_DLL "winpty.dll"
4641
4642static HINSTANCE hWinPtyDLL = NULL;
4643# endif
4644
4645 static int
4646dyn_winpty_init(int verbose)
4647{
4648 int i;
4649 static struct
4650 {
4651 char *name;
4652 FARPROC *ptr;
4653 } winpty_entry[] =
4654 {
4655 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
4656 {"winpty_config_free", (FARPROC*)&winpty_config_free},
4657 {"winpty_config_new", (FARPROC*)&winpty_config_new},
4658 {"winpty_config_set_mouse_mode",
4659 (FARPROC*)&winpty_config_set_mouse_mode},
4660 {"winpty_config_set_initial_size",
4661 (FARPROC*)&winpty_config_set_initial_size},
4662 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
4663 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
4664 {"winpty_error_free", (FARPROC*)&winpty_error_free},
4665 {"winpty_free", (FARPROC*)&winpty_free},
4666 {"winpty_open", (FARPROC*)&winpty_open},
4667 {"winpty_spawn", (FARPROC*)&winpty_spawn},
4668 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
4669 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
4670 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
4671 {"winpty_set_size", (FARPROC*)&winpty_set_size},
4672 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
4673 {NULL, NULL}
4674 };
4675
4676 /* No need to initialize twice. */
4677 if (hWinPtyDLL)
4678 return OK;
4679 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
4680 * winpty.dll. */
4681 if (*p_winptydll != NUL)
4682 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
4683 if (!hWinPtyDLL)
4684 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
4685 if (!hWinPtyDLL)
4686 {
4687 if (verbose)
4688 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
4689 : (char_u *)WINPTY_DLL);
4690 return FAIL;
4691 }
4692 for (i = 0; winpty_entry[i].name != NULL
4693 && winpty_entry[i].ptr != NULL; ++i)
4694 {
4695 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
4696 winpty_entry[i].name)) == NULL)
4697 {
4698 if (verbose)
4699 EMSG2(_(e_loadfunc), winpty_entry[i].name);
4700 return FAIL;
4701 }
4702 }
4703
4704 return OK;
4705}
4706
4707/*
4708 * Create a new terminal of "rows" by "cols" cells.
4709 * Store a reference in "term".
4710 * Return OK or FAIL.
4711 */
4712 static int
4713term_and_job_init(
4714 term_T *term,
4715 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01004716 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004717 jobopt_T *opt)
4718{
4719 WCHAR *cmd_wchar = NULL;
4720 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004721 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004722 channel_T *channel = NULL;
4723 job_T *job = NULL;
4724 DWORD error;
4725 HANDLE jo = NULL;
4726 HANDLE child_process_handle;
4727 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01004728 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004729 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004730 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004731 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004732
4733 if (dyn_winpty_init(TRUE) == FAIL)
4734 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004735 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
4736 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004737
4738 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004739 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004740 cmd = argvar->vval.v_string;
4741 }
4742 else if (argvar->v_type == VAR_LIST)
4743 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004744 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004745 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004746 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004747 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004748 if (cmd == NULL || *cmd == NUL)
4749 {
4750 EMSG(_(e_invarg));
4751 goto failed;
4752 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004753
4754 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004755 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004756 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004757 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004758 if (opt->jo_cwd != NULL)
4759 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004760
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004761 win32_build_env(opt->jo_env, &ga_env, TRUE);
4762 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004763
4764 job = job_alloc();
4765 if (job == NULL)
4766 goto failed;
4767
4768 channel = add_channel();
4769 if (channel == NULL)
4770 goto failed;
4771
4772 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
4773 if (term->tl_winpty_config == NULL)
4774 goto failed;
4775
4776 winpty_config_set_mouse_mode(term->tl_winpty_config,
4777 WINPTY_MOUSE_MODE_FORCE);
4778 winpty_config_set_initial_size(term->tl_winpty_config,
4779 term->tl_cols, term->tl_rows);
4780 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
4781 if (term->tl_winpty == NULL)
4782 goto failed;
4783
4784 spawn_config = winpty_spawn_config_new(
4785 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
4786 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
4787 NULL,
4788 cmd_wchar,
4789 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004790 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004791 &winpty_err);
4792 if (spawn_config == NULL)
4793 goto failed;
4794
4795 channel = add_channel();
4796 if (channel == NULL)
4797 goto failed;
4798
4799 job = job_alloc();
4800 if (job == NULL)
4801 goto failed;
4802
4803 if (opt->jo_set & JO_IN_BUF)
4804 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
4805
4806 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
4807 &child_thread_handle, &error, &winpty_err))
4808 goto failed;
4809
4810 channel_set_pipes(channel,
4811 (sock_T)CreateFileW(
4812 winpty_conin_name(term->tl_winpty),
4813 GENERIC_WRITE, 0, NULL,
4814 OPEN_EXISTING, 0, NULL),
4815 (sock_T)CreateFileW(
4816 winpty_conout_name(term->tl_winpty),
4817 GENERIC_READ, 0, NULL,
4818 OPEN_EXISTING, 0, NULL),
4819 (sock_T)CreateFileW(
4820 winpty_conerr_name(term->tl_winpty),
4821 GENERIC_READ, 0, NULL,
4822 OPEN_EXISTING, 0, NULL));
4823
4824 /* Write lines with CR instead of NL. */
4825 channel->ch_write_text_mode = TRUE;
4826
4827 jo = CreateJobObject(NULL, NULL);
4828 if (jo == NULL)
4829 goto failed;
4830
4831 if (!AssignProcessToJobObject(jo, child_process_handle))
4832 {
4833 /* Failed, switch the way to terminate process with TerminateProcess. */
4834 CloseHandle(jo);
4835 jo = NULL;
4836 }
4837
4838 winpty_spawn_config_free(spawn_config);
4839 vim_free(cmd_wchar);
4840 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004841 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004842
4843 create_vterm(term, term->tl_rows, term->tl_cols);
4844
4845 channel_set_job(channel, job, opt);
4846 job_set_options(job, opt);
4847
4848 job->jv_channel = channel;
4849 job->jv_proc_info.hProcess = child_process_handle;
4850 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
4851 job->jv_job_object = jo;
4852 job->jv_status = JOB_STARTED;
4853 job->jv_tty_in = utf16_to_enc(
4854 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
4855 job->jv_tty_out = utf16_to_enc(
4856 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
4857 ++job->jv_refcount;
4858 term->tl_job = job;
4859
4860 return OK;
4861
4862failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004863 ga_clear(&ga_cmd);
4864 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004865 vim_free(cmd_wchar);
4866 vim_free(cwd_wchar);
4867 if (spawn_config != NULL)
4868 winpty_spawn_config_free(spawn_config);
4869 if (channel != NULL)
4870 channel_clear(channel);
4871 if (job != NULL)
4872 {
4873 job->jv_channel = NULL;
4874 job_cleanup(job);
4875 }
4876 term->tl_job = NULL;
4877 if (jo != NULL)
4878 CloseHandle(jo);
4879 if (term->tl_winpty != NULL)
4880 winpty_free(term->tl_winpty);
4881 term->tl_winpty = NULL;
4882 if (term->tl_winpty_config != NULL)
4883 winpty_config_free(term->tl_winpty_config);
4884 term->tl_winpty_config = NULL;
4885 if (winpty_err != NULL)
4886 {
4887 char_u *msg = utf16_to_enc(
4888 (short_u *)winpty_error_msg(winpty_err), NULL);
4889
4890 EMSG(msg);
4891 winpty_error_free(winpty_err);
4892 }
4893 return FAIL;
4894}
4895
4896 static int
4897create_pty_only(term_T *term, jobopt_T *options)
4898{
4899 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
4900 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
4901 char in_name[80], out_name[80];
4902 channel_T *channel = NULL;
4903
4904 create_vterm(term, term->tl_rows, term->tl_cols);
4905
4906 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
4907 GetCurrentProcessId(),
4908 curbuf->b_fnum);
4909 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
4910 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4911 PIPE_UNLIMITED_INSTANCES,
4912 0, 0, NMPWAIT_NOWAIT, NULL);
4913 if (hPipeIn == INVALID_HANDLE_VALUE)
4914 goto failed;
4915
4916 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
4917 GetCurrentProcessId(),
4918 curbuf->b_fnum);
4919 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
4920 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4921 PIPE_UNLIMITED_INSTANCES,
4922 0, 0, 0, NULL);
4923 if (hPipeOut == INVALID_HANDLE_VALUE)
4924 goto failed;
4925
4926 ConnectNamedPipe(hPipeIn, NULL);
4927 ConnectNamedPipe(hPipeOut, NULL);
4928
4929 term->tl_job = job_alloc();
4930 if (term->tl_job == NULL)
4931 goto failed;
4932 ++term->tl_job->jv_refcount;
4933
4934 /* behave like the job is already finished */
4935 term->tl_job->jv_status = JOB_FINISHED;
4936
4937 channel = add_channel();
4938 if (channel == NULL)
4939 goto failed;
4940 term->tl_job->jv_channel = channel;
4941 channel->ch_keep_open = TRUE;
4942 channel->ch_named_pipe = TRUE;
4943
4944 channel_set_pipes(channel,
4945 (sock_T)hPipeIn,
4946 (sock_T)hPipeOut,
4947 (sock_T)hPipeOut);
4948 channel_set_job(channel, term->tl_job, options);
4949 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
4950 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
4951
4952 return OK;
4953
4954failed:
4955 if (hPipeIn != NULL)
4956 CloseHandle(hPipeIn);
4957 if (hPipeOut != NULL)
4958 CloseHandle(hPipeOut);
4959 return FAIL;
4960}
4961
4962/*
4963 * Free the terminal emulator part of "term".
4964 */
4965 static void
4966term_free_vterm(term_T *term)
4967{
4968 if (term->tl_winpty != NULL)
4969 winpty_free(term->tl_winpty);
4970 term->tl_winpty = NULL;
4971 if (term->tl_winpty_config != NULL)
4972 winpty_config_free(term->tl_winpty_config);
4973 term->tl_winpty_config = NULL;
4974 if (term->tl_vterm != NULL)
4975 vterm_free(term->tl_vterm);
4976 term->tl_vterm = NULL;
4977}
4978
4979/*
4980 * Request size to terminal.
4981 */
4982 static void
4983term_report_winsize(term_T *term, int rows, int cols)
4984{
4985 if (term->tl_winpty)
4986 winpty_set_size(term->tl_winpty, cols, rows, NULL);
4987}
4988
4989 int
4990terminal_enabled(void)
4991{
4992 return dyn_winpty_init(FALSE) == OK;
4993}
4994
4995# else
4996
4997/**************************************
4998 * 3. Unix-like implementation.
4999 */
5000
5001/*
5002 * Create a new terminal of "rows" by "cols" cells.
5003 * Start job for "cmd".
5004 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005005 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005006 * Return OK or FAIL.
5007 */
5008 static int
5009term_and_job_init(
5010 term_T *term,
5011 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005012 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005013 jobopt_T *opt)
5014{
5015 create_vterm(term, term->tl_rows, term->tl_cols);
5016
Bram Moolenaar13568252018-03-16 20:46:58 +01005017 /* This may change a string in "argvar". */
5018 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005019 if (term->tl_job != NULL)
5020 ++term->tl_job->jv_refcount;
5021
5022 return term->tl_job != NULL
5023 && term->tl_job->jv_channel != NULL
5024 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5025}
5026
5027 static int
5028create_pty_only(term_T *term, jobopt_T *opt)
5029{
5030 create_vterm(term, term->tl_rows, term->tl_cols);
5031
5032 term->tl_job = job_alloc();
5033 if (term->tl_job == NULL)
5034 return FAIL;
5035 ++term->tl_job->jv_refcount;
5036
5037 /* behave like the job is already finished */
5038 term->tl_job->jv_status = JOB_FINISHED;
5039
5040 return mch_create_pty_channel(term->tl_job, opt);
5041}
5042
5043/*
5044 * Free the terminal emulator part of "term".
5045 */
5046 static void
5047term_free_vterm(term_T *term)
5048{
5049 if (term->tl_vterm != NULL)
5050 vterm_free(term->tl_vterm);
5051 term->tl_vterm = NULL;
5052}
5053
5054/*
5055 * Request size to terminal.
5056 */
5057 static void
5058term_report_winsize(term_T *term, int rows, int cols)
5059{
5060 /* Use an ioctl() to report the new window size to the job. */
5061 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5062 {
5063 int fd = -1;
5064 int part;
5065
5066 for (part = PART_OUT; part < PART_COUNT; ++part)
5067 {
5068 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5069 if (isatty(fd))
5070 break;
5071 }
5072 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5073 mch_signal_job(term->tl_job, (char_u *)"winch");
5074 }
5075}
5076
5077# endif
5078
5079#endif /* FEAT_TERMINAL */