blob: 50e87c9e450e74ad70dcdeed4b8a31cc0d1f3b0d [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 Moolenaar8fbaeb12018-03-25 18:20:17 +020041 * - For the "drop" command accept another argument for options.
Bram Moolenaar13568252018-03-16 20:46:58 +010042 * - Add a way to set the 16 ANSI colors, to be used for 'termguicolors' and in
43 * the GUI.
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +020044 * - Win32: Make terminal used for :!cmd in the GUI work better. Allow for
45 * redirection.
Bram Moolenaarb852c3e2018-03-11 16:55:36 +010046 * - implement term_setsize()
47 * - Copy text in the vterm to the Vim buffer once in a while, so that
48 * completion works.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +010049 * - Adding WinBar to terminal window doesn't display, text isn't shifted down.
Bram Moolenaar46359e12017-11-29 22:33:38 +010050 * a job that uses 16 colors while Vim is using > 256.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020051 * - in GUI vertical split causes problems. Cursor is flickering. (Hirohito
52 * Higashi, 2017 Sep 19)
Bram Moolenaar3a497e12017-09-30 20:40:27 +020053 * - after resizing windows overlap. (Boris Staletic, #2164)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020054 * - Redirecting output does not work on MS-Windows, Test_terminal_redir_file()
55 * is disabled.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020056 * - cursor blinks in terminal on widows with a timer. (xtal8, #2142)
Bram Moolenaarba6febd2017-10-30 21:56:23 +010057 * - Termdebug does not work when Vim build with mzscheme. gdb hangs.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020058 * - MS-Windows GUI: WinBar has tearoff item
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020059 * - MS-Windows GUI: still need to type a key after shell exits? #1924
Bram Moolenaar51b0f372017-11-18 18:52:04 +010060 * - After executing a shell command the status line isn't redraw.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020061 * - add test for giving error for invalid 'termsize' value.
62 * - support minimal size when 'termsize' is "rows*cols".
63 * - support minimal size when 'termsize' is empty?
64 * - GUI: when using tabs, focus in terminal, click on tab does not work.
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020065 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020066 * - For the GUI fill termios with default values, perhaps like pangoterm:
67 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020068 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
69 * conversions.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020070 * - add an optional limit for the scrollback size. When reaching it remove
71 * 10% at the start.
72 */
73
74#include "vim.h"
75
76#if defined(FEAT_TERMINAL) || defined(PROTO)
77
78#ifndef MIN
79# define MIN(x,y) ((x) < (y) ? (x) : (y))
80#endif
81#ifndef MAX
82# define MAX(x,y) ((x) > (y) ? (x) : (y))
83#endif
84
85#include "libvterm/include/vterm.h"
86
87/* This is VTermScreenCell without the characters, thus much smaller. */
88typedef struct {
89 VTermScreenCellAttrs attrs;
90 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010091 VTermColor fg;
92 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020093} cellattr_T;
94
95typedef struct sb_line_S {
96 int sb_cols; /* can differ per line */
97 cellattr_T *sb_cells; /* allocated */
98 cellattr_T sb_fill_attr; /* for short line */
99} sb_line_T;
100
101/* typedef term_T in structs.h */
102struct terminal_S {
103 term_T *tl_next;
104
105 VTerm *tl_vterm;
106 job_T *tl_job;
107 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +0100108#if defined(FEAT_GUI)
109 int tl_system; /* when non-zero used for :!cmd output */
110 int tl_toprow; /* row with first line of system terminal */
111#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200112
113 /* Set when setting the size of a vterm, reset after redrawing. */
114 int tl_vterm_size_changed;
115
116 /* used when tl_job is NULL and only a pty was created */
117 int tl_tty_fd;
118 char_u *tl_tty_in;
119 char_u *tl_tty_out;
120
121 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
122 int tl_channel_closed;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100123 int tl_finish;
124#define TL_FINISH_UNSET NUL
125#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
126#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
127#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200128 char_u *tl_opencmd;
129 char_u *tl_eof_chars;
130
131#ifdef WIN3264
132 void *tl_winpty_config;
133 void *tl_winpty;
134#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100135#if defined(FEAT_SESSION)
136 char_u *tl_command;
137#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100138 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200139
140 /* last known vterm size */
141 int tl_rows;
142 int tl_cols;
143 /* vterm size does not follow window size */
144 int tl_rows_fixed;
145 int tl_cols_fixed;
146
147 char_u *tl_title; /* NULL or allocated */
148 char_u *tl_status_text; /* NULL or allocated */
149
150 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200151 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200152 int tl_dirty_row_end; /* row below last one to update */
153
154 garray_T tl_scrollback;
155 int tl_scrollback_scrolled;
156 cellattr_T tl_default_color;
157
Bram Moolenaard96ff162018-02-18 22:13:29 +0100158 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
159 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
160
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200161 VTermPos tl_cursor_pos;
162 int tl_cursor_visible;
163 int tl_cursor_blink;
164 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
165 char_u *tl_cursor_color; /* NULL or allocated */
166
167 int tl_using_altscreen;
168};
169
170#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
171#define TMODE_LOOP 2 /* CTRL-W N used */
172
173/*
174 * List of all active terminals.
175 */
176static term_T *first_term = NULL;
177
178/* Terminal active in terminal_loop(). */
179static term_T *in_terminal_loop = NULL;
180
181#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
182#define KEY_BUF_LEN 200
183
184/*
185 * Functions with separate implementation for MS-Windows and Unix-like systems.
186 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100187static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200188static int create_pty_only(term_T *term, jobopt_T *opt);
189static void term_report_winsize(term_T *term, int rows, int cols);
190static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100191#ifdef FEAT_GUI
192static void update_system_term(term_T *term);
193#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200194
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100195/* The character that we know (or assume) that the terminal expects for the
196 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200197static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200198
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100199/* "Terminal" highlight group colors. */
200static int term_default_cterm_fg = -1;
201static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200202
Bram Moolenaard317b382018-02-08 22:33:31 +0100203/* Store the last set and the desired cursor properties, so that we only update
204 * them when needed. Doing it unnecessary may result in flicker. */
205static char_u *last_set_cursor_color = (char_u *)"";
206static char_u *desired_cursor_color = (char_u *)"";
207static int last_set_cursor_shape = -1;
208static int desired_cursor_shape = -1;
209static int last_set_cursor_blink = -1;
210static int desired_cursor_blink = -1;
211
212
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200213/**************************************
214 * 1. Generic code for all systems.
215 */
216
217/*
218 * Determine the terminal size from 'termsize' and the current window.
219 * Assumes term->tl_rows and term->tl_cols are zero.
220 */
221 static void
222set_term_and_win_size(term_T *term)
223{
Bram Moolenaar13568252018-03-16 20:46:58 +0100224#ifdef FEAT_GUI
225 if (term->tl_system)
226 {
227 /* Use the whole screen for the system command. However, it will start
228 * at the command line and scroll up as needed, using tl_toprow. */
229 term->tl_rows = Rows;
230 term->tl_cols = Columns;
231 }
232 else
233#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200234 if (*curwin->w_p_tms != NUL)
235 {
236 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
237
238 term->tl_rows = atoi((char *)curwin->w_p_tms);
239 term->tl_cols = atoi((char *)p);
240 }
241 if (term->tl_rows == 0)
242 term->tl_rows = curwin->w_height;
243 else
244 {
245 win_setheight_win(term->tl_rows, curwin);
246 term->tl_rows_fixed = TRUE;
247 }
248 if (term->tl_cols == 0)
249 term->tl_cols = curwin->w_width;
250 else
251 {
252 win_setwidth_win(term->tl_cols, curwin);
253 term->tl_cols_fixed = TRUE;
254 }
255}
256
257/*
258 * Initialize job options for a terminal job.
259 * Caller may overrule some of them.
260 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100261 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200262init_job_options(jobopt_T *opt)
263{
264 clear_job_options(opt);
265
266 opt->jo_mode = MODE_RAW;
267 opt->jo_out_mode = MODE_RAW;
268 opt->jo_err_mode = MODE_RAW;
269 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
270}
271
272/*
273 * Set job options mandatory for a terminal job.
274 */
275 static void
276setup_job_options(jobopt_T *opt, int rows, int cols)
277{
278 if (!(opt->jo_set & JO_OUT_IO))
279 {
280 /* Connect stdout to the terminal. */
281 opt->jo_io[PART_OUT] = JIO_BUFFER;
282 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
283 opt->jo_modifiable[PART_OUT] = 0;
284 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
285 }
286
287 if (!(opt->jo_set & JO_ERR_IO))
288 {
289 /* Connect stderr to the terminal. */
290 opt->jo_io[PART_ERR] = JIO_BUFFER;
291 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
292 opt->jo_modifiable[PART_ERR] = 0;
293 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
294 }
295
296 opt->jo_pty = TRUE;
297 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
298 opt->jo_term_rows = rows;
299 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
300 opt->jo_term_cols = cols;
301}
302
303/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100304 * Close a terminal buffer (and its window). Used when creating the terminal
305 * fails.
306 */
307 static void
308term_close_buffer(buf_T *buf, buf_T *old_curbuf)
309{
310 free_terminal(buf);
311 if (old_curbuf != NULL)
312 {
313 --curbuf->b_nwindows;
314 curbuf = old_curbuf;
315 curwin->w_buffer = curbuf;
316 ++curbuf->b_nwindows;
317 }
318
319 /* Wiping out the buffer will also close the window and call
320 * free_terminal(). */
321 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
322}
323
324/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200325 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100326 * Use either "argvar" or "argv", the other must be NULL.
327 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
328 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200329 * Returns NULL when failed.
330 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100331 buf_T *
332term_start(
333 typval_T *argvar,
334 char **argv,
335 jobopt_T *opt,
336 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200337{
338 exarg_T split_ea;
339 win_T *old_curwin = curwin;
340 term_T *term;
341 buf_T *old_curbuf = NULL;
342 int res;
343 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100344 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200345
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 *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100414 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200415 {
416 split_ea.line2 = opt->jo_term_rows;
417 split_ea.addr_count = 1;
418 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100419 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200420 {
421 split_ea.line2 = opt->jo_term_cols;
422 split_ea.addr_count = 1;
423 }
424
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100425 if (vertical)
426 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200427 ex_splitview(&split_ea);
428 if (curwin == old_curwin)
429 {
430 /* split failed */
431 vim_free(term);
432 return NULL;
433 }
434 }
435 term->tl_buffer = curbuf;
436 curbuf->b_term = term;
437
438 if (!opt->jo_hidden)
439 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100440 /* Only one size was taken care of with :new, do the other one. With
441 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100442 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200443 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100444 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
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/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003014 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003015 */
3016 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003017init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003018{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003019 VTermColor *fg, *bg;
3020 int fgval, bgval;
3021 int id;
3022
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003023 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3024 term->tl_default_color.width = 1;
3025 fg = &term->tl_default_color.fg;
3026 bg = &term->tl_default_color.bg;
3027
3028 /* Vterm uses a default black background. Set it to white when
3029 * 'background' is "light". */
3030 if (*p_bg == 'l')
3031 {
3032 fgval = 0;
3033 bgval = 255;
3034 }
3035 else
3036 {
3037 fgval = 255;
3038 bgval = 0;
3039 }
3040 fg->red = fg->green = fg->blue = fgval;
3041 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003042 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003043
3044 /* The "Terminal" highlight group overrules the defaults. */
3045 id = syn_name2id((char_u *)"Terminal");
3046
Bram Moolenaar46359e12017-11-29 22:33:38 +01003047 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003048#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3049 if (0
3050# ifdef FEAT_GUI
3051 || gui.in_use
3052# endif
3053# ifdef FEAT_TERMGUICOLORS
3054 || p_tgc
3055# endif
3056 )
3057 {
3058 guicolor_T fg_rgb = INVALCOLOR;
3059 guicolor_T bg_rgb = INVALCOLOR;
3060
3061 if (id != 0)
3062 syn_id2colors(id, &fg_rgb, &bg_rgb);
3063
3064# ifdef FEAT_GUI
3065 if (gui.in_use)
3066 {
3067 if (fg_rgb == INVALCOLOR)
3068 fg_rgb = gui.norm_pixel;
3069 if (bg_rgb == INVALCOLOR)
3070 bg_rgb = gui.back_pixel;
3071 }
3072# ifdef FEAT_TERMGUICOLORS
3073 else
3074# endif
3075# endif
3076# ifdef FEAT_TERMGUICOLORS
3077 {
3078 if (fg_rgb == INVALCOLOR)
3079 fg_rgb = cterm_normal_fg_gui_color;
3080 if (bg_rgb == INVALCOLOR)
3081 bg_rgb = cterm_normal_bg_gui_color;
3082 }
3083# endif
3084 if (fg_rgb != INVALCOLOR)
3085 {
3086 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3087
3088 fg->red = (unsigned)(rgb >> 16);
3089 fg->green = (unsigned)(rgb >> 8) & 255;
3090 fg->blue = (unsigned)rgb & 255;
3091 }
3092 if (bg_rgb != INVALCOLOR)
3093 {
3094 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3095
3096 bg->red = (unsigned)(rgb >> 16);
3097 bg->green = (unsigned)(rgb >> 8) & 255;
3098 bg->blue = (unsigned)rgb & 255;
3099 }
3100 }
3101 else
3102#endif
3103 if (id != 0 && t_colors >= 16)
3104 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003105 if (term_default_cterm_fg >= 0)
3106 cterm_color2rgb(term_default_cterm_fg, fg);
3107 if (term_default_cterm_bg >= 0)
3108 cterm_color2rgb(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003109 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003110 else
3111 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003112#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003113 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003114#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003115
3116 /* In an MS-Windows console we know the normal colors. */
3117 if (cterm_normal_fg_color > 0)
3118 {
3119 cterm_color2rgb(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003120# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003121 tmp = fg->red;
3122 fg->red = fg->blue;
3123 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003124# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003125 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003126# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003127 else
3128 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003129# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003130
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003131 if (cterm_normal_bg_color > 0)
3132 {
3133 cterm_color2rgb(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003134# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003135 tmp = bg->red;
3136 bg->red = bg->blue;
3137 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003138# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003139 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003140# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003141 else
3142 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003143# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003144 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003145}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003146
Bram Moolenaar52acb112018-03-18 19:20:22 +01003147/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003148 * Handles a "drop" command from the job in the terminal.
3149 * "item" is the file name, "item->li_next" may have options.
3150 */
3151 static void
3152handle_drop_command(listitem_T *item)
3153{
3154 char_u *fname = get_tv_string(&item->li_tv);
3155 int bufnr;
3156 win_T *wp;
3157 tabpage_T *tp;
3158 exarg_T ea;
3159
3160 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3161 FOR_ALL_TAB_WINDOWS(tp, wp)
3162 {
3163 if (wp->w_buffer->b_fnum == bufnr)
3164 {
3165 /* buffer is in a window already, go there */
3166 goto_tabpage_win(tp, wp);
3167 return;
3168 }
3169 }
3170
3171 /* open in new window, like ":sbuffer N" */
3172 vim_memset(&ea, 0, sizeof(ea));
3173 ea.cmd = (char_u *)"sbuffer";
3174 goto_buffer(&ea, DOBUF_FIRST, FORWARD, bufnr);
3175}
3176
3177/*
3178 * Handles a function call from the job running in a terminal.
3179 * "item" is the function name, "item->li_next" has the arguments.
3180 */
3181 static void
3182handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3183{
3184 char_u *func;
3185 typval_T argvars[2];
3186 typval_T rettv;
3187 int doesrange;
3188
3189 if (item->li_next == NULL)
3190 {
3191 ch_log(channel, "Missing function arguments for call");
3192 return;
3193 }
3194 func = get_tv_string(&item->li_tv);
3195
3196 if (!ASCII_ISUPPER(*func))
3197 {
3198 ch_log(channel, "Invalid function name: %s", func);
3199 return;
3200 }
3201
3202 argvars[0].v_type = VAR_NUMBER;
3203 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3204 argvars[1] = item->li_next->li_tv;
3205 if (call_func(func, STRLEN(func), &rettv,
3206 2, argvars, /* argv_func */ NULL,
3207 /* firstline */ 1, /* lastline */ 1,
3208 &doesrange, /* evaluate */ TRUE,
3209 /* partial */ NULL, /* selfdict */ NULL) == OK)
3210 {
3211 clear_tv(&rettv);
3212 ch_log(channel, "Function %s called", func);
3213 }
3214 else
3215 ch_log(channel, "Calling function %s failed", func);
3216}
3217
3218/*
3219 * Called by libvterm when it cannot recognize an OSC sequence.
3220 * We recognize a terminal API command.
3221 */
3222 static int
3223parse_osc(const char *command, size_t cmdlen, void *user)
3224{
3225 term_T *term = (term_T *)user;
3226 js_read_T reader;
3227 typval_T tv;
3228 channel_T *channel = term->tl_job == NULL ? NULL
3229 : term->tl_job->jv_channel;
3230
3231 /* We recognize only OSC 5 1 ; {command} */
3232 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3233 return 0; /* not handled */
3234
3235 reader.js_buf = vim_strnsave((char_u *)command + 3, cmdlen - 3);
3236 if (reader.js_buf == NULL)
3237 return 1;
3238 reader.js_fill = NULL;
3239 reader.js_used = 0;
3240 if (json_decode(&reader, &tv, 0) == OK
3241 && tv.v_type == VAR_LIST
3242 && tv.vval.v_list != NULL)
3243 {
3244 listitem_T *item = tv.vval.v_list->lv_first;
3245
3246 if (item == NULL)
3247 ch_log(channel, "Missing command");
3248 else
3249 {
3250 char_u *cmd = get_tv_string(&item->li_tv);
3251
3252 item = item->li_next;
3253 if (item == NULL)
3254 ch_log(channel, "Missing argument for %s", cmd);
3255 else if (STRCMP(cmd, "drop") == 0)
3256 handle_drop_command(item);
3257 else if (STRCMP(cmd, "call") == 0)
3258 handle_call_command(term, channel, item);
3259 else
3260 ch_log(channel, "Invalid command received: %s", cmd);
3261 }
3262 }
3263 else
3264 ch_log(channel, "Invalid JSON received");
3265
3266 vim_free(reader.js_buf);
3267 clear_tv(&tv);
3268 return 1;
3269}
3270
3271static VTermParserCallbacks parser_fallbacks = {
3272 NULL, /* text */
3273 NULL, /* control */
3274 NULL, /* escape */
3275 NULL, /* csi */
3276 parse_osc, /* osc */
3277 NULL, /* dcs */
3278 NULL /* resize */
3279};
3280
3281/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003282 * Create a new vterm and initialize it.
3283 */
3284 static void
3285create_vterm(term_T *term, int rows, int cols)
3286{
3287 VTerm *vterm;
3288 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003289 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003290 VTermValue value;
3291
3292 vterm = vterm_new(rows, cols);
3293 term->tl_vterm = vterm;
3294 screen = vterm_obtain_screen(vterm);
3295 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3296 /* TODO: depends on 'encoding'. */
3297 vterm_set_utf8(vterm, 1);
3298
3299 init_default_colors(term);
3300
3301 vterm_state_set_default_colors(
3302 vterm_obtain_state(vterm),
3303 &term->tl_default_color.fg,
3304 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003305
3306 /* Required to initialize most things. */
3307 vterm_screen_reset(screen, 1 /* hard */);
3308
3309 /* Allow using alternate screen. */
3310 vterm_screen_enable_altscreen(screen, 1);
3311
3312 /* For unix do not use a blinking cursor. In an xterm this causes the
3313 * cursor to blink if it's blinking in the xterm.
3314 * For Windows we respect the system wide setting. */
3315#ifdef WIN3264
3316 if (GetCaretBlinkTime() == INFINITE)
3317 value.boolean = 0;
3318 else
3319 value.boolean = 1;
3320#else
3321 value.boolean = 0;
3322#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003323 state = vterm_obtain_state(vterm);
3324 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3325 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003326}
3327
3328/*
3329 * Return the text to show for the buffer name and status.
3330 */
3331 char_u *
3332term_get_status_text(term_T *term)
3333{
3334 if (term->tl_status_text == NULL)
3335 {
3336 char_u *txt;
3337 size_t len;
3338
3339 if (term->tl_normal_mode)
3340 {
3341 if (term_job_running(term))
3342 txt = (char_u *)_("Terminal");
3343 else
3344 txt = (char_u *)_("Terminal-finished");
3345 }
3346 else if (term->tl_title != NULL)
3347 txt = term->tl_title;
3348 else if (term_none_open(term))
3349 txt = (char_u *)_("active");
3350 else if (term_job_running(term))
3351 txt = (char_u *)_("running");
3352 else
3353 txt = (char_u *)_("finished");
3354 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3355 term->tl_status_text = alloc((int)len);
3356 if (term->tl_status_text != NULL)
3357 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3358 term->tl_buffer->b_fname, txt);
3359 }
3360 return term->tl_status_text;
3361}
3362
3363/*
3364 * Mark references in jobs of terminals.
3365 */
3366 int
3367set_ref_in_term(int copyID)
3368{
3369 int abort = FALSE;
3370 term_T *term;
3371 typval_T tv;
3372
3373 for (term = first_term; term != NULL; term = term->tl_next)
3374 if (term->tl_job != NULL)
3375 {
3376 tv.v_type = VAR_JOB;
3377 tv.vval.v_job = term->tl_job;
3378 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3379 }
3380 return abort;
3381}
3382
3383/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003384 * Cache "Terminal" highlight group colors.
3385 */
3386 void
3387set_terminal_default_colors(int cterm_fg, int cterm_bg)
3388{
3389 term_default_cterm_fg = cterm_fg - 1;
3390 term_default_cterm_bg = cterm_bg - 1;
3391}
3392
3393/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003394 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003395 * Returns NULL when the buffer is not for a terminal window and logs a message
3396 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003397 */
3398 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003399term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003400{
3401 buf_T *buf;
3402
3403 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3404 ++emsg_off;
3405 buf = get_buf_tv(&argvars[0], FALSE);
3406 --emsg_off;
3407 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003408 {
3409 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003410 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003411 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003412 return buf;
3413}
3414
Bram Moolenaard96ff162018-02-18 22:13:29 +01003415 static int
3416same_color(VTermColor *a, VTermColor *b)
3417{
3418 return a->red == b->red
3419 && a->green == b->green
3420 && a->blue == b->blue
3421 && a->ansi_index == b->ansi_index;
3422}
3423
3424 static void
3425dump_term_color(FILE *fd, VTermColor *color)
3426{
3427 fprintf(fd, "%02x%02x%02x%d",
3428 (int)color->red, (int)color->green, (int)color->blue,
3429 (int)color->ansi_index);
3430}
3431
3432/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003433 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003434 *
3435 * Each screen cell in full is:
3436 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3437 * {characters} is a space for an empty cell
3438 * For a double-width character "+" is changed to "*" and the next cell is
3439 * skipped.
3440 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3441 * when "&" use the same as the previous cell.
3442 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3443 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3444 * {color-idx} is a number from 0 to 255
3445 *
3446 * Screen cell with same width, attributes and color as the previous one:
3447 * |{characters}
3448 *
3449 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3450 *
3451 * Repeating the previous screen cell:
3452 * @{count}
3453 */
3454 void
3455f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3456{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003457 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003458 term_T *term;
3459 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003460 int max_height = 0;
3461 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003462 stat_T st;
3463 FILE *fd;
3464 VTermPos pos;
3465 VTermScreen *screen;
3466 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003467 VTermState *state;
3468 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003469
3470 if (check_restricted() || check_secure())
3471 return;
3472 if (buf == NULL)
3473 return;
3474 term = buf->b_term;
3475
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003476 if (argvars[2].v_type != VAR_UNKNOWN)
3477 {
3478 dict_T *d;
3479
3480 if (argvars[2].v_type != VAR_DICT)
3481 {
3482 EMSG(_(e_dictreq));
3483 return;
3484 }
3485 d = argvars[2].vval.v_dict;
3486 if (d != NULL)
3487 {
3488 max_height = get_dict_number(d, (char_u *)"rows");
3489 max_width = get_dict_number(d, (char_u *)"columns");
3490 }
3491 }
3492
Bram Moolenaard96ff162018-02-18 22:13:29 +01003493 fname = get_tv_string_chk(&argvars[1]);
3494 if (fname == NULL)
3495 return;
3496 if (mch_stat((char *)fname, &st) >= 0)
3497 {
3498 EMSG2(_("E953: File exists: %s"), fname);
3499 return;
3500 }
3501
Bram Moolenaard96ff162018-02-18 22:13:29 +01003502 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3503 {
3504 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3505 return;
3506 }
3507
3508 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3509
3510 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003511 state = vterm_obtain_state(term->tl_vterm);
3512 vterm_state_get_cursorpos(state, &cursor_pos);
3513
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003514 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3515 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003516 {
3517 int repeat = 0;
3518
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003519 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3520 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003521 {
3522 VTermScreenCell cell;
3523 int same_attr;
3524 int same_chars = TRUE;
3525 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003526 int is_cursor_pos = (pos.col == cursor_pos.col
3527 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003528
3529 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3530 vim_memset(&cell, 0, sizeof(cell));
3531
3532 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3533 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003534 int c = cell.chars[i];
3535 int pc = prev_cell.chars[i];
3536
3537 /* For the first character NUL is the same as space. */
3538 if (i == 0)
3539 {
3540 c = (c == NUL) ? ' ' : c;
3541 pc = (pc == NUL) ? ' ' : pc;
3542 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003543 if (cell.chars[i] != prev_cell.chars[i])
3544 same_chars = FALSE;
3545 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3546 break;
3547 }
3548 same_attr = vtermAttr2hl(cell.attrs)
3549 == vtermAttr2hl(prev_cell.attrs)
3550 && same_color(&cell.fg, &prev_cell.fg)
3551 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003552 if (same_chars && cell.width == prev_cell.width && same_attr
3553 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003554 {
3555 ++repeat;
3556 }
3557 else
3558 {
3559 if (repeat > 0)
3560 {
3561 fprintf(fd, "@%d", repeat);
3562 repeat = 0;
3563 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003564 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003565
3566 if (cell.chars[0] == NUL)
3567 fputs(" ", fd);
3568 else
3569 {
3570 char_u charbuf[10];
3571 int len;
3572
3573 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3574 && cell.chars[i] != NUL; ++i)
3575 {
3576 len = utf_char2bytes(cell.chars[0], charbuf);
3577 fwrite(charbuf, len, 1, fd);
3578 }
3579 }
3580
3581 /* When only the characters differ we don't write anything, the
3582 * following "|", "@" or NL will indicate using the same
3583 * attributes. */
3584 if (cell.width != prev_cell.width || !same_attr)
3585 {
3586 if (cell.width == 2)
3587 {
3588 fputs("*", fd);
3589 ++pos.col;
3590 }
3591 else
3592 fputs("+", fd);
3593
3594 if (same_attr)
3595 {
3596 fputs("&", fd);
3597 }
3598 else
3599 {
3600 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3601 if (same_color(&cell.fg, &prev_cell.fg))
3602 fputs("&", fd);
3603 else
3604 {
3605 fputs("#", fd);
3606 dump_term_color(fd, &cell.fg);
3607 }
3608 if (same_color(&cell.bg, &prev_cell.bg))
3609 fputs("&", fd);
3610 else
3611 {
3612 fputs("#", fd);
3613 dump_term_color(fd, &cell.bg);
3614 }
3615 }
3616 }
3617
3618 prev_cell = cell;
3619 }
3620 }
3621 if (repeat > 0)
3622 fprintf(fd, "@%d", repeat);
3623 fputs("\n", fd);
3624 }
3625
3626 fclose(fd);
3627}
3628
3629/*
3630 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3631 */
3632 static void
3633dump_is_corrupt(garray_T *gap)
3634{
3635 ga_concat(gap, (char_u *)"CORRUPT");
3636}
3637
3638 static void
3639append_cell(garray_T *gap, cellattr_T *cell)
3640{
3641 if (ga_grow(gap, 1) == OK)
3642 {
3643 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3644 ++gap->ga_len;
3645 }
3646}
3647
3648/*
3649 * Read the dump file from "fd" and append lines to the current buffer.
3650 * Return the cell width of the longest line.
3651 */
3652 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003653read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003654{
3655 int c;
3656 garray_T ga_text;
3657 garray_T ga_cell;
3658 char_u *prev_char = NULL;
3659 int attr = 0;
3660 cellattr_T cell;
3661 term_T *term = curbuf->b_term;
3662 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003663 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003664
3665 ga_init2(&ga_text, 1, 90);
3666 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3667 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003668 cursor_pos->row = -1;
3669 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003670
3671 c = fgetc(fd);
3672 for (;;)
3673 {
3674 if (c == EOF)
3675 break;
3676 if (c == '\n')
3677 {
3678 /* End of a line: append it to the buffer. */
3679 if (ga_text.ga_data == NULL)
3680 dump_is_corrupt(&ga_text);
3681 if (ga_grow(&term->tl_scrollback, 1) == OK)
3682 {
3683 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3684 + term->tl_scrollback.ga_len;
3685
3686 if (max_cells < ga_cell.ga_len)
3687 max_cells = ga_cell.ga_len;
3688 line->sb_cols = ga_cell.ga_len;
3689 line->sb_cells = ga_cell.ga_data;
3690 line->sb_fill_attr = term->tl_default_color;
3691 ++term->tl_scrollback.ga_len;
3692 ga_init(&ga_cell);
3693
3694 ga_append(&ga_text, NUL);
3695 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3696 ga_text.ga_len, FALSE);
3697 }
3698 else
3699 ga_clear(&ga_cell);
3700 ga_text.ga_len = 0;
3701
3702 c = fgetc(fd);
3703 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003704 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003705 {
3706 int prev_len = ga_text.ga_len;
3707
Bram Moolenaar9271d052018-02-25 21:39:46 +01003708 if (c == '>')
3709 {
3710 if (cursor_pos->row != -1)
3711 dump_is_corrupt(&ga_text); /* duplicate cursor */
3712 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3713 cursor_pos->col = ga_cell.ga_len;
3714 }
3715
Bram Moolenaard96ff162018-02-18 22:13:29 +01003716 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3717 c = fgetc(fd);
3718 if (c != EOF)
3719 ga_append(&ga_text, c);
3720 for (;;)
3721 {
3722 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003723 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003724 || c == EOF || c == '\n')
3725 break;
3726 ga_append(&ga_text, c);
3727 }
3728
3729 /* save the character for repeating it */
3730 vim_free(prev_char);
3731 if (ga_text.ga_data != NULL)
3732 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3733 ga_text.ga_len - prev_len);
3734
Bram Moolenaar9271d052018-02-25 21:39:46 +01003735 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003736 {
3737 /* use all attributes from previous cell */
3738 }
3739 else if (c == '+' || c == '*')
3740 {
3741 int is_bg;
3742
3743 cell.width = c == '+' ? 1 : 2;
3744
3745 c = fgetc(fd);
3746 if (c == '&')
3747 {
3748 /* use same attr as previous cell */
3749 c = fgetc(fd);
3750 }
3751 else if (isdigit(c))
3752 {
3753 /* get the decimal attribute */
3754 attr = 0;
3755 while (isdigit(c))
3756 {
3757 attr = attr * 10 + (c - '0');
3758 c = fgetc(fd);
3759 }
3760 hl2vtermAttr(attr, &cell);
3761 }
3762 else
3763 dump_is_corrupt(&ga_text);
3764
3765 /* is_bg == 0: fg, is_bg == 1: bg */
3766 for (is_bg = 0; is_bg <= 1; ++is_bg)
3767 {
3768 if (c == '&')
3769 {
3770 /* use same color as previous cell */
3771 c = fgetc(fd);
3772 }
3773 else if (c == '#')
3774 {
3775 int red, green, blue, index = 0;
3776
3777 c = fgetc(fd);
3778 red = hex2nr(c);
3779 c = fgetc(fd);
3780 red = (red << 4) + hex2nr(c);
3781 c = fgetc(fd);
3782 green = hex2nr(c);
3783 c = fgetc(fd);
3784 green = (green << 4) + hex2nr(c);
3785 c = fgetc(fd);
3786 blue = hex2nr(c);
3787 c = fgetc(fd);
3788 blue = (blue << 4) + hex2nr(c);
3789 c = fgetc(fd);
3790 if (!isdigit(c))
3791 dump_is_corrupt(&ga_text);
3792 while (isdigit(c))
3793 {
3794 index = index * 10 + (c - '0');
3795 c = fgetc(fd);
3796 }
3797
3798 if (is_bg)
3799 {
3800 cell.bg.red = red;
3801 cell.bg.green = green;
3802 cell.bg.blue = blue;
3803 cell.bg.ansi_index = index;
3804 }
3805 else
3806 {
3807 cell.fg.red = red;
3808 cell.fg.green = green;
3809 cell.fg.blue = blue;
3810 cell.fg.ansi_index = index;
3811 }
3812 }
3813 else
3814 dump_is_corrupt(&ga_text);
3815 }
3816 }
3817 else
3818 dump_is_corrupt(&ga_text);
3819
3820 append_cell(&ga_cell, &cell);
3821 }
3822 else if (c == '@')
3823 {
3824 if (prev_char == NULL)
3825 dump_is_corrupt(&ga_text);
3826 else
3827 {
3828 int count = 0;
3829
3830 /* repeat previous character, get the count */
3831 for (;;)
3832 {
3833 c = fgetc(fd);
3834 if (!isdigit(c))
3835 break;
3836 count = count * 10 + (c - '0');
3837 }
3838
3839 while (count-- > 0)
3840 {
3841 ga_concat(&ga_text, prev_char);
3842 append_cell(&ga_cell, &cell);
3843 }
3844 }
3845 }
3846 else
3847 {
3848 dump_is_corrupt(&ga_text);
3849 c = fgetc(fd);
3850 }
3851 }
3852
3853 if (ga_text.ga_len > 0)
3854 {
3855 /* trailing characters after last NL */
3856 dump_is_corrupt(&ga_text);
3857 ga_append(&ga_text, NUL);
3858 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3859 ga_text.ga_len, FALSE);
3860 }
3861
3862 ga_clear(&ga_text);
3863 vim_free(prev_char);
3864
3865 return max_cells;
3866}
3867
3868/*
3869 * Common for "term_dumpdiff()" and "term_dumpload()".
3870 */
3871 static void
3872term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
3873{
3874 jobopt_T opt;
3875 buf_T *buf;
3876 char_u buf1[NUMBUFLEN];
3877 char_u buf2[NUMBUFLEN];
3878 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003879 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003880 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003881 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003882 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003883 char_u *textline = NULL;
3884
3885 /* First open the files. If this fails bail out. */
3886 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
3887 if (do_diff)
3888 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
3889 if (fname1 == NULL || (do_diff && fname2 == NULL))
3890 {
3891 EMSG(_(e_invarg));
3892 return;
3893 }
3894 fd1 = mch_fopen((char *)fname1, READBIN);
3895 if (fd1 == NULL)
3896 {
3897 EMSG2(_(e_notread), fname1);
3898 return;
3899 }
3900 if (do_diff)
3901 {
3902 fd2 = mch_fopen((char *)fname2, READBIN);
3903 if (fd2 == NULL)
3904 {
3905 fclose(fd1);
3906 EMSG2(_(e_notread), fname2);
3907 return;
3908 }
3909 }
3910
3911 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003912 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
3913 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
3914 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
3915 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
3916 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003917
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003918 if (opt.jo_term_name == NULL)
3919 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01003920 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003921
Bram Moolenaarb571c632018-03-21 22:27:59 +01003922 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003923 if (fname_tofree != NULL)
3924 {
3925 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
3926 opt.jo_term_name = fname_tofree;
3927 }
3928 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003929
Bram Moolenaar13568252018-03-16 20:46:58 +01003930 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003931 if (buf != NULL && buf->b_term != NULL)
3932 {
3933 int i;
3934 linenr_T bot_lnum;
3935 linenr_T lnum;
3936 term_T *term = buf->b_term;
3937 int width;
3938 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003939 VTermPos cursor_pos1;
3940 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003941
Bram Moolenaar52acb112018-03-18 19:20:22 +01003942 init_default_colors(term);
3943
Bram Moolenaard96ff162018-02-18 22:13:29 +01003944 rettv->vval.v_number = buf->b_fnum;
3945
3946 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01003947 width = read_dump_file(fd1, &cursor_pos1);
3948
3949 /* position the cursor */
3950 if (cursor_pos1.row >= 0)
3951 {
3952 curwin->w_cursor.lnum = cursor_pos1.row + 1;
3953 coladvance(cursor_pos1.col);
3954 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003955
3956 /* Delete the empty line that was in the empty buffer. */
3957 ml_delete(1, FALSE);
3958
3959 /* For term_dumpload() we are done here. */
3960 if (!do_diff)
3961 goto theend;
3962
3963 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
3964
3965 textline = alloc(width + 1);
3966 if (textline == NULL)
3967 goto theend;
3968 for (i = 0; i < width; ++i)
3969 textline[i] = '=';
3970 textline[width] = NUL;
3971 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3972 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3973 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3974 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3975
3976 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003977 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003978 if (width2 > width)
3979 {
3980 vim_free(textline);
3981 textline = alloc(width2 + 1);
3982 if (textline == NULL)
3983 goto theend;
3984 width = width2;
3985 textline[width] = NUL;
3986 }
3987 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
3988
3989 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
3990 {
3991 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
3992 {
3993 /* bottom part has fewer rows, fill with "-" */
3994 for (i = 0; i < width; ++i)
3995 textline[i] = '-';
3996 }
3997 else
3998 {
3999 char_u *line1;
4000 char_u *line2;
4001 char_u *p1;
4002 char_u *p2;
4003 int col;
4004 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4005 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4006 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4007 ->sb_cells;
4008
4009 /* Make a copy, getting the second line will invalidate it. */
4010 line1 = vim_strsave(ml_get(lnum));
4011 if (line1 == NULL)
4012 break;
4013 p1 = line1;
4014
4015 line2 = ml_get(lnum + bot_lnum);
4016 p2 = line2;
4017 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4018 {
4019 int len1 = utfc_ptr2len(p1);
4020 int len2 = utfc_ptr2len(p2);
4021
4022 textline[col] = ' ';
4023 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004024 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004025 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004026 else if (lnum == cursor_pos1.row + 1
4027 && col == cursor_pos1.col
4028 && (cursor_pos1.row != cursor_pos2.row
4029 || cursor_pos1.col != cursor_pos2.col))
4030 /* cursor in first but not in second */
4031 textline[col] = '>';
4032 else if (lnum == cursor_pos2.row + 1
4033 && col == cursor_pos2.col
4034 && (cursor_pos1.row != cursor_pos2.row
4035 || cursor_pos1.col != cursor_pos2.col))
4036 /* cursor in second but not in first */
4037 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004038 else if (cellattr1 != NULL && cellattr2 != NULL)
4039 {
4040 if ((cellattr1 + col)->width
4041 != (cellattr2 + col)->width)
4042 textline[col] = 'w';
4043 else if (!same_color(&(cellattr1 + col)->fg,
4044 &(cellattr2 + col)->fg))
4045 textline[col] = 'f';
4046 else if (!same_color(&(cellattr1 + col)->bg,
4047 &(cellattr2 + col)->bg))
4048 textline[col] = 'b';
4049 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4050 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4051 textline[col] = 'a';
4052 }
4053 p1 += len1;
4054 p2 += len2;
4055 /* TODO: handle different width */
4056 }
4057 vim_free(line1);
4058
4059 while (col < width)
4060 {
4061 if (*p1 == NUL && *p2 == NUL)
4062 textline[col] = '?';
4063 else if (*p1 == NUL)
4064 {
4065 textline[col] = '+';
4066 p2 += utfc_ptr2len(p2);
4067 }
4068 else
4069 {
4070 textline[col] = '-';
4071 p1 += utfc_ptr2len(p1);
4072 }
4073 ++col;
4074 }
4075 }
4076 if (add_empty_scrollback(term, &term->tl_default_color,
4077 term->tl_top_diff_rows) == OK)
4078 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4079 ++bot_lnum;
4080 }
4081
4082 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4083 {
4084 /* bottom part has more rows, fill with "+" */
4085 for (i = 0; i < width; ++i)
4086 textline[i] = '+';
4087 if (add_empty_scrollback(term, &term->tl_default_color,
4088 term->tl_top_diff_rows) == OK)
4089 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4090 ++lnum;
4091 ++bot_lnum;
4092 }
4093
4094 term->tl_cols = width;
4095 }
4096
4097theend:
4098 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004099 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004100 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004101 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004102 fclose(fd2);
4103}
4104
4105/*
4106 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4107 * bottom files.
4108 * Return FAIL when this is not possible.
4109 */
4110 int
4111term_swap_diff()
4112{
4113 term_T *term = curbuf->b_term;
4114 linenr_T line_count;
4115 linenr_T top_rows;
4116 linenr_T bot_rows;
4117 linenr_T bot_start;
4118 linenr_T lnum;
4119 char_u *p;
4120 sb_line_T *sb_line;
4121
4122 if (term == NULL
4123 || !term_is_finished(curbuf)
4124 || term->tl_top_diff_rows == 0
4125 || term->tl_scrollback.ga_len == 0)
4126 return FAIL;
4127
4128 line_count = curbuf->b_ml.ml_line_count;
4129 top_rows = term->tl_top_diff_rows;
4130 bot_rows = term->tl_bot_diff_rows;
4131 bot_start = line_count - bot_rows;
4132 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4133
4134 /* move lines from top to above the bottom part */
4135 for (lnum = 1; lnum <= top_rows; ++lnum)
4136 {
4137 p = vim_strsave(ml_get(1));
4138 if (p == NULL)
4139 return OK;
4140 ml_append(bot_start, p, 0, FALSE);
4141 ml_delete(1, FALSE);
4142 vim_free(p);
4143 }
4144
4145 /* move lines from bottom to the top */
4146 for (lnum = 1; lnum <= bot_rows; ++lnum)
4147 {
4148 p = vim_strsave(ml_get(bot_start + lnum));
4149 if (p == NULL)
4150 return OK;
4151 ml_delete(bot_start + lnum, FALSE);
4152 ml_append(lnum - 1, p, 0, FALSE);
4153 vim_free(p);
4154 }
4155
4156 if (top_rows == bot_rows)
4157 {
4158 /* rows counts are equal, can swap cell properties */
4159 for (lnum = 0; lnum < top_rows; ++lnum)
4160 {
4161 sb_line_T temp;
4162
4163 temp = *(sb_line + lnum);
4164 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4165 *(sb_line + bot_start + lnum) = temp;
4166 }
4167 }
4168 else
4169 {
4170 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4171 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4172
4173 /* need to copy cell properties into temp memory */
4174 if (temp != NULL)
4175 {
4176 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4177 mch_memmove(term->tl_scrollback.ga_data,
4178 temp + bot_start,
4179 sizeof(sb_line_T) * bot_rows);
4180 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4181 temp + top_rows,
4182 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4183 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4184 + line_count - top_rows,
4185 temp,
4186 sizeof(sb_line_T) * top_rows);
4187 vim_free(temp);
4188 }
4189 }
4190
4191 term->tl_top_diff_rows = bot_rows;
4192 term->tl_bot_diff_rows = top_rows;
4193
4194 update_screen(NOT_VALID);
4195 return OK;
4196}
4197
4198/*
4199 * "term_dumpdiff(filename, filename, options)" function
4200 */
4201 void
4202f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4203{
4204 term_load_dump(argvars, rettv, TRUE);
4205}
4206
4207/*
4208 * "term_dumpload(filename, options)" function
4209 */
4210 void
4211f_term_dumpload(typval_T *argvars, typval_T *rettv)
4212{
4213 term_load_dump(argvars, rettv, FALSE);
4214}
4215
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004216/*
4217 * "term_getaltscreen(buf)" function
4218 */
4219 void
4220f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4221{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004222 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004223
4224 if (buf == NULL)
4225 return;
4226 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4227}
4228
4229/*
4230 * "term_getattr(attr, name)" function
4231 */
4232 void
4233f_term_getattr(typval_T *argvars, typval_T *rettv)
4234{
4235 int attr;
4236 size_t i;
4237 char_u *name;
4238
4239 static struct {
4240 char *name;
4241 int attr;
4242 } attrs[] = {
4243 {"bold", HL_BOLD},
4244 {"italic", HL_ITALIC},
4245 {"underline", HL_UNDERLINE},
4246 {"strike", HL_STRIKETHROUGH},
4247 {"reverse", HL_INVERSE},
4248 };
4249
4250 attr = get_tv_number(&argvars[0]);
4251 name = get_tv_string_chk(&argvars[1]);
4252 if (name == NULL)
4253 return;
4254
4255 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4256 if (STRCMP(name, attrs[i].name) == 0)
4257 {
4258 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4259 break;
4260 }
4261}
4262
4263/*
4264 * "term_getcursor(buf)" function
4265 */
4266 void
4267f_term_getcursor(typval_T *argvars, typval_T *rettv)
4268{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004269 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004270 term_T *term;
4271 list_T *l;
4272 dict_T *d;
4273
4274 if (rettv_list_alloc(rettv) == FAIL)
4275 return;
4276 if (buf == NULL)
4277 return;
4278 term = buf->b_term;
4279
4280 l = rettv->vval.v_list;
4281 list_append_number(l, term->tl_cursor_pos.row + 1);
4282 list_append_number(l, term->tl_cursor_pos.col + 1);
4283
4284 d = dict_alloc();
4285 if (d != NULL)
4286 {
4287 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4288 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4289 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4290 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4291 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4292 ? (char_u *)"" : term->tl_cursor_color);
4293 list_append_dict(l, d);
4294 }
4295}
4296
4297/*
4298 * "term_getjob(buf)" function
4299 */
4300 void
4301f_term_getjob(typval_T *argvars, typval_T *rettv)
4302{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004303 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004304
4305 rettv->v_type = VAR_JOB;
4306 rettv->vval.v_job = NULL;
4307 if (buf == NULL)
4308 return;
4309
4310 rettv->vval.v_job = buf->b_term->tl_job;
4311 if (rettv->vval.v_job != NULL)
4312 ++rettv->vval.v_job->jv_refcount;
4313}
4314
4315 static int
4316get_row_number(typval_T *tv, term_T *term)
4317{
4318 if (tv->v_type == VAR_STRING
4319 && tv->vval.v_string != NULL
4320 && STRCMP(tv->vval.v_string, ".") == 0)
4321 return term->tl_cursor_pos.row;
4322 return (int)get_tv_number(tv) - 1;
4323}
4324
4325/*
4326 * "term_getline(buf, row)" function
4327 */
4328 void
4329f_term_getline(typval_T *argvars, typval_T *rettv)
4330{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004331 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004332 term_T *term;
4333 int row;
4334
4335 rettv->v_type = VAR_STRING;
4336 if (buf == NULL)
4337 return;
4338 term = buf->b_term;
4339 row = get_row_number(&argvars[1], term);
4340
4341 if (term->tl_vterm == NULL)
4342 {
4343 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4344
4345 /* vterm is finished, get the text from the buffer */
4346 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4347 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4348 }
4349 else
4350 {
4351 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4352 VTermRect rect;
4353 int len;
4354 char_u *p;
4355
4356 if (row < 0 || row >= term->tl_rows)
4357 return;
4358 len = term->tl_cols * MB_MAXBYTES + 1;
4359 p = alloc(len);
4360 if (p == NULL)
4361 return;
4362 rettv->vval.v_string = p;
4363
4364 rect.start_col = 0;
4365 rect.end_col = term->tl_cols;
4366 rect.start_row = row;
4367 rect.end_row = row + 1;
4368 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4369 }
4370}
4371
4372/*
4373 * "term_getscrolled(buf)" function
4374 */
4375 void
4376f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4377{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004378 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004379
4380 if (buf == NULL)
4381 return;
4382 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4383}
4384
4385/*
4386 * "term_getsize(buf)" function
4387 */
4388 void
4389f_term_getsize(typval_T *argvars, typval_T *rettv)
4390{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004391 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004392 list_T *l;
4393
4394 if (rettv_list_alloc(rettv) == FAIL)
4395 return;
4396 if (buf == NULL)
4397 return;
4398
4399 l = rettv->vval.v_list;
4400 list_append_number(l, buf->b_term->tl_rows);
4401 list_append_number(l, buf->b_term->tl_cols);
4402}
4403
4404/*
4405 * "term_getstatus(buf)" function
4406 */
4407 void
4408f_term_getstatus(typval_T *argvars, typval_T *rettv)
4409{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004410 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004411 term_T *term;
4412 char_u val[100];
4413
4414 rettv->v_type = VAR_STRING;
4415 if (buf == NULL)
4416 return;
4417 term = buf->b_term;
4418
4419 if (term_job_running(term))
4420 STRCPY(val, "running");
4421 else
4422 STRCPY(val, "finished");
4423 if (term->tl_normal_mode)
4424 STRCAT(val, ",normal");
4425 rettv->vval.v_string = vim_strsave(val);
4426}
4427
4428/*
4429 * "term_gettitle(buf)" function
4430 */
4431 void
4432f_term_gettitle(typval_T *argvars, typval_T *rettv)
4433{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004434 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004435
4436 rettv->v_type = VAR_STRING;
4437 if (buf == NULL)
4438 return;
4439
4440 if (buf->b_term->tl_title != NULL)
4441 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4442}
4443
4444/*
4445 * "term_gettty(buf)" function
4446 */
4447 void
4448f_term_gettty(typval_T *argvars, typval_T *rettv)
4449{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004450 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004451 char_u *p;
4452 int num = 0;
4453
4454 rettv->v_type = VAR_STRING;
4455 if (buf == NULL)
4456 return;
4457 if (argvars[1].v_type != VAR_UNKNOWN)
4458 num = get_tv_number(&argvars[1]);
4459
4460 switch (num)
4461 {
4462 case 0:
4463 if (buf->b_term->tl_job != NULL)
4464 p = buf->b_term->tl_job->jv_tty_out;
4465 else
4466 p = buf->b_term->tl_tty_out;
4467 break;
4468 case 1:
4469 if (buf->b_term->tl_job != NULL)
4470 p = buf->b_term->tl_job->jv_tty_in;
4471 else
4472 p = buf->b_term->tl_tty_in;
4473 break;
4474 default:
4475 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4476 return;
4477 }
4478 if (p != NULL)
4479 rettv->vval.v_string = vim_strsave(p);
4480}
4481
4482/*
4483 * "term_list()" function
4484 */
4485 void
4486f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4487{
4488 term_T *tp;
4489 list_T *l;
4490
4491 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4492 return;
4493
4494 l = rettv->vval.v_list;
4495 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4496 if (tp != NULL && tp->tl_buffer != NULL)
4497 if (list_append_number(l,
4498 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4499 return;
4500}
4501
4502/*
4503 * "term_scrape(buf, row)" function
4504 */
4505 void
4506f_term_scrape(typval_T *argvars, typval_T *rettv)
4507{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004508 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004509 VTermScreen *screen = NULL;
4510 VTermPos pos;
4511 list_T *l;
4512 term_T *term;
4513 char_u *p;
4514 sb_line_T *line;
4515
4516 if (rettv_list_alloc(rettv) == FAIL)
4517 return;
4518 if (buf == NULL)
4519 return;
4520 term = buf->b_term;
4521
4522 l = rettv->vval.v_list;
4523 pos.row = get_row_number(&argvars[1], term);
4524
4525 if (term->tl_vterm != NULL)
4526 {
4527 screen = vterm_obtain_screen(term->tl_vterm);
4528 p = NULL;
4529 line = NULL;
4530 }
4531 else
4532 {
4533 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4534
4535 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4536 return;
4537 p = ml_get_buf(buf, lnum + 1, FALSE);
4538 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4539 }
4540
4541 for (pos.col = 0; pos.col < term->tl_cols; )
4542 {
4543 dict_T *dcell;
4544 int width;
4545 VTermScreenCellAttrs attrs;
4546 VTermColor fg, bg;
4547 char_u rgb[8];
4548 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4549 int off = 0;
4550 int i;
4551
4552 if (screen == NULL)
4553 {
4554 cellattr_T *cellattr;
4555 int len;
4556
4557 /* vterm has finished, get the cell from scrollback */
4558 if (pos.col >= line->sb_cols)
4559 break;
4560 cellattr = line->sb_cells + pos.col;
4561 width = cellattr->width;
4562 attrs = cellattr->attrs;
4563 fg = cellattr->fg;
4564 bg = cellattr->bg;
4565 len = MB_PTR2LEN(p);
4566 mch_memmove(mbs, p, len);
4567 mbs[len] = NUL;
4568 p += len;
4569 }
4570 else
4571 {
4572 VTermScreenCell cell;
4573 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4574 break;
4575 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4576 {
4577 if (cell.chars[i] == 0)
4578 break;
4579 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4580 }
4581 mbs[off] = NUL;
4582 width = cell.width;
4583 attrs = cell.attrs;
4584 fg = cell.fg;
4585 bg = cell.bg;
4586 }
4587 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004588 if (dcell == NULL)
4589 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004590 list_append_dict(l, dcell);
4591
4592 dict_add_nr_str(dcell, "chars", 0, mbs);
4593
4594 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4595 fg.red, fg.green, fg.blue);
4596 dict_add_nr_str(dcell, "fg", 0, rgb);
4597 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4598 bg.red, bg.green, bg.blue);
4599 dict_add_nr_str(dcell, "bg", 0, rgb);
4600
4601 dict_add_nr_str(dcell, "attr",
4602 cell2attr(attrs, fg, bg), NULL);
4603 dict_add_nr_str(dcell, "width", width, NULL);
4604
4605 ++pos.col;
4606 if (width == 2)
4607 ++pos.col;
4608 }
4609}
4610
4611/*
4612 * "term_sendkeys(buf, keys)" function
4613 */
4614 void
4615f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4616{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004617 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004618 char_u *msg;
4619 term_T *term;
4620
4621 rettv->v_type = VAR_UNKNOWN;
4622 if (buf == NULL)
4623 return;
4624
4625 msg = get_tv_string_chk(&argvars[1]);
4626 if (msg == NULL)
4627 return;
4628 term = buf->b_term;
4629 if (term->tl_vterm == NULL)
4630 return;
4631
4632 while (*msg != NUL)
4633 {
4634 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004635 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004636 }
4637}
4638
4639/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004640 * "term_setrestore(buf, command)" function
4641 */
4642 void
4643f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4644{
4645#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004646 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004647 term_T *term;
4648 char_u *cmd;
4649
4650 if (buf == NULL)
4651 return;
4652 term = buf->b_term;
4653 vim_free(term->tl_command);
4654 cmd = get_tv_string_chk(&argvars[1]);
4655 if (cmd != NULL)
4656 term->tl_command = vim_strsave(cmd);
4657 else
4658 term->tl_command = NULL;
4659#endif
4660}
4661
4662/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004663 * "term_setkill(buf, how)" function
4664 */
4665 void
4666f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4667{
4668 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4669 term_T *term;
4670 char_u *how;
4671
4672 if (buf == NULL)
4673 return;
4674 term = buf->b_term;
4675 vim_free(term->tl_kill);
4676 how = get_tv_string_chk(&argvars[1]);
4677 if (how != NULL)
4678 term->tl_kill = vim_strsave(how);
4679 else
4680 term->tl_kill = NULL;
4681}
4682
4683/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004684 * "term_start(command, options)" function
4685 */
4686 void
4687f_term_start(typval_T *argvars, typval_T *rettv)
4688{
4689 jobopt_T opt;
4690 buf_T *buf;
4691
4692 init_job_options(&opt);
4693 if (argvars[1].v_type != VAR_UNKNOWN
4694 && get_job_options(&argvars[1], &opt,
4695 JO_TIMEOUT_ALL + JO_STOPONEXIT
4696 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
4697 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
4698 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
4699 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004700 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004701 + JO2_NORESTORE + JO2_TERM_KILL) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004702 return;
4703
Bram Moolenaar13568252018-03-16 20:46:58 +01004704 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004705
4706 if (buf != NULL && buf->b_term != NULL)
4707 rettv->vval.v_number = buf->b_fnum;
4708}
4709
4710/*
4711 * "term_wait" function
4712 */
4713 void
4714f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
4715{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004716 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004717
4718 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004719 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004720 if (buf->b_term->tl_job == NULL)
4721 {
4722 ch_log(NULL, "term_wait(): no job to wait for");
4723 return;
4724 }
4725 if (buf->b_term->tl_job->jv_channel == NULL)
4726 /* channel is closed, nothing to do */
4727 return;
4728
4729 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01004730 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004731 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
4732 {
4733 /* The job is dead, keep reading channel I/O until the channel is
4734 * closed. buf->b_term may become NULL if the terminal was closed while
4735 * waiting. */
4736 ch_log(NULL, "term_wait(): waiting for channel to close");
4737 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
4738 {
4739 mch_check_messages();
4740 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01004741 if (!buf_valid(buf))
4742 /* If the terminal is closed when the channel is closed the
4743 * buffer disappears. */
4744 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004745 ui_delay(10L, FALSE);
4746 }
4747 mch_check_messages();
4748 parse_queued_messages();
4749 }
4750 else
4751 {
4752 long wait = 10L;
4753
4754 mch_check_messages();
4755 parse_queued_messages();
4756
4757 /* Wait for some time for any channel I/O. */
4758 if (argvars[1].v_type != VAR_UNKNOWN)
4759 wait = get_tv_number(&argvars[1]);
4760 ui_delay(wait, TRUE);
4761 mch_check_messages();
4762
4763 /* Flushing messages on channels is hopefully sufficient.
4764 * TODO: is there a better way? */
4765 parse_queued_messages();
4766 }
4767}
4768
4769/*
4770 * Called when a channel has sent all the lines to a terminal.
4771 * Send a CTRL-D to mark the end of the text.
4772 */
4773 void
4774term_send_eof(channel_T *ch)
4775{
4776 term_T *term;
4777
4778 for (term = first_term; term != NULL; term = term->tl_next)
4779 if (term->tl_job == ch->ch_job)
4780 {
4781 if (term->tl_eof_chars != NULL)
4782 {
4783 channel_send(ch, PART_IN, term->tl_eof_chars,
4784 (int)STRLEN(term->tl_eof_chars), NULL);
4785 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
4786 }
4787# ifdef WIN3264
4788 else
4789 /* Default: CTRL-D */
4790 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
4791# endif
4792 }
4793}
4794
4795# if defined(WIN3264) || defined(PROTO)
4796
4797/**************************************
4798 * 2. MS-Windows implementation.
4799 */
4800
4801# ifndef PROTO
4802
4803#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
4804#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01004805#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004806
4807void* (*winpty_config_new)(UINT64, void*);
4808void* (*winpty_open)(void*, void*);
4809void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
4810BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
4811void (*winpty_config_set_mouse_mode)(void*, int);
4812void (*winpty_config_set_initial_size)(void*, int, int);
4813LPCWSTR (*winpty_conin_name)(void*);
4814LPCWSTR (*winpty_conout_name)(void*);
4815LPCWSTR (*winpty_conerr_name)(void*);
4816void (*winpty_free)(void*);
4817void (*winpty_config_free)(void*);
4818void (*winpty_spawn_config_free)(void*);
4819void (*winpty_error_free)(void*);
4820LPCWSTR (*winpty_error_msg)(void*);
4821BOOL (*winpty_set_size)(void*, int, int, void*);
4822HANDLE (*winpty_agent_process)(void*);
4823
4824#define WINPTY_DLL "winpty.dll"
4825
4826static HINSTANCE hWinPtyDLL = NULL;
4827# endif
4828
4829 static int
4830dyn_winpty_init(int verbose)
4831{
4832 int i;
4833 static struct
4834 {
4835 char *name;
4836 FARPROC *ptr;
4837 } winpty_entry[] =
4838 {
4839 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
4840 {"winpty_config_free", (FARPROC*)&winpty_config_free},
4841 {"winpty_config_new", (FARPROC*)&winpty_config_new},
4842 {"winpty_config_set_mouse_mode",
4843 (FARPROC*)&winpty_config_set_mouse_mode},
4844 {"winpty_config_set_initial_size",
4845 (FARPROC*)&winpty_config_set_initial_size},
4846 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
4847 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
4848 {"winpty_error_free", (FARPROC*)&winpty_error_free},
4849 {"winpty_free", (FARPROC*)&winpty_free},
4850 {"winpty_open", (FARPROC*)&winpty_open},
4851 {"winpty_spawn", (FARPROC*)&winpty_spawn},
4852 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
4853 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
4854 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
4855 {"winpty_set_size", (FARPROC*)&winpty_set_size},
4856 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
4857 {NULL, NULL}
4858 };
4859
4860 /* No need to initialize twice. */
4861 if (hWinPtyDLL)
4862 return OK;
4863 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
4864 * winpty.dll. */
4865 if (*p_winptydll != NUL)
4866 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
4867 if (!hWinPtyDLL)
4868 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
4869 if (!hWinPtyDLL)
4870 {
4871 if (verbose)
4872 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
4873 : (char_u *)WINPTY_DLL);
4874 return FAIL;
4875 }
4876 for (i = 0; winpty_entry[i].name != NULL
4877 && winpty_entry[i].ptr != NULL; ++i)
4878 {
4879 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
4880 winpty_entry[i].name)) == NULL)
4881 {
4882 if (verbose)
4883 EMSG2(_(e_loadfunc), winpty_entry[i].name);
4884 return FAIL;
4885 }
4886 }
4887
4888 return OK;
4889}
4890
4891/*
4892 * Create a new terminal of "rows" by "cols" cells.
4893 * Store a reference in "term".
4894 * Return OK or FAIL.
4895 */
4896 static int
4897term_and_job_init(
4898 term_T *term,
4899 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01004900 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004901 jobopt_T *opt)
4902{
4903 WCHAR *cmd_wchar = NULL;
4904 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004905 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004906 channel_T *channel = NULL;
4907 job_T *job = NULL;
4908 DWORD error;
4909 HANDLE jo = NULL;
4910 HANDLE child_process_handle;
4911 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01004912 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004913 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004914 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004915 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004916
4917 if (dyn_winpty_init(TRUE) == FAIL)
4918 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004919 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
4920 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004921
4922 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004923 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004924 cmd = argvar->vval.v_string;
4925 }
4926 else if (argvar->v_type == VAR_LIST)
4927 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004928 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004929 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004930 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004931 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004932 if (cmd == NULL || *cmd == NUL)
4933 {
4934 EMSG(_(e_invarg));
4935 goto failed;
4936 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004937
4938 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004939 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004940 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004941 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004942 if (opt->jo_cwd != NULL)
4943 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004944
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004945 win32_build_env(opt->jo_env, &ga_env, TRUE);
4946 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004947
4948 job = job_alloc();
4949 if (job == NULL)
4950 goto failed;
4951
4952 channel = add_channel();
4953 if (channel == NULL)
4954 goto failed;
4955
4956 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
4957 if (term->tl_winpty_config == NULL)
4958 goto failed;
4959
4960 winpty_config_set_mouse_mode(term->tl_winpty_config,
4961 WINPTY_MOUSE_MODE_FORCE);
4962 winpty_config_set_initial_size(term->tl_winpty_config,
4963 term->tl_cols, term->tl_rows);
4964 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
4965 if (term->tl_winpty == NULL)
4966 goto failed;
4967
4968 spawn_config = winpty_spawn_config_new(
4969 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
4970 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
4971 NULL,
4972 cmd_wchar,
4973 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004974 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004975 &winpty_err);
4976 if (spawn_config == NULL)
4977 goto failed;
4978
4979 channel = add_channel();
4980 if (channel == NULL)
4981 goto failed;
4982
4983 job = job_alloc();
4984 if (job == NULL)
4985 goto failed;
4986
4987 if (opt->jo_set & JO_IN_BUF)
4988 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
4989
4990 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
4991 &child_thread_handle, &error, &winpty_err))
4992 goto failed;
4993
4994 channel_set_pipes(channel,
4995 (sock_T)CreateFileW(
4996 winpty_conin_name(term->tl_winpty),
4997 GENERIC_WRITE, 0, NULL,
4998 OPEN_EXISTING, 0, NULL),
4999 (sock_T)CreateFileW(
5000 winpty_conout_name(term->tl_winpty),
5001 GENERIC_READ, 0, NULL,
5002 OPEN_EXISTING, 0, NULL),
5003 (sock_T)CreateFileW(
5004 winpty_conerr_name(term->tl_winpty),
5005 GENERIC_READ, 0, NULL,
5006 OPEN_EXISTING, 0, NULL));
5007
5008 /* Write lines with CR instead of NL. */
5009 channel->ch_write_text_mode = TRUE;
5010
5011 jo = CreateJobObject(NULL, NULL);
5012 if (jo == NULL)
5013 goto failed;
5014
5015 if (!AssignProcessToJobObject(jo, child_process_handle))
5016 {
5017 /* Failed, switch the way to terminate process with TerminateProcess. */
5018 CloseHandle(jo);
5019 jo = NULL;
5020 }
5021
5022 winpty_spawn_config_free(spawn_config);
5023 vim_free(cmd_wchar);
5024 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005025 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005026
5027 create_vterm(term, term->tl_rows, term->tl_cols);
5028
5029 channel_set_job(channel, job, opt);
5030 job_set_options(job, opt);
5031
5032 job->jv_channel = channel;
5033 job->jv_proc_info.hProcess = child_process_handle;
5034 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5035 job->jv_job_object = jo;
5036 job->jv_status = JOB_STARTED;
5037 job->jv_tty_in = utf16_to_enc(
5038 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5039 job->jv_tty_out = utf16_to_enc(
5040 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5041 ++job->jv_refcount;
5042 term->tl_job = job;
5043
5044 return OK;
5045
5046failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005047 ga_clear(&ga_cmd);
5048 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005049 vim_free(cmd_wchar);
5050 vim_free(cwd_wchar);
5051 if (spawn_config != NULL)
5052 winpty_spawn_config_free(spawn_config);
5053 if (channel != NULL)
5054 channel_clear(channel);
5055 if (job != NULL)
5056 {
5057 job->jv_channel = NULL;
5058 job_cleanup(job);
5059 }
5060 term->tl_job = NULL;
5061 if (jo != NULL)
5062 CloseHandle(jo);
5063 if (term->tl_winpty != NULL)
5064 winpty_free(term->tl_winpty);
5065 term->tl_winpty = NULL;
5066 if (term->tl_winpty_config != NULL)
5067 winpty_config_free(term->tl_winpty_config);
5068 term->tl_winpty_config = NULL;
5069 if (winpty_err != NULL)
5070 {
5071 char_u *msg = utf16_to_enc(
5072 (short_u *)winpty_error_msg(winpty_err), NULL);
5073
5074 EMSG(msg);
5075 winpty_error_free(winpty_err);
5076 }
5077 return FAIL;
5078}
5079
5080 static int
5081create_pty_only(term_T *term, jobopt_T *options)
5082{
5083 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5084 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5085 char in_name[80], out_name[80];
5086 channel_T *channel = NULL;
5087
5088 create_vterm(term, term->tl_rows, term->tl_cols);
5089
5090 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5091 GetCurrentProcessId(),
5092 curbuf->b_fnum);
5093 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5094 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5095 PIPE_UNLIMITED_INSTANCES,
5096 0, 0, NMPWAIT_NOWAIT, NULL);
5097 if (hPipeIn == INVALID_HANDLE_VALUE)
5098 goto failed;
5099
5100 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5101 GetCurrentProcessId(),
5102 curbuf->b_fnum);
5103 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5104 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5105 PIPE_UNLIMITED_INSTANCES,
5106 0, 0, 0, NULL);
5107 if (hPipeOut == INVALID_HANDLE_VALUE)
5108 goto failed;
5109
5110 ConnectNamedPipe(hPipeIn, NULL);
5111 ConnectNamedPipe(hPipeOut, NULL);
5112
5113 term->tl_job = job_alloc();
5114 if (term->tl_job == NULL)
5115 goto failed;
5116 ++term->tl_job->jv_refcount;
5117
5118 /* behave like the job is already finished */
5119 term->tl_job->jv_status = JOB_FINISHED;
5120
5121 channel = add_channel();
5122 if (channel == NULL)
5123 goto failed;
5124 term->tl_job->jv_channel = channel;
5125 channel->ch_keep_open = TRUE;
5126 channel->ch_named_pipe = TRUE;
5127
5128 channel_set_pipes(channel,
5129 (sock_T)hPipeIn,
5130 (sock_T)hPipeOut,
5131 (sock_T)hPipeOut);
5132 channel_set_job(channel, term->tl_job, options);
5133 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5134 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5135
5136 return OK;
5137
5138failed:
5139 if (hPipeIn != NULL)
5140 CloseHandle(hPipeIn);
5141 if (hPipeOut != NULL)
5142 CloseHandle(hPipeOut);
5143 return FAIL;
5144}
5145
5146/*
5147 * Free the terminal emulator part of "term".
5148 */
5149 static void
5150term_free_vterm(term_T *term)
5151{
5152 if (term->tl_winpty != NULL)
5153 winpty_free(term->tl_winpty);
5154 term->tl_winpty = NULL;
5155 if (term->tl_winpty_config != NULL)
5156 winpty_config_free(term->tl_winpty_config);
5157 term->tl_winpty_config = NULL;
5158 if (term->tl_vterm != NULL)
5159 vterm_free(term->tl_vterm);
5160 term->tl_vterm = NULL;
5161}
5162
5163/*
5164 * Request size to terminal.
5165 */
5166 static void
5167term_report_winsize(term_T *term, int rows, int cols)
5168{
5169 if (term->tl_winpty)
5170 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5171}
5172
5173 int
5174terminal_enabled(void)
5175{
5176 return dyn_winpty_init(FALSE) == OK;
5177}
5178
5179# else
5180
5181/**************************************
5182 * 3. Unix-like implementation.
5183 */
5184
5185/*
5186 * Create a new terminal of "rows" by "cols" cells.
5187 * Start job for "cmd".
5188 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005189 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005190 * Return OK or FAIL.
5191 */
5192 static int
5193term_and_job_init(
5194 term_T *term,
5195 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005196 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005197 jobopt_T *opt)
5198{
5199 create_vterm(term, term->tl_rows, term->tl_cols);
5200
Bram Moolenaar13568252018-03-16 20:46:58 +01005201 /* This may change a string in "argvar". */
5202 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005203 if (term->tl_job != NULL)
5204 ++term->tl_job->jv_refcount;
5205
5206 return term->tl_job != NULL
5207 && term->tl_job->jv_channel != NULL
5208 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5209}
5210
5211 static int
5212create_pty_only(term_T *term, jobopt_T *opt)
5213{
5214 create_vterm(term, term->tl_rows, term->tl_cols);
5215
5216 term->tl_job = job_alloc();
5217 if (term->tl_job == NULL)
5218 return FAIL;
5219 ++term->tl_job->jv_refcount;
5220
5221 /* behave like the job is already finished */
5222 term->tl_job->jv_status = JOB_FINISHED;
5223
5224 return mch_create_pty_channel(term->tl_job, opt);
5225}
5226
5227/*
5228 * Free the terminal emulator part of "term".
5229 */
5230 static void
5231term_free_vterm(term_T *term)
5232{
5233 if (term->tl_vterm != NULL)
5234 vterm_free(term->tl_vterm);
5235 term->tl_vterm = NULL;
5236}
5237
5238/*
5239 * Request size to terminal.
5240 */
5241 static void
5242term_report_winsize(term_T *term, int rows, int cols)
5243{
5244 /* Use an ioctl() to report the new window size to the job. */
5245 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5246 {
5247 int fd = -1;
5248 int part;
5249
5250 for (part = PART_OUT; part < PART_COUNT; ++part)
5251 {
5252 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5253 if (isatty(fd))
5254 break;
5255 }
5256 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5257 mch_signal_job(term->tl_job, (char_u *)"winch");
5258 }
5259}
5260
5261# endif
5262
5263#endif /* FEAT_TERMINAL */