blob: 01919675f1f537f21b6b05dd8a744d7cabe4ed2b [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 * - Win32: Make terminal used for :!cmd in the GUI work better. Allow for
Bram Moolenaar4a696342018-04-05 18:45:26 +020042 * redirection. Probably in call to channel_set_pipes().
Bram Moolenaar802bfb12018-04-15 17:28:13 +020043 * - Win32: Redirecting output does not work, Test_terminal_redir_file()
44 * is disabled.
Bram Moolenaar498c2562018-04-15 23:45:15 +020045 * - Copy text in the vterm to the Vim buffer once in a while, so that
46 * completion works.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +020047 * - When the job only outputs lines, we could handle resizing the terminal
48 * better: store lines separated by line breaks, instead of screen lines,
49 * then when the window is resized redraw those lines.
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020050 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020051 * - For the GUI fill termios with default values, perhaps like pangoterm:
52 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar802bfb12018-04-15 17:28:13 +020053 * - When 'encoding' is not utf-8, or the job is using another encoding, setup
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020054 * conversions.
Bram Moolenaar498c2562018-04-15 23:45:15 +020055 * - Termdebug does not work when Vim build with mzscheme: gdb hangs just after
56 * "run". Everything else works, including communication channel. Not
57 * initializing mzscheme avoid the problem, thus it's not some #ifdef.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020058 */
59
60#include "vim.h"
61
62#if defined(FEAT_TERMINAL) || defined(PROTO)
63
64#ifndef MIN
65# define MIN(x,y) ((x) < (y) ? (x) : (y))
66#endif
67#ifndef MAX
68# define MAX(x,y) ((x) > (y) ? (x) : (y))
69#endif
70
71#include "libvterm/include/vterm.h"
72
73/* This is VTermScreenCell without the characters, thus much smaller. */
74typedef struct {
75 VTermScreenCellAttrs attrs;
76 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010077 VTermColor fg;
78 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020079} cellattr_T;
80
81typedef struct sb_line_S {
82 int sb_cols; /* can differ per line */
83 cellattr_T *sb_cells; /* allocated */
84 cellattr_T sb_fill_attr; /* for short line */
85} sb_line_T;
86
87/* typedef term_T in structs.h */
88struct terminal_S {
89 term_T *tl_next;
90
91 VTerm *tl_vterm;
92 job_T *tl_job;
93 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +010094#if defined(FEAT_GUI)
95 int tl_system; /* when non-zero used for :!cmd output */
96 int tl_toprow; /* row with first line of system terminal */
97#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020098
99 /* Set when setting the size of a vterm, reset after redrawing. */
100 int tl_vterm_size_changed;
101
102 /* used when tl_job is NULL and only a pty was created */
103 int tl_tty_fd;
104 char_u *tl_tty_in;
105 char_u *tl_tty_out;
106
107 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
108 int tl_channel_closed;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100109 int tl_finish;
110#define TL_FINISH_UNSET NUL
111#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
112#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
113#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200114 char_u *tl_opencmd;
115 char_u *tl_eof_chars;
116
117#ifdef WIN3264
118 void *tl_winpty_config;
119 void *tl_winpty;
120#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100121#if defined(FEAT_SESSION)
122 char_u *tl_command;
123#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100124 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200125
126 /* last known vterm size */
127 int tl_rows;
128 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200129
130 char_u *tl_title; /* NULL or allocated */
131 char_u *tl_status_text; /* NULL or allocated */
132
133 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200134 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200135 int tl_dirty_row_end; /* row below last one to update */
136
Bram Moolenaar6eddadf2018-05-06 16:40:16 +0200137 int tl_postponed_scroll; /* to be scrolled up */
138
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200139 garray_T tl_scrollback;
140 int tl_scrollback_scrolled;
141 cellattr_T tl_default_color;
142
Bram Moolenaard96ff162018-02-18 22:13:29 +0100143 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
144 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
145
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200146 VTermPos tl_cursor_pos;
147 int tl_cursor_visible;
148 int tl_cursor_blink;
149 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
150 char_u *tl_cursor_color; /* NULL or allocated */
151
152 int tl_using_altscreen;
153};
154
155#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
156#define TMODE_LOOP 2 /* CTRL-W N used */
157
158/*
159 * List of all active terminals.
160 */
161static term_T *first_term = NULL;
162
163/* Terminal active in terminal_loop(). */
164static term_T *in_terminal_loop = NULL;
165
166#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
167#define KEY_BUF_LEN 200
168
169/*
170 * Functions with separate implementation for MS-Windows and Unix-like systems.
171 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100172static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200173static int create_pty_only(term_T *term, jobopt_T *opt);
174static void term_report_winsize(term_T *term, int rows, int cols);
175static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100176#ifdef FEAT_GUI
177static void update_system_term(term_T *term);
178#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200179
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100180/* The character that we know (or assume) that the terminal expects for the
181 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200182static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200183
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100184/* "Terminal" highlight group colors. */
185static int term_default_cterm_fg = -1;
186static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200187
Bram Moolenaard317b382018-02-08 22:33:31 +0100188/* Store the last set and the desired cursor properties, so that we only update
189 * them when needed. Doing it unnecessary may result in flicker. */
190static char_u *last_set_cursor_color = (char_u *)"";
191static char_u *desired_cursor_color = (char_u *)"";
192static int last_set_cursor_shape = -1;
193static int desired_cursor_shape = -1;
194static int last_set_cursor_blink = -1;
195static int desired_cursor_blink = -1;
196
197
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200198/**************************************
199 * 1. Generic code for all systems.
200 */
201
202/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200203 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
Bram Moolenaar498c2562018-04-15 23:45:15 +0200204 * current window.
205 * Sets "rows" and/or "cols" to zero when it should follow the window size.
206 * Return TRUE if the size is the minimum size: "24*80".
207 */
208 static int
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200209parse_termwinsize(win_T *wp, int *rows, int *cols)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200210{
211 int minsize = FALSE;
212
213 *rows = 0;
214 *cols = 0;
215
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200216 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200217 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200218 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200219
220 /* Syntax of value was already checked when it's set. */
221 if (p == NULL)
222 {
223 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200224 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200225 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200226 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200227 *cols = atoi((char *)p + 1);
228 }
229 return minsize;
230}
231
232/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200233 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200234 */
235 static void
236set_term_and_win_size(term_T *term)
237{
Bram Moolenaar13568252018-03-16 20:46:58 +0100238#ifdef FEAT_GUI
239 if (term->tl_system)
240 {
241 /* Use the whole screen for the system command. However, it will start
242 * at the command line and scroll up as needed, using tl_toprow. */
243 term->tl_rows = Rows;
244 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200245 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100246 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100247#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200248 if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200249 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200250 if (term->tl_rows != 0)
251 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
252 if (term->tl_cols != 0)
253 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200254 }
255 if (term->tl_rows == 0)
256 term->tl_rows = curwin->w_height;
257 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200258 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200259 if (term->tl_cols == 0)
260 term->tl_cols = curwin->w_width;
261 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200262 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200263}
264
265/*
266 * Initialize job options for a terminal job.
267 * Caller may overrule some of them.
268 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100269 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200270init_job_options(jobopt_T *opt)
271{
272 clear_job_options(opt);
273
274 opt->jo_mode = MODE_RAW;
275 opt->jo_out_mode = MODE_RAW;
276 opt->jo_err_mode = MODE_RAW;
277 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
278}
279
280/*
281 * Set job options mandatory for a terminal job.
282 */
283 static void
284setup_job_options(jobopt_T *opt, int rows, int cols)
285{
286 if (!(opt->jo_set & JO_OUT_IO))
287 {
288 /* Connect stdout to the terminal. */
289 opt->jo_io[PART_OUT] = JIO_BUFFER;
290 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
291 opt->jo_modifiable[PART_OUT] = 0;
292 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
293 }
294
295 if (!(opt->jo_set & JO_ERR_IO))
296 {
297 /* Connect stderr to the terminal. */
298 opt->jo_io[PART_ERR] = JIO_BUFFER;
299 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
300 opt->jo_modifiable[PART_ERR] = 0;
301 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
302 }
303
304 opt->jo_pty = TRUE;
305 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
306 opt->jo_term_rows = rows;
307 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
308 opt->jo_term_cols = cols;
309}
310
311/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100312 * Close a terminal buffer (and its window). Used when creating the terminal
313 * fails.
314 */
315 static void
316term_close_buffer(buf_T *buf, buf_T *old_curbuf)
317{
318 free_terminal(buf);
319 if (old_curbuf != NULL)
320 {
321 --curbuf->b_nwindows;
322 curbuf = old_curbuf;
323 curwin->w_buffer = curbuf;
324 ++curbuf->b_nwindows;
325 }
326
327 /* Wiping out the buffer will also close the window and call
328 * free_terminal(). */
329 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
330}
331
332/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200333 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100334 * Use either "argvar" or "argv", the other must be NULL.
335 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
336 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200337 * Returns NULL when failed.
338 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100339 buf_T *
340term_start(
341 typval_T *argvar,
342 char **argv,
343 jobopt_T *opt,
344 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200345{
346 exarg_T split_ea;
347 win_T *old_curwin = curwin;
348 term_T *term;
349 buf_T *old_curbuf = NULL;
350 int res;
351 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100352 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200353
354 if (check_restricted() || check_secure())
355 return NULL;
356
357 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
358 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
359 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
360 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
361 {
362 EMSG(_(e_invarg));
363 return NULL;
364 }
365
366 term = (term_T *)alloc_clear(sizeof(term_T));
367 if (term == NULL)
368 return NULL;
369 term->tl_dirty_row_end = MAX_ROW;
370 term->tl_cursor_visible = TRUE;
371 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
372 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100373#ifdef FEAT_GUI
374 term->tl_system = (flags & TERM_START_SYSTEM);
375#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200376 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
377
378 vim_memset(&split_ea, 0, sizeof(split_ea));
379 if (opt->jo_curwin)
380 {
381 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100382 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200383 {
384 no_write_message();
385 vim_free(term);
386 return NULL;
387 }
388 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100389 ECMD_HIDE
390 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
391 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200392 {
393 vim_free(term);
394 return NULL;
395 }
396 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100397 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200398 {
399 buf_T *buf;
400
401 /* Create a new buffer without a window. Make it the current buffer for
402 * a moment to be able to do the initialisations. */
403 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
404 BLN_NEW | BLN_LISTED);
405 if (buf == NULL || ml_open(buf) == FAIL)
406 {
407 vim_free(term);
408 return NULL;
409 }
410 old_curbuf = curbuf;
411 --curbuf->b_nwindows;
412 curbuf = buf;
413 curwin->w_buffer = buf;
414 ++curbuf->b_nwindows;
415 }
416 else
417 {
418 /* Open a new window or tab. */
419 split_ea.cmdidx = CMD_new;
420 split_ea.cmd = (char_u *)"new";
421 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100422 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200423 {
424 split_ea.line2 = opt->jo_term_rows;
425 split_ea.addr_count = 1;
426 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100427 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200428 {
429 split_ea.line2 = opt->jo_term_cols;
430 split_ea.addr_count = 1;
431 }
432
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100433 if (vertical)
434 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200435 ex_splitview(&split_ea);
436 if (curwin == old_curwin)
437 {
438 /* split failed */
439 vim_free(term);
440 return NULL;
441 }
442 }
443 term->tl_buffer = curbuf;
444 curbuf->b_term = term;
445
446 if (!opt->jo_hidden)
447 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100448 /* Only one size was taken care of with :new, do the other one. With
449 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100450 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200451 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100452 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200453 win_setwidth(opt->jo_term_cols);
454 }
455
456 /* Link the new terminal in the list of active terminals. */
457 term->tl_next = first_term;
458 first_term = term;
459
460 if (opt->jo_term_name != NULL)
461 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100462 else if (argv != NULL)
463 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200464 else
465 {
466 int i;
467 size_t len;
468 char_u *cmd, *p;
469
470 if (argvar->v_type == VAR_STRING)
471 {
472 cmd = argvar->vval.v_string;
473 if (cmd == NULL)
474 cmd = (char_u *)"";
475 else if (STRCMP(cmd, "NONE") == 0)
476 cmd = (char_u *)"pty";
477 }
478 else if (argvar->v_type != VAR_LIST
479 || argvar->vval.v_list == NULL
480 || argvar->vval.v_list->lv_len < 1
481 || (cmd = get_tv_string_chk(
482 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
483 cmd = (char_u*)"";
484
485 len = STRLEN(cmd) + 10;
486 p = alloc((int)len);
487
488 for (i = 0; p != NULL; ++i)
489 {
490 /* Prepend a ! to the command name to avoid the buffer name equals
491 * the executable, otherwise ":w!" would overwrite it. */
492 if (i == 0)
493 vim_snprintf((char *)p, len, "!%s", cmd);
494 else
495 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
496 if (buflist_findname(p) == NULL)
497 {
498 vim_free(curbuf->b_ffname);
499 curbuf->b_ffname = p;
500 break;
501 }
502 }
503 }
504 curbuf->b_fname = curbuf->b_ffname;
505
506 if (opt->jo_term_opencmd != NULL)
507 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
508
509 if (opt->jo_eof_chars != NULL)
510 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
511
512 set_string_option_direct((char_u *)"buftype", -1,
513 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
514
515 /* Mark the buffer as not modifiable. It can only be made modifiable after
516 * the job finished. */
517 curbuf->b_p_ma = FALSE;
518
519 set_term_and_win_size(term);
520 setup_job_options(opt, term->tl_rows, term->tl_cols);
521
Bram Moolenaar13568252018-03-16 20:46:58 +0100522 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100523 return curbuf;
524
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100525#if defined(FEAT_SESSION)
526 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100527 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100528 {
529 term->tl_command = vim_strsave((char_u *)"NONE");
530 }
531 else if (argvar->v_type == VAR_STRING)
532 {
533 char_u *cmd = argvar->vval.v_string;
534
535 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
536 term->tl_command = vim_strsave(cmd);
537 }
538 else if (argvar->v_type == VAR_LIST
539 && argvar->vval.v_list != NULL
540 && argvar->vval.v_list->lv_len > 0)
541 {
542 garray_T ga;
543 listitem_T *item;
544
545 ga_init2(&ga, 1, 100);
546 for (item = argvar->vval.v_list->lv_first;
547 item != NULL; item = item->li_next)
548 {
549 char_u *s = get_tv_string_chk(&item->li_tv);
550 char_u *p;
551
552 if (s == NULL)
553 break;
554 p = vim_strsave_fnameescape(s, FALSE);
555 if (p == NULL)
556 break;
557 ga_concat(&ga, p);
558 vim_free(p);
559 ga_append(&ga, ' ');
560 }
561 if (item == NULL)
562 {
563 ga_append(&ga, NUL);
564 term->tl_command = ga.ga_data;
565 }
566 else
567 ga_clear(&ga);
568 }
569#endif
570
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100571 if (opt->jo_term_kill != NULL)
572 {
573 char_u *p = skiptowhite(opt->jo_term_kill);
574
575 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
576 }
577
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200578 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100579 if (argv == NULL
580 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200581 && argvar->vval.v_string != NULL
582 && STRCMP(argvar->vval.v_string, "NONE") == 0)
583 res = create_pty_only(term, opt);
584 else
Bram Moolenaar13568252018-03-16 20:46:58 +0100585 res = term_and_job_init(term, argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200586
587 newbuf = curbuf;
588 if (res == OK)
589 {
590 /* Get and remember the size we ended up with. Update the pty. */
591 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
592 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100593#ifdef FEAT_GUI
594 if (term->tl_system)
595 {
596 /* display first line below typed command */
597 term->tl_toprow = msg_row + 1;
598 term->tl_dirty_row_end = 0;
599 }
600#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200601
602 /* Make sure we don't get stuck on sending keys to the job, it leads to
603 * a deadlock if the job is waiting for Vim to read. */
604 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
605
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200606 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200607 {
608 --curbuf->b_nwindows;
609 curbuf = old_curbuf;
610 curwin->w_buffer = curbuf;
611 ++curbuf->b_nwindows;
612 }
613 }
614 else
615 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100616 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200617 return NULL;
618 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100619
Bram Moolenaar13568252018-03-16 20:46:58 +0100620 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200621 return newbuf;
622}
623
624/*
625 * ":terminal": open a terminal window and execute a job in it.
626 */
627 void
628ex_terminal(exarg_T *eap)
629{
630 typval_T argvar[2];
631 jobopt_T opt;
632 char_u *cmd;
633 char_u *tofree = NULL;
634
635 init_job_options(&opt);
636
637 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100638 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200639 {
640 char_u *p, *ep;
641
642 cmd += 2;
643 p = skiptowhite(cmd);
644 ep = vim_strchr(cmd, '=');
645 if (ep != NULL && ep < p)
646 p = ep;
647
648 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
649 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100650 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
651 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200652 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
653 opt.jo_term_finish = 'o';
654 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
655 opt.jo_curwin = 1;
656 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
657 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100658 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
659 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100660 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
661 && ep != NULL)
662 {
663 opt.jo_set2 |= JO2_TERM_KILL;
664 opt.jo_term_kill = ep + 1;
665 p = skiptowhite(cmd);
666 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200667 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
668 && ep != NULL && isdigit(ep[1]))
669 {
670 opt.jo_set2 |= JO2_TERM_ROWS;
671 opt.jo_term_rows = atoi((char *)ep + 1);
672 p = skiptowhite(cmd);
673 }
674 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
675 && ep != NULL && isdigit(ep[1]))
676 {
677 opt.jo_set2 |= JO2_TERM_COLS;
678 opt.jo_term_cols = atoi((char *)ep + 1);
679 p = skiptowhite(cmd);
680 }
681 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
682 && ep != NULL)
683 {
684 char_u *buf = NULL;
685 char_u *keys;
686
687 p = skiptowhite(cmd);
688 *p = NUL;
689 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
690 opt.jo_set2 |= JO2_EOF_CHARS;
691 opt.jo_eof_chars = vim_strsave(keys);
692 vim_free(buf);
693 *p = ' ';
694 }
695 else
696 {
697 if (*p)
698 *p = NUL;
699 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100700 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200701 }
702 cmd = skipwhite(p);
703 }
704 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100705 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200706 /* Make a copy of 'shell', an autocommand may change the option. */
707 tofree = cmd = vim_strsave(p_sh);
708
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100709 /* default to close when the shell exits */
710 if (opt.jo_term_finish == NUL)
711 opt.jo_term_finish = 'c';
712 }
713
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200714 if (eap->addr_count > 0)
715 {
716 /* Write lines from current buffer to the job. */
717 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
718 opt.jo_io[PART_IN] = JIO_BUFFER;
719 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
720 opt.jo_in_top = eap->line1;
721 opt.jo_in_bot = eap->line2;
722 }
723
724 argvar[0].v_type = VAR_STRING;
725 argvar[0].vval.v_string = cmd;
726 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100727 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200728 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100729
730theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200731 vim_free(opt.jo_eof_chars);
732}
733
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100734#if defined(FEAT_SESSION) || defined(PROTO)
735/*
736 * Write a :terminal command to the session file to restore the terminal in
737 * window "wp".
738 * Return FAIL if writing fails.
739 */
740 int
741term_write_session(FILE *fd, win_T *wp)
742{
743 term_T *term = wp->w_buffer->b_term;
744
745 /* Create the terminal and run the command. This is not without
746 * risk, but let's assume the user only creates a session when this
747 * will be OK. */
748 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
749 term->tl_cols, term->tl_rows) < 0)
750 return FAIL;
751 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
752 return FAIL;
753
754 return put_eol(fd);
755}
756
757/*
758 * Return TRUE if "buf" has a terminal that should be restored.
759 */
760 int
761term_should_restore(buf_T *buf)
762{
763 term_T *term = buf->b_term;
764
765 return term != NULL && (term->tl_command == NULL
766 || STRCMP(term->tl_command, "NONE") != 0);
767}
768#endif
769
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200770/*
771 * Free the scrollback buffer for "term".
772 */
773 static void
774free_scrollback(term_T *term)
775{
776 int i;
777
778 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
779 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
780 ga_clear(&term->tl_scrollback);
781}
782
783/*
784 * Free a terminal and everything it refers to.
785 * Kills the job if there is one.
786 * Called when wiping out a buffer.
787 */
788 void
789free_terminal(buf_T *buf)
790{
791 term_T *term = buf->b_term;
792 term_T *tp;
793
794 if (term == NULL)
795 return;
796 if (first_term == term)
797 first_term = term->tl_next;
798 else
799 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
800 if (tp->tl_next == term)
801 {
802 tp->tl_next = term->tl_next;
803 break;
804 }
805
806 if (term->tl_job != NULL)
807 {
808 if (term->tl_job->jv_status != JOB_ENDED
809 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100810 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200811 job_stop(term->tl_job, NULL, "kill");
812 job_unref(term->tl_job);
813 }
814
815 free_scrollback(term);
816
817 term_free_vterm(term);
818 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100819#ifdef FEAT_SESSION
820 vim_free(term->tl_command);
821#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100822 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200823 vim_free(term->tl_status_text);
824 vim_free(term->tl_opencmd);
825 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100826 if (desired_cursor_color == term->tl_cursor_color)
827 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200828 vim_free(term->tl_cursor_color);
829 vim_free(term);
830 buf->b_term = NULL;
831 if (in_terminal_loop == term)
832 in_terminal_loop = NULL;
833}
834
835/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100836 * Get the part that is connected to the tty. Normally this is PART_IN, but
837 * when writing buffer lines to the job it can be another. This makes it
838 * possible to do "1,5term vim -".
839 */
840 static ch_part_T
841get_tty_part(term_T *term)
842{
843#ifdef UNIX
844 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
845 int i;
846
847 for (i = 0; i < 3; ++i)
848 {
849 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
850
851 if (isatty(fd))
852 return parts[i];
853 }
854#endif
855 return PART_IN;
856}
857
858/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200859 * Write job output "msg[len]" to the vterm.
860 */
861 static void
862term_write_job_output(term_T *term, char_u *msg, size_t len)
863{
864 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100865 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200866
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100867 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200868
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100869 /* flush vterm buffer when vterm responded to control sequence */
870 if (prevlen != vterm_output_get_buffer_current(vterm))
871 {
872 char buf[KEY_BUF_LEN];
873 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
874
875 if (curlen > 0)
876 channel_send(term->tl_job->jv_channel, get_tty_part(term),
877 (char_u *)buf, (int)curlen, NULL);
878 }
879
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200880 /* this invokes the damage callbacks */
881 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
882}
883
884 static void
885update_cursor(term_T *term, int redraw)
886{
887 if (term->tl_normal_mode)
888 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100889#ifdef FEAT_GUI
890 if (term->tl_system)
891 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
892 term->tl_cursor_pos.col);
893 else
894#endif
895 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200896 if (redraw)
897 {
898 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
899 cursor_on();
900 out_flush();
901#ifdef FEAT_GUI
902 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100903 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200904 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100905 gui_mch_flush();
906 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200907#endif
908 }
909}
910
911/*
912 * Invoked when "msg" output from a job was received. Write it to the terminal
913 * of "buffer".
914 */
915 void
916write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
917{
918 size_t len = STRLEN(msg);
919 term_T *term = buffer->b_term;
920
921 if (term->tl_vterm == NULL)
922 {
923 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
924 return;
925 }
926 ch_log(channel, "writing %d bytes to terminal", (int)len);
927 term_write_job_output(term, msg, len);
928
Bram Moolenaar13568252018-03-16 20:46:58 +0100929#ifdef FEAT_GUI
930 if (term->tl_system)
931 {
932 /* show system output, scrolling up the screen as needed */
933 update_system_term(term);
934 update_cursor(term, TRUE);
935 }
936 else
937#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200938 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
939 * contents, thus no screen update is needed. */
940 if (!term->tl_normal_mode)
941 {
942 /* TODO: only update once in a while. */
943 ch_log(term->tl_job->jv_channel, "updating screen");
944 if (buffer == curbuf)
945 {
946 update_screen(0);
947 update_cursor(term, TRUE);
948 }
949 else
950 redraw_after_callback(TRUE);
951 }
952}
953
954/*
955 * Send a mouse position and click to the vterm
956 */
957 static int
958term_send_mouse(VTerm *vterm, int button, int pressed)
959{
960 VTermModifier mod = VTERM_MOD_NONE;
961
962 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200963 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100964 if (button != 0)
965 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200966 return TRUE;
967}
968
Bram Moolenaarc48369c2018-03-11 19:30:45 +0100969static int enter_mouse_col = -1;
970static int enter_mouse_row = -1;
971
972/*
973 * Handle a mouse click, drag or release.
974 * Return TRUE when a mouse event is sent to the terminal.
975 */
976 static int
977term_mouse_click(VTerm *vterm, int key)
978{
979#if defined(FEAT_CLIPBOARD)
980 /* For modeless selection mouse drag and release events are ignored, unless
981 * they are preceded with a mouse down event */
982 static int ignore_drag_release = TRUE;
983 VTermMouseState mouse_state;
984
985 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
986 if (mouse_state.flags == 0)
987 {
988 /* Terminal is not using the mouse, use modeless selection. */
989 switch (key)
990 {
991 case K_LEFTDRAG:
992 case K_LEFTRELEASE:
993 case K_RIGHTDRAG:
994 case K_RIGHTRELEASE:
995 /* Ignore drag and release events when the button-down wasn't
996 * seen before. */
997 if (ignore_drag_release)
998 {
999 int save_mouse_col, save_mouse_row;
1000
1001 if (enter_mouse_col < 0)
1002 break;
1003
1004 /* mouse click in the window gave us focus, handle that
1005 * click now */
1006 save_mouse_col = mouse_col;
1007 save_mouse_row = mouse_row;
1008 mouse_col = enter_mouse_col;
1009 mouse_row = enter_mouse_row;
1010 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1011 mouse_col = save_mouse_col;
1012 mouse_row = save_mouse_row;
1013 }
1014 /* FALLTHROUGH */
1015 case K_LEFTMOUSE:
1016 case K_RIGHTMOUSE:
1017 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1018 ignore_drag_release = TRUE;
1019 else
1020 ignore_drag_release = FALSE;
1021 /* Should we call mouse_has() here? */
1022 if (clip_star.available)
1023 {
1024 int button, is_click, is_drag;
1025
1026 button = get_mouse_button(KEY2TERMCAP1(key),
1027 &is_click, &is_drag);
1028 if (mouse_model_popup() && button == MOUSE_LEFT
1029 && (mod_mask & MOD_MASK_SHIFT))
1030 {
1031 /* Translate shift-left to right button. */
1032 button = MOUSE_RIGHT;
1033 mod_mask &= ~MOD_MASK_SHIFT;
1034 }
1035 clip_modeless(button, is_click, is_drag);
1036 }
1037 break;
1038
1039 case K_MIDDLEMOUSE:
1040 if (clip_star.available)
1041 insert_reg('*', TRUE);
1042 break;
1043 }
1044 enter_mouse_col = -1;
1045 return FALSE;
1046 }
1047#endif
1048 enter_mouse_col = -1;
1049
1050 switch (key)
1051 {
1052 case K_LEFTMOUSE:
1053 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1054 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1055 case K_LEFTRELEASE:
1056 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1057 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1058 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1059 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1060 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1061 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1062 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1063 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1064 }
1065 return TRUE;
1066}
1067
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001068/*
1069 * Convert typed key "c" into bytes to send to the job.
1070 * Return the number of bytes in "buf".
1071 */
1072 static int
1073term_convert_key(term_T *term, int c, char *buf)
1074{
1075 VTerm *vterm = term->tl_vterm;
1076 VTermKey key = VTERM_KEY_NONE;
1077 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001078 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001079
1080 switch (c)
1081 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001082 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1083
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001084 /* don't use VTERM_KEY_BACKSPACE, it always
1085 * becomes 0x7f DEL */
1086 case K_BS: c = term_backspace_char; break;
1087
1088 case ESC: key = VTERM_KEY_ESCAPE; break;
1089 case K_DEL: key = VTERM_KEY_DEL; break;
1090 case K_DOWN: key = VTERM_KEY_DOWN; break;
1091 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1092 key = VTERM_KEY_DOWN; break;
1093 case K_END: key = VTERM_KEY_END; break;
1094 case K_S_END: mod = VTERM_MOD_SHIFT;
1095 key = VTERM_KEY_END; break;
1096 case K_C_END: mod = VTERM_MOD_CTRL;
1097 key = VTERM_KEY_END; break;
1098 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1099 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1100 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1101 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1102 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1103 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1104 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1105 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1106 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1107 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1108 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1109 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1110 case K_HOME: key = VTERM_KEY_HOME; break;
1111 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1112 key = VTERM_KEY_HOME; break;
1113 case K_C_HOME: mod = VTERM_MOD_CTRL;
1114 key = VTERM_KEY_HOME; break;
1115 case K_INS: key = VTERM_KEY_INS; break;
1116 case K_K0: key = VTERM_KEY_KP_0; break;
1117 case K_K1: key = VTERM_KEY_KP_1; break;
1118 case K_K2: key = VTERM_KEY_KP_2; break;
1119 case K_K3: key = VTERM_KEY_KP_3; break;
1120 case K_K4: key = VTERM_KEY_KP_4; break;
1121 case K_K5: key = VTERM_KEY_KP_5; break;
1122 case K_K6: key = VTERM_KEY_KP_6; break;
1123 case K_K7: key = VTERM_KEY_KP_7; break;
1124 case K_K8: key = VTERM_KEY_KP_8; break;
1125 case K_K9: key = VTERM_KEY_KP_9; break;
1126 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1127 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1128 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1129 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1130 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1131 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1132 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1133 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1134 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1135 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1136 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1137 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1138 case K_LEFT: key = VTERM_KEY_LEFT; break;
1139 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1140 key = VTERM_KEY_LEFT; break;
1141 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1142 key = VTERM_KEY_LEFT; break;
1143 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1144 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1145 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1146 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1147 key = VTERM_KEY_RIGHT; break;
1148 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1149 key = VTERM_KEY_RIGHT; break;
1150 case K_UP: key = VTERM_KEY_UP; break;
1151 case K_S_UP: mod = VTERM_MOD_SHIFT;
1152 key = VTERM_KEY_UP; break;
1153 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001154 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1155 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001156
Bram Moolenaara42ad572017-11-16 13:08:04 +01001157 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1158 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001159 case K_MOUSELEFT: /* TODO */ return 0;
1160 case K_MOUSERIGHT: /* TODO */ return 0;
1161
1162 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001163 case K_LEFTMOUSE_NM:
1164 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001165 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001166 case K_LEFTRELEASE_NM:
1167 case K_MOUSEMOVE:
1168 case K_MIDDLEMOUSE:
1169 case K_MIDDLEDRAG:
1170 case K_MIDDLERELEASE:
1171 case K_RIGHTMOUSE:
1172 case K_RIGHTDRAG:
1173 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1174 return 0;
1175 other = TRUE;
1176 break;
1177
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001178 case K_X1MOUSE: /* TODO */ return 0;
1179 case K_X1DRAG: /* TODO */ return 0;
1180 case K_X1RELEASE: /* TODO */ return 0;
1181 case K_X2MOUSE: /* TODO */ return 0;
1182 case K_X2DRAG: /* TODO */ return 0;
1183 case K_X2RELEASE: /* TODO */ return 0;
1184
1185 case K_IGNORE: return 0;
1186 case K_NOP: return 0;
1187 case K_UNDO: return 0;
1188 case K_HELP: return 0;
1189 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1190 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1191 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1192 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1193 case K_SELECT: return 0;
1194#ifdef FEAT_GUI
1195 case K_VER_SCROLLBAR: return 0;
1196 case K_HOR_SCROLLBAR: return 0;
1197#endif
1198#ifdef FEAT_GUI_TABLINE
1199 case K_TABLINE: return 0;
1200 case K_TABMENU: return 0;
1201#endif
1202#ifdef FEAT_NETBEANS_INTG
1203 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1204#endif
1205#ifdef FEAT_DND
1206 case K_DROP: return 0;
1207#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001208 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001209 case K_PS: vterm_keyboard_start_paste(vterm);
1210 other = TRUE;
1211 break;
1212 case K_PE: vterm_keyboard_end_paste(vterm);
1213 other = TRUE;
1214 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001215 }
1216
1217 /*
1218 * Convert special keys to vterm keys:
1219 * - Write keys to vterm: vterm_keyboard_key()
1220 * - Write output to channel.
1221 * TODO: use mod_mask
1222 */
1223 if (key != VTERM_KEY_NONE)
1224 /* Special key, let vterm convert it. */
1225 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001226 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001227 /* Normal character, let vterm convert it. */
1228 vterm_keyboard_unichar(vterm, c, mod);
1229
1230 /* Read back the converted escape sequence. */
1231 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1232}
1233
1234/*
1235 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001236 * If "check_job_status" is TRUE update the job status.
1237 */
1238 static int
1239term_job_running_check(term_T *term, int check_job_status)
1240{
1241 /* Also consider the job finished when the channel is closed, to avoid a
1242 * race condition when updating the title. */
1243 if (term != NULL
1244 && term->tl_job != NULL
1245 && channel_is_open(term->tl_job->jv_channel))
1246 {
1247 if (check_job_status)
1248 job_status(term->tl_job);
1249 return (term->tl_job->jv_status == JOB_STARTED
1250 || term->tl_job->jv_channel->ch_keep_open);
1251 }
1252 return FALSE;
1253}
1254
1255/*
1256 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001257 */
1258 int
1259term_job_running(term_T *term)
1260{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001261 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001262}
1263
1264/*
1265 * Return TRUE if "term" has an active channel and used ":term NONE".
1266 */
1267 int
1268term_none_open(term_T *term)
1269{
1270 /* Also consider the job finished when the channel is closed, to avoid a
1271 * race condition when updating the title. */
1272 return term != NULL
1273 && term->tl_job != NULL
1274 && channel_is_open(term->tl_job->jv_channel)
1275 && term->tl_job->jv_channel->ch_keep_open;
1276}
1277
1278/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001279 * Used when exiting: kill the job in "buf" if so desired.
1280 * Return OK when the job finished.
1281 * Return FAIL when the job is still running.
1282 */
1283 int
1284term_try_stop_job(buf_T *buf)
1285{
1286 int count;
1287 char *how = (char *)buf->b_term->tl_kill;
1288
1289#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1290 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1291 {
1292 char_u buff[DIALOG_MSG_SIZE];
1293 int ret;
1294
1295 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1296 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1297 if (ret == VIM_YES)
1298 how = "kill";
1299 else if (ret == VIM_CANCEL)
1300 return FAIL;
1301 }
1302#endif
1303 if (how == NULL || *how == NUL)
1304 return FAIL;
1305
1306 job_stop(buf->b_term->tl_job, NULL, how);
1307
1308 /* wait for up to a second for the job to die */
1309 for (count = 0; count < 100; ++count)
1310 {
1311 /* buffer, terminal and job may be cleaned up while waiting */
1312 if (!buf_valid(buf)
1313 || buf->b_term == NULL
1314 || buf->b_term->tl_job == NULL)
1315 return OK;
1316
1317 /* call job_status() to update jv_status */
1318 job_status(buf->b_term->tl_job);
1319 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1320 return OK;
1321 ui_delay(10L, FALSE);
1322 mch_check_messages();
1323 parse_queued_messages();
1324 }
1325 return FAIL;
1326}
1327
1328/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001329 * Add the last line of the scrollback buffer to the buffer in the window.
1330 */
1331 static void
1332add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1333{
1334 buf_T *buf = term->tl_buffer;
1335 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1336 linenr_T lnum = buf->b_ml.ml_line_count;
1337
1338#ifdef WIN3264
1339 if (!enc_utf8 && enc_codepage > 0)
1340 {
1341 WCHAR *ret = NULL;
1342 int length = 0;
1343
1344 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1345 &ret, &length);
1346 if (ret != NULL)
1347 {
1348 WideCharToMultiByte_alloc(enc_codepage, 0,
1349 ret, length, (char **)&text, &len, 0, 0);
1350 vim_free(ret);
1351 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1352 vim_free(text);
1353 }
1354 }
1355 else
1356#endif
1357 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1358 if (empty)
1359 {
1360 /* Delete the empty line that was in the empty buffer. */
1361 curbuf = buf;
1362 ml_delete(1, FALSE);
1363 curbuf = curwin->w_buffer;
1364 }
1365}
1366
1367 static void
1368cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1369{
1370 attr->width = cell->width;
1371 attr->attrs = cell->attrs;
1372 attr->fg = cell->fg;
1373 attr->bg = cell->bg;
1374}
1375
1376 static int
1377equal_celattr(cellattr_T *a, cellattr_T *b)
1378{
1379 /* Comparing the colors should be sufficient. */
1380 return a->fg.red == b->fg.red
1381 && a->fg.green == b->fg.green
1382 && a->fg.blue == b->fg.blue
1383 && a->bg.red == b->bg.red
1384 && a->bg.green == b->bg.green
1385 && a->bg.blue == b->bg.blue;
1386}
1387
Bram Moolenaard96ff162018-02-18 22:13:29 +01001388/*
1389 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1390 * line at this position. Otherwise at the end.
1391 */
1392 static int
1393add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1394{
1395 if (ga_grow(&term->tl_scrollback, 1) == OK)
1396 {
1397 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1398 + term->tl_scrollback.ga_len;
1399
1400 if (lnum > 0)
1401 {
1402 int i;
1403
1404 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1405 {
1406 *line = *(line - 1);
1407 --line;
1408 }
1409 }
1410 line->sb_cols = 0;
1411 line->sb_cells = NULL;
1412 line->sb_fill_attr = *fill_attr;
1413 ++term->tl_scrollback.ga_len;
1414 return OK;
1415 }
1416 return FALSE;
1417}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001418
1419/*
1420 * Add the current lines of the terminal to scrollback and to the buffer.
1421 * Called after the job has ended and when switching to Terminal-Normal mode.
1422 */
1423 static void
1424move_terminal_to_buffer(term_T *term)
1425{
1426 win_T *wp;
1427 int len;
1428 int lines_skipped = 0;
1429 VTermPos pos;
1430 VTermScreenCell cell;
1431 cellattr_T fill_attr, new_fill_attr;
1432 cellattr_T *p;
1433 VTermScreen *screen;
1434
1435 if (term->tl_vterm == NULL)
1436 return;
1437 screen = vterm_obtain_screen(term->tl_vterm);
1438 fill_attr = new_fill_attr = term->tl_default_color;
1439
1440 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1441 {
1442 len = 0;
1443 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1444 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1445 && cell.chars[0] != NUL)
1446 {
1447 len = pos.col + 1;
1448 new_fill_attr = term->tl_default_color;
1449 }
1450 else
1451 /* Assume the last attr is the filler attr. */
1452 cell2cellattr(&cell, &new_fill_attr);
1453
1454 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1455 ++lines_skipped;
1456 else
1457 {
1458 while (lines_skipped > 0)
1459 {
1460 /* Line was skipped, add an empty line. */
1461 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001462 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001463 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001464 }
1465
1466 if (len == 0)
1467 p = NULL;
1468 else
1469 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1470 if ((p != NULL || len == 0)
1471 && ga_grow(&term->tl_scrollback, 1) == OK)
1472 {
1473 garray_T ga;
1474 int width;
1475 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1476 + term->tl_scrollback.ga_len;
1477
1478 ga_init2(&ga, 1, 100);
1479 for (pos.col = 0; pos.col < len; pos.col += width)
1480 {
1481 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1482 {
1483 width = 1;
1484 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1485 if (ga_grow(&ga, 1) == OK)
1486 ga.ga_len += utf_char2bytes(' ',
1487 (char_u *)ga.ga_data + ga.ga_len);
1488 }
1489 else
1490 {
1491 width = cell.width;
1492
1493 cell2cellattr(&cell, &p[pos.col]);
1494
1495 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1496 {
1497 int i;
1498 int c;
1499
1500 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1501 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1502 (char_u *)ga.ga_data + ga.ga_len);
1503 }
1504 }
1505 }
1506 line->sb_cols = len;
1507 line->sb_cells = p;
1508 line->sb_fill_attr = new_fill_attr;
1509 fill_attr = new_fill_attr;
1510 ++term->tl_scrollback.ga_len;
1511
1512 if (ga_grow(&ga, 1) == FAIL)
1513 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1514 else
1515 {
1516 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1517 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1518 }
1519 ga_clear(&ga);
1520 }
1521 else
1522 vim_free(p);
1523 }
1524 }
1525
1526 /* Obtain the current background color. */
1527 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1528 &term->tl_default_color.fg, &term->tl_default_color.bg);
1529
1530 FOR_ALL_WINDOWS(wp)
1531 {
1532 if (wp->w_buffer == term->tl_buffer)
1533 {
1534 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1535 wp->w_cursor.col = 0;
1536 wp->w_valid = 0;
1537 if (wp->w_cursor.lnum >= wp->w_height)
1538 {
1539 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1540
1541 if (wp->w_topline < min_topline)
1542 wp->w_topline = min_topline;
1543 }
1544 redraw_win_later(wp, NOT_VALID);
1545 }
1546 }
1547}
1548
1549 static void
1550set_terminal_mode(term_T *term, int normal_mode)
1551{
1552 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001553 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001554 if (term->tl_buffer == curbuf)
1555 maketitle();
1556}
1557
1558/*
1559 * Called after the job if finished and Terminal mode is not active:
1560 * Move the vterm contents into the scrollback buffer and free the vterm.
1561 */
1562 static void
1563cleanup_vterm(term_T *term)
1564{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001565 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001566 move_terminal_to_buffer(term);
1567 term_free_vterm(term);
1568 set_terminal_mode(term, FALSE);
1569}
1570
1571/*
1572 * Switch from Terminal-Job mode to Terminal-Normal mode.
1573 * Suspends updating the terminal window.
1574 */
1575 static void
1576term_enter_normal_mode(void)
1577{
1578 term_T *term = curbuf->b_term;
1579
1580 /* Append the current terminal contents to the buffer. */
1581 move_terminal_to_buffer(term);
1582
1583 set_terminal_mode(term, TRUE);
1584
1585 /* Move the window cursor to the position of the cursor in the
1586 * terminal. */
1587 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1588 + term->tl_cursor_pos.row + 1;
1589 check_cursor();
1590 coladvance(term->tl_cursor_pos.col);
1591
1592 /* Display the same lines as in the terminal. */
1593 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1594}
1595
1596/*
1597 * Returns TRUE if the current window contains a terminal and we are in
1598 * Terminal-Normal mode.
1599 */
1600 int
1601term_in_normal_mode(void)
1602{
1603 term_T *term = curbuf->b_term;
1604
1605 return term != NULL && term->tl_normal_mode;
1606}
1607
1608/*
1609 * Switch from Terminal-Normal mode to Terminal-Job mode.
1610 * Restores updating the terminal window.
1611 */
1612 void
1613term_enter_job_mode()
1614{
1615 term_T *term = curbuf->b_term;
1616 sb_line_T *line;
1617 garray_T *gap;
1618
1619 /* Remove the terminal contents from the scrollback and the buffer. */
1620 gap = &term->tl_scrollback;
1621 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1622 && gap->ga_len > 0)
1623 {
1624 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1625 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1626 vim_free(line->sb_cells);
1627 --gap->ga_len;
1628 }
1629 check_cursor();
1630
1631 set_terminal_mode(term, FALSE);
1632
1633 if (term->tl_channel_closed)
1634 cleanup_vterm(term);
1635 redraw_buf_and_status_later(curbuf, NOT_VALID);
1636}
1637
1638/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001639 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001640 * Note: while waiting a terminal may be closed and freed if the channel is
1641 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001642 */
1643 static int
1644term_vgetc()
1645{
1646 int c;
1647 int save_State = State;
1648
1649 State = TERMINAL;
1650 got_int = FALSE;
1651#ifdef WIN3264
1652 ctrl_break_was_pressed = FALSE;
1653#endif
1654 c = vgetc();
1655 got_int = FALSE;
1656 State = save_State;
1657 return c;
1658}
1659
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001660static int mouse_was_outside = FALSE;
1661
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001662/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001663 * Send keys to terminal.
1664 * Return FAIL when the key needs to be handled in Normal mode.
1665 * Return OK when the key was dropped or sent to the terminal.
1666 */
1667 int
1668send_keys_to_term(term_T *term, int c, int typed)
1669{
1670 char msg[KEY_BUF_LEN];
1671 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001672 int dragging_outside = FALSE;
1673
1674 /* Catch keys that need to be handled as in Normal mode. */
1675 switch (c)
1676 {
1677 case NUL:
1678 case K_ZERO:
1679 if (typed)
1680 stuffcharReadbuff(c);
1681 return FAIL;
1682
Bram Moolenaar231a2db2018-05-06 13:53:50 +02001683 case K_TABLINE:
1684 stuffcharReadbuff(c);
1685 return FAIL;
1686
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001687 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001688 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001689 return FAIL;
1690
1691 case K_LEFTDRAG:
1692 case K_MIDDLEDRAG:
1693 case K_RIGHTDRAG:
1694 case K_X1DRAG:
1695 case K_X2DRAG:
1696 dragging_outside = mouse_was_outside;
1697 /* FALLTHROUGH */
1698 case K_LEFTMOUSE:
1699 case K_LEFTMOUSE_NM:
1700 case K_LEFTRELEASE:
1701 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001702 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001703 case K_MIDDLEMOUSE:
1704 case K_MIDDLERELEASE:
1705 case K_RIGHTMOUSE:
1706 case K_RIGHTRELEASE:
1707 case K_X1MOUSE:
1708 case K_X1RELEASE:
1709 case K_X2MOUSE:
1710 case K_X2RELEASE:
1711
1712 case K_MOUSEUP:
1713 case K_MOUSEDOWN:
1714 case K_MOUSELEFT:
1715 case K_MOUSERIGHT:
1716 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001717 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001718 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001719 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001720 || dragging_outside)
1721 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001722 /* click or scroll outside the current window or on status line
1723 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001724 if (typed)
1725 {
1726 stuffcharReadbuff(c);
1727 mouse_was_outside = TRUE;
1728 }
1729 return FAIL;
1730 }
1731 }
1732 if (typed)
1733 mouse_was_outside = FALSE;
1734
1735 /* Convert the typed key to a sequence of bytes for the job. */
1736 len = term_convert_key(term, c, msg);
1737 if (len > 0)
1738 /* TODO: if FAIL is returned, stop? */
1739 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1740 (char_u *)msg, (int)len, NULL);
1741
1742 return OK;
1743}
1744
1745 static void
1746position_cursor(win_T *wp, VTermPos *pos)
1747{
1748 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1749 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1750 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1751}
1752
1753/*
1754 * Handle CTRL-W "": send register contents to the job.
1755 */
1756 static void
1757term_paste_register(int prev_c UNUSED)
1758{
1759 int c;
1760 list_T *l;
1761 listitem_T *item;
1762 long reglen = 0;
1763 int type;
1764
1765#ifdef FEAT_CMDL_INFO
1766 if (add_to_showcmd(prev_c))
1767 if (add_to_showcmd('"'))
1768 out_flush();
1769#endif
1770 c = term_vgetc();
1771#ifdef FEAT_CMDL_INFO
1772 clear_showcmd();
1773#endif
1774 if (!term_use_loop())
1775 /* job finished while waiting for a character */
1776 return;
1777
1778 /* CTRL-W "= prompt for expression to evaluate. */
1779 if (c == '=' && get_expr_register() != '=')
1780 return;
1781 if (!term_use_loop())
1782 /* job finished while waiting for a character */
1783 return;
1784
1785 l = (list_T *)get_reg_contents(c, GREG_LIST);
1786 if (l != NULL)
1787 {
1788 type = get_reg_type(c, &reglen);
1789 for (item = l->lv_first; item != NULL; item = item->li_next)
1790 {
1791 char_u *s = get_tv_string(&item->li_tv);
1792#ifdef WIN3264
1793 char_u *tmp = s;
1794
1795 if (!enc_utf8 && enc_codepage > 0)
1796 {
1797 WCHAR *ret = NULL;
1798 int length = 0;
1799
1800 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1801 (int)STRLEN(s), &ret, &length);
1802 if (ret != NULL)
1803 {
1804 WideCharToMultiByte_alloc(CP_UTF8, 0,
1805 ret, length, (char **)&s, &length, 0, 0);
1806 vim_free(ret);
1807 }
1808 }
1809#endif
1810 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1811 s, (int)STRLEN(s), NULL);
1812#ifdef WIN3264
1813 if (tmp != s)
1814 vim_free(s);
1815#endif
1816
1817 if (item->li_next != NULL || type == MLINE)
1818 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1819 (char_u *)"\r", 1, NULL);
1820 }
1821 list_free(l);
1822 }
1823}
1824
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001825/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001826 * Return TRUE when waiting for a character in the terminal, the cursor of the
1827 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001828 */
1829 int
1830terminal_is_active()
1831{
1832 return in_terminal_loop != NULL;
1833}
1834
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001835#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001836 cursorentry_T *
1837term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1838{
1839 term_T *term = in_terminal_loop;
1840 static cursorentry_T entry;
1841
1842 vim_memset(&entry, 0, sizeof(entry));
1843 entry.shape = entry.mshape =
1844 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1845 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1846 SHAPE_BLOCK;
1847 entry.percentage = 20;
1848 if (term->tl_cursor_blink)
1849 {
1850 entry.blinkwait = 700;
1851 entry.blinkon = 400;
1852 entry.blinkoff = 250;
1853 }
1854 *fg = gui.back_pixel;
1855 if (term->tl_cursor_color == NULL)
1856 *bg = gui.norm_pixel;
1857 else
1858 *bg = color_name2handle(term->tl_cursor_color);
1859 entry.name = "n";
1860 entry.used_for = SHAPE_CURSOR;
1861
1862 return &entry;
1863}
1864#endif
1865
Bram Moolenaard317b382018-02-08 22:33:31 +01001866 static void
1867may_output_cursor_props(void)
1868{
1869 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1870 || last_set_cursor_shape != desired_cursor_shape
1871 || last_set_cursor_blink != desired_cursor_blink)
1872 {
1873 last_set_cursor_color = desired_cursor_color;
1874 last_set_cursor_shape = desired_cursor_shape;
1875 last_set_cursor_blink = desired_cursor_blink;
1876 term_cursor_color(desired_cursor_color);
1877 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1878 /* this will restore the initial cursor style, if possible */
1879 ui_cursor_shape_forced(TRUE);
1880 else
1881 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1882 }
1883}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001884
Bram Moolenaard317b382018-02-08 22:33:31 +01001885/*
1886 * Set the cursor color and shape, if not last set to these.
1887 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001888 static void
1889may_set_cursor_props(term_T *term)
1890{
1891#ifdef FEAT_GUI
1892 /* For the GUI the cursor properties are obtained with
1893 * term_get_cursor_shape(). */
1894 if (gui.in_use)
1895 return;
1896#endif
1897 if (in_terminal_loop == term)
1898 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001899 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001900 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001901 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001902 desired_cursor_color = (char_u *)"";
1903 desired_cursor_shape = term->tl_cursor_shape;
1904 desired_cursor_blink = term->tl_cursor_blink;
1905 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001906 }
1907}
1908
Bram Moolenaard317b382018-02-08 22:33:31 +01001909/*
1910 * Reset the desired cursor properties and restore them when needed.
1911 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001912 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001913prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001914{
1915#ifdef FEAT_GUI
1916 if (gui.in_use)
1917 return;
1918#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001919 desired_cursor_color = (char_u *)"";
1920 desired_cursor_shape = -1;
1921 desired_cursor_blink = -1;
1922 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001923}
1924
1925/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001926 * Returns TRUE if the current window contains a terminal and we are sending
1927 * keys to the job.
1928 * If "check_job_status" is TRUE update the job status.
1929 */
1930 static int
1931term_use_loop_check(int check_job_status)
1932{
1933 term_T *term = curbuf->b_term;
1934
1935 return term != NULL
1936 && !term->tl_normal_mode
1937 && term->tl_vterm != NULL
1938 && term_job_running_check(term, check_job_status);
1939}
1940
1941/*
1942 * Returns TRUE if the current window contains a terminal and we are sending
1943 * keys to the job.
1944 */
1945 int
1946term_use_loop(void)
1947{
1948 return term_use_loop_check(FALSE);
1949}
1950
1951/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001952 * Called when entering a window with the mouse. If this is a terminal window
1953 * we may want to change state.
1954 */
1955 void
1956term_win_entered()
1957{
1958 term_T *term = curbuf->b_term;
1959
1960 if (term != NULL)
1961 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001962 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001963 {
1964 reset_VIsual_and_resel();
1965 if (State & INSERT)
1966 stop_insert_mode = TRUE;
1967 }
1968 mouse_was_outside = FALSE;
1969 enter_mouse_col = mouse_col;
1970 enter_mouse_row = mouse_row;
1971 }
1972}
1973
1974/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001975 * Wait for input and send it to the job.
1976 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1977 * when there is no more typahead.
1978 * Return when the start of a CTRL-W command is typed or anything else that
1979 * should be handled as a Normal mode command.
1980 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1981 * the terminal was closed.
1982 */
1983 int
1984terminal_loop(int blocking)
1985{
1986 int c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02001987 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001988 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001989#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001990 int tty_fd = curbuf->b_term->tl_job->jv_channel
1991 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001992#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001993 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001994
1995 /* Remember the terminal we are sending keys to. However, the terminal
1996 * might be closed while waiting for a character, e.g. typing "exit" in a
1997 * shell and ++close was used. Therefore use curbuf->b_term instead of a
1998 * stored reference. */
1999 in_terminal_loop = curbuf->b_term;
2000
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002001 if (*curwin->w_p_twk != NUL)
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002002 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002003 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2004 may_set_cursor_props(curbuf->b_term);
2005
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002006 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002007 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002008#ifdef FEAT_GUI
2009 if (!curbuf->b_term->tl_system)
2010#endif
2011 /* TODO: skip screen update when handling a sequence of keys. */
2012 /* Repeat redrawing in case a message is received while redrawing.
2013 */
2014 while (must_redraw != 0)
2015 if (update_screen(0) == FAIL)
2016 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002017 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002018 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002019
2020 c = term_vgetc();
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002021 if (!term_use_loop_check(TRUE))
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002022 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002023 /* Job finished while waiting for a character. Push back the
2024 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002025 if (c != K_IGNORE)
2026 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002027 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002028 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002029 if (c == K_IGNORE)
2030 continue;
2031
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002032#ifdef UNIX
2033 /*
2034 * The shell or another program may change the tty settings. Getting
2035 * them for every typed character is a bit of overhead, but it's needed
2036 * for the first character typed, e.g. when Vim starts in a shell.
2037 */
2038 if (isatty(tty_fd))
2039 {
2040 ttyinfo_T info;
2041
2042 /* Get the current backspace character of the pty. */
2043 if (get_tty_info(tty_fd, &info) == OK)
2044 term_backspace_char = info.backspace;
2045 }
2046#endif
2047
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002048#ifdef WIN3264
2049 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2050 * Use CTRL-BREAK to kill the job. */
2051 if (ctrl_break_was_pressed)
2052 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2053#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002054 /* Was either CTRL-W (termwinkey) or CTRL-\ pressed?
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002055 * Not in a system terminal. */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002056 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002057#ifdef FEAT_GUI
2058 && !curbuf->b_term->tl_system
2059#endif
2060 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002061 {
2062 int prev_c = c;
2063
2064#ifdef FEAT_CMDL_INFO
2065 if (add_to_showcmd(c))
2066 out_flush();
2067#endif
2068 c = term_vgetc();
2069#ifdef FEAT_CMDL_INFO
2070 clear_showcmd();
2071#endif
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002072 if (!term_use_loop_check(TRUE))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002073 /* job finished while waiting for a character */
2074 break;
2075
2076 if (prev_c == Ctrl_BSL)
2077 {
2078 if (c == Ctrl_N)
2079 {
2080 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2081 term_enter_normal_mode();
2082 ret = FAIL;
2083 goto theend;
2084 }
2085 /* Send both keys to the terminal. */
2086 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2087 }
2088 else if (c == Ctrl_C)
2089 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002090 /* "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002091 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2092 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002093 else if (termwinkey == 0 && c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002094 {
2095 /* "CTRL-W .": send CTRL-W to the job */
2096 c = Ctrl_W;
2097 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002098 else if (termwinkey == 0 && c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002099 {
2100 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2101 c = Ctrl_BSL;
2102 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002103 else if (c == 'N')
2104 {
2105 /* CTRL-W N : go to Terminal-Normal mode. */
2106 term_enter_normal_mode();
2107 ret = FAIL;
2108 goto theend;
2109 }
2110 else if (c == '"')
2111 {
2112 term_paste_register(prev_c);
2113 continue;
2114 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002115 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002116 {
2117 stuffcharReadbuff(Ctrl_W);
2118 stuffcharReadbuff(c);
2119 ret = OK;
2120 goto theend;
2121 }
2122 }
2123# ifdef WIN3264
2124 if (!enc_utf8 && has_mbyte && c >= 0x80)
2125 {
2126 WCHAR wc;
2127 char_u mb[3];
2128
2129 mb[0] = (unsigned)c >> 8;
2130 mb[1] = c;
2131 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2132 c = wc;
2133 }
2134# endif
2135 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2136 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002137 if (c == K_MOUSEMOVE)
2138 /* We are sure to come back here, don't reset the cursor color
2139 * and shape to avoid flickering. */
2140 restore_cursor = FALSE;
2141
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002142 ret = OK;
2143 goto theend;
2144 }
2145 }
2146 ret = FAIL;
2147
2148theend:
2149 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002150 if (restore_cursor)
2151 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002152 return ret;
2153}
2154
2155/*
2156 * Called when a job has finished.
2157 * This updates the title and status, but does not close the vterm, because
2158 * there might still be pending output in the channel.
2159 */
2160 void
2161term_job_ended(job_T *job)
2162{
2163 term_T *term;
2164 int did_one = FALSE;
2165
2166 for (term = first_term; term != NULL; term = term->tl_next)
2167 if (term->tl_job == job)
2168 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002169 VIM_CLEAR(term->tl_title);
2170 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002171 redraw_buf_and_status_later(term->tl_buffer, VALID);
2172 did_one = TRUE;
2173 }
2174 if (did_one)
2175 redraw_statuslines();
2176 if (curbuf->b_term != NULL)
2177 {
2178 if (curbuf->b_term->tl_job == job)
2179 maketitle();
2180 update_cursor(curbuf->b_term, TRUE);
2181 }
2182}
2183
2184 static void
2185may_toggle_cursor(term_T *term)
2186{
2187 if (in_terminal_loop == term)
2188 {
2189 if (term->tl_cursor_visible)
2190 cursor_on();
2191 else
2192 cursor_off();
2193 }
2194}
2195
2196/*
2197 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002198 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002199 */
2200 static int
2201color2index(VTermColor *color, int fg, int *boldp)
2202{
2203 int red = color->red;
2204 int blue = color->blue;
2205 int green = color->green;
2206
Bram Moolenaar46359e12017-11-29 22:33:38 +01002207 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002208 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002209 /* First 16 colors and default: use the ANSI index, because these
2210 * colors can be redefined. */
2211 if (t_colors >= 16)
2212 return color->ansi_index;
2213 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002214 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002215 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002216 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002217 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2218 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2219 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002220 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002221 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2222 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2223 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2224 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2225 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2226 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2227 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2228 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2229 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2230 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2231 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002232 }
2233 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002234
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002235 if (t_colors >= 256)
2236 {
2237 if (red == blue && red == green)
2238 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002239 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002240 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002241 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2242 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2243 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002244 int i;
2245
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002246 if (red < 5)
2247 return 17; /* 00/00/00 */
2248 if (red > 245) /* ff/ff/ff */
2249 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002250 for (i = 0; i < 23; ++i)
2251 if (red < cutoff[i])
2252 return i + 233;
2253 return 256;
2254 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002255 {
2256 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2257 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002258
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002259 /* 216-color cube */
2260 for (ri = 0; ri < 5; ++ri)
2261 if (red < cutoff[ri])
2262 break;
2263 for (gi = 0; gi < 5; ++gi)
2264 if (green < cutoff[gi])
2265 break;
2266 for (bi = 0; bi < 5; ++bi)
2267 if (blue < cutoff[bi])
2268 break;
2269 return 17 + ri * 36 + gi * 6 + bi;
2270 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002271 }
2272 return 0;
2273}
2274
2275/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002276 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002277 */
2278 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002279vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002280{
2281 int attr = 0;
2282
2283 if (cellattrs.bold)
2284 attr |= HL_BOLD;
2285 if (cellattrs.underline)
2286 attr |= HL_UNDERLINE;
2287 if (cellattrs.italic)
2288 attr |= HL_ITALIC;
2289 if (cellattrs.strike)
2290 attr |= HL_STRIKETHROUGH;
2291 if (cellattrs.reverse)
2292 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002293 return attr;
2294}
2295
2296/*
2297 * Store Vterm attributes in "cell" from highlight flags.
2298 */
2299 static void
2300hl2vtermAttr(int attr, cellattr_T *cell)
2301{
2302 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2303 if (attr & HL_BOLD)
2304 cell->attrs.bold = 1;
2305 if (attr & HL_UNDERLINE)
2306 cell->attrs.underline = 1;
2307 if (attr & HL_ITALIC)
2308 cell->attrs.italic = 1;
2309 if (attr & HL_STRIKETHROUGH)
2310 cell->attrs.strike = 1;
2311 if (attr & HL_INVERSE)
2312 cell->attrs.reverse = 1;
2313}
2314
2315/*
2316 * Convert the attributes of a vterm cell into an attribute index.
2317 */
2318 static int
2319cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2320{
2321 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002322
2323#ifdef FEAT_GUI
2324 if (gui.in_use)
2325 {
2326 guicolor_T fg, bg;
2327
2328 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2329 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2330 return get_gui_attr_idx(attr, fg, bg);
2331 }
2332 else
2333#endif
2334#ifdef FEAT_TERMGUICOLORS
2335 if (p_tgc)
2336 {
2337 guicolor_T fg, bg;
2338
2339 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2340 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2341
2342 return get_tgc_attr_idx(attr, fg, bg);
2343 }
2344 else
2345#endif
2346 {
2347 int bold = MAYBE;
2348 int fg = color2index(&cellfg, TRUE, &bold);
2349 int bg = color2index(&cellbg, FALSE, &bold);
2350
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002351 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002352 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002353 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002354 if (fg == 0 && term_default_cterm_fg >= 0)
2355 fg = term_default_cterm_fg + 1;
2356 if (bg == 0 && term_default_cterm_bg >= 0)
2357 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002358 }
2359
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002360 /* with 8 colors set the bold attribute to get a bright foreground */
2361 if (bold == TRUE)
2362 attr |= HL_BOLD;
2363 return get_cterm_attr_idx(attr, fg, bg);
2364 }
2365 return 0;
2366}
2367
2368 static int
2369handle_damage(VTermRect rect, void *user)
2370{
2371 term_T *term = (term_T *)user;
2372
2373 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2374 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002375 redraw_buf_later(term->tl_buffer, SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002376 return 1;
2377}
2378
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002379 static void
2380term_scroll_up(term_T *term, int start_row, int count)
2381{
2382 win_T *wp;
2383 VTermColor fg, bg;
2384 VTermScreenCellAttrs attr;
2385 int clear_attr;
2386
2387 /* Set the color to clear lines with. */
2388 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2389 &fg, &bg);
2390 vim_memset(&attr, 0, sizeof(attr));
2391 clear_attr = cell2attr(attr, fg, bg);
2392
2393 FOR_ALL_WINDOWS(wp)
2394 {
2395 if (wp->w_buffer == term->tl_buffer)
2396 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
2397 }
2398}
2399
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002400 static int
2401handle_moverect(VTermRect dest, VTermRect src, void *user)
2402{
2403 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002404 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002405
2406 /* Scrolling up is done much more efficiently by deleting lines instead of
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002407 * redrawing the text. But avoid doing this multiple times, postpone until
2408 * the redraw happens. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002409 if (dest.start_col == src.start_col
2410 && dest.end_col == src.end_col
2411 && dest.start_row < src.start_row)
2412 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002413 if (dest.start_row == 0)
2414 term->tl_postponed_scroll += count;
2415 else
2416 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002417 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002418
2419 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2420 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2421
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002422 /* Note sure if the scrolling will work correctly, let's do a complete
2423 * redraw later. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002424 redraw_buf_later(term->tl_buffer, NOT_VALID);
2425 return 1;
2426}
2427
2428 static int
2429handle_movecursor(
2430 VTermPos pos,
2431 VTermPos oldpos UNUSED,
2432 int visible,
2433 void *user)
2434{
2435 term_T *term = (term_T *)user;
2436 win_T *wp;
2437
2438 term->tl_cursor_pos = pos;
2439 term->tl_cursor_visible = visible;
2440
2441 FOR_ALL_WINDOWS(wp)
2442 {
2443 if (wp->w_buffer == term->tl_buffer)
2444 position_cursor(wp, &pos);
2445 }
2446 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2447 {
2448 may_toggle_cursor(term);
2449 update_cursor(term, term->tl_cursor_visible);
2450 }
2451
2452 return 1;
2453}
2454
2455 static int
2456handle_settermprop(
2457 VTermProp prop,
2458 VTermValue *value,
2459 void *user)
2460{
2461 term_T *term = (term_T *)user;
2462
2463 switch (prop)
2464 {
2465 case VTERM_PROP_TITLE:
2466 vim_free(term->tl_title);
2467 /* a blank title isn't useful, make it empty, so that "running" is
2468 * displayed */
2469 if (*skipwhite((char_u *)value->string) == NUL)
2470 term->tl_title = NULL;
2471#ifdef WIN3264
2472 else if (!enc_utf8 && enc_codepage > 0)
2473 {
2474 WCHAR *ret = NULL;
2475 int length = 0;
2476
2477 MultiByteToWideChar_alloc(CP_UTF8, 0,
2478 (char*)value->string, (int)STRLEN(value->string),
2479 &ret, &length);
2480 if (ret != NULL)
2481 {
2482 WideCharToMultiByte_alloc(enc_codepage, 0,
2483 ret, length, (char**)&term->tl_title,
2484 &length, 0, 0);
2485 vim_free(ret);
2486 }
2487 }
2488#endif
2489 else
2490 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002491 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002492 if (term == curbuf->b_term)
2493 maketitle();
2494 break;
2495
2496 case VTERM_PROP_CURSORVISIBLE:
2497 term->tl_cursor_visible = value->boolean;
2498 may_toggle_cursor(term);
2499 out_flush();
2500 break;
2501
2502 case VTERM_PROP_CURSORBLINK:
2503 term->tl_cursor_blink = value->boolean;
2504 may_set_cursor_props(term);
2505 break;
2506
2507 case VTERM_PROP_CURSORSHAPE:
2508 term->tl_cursor_shape = value->number;
2509 may_set_cursor_props(term);
2510 break;
2511
2512 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002513 if (desired_cursor_color == term->tl_cursor_color)
2514 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002515 vim_free(term->tl_cursor_color);
2516 if (*value->string == NUL)
2517 term->tl_cursor_color = NULL;
2518 else
2519 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2520 may_set_cursor_props(term);
2521 break;
2522
2523 case VTERM_PROP_ALTSCREEN:
2524 /* TODO: do anything else? */
2525 term->tl_using_altscreen = value->boolean;
2526 break;
2527
2528 default:
2529 break;
2530 }
2531 /* Always return 1, otherwise vterm doesn't store the value internally. */
2532 return 1;
2533}
2534
2535/*
2536 * The job running in the terminal resized the terminal.
2537 */
2538 static int
2539handle_resize(int rows, int cols, void *user)
2540{
2541 term_T *term = (term_T *)user;
2542 win_T *wp;
2543
2544 term->tl_rows = rows;
2545 term->tl_cols = cols;
2546 if (term->tl_vterm_size_changed)
2547 /* Size was set by vterm_set_size(), don't set the window size. */
2548 term->tl_vterm_size_changed = FALSE;
2549 else
2550 {
2551 FOR_ALL_WINDOWS(wp)
2552 {
2553 if (wp->w_buffer == term->tl_buffer)
2554 {
2555 win_setheight_win(rows, wp);
2556 win_setwidth_win(cols, wp);
2557 }
2558 }
2559 redraw_buf_later(term->tl_buffer, NOT_VALID);
2560 }
2561 return 1;
2562}
2563
2564/*
2565 * Handle a line that is pushed off the top of the screen.
2566 */
2567 static int
2568handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2569{
2570 term_T *term = (term_T *)user;
2571
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002572 /* If the number of lines that are stored goes over 'termscrollback' then
2573 * delete the first 10%. */
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002574 if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002575 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002576 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002577 int i;
2578
2579 curbuf = term->tl_buffer;
2580 for (i = 0; i < todo; ++i)
2581 {
2582 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2583 ml_delete(1, FALSE);
2584 }
2585 curbuf = curwin->w_buffer;
2586
2587 term->tl_scrollback.ga_len -= todo;
2588 mch_memmove(term->tl_scrollback.ga_data,
2589 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2590 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
2591 }
2592
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002593 if (ga_grow(&term->tl_scrollback, 1) == OK)
2594 {
2595 cellattr_T *p = NULL;
2596 int len = 0;
2597 int i;
2598 int c;
2599 int col;
2600 sb_line_T *line;
2601 garray_T ga;
2602 cellattr_T fill_attr = term->tl_default_color;
2603
2604 /* do not store empty cells at the end */
2605 for (i = 0; i < cols; ++i)
2606 if (cells[i].chars[0] != 0)
2607 len = i + 1;
2608 else
2609 cell2cellattr(&cells[i], &fill_attr);
2610
2611 ga_init2(&ga, 1, 100);
2612 if (len > 0)
2613 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2614 if (p != NULL)
2615 {
2616 for (col = 0; col < len; col += cells[col].width)
2617 {
2618 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2619 {
2620 ga.ga_len = 0;
2621 break;
2622 }
2623 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2624 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2625 (char_u *)ga.ga_data + ga.ga_len);
2626 cell2cellattr(&cells[col], &p[col]);
2627 }
2628 }
2629 if (ga_grow(&ga, 1) == FAIL)
2630 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2631 else
2632 {
2633 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2634 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2635 }
2636 ga_clear(&ga);
2637
2638 line = (sb_line_T *)term->tl_scrollback.ga_data
2639 + term->tl_scrollback.ga_len;
2640 line->sb_cols = len;
2641 line->sb_cells = p;
2642 line->sb_fill_attr = fill_attr;
2643 ++term->tl_scrollback.ga_len;
2644 ++term->tl_scrollback_scrolled;
2645 }
2646 return 0; /* ignored */
2647}
2648
2649static VTermScreenCallbacks screen_callbacks = {
2650 handle_damage, /* damage */
2651 handle_moverect, /* moverect */
2652 handle_movecursor, /* movecursor */
2653 handle_settermprop, /* settermprop */
2654 NULL, /* bell */
2655 handle_resize, /* resize */
2656 handle_pushline, /* sb_pushline */
2657 NULL /* sb_popline */
2658};
2659
2660/*
2661 * Called when a channel has been closed.
2662 * If this was a channel for a terminal window then finish it up.
2663 */
2664 void
2665term_channel_closed(channel_T *ch)
2666{
2667 term_T *term;
2668 int did_one = FALSE;
2669
2670 for (term = first_term; term != NULL; term = term->tl_next)
2671 if (term->tl_job == ch->ch_job)
2672 {
2673 term->tl_channel_closed = TRUE;
2674 did_one = TRUE;
2675
Bram Moolenaard23a8232018-02-10 18:45:26 +01002676 VIM_CLEAR(term->tl_title);
2677 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002678
2679 /* Unless in Terminal-Normal mode: clear the vterm. */
2680 if (!term->tl_normal_mode)
2681 {
2682 int fnum = term->tl_buffer->b_fnum;
2683
2684 cleanup_vterm(term);
2685
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002686 if (term->tl_finish == TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002687 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002688 aco_save_T aco;
2689
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002690 /* ++close or term_finish == "close" */
2691 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002692 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002693 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002694 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002695 break;
2696 }
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002697 if (term->tl_finish == TL_FINISH_OPEN
2698 && term->tl_buffer->b_nwindows == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002699 {
2700 char buf[50];
2701
2702 /* TODO: use term_opencmd */
2703 ch_log(NULL, "terminal job finished, opening window");
2704 vim_snprintf(buf, sizeof(buf),
2705 term->tl_opencmd == NULL
2706 ? "botright sbuf %d"
2707 : (char *)term->tl_opencmd, fnum);
2708 do_cmdline_cmd((char_u *)buf);
2709 }
2710 else
2711 ch_log(NULL, "terminal job finished");
2712 }
2713
2714 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2715 }
2716 if (did_one)
2717 {
2718 redraw_statuslines();
2719
2720 /* Need to break out of vgetc(). */
2721 ins_char_typebuf(K_IGNORE);
2722 typebuf_was_filled = TRUE;
2723
2724 term = curbuf->b_term;
2725 if (term != NULL)
2726 {
2727 if (term->tl_job == ch->ch_job)
2728 maketitle();
2729 update_cursor(term, term->tl_cursor_visible);
2730 }
2731 }
2732}
2733
2734/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002735 * Fill one screen line from a line of the terminal.
2736 * Advances "pos" to past the last column.
2737 */
2738 static void
2739term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2740{
2741 int off = screen_get_current_line_off();
2742
2743 for (pos->col = 0; pos->col < max_col; )
2744 {
2745 VTermScreenCell cell;
2746 int c;
2747
2748 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2749 vim_memset(&cell, 0, sizeof(cell));
2750
2751 c = cell.chars[0];
2752 if (c == NUL)
2753 {
2754 ScreenLines[off] = ' ';
2755 if (enc_utf8)
2756 ScreenLinesUC[off] = NUL;
2757 }
2758 else
2759 {
2760 if (enc_utf8)
2761 {
2762 int i;
2763
2764 /* composing chars */
2765 for (i = 0; i < Screen_mco
2766 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2767 {
2768 ScreenLinesC[i][off] = cell.chars[i + 1];
2769 if (cell.chars[i + 1] == 0)
2770 break;
2771 }
2772 if (c >= 0x80 || (Screen_mco > 0
2773 && ScreenLinesC[0][off] != 0))
2774 {
2775 ScreenLines[off] = ' ';
2776 ScreenLinesUC[off] = c;
2777 }
2778 else
2779 {
2780 ScreenLines[off] = c;
2781 ScreenLinesUC[off] = NUL;
2782 }
2783 }
2784#ifdef WIN3264
2785 else if (has_mbyte && c >= 0x80)
2786 {
2787 char_u mb[MB_MAXBYTES+1];
2788 WCHAR wc = c;
2789
2790 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2791 (char*)mb, 2, 0, 0) > 1)
2792 {
2793 ScreenLines[off] = mb[0];
2794 ScreenLines[off + 1] = mb[1];
2795 cell.width = mb_ptr2cells(mb);
2796 }
2797 else
2798 ScreenLines[off] = c;
2799 }
2800#endif
2801 else
2802 ScreenLines[off] = c;
2803 }
2804 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2805
2806 ++pos->col;
2807 ++off;
2808 if (cell.width == 2)
2809 {
2810 if (enc_utf8)
2811 ScreenLinesUC[off] = NUL;
2812
2813 /* don't set the second byte to NUL for a DBCS encoding, it
2814 * has been set above */
2815 if (enc_utf8 || !has_mbyte)
2816 ScreenLines[off] = NUL;
2817
2818 ++pos->col;
2819 ++off;
2820 }
2821 }
2822}
2823
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002824#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002825 static void
2826update_system_term(term_T *term)
2827{
2828 VTermPos pos;
2829 VTermScreen *screen;
2830
2831 if (term->tl_vterm == NULL)
2832 return;
2833 screen = vterm_obtain_screen(term->tl_vterm);
2834
2835 /* Scroll up to make more room for terminal lines if needed. */
2836 while (term->tl_toprow > 0
2837 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2838 {
2839 int save_p_more = p_more;
2840
2841 p_more = FALSE;
2842 msg_row = Rows - 1;
2843 msg_puts((char_u *)"\n");
2844 p_more = save_p_more;
2845 --term->tl_toprow;
2846 }
2847
2848 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2849 && pos.row < Rows; ++pos.row)
2850 {
2851 if (pos.row < term->tl_rows)
2852 {
2853 int max_col = MIN(Columns, term->tl_cols);
2854
2855 term_line2screenline(screen, &pos, max_col);
2856 }
2857 else
2858 pos.col = 0;
2859
2860 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2861 }
2862
2863 term->tl_dirty_row_start = MAX_ROW;
2864 term->tl_dirty_row_end = 0;
2865 update_cursor(term, TRUE);
2866}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002867#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002868
2869/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002870 * Return TRUE if window "wp" is to be redrawn with term_update_window().
2871 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002872 * Terminal-Normal mode.
2873 */
2874 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002875term_do_update_window(win_T *wp)
2876{
2877 term_T *term = wp->w_buffer->b_term;
2878
2879 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
2880}
2881
2882/*
2883 * Called to update a window that contains an active terminal.
2884 */
2885 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002886term_update_window(win_T *wp)
2887{
2888 term_T *term = wp->w_buffer->b_term;
2889 VTerm *vterm;
2890 VTermScreen *screen;
2891 VTermState *state;
2892 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002893 int rows, cols;
2894 int newrows, newcols;
2895 int minsize;
2896 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002897
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002898 vterm = term->tl_vterm;
2899 screen = vterm_obtain_screen(vterm);
2900 state = vterm_obtain_state(vterm);
2901
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002902 /* We use NOT_VALID on a resize or scroll, redraw everything then. With
2903 * SOME_VALID only redraw what was marked dirty. */
2904 if (wp->w_redr_type > SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002905 {
2906 term->tl_dirty_row_start = 0;
2907 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002908
2909 if (term->tl_postponed_scroll > 0
2910 && term->tl_postponed_scroll < term->tl_rows / 3)
2911 /* Scrolling is usually faster than redrawing, when there are only
2912 * a few lines to scroll. */
2913 term_scroll_up(term, 0, term->tl_postponed_scroll);
2914 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002915 }
2916
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002917 /*
2918 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002919 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002920 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002921 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002922
Bram Moolenaar498c2562018-04-15 23:45:15 +02002923 newrows = 99999;
2924 newcols = 99999;
2925 FOR_ALL_WINDOWS(twp)
2926 {
2927 /* When more than one window shows the same terminal, use the
2928 * smallest size. */
2929 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002930 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02002931 newrows = MIN(newrows, twp->w_height);
2932 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002933 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02002934 }
2935 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
2936 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
2937
2938 if (term->tl_rows != newrows || term->tl_cols != newcols)
2939 {
2940
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002941
2942 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002943 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002944 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02002945 newrows);
2946 term_report_winsize(term, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002947 }
2948
2949 /* The cursor may have been moved when resizing. */
2950 vterm_state_get_cursorpos(state, &pos);
2951 position_cursor(wp, &pos);
2952
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002953 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2954 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002955 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002956 if (pos.row < term->tl_rows)
2957 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002958 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002959
Bram Moolenaar13568252018-03-16 20:46:58 +01002960 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002961 }
2962 else
2963 pos.col = 0;
2964
Bram Moolenaarf118d482018-03-13 13:14:00 +01002965 screen_line(wp->w_winrow + pos.row
2966#ifdef FEAT_MENU
2967 + winbar_height(wp)
2968#endif
2969 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002970 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002971 term->tl_dirty_row_start = MAX_ROW;
2972 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002973}
2974
2975/*
2976 * Return TRUE if "wp" is a terminal window where the job has finished.
2977 */
2978 int
2979term_is_finished(buf_T *buf)
2980{
2981 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2982}
2983
2984/*
2985 * Return TRUE if "wp" is a terminal window where the job has finished or we
2986 * are in Terminal-Normal mode, thus we show the buffer contents.
2987 */
2988 int
2989term_show_buffer(buf_T *buf)
2990{
2991 term_T *term = buf->b_term;
2992
2993 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2994}
2995
2996/*
2997 * The current buffer is going to be changed. If there is terminal
2998 * highlighting remove it now.
2999 */
3000 void
3001term_change_in_curbuf(void)
3002{
3003 term_T *term = curbuf->b_term;
3004
3005 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3006 {
3007 free_scrollback(term);
3008 redraw_buf_later(term->tl_buffer, NOT_VALID);
3009
3010 /* The buffer is now like a normal buffer, it cannot be easily
3011 * abandoned when changed. */
3012 set_string_option_direct((char_u *)"buftype", -1,
3013 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3014 }
3015}
3016
3017/*
3018 * Get the screen attribute for a position in the buffer.
3019 * Use a negative "col" to get the filler background color.
3020 */
3021 int
3022term_get_attr(buf_T *buf, linenr_T lnum, int col)
3023{
3024 term_T *term = buf->b_term;
3025 sb_line_T *line;
3026 cellattr_T *cellattr;
3027
3028 if (lnum > term->tl_scrollback.ga_len)
3029 cellattr = &term->tl_default_color;
3030 else
3031 {
3032 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3033 if (col < 0 || col >= line->sb_cols)
3034 cellattr = &line->sb_fill_attr;
3035 else
3036 cellattr = line->sb_cells + col;
3037 }
3038 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3039}
3040
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003041/*
3042 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003043 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003044 */
3045 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003046cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003047{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003048 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003049}
3050
3051/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003052 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003053 */
3054 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003055init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003056{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003057 VTermColor *fg, *bg;
3058 int fgval, bgval;
3059 int id;
3060
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003061 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3062 term->tl_default_color.width = 1;
3063 fg = &term->tl_default_color.fg;
3064 bg = &term->tl_default_color.bg;
3065
3066 /* Vterm uses a default black background. Set it to white when
3067 * 'background' is "light". */
3068 if (*p_bg == 'l')
3069 {
3070 fgval = 0;
3071 bgval = 255;
3072 }
3073 else
3074 {
3075 fgval = 255;
3076 bgval = 0;
3077 }
3078 fg->red = fg->green = fg->blue = fgval;
3079 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003080 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003081
3082 /* The "Terminal" highlight group overrules the defaults. */
3083 id = syn_name2id((char_u *)"Terminal");
3084
Bram Moolenaar46359e12017-11-29 22:33:38 +01003085 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003086#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3087 if (0
3088# ifdef FEAT_GUI
3089 || gui.in_use
3090# endif
3091# ifdef FEAT_TERMGUICOLORS
3092 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003093# ifdef FEAT_VTP
3094 /* Finally get INVALCOLOR on this execution path */
3095 || (!p_tgc && t_colors >= 256)
3096# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003097# endif
3098 )
3099 {
3100 guicolor_T fg_rgb = INVALCOLOR;
3101 guicolor_T bg_rgb = INVALCOLOR;
3102
3103 if (id != 0)
3104 syn_id2colors(id, &fg_rgb, &bg_rgb);
3105
3106# ifdef FEAT_GUI
3107 if (gui.in_use)
3108 {
3109 if (fg_rgb == INVALCOLOR)
3110 fg_rgb = gui.norm_pixel;
3111 if (bg_rgb == INVALCOLOR)
3112 bg_rgb = gui.back_pixel;
3113 }
3114# ifdef FEAT_TERMGUICOLORS
3115 else
3116# endif
3117# endif
3118# ifdef FEAT_TERMGUICOLORS
3119 {
3120 if (fg_rgb == INVALCOLOR)
3121 fg_rgb = cterm_normal_fg_gui_color;
3122 if (bg_rgb == INVALCOLOR)
3123 bg_rgb = cterm_normal_bg_gui_color;
3124 }
3125# endif
3126 if (fg_rgb != INVALCOLOR)
3127 {
3128 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3129
3130 fg->red = (unsigned)(rgb >> 16);
3131 fg->green = (unsigned)(rgb >> 8) & 255;
3132 fg->blue = (unsigned)rgb & 255;
3133 }
3134 if (bg_rgb != INVALCOLOR)
3135 {
3136 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3137
3138 bg->red = (unsigned)(rgb >> 16);
3139 bg->green = (unsigned)(rgb >> 8) & 255;
3140 bg->blue = (unsigned)rgb & 255;
3141 }
3142 }
3143 else
3144#endif
3145 if (id != 0 && t_colors >= 16)
3146 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003147 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003148 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003149 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003150 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003151 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003152 else
3153 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003154#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003155 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003156#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003157
3158 /* In an MS-Windows console we know the normal colors. */
3159 if (cterm_normal_fg_color > 0)
3160 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003161 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003162# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003163 tmp = fg->red;
3164 fg->red = fg->blue;
3165 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003166# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003167 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003168# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003169 else
3170 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003171# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003172
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003173 if (cterm_normal_bg_color > 0)
3174 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003175 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003176# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003177 tmp = bg->red;
3178 bg->red = bg->blue;
3179 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003180# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003181 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003182# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003183 else
3184 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003185# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003186 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003187}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003188
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003189#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3190/*
3191 * Set the 16 ANSI colors from array of RGB values
3192 */
3193 static void
3194set_vterm_palette(VTerm *vterm, long_u *rgb)
3195{
3196 int index = 0;
3197 VTermState *state = vterm_obtain_state(vterm);
3198 for (; index < 16; index++)
3199 {
3200 VTermColor color;
3201 color.red = (unsigned)(rgb[index] >> 16);
3202 color.green = (unsigned)(rgb[index] >> 8) & 255;
3203 color.blue = (unsigned)rgb[index] & 255;
3204 vterm_state_set_palette_color(state, index, &color);
3205 }
3206}
3207
3208/*
3209 * Set the ANSI color palette from a list of colors
3210 */
3211 static int
3212set_ansi_colors_list(VTerm *vterm, list_T *list)
3213{
3214 int n = 0;
3215 long_u rgb[16];
3216 listitem_T *li = list->lv_first;
3217
3218 for (; li != NULL && n < 16; li = li->li_next, n++)
3219 {
3220 char_u *color_name;
3221 guicolor_T guicolor;
3222
3223 color_name = get_tv_string_chk(&li->li_tv);
3224 if (color_name == NULL)
3225 return FAIL;
3226
3227 guicolor = GUI_GET_COLOR(color_name);
3228 if (guicolor == INVALCOLOR)
3229 return FAIL;
3230
3231 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3232 }
3233
3234 if (n != 16 || li != NULL)
3235 return FAIL;
3236
3237 set_vterm_palette(vterm, rgb);
3238
3239 return OK;
3240}
3241
3242/*
3243 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3244 */
3245 static void
3246init_vterm_ansi_colors(VTerm *vterm)
3247{
3248 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3249
3250 if (var != NULL
3251 && (var->di_tv.v_type != VAR_LIST
3252 || var->di_tv.vval.v_list == NULL
3253 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
3254 EMSG2(_(e_invarg2), "g:terminal_ansi_colors");
3255}
3256#endif
3257
Bram Moolenaar52acb112018-03-18 19:20:22 +01003258/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003259 * Handles a "drop" command from the job in the terminal.
3260 * "item" is the file name, "item->li_next" may have options.
3261 */
3262 static void
3263handle_drop_command(listitem_T *item)
3264{
3265 char_u *fname = get_tv_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003266 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003267 int bufnr;
3268 win_T *wp;
3269 tabpage_T *tp;
3270 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003271 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003272
3273 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3274 FOR_ALL_TAB_WINDOWS(tp, wp)
3275 {
3276 if (wp->w_buffer->b_fnum == bufnr)
3277 {
3278 /* buffer is in a window already, go there */
3279 goto_tabpage_win(tp, wp);
3280 return;
3281 }
3282 }
3283
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003284 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003285
3286 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3287 && opt_item->li_tv.vval.v_dict != NULL)
3288 {
3289 dict_T *dict = opt_item->li_tv.vval.v_dict;
3290 char_u *p;
3291
3292 p = get_dict_string(dict, (char_u *)"ff", FALSE);
3293 if (p == NULL)
3294 p = get_dict_string(dict, (char_u *)"fileformat", FALSE);
3295 if (p != NULL)
3296 {
3297 if (check_ff_value(p) == FAIL)
3298 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3299 else
3300 ea.force_ff = *p;
3301 }
3302 p = get_dict_string(dict, (char_u *)"enc", FALSE);
3303 if (p == NULL)
3304 p = get_dict_string(dict, (char_u *)"encoding", FALSE);
3305 if (p != NULL)
3306 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003307 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003308 if (ea.cmd != NULL)
3309 {
3310 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3311 ea.force_enc = 11;
3312 tofree = ea.cmd;
3313 }
3314 }
3315
3316 p = get_dict_string(dict, (char_u *)"bad", FALSE);
3317 if (p != NULL)
3318 get_bad_opt(p, &ea);
3319
3320 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3321 ea.force_bin = FORCE_BIN;
3322 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3323 ea.force_bin = FORCE_BIN;
3324 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3325 ea.force_bin = FORCE_NOBIN;
3326 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3327 ea.force_bin = FORCE_NOBIN;
3328 }
3329
3330 /* open in new window, like ":split fname" */
3331 if (ea.cmd == NULL)
3332 ea.cmd = (char_u *)"split";
3333 ea.arg = fname;
3334 ea.cmdidx = CMD_split;
3335 ex_splitview(&ea);
3336
3337 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003338}
3339
3340/*
3341 * Handles a function call from the job running in a terminal.
3342 * "item" is the function name, "item->li_next" has the arguments.
3343 */
3344 static void
3345handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3346{
3347 char_u *func;
3348 typval_T argvars[2];
3349 typval_T rettv;
3350 int doesrange;
3351
3352 if (item->li_next == NULL)
3353 {
3354 ch_log(channel, "Missing function arguments for call");
3355 return;
3356 }
3357 func = get_tv_string(&item->li_tv);
3358
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003359 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003360 {
3361 ch_log(channel, "Invalid function name: %s", func);
3362 return;
3363 }
3364
3365 argvars[0].v_type = VAR_NUMBER;
3366 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3367 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003368 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003369 2, argvars, /* argv_func */ NULL,
3370 /* firstline */ 1, /* lastline */ 1,
3371 &doesrange, /* evaluate */ TRUE,
3372 /* partial */ NULL, /* selfdict */ NULL) == OK)
3373 {
3374 clear_tv(&rettv);
3375 ch_log(channel, "Function %s called", func);
3376 }
3377 else
3378 ch_log(channel, "Calling function %s failed", func);
3379}
3380
3381/*
3382 * Called by libvterm when it cannot recognize an OSC sequence.
3383 * We recognize a terminal API command.
3384 */
3385 static int
3386parse_osc(const char *command, size_t cmdlen, void *user)
3387{
3388 term_T *term = (term_T *)user;
3389 js_read_T reader;
3390 typval_T tv;
3391 channel_T *channel = term->tl_job == NULL ? NULL
3392 : term->tl_job->jv_channel;
3393
3394 /* We recognize only OSC 5 1 ; {command} */
3395 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3396 return 0; /* not handled */
3397
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003398 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003399 if (reader.js_buf == NULL)
3400 return 1;
3401 reader.js_fill = NULL;
3402 reader.js_used = 0;
3403 if (json_decode(&reader, &tv, 0) == OK
3404 && tv.v_type == VAR_LIST
3405 && tv.vval.v_list != NULL)
3406 {
3407 listitem_T *item = tv.vval.v_list->lv_first;
3408
3409 if (item == NULL)
3410 ch_log(channel, "Missing command");
3411 else
3412 {
3413 char_u *cmd = get_tv_string(&item->li_tv);
3414
Bram Moolenaara997b452018-04-17 23:24:06 +02003415 /* Make sure an invoked command doesn't delete the buffer (and the
3416 * terminal) under our fingers. */
3417 ++term->tl_buffer->b_locked;
3418
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003419 item = item->li_next;
3420 if (item == NULL)
3421 ch_log(channel, "Missing argument for %s", cmd);
3422 else if (STRCMP(cmd, "drop") == 0)
3423 handle_drop_command(item);
3424 else if (STRCMP(cmd, "call") == 0)
3425 handle_call_command(term, channel, item);
3426 else
3427 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003428 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003429 }
3430 }
3431 else
3432 ch_log(channel, "Invalid JSON received");
3433
3434 vim_free(reader.js_buf);
3435 clear_tv(&tv);
3436 return 1;
3437}
3438
3439static VTermParserCallbacks parser_fallbacks = {
3440 NULL, /* text */
3441 NULL, /* control */
3442 NULL, /* escape */
3443 NULL, /* csi */
3444 parse_osc, /* osc */
3445 NULL, /* dcs */
3446 NULL /* resize */
3447};
3448
3449/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003450 * Use Vim's allocation functions for vterm so profiling works.
3451 */
3452 static void *
3453vterm_malloc(size_t size, void *data UNUSED)
3454{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003455 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003456}
3457
3458 static void
3459vterm_memfree(void *ptr, void *data UNUSED)
3460{
3461 vim_free(ptr);
3462}
3463
3464static VTermAllocatorFunctions vterm_allocator = {
3465 &vterm_malloc,
3466 &vterm_memfree
3467};
3468
3469/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003470 * Create a new vterm and initialize it.
3471 */
3472 static void
3473create_vterm(term_T *term, int rows, int cols)
3474{
3475 VTerm *vterm;
3476 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003477 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003478 VTermValue value;
3479
Bram Moolenaar756ef112018-04-10 12:04:27 +02003480 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003481 term->tl_vterm = vterm;
3482 screen = vterm_obtain_screen(vterm);
3483 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3484 /* TODO: depends on 'encoding'. */
3485 vterm_set_utf8(vterm, 1);
3486
3487 init_default_colors(term);
3488
3489 vterm_state_set_default_colors(
3490 vterm_obtain_state(vterm),
3491 &term->tl_default_color.fg,
3492 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003493
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003494 if (t_colors >= 16)
3495 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3496
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003497 /* Required to initialize most things. */
3498 vterm_screen_reset(screen, 1 /* hard */);
3499
3500 /* Allow using alternate screen. */
3501 vterm_screen_enable_altscreen(screen, 1);
3502
3503 /* For unix do not use a blinking cursor. In an xterm this causes the
3504 * cursor to blink if it's blinking in the xterm.
3505 * For Windows we respect the system wide setting. */
3506#ifdef WIN3264
3507 if (GetCaretBlinkTime() == INFINITE)
3508 value.boolean = 0;
3509 else
3510 value.boolean = 1;
3511#else
3512 value.boolean = 0;
3513#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003514 state = vterm_obtain_state(vterm);
3515 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3516 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003517}
3518
3519/*
3520 * Return the text to show for the buffer name and status.
3521 */
3522 char_u *
3523term_get_status_text(term_T *term)
3524{
3525 if (term->tl_status_text == NULL)
3526 {
3527 char_u *txt;
3528 size_t len;
3529
3530 if (term->tl_normal_mode)
3531 {
3532 if (term_job_running(term))
3533 txt = (char_u *)_("Terminal");
3534 else
3535 txt = (char_u *)_("Terminal-finished");
3536 }
3537 else if (term->tl_title != NULL)
3538 txt = term->tl_title;
3539 else if (term_none_open(term))
3540 txt = (char_u *)_("active");
3541 else if (term_job_running(term))
3542 txt = (char_u *)_("running");
3543 else
3544 txt = (char_u *)_("finished");
3545 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3546 term->tl_status_text = alloc((int)len);
3547 if (term->tl_status_text != NULL)
3548 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3549 term->tl_buffer->b_fname, txt);
3550 }
3551 return term->tl_status_text;
3552}
3553
3554/*
3555 * Mark references in jobs of terminals.
3556 */
3557 int
3558set_ref_in_term(int copyID)
3559{
3560 int abort = FALSE;
3561 term_T *term;
3562 typval_T tv;
3563
3564 for (term = first_term; term != NULL; term = term->tl_next)
3565 if (term->tl_job != NULL)
3566 {
3567 tv.v_type = VAR_JOB;
3568 tv.vval.v_job = term->tl_job;
3569 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3570 }
3571 return abort;
3572}
3573
3574/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003575 * Cache "Terminal" highlight group colors.
3576 */
3577 void
3578set_terminal_default_colors(int cterm_fg, int cterm_bg)
3579{
3580 term_default_cterm_fg = cterm_fg - 1;
3581 term_default_cterm_bg = cterm_bg - 1;
3582}
3583
3584/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003585 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003586 * Returns NULL when the buffer is not for a terminal window and logs a message
3587 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003588 */
3589 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003590term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003591{
3592 buf_T *buf;
3593
3594 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3595 ++emsg_off;
3596 buf = get_buf_tv(&argvars[0], FALSE);
3597 --emsg_off;
3598 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003599 {
3600 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003601 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003602 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003603 return buf;
3604}
3605
Bram Moolenaard96ff162018-02-18 22:13:29 +01003606 static int
3607same_color(VTermColor *a, VTermColor *b)
3608{
3609 return a->red == b->red
3610 && a->green == b->green
3611 && a->blue == b->blue
3612 && a->ansi_index == b->ansi_index;
3613}
3614
3615 static void
3616dump_term_color(FILE *fd, VTermColor *color)
3617{
3618 fprintf(fd, "%02x%02x%02x%d",
3619 (int)color->red, (int)color->green, (int)color->blue,
3620 (int)color->ansi_index);
3621}
3622
3623/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003624 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003625 *
3626 * Each screen cell in full is:
3627 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3628 * {characters} is a space for an empty cell
3629 * For a double-width character "+" is changed to "*" and the next cell is
3630 * skipped.
3631 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3632 * when "&" use the same as the previous cell.
3633 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3634 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3635 * {color-idx} is a number from 0 to 255
3636 *
3637 * Screen cell with same width, attributes and color as the previous one:
3638 * |{characters}
3639 *
3640 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3641 *
3642 * Repeating the previous screen cell:
3643 * @{count}
3644 */
3645 void
3646f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3647{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003648 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003649 term_T *term;
3650 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003651 int max_height = 0;
3652 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003653 stat_T st;
3654 FILE *fd;
3655 VTermPos pos;
3656 VTermScreen *screen;
3657 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003658 VTermState *state;
3659 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003660
3661 if (check_restricted() || check_secure())
3662 return;
3663 if (buf == NULL)
3664 return;
3665 term = buf->b_term;
3666
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003667 if (argvars[2].v_type != VAR_UNKNOWN)
3668 {
3669 dict_T *d;
3670
3671 if (argvars[2].v_type != VAR_DICT)
3672 {
3673 EMSG(_(e_dictreq));
3674 return;
3675 }
3676 d = argvars[2].vval.v_dict;
3677 if (d != NULL)
3678 {
3679 max_height = get_dict_number(d, (char_u *)"rows");
3680 max_width = get_dict_number(d, (char_u *)"columns");
3681 }
3682 }
3683
Bram Moolenaard96ff162018-02-18 22:13:29 +01003684 fname = get_tv_string_chk(&argvars[1]);
3685 if (fname == NULL)
3686 return;
3687 if (mch_stat((char *)fname, &st) >= 0)
3688 {
3689 EMSG2(_("E953: File exists: %s"), fname);
3690 return;
3691 }
3692
Bram Moolenaard96ff162018-02-18 22:13:29 +01003693 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3694 {
3695 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3696 return;
3697 }
3698
3699 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3700
3701 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003702 state = vterm_obtain_state(term->tl_vterm);
3703 vterm_state_get_cursorpos(state, &cursor_pos);
3704
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003705 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3706 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003707 {
3708 int repeat = 0;
3709
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003710 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3711 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003712 {
3713 VTermScreenCell cell;
3714 int same_attr;
3715 int same_chars = TRUE;
3716 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003717 int is_cursor_pos = (pos.col == cursor_pos.col
3718 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003719
3720 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3721 vim_memset(&cell, 0, sizeof(cell));
3722
3723 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3724 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003725 int c = cell.chars[i];
3726 int pc = prev_cell.chars[i];
3727
3728 /* For the first character NUL is the same as space. */
3729 if (i == 0)
3730 {
3731 c = (c == NUL) ? ' ' : c;
3732 pc = (pc == NUL) ? ' ' : pc;
3733 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003734 if (cell.chars[i] != prev_cell.chars[i])
3735 same_chars = FALSE;
3736 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3737 break;
3738 }
3739 same_attr = vtermAttr2hl(cell.attrs)
3740 == vtermAttr2hl(prev_cell.attrs)
3741 && same_color(&cell.fg, &prev_cell.fg)
3742 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003743 if (same_chars && cell.width == prev_cell.width && same_attr
3744 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003745 {
3746 ++repeat;
3747 }
3748 else
3749 {
3750 if (repeat > 0)
3751 {
3752 fprintf(fd, "@%d", repeat);
3753 repeat = 0;
3754 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003755 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003756
3757 if (cell.chars[0] == NUL)
3758 fputs(" ", fd);
3759 else
3760 {
3761 char_u charbuf[10];
3762 int len;
3763
3764 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3765 && cell.chars[i] != NUL; ++i)
3766 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02003767 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003768 fwrite(charbuf, len, 1, fd);
3769 }
3770 }
3771
3772 /* When only the characters differ we don't write anything, the
3773 * following "|", "@" or NL will indicate using the same
3774 * attributes. */
3775 if (cell.width != prev_cell.width || !same_attr)
3776 {
3777 if (cell.width == 2)
3778 {
3779 fputs("*", fd);
3780 ++pos.col;
3781 }
3782 else
3783 fputs("+", fd);
3784
3785 if (same_attr)
3786 {
3787 fputs("&", fd);
3788 }
3789 else
3790 {
3791 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3792 if (same_color(&cell.fg, &prev_cell.fg))
3793 fputs("&", fd);
3794 else
3795 {
3796 fputs("#", fd);
3797 dump_term_color(fd, &cell.fg);
3798 }
3799 if (same_color(&cell.bg, &prev_cell.bg))
3800 fputs("&", fd);
3801 else
3802 {
3803 fputs("#", fd);
3804 dump_term_color(fd, &cell.bg);
3805 }
3806 }
3807 }
3808
3809 prev_cell = cell;
3810 }
3811 }
3812 if (repeat > 0)
3813 fprintf(fd, "@%d", repeat);
3814 fputs("\n", fd);
3815 }
3816
3817 fclose(fd);
3818}
3819
3820/*
3821 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3822 */
3823 static void
3824dump_is_corrupt(garray_T *gap)
3825{
3826 ga_concat(gap, (char_u *)"CORRUPT");
3827}
3828
3829 static void
3830append_cell(garray_T *gap, cellattr_T *cell)
3831{
3832 if (ga_grow(gap, 1) == OK)
3833 {
3834 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3835 ++gap->ga_len;
3836 }
3837}
3838
3839/*
3840 * Read the dump file from "fd" and append lines to the current buffer.
3841 * Return the cell width of the longest line.
3842 */
3843 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003844read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003845{
3846 int c;
3847 garray_T ga_text;
3848 garray_T ga_cell;
3849 char_u *prev_char = NULL;
3850 int attr = 0;
3851 cellattr_T cell;
3852 term_T *term = curbuf->b_term;
3853 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003854 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003855
3856 ga_init2(&ga_text, 1, 90);
3857 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3858 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003859 cursor_pos->row = -1;
3860 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003861
3862 c = fgetc(fd);
3863 for (;;)
3864 {
3865 if (c == EOF)
3866 break;
3867 if (c == '\n')
3868 {
3869 /* End of a line: append it to the buffer. */
3870 if (ga_text.ga_data == NULL)
3871 dump_is_corrupt(&ga_text);
3872 if (ga_grow(&term->tl_scrollback, 1) == OK)
3873 {
3874 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3875 + term->tl_scrollback.ga_len;
3876
3877 if (max_cells < ga_cell.ga_len)
3878 max_cells = ga_cell.ga_len;
3879 line->sb_cols = ga_cell.ga_len;
3880 line->sb_cells = ga_cell.ga_data;
3881 line->sb_fill_attr = term->tl_default_color;
3882 ++term->tl_scrollback.ga_len;
3883 ga_init(&ga_cell);
3884
3885 ga_append(&ga_text, NUL);
3886 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3887 ga_text.ga_len, FALSE);
3888 }
3889 else
3890 ga_clear(&ga_cell);
3891 ga_text.ga_len = 0;
3892
3893 c = fgetc(fd);
3894 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003895 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003896 {
3897 int prev_len = ga_text.ga_len;
3898
Bram Moolenaar9271d052018-02-25 21:39:46 +01003899 if (c == '>')
3900 {
3901 if (cursor_pos->row != -1)
3902 dump_is_corrupt(&ga_text); /* duplicate cursor */
3903 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3904 cursor_pos->col = ga_cell.ga_len;
3905 }
3906
Bram Moolenaard96ff162018-02-18 22:13:29 +01003907 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3908 c = fgetc(fd);
3909 if (c != EOF)
3910 ga_append(&ga_text, c);
3911 for (;;)
3912 {
3913 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003914 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003915 || c == EOF || c == '\n')
3916 break;
3917 ga_append(&ga_text, c);
3918 }
3919
3920 /* save the character for repeating it */
3921 vim_free(prev_char);
3922 if (ga_text.ga_data != NULL)
3923 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3924 ga_text.ga_len - prev_len);
3925
Bram Moolenaar9271d052018-02-25 21:39:46 +01003926 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003927 {
3928 /* use all attributes from previous cell */
3929 }
3930 else if (c == '+' || c == '*')
3931 {
3932 int is_bg;
3933
3934 cell.width = c == '+' ? 1 : 2;
3935
3936 c = fgetc(fd);
3937 if (c == '&')
3938 {
3939 /* use same attr as previous cell */
3940 c = fgetc(fd);
3941 }
3942 else if (isdigit(c))
3943 {
3944 /* get the decimal attribute */
3945 attr = 0;
3946 while (isdigit(c))
3947 {
3948 attr = attr * 10 + (c - '0');
3949 c = fgetc(fd);
3950 }
3951 hl2vtermAttr(attr, &cell);
3952 }
3953 else
3954 dump_is_corrupt(&ga_text);
3955
3956 /* is_bg == 0: fg, is_bg == 1: bg */
3957 for (is_bg = 0; is_bg <= 1; ++is_bg)
3958 {
3959 if (c == '&')
3960 {
3961 /* use same color as previous cell */
3962 c = fgetc(fd);
3963 }
3964 else if (c == '#')
3965 {
3966 int red, green, blue, index = 0;
3967
3968 c = fgetc(fd);
3969 red = hex2nr(c);
3970 c = fgetc(fd);
3971 red = (red << 4) + hex2nr(c);
3972 c = fgetc(fd);
3973 green = hex2nr(c);
3974 c = fgetc(fd);
3975 green = (green << 4) + hex2nr(c);
3976 c = fgetc(fd);
3977 blue = hex2nr(c);
3978 c = fgetc(fd);
3979 blue = (blue << 4) + hex2nr(c);
3980 c = fgetc(fd);
3981 if (!isdigit(c))
3982 dump_is_corrupt(&ga_text);
3983 while (isdigit(c))
3984 {
3985 index = index * 10 + (c - '0');
3986 c = fgetc(fd);
3987 }
3988
3989 if (is_bg)
3990 {
3991 cell.bg.red = red;
3992 cell.bg.green = green;
3993 cell.bg.blue = blue;
3994 cell.bg.ansi_index = index;
3995 }
3996 else
3997 {
3998 cell.fg.red = red;
3999 cell.fg.green = green;
4000 cell.fg.blue = blue;
4001 cell.fg.ansi_index = index;
4002 }
4003 }
4004 else
4005 dump_is_corrupt(&ga_text);
4006 }
4007 }
4008 else
4009 dump_is_corrupt(&ga_text);
4010
4011 append_cell(&ga_cell, &cell);
4012 }
4013 else if (c == '@')
4014 {
4015 if (prev_char == NULL)
4016 dump_is_corrupt(&ga_text);
4017 else
4018 {
4019 int count = 0;
4020
4021 /* repeat previous character, get the count */
4022 for (;;)
4023 {
4024 c = fgetc(fd);
4025 if (!isdigit(c))
4026 break;
4027 count = count * 10 + (c - '0');
4028 }
4029
4030 while (count-- > 0)
4031 {
4032 ga_concat(&ga_text, prev_char);
4033 append_cell(&ga_cell, &cell);
4034 }
4035 }
4036 }
4037 else
4038 {
4039 dump_is_corrupt(&ga_text);
4040 c = fgetc(fd);
4041 }
4042 }
4043
4044 if (ga_text.ga_len > 0)
4045 {
4046 /* trailing characters after last NL */
4047 dump_is_corrupt(&ga_text);
4048 ga_append(&ga_text, NUL);
4049 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4050 ga_text.ga_len, FALSE);
4051 }
4052
4053 ga_clear(&ga_text);
4054 vim_free(prev_char);
4055
4056 return max_cells;
4057}
4058
4059/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004060 * Return an allocated string with at least "text_width" "=" characters and
4061 * "fname" inserted in the middle.
4062 */
4063 static char_u *
4064get_separator(int text_width, char_u *fname)
4065{
4066 int width = MAX(text_width, curwin->w_width);
4067 char_u *textline;
4068 int fname_size;
4069 char_u *p = fname;
4070 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004071 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004072
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004073 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004074 if (textline == NULL)
4075 return NULL;
4076
4077 fname_size = vim_strsize(fname);
4078 if (fname_size < width - 8)
4079 {
4080 /* enough room, don't use the full window width */
4081 width = MAX(text_width, fname_size + 8);
4082 }
4083 else if (fname_size > width - 8)
4084 {
4085 /* full name doesn't fit, use only the tail */
4086 p = gettail(fname);
4087 fname_size = vim_strsize(p);
4088 }
4089 /* skip characters until the name fits */
4090 while (fname_size > width - 8)
4091 {
4092 p += (*mb_ptr2len)(p);
4093 fname_size = vim_strsize(p);
4094 }
4095
4096 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4097 textline[i] = '=';
4098 textline[i++] = ' ';
4099
4100 STRCPY(textline + i, p);
4101 off = STRLEN(textline);
4102 textline[off] = ' ';
4103 for (i = 1; i < (width - fname_size) / 2; ++i)
4104 textline[off + i] = '=';
4105 textline[off + i] = NUL;
4106
4107 return textline;
4108}
4109
4110/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004111 * Common for "term_dumpdiff()" and "term_dumpload()".
4112 */
4113 static void
4114term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4115{
4116 jobopt_T opt;
4117 buf_T *buf;
4118 char_u buf1[NUMBUFLEN];
4119 char_u buf2[NUMBUFLEN];
4120 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004121 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004122 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004123 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004124 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004125 char_u *textline = NULL;
4126
4127 /* First open the files. If this fails bail out. */
4128 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
4129 if (do_diff)
4130 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
4131 if (fname1 == NULL || (do_diff && fname2 == NULL))
4132 {
4133 EMSG(_(e_invarg));
4134 return;
4135 }
4136 fd1 = mch_fopen((char *)fname1, READBIN);
4137 if (fd1 == NULL)
4138 {
4139 EMSG2(_(e_notread), fname1);
4140 return;
4141 }
4142 if (do_diff)
4143 {
4144 fd2 = mch_fopen((char *)fname2, READBIN);
4145 if (fd2 == NULL)
4146 {
4147 fclose(fd1);
4148 EMSG2(_(e_notread), fname2);
4149 return;
4150 }
4151 }
4152
4153 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004154 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4155 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4156 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4157 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4158 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004159
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004160 if (opt.jo_term_name == NULL)
4161 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004162 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004163
Bram Moolenaarb571c632018-03-21 22:27:59 +01004164 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004165 if (fname_tofree != NULL)
4166 {
4167 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4168 opt.jo_term_name = fname_tofree;
4169 }
4170 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004171
Bram Moolenaar13568252018-03-16 20:46:58 +01004172 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004173 if (buf != NULL && buf->b_term != NULL)
4174 {
4175 int i;
4176 linenr_T bot_lnum;
4177 linenr_T lnum;
4178 term_T *term = buf->b_term;
4179 int width;
4180 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004181 VTermPos cursor_pos1;
4182 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004183
Bram Moolenaar52acb112018-03-18 19:20:22 +01004184 init_default_colors(term);
4185
Bram Moolenaard96ff162018-02-18 22:13:29 +01004186 rettv->vval.v_number = buf->b_fnum;
4187
4188 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004189 width = read_dump_file(fd1, &cursor_pos1);
4190
4191 /* position the cursor */
4192 if (cursor_pos1.row >= 0)
4193 {
4194 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4195 coladvance(cursor_pos1.col);
4196 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004197
4198 /* Delete the empty line that was in the empty buffer. */
4199 ml_delete(1, FALSE);
4200
4201 /* For term_dumpload() we are done here. */
4202 if (!do_diff)
4203 goto theend;
4204
4205 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4206
Bram Moolenaar4a696342018-04-05 18:45:26 +02004207 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004208 if (textline == NULL)
4209 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004210 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4211 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4212 vim_free(textline);
4213
4214 textline = get_separator(width, fname2);
4215 if (textline == NULL)
4216 goto theend;
4217 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4218 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004219 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004220
4221 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004222 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004223 if (width2 > width)
4224 {
4225 vim_free(textline);
4226 textline = alloc(width2 + 1);
4227 if (textline == NULL)
4228 goto theend;
4229 width = width2;
4230 textline[width] = NUL;
4231 }
4232 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4233
4234 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4235 {
4236 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4237 {
4238 /* bottom part has fewer rows, fill with "-" */
4239 for (i = 0; i < width; ++i)
4240 textline[i] = '-';
4241 }
4242 else
4243 {
4244 char_u *line1;
4245 char_u *line2;
4246 char_u *p1;
4247 char_u *p2;
4248 int col;
4249 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4250 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4251 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4252 ->sb_cells;
4253
4254 /* Make a copy, getting the second line will invalidate it. */
4255 line1 = vim_strsave(ml_get(lnum));
4256 if (line1 == NULL)
4257 break;
4258 p1 = line1;
4259
4260 line2 = ml_get(lnum + bot_lnum);
4261 p2 = line2;
4262 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4263 {
4264 int len1 = utfc_ptr2len(p1);
4265 int len2 = utfc_ptr2len(p2);
4266
4267 textline[col] = ' ';
4268 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004269 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004270 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004271 else if (lnum == cursor_pos1.row + 1
4272 && col == cursor_pos1.col
4273 && (cursor_pos1.row != cursor_pos2.row
4274 || cursor_pos1.col != cursor_pos2.col))
4275 /* cursor in first but not in second */
4276 textline[col] = '>';
4277 else if (lnum == cursor_pos2.row + 1
4278 && col == cursor_pos2.col
4279 && (cursor_pos1.row != cursor_pos2.row
4280 || cursor_pos1.col != cursor_pos2.col))
4281 /* cursor in second but not in first */
4282 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004283 else if (cellattr1 != NULL && cellattr2 != NULL)
4284 {
4285 if ((cellattr1 + col)->width
4286 != (cellattr2 + col)->width)
4287 textline[col] = 'w';
4288 else if (!same_color(&(cellattr1 + col)->fg,
4289 &(cellattr2 + col)->fg))
4290 textline[col] = 'f';
4291 else if (!same_color(&(cellattr1 + col)->bg,
4292 &(cellattr2 + col)->bg))
4293 textline[col] = 'b';
4294 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4295 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4296 textline[col] = 'a';
4297 }
4298 p1 += len1;
4299 p2 += len2;
4300 /* TODO: handle different width */
4301 }
4302 vim_free(line1);
4303
4304 while (col < width)
4305 {
4306 if (*p1 == NUL && *p2 == NUL)
4307 textline[col] = '?';
4308 else if (*p1 == NUL)
4309 {
4310 textline[col] = '+';
4311 p2 += utfc_ptr2len(p2);
4312 }
4313 else
4314 {
4315 textline[col] = '-';
4316 p1 += utfc_ptr2len(p1);
4317 }
4318 ++col;
4319 }
4320 }
4321 if (add_empty_scrollback(term, &term->tl_default_color,
4322 term->tl_top_diff_rows) == OK)
4323 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4324 ++bot_lnum;
4325 }
4326
4327 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4328 {
4329 /* bottom part has more rows, fill with "+" */
4330 for (i = 0; i < width; ++i)
4331 textline[i] = '+';
4332 if (add_empty_scrollback(term, &term->tl_default_color,
4333 term->tl_top_diff_rows) == OK)
4334 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4335 ++lnum;
4336 ++bot_lnum;
4337 }
4338
4339 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004340
4341 /* looks better without wrapping */
4342 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004343 }
4344
4345theend:
4346 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004347 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004348 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004349 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004350 fclose(fd2);
4351}
4352
4353/*
4354 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4355 * bottom files.
4356 * Return FAIL when this is not possible.
4357 */
4358 int
4359term_swap_diff()
4360{
4361 term_T *term = curbuf->b_term;
4362 linenr_T line_count;
4363 linenr_T top_rows;
4364 linenr_T bot_rows;
4365 linenr_T bot_start;
4366 linenr_T lnum;
4367 char_u *p;
4368 sb_line_T *sb_line;
4369
4370 if (term == NULL
4371 || !term_is_finished(curbuf)
4372 || term->tl_top_diff_rows == 0
4373 || term->tl_scrollback.ga_len == 0)
4374 return FAIL;
4375
4376 line_count = curbuf->b_ml.ml_line_count;
4377 top_rows = term->tl_top_diff_rows;
4378 bot_rows = term->tl_bot_diff_rows;
4379 bot_start = line_count - bot_rows;
4380 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4381
4382 /* move lines from top to above the bottom part */
4383 for (lnum = 1; lnum <= top_rows; ++lnum)
4384 {
4385 p = vim_strsave(ml_get(1));
4386 if (p == NULL)
4387 return OK;
4388 ml_append(bot_start, p, 0, FALSE);
4389 ml_delete(1, FALSE);
4390 vim_free(p);
4391 }
4392
4393 /* move lines from bottom to the top */
4394 for (lnum = 1; lnum <= bot_rows; ++lnum)
4395 {
4396 p = vim_strsave(ml_get(bot_start + lnum));
4397 if (p == NULL)
4398 return OK;
4399 ml_delete(bot_start + lnum, FALSE);
4400 ml_append(lnum - 1, p, 0, FALSE);
4401 vim_free(p);
4402 }
4403
4404 if (top_rows == bot_rows)
4405 {
4406 /* rows counts are equal, can swap cell properties */
4407 for (lnum = 0; lnum < top_rows; ++lnum)
4408 {
4409 sb_line_T temp;
4410
4411 temp = *(sb_line + lnum);
4412 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4413 *(sb_line + bot_start + lnum) = temp;
4414 }
4415 }
4416 else
4417 {
4418 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4419 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4420
4421 /* need to copy cell properties into temp memory */
4422 if (temp != NULL)
4423 {
4424 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4425 mch_memmove(term->tl_scrollback.ga_data,
4426 temp + bot_start,
4427 sizeof(sb_line_T) * bot_rows);
4428 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4429 temp + top_rows,
4430 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4431 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4432 + line_count - top_rows,
4433 temp,
4434 sizeof(sb_line_T) * top_rows);
4435 vim_free(temp);
4436 }
4437 }
4438
4439 term->tl_top_diff_rows = bot_rows;
4440 term->tl_bot_diff_rows = top_rows;
4441
4442 update_screen(NOT_VALID);
4443 return OK;
4444}
4445
4446/*
4447 * "term_dumpdiff(filename, filename, options)" function
4448 */
4449 void
4450f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4451{
4452 term_load_dump(argvars, rettv, TRUE);
4453}
4454
4455/*
4456 * "term_dumpload(filename, options)" function
4457 */
4458 void
4459f_term_dumpload(typval_T *argvars, typval_T *rettv)
4460{
4461 term_load_dump(argvars, rettv, FALSE);
4462}
4463
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004464/*
4465 * "term_getaltscreen(buf)" function
4466 */
4467 void
4468f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4469{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004470 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004471
4472 if (buf == NULL)
4473 return;
4474 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4475}
4476
4477/*
4478 * "term_getattr(attr, name)" function
4479 */
4480 void
4481f_term_getattr(typval_T *argvars, typval_T *rettv)
4482{
4483 int attr;
4484 size_t i;
4485 char_u *name;
4486
4487 static struct {
4488 char *name;
4489 int attr;
4490 } attrs[] = {
4491 {"bold", HL_BOLD},
4492 {"italic", HL_ITALIC},
4493 {"underline", HL_UNDERLINE},
4494 {"strike", HL_STRIKETHROUGH},
4495 {"reverse", HL_INVERSE},
4496 };
4497
4498 attr = get_tv_number(&argvars[0]);
4499 name = get_tv_string_chk(&argvars[1]);
4500 if (name == NULL)
4501 return;
4502
4503 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4504 if (STRCMP(name, attrs[i].name) == 0)
4505 {
4506 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4507 break;
4508 }
4509}
4510
4511/*
4512 * "term_getcursor(buf)" function
4513 */
4514 void
4515f_term_getcursor(typval_T *argvars, typval_T *rettv)
4516{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004517 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004518 term_T *term;
4519 list_T *l;
4520 dict_T *d;
4521
4522 if (rettv_list_alloc(rettv) == FAIL)
4523 return;
4524 if (buf == NULL)
4525 return;
4526 term = buf->b_term;
4527
4528 l = rettv->vval.v_list;
4529 list_append_number(l, term->tl_cursor_pos.row + 1);
4530 list_append_number(l, term->tl_cursor_pos.col + 1);
4531
4532 d = dict_alloc();
4533 if (d != NULL)
4534 {
4535 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4536 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4537 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4538 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4539 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4540 ? (char_u *)"" : term->tl_cursor_color);
4541 list_append_dict(l, d);
4542 }
4543}
4544
4545/*
4546 * "term_getjob(buf)" function
4547 */
4548 void
4549f_term_getjob(typval_T *argvars, typval_T *rettv)
4550{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004551 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004552
4553 rettv->v_type = VAR_JOB;
4554 rettv->vval.v_job = NULL;
4555 if (buf == NULL)
4556 return;
4557
4558 rettv->vval.v_job = buf->b_term->tl_job;
4559 if (rettv->vval.v_job != NULL)
4560 ++rettv->vval.v_job->jv_refcount;
4561}
4562
4563 static int
4564get_row_number(typval_T *tv, term_T *term)
4565{
4566 if (tv->v_type == VAR_STRING
4567 && tv->vval.v_string != NULL
4568 && STRCMP(tv->vval.v_string, ".") == 0)
4569 return term->tl_cursor_pos.row;
4570 return (int)get_tv_number(tv) - 1;
4571}
4572
4573/*
4574 * "term_getline(buf, row)" function
4575 */
4576 void
4577f_term_getline(typval_T *argvars, typval_T *rettv)
4578{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004579 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004580 term_T *term;
4581 int row;
4582
4583 rettv->v_type = VAR_STRING;
4584 if (buf == NULL)
4585 return;
4586 term = buf->b_term;
4587 row = get_row_number(&argvars[1], term);
4588
4589 if (term->tl_vterm == NULL)
4590 {
4591 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4592
4593 /* vterm is finished, get the text from the buffer */
4594 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4595 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4596 }
4597 else
4598 {
4599 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4600 VTermRect rect;
4601 int len;
4602 char_u *p;
4603
4604 if (row < 0 || row >= term->tl_rows)
4605 return;
4606 len = term->tl_cols * MB_MAXBYTES + 1;
4607 p = alloc(len);
4608 if (p == NULL)
4609 return;
4610 rettv->vval.v_string = p;
4611
4612 rect.start_col = 0;
4613 rect.end_col = term->tl_cols;
4614 rect.start_row = row;
4615 rect.end_row = row + 1;
4616 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4617 }
4618}
4619
4620/*
4621 * "term_getscrolled(buf)" function
4622 */
4623 void
4624f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4625{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004626 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004627
4628 if (buf == NULL)
4629 return;
4630 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4631}
4632
4633/*
4634 * "term_getsize(buf)" function
4635 */
4636 void
4637f_term_getsize(typval_T *argvars, typval_T *rettv)
4638{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004639 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004640 list_T *l;
4641
4642 if (rettv_list_alloc(rettv) == FAIL)
4643 return;
4644 if (buf == NULL)
4645 return;
4646
4647 l = rettv->vval.v_list;
4648 list_append_number(l, buf->b_term->tl_rows);
4649 list_append_number(l, buf->b_term->tl_cols);
4650}
4651
4652/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004653 * "term_setsize(buf, rows, cols)" function
4654 */
4655 void
4656f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4657{
4658 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4659 term_T *term;
4660 varnumber_T rows, cols;
4661
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004662 if (buf == NULL)
4663 {
4664 EMSG(_("E955: Not a terminal buffer"));
4665 return;
4666 }
4667 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004668 return;
4669 term = buf->b_term;
4670 rows = get_tv_number(&argvars[1]);
4671 rows = rows <= 0 ? term->tl_rows : rows;
4672 cols = get_tv_number(&argvars[2]);
4673 cols = cols <= 0 ? term->tl_cols : cols;
4674 vterm_set_size(term->tl_vterm, rows, cols);
4675 /* handle_resize() will resize the windows */
4676
4677 /* Get and remember the size we ended up with. Update the pty. */
4678 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
4679 term_report_winsize(term, term->tl_rows, term->tl_cols);
4680}
4681
4682/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004683 * "term_getstatus(buf)" function
4684 */
4685 void
4686f_term_getstatus(typval_T *argvars, typval_T *rettv)
4687{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004688 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004689 term_T *term;
4690 char_u val[100];
4691
4692 rettv->v_type = VAR_STRING;
4693 if (buf == NULL)
4694 return;
4695 term = buf->b_term;
4696
4697 if (term_job_running(term))
4698 STRCPY(val, "running");
4699 else
4700 STRCPY(val, "finished");
4701 if (term->tl_normal_mode)
4702 STRCAT(val, ",normal");
4703 rettv->vval.v_string = vim_strsave(val);
4704}
4705
4706/*
4707 * "term_gettitle(buf)" function
4708 */
4709 void
4710f_term_gettitle(typval_T *argvars, typval_T *rettv)
4711{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004712 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004713
4714 rettv->v_type = VAR_STRING;
4715 if (buf == NULL)
4716 return;
4717
4718 if (buf->b_term->tl_title != NULL)
4719 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4720}
4721
4722/*
4723 * "term_gettty(buf)" function
4724 */
4725 void
4726f_term_gettty(typval_T *argvars, typval_T *rettv)
4727{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004728 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004729 char_u *p;
4730 int num = 0;
4731
4732 rettv->v_type = VAR_STRING;
4733 if (buf == NULL)
4734 return;
4735 if (argvars[1].v_type != VAR_UNKNOWN)
4736 num = get_tv_number(&argvars[1]);
4737
4738 switch (num)
4739 {
4740 case 0:
4741 if (buf->b_term->tl_job != NULL)
4742 p = buf->b_term->tl_job->jv_tty_out;
4743 else
4744 p = buf->b_term->tl_tty_out;
4745 break;
4746 case 1:
4747 if (buf->b_term->tl_job != NULL)
4748 p = buf->b_term->tl_job->jv_tty_in;
4749 else
4750 p = buf->b_term->tl_tty_in;
4751 break;
4752 default:
4753 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4754 return;
4755 }
4756 if (p != NULL)
4757 rettv->vval.v_string = vim_strsave(p);
4758}
4759
4760/*
4761 * "term_list()" function
4762 */
4763 void
4764f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4765{
4766 term_T *tp;
4767 list_T *l;
4768
4769 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4770 return;
4771
4772 l = rettv->vval.v_list;
4773 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4774 if (tp != NULL && tp->tl_buffer != NULL)
4775 if (list_append_number(l,
4776 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4777 return;
4778}
4779
4780/*
4781 * "term_scrape(buf, row)" function
4782 */
4783 void
4784f_term_scrape(typval_T *argvars, typval_T *rettv)
4785{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004786 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004787 VTermScreen *screen = NULL;
4788 VTermPos pos;
4789 list_T *l;
4790 term_T *term;
4791 char_u *p;
4792 sb_line_T *line;
4793
4794 if (rettv_list_alloc(rettv) == FAIL)
4795 return;
4796 if (buf == NULL)
4797 return;
4798 term = buf->b_term;
4799
4800 l = rettv->vval.v_list;
4801 pos.row = get_row_number(&argvars[1], term);
4802
4803 if (term->tl_vterm != NULL)
4804 {
4805 screen = vterm_obtain_screen(term->tl_vterm);
4806 p = NULL;
4807 line = NULL;
4808 }
4809 else
4810 {
4811 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4812
4813 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4814 return;
4815 p = ml_get_buf(buf, lnum + 1, FALSE);
4816 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4817 }
4818
4819 for (pos.col = 0; pos.col < term->tl_cols; )
4820 {
4821 dict_T *dcell;
4822 int width;
4823 VTermScreenCellAttrs attrs;
4824 VTermColor fg, bg;
4825 char_u rgb[8];
4826 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4827 int off = 0;
4828 int i;
4829
4830 if (screen == NULL)
4831 {
4832 cellattr_T *cellattr;
4833 int len;
4834
4835 /* vterm has finished, get the cell from scrollback */
4836 if (pos.col >= line->sb_cols)
4837 break;
4838 cellattr = line->sb_cells + pos.col;
4839 width = cellattr->width;
4840 attrs = cellattr->attrs;
4841 fg = cellattr->fg;
4842 bg = cellattr->bg;
4843 len = MB_PTR2LEN(p);
4844 mch_memmove(mbs, p, len);
4845 mbs[len] = NUL;
4846 p += len;
4847 }
4848 else
4849 {
4850 VTermScreenCell cell;
4851 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4852 break;
4853 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4854 {
4855 if (cell.chars[i] == 0)
4856 break;
4857 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4858 }
4859 mbs[off] = NUL;
4860 width = cell.width;
4861 attrs = cell.attrs;
4862 fg = cell.fg;
4863 bg = cell.bg;
4864 }
4865 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004866 if (dcell == NULL)
4867 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004868 list_append_dict(l, dcell);
4869
4870 dict_add_nr_str(dcell, "chars", 0, mbs);
4871
4872 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4873 fg.red, fg.green, fg.blue);
4874 dict_add_nr_str(dcell, "fg", 0, rgb);
4875 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4876 bg.red, bg.green, bg.blue);
4877 dict_add_nr_str(dcell, "bg", 0, rgb);
4878
4879 dict_add_nr_str(dcell, "attr",
4880 cell2attr(attrs, fg, bg), NULL);
4881 dict_add_nr_str(dcell, "width", width, NULL);
4882
4883 ++pos.col;
4884 if (width == 2)
4885 ++pos.col;
4886 }
4887}
4888
4889/*
4890 * "term_sendkeys(buf, keys)" function
4891 */
4892 void
4893f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4894{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004895 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004896 char_u *msg;
4897 term_T *term;
4898
4899 rettv->v_type = VAR_UNKNOWN;
4900 if (buf == NULL)
4901 return;
4902
4903 msg = get_tv_string_chk(&argvars[1]);
4904 if (msg == NULL)
4905 return;
4906 term = buf->b_term;
4907 if (term->tl_vterm == NULL)
4908 return;
4909
4910 while (*msg != NUL)
4911 {
4912 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004913 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004914 }
4915}
4916
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004917#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
4918/*
4919 * "term_getansicolors(buf)" function
4920 */
4921 void
4922f_term_getansicolors(typval_T *argvars, typval_T *rettv)
4923{
4924 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
4925 term_T *term;
4926 VTermState *state;
4927 VTermColor color;
4928 char_u hexbuf[10];
4929 int index;
4930 list_T *list;
4931
4932 if (rettv_list_alloc(rettv) == FAIL)
4933 return;
4934
4935 if (buf == NULL)
4936 return;
4937 term = buf->b_term;
4938 if (term->tl_vterm == NULL)
4939 return;
4940
4941 list = rettv->vval.v_list;
4942 state = vterm_obtain_state(term->tl_vterm);
4943 for (index = 0; index < 16; index++)
4944 {
4945 vterm_state_get_palette_color(state, index, &color);
4946 sprintf((char *)hexbuf, "#%02x%02x%02x",
4947 color.red, color.green, color.blue);
4948 if (list_append_string(list, hexbuf, 7) == FAIL)
4949 return;
4950 }
4951}
4952
4953/*
4954 * "term_setansicolors(buf, list)" function
4955 */
4956 void
4957f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
4958{
4959 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
4960 term_T *term;
4961
4962 if (buf == NULL)
4963 return;
4964 term = buf->b_term;
4965 if (term->tl_vterm == NULL)
4966 return;
4967
4968 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
4969 {
4970 EMSG(_(e_listreq));
4971 return;
4972 }
4973
4974 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
4975 EMSG(_(e_invarg));
4976}
4977#endif
4978
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004979/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004980 * "term_setrestore(buf, command)" function
4981 */
4982 void
4983f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4984{
4985#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004986 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004987 term_T *term;
4988 char_u *cmd;
4989
4990 if (buf == NULL)
4991 return;
4992 term = buf->b_term;
4993 vim_free(term->tl_command);
4994 cmd = get_tv_string_chk(&argvars[1]);
4995 if (cmd != NULL)
4996 term->tl_command = vim_strsave(cmd);
4997 else
4998 term->tl_command = NULL;
4999#endif
5000}
5001
5002/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005003 * "term_setkill(buf, how)" function
5004 */
5005 void
5006f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5007{
5008 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5009 term_T *term;
5010 char_u *how;
5011
5012 if (buf == NULL)
5013 return;
5014 term = buf->b_term;
5015 vim_free(term->tl_kill);
5016 how = get_tv_string_chk(&argvars[1]);
5017 if (how != NULL)
5018 term->tl_kill = vim_strsave(how);
5019 else
5020 term->tl_kill = NULL;
5021}
5022
5023/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005024 * "term_start(command, options)" function
5025 */
5026 void
5027f_term_start(typval_T *argvars, typval_T *rettv)
5028{
5029 jobopt_T opt;
5030 buf_T *buf;
5031
5032 init_job_options(&opt);
5033 if (argvars[1].v_type != VAR_UNKNOWN
5034 && get_job_options(&argvars[1], &opt,
5035 JO_TIMEOUT_ALL + JO_STOPONEXIT
5036 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5037 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5038 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5039 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005040 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005041 + JO2_NORESTORE + JO2_TERM_KILL
5042 + JO2_ANSI_COLORS) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005043 return;
5044
Bram Moolenaar13568252018-03-16 20:46:58 +01005045 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005046
5047 if (buf != NULL && buf->b_term != NULL)
5048 rettv->vval.v_number = buf->b_fnum;
5049}
5050
5051/*
5052 * "term_wait" function
5053 */
5054 void
5055f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5056{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005057 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005058
5059 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005060 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005061 if (buf->b_term->tl_job == NULL)
5062 {
5063 ch_log(NULL, "term_wait(): no job to wait for");
5064 return;
5065 }
5066 if (buf->b_term->tl_job->jv_channel == NULL)
5067 /* channel is closed, nothing to do */
5068 return;
5069
5070 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005071 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005072 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5073 {
5074 /* The job is dead, keep reading channel I/O until the channel is
5075 * closed. buf->b_term may become NULL if the terminal was closed while
5076 * waiting. */
5077 ch_log(NULL, "term_wait(): waiting for channel to close");
5078 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5079 {
5080 mch_check_messages();
5081 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01005082 if (!buf_valid(buf))
5083 /* If the terminal is closed when the channel is closed the
5084 * buffer disappears. */
5085 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005086 ui_delay(10L, FALSE);
5087 }
5088 mch_check_messages();
5089 parse_queued_messages();
5090 }
5091 else
5092 {
5093 long wait = 10L;
5094
5095 mch_check_messages();
5096 parse_queued_messages();
5097
5098 /* Wait for some time for any channel I/O. */
5099 if (argvars[1].v_type != VAR_UNKNOWN)
5100 wait = get_tv_number(&argvars[1]);
5101 ui_delay(wait, TRUE);
5102 mch_check_messages();
5103
5104 /* Flushing messages on channels is hopefully sufficient.
5105 * TODO: is there a better way? */
5106 parse_queued_messages();
5107 }
5108}
5109
5110/*
5111 * Called when a channel has sent all the lines to a terminal.
5112 * Send a CTRL-D to mark the end of the text.
5113 */
5114 void
5115term_send_eof(channel_T *ch)
5116{
5117 term_T *term;
5118
5119 for (term = first_term; term != NULL; term = term->tl_next)
5120 if (term->tl_job == ch->ch_job)
5121 {
5122 if (term->tl_eof_chars != NULL)
5123 {
5124 channel_send(ch, PART_IN, term->tl_eof_chars,
5125 (int)STRLEN(term->tl_eof_chars), NULL);
5126 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5127 }
5128# ifdef WIN3264
5129 else
5130 /* Default: CTRL-D */
5131 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5132# endif
5133 }
5134}
5135
5136# if defined(WIN3264) || defined(PROTO)
5137
5138/**************************************
5139 * 2. MS-Windows implementation.
5140 */
5141
5142# ifndef PROTO
5143
5144#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5145#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005146#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005147
5148void* (*winpty_config_new)(UINT64, void*);
5149void* (*winpty_open)(void*, void*);
5150void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5151BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5152void (*winpty_config_set_mouse_mode)(void*, int);
5153void (*winpty_config_set_initial_size)(void*, int, int);
5154LPCWSTR (*winpty_conin_name)(void*);
5155LPCWSTR (*winpty_conout_name)(void*);
5156LPCWSTR (*winpty_conerr_name)(void*);
5157void (*winpty_free)(void*);
5158void (*winpty_config_free)(void*);
5159void (*winpty_spawn_config_free)(void*);
5160void (*winpty_error_free)(void*);
5161LPCWSTR (*winpty_error_msg)(void*);
5162BOOL (*winpty_set_size)(void*, int, int, void*);
5163HANDLE (*winpty_agent_process)(void*);
5164
5165#define WINPTY_DLL "winpty.dll"
5166
5167static HINSTANCE hWinPtyDLL = NULL;
5168# endif
5169
5170 static int
5171dyn_winpty_init(int verbose)
5172{
5173 int i;
5174 static struct
5175 {
5176 char *name;
5177 FARPROC *ptr;
5178 } winpty_entry[] =
5179 {
5180 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5181 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5182 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5183 {"winpty_config_set_mouse_mode",
5184 (FARPROC*)&winpty_config_set_mouse_mode},
5185 {"winpty_config_set_initial_size",
5186 (FARPROC*)&winpty_config_set_initial_size},
5187 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5188 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5189 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5190 {"winpty_free", (FARPROC*)&winpty_free},
5191 {"winpty_open", (FARPROC*)&winpty_open},
5192 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5193 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5194 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5195 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5196 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5197 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5198 {NULL, NULL}
5199 };
5200
5201 /* No need to initialize twice. */
5202 if (hWinPtyDLL)
5203 return OK;
5204 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5205 * winpty.dll. */
5206 if (*p_winptydll != NUL)
5207 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5208 if (!hWinPtyDLL)
5209 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5210 if (!hWinPtyDLL)
5211 {
5212 if (verbose)
5213 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
5214 : (char_u *)WINPTY_DLL);
5215 return FAIL;
5216 }
5217 for (i = 0; winpty_entry[i].name != NULL
5218 && winpty_entry[i].ptr != NULL; ++i)
5219 {
5220 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5221 winpty_entry[i].name)) == NULL)
5222 {
5223 if (verbose)
5224 EMSG2(_(e_loadfunc), winpty_entry[i].name);
5225 return FAIL;
5226 }
5227 }
5228
5229 return OK;
5230}
5231
5232/*
5233 * Create a new terminal of "rows" by "cols" cells.
5234 * Store a reference in "term".
5235 * Return OK or FAIL.
5236 */
5237 static int
5238term_and_job_init(
5239 term_T *term,
5240 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005241 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005242 jobopt_T *opt)
5243{
5244 WCHAR *cmd_wchar = NULL;
5245 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005246 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005247 channel_T *channel = NULL;
5248 job_T *job = NULL;
5249 DWORD error;
5250 HANDLE jo = NULL;
5251 HANDLE child_process_handle;
5252 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005253 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005254 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005255 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005256 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005257
5258 if (dyn_winpty_init(TRUE) == FAIL)
5259 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005260 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5261 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005262
5263 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005264 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005265 cmd = argvar->vval.v_string;
5266 }
5267 else if (argvar->v_type == VAR_LIST)
5268 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005269 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005270 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005271 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005272 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005273 if (cmd == NULL || *cmd == NUL)
5274 {
5275 EMSG(_(e_invarg));
5276 goto failed;
5277 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005278
5279 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005280 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005281 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005282 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005283 if (opt->jo_cwd != NULL)
5284 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005285
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005286 win32_build_env(opt->jo_env, &ga_env, TRUE);
5287 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005288
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005289 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5290 if (term->tl_winpty_config == NULL)
5291 goto failed;
5292
5293 winpty_config_set_mouse_mode(term->tl_winpty_config,
5294 WINPTY_MOUSE_MODE_FORCE);
5295 winpty_config_set_initial_size(term->tl_winpty_config,
5296 term->tl_cols, term->tl_rows);
5297 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5298 if (term->tl_winpty == NULL)
5299 goto failed;
5300
5301 spawn_config = winpty_spawn_config_new(
5302 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5303 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5304 NULL,
5305 cmd_wchar,
5306 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005307 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005308 &winpty_err);
5309 if (spawn_config == NULL)
5310 goto failed;
5311
5312 channel = add_channel();
5313 if (channel == NULL)
5314 goto failed;
5315
5316 job = job_alloc();
5317 if (job == NULL)
5318 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02005319 if (argvar->v_type == VAR_STRING)
5320 {
5321 int argc;
5322
5323 build_argv_from_string(cmd, &job->jv_argv, &argc);
5324 }
5325 else
5326 {
5327 int argc;
5328
5329 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5330 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005331
5332 if (opt->jo_set & JO_IN_BUF)
5333 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5334
5335 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5336 &child_thread_handle, &error, &winpty_err))
5337 goto failed;
5338
5339 channel_set_pipes(channel,
5340 (sock_T)CreateFileW(
5341 winpty_conin_name(term->tl_winpty),
5342 GENERIC_WRITE, 0, NULL,
5343 OPEN_EXISTING, 0, NULL),
5344 (sock_T)CreateFileW(
5345 winpty_conout_name(term->tl_winpty),
5346 GENERIC_READ, 0, NULL,
5347 OPEN_EXISTING, 0, NULL),
5348 (sock_T)CreateFileW(
5349 winpty_conerr_name(term->tl_winpty),
5350 GENERIC_READ, 0, NULL,
5351 OPEN_EXISTING, 0, NULL));
5352
5353 /* Write lines with CR instead of NL. */
5354 channel->ch_write_text_mode = TRUE;
5355
5356 jo = CreateJobObject(NULL, NULL);
5357 if (jo == NULL)
5358 goto failed;
5359
5360 if (!AssignProcessToJobObject(jo, child_process_handle))
5361 {
5362 /* Failed, switch the way to terminate process with TerminateProcess. */
5363 CloseHandle(jo);
5364 jo = NULL;
5365 }
5366
5367 winpty_spawn_config_free(spawn_config);
5368 vim_free(cmd_wchar);
5369 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005370 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005371
5372 create_vterm(term, term->tl_rows, term->tl_cols);
5373
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005374#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5375 if (opt->jo_set2 & JO2_ANSI_COLORS)
5376 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5377 else
5378 init_vterm_ansi_colors(term->tl_vterm);
5379#endif
5380
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005381 channel_set_job(channel, job, opt);
5382 job_set_options(job, opt);
5383
5384 job->jv_channel = channel;
5385 job->jv_proc_info.hProcess = child_process_handle;
5386 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5387 job->jv_job_object = jo;
5388 job->jv_status = JOB_STARTED;
5389 job->jv_tty_in = utf16_to_enc(
5390 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5391 job->jv_tty_out = utf16_to_enc(
5392 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5393 ++job->jv_refcount;
5394 term->tl_job = job;
5395
5396 return OK;
5397
5398failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005399 ga_clear(&ga_cmd);
5400 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005401 vim_free(cmd_wchar);
5402 vim_free(cwd_wchar);
5403 if (spawn_config != NULL)
5404 winpty_spawn_config_free(spawn_config);
5405 if (channel != NULL)
5406 channel_clear(channel);
5407 if (job != NULL)
5408 {
5409 job->jv_channel = NULL;
5410 job_cleanup(job);
5411 }
5412 term->tl_job = NULL;
5413 if (jo != NULL)
5414 CloseHandle(jo);
5415 if (term->tl_winpty != NULL)
5416 winpty_free(term->tl_winpty);
5417 term->tl_winpty = NULL;
5418 if (term->tl_winpty_config != NULL)
5419 winpty_config_free(term->tl_winpty_config);
5420 term->tl_winpty_config = NULL;
5421 if (winpty_err != NULL)
5422 {
5423 char_u *msg = utf16_to_enc(
5424 (short_u *)winpty_error_msg(winpty_err), NULL);
5425
5426 EMSG(msg);
5427 winpty_error_free(winpty_err);
5428 }
5429 return FAIL;
5430}
5431
5432 static int
5433create_pty_only(term_T *term, jobopt_T *options)
5434{
5435 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5436 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5437 char in_name[80], out_name[80];
5438 channel_T *channel = NULL;
5439
5440 create_vterm(term, term->tl_rows, term->tl_cols);
5441
5442 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5443 GetCurrentProcessId(),
5444 curbuf->b_fnum);
5445 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5446 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5447 PIPE_UNLIMITED_INSTANCES,
5448 0, 0, NMPWAIT_NOWAIT, NULL);
5449 if (hPipeIn == INVALID_HANDLE_VALUE)
5450 goto failed;
5451
5452 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5453 GetCurrentProcessId(),
5454 curbuf->b_fnum);
5455 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5456 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5457 PIPE_UNLIMITED_INSTANCES,
5458 0, 0, 0, NULL);
5459 if (hPipeOut == INVALID_HANDLE_VALUE)
5460 goto failed;
5461
5462 ConnectNamedPipe(hPipeIn, NULL);
5463 ConnectNamedPipe(hPipeOut, NULL);
5464
5465 term->tl_job = job_alloc();
5466 if (term->tl_job == NULL)
5467 goto failed;
5468 ++term->tl_job->jv_refcount;
5469
5470 /* behave like the job is already finished */
5471 term->tl_job->jv_status = JOB_FINISHED;
5472
5473 channel = add_channel();
5474 if (channel == NULL)
5475 goto failed;
5476 term->tl_job->jv_channel = channel;
5477 channel->ch_keep_open = TRUE;
5478 channel->ch_named_pipe = TRUE;
5479
5480 channel_set_pipes(channel,
5481 (sock_T)hPipeIn,
5482 (sock_T)hPipeOut,
5483 (sock_T)hPipeOut);
5484 channel_set_job(channel, term->tl_job, options);
5485 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5486 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5487
5488 return OK;
5489
5490failed:
5491 if (hPipeIn != NULL)
5492 CloseHandle(hPipeIn);
5493 if (hPipeOut != NULL)
5494 CloseHandle(hPipeOut);
5495 return FAIL;
5496}
5497
5498/*
5499 * Free the terminal emulator part of "term".
5500 */
5501 static void
5502term_free_vterm(term_T *term)
5503{
5504 if (term->tl_winpty != NULL)
5505 winpty_free(term->tl_winpty);
5506 term->tl_winpty = NULL;
5507 if (term->tl_winpty_config != NULL)
5508 winpty_config_free(term->tl_winpty_config);
5509 term->tl_winpty_config = NULL;
5510 if (term->tl_vterm != NULL)
5511 vterm_free(term->tl_vterm);
5512 term->tl_vterm = NULL;
5513}
5514
5515/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005516 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005517 */
5518 static void
5519term_report_winsize(term_T *term, int rows, int cols)
5520{
5521 if (term->tl_winpty)
5522 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5523}
5524
5525 int
5526terminal_enabled(void)
5527{
5528 return dyn_winpty_init(FALSE) == OK;
5529}
5530
5531# else
5532
5533/**************************************
5534 * 3. Unix-like implementation.
5535 */
5536
5537/*
5538 * Create a new terminal of "rows" by "cols" cells.
5539 * Start job for "cmd".
5540 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005541 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005542 * Return OK or FAIL.
5543 */
5544 static int
5545term_and_job_init(
5546 term_T *term,
5547 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005548 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005549 jobopt_T *opt)
5550{
5551 create_vterm(term, term->tl_rows, term->tl_cols);
5552
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005553#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5554 if (opt->jo_set2 & JO2_ANSI_COLORS)
5555 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5556 else
5557 init_vterm_ansi_colors(term->tl_vterm);
5558#endif
5559
Bram Moolenaar13568252018-03-16 20:46:58 +01005560 /* This may change a string in "argvar". */
5561 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005562 if (term->tl_job != NULL)
5563 ++term->tl_job->jv_refcount;
5564
5565 return term->tl_job != NULL
5566 && term->tl_job->jv_channel != NULL
5567 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5568}
5569
5570 static int
5571create_pty_only(term_T *term, jobopt_T *opt)
5572{
5573 create_vterm(term, term->tl_rows, term->tl_cols);
5574
5575 term->tl_job = job_alloc();
5576 if (term->tl_job == NULL)
5577 return FAIL;
5578 ++term->tl_job->jv_refcount;
5579
5580 /* behave like the job is already finished */
5581 term->tl_job->jv_status = JOB_FINISHED;
5582
5583 return mch_create_pty_channel(term->tl_job, opt);
5584}
5585
5586/*
5587 * Free the terminal emulator part of "term".
5588 */
5589 static void
5590term_free_vterm(term_T *term)
5591{
5592 if (term->tl_vterm != NULL)
5593 vterm_free(term->tl_vterm);
5594 term->tl_vterm = NULL;
5595}
5596
5597/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005598 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005599 */
5600 static void
5601term_report_winsize(term_T *term, int rows, int cols)
5602{
5603 /* Use an ioctl() to report the new window size to the job. */
5604 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5605 {
5606 int fd = -1;
5607 int part;
5608
5609 for (part = PART_OUT; part < PART_COUNT; ++part)
5610 {
5611 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5612 if (isatty(fd))
5613 break;
5614 }
5615 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5616 mch_signal_job(term->tl_job, (char_u *)"winch");
5617 }
5618}
5619
5620# endif
5621
5622#endif /* FEAT_TERMINAL */