blob: 60d0098c504cebca6e72c9864997dcd9dee06cee [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 * - Win32: In the GUI use a terminal emulator for :!cmd.
42 * - Add a way to set the 16 ANSI colors, to be used for 'termguicolors' and in
43 * the GUI.
44 * - Some way for the job running in the terminal to send a :drop command back
45 * to the Vim running the terminal. Should be usable by a simple shell or
46 * python script.
Bram Moolenaarb852c3e2018-03-11 16:55:36 +010047 * - implement term_setsize()
48 * - Copy text in the vterm to the Vim buffer once in a while, so that
49 * completion works.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +010050 * - Adding WinBar to terminal window doesn't display, text isn't shifted down.
Bram Moolenaar46359e12017-11-29 22:33:38 +010051 * a job that uses 16 colors while Vim is using > 256.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020052 * - in GUI vertical split causes problems. Cursor is flickering. (Hirohito
53 * Higashi, 2017 Sep 19)
Bram Moolenaar3a497e12017-09-30 20:40:27 +020054 * - after resizing windows overlap. (Boris Staletic, #2164)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020055 * - Redirecting output does not work on MS-Windows, Test_terminal_redir_file()
56 * is disabled.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020057 * - cursor blinks in terminal on widows with a timer. (xtal8, #2142)
Bram Moolenaarba6febd2017-10-30 21:56:23 +010058 * - Termdebug does not work when Vim build with mzscheme. gdb hangs.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020059 * - MS-Windows GUI: WinBar has tearoff item
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020060 * - MS-Windows GUI: still need to type a key after shell exits? #1924
Bram Moolenaar51b0f372017-11-18 18:52:04 +010061 * - After executing a shell command the status line isn't redraw.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020062 * - add test for giving error for invalid 'termsize' value.
63 * - support minimal size when 'termsize' is "rows*cols".
64 * - support minimal size when 'termsize' is empty?
65 * - GUI: when using tabs, focus in terminal, click on tab does not work.
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020066 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020067 * - For the GUI fill termios with default values, perhaps like pangoterm:
68 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020069 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
70 * conversions.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020071 * - add an optional limit for the scrollback size. When reaching it remove
72 * 10% at the start.
73 */
74
75#include "vim.h"
76
77#if defined(FEAT_TERMINAL) || defined(PROTO)
78
79#ifndef MIN
80# define MIN(x,y) ((x) < (y) ? (x) : (y))
81#endif
82#ifndef MAX
83# define MAX(x,y) ((x) > (y) ? (x) : (y))
84#endif
85
86#include "libvterm/include/vterm.h"
87
88/* This is VTermScreenCell without the characters, thus much smaller. */
89typedef struct {
90 VTermScreenCellAttrs attrs;
91 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010092 VTermColor fg;
93 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020094} cellattr_T;
95
96typedef struct sb_line_S {
97 int sb_cols; /* can differ per line */
98 cellattr_T *sb_cells; /* allocated */
99 cellattr_T sb_fill_attr; /* for short line */
100} sb_line_T;
101
102/* typedef term_T in structs.h */
103struct terminal_S {
104 term_T *tl_next;
105
106 VTerm *tl_vterm;
107 job_T *tl_job;
108 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +0100109#if defined(FEAT_GUI)
110 int tl_system; /* when non-zero used for :!cmd output */
111 int tl_toprow; /* row with first line of system terminal */
112#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200113
114 /* Set when setting the size of a vterm, reset after redrawing. */
115 int tl_vterm_size_changed;
116
117 /* used when tl_job is NULL and only a pty was created */
118 int tl_tty_fd;
119 char_u *tl_tty_in;
120 char_u *tl_tty_out;
121
122 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
123 int tl_channel_closed;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100124 int tl_finish;
125#define TL_FINISH_UNSET NUL
126#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
127#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
128#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200129 char_u *tl_opencmd;
130 char_u *tl_eof_chars;
131
132#ifdef WIN3264
133 void *tl_winpty_config;
134 void *tl_winpty;
135#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100136#if defined(FEAT_SESSION)
137 char_u *tl_command;
138#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100139 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200140
141 /* last known vterm size */
142 int tl_rows;
143 int tl_cols;
144 /* vterm size does not follow window size */
145 int tl_rows_fixed;
146 int tl_cols_fixed;
147
148 char_u *tl_title; /* NULL or allocated */
149 char_u *tl_status_text; /* NULL or allocated */
150
151 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200152 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200153 int tl_dirty_row_end; /* row below last one to update */
154
155 garray_T tl_scrollback;
156 int tl_scrollback_scrolled;
157 cellattr_T tl_default_color;
158
Bram Moolenaard96ff162018-02-18 22:13:29 +0100159 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
160 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
161
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200162 VTermPos tl_cursor_pos;
163 int tl_cursor_visible;
164 int tl_cursor_blink;
165 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
166 char_u *tl_cursor_color; /* NULL or allocated */
167
168 int tl_using_altscreen;
169};
170
171#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
172#define TMODE_LOOP 2 /* CTRL-W N used */
173
174/*
175 * List of all active terminals.
176 */
177static term_T *first_term = NULL;
178
179/* Terminal active in terminal_loop(). */
180static term_T *in_terminal_loop = NULL;
181
182#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
183#define KEY_BUF_LEN 200
184
185/*
186 * Functions with separate implementation for MS-Windows and Unix-like systems.
187 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100188static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200189static int create_pty_only(term_T *term, jobopt_T *opt);
190static void term_report_winsize(term_T *term, int rows, int cols);
191static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100192#ifdef FEAT_GUI
193static void update_system_term(term_T *term);
194#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200195
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100196/* The character that we know (or assume) that the terminal expects for the
197 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200198static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200199
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100200/* "Terminal" highlight group colors. */
201static int term_default_cterm_fg = -1;
202static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200203
Bram Moolenaard317b382018-02-08 22:33:31 +0100204/* Store the last set and the desired cursor properties, so that we only update
205 * them when needed. Doing it unnecessary may result in flicker. */
206static char_u *last_set_cursor_color = (char_u *)"";
207static char_u *desired_cursor_color = (char_u *)"";
208static int last_set_cursor_shape = -1;
209static int desired_cursor_shape = -1;
210static int last_set_cursor_blink = -1;
211static int desired_cursor_blink = -1;
212
213
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200214/**************************************
215 * 1. Generic code for all systems.
216 */
217
218/*
219 * Determine the terminal size from 'termsize' and the current window.
220 * Assumes term->tl_rows and term->tl_cols are zero.
221 */
222 static void
223set_term_and_win_size(term_T *term)
224{
Bram Moolenaar13568252018-03-16 20:46:58 +0100225#ifdef FEAT_GUI
226 if (term->tl_system)
227 {
228 /* Use the whole screen for the system command. However, it will start
229 * at the command line and scroll up as needed, using tl_toprow. */
230 term->tl_rows = Rows;
231 term->tl_cols = Columns;
232 }
233 else
234#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200235 if (*curwin->w_p_tms != NUL)
236 {
237 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
238
239 term->tl_rows = atoi((char *)curwin->w_p_tms);
240 term->tl_cols = atoi((char *)p);
241 }
242 if (term->tl_rows == 0)
243 term->tl_rows = curwin->w_height;
244 else
245 {
246 win_setheight_win(term->tl_rows, curwin);
247 term->tl_rows_fixed = TRUE;
248 }
249 if (term->tl_cols == 0)
250 term->tl_cols = curwin->w_width;
251 else
252 {
253 win_setwidth_win(term->tl_cols, curwin);
254 term->tl_cols_fixed = TRUE;
255 }
256}
257
258/*
259 * Initialize job options for a terminal job.
260 * Caller may overrule some of them.
261 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100262 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200263init_job_options(jobopt_T *opt)
264{
265 clear_job_options(opt);
266
267 opt->jo_mode = MODE_RAW;
268 opt->jo_out_mode = MODE_RAW;
269 opt->jo_err_mode = MODE_RAW;
270 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
271}
272
273/*
274 * Set job options mandatory for a terminal job.
275 */
276 static void
277setup_job_options(jobopt_T *opt, int rows, int cols)
278{
279 if (!(opt->jo_set & JO_OUT_IO))
280 {
281 /* Connect stdout to the terminal. */
282 opt->jo_io[PART_OUT] = JIO_BUFFER;
283 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
284 opt->jo_modifiable[PART_OUT] = 0;
285 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
286 }
287
288 if (!(opt->jo_set & JO_ERR_IO))
289 {
290 /* Connect stderr to the terminal. */
291 opt->jo_io[PART_ERR] = JIO_BUFFER;
292 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
293 opt->jo_modifiable[PART_ERR] = 0;
294 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
295 }
296
297 opt->jo_pty = TRUE;
298 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
299 opt->jo_term_rows = rows;
300 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
301 opt->jo_term_cols = cols;
302}
303
304/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100305 * Close a terminal buffer (and its window). Used when creating the terminal
306 * fails.
307 */
308 static void
309term_close_buffer(buf_T *buf, buf_T *old_curbuf)
310{
311 free_terminal(buf);
312 if (old_curbuf != NULL)
313 {
314 --curbuf->b_nwindows;
315 curbuf = old_curbuf;
316 curwin->w_buffer = curbuf;
317 ++curbuf->b_nwindows;
318 }
319
320 /* Wiping out the buffer will also close the window and call
321 * free_terminal(). */
322 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
323}
324
325/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200326 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100327 * Use either "argvar" or "argv", the other must be NULL.
328 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
329 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200330 * Returns NULL when failed.
331 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100332 buf_T *
333term_start(
334 typval_T *argvar,
335 char **argv,
336 jobopt_T *opt,
337 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200338{
339 exarg_T split_ea;
340 win_T *old_curwin = curwin;
341 term_T *term;
342 buf_T *old_curbuf = NULL;
343 int res;
344 buf_T *newbuf;
345
346 if (check_restricted() || check_secure())
347 return NULL;
348
349 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
350 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
351 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
352 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
353 {
354 EMSG(_(e_invarg));
355 return NULL;
356 }
357
358 term = (term_T *)alloc_clear(sizeof(term_T));
359 if (term == NULL)
360 return NULL;
361 term->tl_dirty_row_end = MAX_ROW;
362 term->tl_cursor_visible = TRUE;
363 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
364 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100365#ifdef FEAT_GUI
366 term->tl_system = (flags & TERM_START_SYSTEM);
367#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200368 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
369
370 vim_memset(&split_ea, 0, sizeof(split_ea));
371 if (opt->jo_curwin)
372 {
373 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100374 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200375 {
376 no_write_message();
377 vim_free(term);
378 return NULL;
379 }
380 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100381 ECMD_HIDE
382 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
383 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200384 {
385 vim_free(term);
386 return NULL;
387 }
388 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100389 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200390 {
391 buf_T *buf;
392
393 /* Create a new buffer without a window. Make it the current buffer for
394 * a moment to be able to do the initialisations. */
395 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
396 BLN_NEW | BLN_LISTED);
397 if (buf == NULL || ml_open(buf) == FAIL)
398 {
399 vim_free(term);
400 return NULL;
401 }
402 old_curbuf = curbuf;
403 --curbuf->b_nwindows;
404 curbuf = buf;
405 curwin->w_buffer = buf;
406 ++curbuf->b_nwindows;
407 }
408 else
409 {
410 /* Open a new window or tab. */
411 split_ea.cmdidx = CMD_new;
412 split_ea.cmd = (char_u *)"new";
413 split_ea.arg = (char_u *)"";
414 if (opt->jo_term_rows > 0 && !(cmdmod.split & WSP_VERT))
415 {
416 split_ea.line2 = opt->jo_term_rows;
417 split_ea.addr_count = 1;
418 }
419 if (opt->jo_term_cols > 0 && (cmdmod.split & WSP_VERT))
420 {
421 split_ea.line2 = opt->jo_term_cols;
422 split_ea.addr_count = 1;
423 }
424
425 ex_splitview(&split_ea);
426 if (curwin == old_curwin)
427 {
428 /* split failed */
429 vim_free(term);
430 return NULL;
431 }
432 }
433 term->tl_buffer = curbuf;
434 curbuf->b_term = term;
435
436 if (!opt->jo_hidden)
437 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100438 /* Only one size was taken care of with :new, do the other one. With
439 * "curwin" both need to be done. */
440 if (opt->jo_term_rows > 0 && (opt->jo_curwin
441 || (cmdmod.split & WSP_VERT)))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200442 win_setheight(opt->jo_term_rows);
Bram Moolenaarda650582018-02-20 15:51:40 +0100443 if (opt->jo_term_cols > 0 && (opt->jo_curwin
444 || !(cmdmod.split & WSP_VERT)))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200445 win_setwidth(opt->jo_term_cols);
446 }
447
448 /* Link the new terminal in the list of active terminals. */
449 term->tl_next = first_term;
450 first_term = term;
451
452 if (opt->jo_term_name != NULL)
453 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100454 else if (argv != NULL)
455 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200456 else
457 {
458 int i;
459 size_t len;
460 char_u *cmd, *p;
461
462 if (argvar->v_type == VAR_STRING)
463 {
464 cmd = argvar->vval.v_string;
465 if (cmd == NULL)
466 cmd = (char_u *)"";
467 else if (STRCMP(cmd, "NONE") == 0)
468 cmd = (char_u *)"pty";
469 }
470 else if (argvar->v_type != VAR_LIST
471 || argvar->vval.v_list == NULL
472 || argvar->vval.v_list->lv_len < 1
473 || (cmd = get_tv_string_chk(
474 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
475 cmd = (char_u*)"";
476
477 len = STRLEN(cmd) + 10;
478 p = alloc((int)len);
479
480 for (i = 0; p != NULL; ++i)
481 {
482 /* Prepend a ! to the command name to avoid the buffer name equals
483 * the executable, otherwise ":w!" would overwrite it. */
484 if (i == 0)
485 vim_snprintf((char *)p, len, "!%s", cmd);
486 else
487 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
488 if (buflist_findname(p) == NULL)
489 {
490 vim_free(curbuf->b_ffname);
491 curbuf->b_ffname = p;
492 break;
493 }
494 }
495 }
496 curbuf->b_fname = curbuf->b_ffname;
497
498 if (opt->jo_term_opencmd != NULL)
499 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
500
501 if (opt->jo_eof_chars != NULL)
502 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
503
504 set_string_option_direct((char_u *)"buftype", -1,
505 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
506
507 /* Mark the buffer as not modifiable. It can only be made modifiable after
508 * the job finished. */
509 curbuf->b_p_ma = FALSE;
510
511 set_term_and_win_size(term);
512 setup_job_options(opt, term->tl_rows, term->tl_cols);
513
Bram Moolenaar13568252018-03-16 20:46:58 +0100514 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100515 return curbuf;
516
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100517#if defined(FEAT_SESSION)
518 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100519 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100520 {
521 term->tl_command = vim_strsave((char_u *)"NONE");
522 }
523 else if (argvar->v_type == VAR_STRING)
524 {
525 char_u *cmd = argvar->vval.v_string;
526
527 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
528 term->tl_command = vim_strsave(cmd);
529 }
530 else if (argvar->v_type == VAR_LIST
531 && argvar->vval.v_list != NULL
532 && argvar->vval.v_list->lv_len > 0)
533 {
534 garray_T ga;
535 listitem_T *item;
536
537 ga_init2(&ga, 1, 100);
538 for (item = argvar->vval.v_list->lv_first;
539 item != NULL; item = item->li_next)
540 {
541 char_u *s = get_tv_string_chk(&item->li_tv);
542 char_u *p;
543
544 if (s == NULL)
545 break;
546 p = vim_strsave_fnameescape(s, FALSE);
547 if (p == NULL)
548 break;
549 ga_concat(&ga, p);
550 vim_free(p);
551 ga_append(&ga, ' ');
552 }
553 if (item == NULL)
554 {
555 ga_append(&ga, NUL);
556 term->tl_command = ga.ga_data;
557 }
558 else
559 ga_clear(&ga);
560 }
561#endif
562
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100563 if (opt->jo_term_kill != NULL)
564 {
565 char_u *p = skiptowhite(opt->jo_term_kill);
566
567 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
568 }
569
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200570 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100571 if (argv == NULL
572 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200573 && argvar->vval.v_string != NULL
574 && STRCMP(argvar->vval.v_string, "NONE") == 0)
575 res = create_pty_only(term, opt);
576 else
Bram Moolenaar13568252018-03-16 20:46:58 +0100577 res = term_and_job_init(term, argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200578
579 newbuf = curbuf;
580 if (res == OK)
581 {
582 /* Get and remember the size we ended up with. Update the pty. */
583 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
584 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100585#ifdef FEAT_GUI
586 if (term->tl_system)
587 {
588 /* display first line below typed command */
589 term->tl_toprow = msg_row + 1;
590 term->tl_dirty_row_end = 0;
591 }
592#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200593
594 /* Make sure we don't get stuck on sending keys to the job, it leads to
595 * a deadlock if the job is waiting for Vim to read. */
596 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
597
Bram Moolenaar13568252018-03-16 20:46:58 +0100598 if (old_curbuf == NULL)
Bram Moolenaarab5e7c32018-02-13 14:07:18 +0100599 {
600 ++curbuf->b_locked;
601 apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
602 --curbuf->b_locked;
603 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100604 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200605 {
606 --curbuf->b_nwindows;
607 curbuf = old_curbuf;
608 curwin->w_buffer = curbuf;
609 ++curbuf->b_nwindows;
610 }
611 }
612 else
613 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100614 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200615 return NULL;
616 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100617
Bram Moolenaar13568252018-03-16 20:46:58 +0100618 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200619 return newbuf;
620}
621
622/*
623 * ":terminal": open a terminal window and execute a job in it.
624 */
625 void
626ex_terminal(exarg_T *eap)
627{
628 typval_T argvar[2];
629 jobopt_T opt;
630 char_u *cmd;
631 char_u *tofree = NULL;
632
633 init_job_options(&opt);
634
635 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100636 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200637 {
638 char_u *p, *ep;
639
640 cmd += 2;
641 p = skiptowhite(cmd);
642 ep = vim_strchr(cmd, '=');
643 if (ep != NULL && ep < p)
644 p = ep;
645
646 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
647 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100648 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
649 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200650 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
651 opt.jo_term_finish = 'o';
652 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
653 opt.jo_curwin = 1;
654 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
655 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100656 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
657 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100658 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
659 && ep != NULL)
660 {
661 opt.jo_set2 |= JO2_TERM_KILL;
662 opt.jo_term_kill = ep + 1;
663 p = skiptowhite(cmd);
664 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200665 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
666 && ep != NULL && isdigit(ep[1]))
667 {
668 opt.jo_set2 |= JO2_TERM_ROWS;
669 opt.jo_term_rows = atoi((char *)ep + 1);
670 p = skiptowhite(cmd);
671 }
672 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
673 && ep != NULL && isdigit(ep[1]))
674 {
675 opt.jo_set2 |= JO2_TERM_COLS;
676 opt.jo_term_cols = atoi((char *)ep + 1);
677 p = skiptowhite(cmd);
678 }
679 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
680 && ep != NULL)
681 {
682 char_u *buf = NULL;
683 char_u *keys;
684
685 p = skiptowhite(cmd);
686 *p = NUL;
687 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
688 opt.jo_set2 |= JO2_EOF_CHARS;
689 opt.jo_eof_chars = vim_strsave(keys);
690 vim_free(buf);
691 *p = ' ';
692 }
693 else
694 {
695 if (*p)
696 *p = NUL;
697 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100698 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200699 }
700 cmd = skipwhite(p);
701 }
702 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100703 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200704 /* Make a copy of 'shell', an autocommand may change the option. */
705 tofree = cmd = vim_strsave(p_sh);
706
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100707 /* default to close when the shell exits */
708 if (opt.jo_term_finish == NUL)
709 opt.jo_term_finish = 'c';
710 }
711
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200712 if (eap->addr_count > 0)
713 {
714 /* Write lines from current buffer to the job. */
715 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
716 opt.jo_io[PART_IN] = JIO_BUFFER;
717 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
718 opt.jo_in_top = eap->line1;
719 opt.jo_in_bot = eap->line2;
720 }
721
722 argvar[0].v_type = VAR_STRING;
723 argvar[0].vval.v_string = cmd;
724 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100725 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200726 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100727
728theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200729 vim_free(opt.jo_eof_chars);
730}
731
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100732#if defined(FEAT_SESSION) || defined(PROTO)
733/*
734 * Write a :terminal command to the session file to restore the terminal in
735 * window "wp".
736 * Return FAIL if writing fails.
737 */
738 int
739term_write_session(FILE *fd, win_T *wp)
740{
741 term_T *term = wp->w_buffer->b_term;
742
743 /* Create the terminal and run the command. This is not without
744 * risk, but let's assume the user only creates a session when this
745 * will be OK. */
746 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
747 term->tl_cols, term->tl_rows) < 0)
748 return FAIL;
749 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
750 return FAIL;
751
752 return put_eol(fd);
753}
754
755/*
756 * Return TRUE if "buf" has a terminal that should be restored.
757 */
758 int
759term_should_restore(buf_T *buf)
760{
761 term_T *term = buf->b_term;
762
763 return term != NULL && (term->tl_command == NULL
764 || STRCMP(term->tl_command, "NONE") != 0);
765}
766#endif
767
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200768/*
769 * Free the scrollback buffer for "term".
770 */
771 static void
772free_scrollback(term_T *term)
773{
774 int i;
775
776 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
777 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
778 ga_clear(&term->tl_scrollback);
779}
780
781/*
782 * Free a terminal and everything it refers to.
783 * Kills the job if there is one.
784 * Called when wiping out a buffer.
785 */
786 void
787free_terminal(buf_T *buf)
788{
789 term_T *term = buf->b_term;
790 term_T *tp;
791
792 if (term == NULL)
793 return;
794 if (first_term == term)
795 first_term = term->tl_next;
796 else
797 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
798 if (tp->tl_next == term)
799 {
800 tp->tl_next = term->tl_next;
801 break;
802 }
803
804 if (term->tl_job != NULL)
805 {
806 if (term->tl_job->jv_status != JOB_ENDED
807 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100808 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200809 job_stop(term->tl_job, NULL, "kill");
810 job_unref(term->tl_job);
811 }
812
813 free_scrollback(term);
814
815 term_free_vterm(term);
816 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100817#ifdef FEAT_SESSION
818 vim_free(term->tl_command);
819#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100820 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200821 vim_free(term->tl_status_text);
822 vim_free(term->tl_opencmd);
823 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100824 if (desired_cursor_color == term->tl_cursor_color)
825 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200826 vim_free(term->tl_cursor_color);
827 vim_free(term);
828 buf->b_term = NULL;
829 if (in_terminal_loop == term)
830 in_terminal_loop = NULL;
831}
832
833/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100834 * Get the part that is connected to the tty. Normally this is PART_IN, but
835 * when writing buffer lines to the job it can be another. This makes it
836 * possible to do "1,5term vim -".
837 */
838 static ch_part_T
839get_tty_part(term_T *term)
840{
841#ifdef UNIX
842 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
843 int i;
844
845 for (i = 0; i < 3; ++i)
846 {
847 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
848
849 if (isatty(fd))
850 return parts[i];
851 }
852#endif
853 return PART_IN;
854}
855
856/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200857 * Write job output "msg[len]" to the vterm.
858 */
859 static void
860term_write_job_output(term_T *term, char_u *msg, size_t len)
861{
862 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100863 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200864
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100865 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200866
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100867 /* flush vterm buffer when vterm responded to control sequence */
868 if (prevlen != vterm_output_get_buffer_current(vterm))
869 {
870 char buf[KEY_BUF_LEN];
871 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
872
873 if (curlen > 0)
874 channel_send(term->tl_job->jv_channel, get_tty_part(term),
875 (char_u *)buf, (int)curlen, NULL);
876 }
877
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200878 /* this invokes the damage callbacks */
879 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
880}
881
882 static void
883update_cursor(term_T *term, int redraw)
884{
885 if (term->tl_normal_mode)
886 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100887#ifdef FEAT_GUI
888 if (term->tl_system)
889 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
890 term->tl_cursor_pos.col);
891 else
892#endif
893 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200894 if (redraw)
895 {
896 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
897 cursor_on();
898 out_flush();
899#ifdef FEAT_GUI
900 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100901 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200902 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100903 gui_mch_flush();
904 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200905#endif
906 }
907}
908
909/*
910 * Invoked when "msg" output from a job was received. Write it to the terminal
911 * of "buffer".
912 */
913 void
914write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
915{
916 size_t len = STRLEN(msg);
917 term_T *term = buffer->b_term;
918
919 if (term->tl_vterm == NULL)
920 {
921 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
922 return;
923 }
924 ch_log(channel, "writing %d bytes to terminal", (int)len);
925 term_write_job_output(term, msg, len);
926
Bram Moolenaar13568252018-03-16 20:46:58 +0100927#ifdef FEAT_GUI
928 if (term->tl_system)
929 {
930 /* show system output, scrolling up the screen as needed */
931 update_system_term(term);
932 update_cursor(term, TRUE);
933 }
934 else
935#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200936 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
937 * contents, thus no screen update is needed. */
938 if (!term->tl_normal_mode)
939 {
940 /* TODO: only update once in a while. */
941 ch_log(term->tl_job->jv_channel, "updating screen");
942 if (buffer == curbuf)
943 {
944 update_screen(0);
945 update_cursor(term, TRUE);
946 }
947 else
948 redraw_after_callback(TRUE);
949 }
950}
951
952/*
953 * Send a mouse position and click to the vterm
954 */
955 static int
956term_send_mouse(VTerm *vterm, int button, int pressed)
957{
958 VTermModifier mod = VTERM_MOD_NONE;
959
960 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200961 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100962 if (button != 0)
963 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200964 return TRUE;
965}
966
Bram Moolenaarc48369c2018-03-11 19:30:45 +0100967static int enter_mouse_col = -1;
968static int enter_mouse_row = -1;
969
970/*
971 * Handle a mouse click, drag or release.
972 * Return TRUE when a mouse event is sent to the terminal.
973 */
974 static int
975term_mouse_click(VTerm *vterm, int key)
976{
977#if defined(FEAT_CLIPBOARD)
978 /* For modeless selection mouse drag and release events are ignored, unless
979 * they are preceded with a mouse down event */
980 static int ignore_drag_release = TRUE;
981 VTermMouseState mouse_state;
982
983 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
984 if (mouse_state.flags == 0)
985 {
986 /* Terminal is not using the mouse, use modeless selection. */
987 switch (key)
988 {
989 case K_LEFTDRAG:
990 case K_LEFTRELEASE:
991 case K_RIGHTDRAG:
992 case K_RIGHTRELEASE:
993 /* Ignore drag and release events when the button-down wasn't
994 * seen before. */
995 if (ignore_drag_release)
996 {
997 int save_mouse_col, save_mouse_row;
998
999 if (enter_mouse_col < 0)
1000 break;
1001
1002 /* mouse click in the window gave us focus, handle that
1003 * click now */
1004 save_mouse_col = mouse_col;
1005 save_mouse_row = mouse_row;
1006 mouse_col = enter_mouse_col;
1007 mouse_row = enter_mouse_row;
1008 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1009 mouse_col = save_mouse_col;
1010 mouse_row = save_mouse_row;
1011 }
1012 /* FALLTHROUGH */
1013 case K_LEFTMOUSE:
1014 case K_RIGHTMOUSE:
1015 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1016 ignore_drag_release = TRUE;
1017 else
1018 ignore_drag_release = FALSE;
1019 /* Should we call mouse_has() here? */
1020 if (clip_star.available)
1021 {
1022 int button, is_click, is_drag;
1023
1024 button = get_mouse_button(KEY2TERMCAP1(key),
1025 &is_click, &is_drag);
1026 if (mouse_model_popup() && button == MOUSE_LEFT
1027 && (mod_mask & MOD_MASK_SHIFT))
1028 {
1029 /* Translate shift-left to right button. */
1030 button = MOUSE_RIGHT;
1031 mod_mask &= ~MOD_MASK_SHIFT;
1032 }
1033 clip_modeless(button, is_click, is_drag);
1034 }
1035 break;
1036
1037 case K_MIDDLEMOUSE:
1038 if (clip_star.available)
1039 insert_reg('*', TRUE);
1040 break;
1041 }
1042 enter_mouse_col = -1;
1043 return FALSE;
1044 }
1045#endif
1046 enter_mouse_col = -1;
1047
1048 switch (key)
1049 {
1050 case K_LEFTMOUSE:
1051 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1052 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1053 case K_LEFTRELEASE:
1054 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1055 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1056 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1057 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1058 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1059 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1060 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1061 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1062 }
1063 return TRUE;
1064}
1065
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001066/*
1067 * Convert typed key "c" into bytes to send to the job.
1068 * Return the number of bytes in "buf".
1069 */
1070 static int
1071term_convert_key(term_T *term, int c, char *buf)
1072{
1073 VTerm *vterm = term->tl_vterm;
1074 VTermKey key = VTERM_KEY_NONE;
1075 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001076 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001077
1078 switch (c)
1079 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001080 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1081
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001082 /* don't use VTERM_KEY_BACKSPACE, it always
1083 * becomes 0x7f DEL */
1084 case K_BS: c = term_backspace_char; break;
1085
1086 case ESC: key = VTERM_KEY_ESCAPE; break;
1087 case K_DEL: key = VTERM_KEY_DEL; break;
1088 case K_DOWN: key = VTERM_KEY_DOWN; break;
1089 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1090 key = VTERM_KEY_DOWN; break;
1091 case K_END: key = VTERM_KEY_END; break;
1092 case K_S_END: mod = VTERM_MOD_SHIFT;
1093 key = VTERM_KEY_END; break;
1094 case K_C_END: mod = VTERM_MOD_CTRL;
1095 key = VTERM_KEY_END; break;
1096 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1097 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1098 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1099 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1100 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1101 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1102 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1103 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1104 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1105 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1106 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1107 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1108 case K_HOME: key = VTERM_KEY_HOME; break;
1109 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1110 key = VTERM_KEY_HOME; break;
1111 case K_C_HOME: mod = VTERM_MOD_CTRL;
1112 key = VTERM_KEY_HOME; break;
1113 case K_INS: key = VTERM_KEY_INS; break;
1114 case K_K0: key = VTERM_KEY_KP_0; break;
1115 case K_K1: key = VTERM_KEY_KP_1; break;
1116 case K_K2: key = VTERM_KEY_KP_2; break;
1117 case K_K3: key = VTERM_KEY_KP_3; break;
1118 case K_K4: key = VTERM_KEY_KP_4; break;
1119 case K_K5: key = VTERM_KEY_KP_5; break;
1120 case K_K6: key = VTERM_KEY_KP_6; break;
1121 case K_K7: key = VTERM_KEY_KP_7; break;
1122 case K_K8: key = VTERM_KEY_KP_8; break;
1123 case K_K9: key = VTERM_KEY_KP_9; break;
1124 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1125 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1126 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1127 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1128 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1129 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1130 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1131 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1132 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1133 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1134 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1135 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1136 case K_LEFT: key = VTERM_KEY_LEFT; break;
1137 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1138 key = VTERM_KEY_LEFT; break;
1139 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1140 key = VTERM_KEY_LEFT; break;
1141 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1142 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1143 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1144 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1145 key = VTERM_KEY_RIGHT; break;
1146 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1147 key = VTERM_KEY_RIGHT; break;
1148 case K_UP: key = VTERM_KEY_UP; break;
1149 case K_S_UP: mod = VTERM_MOD_SHIFT;
1150 key = VTERM_KEY_UP; break;
1151 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001152 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1153 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001154
Bram Moolenaara42ad572017-11-16 13:08:04 +01001155 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1156 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001157 case K_MOUSELEFT: /* TODO */ return 0;
1158 case K_MOUSERIGHT: /* TODO */ return 0;
1159
1160 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001161 case K_LEFTMOUSE_NM:
1162 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001163 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001164 case K_LEFTRELEASE_NM:
1165 case K_MOUSEMOVE:
1166 case K_MIDDLEMOUSE:
1167 case K_MIDDLEDRAG:
1168 case K_MIDDLERELEASE:
1169 case K_RIGHTMOUSE:
1170 case K_RIGHTDRAG:
1171 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1172 return 0;
1173 other = TRUE;
1174 break;
1175
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001176 case K_X1MOUSE: /* TODO */ return 0;
1177 case K_X1DRAG: /* TODO */ return 0;
1178 case K_X1RELEASE: /* TODO */ return 0;
1179 case K_X2MOUSE: /* TODO */ return 0;
1180 case K_X2DRAG: /* TODO */ return 0;
1181 case K_X2RELEASE: /* TODO */ return 0;
1182
1183 case K_IGNORE: return 0;
1184 case K_NOP: return 0;
1185 case K_UNDO: return 0;
1186 case K_HELP: return 0;
1187 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1188 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1189 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1190 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1191 case K_SELECT: return 0;
1192#ifdef FEAT_GUI
1193 case K_VER_SCROLLBAR: return 0;
1194 case K_HOR_SCROLLBAR: return 0;
1195#endif
1196#ifdef FEAT_GUI_TABLINE
1197 case K_TABLINE: return 0;
1198 case K_TABMENU: return 0;
1199#endif
1200#ifdef FEAT_NETBEANS_INTG
1201 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1202#endif
1203#ifdef FEAT_DND
1204 case K_DROP: return 0;
1205#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001206 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001207 case K_PS: vterm_keyboard_start_paste(vterm);
1208 other = TRUE;
1209 break;
1210 case K_PE: vterm_keyboard_end_paste(vterm);
1211 other = TRUE;
1212 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001213 }
1214
1215 /*
1216 * Convert special keys to vterm keys:
1217 * - Write keys to vterm: vterm_keyboard_key()
1218 * - Write output to channel.
1219 * TODO: use mod_mask
1220 */
1221 if (key != VTERM_KEY_NONE)
1222 /* Special key, let vterm convert it. */
1223 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001224 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001225 /* Normal character, let vterm convert it. */
1226 vterm_keyboard_unichar(vterm, c, mod);
1227
1228 /* Read back the converted escape sequence. */
1229 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1230}
1231
1232/*
1233 * Return TRUE if the job for "term" is still running.
1234 */
1235 int
1236term_job_running(term_T *term)
1237{
1238 /* Also consider the job finished when the channel is closed, to avoid a
1239 * race condition when updating the title. */
1240 return term != NULL
1241 && term->tl_job != NULL
1242 && channel_is_open(term->tl_job->jv_channel)
1243 && (term->tl_job->jv_status == JOB_STARTED
1244 || term->tl_job->jv_channel->ch_keep_open);
1245}
1246
1247/*
1248 * Return TRUE if "term" has an active channel and used ":term NONE".
1249 */
1250 int
1251term_none_open(term_T *term)
1252{
1253 /* Also consider the job finished when the channel is closed, to avoid a
1254 * race condition when updating the title. */
1255 return term != NULL
1256 && term->tl_job != NULL
1257 && channel_is_open(term->tl_job->jv_channel)
1258 && term->tl_job->jv_channel->ch_keep_open;
1259}
1260
1261/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001262 * Used when exiting: kill the job in "buf" if so desired.
1263 * Return OK when the job finished.
1264 * Return FAIL when the job is still running.
1265 */
1266 int
1267term_try_stop_job(buf_T *buf)
1268{
1269 int count;
1270 char *how = (char *)buf->b_term->tl_kill;
1271
1272#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1273 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1274 {
1275 char_u buff[DIALOG_MSG_SIZE];
1276 int ret;
1277
1278 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1279 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1280 if (ret == VIM_YES)
1281 how = "kill";
1282 else if (ret == VIM_CANCEL)
1283 return FAIL;
1284 }
1285#endif
1286 if (how == NULL || *how == NUL)
1287 return FAIL;
1288
1289 job_stop(buf->b_term->tl_job, NULL, how);
1290
1291 /* wait for up to a second for the job to die */
1292 for (count = 0; count < 100; ++count)
1293 {
1294 /* buffer, terminal and job may be cleaned up while waiting */
1295 if (!buf_valid(buf)
1296 || buf->b_term == NULL
1297 || buf->b_term->tl_job == NULL)
1298 return OK;
1299
1300 /* call job_status() to update jv_status */
1301 job_status(buf->b_term->tl_job);
1302 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1303 return OK;
1304 ui_delay(10L, FALSE);
1305 mch_check_messages();
1306 parse_queued_messages();
1307 }
1308 return FAIL;
1309}
1310
1311/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001312 * Add the last line of the scrollback buffer to the buffer in the window.
1313 */
1314 static void
1315add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1316{
1317 buf_T *buf = term->tl_buffer;
1318 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1319 linenr_T lnum = buf->b_ml.ml_line_count;
1320
1321#ifdef WIN3264
1322 if (!enc_utf8 && enc_codepage > 0)
1323 {
1324 WCHAR *ret = NULL;
1325 int length = 0;
1326
1327 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1328 &ret, &length);
1329 if (ret != NULL)
1330 {
1331 WideCharToMultiByte_alloc(enc_codepage, 0,
1332 ret, length, (char **)&text, &len, 0, 0);
1333 vim_free(ret);
1334 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1335 vim_free(text);
1336 }
1337 }
1338 else
1339#endif
1340 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1341 if (empty)
1342 {
1343 /* Delete the empty line that was in the empty buffer. */
1344 curbuf = buf;
1345 ml_delete(1, FALSE);
1346 curbuf = curwin->w_buffer;
1347 }
1348}
1349
1350 static void
1351cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1352{
1353 attr->width = cell->width;
1354 attr->attrs = cell->attrs;
1355 attr->fg = cell->fg;
1356 attr->bg = cell->bg;
1357}
1358
1359 static int
1360equal_celattr(cellattr_T *a, cellattr_T *b)
1361{
1362 /* Comparing the colors should be sufficient. */
1363 return a->fg.red == b->fg.red
1364 && a->fg.green == b->fg.green
1365 && a->fg.blue == b->fg.blue
1366 && a->bg.red == b->bg.red
1367 && a->bg.green == b->bg.green
1368 && a->bg.blue == b->bg.blue;
1369}
1370
Bram Moolenaard96ff162018-02-18 22:13:29 +01001371/*
1372 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1373 * line at this position. Otherwise at the end.
1374 */
1375 static int
1376add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1377{
1378 if (ga_grow(&term->tl_scrollback, 1) == OK)
1379 {
1380 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1381 + term->tl_scrollback.ga_len;
1382
1383 if (lnum > 0)
1384 {
1385 int i;
1386
1387 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1388 {
1389 *line = *(line - 1);
1390 --line;
1391 }
1392 }
1393 line->sb_cols = 0;
1394 line->sb_cells = NULL;
1395 line->sb_fill_attr = *fill_attr;
1396 ++term->tl_scrollback.ga_len;
1397 return OK;
1398 }
1399 return FALSE;
1400}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001401
1402/*
1403 * Add the current lines of the terminal to scrollback and to the buffer.
1404 * Called after the job has ended and when switching to Terminal-Normal mode.
1405 */
1406 static void
1407move_terminal_to_buffer(term_T *term)
1408{
1409 win_T *wp;
1410 int len;
1411 int lines_skipped = 0;
1412 VTermPos pos;
1413 VTermScreenCell cell;
1414 cellattr_T fill_attr, new_fill_attr;
1415 cellattr_T *p;
1416 VTermScreen *screen;
1417
1418 if (term->tl_vterm == NULL)
1419 return;
1420 screen = vterm_obtain_screen(term->tl_vterm);
1421 fill_attr = new_fill_attr = term->tl_default_color;
1422
1423 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1424 {
1425 len = 0;
1426 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1427 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1428 && cell.chars[0] != NUL)
1429 {
1430 len = pos.col + 1;
1431 new_fill_attr = term->tl_default_color;
1432 }
1433 else
1434 /* Assume the last attr is the filler attr. */
1435 cell2cellattr(&cell, &new_fill_attr);
1436
1437 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1438 ++lines_skipped;
1439 else
1440 {
1441 while (lines_skipped > 0)
1442 {
1443 /* Line was skipped, add an empty line. */
1444 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001445 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001446 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001447 }
1448
1449 if (len == 0)
1450 p = NULL;
1451 else
1452 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1453 if ((p != NULL || len == 0)
1454 && ga_grow(&term->tl_scrollback, 1) == OK)
1455 {
1456 garray_T ga;
1457 int width;
1458 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1459 + term->tl_scrollback.ga_len;
1460
1461 ga_init2(&ga, 1, 100);
1462 for (pos.col = 0; pos.col < len; pos.col += width)
1463 {
1464 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1465 {
1466 width = 1;
1467 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1468 if (ga_grow(&ga, 1) == OK)
1469 ga.ga_len += utf_char2bytes(' ',
1470 (char_u *)ga.ga_data + ga.ga_len);
1471 }
1472 else
1473 {
1474 width = cell.width;
1475
1476 cell2cellattr(&cell, &p[pos.col]);
1477
1478 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1479 {
1480 int i;
1481 int c;
1482
1483 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1484 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1485 (char_u *)ga.ga_data + ga.ga_len);
1486 }
1487 }
1488 }
1489 line->sb_cols = len;
1490 line->sb_cells = p;
1491 line->sb_fill_attr = new_fill_attr;
1492 fill_attr = new_fill_attr;
1493 ++term->tl_scrollback.ga_len;
1494
1495 if (ga_grow(&ga, 1) == FAIL)
1496 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1497 else
1498 {
1499 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1500 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1501 }
1502 ga_clear(&ga);
1503 }
1504 else
1505 vim_free(p);
1506 }
1507 }
1508
1509 /* Obtain the current background color. */
1510 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1511 &term->tl_default_color.fg, &term->tl_default_color.bg);
1512
1513 FOR_ALL_WINDOWS(wp)
1514 {
1515 if (wp->w_buffer == term->tl_buffer)
1516 {
1517 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1518 wp->w_cursor.col = 0;
1519 wp->w_valid = 0;
1520 if (wp->w_cursor.lnum >= wp->w_height)
1521 {
1522 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1523
1524 if (wp->w_topline < min_topline)
1525 wp->w_topline = min_topline;
1526 }
1527 redraw_win_later(wp, NOT_VALID);
1528 }
1529 }
1530}
1531
1532 static void
1533set_terminal_mode(term_T *term, int normal_mode)
1534{
1535 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001536 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001537 if (term->tl_buffer == curbuf)
1538 maketitle();
1539}
1540
1541/*
1542 * Called after the job if finished and Terminal mode is not active:
1543 * Move the vterm contents into the scrollback buffer and free the vterm.
1544 */
1545 static void
1546cleanup_vterm(term_T *term)
1547{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001548 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001549 move_terminal_to_buffer(term);
1550 term_free_vterm(term);
1551 set_terminal_mode(term, FALSE);
1552}
1553
1554/*
1555 * Switch from Terminal-Job mode to Terminal-Normal mode.
1556 * Suspends updating the terminal window.
1557 */
1558 static void
1559term_enter_normal_mode(void)
1560{
1561 term_T *term = curbuf->b_term;
1562
1563 /* Append the current terminal contents to the buffer. */
1564 move_terminal_to_buffer(term);
1565
1566 set_terminal_mode(term, TRUE);
1567
1568 /* Move the window cursor to the position of the cursor in the
1569 * terminal. */
1570 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1571 + term->tl_cursor_pos.row + 1;
1572 check_cursor();
1573 coladvance(term->tl_cursor_pos.col);
1574
1575 /* Display the same lines as in the terminal. */
1576 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1577}
1578
1579/*
1580 * Returns TRUE if the current window contains a terminal and we are in
1581 * Terminal-Normal mode.
1582 */
1583 int
1584term_in_normal_mode(void)
1585{
1586 term_T *term = curbuf->b_term;
1587
1588 return term != NULL && term->tl_normal_mode;
1589}
1590
1591/*
1592 * Switch from Terminal-Normal mode to Terminal-Job mode.
1593 * Restores updating the terminal window.
1594 */
1595 void
1596term_enter_job_mode()
1597{
1598 term_T *term = curbuf->b_term;
1599 sb_line_T *line;
1600 garray_T *gap;
1601
1602 /* Remove the terminal contents from the scrollback and the buffer. */
1603 gap = &term->tl_scrollback;
1604 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1605 && gap->ga_len > 0)
1606 {
1607 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1608 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1609 vim_free(line->sb_cells);
1610 --gap->ga_len;
1611 }
1612 check_cursor();
1613
1614 set_terminal_mode(term, FALSE);
1615
1616 if (term->tl_channel_closed)
1617 cleanup_vterm(term);
1618 redraw_buf_and_status_later(curbuf, NOT_VALID);
1619}
1620
1621/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001622 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001623 * Note: while waiting a terminal may be closed and freed if the channel is
1624 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001625 */
1626 static int
1627term_vgetc()
1628{
1629 int c;
1630 int save_State = State;
1631
1632 State = TERMINAL;
1633 got_int = FALSE;
1634#ifdef WIN3264
1635 ctrl_break_was_pressed = FALSE;
1636#endif
1637 c = vgetc();
1638 got_int = FALSE;
1639 State = save_State;
1640 return c;
1641}
1642
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001643static int mouse_was_outside = FALSE;
1644
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001645/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001646 * Send keys to terminal.
1647 * Return FAIL when the key needs to be handled in Normal mode.
1648 * Return OK when the key was dropped or sent to the terminal.
1649 */
1650 int
1651send_keys_to_term(term_T *term, int c, int typed)
1652{
1653 char msg[KEY_BUF_LEN];
1654 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001655 int dragging_outside = FALSE;
1656
1657 /* Catch keys that need to be handled as in Normal mode. */
1658 switch (c)
1659 {
1660 case NUL:
1661 case K_ZERO:
1662 if (typed)
1663 stuffcharReadbuff(c);
1664 return FAIL;
1665
1666 case K_IGNORE:
1667 return FAIL;
1668
1669 case K_LEFTDRAG:
1670 case K_MIDDLEDRAG:
1671 case K_RIGHTDRAG:
1672 case K_X1DRAG:
1673 case K_X2DRAG:
1674 dragging_outside = mouse_was_outside;
1675 /* FALLTHROUGH */
1676 case K_LEFTMOUSE:
1677 case K_LEFTMOUSE_NM:
1678 case K_LEFTRELEASE:
1679 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001680 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001681 case K_MIDDLEMOUSE:
1682 case K_MIDDLERELEASE:
1683 case K_RIGHTMOUSE:
1684 case K_RIGHTRELEASE:
1685 case K_X1MOUSE:
1686 case K_X1RELEASE:
1687 case K_X2MOUSE:
1688 case K_X2RELEASE:
1689
1690 case K_MOUSEUP:
1691 case K_MOUSEDOWN:
1692 case K_MOUSELEFT:
1693 case K_MOUSERIGHT:
1694 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001695 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001696 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001697 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001698 || dragging_outside)
1699 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001700 /* click or scroll outside the current window or on status line
1701 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001702 if (typed)
1703 {
1704 stuffcharReadbuff(c);
1705 mouse_was_outside = TRUE;
1706 }
1707 return FAIL;
1708 }
1709 }
1710 if (typed)
1711 mouse_was_outside = FALSE;
1712
1713 /* Convert the typed key to a sequence of bytes for the job. */
1714 len = term_convert_key(term, c, msg);
1715 if (len > 0)
1716 /* TODO: if FAIL is returned, stop? */
1717 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1718 (char_u *)msg, (int)len, NULL);
1719
1720 return OK;
1721}
1722
1723 static void
1724position_cursor(win_T *wp, VTermPos *pos)
1725{
1726 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1727 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1728 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1729}
1730
1731/*
1732 * Handle CTRL-W "": send register contents to the job.
1733 */
1734 static void
1735term_paste_register(int prev_c UNUSED)
1736{
1737 int c;
1738 list_T *l;
1739 listitem_T *item;
1740 long reglen = 0;
1741 int type;
1742
1743#ifdef FEAT_CMDL_INFO
1744 if (add_to_showcmd(prev_c))
1745 if (add_to_showcmd('"'))
1746 out_flush();
1747#endif
1748 c = term_vgetc();
1749#ifdef FEAT_CMDL_INFO
1750 clear_showcmd();
1751#endif
1752 if (!term_use_loop())
1753 /* job finished while waiting for a character */
1754 return;
1755
1756 /* CTRL-W "= prompt for expression to evaluate. */
1757 if (c == '=' && get_expr_register() != '=')
1758 return;
1759 if (!term_use_loop())
1760 /* job finished while waiting for a character */
1761 return;
1762
1763 l = (list_T *)get_reg_contents(c, GREG_LIST);
1764 if (l != NULL)
1765 {
1766 type = get_reg_type(c, &reglen);
1767 for (item = l->lv_first; item != NULL; item = item->li_next)
1768 {
1769 char_u *s = get_tv_string(&item->li_tv);
1770#ifdef WIN3264
1771 char_u *tmp = s;
1772
1773 if (!enc_utf8 && enc_codepage > 0)
1774 {
1775 WCHAR *ret = NULL;
1776 int length = 0;
1777
1778 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1779 (int)STRLEN(s), &ret, &length);
1780 if (ret != NULL)
1781 {
1782 WideCharToMultiByte_alloc(CP_UTF8, 0,
1783 ret, length, (char **)&s, &length, 0, 0);
1784 vim_free(ret);
1785 }
1786 }
1787#endif
1788 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1789 s, (int)STRLEN(s), NULL);
1790#ifdef WIN3264
1791 if (tmp != s)
1792 vim_free(s);
1793#endif
1794
1795 if (item->li_next != NULL || type == MLINE)
1796 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1797 (char_u *)"\r", 1, NULL);
1798 }
1799 list_free(l);
1800 }
1801}
1802
1803#if defined(FEAT_GUI) || defined(PROTO)
1804/*
1805 * Return TRUE when the cursor of the terminal should be displayed.
1806 */
1807 int
1808terminal_is_active()
1809{
1810 return in_terminal_loop != NULL;
1811}
1812
1813 cursorentry_T *
1814term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1815{
1816 term_T *term = in_terminal_loop;
1817 static cursorentry_T entry;
1818
1819 vim_memset(&entry, 0, sizeof(entry));
1820 entry.shape = entry.mshape =
1821 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1822 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1823 SHAPE_BLOCK;
1824 entry.percentage = 20;
1825 if (term->tl_cursor_blink)
1826 {
1827 entry.blinkwait = 700;
1828 entry.blinkon = 400;
1829 entry.blinkoff = 250;
1830 }
1831 *fg = gui.back_pixel;
1832 if (term->tl_cursor_color == NULL)
1833 *bg = gui.norm_pixel;
1834 else
1835 *bg = color_name2handle(term->tl_cursor_color);
1836 entry.name = "n";
1837 entry.used_for = SHAPE_CURSOR;
1838
1839 return &entry;
1840}
1841#endif
1842
Bram Moolenaard317b382018-02-08 22:33:31 +01001843 static void
1844may_output_cursor_props(void)
1845{
1846 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1847 || last_set_cursor_shape != desired_cursor_shape
1848 || last_set_cursor_blink != desired_cursor_blink)
1849 {
1850 last_set_cursor_color = desired_cursor_color;
1851 last_set_cursor_shape = desired_cursor_shape;
1852 last_set_cursor_blink = desired_cursor_blink;
1853 term_cursor_color(desired_cursor_color);
1854 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1855 /* this will restore the initial cursor style, if possible */
1856 ui_cursor_shape_forced(TRUE);
1857 else
1858 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1859 }
1860}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001861
Bram Moolenaard317b382018-02-08 22:33:31 +01001862/*
1863 * Set the cursor color and shape, if not last set to these.
1864 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001865 static void
1866may_set_cursor_props(term_T *term)
1867{
1868#ifdef FEAT_GUI
1869 /* For the GUI the cursor properties are obtained with
1870 * term_get_cursor_shape(). */
1871 if (gui.in_use)
1872 return;
1873#endif
1874 if (in_terminal_loop == term)
1875 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001876 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001877 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001878 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001879 desired_cursor_color = (char_u *)"";
1880 desired_cursor_shape = term->tl_cursor_shape;
1881 desired_cursor_blink = term->tl_cursor_blink;
1882 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001883 }
1884}
1885
Bram Moolenaard317b382018-02-08 22:33:31 +01001886/*
1887 * Reset the desired cursor properties and restore them when needed.
1888 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001889 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001890prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001891{
1892#ifdef FEAT_GUI
1893 if (gui.in_use)
1894 return;
1895#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001896 desired_cursor_color = (char_u *)"";
1897 desired_cursor_shape = -1;
1898 desired_cursor_blink = -1;
1899 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001900}
1901
1902/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001903 * Called when entering a window with the mouse. If this is a terminal window
1904 * we may want to change state.
1905 */
1906 void
1907term_win_entered()
1908{
1909 term_T *term = curbuf->b_term;
1910
1911 if (term != NULL)
1912 {
1913 if (term_use_loop())
1914 {
1915 reset_VIsual_and_resel();
1916 if (State & INSERT)
1917 stop_insert_mode = TRUE;
1918 }
1919 mouse_was_outside = FALSE;
1920 enter_mouse_col = mouse_col;
1921 enter_mouse_row = mouse_row;
1922 }
1923}
1924
1925/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001926 * Returns TRUE if the current window contains a terminal and we are sending
1927 * keys to the job.
1928 */
1929 int
1930term_use_loop(void)
1931{
1932 term_T *term = curbuf->b_term;
1933
1934 return term != NULL
1935 && !term->tl_normal_mode
1936 && term->tl_vterm != NULL
1937 && term_job_running(term);
1938}
1939
1940/*
1941 * Wait for input and send it to the job.
1942 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1943 * when there is no more typahead.
1944 * Return when the start of a CTRL-W command is typed or anything else that
1945 * should be handled as a Normal mode command.
1946 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1947 * the terminal was closed.
1948 */
1949 int
1950terminal_loop(int blocking)
1951{
1952 int c;
1953 int termkey = 0;
1954 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001955#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001956 int tty_fd = curbuf->b_term->tl_job->jv_channel
1957 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001958#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001959 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001960
1961 /* Remember the terminal we are sending keys to. However, the terminal
1962 * might be closed while waiting for a character, e.g. typing "exit" in a
1963 * shell and ++close was used. Therefore use curbuf->b_term instead of a
1964 * stored reference. */
1965 in_terminal_loop = curbuf->b_term;
1966
1967 if (*curwin->w_p_tk != NUL)
1968 termkey = string_to_key(curwin->w_p_tk, TRUE);
1969 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
1970 may_set_cursor_props(curbuf->b_term);
1971
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001972 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001973 {
Bram Moolenaar13568252018-03-16 20:46:58 +01001974#ifdef FEAT_GUI
1975 if (!curbuf->b_term->tl_system)
1976#endif
1977 /* TODO: skip screen update when handling a sequence of keys. */
1978 /* Repeat redrawing in case a message is received while redrawing.
1979 */
1980 while (must_redraw != 0)
1981 if (update_screen(0) == FAIL)
1982 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001983 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01001984 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001985
1986 c = term_vgetc();
1987 if (!term_use_loop())
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001988 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001989 /* Job finished while waiting for a character. Push back the
1990 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001991 if (c != K_IGNORE)
1992 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001993 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001994 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001995 if (c == K_IGNORE)
1996 continue;
1997
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001998#ifdef UNIX
1999 /*
2000 * The shell or another program may change the tty settings. Getting
2001 * them for every typed character is a bit of overhead, but it's needed
2002 * for the first character typed, e.g. when Vim starts in a shell.
2003 */
2004 if (isatty(tty_fd))
2005 {
2006 ttyinfo_T info;
2007
2008 /* Get the current backspace character of the pty. */
2009 if (get_tty_info(tty_fd, &info) == OK)
2010 term_backspace_char = info.backspace;
2011 }
2012#endif
2013
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002014#ifdef WIN3264
2015 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2016 * Use CTRL-BREAK to kill the job. */
2017 if (ctrl_break_was_pressed)
2018 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2019#endif
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002020 /* Was either CTRL-W (termkey) or CTRL-\ pressed?
2021 * Not in a system terminal. */
2022 if ((c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
2023#ifdef FEAT_GUI
2024 && !curbuf->b_term->tl_system
2025#endif
2026 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002027 {
2028 int prev_c = c;
2029
2030#ifdef FEAT_CMDL_INFO
2031 if (add_to_showcmd(c))
2032 out_flush();
2033#endif
2034 c = term_vgetc();
2035#ifdef FEAT_CMDL_INFO
2036 clear_showcmd();
2037#endif
2038 if (!term_use_loop())
2039 /* job finished while waiting for a character */
2040 break;
2041
2042 if (prev_c == Ctrl_BSL)
2043 {
2044 if (c == Ctrl_N)
2045 {
2046 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2047 term_enter_normal_mode();
2048 ret = FAIL;
2049 goto theend;
2050 }
2051 /* Send both keys to the terminal. */
2052 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2053 }
2054 else if (c == Ctrl_C)
2055 {
2056 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
2057 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2058 }
2059 else if (termkey == 0 && c == '.')
2060 {
2061 /* "CTRL-W .": send CTRL-W to the job */
2062 c = Ctrl_W;
2063 }
2064 else if (c == 'N')
2065 {
2066 /* CTRL-W N : go to Terminal-Normal mode. */
2067 term_enter_normal_mode();
2068 ret = FAIL;
2069 goto theend;
2070 }
2071 else if (c == '"')
2072 {
2073 term_paste_register(prev_c);
2074 continue;
2075 }
2076 else if (termkey == 0 || c != termkey)
2077 {
2078 stuffcharReadbuff(Ctrl_W);
2079 stuffcharReadbuff(c);
2080 ret = OK;
2081 goto theend;
2082 }
2083 }
2084# ifdef WIN3264
2085 if (!enc_utf8 && has_mbyte && c >= 0x80)
2086 {
2087 WCHAR wc;
2088 char_u mb[3];
2089
2090 mb[0] = (unsigned)c >> 8;
2091 mb[1] = c;
2092 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2093 c = wc;
2094 }
2095# endif
2096 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2097 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002098 if (c == K_MOUSEMOVE)
2099 /* We are sure to come back here, don't reset the cursor color
2100 * and shape to avoid flickering. */
2101 restore_cursor = FALSE;
2102
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002103 ret = OK;
2104 goto theend;
2105 }
2106 }
2107 ret = FAIL;
2108
2109theend:
2110 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002111 if (restore_cursor)
2112 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002113 return ret;
2114}
2115
2116/*
2117 * Called when a job has finished.
2118 * This updates the title and status, but does not close the vterm, because
2119 * there might still be pending output in the channel.
2120 */
2121 void
2122term_job_ended(job_T *job)
2123{
2124 term_T *term;
2125 int did_one = FALSE;
2126
2127 for (term = first_term; term != NULL; term = term->tl_next)
2128 if (term->tl_job == job)
2129 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002130 VIM_CLEAR(term->tl_title);
2131 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002132 redraw_buf_and_status_later(term->tl_buffer, VALID);
2133 did_one = TRUE;
2134 }
2135 if (did_one)
2136 redraw_statuslines();
2137 if (curbuf->b_term != NULL)
2138 {
2139 if (curbuf->b_term->tl_job == job)
2140 maketitle();
2141 update_cursor(curbuf->b_term, TRUE);
2142 }
2143}
2144
2145 static void
2146may_toggle_cursor(term_T *term)
2147{
2148 if (in_terminal_loop == term)
2149 {
2150 if (term->tl_cursor_visible)
2151 cursor_on();
2152 else
2153 cursor_off();
2154 }
2155}
2156
2157/*
2158 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002159 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002160 */
2161 static int
2162color2index(VTermColor *color, int fg, int *boldp)
2163{
2164 int red = color->red;
2165 int blue = color->blue;
2166 int green = color->green;
2167
Bram Moolenaar46359e12017-11-29 22:33:38 +01002168 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002169 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002170 /* First 16 colors and default: use the ANSI index, because these
2171 * colors can be redefined. */
2172 if (t_colors >= 16)
2173 return color->ansi_index;
2174 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002175 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002176 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002177 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002178 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2179 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2180 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
2181 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue*/
2182 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2183 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2184 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2185 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2186 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2187 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2188 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2189 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2190 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2191 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2192 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002193 }
2194 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002195
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002196 if (t_colors >= 256)
2197 {
2198 if (red == blue && red == green)
2199 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002200 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002201 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002202 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2203 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2204 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002205 int i;
2206
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002207 if (red < 5)
2208 return 17; /* 00/00/00 */
2209 if (red > 245) /* ff/ff/ff */
2210 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002211 for (i = 0; i < 23; ++i)
2212 if (red < cutoff[i])
2213 return i + 233;
2214 return 256;
2215 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002216 {
2217 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2218 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002219
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002220 /* 216-color cube */
2221 for (ri = 0; ri < 5; ++ri)
2222 if (red < cutoff[ri])
2223 break;
2224 for (gi = 0; gi < 5; ++gi)
2225 if (green < cutoff[gi])
2226 break;
2227 for (bi = 0; bi < 5; ++bi)
2228 if (blue < cutoff[bi])
2229 break;
2230 return 17 + ri * 36 + gi * 6 + bi;
2231 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002232 }
2233 return 0;
2234}
2235
2236/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002237 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002238 */
2239 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002240vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002241{
2242 int attr = 0;
2243
2244 if (cellattrs.bold)
2245 attr |= HL_BOLD;
2246 if (cellattrs.underline)
2247 attr |= HL_UNDERLINE;
2248 if (cellattrs.italic)
2249 attr |= HL_ITALIC;
2250 if (cellattrs.strike)
2251 attr |= HL_STRIKETHROUGH;
2252 if (cellattrs.reverse)
2253 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002254 return attr;
2255}
2256
2257/*
2258 * Store Vterm attributes in "cell" from highlight flags.
2259 */
2260 static void
2261hl2vtermAttr(int attr, cellattr_T *cell)
2262{
2263 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2264 if (attr & HL_BOLD)
2265 cell->attrs.bold = 1;
2266 if (attr & HL_UNDERLINE)
2267 cell->attrs.underline = 1;
2268 if (attr & HL_ITALIC)
2269 cell->attrs.italic = 1;
2270 if (attr & HL_STRIKETHROUGH)
2271 cell->attrs.strike = 1;
2272 if (attr & HL_INVERSE)
2273 cell->attrs.reverse = 1;
2274}
2275
2276/*
2277 * Convert the attributes of a vterm cell into an attribute index.
2278 */
2279 static int
2280cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2281{
2282 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002283
2284#ifdef FEAT_GUI
2285 if (gui.in_use)
2286 {
2287 guicolor_T fg, bg;
2288
2289 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2290 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2291 return get_gui_attr_idx(attr, fg, bg);
2292 }
2293 else
2294#endif
2295#ifdef FEAT_TERMGUICOLORS
2296 if (p_tgc)
2297 {
2298 guicolor_T fg, bg;
2299
2300 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2301 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2302
2303 return get_tgc_attr_idx(attr, fg, bg);
2304 }
2305 else
2306#endif
2307 {
2308 int bold = MAYBE;
2309 int fg = color2index(&cellfg, TRUE, &bold);
2310 int bg = color2index(&cellbg, FALSE, &bold);
2311
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002312 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002313 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002314 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002315 if (fg == 0 && term_default_cterm_fg >= 0)
2316 fg = term_default_cterm_fg + 1;
2317 if (bg == 0 && term_default_cterm_bg >= 0)
2318 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002319 }
2320
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002321 /* with 8 colors set the bold attribute to get a bright foreground */
2322 if (bold == TRUE)
2323 attr |= HL_BOLD;
2324 return get_cterm_attr_idx(attr, fg, bg);
2325 }
2326 return 0;
2327}
2328
2329 static int
2330handle_damage(VTermRect rect, void *user)
2331{
2332 term_T *term = (term_T *)user;
2333
2334 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2335 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2336 redraw_buf_later(term->tl_buffer, NOT_VALID);
2337 return 1;
2338}
2339
2340 static int
2341handle_moverect(VTermRect dest, VTermRect src, void *user)
2342{
2343 term_T *term = (term_T *)user;
2344
2345 /* Scrolling up is done much more efficiently by deleting lines instead of
2346 * redrawing the text. */
2347 if (dest.start_col == src.start_col
2348 && dest.end_col == src.end_col
2349 && dest.start_row < src.start_row)
2350 {
2351 win_T *wp;
2352 VTermColor fg, bg;
2353 VTermScreenCellAttrs attr;
2354 int clear_attr;
2355
2356 /* Set the color to clear lines with. */
2357 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2358 &fg, &bg);
2359 vim_memset(&attr, 0, sizeof(attr));
2360 clear_attr = cell2attr(attr, fg, bg);
2361
2362 FOR_ALL_WINDOWS(wp)
2363 {
2364 if (wp->w_buffer == term->tl_buffer)
2365 win_del_lines(wp, dest.start_row,
2366 src.start_row - dest.start_row, FALSE, FALSE,
2367 clear_attr);
2368 }
2369 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002370
2371 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2372 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2373
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002374 redraw_buf_later(term->tl_buffer, NOT_VALID);
2375 return 1;
2376}
2377
2378 static int
2379handle_movecursor(
2380 VTermPos pos,
2381 VTermPos oldpos UNUSED,
2382 int visible,
2383 void *user)
2384{
2385 term_T *term = (term_T *)user;
2386 win_T *wp;
2387
2388 term->tl_cursor_pos = pos;
2389 term->tl_cursor_visible = visible;
2390
2391 FOR_ALL_WINDOWS(wp)
2392 {
2393 if (wp->w_buffer == term->tl_buffer)
2394 position_cursor(wp, &pos);
2395 }
2396 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2397 {
2398 may_toggle_cursor(term);
2399 update_cursor(term, term->tl_cursor_visible);
2400 }
2401
2402 return 1;
2403}
2404
2405 static int
2406handle_settermprop(
2407 VTermProp prop,
2408 VTermValue *value,
2409 void *user)
2410{
2411 term_T *term = (term_T *)user;
2412
2413 switch (prop)
2414 {
2415 case VTERM_PROP_TITLE:
2416 vim_free(term->tl_title);
2417 /* a blank title isn't useful, make it empty, so that "running" is
2418 * displayed */
2419 if (*skipwhite((char_u *)value->string) == NUL)
2420 term->tl_title = NULL;
2421#ifdef WIN3264
2422 else if (!enc_utf8 && enc_codepage > 0)
2423 {
2424 WCHAR *ret = NULL;
2425 int length = 0;
2426
2427 MultiByteToWideChar_alloc(CP_UTF8, 0,
2428 (char*)value->string, (int)STRLEN(value->string),
2429 &ret, &length);
2430 if (ret != NULL)
2431 {
2432 WideCharToMultiByte_alloc(enc_codepage, 0,
2433 ret, length, (char**)&term->tl_title,
2434 &length, 0, 0);
2435 vim_free(ret);
2436 }
2437 }
2438#endif
2439 else
2440 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002441 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002442 if (term == curbuf->b_term)
2443 maketitle();
2444 break;
2445
2446 case VTERM_PROP_CURSORVISIBLE:
2447 term->tl_cursor_visible = value->boolean;
2448 may_toggle_cursor(term);
2449 out_flush();
2450 break;
2451
2452 case VTERM_PROP_CURSORBLINK:
2453 term->tl_cursor_blink = value->boolean;
2454 may_set_cursor_props(term);
2455 break;
2456
2457 case VTERM_PROP_CURSORSHAPE:
2458 term->tl_cursor_shape = value->number;
2459 may_set_cursor_props(term);
2460 break;
2461
2462 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002463 if (desired_cursor_color == term->tl_cursor_color)
2464 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002465 vim_free(term->tl_cursor_color);
2466 if (*value->string == NUL)
2467 term->tl_cursor_color = NULL;
2468 else
2469 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2470 may_set_cursor_props(term);
2471 break;
2472
2473 case VTERM_PROP_ALTSCREEN:
2474 /* TODO: do anything else? */
2475 term->tl_using_altscreen = value->boolean;
2476 break;
2477
2478 default:
2479 break;
2480 }
2481 /* Always return 1, otherwise vterm doesn't store the value internally. */
2482 return 1;
2483}
2484
2485/*
2486 * The job running in the terminal resized the terminal.
2487 */
2488 static int
2489handle_resize(int rows, int cols, void *user)
2490{
2491 term_T *term = (term_T *)user;
2492 win_T *wp;
2493
2494 term->tl_rows = rows;
2495 term->tl_cols = cols;
2496 if (term->tl_vterm_size_changed)
2497 /* Size was set by vterm_set_size(), don't set the window size. */
2498 term->tl_vterm_size_changed = FALSE;
2499 else
2500 {
2501 FOR_ALL_WINDOWS(wp)
2502 {
2503 if (wp->w_buffer == term->tl_buffer)
2504 {
2505 win_setheight_win(rows, wp);
2506 win_setwidth_win(cols, wp);
2507 }
2508 }
2509 redraw_buf_later(term->tl_buffer, NOT_VALID);
2510 }
2511 return 1;
2512}
2513
2514/*
2515 * Handle a line that is pushed off the top of the screen.
2516 */
2517 static int
2518handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2519{
2520 term_T *term = (term_T *)user;
2521
2522 /* TODO: Limit the number of lines that are stored. */
2523 if (ga_grow(&term->tl_scrollback, 1) == OK)
2524 {
2525 cellattr_T *p = NULL;
2526 int len = 0;
2527 int i;
2528 int c;
2529 int col;
2530 sb_line_T *line;
2531 garray_T ga;
2532 cellattr_T fill_attr = term->tl_default_color;
2533
2534 /* do not store empty cells at the end */
2535 for (i = 0; i < cols; ++i)
2536 if (cells[i].chars[0] != 0)
2537 len = i + 1;
2538 else
2539 cell2cellattr(&cells[i], &fill_attr);
2540
2541 ga_init2(&ga, 1, 100);
2542 if (len > 0)
2543 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2544 if (p != NULL)
2545 {
2546 for (col = 0; col < len; col += cells[col].width)
2547 {
2548 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2549 {
2550 ga.ga_len = 0;
2551 break;
2552 }
2553 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2554 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2555 (char_u *)ga.ga_data + ga.ga_len);
2556 cell2cellattr(&cells[col], &p[col]);
2557 }
2558 }
2559 if (ga_grow(&ga, 1) == FAIL)
2560 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2561 else
2562 {
2563 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2564 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2565 }
2566 ga_clear(&ga);
2567
2568 line = (sb_line_T *)term->tl_scrollback.ga_data
2569 + term->tl_scrollback.ga_len;
2570 line->sb_cols = len;
2571 line->sb_cells = p;
2572 line->sb_fill_attr = fill_attr;
2573 ++term->tl_scrollback.ga_len;
2574 ++term->tl_scrollback_scrolled;
2575 }
2576 return 0; /* ignored */
2577}
2578
2579static VTermScreenCallbacks screen_callbacks = {
2580 handle_damage, /* damage */
2581 handle_moverect, /* moverect */
2582 handle_movecursor, /* movecursor */
2583 handle_settermprop, /* settermprop */
2584 NULL, /* bell */
2585 handle_resize, /* resize */
2586 handle_pushline, /* sb_pushline */
2587 NULL /* sb_popline */
2588};
2589
2590/*
2591 * Called when a channel has been closed.
2592 * If this was a channel for a terminal window then finish it up.
2593 */
2594 void
2595term_channel_closed(channel_T *ch)
2596{
2597 term_T *term;
2598 int did_one = FALSE;
2599
2600 for (term = first_term; term != NULL; term = term->tl_next)
2601 if (term->tl_job == ch->ch_job)
2602 {
2603 term->tl_channel_closed = TRUE;
2604 did_one = TRUE;
2605
Bram Moolenaard23a8232018-02-10 18:45:26 +01002606 VIM_CLEAR(term->tl_title);
2607 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002608
2609 /* Unless in Terminal-Normal mode: clear the vterm. */
2610 if (!term->tl_normal_mode)
2611 {
2612 int fnum = term->tl_buffer->b_fnum;
2613
2614 cleanup_vterm(term);
2615
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002616 if (term->tl_finish == TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002617 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002618 aco_save_T aco;
2619
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002620 /* ++close or term_finish == "close" */
2621 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002622 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002623 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002624 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002625 break;
2626 }
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002627 if (term->tl_finish == TL_FINISH_OPEN
2628 && term->tl_buffer->b_nwindows == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002629 {
2630 char buf[50];
2631
2632 /* TODO: use term_opencmd */
2633 ch_log(NULL, "terminal job finished, opening window");
2634 vim_snprintf(buf, sizeof(buf),
2635 term->tl_opencmd == NULL
2636 ? "botright sbuf %d"
2637 : (char *)term->tl_opencmd, fnum);
2638 do_cmdline_cmd((char_u *)buf);
2639 }
2640 else
2641 ch_log(NULL, "terminal job finished");
2642 }
2643
2644 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2645 }
2646 if (did_one)
2647 {
2648 redraw_statuslines();
2649
2650 /* Need to break out of vgetc(). */
2651 ins_char_typebuf(K_IGNORE);
2652 typebuf_was_filled = TRUE;
2653
2654 term = curbuf->b_term;
2655 if (term != NULL)
2656 {
2657 if (term->tl_job == ch->ch_job)
2658 maketitle();
2659 update_cursor(term, term->tl_cursor_visible);
2660 }
2661 }
2662}
2663
2664/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002665 * Fill one screen line from a line of the terminal.
2666 * Advances "pos" to past the last column.
2667 */
2668 static void
2669term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2670{
2671 int off = screen_get_current_line_off();
2672
2673 for (pos->col = 0; pos->col < max_col; )
2674 {
2675 VTermScreenCell cell;
2676 int c;
2677
2678 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2679 vim_memset(&cell, 0, sizeof(cell));
2680
2681 c = cell.chars[0];
2682 if (c == NUL)
2683 {
2684 ScreenLines[off] = ' ';
2685 if (enc_utf8)
2686 ScreenLinesUC[off] = NUL;
2687 }
2688 else
2689 {
2690 if (enc_utf8)
2691 {
2692 int i;
2693
2694 /* composing chars */
2695 for (i = 0; i < Screen_mco
2696 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2697 {
2698 ScreenLinesC[i][off] = cell.chars[i + 1];
2699 if (cell.chars[i + 1] == 0)
2700 break;
2701 }
2702 if (c >= 0x80 || (Screen_mco > 0
2703 && ScreenLinesC[0][off] != 0))
2704 {
2705 ScreenLines[off] = ' ';
2706 ScreenLinesUC[off] = c;
2707 }
2708 else
2709 {
2710 ScreenLines[off] = c;
2711 ScreenLinesUC[off] = NUL;
2712 }
2713 }
2714#ifdef WIN3264
2715 else if (has_mbyte && c >= 0x80)
2716 {
2717 char_u mb[MB_MAXBYTES+1];
2718 WCHAR wc = c;
2719
2720 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2721 (char*)mb, 2, 0, 0) > 1)
2722 {
2723 ScreenLines[off] = mb[0];
2724 ScreenLines[off + 1] = mb[1];
2725 cell.width = mb_ptr2cells(mb);
2726 }
2727 else
2728 ScreenLines[off] = c;
2729 }
2730#endif
2731 else
2732 ScreenLines[off] = c;
2733 }
2734 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2735
2736 ++pos->col;
2737 ++off;
2738 if (cell.width == 2)
2739 {
2740 if (enc_utf8)
2741 ScreenLinesUC[off] = NUL;
2742
2743 /* don't set the second byte to NUL for a DBCS encoding, it
2744 * has been set above */
2745 if (enc_utf8 || !has_mbyte)
2746 ScreenLines[off] = NUL;
2747
2748 ++pos->col;
2749 ++off;
2750 }
2751 }
2752}
2753
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002754#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002755 static void
2756update_system_term(term_T *term)
2757{
2758 VTermPos pos;
2759 VTermScreen *screen;
2760
2761 if (term->tl_vterm == NULL)
2762 return;
2763 screen = vterm_obtain_screen(term->tl_vterm);
2764
2765 /* Scroll up to make more room for terminal lines if needed. */
2766 while (term->tl_toprow > 0
2767 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2768 {
2769 int save_p_more = p_more;
2770
2771 p_more = FALSE;
2772 msg_row = Rows - 1;
2773 msg_puts((char_u *)"\n");
2774 p_more = save_p_more;
2775 --term->tl_toprow;
2776 }
2777
2778 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2779 && pos.row < Rows; ++pos.row)
2780 {
2781 if (pos.row < term->tl_rows)
2782 {
2783 int max_col = MIN(Columns, term->tl_cols);
2784
2785 term_line2screenline(screen, &pos, max_col);
2786 }
2787 else
2788 pos.col = 0;
2789
2790 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2791 }
2792
2793 term->tl_dirty_row_start = MAX_ROW;
2794 term->tl_dirty_row_end = 0;
2795 update_cursor(term, TRUE);
2796}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002797#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002798
2799/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002800 * Called to update a window that contains an active terminal.
2801 * Returns FAIL when there is no terminal running in this window or in
2802 * Terminal-Normal mode.
2803 */
2804 int
2805term_update_window(win_T *wp)
2806{
2807 term_T *term = wp->w_buffer->b_term;
2808 VTerm *vterm;
2809 VTermScreen *screen;
2810 VTermState *state;
2811 VTermPos pos;
2812
2813 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2814 return FAIL;
2815
2816 vterm = term->tl_vterm;
2817 screen = vterm_obtain_screen(vterm);
2818 state = vterm_obtain_state(vterm);
2819
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002820 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002821 {
2822 term->tl_dirty_row_start = 0;
2823 term->tl_dirty_row_end = MAX_ROW;
2824 }
2825
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002826 /*
2827 * If the window was resized a redraw will be triggered and we get here.
2828 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2829 */
2830 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
2831 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
2832 {
2833 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
2834 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
2835 win_T *twp;
2836
2837 FOR_ALL_WINDOWS(twp)
2838 {
2839 /* When more than one window shows the same terminal, use the
2840 * smallest size. */
2841 if (twp->w_buffer == term->tl_buffer)
2842 {
2843 if (!term->tl_rows_fixed && rows > twp->w_height)
2844 rows = twp->w_height;
2845 if (!term->tl_cols_fixed && cols > twp->w_width)
2846 cols = twp->w_width;
2847 }
2848 }
2849
2850 term->tl_vterm_size_changed = TRUE;
2851 vterm_set_size(vterm, rows, cols);
2852 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
2853 rows);
2854 term_report_winsize(term, rows, cols);
2855 }
2856
2857 /* The cursor may have been moved when resizing. */
2858 vterm_state_get_cursorpos(state, &pos);
2859 position_cursor(wp, &pos);
2860
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002861 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2862 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002863 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002864 if (pos.row < term->tl_rows)
2865 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002866 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002867
Bram Moolenaar13568252018-03-16 20:46:58 +01002868 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002869 }
2870 else
2871 pos.col = 0;
2872
Bram Moolenaarf118d482018-03-13 13:14:00 +01002873 screen_line(wp->w_winrow + pos.row
2874#ifdef FEAT_MENU
2875 + winbar_height(wp)
2876#endif
2877 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002878 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002879 term->tl_dirty_row_start = MAX_ROW;
2880 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002881
2882 return OK;
2883}
2884
2885/*
2886 * Return TRUE if "wp" is a terminal window where the job has finished.
2887 */
2888 int
2889term_is_finished(buf_T *buf)
2890{
2891 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2892}
2893
2894/*
2895 * Return TRUE if "wp" is a terminal window where the job has finished or we
2896 * are in Terminal-Normal mode, thus we show the buffer contents.
2897 */
2898 int
2899term_show_buffer(buf_T *buf)
2900{
2901 term_T *term = buf->b_term;
2902
2903 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2904}
2905
2906/*
2907 * The current buffer is going to be changed. If there is terminal
2908 * highlighting remove it now.
2909 */
2910 void
2911term_change_in_curbuf(void)
2912{
2913 term_T *term = curbuf->b_term;
2914
2915 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2916 {
2917 free_scrollback(term);
2918 redraw_buf_later(term->tl_buffer, NOT_VALID);
2919
2920 /* The buffer is now like a normal buffer, it cannot be easily
2921 * abandoned when changed. */
2922 set_string_option_direct((char_u *)"buftype", -1,
2923 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2924 }
2925}
2926
2927/*
2928 * Get the screen attribute for a position in the buffer.
2929 * Use a negative "col" to get the filler background color.
2930 */
2931 int
2932term_get_attr(buf_T *buf, linenr_T lnum, int col)
2933{
2934 term_T *term = buf->b_term;
2935 sb_line_T *line;
2936 cellattr_T *cellattr;
2937
2938 if (lnum > term->tl_scrollback.ga_len)
2939 cellattr = &term->tl_default_color;
2940 else
2941 {
2942 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
2943 if (col < 0 || col >= line->sb_cols)
2944 cellattr = &line->sb_fill_attr;
2945 else
2946 cellattr = line->sb_cells + col;
2947 }
2948 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
2949}
2950
2951static VTermColor ansi_table[16] = {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002952 { 0, 0, 0, 1}, /* black */
2953 {224, 0, 0, 2}, /* dark red */
2954 { 0, 224, 0, 3}, /* dark green */
2955 {224, 224, 0, 4}, /* dark yellow / brown */
2956 { 0, 0, 224, 5}, /* dark blue */
2957 {224, 0, 224, 6}, /* dark magenta */
2958 { 0, 224, 224, 7}, /* dark cyan */
2959 {224, 224, 224, 8}, /* light grey */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002960
Bram Moolenaar46359e12017-11-29 22:33:38 +01002961 {128, 128, 128, 9}, /* dark grey */
2962 {255, 64, 64, 10}, /* light red */
2963 { 64, 255, 64, 11}, /* light green */
2964 {255, 255, 64, 12}, /* yellow */
2965 { 64, 64, 255, 13}, /* light blue */
2966 {255, 64, 255, 14}, /* light magenta */
2967 { 64, 255, 255, 15}, /* light cyan */
2968 {255, 255, 255, 16}, /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002969};
2970
2971static int cube_value[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002972 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002973};
2974
2975static int grey_ramp[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002976 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
2977 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002978};
2979
2980/*
2981 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002982 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002983 */
2984 static void
2985cterm_color2rgb(int nr, VTermColor *rgb)
2986{
2987 int idx;
2988
2989 if (nr < 16)
2990 {
2991 *rgb = ansi_table[nr];
2992 }
2993 else if (nr < 232)
2994 {
2995 /* 216 color cube */
2996 idx = nr - 16;
2997 rgb->blue = cube_value[idx % 6];
2998 rgb->green = cube_value[idx / 6 % 6];
2999 rgb->red = cube_value[idx / 36 % 6];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003000 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003001 }
3002 else if (nr < 256)
3003 {
3004 /* 24 grey scale ramp */
3005 idx = nr - 232;
3006 rgb->blue = grey_ramp[idx];
3007 rgb->green = grey_ramp[idx];
3008 rgb->red = grey_ramp[idx];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003009 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003010 }
3011}
3012
3013/*
3014 * Create a new vterm and initialize it.
3015 */
3016 static void
3017create_vterm(term_T *term, int rows, int cols)
3018{
3019 VTerm *vterm;
3020 VTermScreen *screen;
3021 VTermValue value;
3022 VTermColor *fg, *bg;
3023 int fgval, bgval;
3024 int id;
3025
3026 vterm = vterm_new(rows, cols);
3027 term->tl_vterm = vterm;
3028 screen = vterm_obtain_screen(vterm);
3029 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3030 /* TODO: depends on 'encoding'. */
3031 vterm_set_utf8(vterm, 1);
3032
3033 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3034 term->tl_default_color.width = 1;
3035 fg = &term->tl_default_color.fg;
3036 bg = &term->tl_default_color.bg;
3037
3038 /* Vterm uses a default black background. Set it to white when
3039 * 'background' is "light". */
3040 if (*p_bg == 'l')
3041 {
3042 fgval = 0;
3043 bgval = 255;
3044 }
3045 else
3046 {
3047 fgval = 255;
3048 bgval = 0;
3049 }
3050 fg->red = fg->green = fg->blue = fgval;
3051 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003052 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003053
3054 /* The "Terminal" highlight group overrules the defaults. */
3055 id = syn_name2id((char_u *)"Terminal");
3056
Bram Moolenaar46359e12017-11-29 22:33:38 +01003057 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003058#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3059 if (0
3060# ifdef FEAT_GUI
3061 || gui.in_use
3062# endif
3063# ifdef FEAT_TERMGUICOLORS
3064 || p_tgc
3065# endif
3066 )
3067 {
3068 guicolor_T fg_rgb = INVALCOLOR;
3069 guicolor_T bg_rgb = INVALCOLOR;
3070
3071 if (id != 0)
3072 syn_id2colors(id, &fg_rgb, &bg_rgb);
3073
3074# ifdef FEAT_GUI
3075 if (gui.in_use)
3076 {
3077 if (fg_rgb == INVALCOLOR)
3078 fg_rgb = gui.norm_pixel;
3079 if (bg_rgb == INVALCOLOR)
3080 bg_rgb = gui.back_pixel;
3081 }
3082# ifdef FEAT_TERMGUICOLORS
3083 else
3084# endif
3085# endif
3086# ifdef FEAT_TERMGUICOLORS
3087 {
3088 if (fg_rgb == INVALCOLOR)
3089 fg_rgb = cterm_normal_fg_gui_color;
3090 if (bg_rgb == INVALCOLOR)
3091 bg_rgb = cterm_normal_bg_gui_color;
3092 }
3093# endif
3094 if (fg_rgb != INVALCOLOR)
3095 {
3096 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3097
3098 fg->red = (unsigned)(rgb >> 16);
3099 fg->green = (unsigned)(rgb >> 8) & 255;
3100 fg->blue = (unsigned)rgb & 255;
3101 }
3102 if (bg_rgb != INVALCOLOR)
3103 {
3104 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3105
3106 bg->red = (unsigned)(rgb >> 16);
3107 bg->green = (unsigned)(rgb >> 8) & 255;
3108 bg->blue = (unsigned)rgb & 255;
3109 }
3110 }
3111 else
3112#endif
3113 if (id != 0 && t_colors >= 16)
3114 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003115 if (term_default_cterm_fg >= 0)
3116 cterm_color2rgb(term_default_cterm_fg, fg);
3117 if (term_default_cterm_bg >= 0)
3118 cterm_color2rgb(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003119 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003120 else
3121 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003122#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003123 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003124#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003125
3126 /* In an MS-Windows console we know the normal colors. */
3127 if (cterm_normal_fg_color > 0)
3128 {
3129 cterm_color2rgb(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003130# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003131 tmp = fg->red;
3132 fg->red = fg->blue;
3133 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003134# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003135 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003136# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003137 else
3138 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003139# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003140
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003141 if (cterm_normal_bg_color > 0)
3142 {
3143 cterm_color2rgb(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003144# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003145 tmp = bg->red;
3146 bg->red = bg->blue;
3147 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003148# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003149 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003150# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003151 else
3152 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003153# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003154 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003155
3156 vterm_state_set_default_colors(vterm_obtain_state(vterm), fg, bg);
3157
3158 /* Required to initialize most things. */
3159 vterm_screen_reset(screen, 1 /* hard */);
3160
3161 /* Allow using alternate screen. */
3162 vterm_screen_enable_altscreen(screen, 1);
3163
3164 /* For unix do not use a blinking cursor. In an xterm this causes the
3165 * cursor to blink if it's blinking in the xterm.
3166 * For Windows we respect the system wide setting. */
3167#ifdef WIN3264
3168 if (GetCaretBlinkTime() == INFINITE)
3169 value.boolean = 0;
3170 else
3171 value.boolean = 1;
3172#else
3173 value.boolean = 0;
3174#endif
3175 vterm_state_set_termprop(vterm_obtain_state(vterm),
3176 VTERM_PROP_CURSORBLINK, &value);
3177}
3178
3179/*
3180 * Return the text to show for the buffer name and status.
3181 */
3182 char_u *
3183term_get_status_text(term_T *term)
3184{
3185 if (term->tl_status_text == NULL)
3186 {
3187 char_u *txt;
3188 size_t len;
3189
3190 if (term->tl_normal_mode)
3191 {
3192 if (term_job_running(term))
3193 txt = (char_u *)_("Terminal");
3194 else
3195 txt = (char_u *)_("Terminal-finished");
3196 }
3197 else if (term->tl_title != NULL)
3198 txt = term->tl_title;
3199 else if (term_none_open(term))
3200 txt = (char_u *)_("active");
3201 else if (term_job_running(term))
3202 txt = (char_u *)_("running");
3203 else
3204 txt = (char_u *)_("finished");
3205 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3206 term->tl_status_text = alloc((int)len);
3207 if (term->tl_status_text != NULL)
3208 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3209 term->tl_buffer->b_fname, txt);
3210 }
3211 return term->tl_status_text;
3212}
3213
3214/*
3215 * Mark references in jobs of terminals.
3216 */
3217 int
3218set_ref_in_term(int copyID)
3219{
3220 int abort = FALSE;
3221 term_T *term;
3222 typval_T tv;
3223
3224 for (term = first_term; term != NULL; term = term->tl_next)
3225 if (term->tl_job != NULL)
3226 {
3227 tv.v_type = VAR_JOB;
3228 tv.vval.v_job = term->tl_job;
3229 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3230 }
3231 return abort;
3232}
3233
3234/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003235 * Cache "Terminal" highlight group colors.
3236 */
3237 void
3238set_terminal_default_colors(int cterm_fg, int cterm_bg)
3239{
3240 term_default_cterm_fg = cterm_fg - 1;
3241 term_default_cterm_bg = cterm_bg - 1;
3242}
3243
3244/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003245 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003246 * Returns NULL when the buffer is not for a terminal window and logs a message
3247 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003248 */
3249 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003250term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003251{
3252 buf_T *buf;
3253
3254 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3255 ++emsg_off;
3256 buf = get_buf_tv(&argvars[0], FALSE);
3257 --emsg_off;
3258 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003259 {
3260 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003261 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003262 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003263 return buf;
3264}
3265
Bram Moolenaard96ff162018-02-18 22:13:29 +01003266 static int
3267same_color(VTermColor *a, VTermColor *b)
3268{
3269 return a->red == b->red
3270 && a->green == b->green
3271 && a->blue == b->blue
3272 && a->ansi_index == b->ansi_index;
3273}
3274
3275 static void
3276dump_term_color(FILE *fd, VTermColor *color)
3277{
3278 fprintf(fd, "%02x%02x%02x%d",
3279 (int)color->red, (int)color->green, (int)color->blue,
3280 (int)color->ansi_index);
3281}
3282
3283/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003284 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003285 *
3286 * Each screen cell in full is:
3287 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3288 * {characters} is a space for an empty cell
3289 * For a double-width character "+" is changed to "*" and the next cell is
3290 * skipped.
3291 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3292 * when "&" use the same as the previous cell.
3293 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3294 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3295 * {color-idx} is a number from 0 to 255
3296 *
3297 * Screen cell with same width, attributes and color as the previous one:
3298 * |{characters}
3299 *
3300 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3301 *
3302 * Repeating the previous screen cell:
3303 * @{count}
3304 */
3305 void
3306f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3307{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003308 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003309 term_T *term;
3310 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003311 int max_height = 0;
3312 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003313 stat_T st;
3314 FILE *fd;
3315 VTermPos pos;
3316 VTermScreen *screen;
3317 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003318 VTermState *state;
3319 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003320
3321 if (check_restricted() || check_secure())
3322 return;
3323 if (buf == NULL)
3324 return;
3325 term = buf->b_term;
3326
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003327 if (argvars[2].v_type != VAR_UNKNOWN)
3328 {
3329 dict_T *d;
3330
3331 if (argvars[2].v_type != VAR_DICT)
3332 {
3333 EMSG(_(e_dictreq));
3334 return;
3335 }
3336 d = argvars[2].vval.v_dict;
3337 if (d != NULL)
3338 {
3339 max_height = get_dict_number(d, (char_u *)"rows");
3340 max_width = get_dict_number(d, (char_u *)"columns");
3341 }
3342 }
3343
Bram Moolenaard96ff162018-02-18 22:13:29 +01003344 fname = get_tv_string_chk(&argvars[1]);
3345 if (fname == NULL)
3346 return;
3347 if (mch_stat((char *)fname, &st) >= 0)
3348 {
3349 EMSG2(_("E953: File exists: %s"), fname);
3350 return;
3351 }
3352
Bram Moolenaard96ff162018-02-18 22:13:29 +01003353 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3354 {
3355 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3356 return;
3357 }
3358
3359 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3360
3361 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003362 state = vterm_obtain_state(term->tl_vterm);
3363 vterm_state_get_cursorpos(state, &cursor_pos);
3364
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003365 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3366 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003367 {
3368 int repeat = 0;
3369
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003370 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3371 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003372 {
3373 VTermScreenCell cell;
3374 int same_attr;
3375 int same_chars = TRUE;
3376 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003377 int is_cursor_pos = (pos.col == cursor_pos.col
3378 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003379
3380 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3381 vim_memset(&cell, 0, sizeof(cell));
3382
3383 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3384 {
3385 if (cell.chars[i] != prev_cell.chars[i])
3386 same_chars = FALSE;
3387 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3388 break;
3389 }
3390 same_attr = vtermAttr2hl(cell.attrs)
3391 == vtermAttr2hl(prev_cell.attrs)
3392 && same_color(&cell.fg, &prev_cell.fg)
3393 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003394 if (same_chars && cell.width == prev_cell.width && same_attr
3395 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003396 {
3397 ++repeat;
3398 }
3399 else
3400 {
3401 if (repeat > 0)
3402 {
3403 fprintf(fd, "@%d", repeat);
3404 repeat = 0;
3405 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003406 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003407
3408 if (cell.chars[0] == NUL)
3409 fputs(" ", fd);
3410 else
3411 {
3412 char_u charbuf[10];
3413 int len;
3414
3415 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3416 && cell.chars[i] != NUL; ++i)
3417 {
3418 len = utf_char2bytes(cell.chars[0], charbuf);
3419 fwrite(charbuf, len, 1, fd);
3420 }
3421 }
3422
3423 /* When only the characters differ we don't write anything, the
3424 * following "|", "@" or NL will indicate using the same
3425 * attributes. */
3426 if (cell.width != prev_cell.width || !same_attr)
3427 {
3428 if (cell.width == 2)
3429 {
3430 fputs("*", fd);
3431 ++pos.col;
3432 }
3433 else
3434 fputs("+", fd);
3435
3436 if (same_attr)
3437 {
3438 fputs("&", fd);
3439 }
3440 else
3441 {
3442 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3443 if (same_color(&cell.fg, &prev_cell.fg))
3444 fputs("&", fd);
3445 else
3446 {
3447 fputs("#", fd);
3448 dump_term_color(fd, &cell.fg);
3449 }
3450 if (same_color(&cell.bg, &prev_cell.bg))
3451 fputs("&", fd);
3452 else
3453 {
3454 fputs("#", fd);
3455 dump_term_color(fd, &cell.bg);
3456 }
3457 }
3458 }
3459
3460 prev_cell = cell;
3461 }
3462 }
3463 if (repeat > 0)
3464 fprintf(fd, "@%d", repeat);
3465 fputs("\n", fd);
3466 }
3467
3468 fclose(fd);
3469}
3470
3471/*
3472 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3473 */
3474 static void
3475dump_is_corrupt(garray_T *gap)
3476{
3477 ga_concat(gap, (char_u *)"CORRUPT");
3478}
3479
3480 static void
3481append_cell(garray_T *gap, cellattr_T *cell)
3482{
3483 if (ga_grow(gap, 1) == OK)
3484 {
3485 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3486 ++gap->ga_len;
3487 }
3488}
3489
3490/*
3491 * Read the dump file from "fd" and append lines to the current buffer.
3492 * Return the cell width of the longest line.
3493 */
3494 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003495read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003496{
3497 int c;
3498 garray_T ga_text;
3499 garray_T ga_cell;
3500 char_u *prev_char = NULL;
3501 int attr = 0;
3502 cellattr_T cell;
3503 term_T *term = curbuf->b_term;
3504 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003505 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003506
3507 ga_init2(&ga_text, 1, 90);
3508 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3509 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003510 cursor_pos->row = -1;
3511 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003512
3513 c = fgetc(fd);
3514 for (;;)
3515 {
3516 if (c == EOF)
3517 break;
3518 if (c == '\n')
3519 {
3520 /* End of a line: append it to the buffer. */
3521 if (ga_text.ga_data == NULL)
3522 dump_is_corrupt(&ga_text);
3523 if (ga_grow(&term->tl_scrollback, 1) == OK)
3524 {
3525 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3526 + term->tl_scrollback.ga_len;
3527
3528 if (max_cells < ga_cell.ga_len)
3529 max_cells = ga_cell.ga_len;
3530 line->sb_cols = ga_cell.ga_len;
3531 line->sb_cells = ga_cell.ga_data;
3532 line->sb_fill_attr = term->tl_default_color;
3533 ++term->tl_scrollback.ga_len;
3534 ga_init(&ga_cell);
3535
3536 ga_append(&ga_text, NUL);
3537 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3538 ga_text.ga_len, FALSE);
3539 }
3540 else
3541 ga_clear(&ga_cell);
3542 ga_text.ga_len = 0;
3543
3544 c = fgetc(fd);
3545 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003546 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003547 {
3548 int prev_len = ga_text.ga_len;
3549
Bram Moolenaar9271d052018-02-25 21:39:46 +01003550 if (c == '>')
3551 {
3552 if (cursor_pos->row != -1)
3553 dump_is_corrupt(&ga_text); /* duplicate cursor */
3554 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3555 cursor_pos->col = ga_cell.ga_len;
3556 }
3557
Bram Moolenaard96ff162018-02-18 22:13:29 +01003558 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3559 c = fgetc(fd);
3560 if (c != EOF)
3561 ga_append(&ga_text, c);
3562 for (;;)
3563 {
3564 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003565 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003566 || c == EOF || c == '\n')
3567 break;
3568 ga_append(&ga_text, c);
3569 }
3570
3571 /* save the character for repeating it */
3572 vim_free(prev_char);
3573 if (ga_text.ga_data != NULL)
3574 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3575 ga_text.ga_len - prev_len);
3576
Bram Moolenaar9271d052018-02-25 21:39:46 +01003577 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003578 {
3579 /* use all attributes from previous cell */
3580 }
3581 else if (c == '+' || c == '*')
3582 {
3583 int is_bg;
3584
3585 cell.width = c == '+' ? 1 : 2;
3586
3587 c = fgetc(fd);
3588 if (c == '&')
3589 {
3590 /* use same attr as previous cell */
3591 c = fgetc(fd);
3592 }
3593 else if (isdigit(c))
3594 {
3595 /* get the decimal attribute */
3596 attr = 0;
3597 while (isdigit(c))
3598 {
3599 attr = attr * 10 + (c - '0');
3600 c = fgetc(fd);
3601 }
3602 hl2vtermAttr(attr, &cell);
3603 }
3604 else
3605 dump_is_corrupt(&ga_text);
3606
3607 /* is_bg == 0: fg, is_bg == 1: bg */
3608 for (is_bg = 0; is_bg <= 1; ++is_bg)
3609 {
3610 if (c == '&')
3611 {
3612 /* use same color as previous cell */
3613 c = fgetc(fd);
3614 }
3615 else if (c == '#')
3616 {
3617 int red, green, blue, index = 0;
3618
3619 c = fgetc(fd);
3620 red = hex2nr(c);
3621 c = fgetc(fd);
3622 red = (red << 4) + hex2nr(c);
3623 c = fgetc(fd);
3624 green = hex2nr(c);
3625 c = fgetc(fd);
3626 green = (green << 4) + hex2nr(c);
3627 c = fgetc(fd);
3628 blue = hex2nr(c);
3629 c = fgetc(fd);
3630 blue = (blue << 4) + hex2nr(c);
3631 c = fgetc(fd);
3632 if (!isdigit(c))
3633 dump_is_corrupt(&ga_text);
3634 while (isdigit(c))
3635 {
3636 index = index * 10 + (c - '0');
3637 c = fgetc(fd);
3638 }
3639
3640 if (is_bg)
3641 {
3642 cell.bg.red = red;
3643 cell.bg.green = green;
3644 cell.bg.blue = blue;
3645 cell.bg.ansi_index = index;
3646 }
3647 else
3648 {
3649 cell.fg.red = red;
3650 cell.fg.green = green;
3651 cell.fg.blue = blue;
3652 cell.fg.ansi_index = index;
3653 }
3654 }
3655 else
3656 dump_is_corrupt(&ga_text);
3657 }
3658 }
3659 else
3660 dump_is_corrupt(&ga_text);
3661
3662 append_cell(&ga_cell, &cell);
3663 }
3664 else if (c == '@')
3665 {
3666 if (prev_char == NULL)
3667 dump_is_corrupt(&ga_text);
3668 else
3669 {
3670 int count = 0;
3671
3672 /* repeat previous character, get the count */
3673 for (;;)
3674 {
3675 c = fgetc(fd);
3676 if (!isdigit(c))
3677 break;
3678 count = count * 10 + (c - '0');
3679 }
3680
3681 while (count-- > 0)
3682 {
3683 ga_concat(&ga_text, prev_char);
3684 append_cell(&ga_cell, &cell);
3685 }
3686 }
3687 }
3688 else
3689 {
3690 dump_is_corrupt(&ga_text);
3691 c = fgetc(fd);
3692 }
3693 }
3694
3695 if (ga_text.ga_len > 0)
3696 {
3697 /* trailing characters after last NL */
3698 dump_is_corrupt(&ga_text);
3699 ga_append(&ga_text, NUL);
3700 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3701 ga_text.ga_len, FALSE);
3702 }
3703
3704 ga_clear(&ga_text);
3705 vim_free(prev_char);
3706
3707 return max_cells;
3708}
3709
3710/*
3711 * Common for "term_dumpdiff()" and "term_dumpload()".
3712 */
3713 static void
3714term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
3715{
3716 jobopt_T opt;
3717 buf_T *buf;
3718 char_u buf1[NUMBUFLEN];
3719 char_u buf2[NUMBUFLEN];
3720 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003721 char_u *fname2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003722 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003723 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003724 char_u *textline = NULL;
3725
3726 /* First open the files. If this fails bail out. */
3727 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
3728 if (do_diff)
3729 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
3730 if (fname1 == NULL || (do_diff && fname2 == NULL))
3731 {
3732 EMSG(_(e_invarg));
3733 return;
3734 }
3735 fd1 = mch_fopen((char *)fname1, READBIN);
3736 if (fd1 == NULL)
3737 {
3738 EMSG2(_(e_notread), fname1);
3739 return;
3740 }
3741 if (do_diff)
3742 {
3743 fd2 = mch_fopen((char *)fname2, READBIN);
3744 if (fd2 == NULL)
3745 {
3746 fclose(fd1);
3747 EMSG2(_(e_notread), fname2);
3748 return;
3749 }
3750 }
3751
3752 init_job_options(&opt);
3753 /* TODO: use the {options} argument */
3754
3755 /* TODO: use the file name arguments for the buffer name */
3756 opt.jo_term_name = (char_u *)"dump diff";
3757
Bram Moolenaar13568252018-03-16 20:46:58 +01003758 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003759 if (buf != NULL && buf->b_term != NULL)
3760 {
3761 int i;
3762 linenr_T bot_lnum;
3763 linenr_T lnum;
3764 term_T *term = buf->b_term;
3765 int width;
3766 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003767 VTermPos cursor_pos1;
3768 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003769
3770 rettv->vval.v_number = buf->b_fnum;
3771
3772 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01003773 width = read_dump_file(fd1, &cursor_pos1);
3774
3775 /* position the cursor */
3776 if (cursor_pos1.row >= 0)
3777 {
3778 curwin->w_cursor.lnum = cursor_pos1.row + 1;
3779 coladvance(cursor_pos1.col);
3780 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003781
3782 /* Delete the empty line that was in the empty buffer. */
3783 ml_delete(1, FALSE);
3784
3785 /* For term_dumpload() we are done here. */
3786 if (!do_diff)
3787 goto theend;
3788
3789 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
3790
3791 textline = alloc(width + 1);
3792 if (textline == NULL)
3793 goto theend;
3794 for (i = 0; i < width; ++i)
3795 textline[i] = '=';
3796 textline[width] = NUL;
3797 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3798 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3799 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3800 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3801
3802 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003803 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003804 if (width2 > width)
3805 {
3806 vim_free(textline);
3807 textline = alloc(width2 + 1);
3808 if (textline == NULL)
3809 goto theend;
3810 width = width2;
3811 textline[width] = NUL;
3812 }
3813 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
3814
3815 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
3816 {
3817 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
3818 {
3819 /* bottom part has fewer rows, fill with "-" */
3820 for (i = 0; i < width; ++i)
3821 textline[i] = '-';
3822 }
3823 else
3824 {
3825 char_u *line1;
3826 char_u *line2;
3827 char_u *p1;
3828 char_u *p2;
3829 int col;
3830 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3831 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
3832 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
3833 ->sb_cells;
3834
3835 /* Make a copy, getting the second line will invalidate it. */
3836 line1 = vim_strsave(ml_get(lnum));
3837 if (line1 == NULL)
3838 break;
3839 p1 = line1;
3840
3841 line2 = ml_get(lnum + bot_lnum);
3842 p2 = line2;
3843 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
3844 {
3845 int len1 = utfc_ptr2len(p1);
3846 int len2 = utfc_ptr2len(p2);
3847
3848 textline[col] = ' ';
3849 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01003850 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01003851 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01003852 else if (lnum == cursor_pos1.row + 1
3853 && col == cursor_pos1.col
3854 && (cursor_pos1.row != cursor_pos2.row
3855 || cursor_pos1.col != cursor_pos2.col))
3856 /* cursor in first but not in second */
3857 textline[col] = '>';
3858 else if (lnum == cursor_pos2.row + 1
3859 && col == cursor_pos2.col
3860 && (cursor_pos1.row != cursor_pos2.row
3861 || cursor_pos1.col != cursor_pos2.col))
3862 /* cursor in second but not in first */
3863 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01003864 else if (cellattr1 != NULL && cellattr2 != NULL)
3865 {
3866 if ((cellattr1 + col)->width
3867 != (cellattr2 + col)->width)
3868 textline[col] = 'w';
3869 else if (!same_color(&(cellattr1 + col)->fg,
3870 &(cellattr2 + col)->fg))
3871 textline[col] = 'f';
3872 else if (!same_color(&(cellattr1 + col)->bg,
3873 &(cellattr2 + col)->bg))
3874 textline[col] = 'b';
3875 else if (vtermAttr2hl((cellattr1 + col)->attrs)
3876 != vtermAttr2hl(((cellattr2 + col)->attrs)))
3877 textline[col] = 'a';
3878 }
3879 p1 += len1;
3880 p2 += len2;
3881 /* TODO: handle different width */
3882 }
3883 vim_free(line1);
3884
3885 while (col < width)
3886 {
3887 if (*p1 == NUL && *p2 == NUL)
3888 textline[col] = '?';
3889 else if (*p1 == NUL)
3890 {
3891 textline[col] = '+';
3892 p2 += utfc_ptr2len(p2);
3893 }
3894 else
3895 {
3896 textline[col] = '-';
3897 p1 += utfc_ptr2len(p1);
3898 }
3899 ++col;
3900 }
3901 }
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 ++bot_lnum;
3906 }
3907
3908 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
3909 {
3910 /* bottom part has more rows, fill with "+" */
3911 for (i = 0; i < width; ++i)
3912 textline[i] = '+';
3913 if (add_empty_scrollback(term, &term->tl_default_color,
3914 term->tl_top_diff_rows) == OK)
3915 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3916 ++lnum;
3917 ++bot_lnum;
3918 }
3919
3920 term->tl_cols = width;
3921 }
3922
3923theend:
3924 vim_free(textline);
3925 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003926 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003927 fclose(fd2);
3928}
3929
3930/*
3931 * If the current buffer shows the output of term_dumpdiff(), swap the top and
3932 * bottom files.
3933 * Return FAIL when this is not possible.
3934 */
3935 int
3936term_swap_diff()
3937{
3938 term_T *term = curbuf->b_term;
3939 linenr_T line_count;
3940 linenr_T top_rows;
3941 linenr_T bot_rows;
3942 linenr_T bot_start;
3943 linenr_T lnum;
3944 char_u *p;
3945 sb_line_T *sb_line;
3946
3947 if (term == NULL
3948 || !term_is_finished(curbuf)
3949 || term->tl_top_diff_rows == 0
3950 || term->tl_scrollback.ga_len == 0)
3951 return FAIL;
3952
3953 line_count = curbuf->b_ml.ml_line_count;
3954 top_rows = term->tl_top_diff_rows;
3955 bot_rows = term->tl_bot_diff_rows;
3956 bot_start = line_count - bot_rows;
3957 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3958
3959 /* move lines from top to above the bottom part */
3960 for (lnum = 1; lnum <= top_rows; ++lnum)
3961 {
3962 p = vim_strsave(ml_get(1));
3963 if (p == NULL)
3964 return OK;
3965 ml_append(bot_start, p, 0, FALSE);
3966 ml_delete(1, FALSE);
3967 vim_free(p);
3968 }
3969
3970 /* move lines from bottom to the top */
3971 for (lnum = 1; lnum <= bot_rows; ++lnum)
3972 {
3973 p = vim_strsave(ml_get(bot_start + lnum));
3974 if (p == NULL)
3975 return OK;
3976 ml_delete(bot_start + lnum, FALSE);
3977 ml_append(lnum - 1, p, 0, FALSE);
3978 vim_free(p);
3979 }
3980
3981 if (top_rows == bot_rows)
3982 {
3983 /* rows counts are equal, can swap cell properties */
3984 for (lnum = 0; lnum < top_rows; ++lnum)
3985 {
3986 sb_line_T temp;
3987
3988 temp = *(sb_line + lnum);
3989 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
3990 *(sb_line + bot_start + lnum) = temp;
3991 }
3992 }
3993 else
3994 {
3995 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
3996 sb_line_T *temp = (sb_line_T *)alloc((int)size);
3997
3998 /* need to copy cell properties into temp memory */
3999 if (temp != NULL)
4000 {
4001 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4002 mch_memmove(term->tl_scrollback.ga_data,
4003 temp + bot_start,
4004 sizeof(sb_line_T) * bot_rows);
4005 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4006 temp + top_rows,
4007 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4008 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4009 + line_count - top_rows,
4010 temp,
4011 sizeof(sb_line_T) * top_rows);
4012 vim_free(temp);
4013 }
4014 }
4015
4016 term->tl_top_diff_rows = bot_rows;
4017 term->tl_bot_diff_rows = top_rows;
4018
4019 update_screen(NOT_VALID);
4020 return OK;
4021}
4022
4023/*
4024 * "term_dumpdiff(filename, filename, options)" function
4025 */
4026 void
4027f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4028{
4029 term_load_dump(argvars, rettv, TRUE);
4030}
4031
4032/*
4033 * "term_dumpload(filename, options)" function
4034 */
4035 void
4036f_term_dumpload(typval_T *argvars, typval_T *rettv)
4037{
4038 term_load_dump(argvars, rettv, FALSE);
4039}
4040
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004041/*
4042 * "term_getaltscreen(buf)" function
4043 */
4044 void
4045f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4046{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004047 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004048
4049 if (buf == NULL)
4050 return;
4051 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4052}
4053
4054/*
4055 * "term_getattr(attr, name)" function
4056 */
4057 void
4058f_term_getattr(typval_T *argvars, typval_T *rettv)
4059{
4060 int attr;
4061 size_t i;
4062 char_u *name;
4063
4064 static struct {
4065 char *name;
4066 int attr;
4067 } attrs[] = {
4068 {"bold", HL_BOLD},
4069 {"italic", HL_ITALIC},
4070 {"underline", HL_UNDERLINE},
4071 {"strike", HL_STRIKETHROUGH},
4072 {"reverse", HL_INVERSE},
4073 };
4074
4075 attr = get_tv_number(&argvars[0]);
4076 name = get_tv_string_chk(&argvars[1]);
4077 if (name == NULL)
4078 return;
4079
4080 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4081 if (STRCMP(name, attrs[i].name) == 0)
4082 {
4083 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4084 break;
4085 }
4086}
4087
4088/*
4089 * "term_getcursor(buf)" function
4090 */
4091 void
4092f_term_getcursor(typval_T *argvars, typval_T *rettv)
4093{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004094 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004095 term_T *term;
4096 list_T *l;
4097 dict_T *d;
4098
4099 if (rettv_list_alloc(rettv) == FAIL)
4100 return;
4101 if (buf == NULL)
4102 return;
4103 term = buf->b_term;
4104
4105 l = rettv->vval.v_list;
4106 list_append_number(l, term->tl_cursor_pos.row + 1);
4107 list_append_number(l, term->tl_cursor_pos.col + 1);
4108
4109 d = dict_alloc();
4110 if (d != NULL)
4111 {
4112 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4113 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4114 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4115 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4116 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4117 ? (char_u *)"" : term->tl_cursor_color);
4118 list_append_dict(l, d);
4119 }
4120}
4121
4122/*
4123 * "term_getjob(buf)" function
4124 */
4125 void
4126f_term_getjob(typval_T *argvars, typval_T *rettv)
4127{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004128 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004129
4130 rettv->v_type = VAR_JOB;
4131 rettv->vval.v_job = NULL;
4132 if (buf == NULL)
4133 return;
4134
4135 rettv->vval.v_job = buf->b_term->tl_job;
4136 if (rettv->vval.v_job != NULL)
4137 ++rettv->vval.v_job->jv_refcount;
4138}
4139
4140 static int
4141get_row_number(typval_T *tv, term_T *term)
4142{
4143 if (tv->v_type == VAR_STRING
4144 && tv->vval.v_string != NULL
4145 && STRCMP(tv->vval.v_string, ".") == 0)
4146 return term->tl_cursor_pos.row;
4147 return (int)get_tv_number(tv) - 1;
4148}
4149
4150/*
4151 * "term_getline(buf, row)" function
4152 */
4153 void
4154f_term_getline(typval_T *argvars, typval_T *rettv)
4155{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004156 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004157 term_T *term;
4158 int row;
4159
4160 rettv->v_type = VAR_STRING;
4161 if (buf == NULL)
4162 return;
4163 term = buf->b_term;
4164 row = get_row_number(&argvars[1], term);
4165
4166 if (term->tl_vterm == NULL)
4167 {
4168 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4169
4170 /* vterm is finished, get the text from the buffer */
4171 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4172 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4173 }
4174 else
4175 {
4176 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4177 VTermRect rect;
4178 int len;
4179 char_u *p;
4180
4181 if (row < 0 || row >= term->tl_rows)
4182 return;
4183 len = term->tl_cols * MB_MAXBYTES + 1;
4184 p = alloc(len);
4185 if (p == NULL)
4186 return;
4187 rettv->vval.v_string = p;
4188
4189 rect.start_col = 0;
4190 rect.end_col = term->tl_cols;
4191 rect.start_row = row;
4192 rect.end_row = row + 1;
4193 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4194 }
4195}
4196
4197/*
4198 * "term_getscrolled(buf)" function
4199 */
4200 void
4201f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4202{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004203 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004204
4205 if (buf == NULL)
4206 return;
4207 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4208}
4209
4210/*
4211 * "term_getsize(buf)" function
4212 */
4213 void
4214f_term_getsize(typval_T *argvars, typval_T *rettv)
4215{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004216 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004217 list_T *l;
4218
4219 if (rettv_list_alloc(rettv) == FAIL)
4220 return;
4221 if (buf == NULL)
4222 return;
4223
4224 l = rettv->vval.v_list;
4225 list_append_number(l, buf->b_term->tl_rows);
4226 list_append_number(l, buf->b_term->tl_cols);
4227}
4228
4229/*
4230 * "term_getstatus(buf)" function
4231 */
4232 void
4233f_term_getstatus(typval_T *argvars, typval_T *rettv)
4234{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004235 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004236 term_T *term;
4237 char_u val[100];
4238
4239 rettv->v_type = VAR_STRING;
4240 if (buf == NULL)
4241 return;
4242 term = buf->b_term;
4243
4244 if (term_job_running(term))
4245 STRCPY(val, "running");
4246 else
4247 STRCPY(val, "finished");
4248 if (term->tl_normal_mode)
4249 STRCAT(val, ",normal");
4250 rettv->vval.v_string = vim_strsave(val);
4251}
4252
4253/*
4254 * "term_gettitle(buf)" function
4255 */
4256 void
4257f_term_gettitle(typval_T *argvars, typval_T *rettv)
4258{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004259 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004260
4261 rettv->v_type = VAR_STRING;
4262 if (buf == NULL)
4263 return;
4264
4265 if (buf->b_term->tl_title != NULL)
4266 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4267}
4268
4269/*
4270 * "term_gettty(buf)" function
4271 */
4272 void
4273f_term_gettty(typval_T *argvars, typval_T *rettv)
4274{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004275 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004276 char_u *p;
4277 int num = 0;
4278
4279 rettv->v_type = VAR_STRING;
4280 if (buf == NULL)
4281 return;
4282 if (argvars[1].v_type != VAR_UNKNOWN)
4283 num = get_tv_number(&argvars[1]);
4284
4285 switch (num)
4286 {
4287 case 0:
4288 if (buf->b_term->tl_job != NULL)
4289 p = buf->b_term->tl_job->jv_tty_out;
4290 else
4291 p = buf->b_term->tl_tty_out;
4292 break;
4293 case 1:
4294 if (buf->b_term->tl_job != NULL)
4295 p = buf->b_term->tl_job->jv_tty_in;
4296 else
4297 p = buf->b_term->tl_tty_in;
4298 break;
4299 default:
4300 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4301 return;
4302 }
4303 if (p != NULL)
4304 rettv->vval.v_string = vim_strsave(p);
4305}
4306
4307/*
4308 * "term_list()" function
4309 */
4310 void
4311f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4312{
4313 term_T *tp;
4314 list_T *l;
4315
4316 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4317 return;
4318
4319 l = rettv->vval.v_list;
4320 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4321 if (tp != NULL && tp->tl_buffer != NULL)
4322 if (list_append_number(l,
4323 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4324 return;
4325}
4326
4327/*
4328 * "term_scrape(buf, row)" function
4329 */
4330 void
4331f_term_scrape(typval_T *argvars, typval_T *rettv)
4332{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004333 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004334 VTermScreen *screen = NULL;
4335 VTermPos pos;
4336 list_T *l;
4337 term_T *term;
4338 char_u *p;
4339 sb_line_T *line;
4340
4341 if (rettv_list_alloc(rettv) == FAIL)
4342 return;
4343 if (buf == NULL)
4344 return;
4345 term = buf->b_term;
4346
4347 l = rettv->vval.v_list;
4348 pos.row = get_row_number(&argvars[1], term);
4349
4350 if (term->tl_vterm != NULL)
4351 {
4352 screen = vterm_obtain_screen(term->tl_vterm);
4353 p = NULL;
4354 line = NULL;
4355 }
4356 else
4357 {
4358 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4359
4360 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4361 return;
4362 p = ml_get_buf(buf, lnum + 1, FALSE);
4363 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4364 }
4365
4366 for (pos.col = 0; pos.col < term->tl_cols; )
4367 {
4368 dict_T *dcell;
4369 int width;
4370 VTermScreenCellAttrs attrs;
4371 VTermColor fg, bg;
4372 char_u rgb[8];
4373 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4374 int off = 0;
4375 int i;
4376
4377 if (screen == NULL)
4378 {
4379 cellattr_T *cellattr;
4380 int len;
4381
4382 /* vterm has finished, get the cell from scrollback */
4383 if (pos.col >= line->sb_cols)
4384 break;
4385 cellattr = line->sb_cells + pos.col;
4386 width = cellattr->width;
4387 attrs = cellattr->attrs;
4388 fg = cellattr->fg;
4389 bg = cellattr->bg;
4390 len = MB_PTR2LEN(p);
4391 mch_memmove(mbs, p, len);
4392 mbs[len] = NUL;
4393 p += len;
4394 }
4395 else
4396 {
4397 VTermScreenCell cell;
4398 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4399 break;
4400 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4401 {
4402 if (cell.chars[i] == 0)
4403 break;
4404 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4405 }
4406 mbs[off] = NUL;
4407 width = cell.width;
4408 attrs = cell.attrs;
4409 fg = cell.fg;
4410 bg = cell.bg;
4411 }
4412 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004413 if (dcell == NULL)
4414 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004415 list_append_dict(l, dcell);
4416
4417 dict_add_nr_str(dcell, "chars", 0, mbs);
4418
4419 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4420 fg.red, fg.green, fg.blue);
4421 dict_add_nr_str(dcell, "fg", 0, rgb);
4422 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4423 bg.red, bg.green, bg.blue);
4424 dict_add_nr_str(dcell, "bg", 0, rgb);
4425
4426 dict_add_nr_str(dcell, "attr",
4427 cell2attr(attrs, fg, bg), NULL);
4428 dict_add_nr_str(dcell, "width", width, NULL);
4429
4430 ++pos.col;
4431 if (width == 2)
4432 ++pos.col;
4433 }
4434}
4435
4436/*
4437 * "term_sendkeys(buf, keys)" function
4438 */
4439 void
4440f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4441{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004442 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004443 char_u *msg;
4444 term_T *term;
4445
4446 rettv->v_type = VAR_UNKNOWN;
4447 if (buf == NULL)
4448 return;
4449
4450 msg = get_tv_string_chk(&argvars[1]);
4451 if (msg == NULL)
4452 return;
4453 term = buf->b_term;
4454 if (term->tl_vterm == NULL)
4455 return;
4456
4457 while (*msg != NUL)
4458 {
4459 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004460 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004461 }
4462}
4463
4464/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004465 * "term_setrestore(buf, command)" function
4466 */
4467 void
4468f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4469{
4470#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004471 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004472 term_T *term;
4473 char_u *cmd;
4474
4475 if (buf == NULL)
4476 return;
4477 term = buf->b_term;
4478 vim_free(term->tl_command);
4479 cmd = get_tv_string_chk(&argvars[1]);
4480 if (cmd != NULL)
4481 term->tl_command = vim_strsave(cmd);
4482 else
4483 term->tl_command = NULL;
4484#endif
4485}
4486
4487/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004488 * "term_setkill(buf, how)" function
4489 */
4490 void
4491f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4492{
4493 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4494 term_T *term;
4495 char_u *how;
4496
4497 if (buf == NULL)
4498 return;
4499 term = buf->b_term;
4500 vim_free(term->tl_kill);
4501 how = get_tv_string_chk(&argvars[1]);
4502 if (how != NULL)
4503 term->tl_kill = vim_strsave(how);
4504 else
4505 term->tl_kill = NULL;
4506}
4507
4508/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004509 * "term_start(command, options)" function
4510 */
4511 void
4512f_term_start(typval_T *argvars, typval_T *rettv)
4513{
4514 jobopt_T opt;
4515 buf_T *buf;
4516
4517 init_job_options(&opt);
4518 if (argvars[1].v_type != VAR_UNKNOWN
4519 && get_job_options(&argvars[1], &opt,
4520 JO_TIMEOUT_ALL + JO_STOPONEXIT
4521 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
4522 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
4523 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
4524 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004525 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004526 + JO2_NORESTORE + JO2_TERM_KILL) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004527 return;
4528
4529 if (opt.jo_vertical)
4530 cmdmod.split = WSP_VERT;
Bram Moolenaar13568252018-03-16 20:46:58 +01004531 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004532
4533 if (buf != NULL && buf->b_term != NULL)
4534 rettv->vval.v_number = buf->b_fnum;
4535}
4536
4537/*
4538 * "term_wait" function
4539 */
4540 void
4541f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
4542{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004543 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004544
4545 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004546 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004547 if (buf->b_term->tl_job == NULL)
4548 {
4549 ch_log(NULL, "term_wait(): no job to wait for");
4550 return;
4551 }
4552 if (buf->b_term->tl_job->jv_channel == NULL)
4553 /* channel is closed, nothing to do */
4554 return;
4555
4556 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01004557 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004558 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
4559 {
4560 /* The job is dead, keep reading channel I/O until the channel is
4561 * closed. buf->b_term may become NULL if the terminal was closed while
4562 * waiting. */
4563 ch_log(NULL, "term_wait(): waiting for channel to close");
4564 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
4565 {
4566 mch_check_messages();
4567 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01004568 if (!buf_valid(buf))
4569 /* If the terminal is closed when the channel is closed the
4570 * buffer disappears. */
4571 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004572 ui_delay(10L, FALSE);
4573 }
4574 mch_check_messages();
4575 parse_queued_messages();
4576 }
4577 else
4578 {
4579 long wait = 10L;
4580
4581 mch_check_messages();
4582 parse_queued_messages();
4583
4584 /* Wait for some time for any channel I/O. */
4585 if (argvars[1].v_type != VAR_UNKNOWN)
4586 wait = get_tv_number(&argvars[1]);
4587 ui_delay(wait, TRUE);
4588 mch_check_messages();
4589
4590 /* Flushing messages on channels is hopefully sufficient.
4591 * TODO: is there a better way? */
4592 parse_queued_messages();
4593 }
4594}
4595
4596/*
4597 * Called when a channel has sent all the lines to a terminal.
4598 * Send a CTRL-D to mark the end of the text.
4599 */
4600 void
4601term_send_eof(channel_T *ch)
4602{
4603 term_T *term;
4604
4605 for (term = first_term; term != NULL; term = term->tl_next)
4606 if (term->tl_job == ch->ch_job)
4607 {
4608 if (term->tl_eof_chars != NULL)
4609 {
4610 channel_send(ch, PART_IN, term->tl_eof_chars,
4611 (int)STRLEN(term->tl_eof_chars), NULL);
4612 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
4613 }
4614# ifdef WIN3264
4615 else
4616 /* Default: CTRL-D */
4617 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
4618# endif
4619 }
4620}
4621
4622# if defined(WIN3264) || defined(PROTO)
4623
4624/**************************************
4625 * 2. MS-Windows implementation.
4626 */
4627
4628# ifndef PROTO
4629
4630#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
4631#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01004632#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004633
4634void* (*winpty_config_new)(UINT64, void*);
4635void* (*winpty_open)(void*, void*);
4636void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
4637BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
4638void (*winpty_config_set_mouse_mode)(void*, int);
4639void (*winpty_config_set_initial_size)(void*, int, int);
4640LPCWSTR (*winpty_conin_name)(void*);
4641LPCWSTR (*winpty_conout_name)(void*);
4642LPCWSTR (*winpty_conerr_name)(void*);
4643void (*winpty_free)(void*);
4644void (*winpty_config_free)(void*);
4645void (*winpty_spawn_config_free)(void*);
4646void (*winpty_error_free)(void*);
4647LPCWSTR (*winpty_error_msg)(void*);
4648BOOL (*winpty_set_size)(void*, int, int, void*);
4649HANDLE (*winpty_agent_process)(void*);
4650
4651#define WINPTY_DLL "winpty.dll"
4652
4653static HINSTANCE hWinPtyDLL = NULL;
4654# endif
4655
4656 static int
4657dyn_winpty_init(int verbose)
4658{
4659 int i;
4660 static struct
4661 {
4662 char *name;
4663 FARPROC *ptr;
4664 } winpty_entry[] =
4665 {
4666 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
4667 {"winpty_config_free", (FARPROC*)&winpty_config_free},
4668 {"winpty_config_new", (FARPROC*)&winpty_config_new},
4669 {"winpty_config_set_mouse_mode",
4670 (FARPROC*)&winpty_config_set_mouse_mode},
4671 {"winpty_config_set_initial_size",
4672 (FARPROC*)&winpty_config_set_initial_size},
4673 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
4674 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
4675 {"winpty_error_free", (FARPROC*)&winpty_error_free},
4676 {"winpty_free", (FARPROC*)&winpty_free},
4677 {"winpty_open", (FARPROC*)&winpty_open},
4678 {"winpty_spawn", (FARPROC*)&winpty_spawn},
4679 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
4680 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
4681 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
4682 {"winpty_set_size", (FARPROC*)&winpty_set_size},
4683 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
4684 {NULL, NULL}
4685 };
4686
4687 /* No need to initialize twice. */
4688 if (hWinPtyDLL)
4689 return OK;
4690 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
4691 * winpty.dll. */
4692 if (*p_winptydll != NUL)
4693 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
4694 if (!hWinPtyDLL)
4695 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
4696 if (!hWinPtyDLL)
4697 {
4698 if (verbose)
4699 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
4700 : (char_u *)WINPTY_DLL);
4701 return FAIL;
4702 }
4703 for (i = 0; winpty_entry[i].name != NULL
4704 && winpty_entry[i].ptr != NULL; ++i)
4705 {
4706 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
4707 winpty_entry[i].name)) == NULL)
4708 {
4709 if (verbose)
4710 EMSG2(_(e_loadfunc), winpty_entry[i].name);
4711 return FAIL;
4712 }
4713 }
4714
4715 return OK;
4716}
4717
4718/*
4719 * Create a new terminal of "rows" by "cols" cells.
4720 * Store a reference in "term".
4721 * Return OK or FAIL.
4722 */
4723 static int
4724term_and_job_init(
4725 term_T *term,
4726 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01004727 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004728 jobopt_T *opt)
4729{
4730 WCHAR *cmd_wchar = NULL;
4731 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004732 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004733 channel_T *channel = NULL;
4734 job_T *job = NULL;
4735 DWORD error;
4736 HANDLE jo = NULL;
4737 HANDLE child_process_handle;
4738 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01004739 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004740 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004741 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004742 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004743
4744 if (dyn_winpty_init(TRUE) == FAIL)
4745 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004746 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
4747 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004748
4749 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004750 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004751 cmd = argvar->vval.v_string;
4752 }
4753 else if (argvar->v_type == VAR_LIST)
4754 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004755 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004756 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004757 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004758 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004759 if (cmd == NULL || *cmd == NUL)
4760 {
4761 EMSG(_(e_invarg));
4762 goto failed;
4763 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004764
4765 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004766 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004767 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004768 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004769 if (opt->jo_cwd != NULL)
4770 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004771
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004772 win32_build_env(opt->jo_env, &ga_env, TRUE);
4773 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004774
4775 job = job_alloc();
4776 if (job == NULL)
4777 goto failed;
4778
4779 channel = add_channel();
4780 if (channel == NULL)
4781 goto failed;
4782
4783 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
4784 if (term->tl_winpty_config == NULL)
4785 goto failed;
4786
4787 winpty_config_set_mouse_mode(term->tl_winpty_config,
4788 WINPTY_MOUSE_MODE_FORCE);
4789 winpty_config_set_initial_size(term->tl_winpty_config,
4790 term->tl_cols, term->tl_rows);
4791 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
4792 if (term->tl_winpty == NULL)
4793 goto failed;
4794
4795 spawn_config = winpty_spawn_config_new(
4796 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
4797 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
4798 NULL,
4799 cmd_wchar,
4800 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004801 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004802 &winpty_err);
4803 if (spawn_config == NULL)
4804 goto failed;
4805
4806 channel = add_channel();
4807 if (channel == NULL)
4808 goto failed;
4809
4810 job = job_alloc();
4811 if (job == NULL)
4812 goto failed;
4813
4814 if (opt->jo_set & JO_IN_BUF)
4815 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
4816
4817 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
4818 &child_thread_handle, &error, &winpty_err))
4819 goto failed;
4820
4821 channel_set_pipes(channel,
4822 (sock_T)CreateFileW(
4823 winpty_conin_name(term->tl_winpty),
4824 GENERIC_WRITE, 0, NULL,
4825 OPEN_EXISTING, 0, NULL),
4826 (sock_T)CreateFileW(
4827 winpty_conout_name(term->tl_winpty),
4828 GENERIC_READ, 0, NULL,
4829 OPEN_EXISTING, 0, NULL),
4830 (sock_T)CreateFileW(
4831 winpty_conerr_name(term->tl_winpty),
4832 GENERIC_READ, 0, NULL,
4833 OPEN_EXISTING, 0, NULL));
4834
4835 /* Write lines with CR instead of NL. */
4836 channel->ch_write_text_mode = TRUE;
4837
4838 jo = CreateJobObject(NULL, NULL);
4839 if (jo == NULL)
4840 goto failed;
4841
4842 if (!AssignProcessToJobObject(jo, child_process_handle))
4843 {
4844 /* Failed, switch the way to terminate process with TerminateProcess. */
4845 CloseHandle(jo);
4846 jo = NULL;
4847 }
4848
4849 winpty_spawn_config_free(spawn_config);
4850 vim_free(cmd_wchar);
4851 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004852 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004853
4854 create_vterm(term, term->tl_rows, term->tl_cols);
4855
4856 channel_set_job(channel, job, opt);
4857 job_set_options(job, opt);
4858
4859 job->jv_channel = channel;
4860 job->jv_proc_info.hProcess = child_process_handle;
4861 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
4862 job->jv_job_object = jo;
4863 job->jv_status = JOB_STARTED;
4864 job->jv_tty_in = utf16_to_enc(
4865 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
4866 job->jv_tty_out = utf16_to_enc(
4867 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
4868 ++job->jv_refcount;
4869 term->tl_job = job;
4870
4871 return OK;
4872
4873failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004874 ga_clear(&ga_cmd);
4875 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004876 vim_free(cmd_wchar);
4877 vim_free(cwd_wchar);
4878 if (spawn_config != NULL)
4879 winpty_spawn_config_free(spawn_config);
4880 if (channel != NULL)
4881 channel_clear(channel);
4882 if (job != NULL)
4883 {
4884 job->jv_channel = NULL;
4885 job_cleanup(job);
4886 }
4887 term->tl_job = NULL;
4888 if (jo != NULL)
4889 CloseHandle(jo);
4890 if (term->tl_winpty != NULL)
4891 winpty_free(term->tl_winpty);
4892 term->tl_winpty = NULL;
4893 if (term->tl_winpty_config != NULL)
4894 winpty_config_free(term->tl_winpty_config);
4895 term->tl_winpty_config = NULL;
4896 if (winpty_err != NULL)
4897 {
4898 char_u *msg = utf16_to_enc(
4899 (short_u *)winpty_error_msg(winpty_err), NULL);
4900
4901 EMSG(msg);
4902 winpty_error_free(winpty_err);
4903 }
4904 return FAIL;
4905}
4906
4907 static int
4908create_pty_only(term_T *term, jobopt_T *options)
4909{
4910 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
4911 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
4912 char in_name[80], out_name[80];
4913 channel_T *channel = NULL;
4914
4915 create_vterm(term, term->tl_rows, term->tl_cols);
4916
4917 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
4918 GetCurrentProcessId(),
4919 curbuf->b_fnum);
4920 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
4921 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4922 PIPE_UNLIMITED_INSTANCES,
4923 0, 0, NMPWAIT_NOWAIT, NULL);
4924 if (hPipeIn == INVALID_HANDLE_VALUE)
4925 goto failed;
4926
4927 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
4928 GetCurrentProcessId(),
4929 curbuf->b_fnum);
4930 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
4931 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4932 PIPE_UNLIMITED_INSTANCES,
4933 0, 0, 0, NULL);
4934 if (hPipeOut == INVALID_HANDLE_VALUE)
4935 goto failed;
4936
4937 ConnectNamedPipe(hPipeIn, NULL);
4938 ConnectNamedPipe(hPipeOut, NULL);
4939
4940 term->tl_job = job_alloc();
4941 if (term->tl_job == NULL)
4942 goto failed;
4943 ++term->tl_job->jv_refcount;
4944
4945 /* behave like the job is already finished */
4946 term->tl_job->jv_status = JOB_FINISHED;
4947
4948 channel = add_channel();
4949 if (channel == NULL)
4950 goto failed;
4951 term->tl_job->jv_channel = channel;
4952 channel->ch_keep_open = TRUE;
4953 channel->ch_named_pipe = TRUE;
4954
4955 channel_set_pipes(channel,
4956 (sock_T)hPipeIn,
4957 (sock_T)hPipeOut,
4958 (sock_T)hPipeOut);
4959 channel_set_job(channel, term->tl_job, options);
4960 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
4961 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
4962
4963 return OK;
4964
4965failed:
4966 if (hPipeIn != NULL)
4967 CloseHandle(hPipeIn);
4968 if (hPipeOut != NULL)
4969 CloseHandle(hPipeOut);
4970 return FAIL;
4971}
4972
4973/*
4974 * Free the terminal emulator part of "term".
4975 */
4976 static void
4977term_free_vterm(term_T *term)
4978{
4979 if (term->tl_winpty != NULL)
4980 winpty_free(term->tl_winpty);
4981 term->tl_winpty = NULL;
4982 if (term->tl_winpty_config != NULL)
4983 winpty_config_free(term->tl_winpty_config);
4984 term->tl_winpty_config = NULL;
4985 if (term->tl_vterm != NULL)
4986 vterm_free(term->tl_vterm);
4987 term->tl_vterm = NULL;
4988}
4989
4990/*
4991 * Request size to terminal.
4992 */
4993 static void
4994term_report_winsize(term_T *term, int rows, int cols)
4995{
4996 if (term->tl_winpty)
4997 winpty_set_size(term->tl_winpty, cols, rows, NULL);
4998}
4999
5000 int
5001terminal_enabled(void)
5002{
5003 return dyn_winpty_init(FALSE) == OK;
5004}
5005
5006# else
5007
5008/**************************************
5009 * 3. Unix-like implementation.
5010 */
5011
5012/*
5013 * Create a new terminal of "rows" by "cols" cells.
5014 * Start job for "cmd".
5015 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005016 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005017 * Return OK or FAIL.
5018 */
5019 static int
5020term_and_job_init(
5021 term_T *term,
5022 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005023 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005024 jobopt_T *opt)
5025{
5026 create_vterm(term, term->tl_rows, term->tl_cols);
5027
Bram Moolenaar13568252018-03-16 20:46:58 +01005028 /* This may change a string in "argvar". */
5029 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005030 if (term->tl_job != NULL)
5031 ++term->tl_job->jv_refcount;
5032
5033 return term->tl_job != NULL
5034 && term->tl_job->jv_channel != NULL
5035 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5036}
5037
5038 static int
5039create_pty_only(term_T *term, jobopt_T *opt)
5040{
5041 create_vterm(term, term->tl_rows, term->tl_cols);
5042
5043 term->tl_job = job_alloc();
5044 if (term->tl_job == NULL)
5045 return FAIL;
5046 ++term->tl_job->jv_refcount;
5047
5048 /* behave like the job is already finished */
5049 term->tl_job->jv_status = JOB_FINISHED;
5050
5051 return mch_create_pty_channel(term->tl_job, opt);
5052}
5053
5054/*
5055 * Free the terminal emulator part of "term".
5056 */
5057 static void
5058term_free_vterm(term_T *term)
5059{
5060 if (term->tl_vterm != NULL)
5061 vterm_free(term->tl_vterm);
5062 term->tl_vterm = NULL;
5063}
5064
5065/*
5066 * Request size to terminal.
5067 */
5068 static void
5069term_report_winsize(term_T *term, int rows, int cols)
5070{
5071 /* Use an ioctl() to report the new window size to the job. */
5072 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5073 {
5074 int fd = -1;
5075 int part;
5076
5077 for (part = PART_OUT; part < PART_COUNT; ++part)
5078 {
5079 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5080 if (isatty(fd))
5081 break;
5082 }
5083 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5084 mch_signal_job(term->tl_job, (char_u *)"winch");
5085 }
5086}
5087
5088# endif
5089
5090#endif /* FEAT_TERMINAL */