blob: 277f18bcd6aad801d6e972582e412f9df771d80e [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 Moolenaara10ae5e2018-05-11 20:48:29 +020041 * - Win32: Termdebug doesn't work, because gdb does not support mi2. This
42 * plugin: https://github.com/cpiger/NeoDebug runs gdb as a job, redirecting
43 * input and output. Command I/O is in gdb window.
Bram Moolenaarf25329c2018-05-06 21:49:32 +020044 * - Win32: Redirecting input does not work, half of Test_terminal_redir_file()
Bram Moolenaar802bfb12018-04-15 17:28:13 +020045 * is disabled.
Bram Moolenaarf25329c2018-05-06 21:49:32 +020046 * - Win32: Redirecting output works but includes escape sequences.
47 * - Win32: Make terminal used for :!cmd in the GUI work better. Allow for
48 * redirection.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +020049 * - When the job only outputs lines, we could handle resizing the terminal
50 * better: store lines separated by line breaks, instead of screen lines,
51 * then when the window is resized redraw those lines.
Bram Moolenaarf25329c2018-05-06 21:49:32 +020052 * - Redrawing is slow with Athena and Motif. (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020053 * - For the GUI fill termios with default values, perhaps like pangoterm:
54 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar802bfb12018-04-15 17:28:13 +020055 * - When 'encoding' is not utf-8, or the job is using another encoding, setup
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020056 * conversions.
Bram Moolenaar498c2562018-04-15 23:45:15 +020057 * - Termdebug does not work when Vim build with mzscheme: gdb hangs just after
58 * "run". Everything else works, including communication channel. Not
59 * initializing mzscheme avoid the problem, thus it's not some #ifdef.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020060 */
61
62#include "vim.h"
63
64#if defined(FEAT_TERMINAL) || defined(PROTO)
65
66#ifndef MIN
67# define MIN(x,y) ((x) < (y) ? (x) : (y))
68#endif
69#ifndef MAX
70# define MAX(x,y) ((x) > (y) ? (x) : (y))
71#endif
72
73#include "libvterm/include/vterm.h"
74
75/* This is VTermScreenCell without the characters, thus much smaller. */
76typedef struct {
77 VTermScreenCellAttrs attrs;
78 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010079 VTermColor fg;
80 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020081} cellattr_T;
82
83typedef struct sb_line_S {
84 int sb_cols; /* can differ per line */
85 cellattr_T *sb_cells; /* allocated */
86 cellattr_T sb_fill_attr; /* for short line */
87} sb_line_T;
88
89/* typedef term_T in structs.h */
90struct terminal_S {
91 term_T *tl_next;
92
93 VTerm *tl_vterm;
94 job_T *tl_job;
95 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +010096#if defined(FEAT_GUI)
97 int tl_system; /* when non-zero used for :!cmd output */
98 int tl_toprow; /* row with first line of system terminal */
99#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200100
101 /* Set when setting the size of a vterm, reset after redrawing. */
102 int tl_vterm_size_changed;
103
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200104 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
105 int tl_channel_closed;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +0200106 int tl_channel_recently_closed; // still need to handle tl_finish
107
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100108 int tl_finish;
109#define TL_FINISH_UNSET NUL
110#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
111#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
112#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200113 char_u *tl_opencmd;
114 char_u *tl_eof_chars;
115
116#ifdef WIN3264
117 void *tl_winpty_config;
118 void *tl_winpty;
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200119
120 FILE *tl_out_fd;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200121#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100122#if defined(FEAT_SESSION)
123 char_u *tl_command;
124#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100125 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200126
127 /* last known vterm size */
128 int tl_rows;
129 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200130
131 char_u *tl_title; /* NULL or allocated */
132 char_u *tl_status_text; /* NULL or allocated */
133
134 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200135 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200136 int tl_dirty_row_end; /* row below last one to update */
Bram Moolenaar56bc8e22018-05-10 18:05:56 +0200137 int tl_dirty_snapshot; /* text updated after making snapshot */
138#ifdef FEAT_TIMERS
139 int tl_timer_set;
140 proftime_T tl_timer_due;
141#endif
Bram Moolenaar6eddadf2018-05-06 16:40:16 +0200142 int tl_postponed_scroll; /* to be scrolled up */
143
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200144 garray_T tl_scrollback;
145 int tl_scrollback_scrolled;
146 cellattr_T tl_default_color;
147
Bram Moolenaard96ff162018-02-18 22:13:29 +0100148 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
149 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
150
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200151 VTermPos tl_cursor_pos;
152 int tl_cursor_visible;
153 int tl_cursor_blink;
154 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
155 char_u *tl_cursor_color; /* NULL or allocated */
156
157 int tl_using_altscreen;
158};
159
160#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
161#define TMODE_LOOP 2 /* CTRL-W N used */
162
163/*
164 * List of all active terminals.
165 */
166static term_T *first_term = NULL;
167
168/* Terminal active in terminal_loop(). */
169static term_T *in_terminal_loop = NULL;
170
171#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
172#define KEY_BUF_LEN 200
173
174/*
175 * Functions with separate implementation for MS-Windows and Unix-like systems.
176 */
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200177static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt, jobopt_T *orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200178static int create_pty_only(term_T *term, jobopt_T *opt);
179static void term_report_winsize(term_T *term, int rows, int cols);
180static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100181#ifdef FEAT_GUI
182static void update_system_term(term_T *term);
183#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200184
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100185/* The character that we know (or assume) that the terminal expects for the
186 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200187static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200188
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100189/* "Terminal" highlight group colors. */
190static int term_default_cterm_fg = -1;
191static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200192
Bram Moolenaard317b382018-02-08 22:33:31 +0100193/* Store the last set and the desired cursor properties, so that we only update
194 * them when needed. Doing it unnecessary may result in flicker. */
195static char_u *last_set_cursor_color = (char_u *)"";
196static char_u *desired_cursor_color = (char_u *)"";
197static int last_set_cursor_shape = -1;
198static int desired_cursor_shape = -1;
199static int last_set_cursor_blink = -1;
200static int desired_cursor_blink = -1;
201
202
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200203/**************************************
204 * 1. Generic code for all systems.
205 */
206
207/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200208 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
Bram Moolenaar498c2562018-04-15 23:45:15 +0200209 * current window.
210 * Sets "rows" and/or "cols" to zero when it should follow the window size.
211 * Return TRUE if the size is the minimum size: "24*80".
212 */
213 static int
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200214parse_termwinsize(win_T *wp, int *rows, int *cols)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200215{
216 int minsize = FALSE;
217
218 *rows = 0;
219 *cols = 0;
220
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200221 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200222 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200223 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200224
225 /* Syntax of value was already checked when it's set. */
226 if (p == NULL)
227 {
228 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200229 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200230 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200231 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200232 *cols = atoi((char *)p + 1);
233 }
234 return minsize;
235}
236
237/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200238 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200239 */
240 static void
241set_term_and_win_size(term_T *term)
242{
Bram Moolenaar13568252018-03-16 20:46:58 +0100243#ifdef FEAT_GUI
244 if (term->tl_system)
245 {
246 /* Use the whole screen for the system command. However, it will start
247 * at the command line and scroll up as needed, using tl_toprow. */
248 term->tl_rows = Rows;
249 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200250 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100251 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100252#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200253 if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200254 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200255 if (term->tl_rows != 0)
256 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
257 if (term->tl_cols != 0)
258 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200259 }
260 if (term->tl_rows == 0)
261 term->tl_rows = curwin->w_height;
262 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200263 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200264 if (term->tl_cols == 0)
265 term->tl_cols = curwin->w_width;
266 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200267 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200268}
269
270/*
271 * Initialize job options for a terminal job.
272 * Caller may overrule some of them.
273 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100274 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200275init_job_options(jobopt_T *opt)
276{
277 clear_job_options(opt);
278
279 opt->jo_mode = MODE_RAW;
280 opt->jo_out_mode = MODE_RAW;
281 opt->jo_err_mode = MODE_RAW;
282 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
283}
284
285/*
286 * Set job options mandatory for a terminal job.
287 */
288 static void
289setup_job_options(jobopt_T *opt, int rows, int cols)
290{
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200291#ifndef WIN3264
292 /* Win32: Redirecting the job output won't work, thus always connect stdout
293 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200294 if (!(opt->jo_set & JO_OUT_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200295#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200296 {
297 /* Connect stdout to the terminal. */
298 opt->jo_io[PART_OUT] = JIO_BUFFER;
299 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
300 opt->jo_modifiable[PART_OUT] = 0;
301 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
302 }
303
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200304#ifndef WIN3264
305 /* Win32: Redirecting the job output won't work, thus always connect stderr
306 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200307 if (!(opt->jo_set & JO_ERR_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200308#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200309 {
310 /* Connect stderr to the terminal. */
311 opt->jo_io[PART_ERR] = JIO_BUFFER;
312 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
313 opt->jo_modifiable[PART_ERR] = 0;
314 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
315 }
316
317 opt->jo_pty = TRUE;
318 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
319 opt->jo_term_rows = rows;
320 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
321 opt->jo_term_cols = cols;
322}
323
324/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100325 * Close a terminal buffer (and its window). Used when creating the terminal
326 * fails.
327 */
328 static void
329term_close_buffer(buf_T *buf, buf_T *old_curbuf)
330{
331 free_terminal(buf);
332 if (old_curbuf != NULL)
333 {
334 --curbuf->b_nwindows;
335 curbuf = old_curbuf;
336 curwin->w_buffer = curbuf;
337 ++curbuf->b_nwindows;
338 }
339
340 /* Wiping out the buffer will also close the window and call
341 * free_terminal(). */
342 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
343}
344
345/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200346 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100347 * Use either "argvar" or "argv", the other must be NULL.
348 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
349 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200350 * Returns NULL when failed.
351 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100352 buf_T *
353term_start(
354 typval_T *argvar,
355 char **argv,
356 jobopt_T *opt,
357 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200358{
359 exarg_T split_ea;
360 win_T *old_curwin = curwin;
361 term_T *term;
362 buf_T *old_curbuf = NULL;
363 int res;
364 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100365 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200366 jobopt_T orig_opt; // only partly filled
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200367
368 if (check_restricted() || check_secure())
369 return NULL;
370
371 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
372 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
373 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
374 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
375 {
376 EMSG(_(e_invarg));
377 return NULL;
378 }
379
380 term = (term_T *)alloc_clear(sizeof(term_T));
381 if (term == NULL)
382 return NULL;
383 term->tl_dirty_row_end = MAX_ROW;
384 term->tl_cursor_visible = TRUE;
385 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
386 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100387#ifdef FEAT_GUI
388 term->tl_system = (flags & TERM_START_SYSTEM);
389#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200390 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
391
392 vim_memset(&split_ea, 0, sizeof(split_ea));
393 if (opt->jo_curwin)
394 {
395 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100396 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200397 {
398 no_write_message();
399 vim_free(term);
400 return NULL;
401 }
402 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100403 ECMD_HIDE
404 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
405 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200406 {
407 vim_free(term);
408 return NULL;
409 }
410 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100411 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200412 {
413 buf_T *buf;
414
415 /* Create a new buffer without a window. Make it the current buffer for
416 * a moment to be able to do the initialisations. */
417 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
418 BLN_NEW | BLN_LISTED);
419 if (buf == NULL || ml_open(buf) == FAIL)
420 {
421 vim_free(term);
422 return NULL;
423 }
424 old_curbuf = curbuf;
425 --curbuf->b_nwindows;
426 curbuf = buf;
427 curwin->w_buffer = buf;
428 ++curbuf->b_nwindows;
429 }
430 else
431 {
432 /* Open a new window or tab. */
433 split_ea.cmdidx = CMD_new;
434 split_ea.cmd = (char_u *)"new";
435 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100436 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200437 {
438 split_ea.line2 = opt->jo_term_rows;
439 split_ea.addr_count = 1;
440 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100441 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200442 {
443 split_ea.line2 = opt->jo_term_cols;
444 split_ea.addr_count = 1;
445 }
446
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100447 if (vertical)
448 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200449 ex_splitview(&split_ea);
450 if (curwin == old_curwin)
451 {
452 /* split failed */
453 vim_free(term);
454 return NULL;
455 }
456 }
457 term->tl_buffer = curbuf;
458 curbuf->b_term = term;
459
460 if (!opt->jo_hidden)
461 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100462 /* Only one size was taken care of with :new, do the other one. With
463 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100464 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200465 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100466 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200467 win_setwidth(opt->jo_term_cols);
468 }
469
470 /* Link the new terminal in the list of active terminals. */
471 term->tl_next = first_term;
472 first_term = term;
473
474 if (opt->jo_term_name != NULL)
475 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100476 else if (argv != NULL)
477 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200478 else
479 {
480 int i;
481 size_t len;
482 char_u *cmd, *p;
483
484 if (argvar->v_type == VAR_STRING)
485 {
486 cmd = argvar->vval.v_string;
487 if (cmd == NULL)
488 cmd = (char_u *)"";
489 else if (STRCMP(cmd, "NONE") == 0)
490 cmd = (char_u *)"pty";
491 }
492 else if (argvar->v_type != VAR_LIST
493 || argvar->vval.v_list == NULL
494 || argvar->vval.v_list->lv_len < 1
495 || (cmd = get_tv_string_chk(
496 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
497 cmd = (char_u*)"";
498
499 len = STRLEN(cmd) + 10;
500 p = alloc((int)len);
501
502 for (i = 0; p != NULL; ++i)
503 {
504 /* Prepend a ! to the command name to avoid the buffer name equals
505 * the executable, otherwise ":w!" would overwrite it. */
506 if (i == 0)
507 vim_snprintf((char *)p, len, "!%s", cmd);
508 else
509 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
510 if (buflist_findname(p) == NULL)
511 {
512 vim_free(curbuf->b_ffname);
513 curbuf->b_ffname = p;
514 break;
515 }
516 }
517 }
518 curbuf->b_fname = curbuf->b_ffname;
519
520 if (opt->jo_term_opencmd != NULL)
521 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
522
523 if (opt->jo_eof_chars != NULL)
524 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
525
526 set_string_option_direct((char_u *)"buftype", -1,
527 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
528
529 /* Mark the buffer as not modifiable. It can only be made modifiable after
530 * the job finished. */
531 curbuf->b_p_ma = FALSE;
532
533 set_term_and_win_size(term);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200534#ifdef WIN3264
535 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
536#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200537 setup_job_options(opt, term->tl_rows, term->tl_cols);
538
Bram Moolenaar13568252018-03-16 20:46:58 +0100539 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100540 return curbuf;
541
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100542#if defined(FEAT_SESSION)
543 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100544 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100545 {
546 term->tl_command = vim_strsave((char_u *)"NONE");
547 }
548 else if (argvar->v_type == VAR_STRING)
549 {
550 char_u *cmd = argvar->vval.v_string;
551
552 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
553 term->tl_command = vim_strsave(cmd);
554 }
555 else if (argvar->v_type == VAR_LIST
556 && argvar->vval.v_list != NULL
557 && argvar->vval.v_list->lv_len > 0)
558 {
559 garray_T ga;
560 listitem_T *item;
561
562 ga_init2(&ga, 1, 100);
563 for (item = argvar->vval.v_list->lv_first;
564 item != NULL; item = item->li_next)
565 {
566 char_u *s = get_tv_string_chk(&item->li_tv);
567 char_u *p;
568
569 if (s == NULL)
570 break;
571 p = vim_strsave_fnameescape(s, FALSE);
572 if (p == NULL)
573 break;
574 ga_concat(&ga, p);
575 vim_free(p);
576 ga_append(&ga, ' ');
577 }
578 if (item == NULL)
579 {
580 ga_append(&ga, NUL);
581 term->tl_command = ga.ga_data;
582 }
583 else
584 ga_clear(&ga);
585 }
586#endif
587
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100588 if (opt->jo_term_kill != NULL)
589 {
590 char_u *p = skiptowhite(opt->jo_term_kill);
591
592 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
593 }
594
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200595 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100596 if (argv == NULL
597 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200598 && argvar->vval.v_string != NULL
599 && STRCMP(argvar->vval.v_string, "NONE") == 0)
600 res = create_pty_only(term, opt);
601 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200602 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200603
604 newbuf = curbuf;
605 if (res == OK)
606 {
607 /* Get and remember the size we ended up with. Update the pty. */
608 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
609 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100610#ifdef FEAT_GUI
611 if (term->tl_system)
612 {
613 /* display first line below typed command */
614 term->tl_toprow = msg_row + 1;
615 term->tl_dirty_row_end = 0;
616 }
617#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200618
619 /* Make sure we don't get stuck on sending keys to the job, it leads to
620 * a deadlock if the job is waiting for Vim to read. */
621 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
622
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200623 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200624 {
625 --curbuf->b_nwindows;
626 curbuf = old_curbuf;
627 curwin->w_buffer = curbuf;
628 ++curbuf->b_nwindows;
629 }
630 }
631 else
632 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100633 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200634 return NULL;
635 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100636
Bram Moolenaar13568252018-03-16 20:46:58 +0100637 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200638 return newbuf;
639}
640
641/*
642 * ":terminal": open a terminal window and execute a job in it.
643 */
644 void
645ex_terminal(exarg_T *eap)
646{
647 typval_T argvar[2];
648 jobopt_T opt;
649 char_u *cmd;
650 char_u *tofree = NULL;
651
652 init_job_options(&opt);
653
654 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100655 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200656 {
657 char_u *p, *ep;
658
659 cmd += 2;
660 p = skiptowhite(cmd);
661 ep = vim_strchr(cmd, '=');
662 if (ep != NULL && ep < p)
663 p = ep;
664
665 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
666 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100667 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
668 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200669 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
670 opt.jo_term_finish = 'o';
671 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
672 opt.jo_curwin = 1;
673 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
674 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100675 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
676 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100677 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
678 && ep != NULL)
679 {
680 opt.jo_set2 |= JO2_TERM_KILL;
681 opt.jo_term_kill = ep + 1;
682 p = skiptowhite(cmd);
683 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200684 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
685 && ep != NULL && isdigit(ep[1]))
686 {
687 opt.jo_set2 |= JO2_TERM_ROWS;
688 opt.jo_term_rows = atoi((char *)ep + 1);
689 p = skiptowhite(cmd);
690 }
691 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
692 && ep != NULL && isdigit(ep[1]))
693 {
694 opt.jo_set2 |= JO2_TERM_COLS;
695 opt.jo_term_cols = atoi((char *)ep + 1);
696 p = skiptowhite(cmd);
697 }
698 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
699 && ep != NULL)
700 {
701 char_u *buf = NULL;
702 char_u *keys;
703
704 p = skiptowhite(cmd);
705 *p = NUL;
706 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
707 opt.jo_set2 |= JO2_EOF_CHARS;
708 opt.jo_eof_chars = vim_strsave(keys);
709 vim_free(buf);
710 *p = ' ';
711 }
712 else
713 {
714 if (*p)
715 *p = NUL;
716 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100717 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200718 }
719 cmd = skipwhite(p);
720 }
721 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100722 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200723 /* Make a copy of 'shell', an autocommand may change the option. */
724 tofree = cmd = vim_strsave(p_sh);
725
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100726 /* default to close when the shell exits */
727 if (opt.jo_term_finish == NUL)
728 opt.jo_term_finish = 'c';
729 }
730
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200731 if (eap->addr_count > 0)
732 {
733 /* Write lines from current buffer to the job. */
734 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
735 opt.jo_io[PART_IN] = JIO_BUFFER;
736 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
737 opt.jo_in_top = eap->line1;
738 opt.jo_in_bot = eap->line2;
739 }
740
741 argvar[0].v_type = VAR_STRING;
742 argvar[0].vval.v_string = cmd;
743 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100744 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200745 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100746
747theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200748 vim_free(opt.jo_eof_chars);
749}
750
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100751#if defined(FEAT_SESSION) || defined(PROTO)
752/*
753 * Write a :terminal command to the session file to restore the terminal in
754 * window "wp".
755 * Return FAIL if writing fails.
756 */
757 int
758term_write_session(FILE *fd, win_T *wp)
759{
760 term_T *term = wp->w_buffer->b_term;
761
762 /* Create the terminal and run the command. This is not without
763 * risk, but let's assume the user only creates a session when this
764 * will be OK. */
765 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
766 term->tl_cols, term->tl_rows) < 0)
767 return FAIL;
768 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
769 return FAIL;
770
771 return put_eol(fd);
772}
773
774/*
775 * Return TRUE if "buf" has a terminal that should be restored.
776 */
777 int
778term_should_restore(buf_T *buf)
779{
780 term_T *term = buf->b_term;
781
782 return term != NULL && (term->tl_command == NULL
783 || STRCMP(term->tl_command, "NONE") != 0);
784}
785#endif
786
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200787/*
788 * Free the scrollback buffer for "term".
789 */
790 static void
791free_scrollback(term_T *term)
792{
793 int i;
794
795 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
796 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
797 ga_clear(&term->tl_scrollback);
798}
799
800/*
801 * Free a terminal and everything it refers to.
802 * Kills the job if there is one.
803 * Called when wiping out a buffer.
804 */
805 void
806free_terminal(buf_T *buf)
807{
808 term_T *term = buf->b_term;
809 term_T *tp;
810
811 if (term == NULL)
812 return;
813 if (first_term == term)
814 first_term = term->tl_next;
815 else
816 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
817 if (tp->tl_next == term)
818 {
819 tp->tl_next = term->tl_next;
820 break;
821 }
822
823 if (term->tl_job != NULL)
824 {
825 if (term->tl_job->jv_status != JOB_ENDED
826 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100827 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200828 job_stop(term->tl_job, NULL, "kill");
829 job_unref(term->tl_job);
830 }
831
832 free_scrollback(term);
833
834 term_free_vterm(term);
835 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100836#ifdef FEAT_SESSION
837 vim_free(term->tl_command);
838#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100839 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200840 vim_free(term->tl_status_text);
841 vim_free(term->tl_opencmd);
842 vim_free(term->tl_eof_chars);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200843#ifdef WIN3264
844 if (term->tl_out_fd != NULL)
845 fclose(term->tl_out_fd);
846#endif
Bram Moolenaard317b382018-02-08 22:33:31 +0100847 if (desired_cursor_color == term->tl_cursor_color)
848 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200849 vim_free(term->tl_cursor_color);
850 vim_free(term);
851 buf->b_term = NULL;
852 if (in_terminal_loop == term)
853 in_terminal_loop = NULL;
854}
855
856/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100857 * Get the part that is connected to the tty. Normally this is PART_IN, but
858 * when writing buffer lines to the job it can be another. This makes it
859 * possible to do "1,5term vim -".
860 */
861 static ch_part_T
862get_tty_part(term_T *term)
863{
864#ifdef UNIX
865 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
866 int i;
867
868 for (i = 0; i < 3; ++i)
869 {
870 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
871
872 if (isatty(fd))
873 return parts[i];
874 }
875#endif
876 return PART_IN;
877}
878
879/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200880 * Write job output "msg[len]" to the vterm.
881 */
882 static void
883term_write_job_output(term_T *term, char_u *msg, size_t len)
884{
885 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100886 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200887
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100888 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200889
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100890 /* flush vterm buffer when vterm responded to control sequence */
891 if (prevlen != vterm_output_get_buffer_current(vterm))
892 {
893 char buf[KEY_BUF_LEN];
894 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
895
896 if (curlen > 0)
897 channel_send(term->tl_job->jv_channel, get_tty_part(term),
898 (char_u *)buf, (int)curlen, NULL);
899 }
900
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200901 /* this invokes the damage callbacks */
902 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
903}
904
905 static void
906update_cursor(term_T *term, int redraw)
907{
908 if (term->tl_normal_mode)
909 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100910#ifdef FEAT_GUI
911 if (term->tl_system)
912 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
913 term->tl_cursor_pos.col);
914 else
915#endif
916 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200917 if (redraw)
918 {
919 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
920 cursor_on();
921 out_flush();
922#ifdef FEAT_GUI
923 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100924 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200925 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100926 gui_mch_flush();
927 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200928#endif
929 }
930}
931
932/*
933 * Invoked when "msg" output from a job was received. Write it to the terminal
934 * of "buffer".
935 */
936 void
937write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
938{
939 size_t len = STRLEN(msg);
940 term_T *term = buffer->b_term;
941
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200942#ifdef WIN3264
943 /* Win32: Cannot redirect output of the job, intercept it here and write to
944 * the file. */
945 if (term->tl_out_fd != NULL)
946 {
947 ch_log(channel, "Writing %d bytes to output file", (int)len);
948 fwrite(msg, len, 1, term->tl_out_fd);
949 return;
950 }
951#endif
952
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200953 if (term->tl_vterm == NULL)
954 {
955 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
956 return;
957 }
958 ch_log(channel, "writing %d bytes to terminal", (int)len);
959 term_write_job_output(term, msg, len);
960
Bram Moolenaar13568252018-03-16 20:46:58 +0100961#ifdef FEAT_GUI
962 if (term->tl_system)
963 {
964 /* show system output, scrolling up the screen as needed */
965 update_system_term(term);
966 update_cursor(term, TRUE);
967 }
968 else
969#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200970 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
971 * contents, thus no screen update is needed. */
972 if (!term->tl_normal_mode)
973 {
974 /* TODO: only update once in a while. */
975 ch_log(term->tl_job->jv_channel, "updating screen");
976 if (buffer == curbuf)
977 {
978 update_screen(0);
Bram Moolenaara10ae5e2018-05-11 20:48:29 +0200979 /* update_screen() can be slow, check the terminal wasn't closed
980 * already */
981 if (buffer == curbuf && curbuf->b_term != NULL)
982 update_cursor(curbuf->b_term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200983 }
984 else
985 redraw_after_callback(TRUE);
986 }
987}
988
989/*
990 * Send a mouse position and click to the vterm
991 */
992 static int
993term_send_mouse(VTerm *vterm, int button, int pressed)
994{
995 VTermModifier mod = VTERM_MOD_NONE;
996
997 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200998 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100999 if (button != 0)
1000 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001001 return TRUE;
1002}
1003
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001004static int enter_mouse_col = -1;
1005static int enter_mouse_row = -1;
1006
1007/*
1008 * Handle a mouse click, drag or release.
1009 * Return TRUE when a mouse event is sent to the terminal.
1010 */
1011 static int
1012term_mouse_click(VTerm *vterm, int key)
1013{
1014#if defined(FEAT_CLIPBOARD)
1015 /* For modeless selection mouse drag and release events are ignored, unless
1016 * they are preceded with a mouse down event */
1017 static int ignore_drag_release = TRUE;
1018 VTermMouseState mouse_state;
1019
1020 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1021 if (mouse_state.flags == 0)
1022 {
1023 /* Terminal is not using the mouse, use modeless selection. */
1024 switch (key)
1025 {
1026 case K_LEFTDRAG:
1027 case K_LEFTRELEASE:
1028 case K_RIGHTDRAG:
1029 case K_RIGHTRELEASE:
1030 /* Ignore drag and release events when the button-down wasn't
1031 * seen before. */
1032 if (ignore_drag_release)
1033 {
1034 int save_mouse_col, save_mouse_row;
1035
1036 if (enter_mouse_col < 0)
1037 break;
1038
1039 /* mouse click in the window gave us focus, handle that
1040 * click now */
1041 save_mouse_col = mouse_col;
1042 save_mouse_row = mouse_row;
1043 mouse_col = enter_mouse_col;
1044 mouse_row = enter_mouse_row;
1045 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1046 mouse_col = save_mouse_col;
1047 mouse_row = save_mouse_row;
1048 }
1049 /* FALLTHROUGH */
1050 case K_LEFTMOUSE:
1051 case K_RIGHTMOUSE:
1052 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1053 ignore_drag_release = TRUE;
1054 else
1055 ignore_drag_release = FALSE;
1056 /* Should we call mouse_has() here? */
1057 if (clip_star.available)
1058 {
1059 int button, is_click, is_drag;
1060
1061 button = get_mouse_button(KEY2TERMCAP1(key),
1062 &is_click, &is_drag);
1063 if (mouse_model_popup() && button == MOUSE_LEFT
1064 && (mod_mask & MOD_MASK_SHIFT))
1065 {
1066 /* Translate shift-left to right button. */
1067 button = MOUSE_RIGHT;
1068 mod_mask &= ~MOD_MASK_SHIFT;
1069 }
1070 clip_modeless(button, is_click, is_drag);
1071 }
1072 break;
1073
1074 case K_MIDDLEMOUSE:
1075 if (clip_star.available)
1076 insert_reg('*', TRUE);
1077 break;
1078 }
1079 enter_mouse_col = -1;
1080 return FALSE;
1081 }
1082#endif
1083 enter_mouse_col = -1;
1084
1085 switch (key)
1086 {
1087 case K_LEFTMOUSE:
1088 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1089 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1090 case K_LEFTRELEASE:
1091 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1092 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1093 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1094 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1095 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1096 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1097 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1098 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1099 }
1100 return TRUE;
1101}
1102
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001103/*
1104 * Convert typed key "c" into bytes to send to the job.
1105 * Return the number of bytes in "buf".
1106 */
1107 static int
1108term_convert_key(term_T *term, int c, char *buf)
1109{
1110 VTerm *vterm = term->tl_vterm;
1111 VTermKey key = VTERM_KEY_NONE;
1112 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001113 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001114
1115 switch (c)
1116 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001117 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1118
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001119 /* don't use VTERM_KEY_BACKSPACE, it always
1120 * becomes 0x7f DEL */
1121 case K_BS: c = term_backspace_char; break;
1122
1123 case ESC: key = VTERM_KEY_ESCAPE; break;
1124 case K_DEL: key = VTERM_KEY_DEL; break;
1125 case K_DOWN: key = VTERM_KEY_DOWN; break;
1126 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1127 key = VTERM_KEY_DOWN; break;
1128 case K_END: key = VTERM_KEY_END; break;
1129 case K_S_END: mod = VTERM_MOD_SHIFT;
1130 key = VTERM_KEY_END; break;
1131 case K_C_END: mod = VTERM_MOD_CTRL;
1132 key = VTERM_KEY_END; break;
1133 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1134 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1135 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1136 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1137 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1138 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1139 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1140 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1141 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1142 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1143 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1144 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1145 case K_HOME: key = VTERM_KEY_HOME; break;
1146 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1147 key = VTERM_KEY_HOME; break;
1148 case K_C_HOME: mod = VTERM_MOD_CTRL;
1149 key = VTERM_KEY_HOME; break;
1150 case K_INS: key = VTERM_KEY_INS; break;
1151 case K_K0: key = VTERM_KEY_KP_0; break;
1152 case K_K1: key = VTERM_KEY_KP_1; break;
1153 case K_K2: key = VTERM_KEY_KP_2; break;
1154 case K_K3: key = VTERM_KEY_KP_3; break;
1155 case K_K4: key = VTERM_KEY_KP_4; break;
1156 case K_K5: key = VTERM_KEY_KP_5; break;
1157 case K_K6: key = VTERM_KEY_KP_6; break;
1158 case K_K7: key = VTERM_KEY_KP_7; break;
1159 case K_K8: key = VTERM_KEY_KP_8; break;
1160 case K_K9: key = VTERM_KEY_KP_9; break;
1161 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1162 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1163 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1164 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1165 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1166 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1167 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1168 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1169 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1170 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1171 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1172 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1173 case K_LEFT: key = VTERM_KEY_LEFT; break;
1174 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1175 key = VTERM_KEY_LEFT; break;
1176 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1177 key = VTERM_KEY_LEFT; break;
1178 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1179 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1180 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1181 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1182 key = VTERM_KEY_RIGHT; break;
1183 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1184 key = VTERM_KEY_RIGHT; break;
1185 case K_UP: key = VTERM_KEY_UP; break;
1186 case K_S_UP: mod = VTERM_MOD_SHIFT;
1187 key = VTERM_KEY_UP; break;
1188 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001189 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1190 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001191
Bram Moolenaara42ad572017-11-16 13:08:04 +01001192 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1193 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001194 case K_MOUSELEFT: /* TODO */ return 0;
1195 case K_MOUSERIGHT: /* TODO */ return 0;
1196
1197 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001198 case K_LEFTMOUSE_NM:
1199 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001200 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001201 case K_LEFTRELEASE_NM:
1202 case K_MOUSEMOVE:
1203 case K_MIDDLEMOUSE:
1204 case K_MIDDLEDRAG:
1205 case K_MIDDLERELEASE:
1206 case K_RIGHTMOUSE:
1207 case K_RIGHTDRAG:
1208 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1209 return 0;
1210 other = TRUE;
1211 break;
1212
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001213 case K_X1MOUSE: /* TODO */ return 0;
1214 case K_X1DRAG: /* TODO */ return 0;
1215 case K_X1RELEASE: /* TODO */ return 0;
1216 case K_X2MOUSE: /* TODO */ return 0;
1217 case K_X2DRAG: /* TODO */ return 0;
1218 case K_X2RELEASE: /* TODO */ return 0;
1219
1220 case K_IGNORE: return 0;
1221 case K_NOP: return 0;
1222 case K_UNDO: return 0;
1223 case K_HELP: return 0;
1224 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1225 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1226 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1227 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1228 case K_SELECT: return 0;
1229#ifdef FEAT_GUI
1230 case K_VER_SCROLLBAR: return 0;
1231 case K_HOR_SCROLLBAR: return 0;
1232#endif
1233#ifdef FEAT_GUI_TABLINE
1234 case K_TABLINE: return 0;
1235 case K_TABMENU: return 0;
1236#endif
1237#ifdef FEAT_NETBEANS_INTG
1238 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1239#endif
1240#ifdef FEAT_DND
1241 case K_DROP: return 0;
1242#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001243 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001244 case K_PS: vterm_keyboard_start_paste(vterm);
1245 other = TRUE;
1246 break;
1247 case K_PE: vterm_keyboard_end_paste(vterm);
1248 other = TRUE;
1249 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001250 }
1251
1252 /*
1253 * Convert special keys to vterm keys:
1254 * - Write keys to vterm: vterm_keyboard_key()
1255 * - Write output to channel.
1256 * TODO: use mod_mask
1257 */
1258 if (key != VTERM_KEY_NONE)
1259 /* Special key, let vterm convert it. */
1260 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001261 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001262 /* Normal character, let vterm convert it. */
1263 vterm_keyboard_unichar(vterm, c, mod);
1264
1265 /* Read back the converted escape sequence. */
1266 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1267}
1268
1269/*
1270 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001271 * If "check_job_status" is TRUE update the job status.
1272 */
1273 static int
1274term_job_running_check(term_T *term, int check_job_status)
1275{
1276 /* Also consider the job finished when the channel is closed, to avoid a
1277 * race condition when updating the title. */
1278 if (term != NULL
1279 && term->tl_job != NULL
1280 && channel_is_open(term->tl_job->jv_channel))
1281 {
1282 if (check_job_status)
1283 job_status(term->tl_job);
1284 return (term->tl_job->jv_status == JOB_STARTED
1285 || term->tl_job->jv_channel->ch_keep_open);
1286 }
1287 return FALSE;
1288}
1289
1290/*
1291 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001292 */
1293 int
1294term_job_running(term_T *term)
1295{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001296 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001297}
1298
1299/*
1300 * Return TRUE if "term" has an active channel and used ":term NONE".
1301 */
1302 int
1303term_none_open(term_T *term)
1304{
1305 /* Also consider the job finished when the channel is closed, to avoid a
1306 * race condition when updating the title. */
1307 return term != NULL
1308 && term->tl_job != NULL
1309 && channel_is_open(term->tl_job->jv_channel)
1310 && term->tl_job->jv_channel->ch_keep_open;
1311}
1312
1313/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001314 * Used when exiting: kill the job in "buf" if so desired.
1315 * Return OK when the job finished.
1316 * Return FAIL when the job is still running.
1317 */
1318 int
1319term_try_stop_job(buf_T *buf)
1320{
1321 int count;
1322 char *how = (char *)buf->b_term->tl_kill;
1323
1324#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1325 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1326 {
1327 char_u buff[DIALOG_MSG_SIZE];
1328 int ret;
1329
1330 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1331 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1332 if (ret == VIM_YES)
1333 how = "kill";
1334 else if (ret == VIM_CANCEL)
1335 return FAIL;
1336 }
1337#endif
1338 if (how == NULL || *how == NUL)
1339 return FAIL;
1340
1341 job_stop(buf->b_term->tl_job, NULL, how);
1342
1343 /* wait for up to a second for the job to die */
1344 for (count = 0; count < 100; ++count)
1345 {
1346 /* buffer, terminal and job may be cleaned up while waiting */
1347 if (!buf_valid(buf)
1348 || buf->b_term == NULL
1349 || buf->b_term->tl_job == NULL)
1350 return OK;
1351
1352 /* call job_status() to update jv_status */
1353 job_status(buf->b_term->tl_job);
1354 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1355 return OK;
1356 ui_delay(10L, FALSE);
1357 mch_check_messages();
1358 parse_queued_messages();
1359 }
1360 return FAIL;
1361}
1362
1363/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001364 * Add the last line of the scrollback buffer to the buffer in the window.
1365 */
1366 static void
1367add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1368{
1369 buf_T *buf = term->tl_buffer;
1370 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1371 linenr_T lnum = buf->b_ml.ml_line_count;
1372
1373#ifdef WIN3264
1374 if (!enc_utf8 && enc_codepage > 0)
1375 {
1376 WCHAR *ret = NULL;
1377 int length = 0;
1378
1379 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1380 &ret, &length);
1381 if (ret != NULL)
1382 {
1383 WideCharToMultiByte_alloc(enc_codepage, 0,
1384 ret, length, (char **)&text, &len, 0, 0);
1385 vim_free(ret);
1386 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1387 vim_free(text);
1388 }
1389 }
1390 else
1391#endif
1392 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1393 if (empty)
1394 {
1395 /* Delete the empty line that was in the empty buffer. */
1396 curbuf = buf;
1397 ml_delete(1, FALSE);
1398 curbuf = curwin->w_buffer;
1399 }
1400}
1401
1402 static void
1403cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1404{
1405 attr->width = cell->width;
1406 attr->attrs = cell->attrs;
1407 attr->fg = cell->fg;
1408 attr->bg = cell->bg;
1409}
1410
1411 static int
1412equal_celattr(cellattr_T *a, cellattr_T *b)
1413{
1414 /* Comparing the colors should be sufficient. */
1415 return a->fg.red == b->fg.red
1416 && a->fg.green == b->fg.green
1417 && a->fg.blue == b->fg.blue
1418 && a->bg.red == b->bg.red
1419 && a->bg.green == b->bg.green
1420 && a->bg.blue == b->bg.blue;
1421}
1422
Bram Moolenaard96ff162018-02-18 22:13:29 +01001423/*
1424 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1425 * line at this position. Otherwise at the end.
1426 */
1427 static int
1428add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1429{
1430 if (ga_grow(&term->tl_scrollback, 1) == OK)
1431 {
1432 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1433 + term->tl_scrollback.ga_len;
1434
1435 if (lnum > 0)
1436 {
1437 int i;
1438
1439 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1440 {
1441 *line = *(line - 1);
1442 --line;
1443 }
1444 }
1445 line->sb_cols = 0;
1446 line->sb_cells = NULL;
1447 line->sb_fill_attr = *fill_attr;
1448 ++term->tl_scrollback.ga_len;
1449 return OK;
1450 }
1451 return FALSE;
1452}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001453
1454/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001455 * Remove the terminal contents from the scrollback and the buffer.
1456 * Used before adding a new scrollback line or updating the buffer for lines
1457 * displayed in the terminal.
1458 */
1459 static void
1460cleanup_scrollback(term_T *term)
1461{
1462 sb_line_T *line;
1463 garray_T *gap;
1464
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001465 curbuf = term->tl_buffer;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001466 gap = &term->tl_scrollback;
1467 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1468 && gap->ga_len > 0)
1469 {
1470 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1471 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1472 vim_free(line->sb_cells);
1473 --gap->ga_len;
1474 }
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001475 curbuf = curwin->w_buffer;
1476 if (curbuf == term->tl_buffer)
1477 check_cursor();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001478}
1479
1480/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001481 * Add the current lines of the terminal to scrollback and to the buffer.
1482 * Called after the job has ended and when switching to Terminal-Normal mode.
1483 */
1484 static void
1485move_terminal_to_buffer(term_T *term)
1486{
1487 win_T *wp;
1488 int len;
1489 int lines_skipped = 0;
1490 VTermPos pos;
1491 VTermScreenCell cell;
1492 cellattr_T fill_attr, new_fill_attr;
1493 cellattr_T *p;
1494 VTermScreen *screen;
1495
1496 if (term->tl_vterm == NULL)
1497 return;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001498
1499 /* Nothing to do if the buffer already has the lines and nothing was
1500 * changed. */
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001501 if (!term->tl_dirty_snapshot && term->tl_buffer->b_ml.ml_line_count
1502 > term->tl_scrollback_scrolled)
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001503 return;
1504
1505 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1506 "Adding terminal window snapshot to buffer");
1507
1508 /* First remove the lines that were appended before, they might be
1509 * outdated. */
1510 cleanup_scrollback(term);
1511
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001512 screen = vterm_obtain_screen(term->tl_vterm);
1513 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001514 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1515 {
1516 len = 0;
1517 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1518 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1519 && cell.chars[0] != NUL)
1520 {
1521 len = pos.col + 1;
1522 new_fill_attr = term->tl_default_color;
1523 }
1524 else
1525 /* Assume the last attr is the filler attr. */
1526 cell2cellattr(&cell, &new_fill_attr);
1527
1528 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1529 ++lines_skipped;
1530 else
1531 {
1532 while (lines_skipped > 0)
1533 {
1534 /* Line was skipped, add an empty line. */
1535 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001536 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001537 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001538 }
1539
1540 if (len == 0)
1541 p = NULL;
1542 else
1543 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1544 if ((p != NULL || len == 0)
1545 && ga_grow(&term->tl_scrollback, 1) == OK)
1546 {
1547 garray_T ga;
1548 int width;
1549 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1550 + term->tl_scrollback.ga_len;
1551
1552 ga_init2(&ga, 1, 100);
1553 for (pos.col = 0; pos.col < len; pos.col += width)
1554 {
1555 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1556 {
1557 width = 1;
1558 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1559 if (ga_grow(&ga, 1) == OK)
1560 ga.ga_len += utf_char2bytes(' ',
1561 (char_u *)ga.ga_data + ga.ga_len);
1562 }
1563 else
1564 {
1565 width = cell.width;
1566
1567 cell2cellattr(&cell, &p[pos.col]);
1568
1569 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1570 {
1571 int i;
1572 int c;
1573
1574 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1575 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1576 (char_u *)ga.ga_data + ga.ga_len);
1577 }
1578 }
1579 }
1580 line->sb_cols = len;
1581 line->sb_cells = p;
1582 line->sb_fill_attr = new_fill_attr;
1583 fill_attr = new_fill_attr;
1584 ++term->tl_scrollback.ga_len;
1585
1586 if (ga_grow(&ga, 1) == FAIL)
1587 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1588 else
1589 {
1590 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1591 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1592 }
1593 ga_clear(&ga);
1594 }
1595 else
1596 vim_free(p);
1597 }
1598 }
1599
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001600 term->tl_dirty_snapshot = FALSE;
1601#ifdef FEAT_TIMERS
1602 term->tl_timer_set = FALSE;
1603#endif
1604
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001605 /* Obtain the current background color. */
1606 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1607 &term->tl_default_color.fg, &term->tl_default_color.bg);
1608
1609 FOR_ALL_WINDOWS(wp)
1610 {
1611 if (wp->w_buffer == term->tl_buffer)
1612 {
1613 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1614 wp->w_cursor.col = 0;
1615 wp->w_valid = 0;
1616 if (wp->w_cursor.lnum >= wp->w_height)
1617 {
1618 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1619
1620 if (wp->w_topline < min_topline)
1621 wp->w_topline = min_topline;
1622 }
1623 redraw_win_later(wp, NOT_VALID);
1624 }
1625 }
1626}
1627
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001628#if defined(FEAT_TIMERS) || defined(PROTO)
1629/*
1630 * Check if any terminal timer expired. If so, copy text from the terminal to
1631 * the buffer.
1632 * Return the time until the next timer will expire.
1633 */
1634 int
1635term_check_timers(int next_due_arg, proftime_T *now)
1636{
1637 term_T *term;
1638 int next_due = next_due_arg;
1639
1640 for (term = first_term; term != NULL; term = term->tl_next)
1641 {
1642 if (term->tl_timer_set && !term->tl_normal_mode)
1643 {
1644 long this_due = proftime_time_left(&term->tl_timer_due, now);
1645
1646 if (this_due <= 1)
1647 {
1648 term->tl_timer_set = FALSE;
1649 move_terminal_to_buffer(term);
1650 }
1651 else if (next_due == -1 || next_due > this_due)
1652 next_due = this_due;
1653 }
1654 }
1655
1656 return next_due;
1657}
1658#endif
1659
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001660 static void
1661set_terminal_mode(term_T *term, int normal_mode)
1662{
1663 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001664 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001665 if (term->tl_buffer == curbuf)
1666 maketitle();
1667}
1668
1669/*
1670 * Called after the job if finished and Terminal mode is not active:
1671 * Move the vterm contents into the scrollback buffer and free the vterm.
1672 */
1673 static void
1674cleanup_vterm(term_T *term)
1675{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001676 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001677 move_terminal_to_buffer(term);
1678 term_free_vterm(term);
1679 set_terminal_mode(term, FALSE);
1680}
1681
1682/*
1683 * Switch from Terminal-Job mode to Terminal-Normal mode.
1684 * Suspends updating the terminal window.
1685 */
1686 static void
1687term_enter_normal_mode(void)
1688{
1689 term_T *term = curbuf->b_term;
1690
1691 /* Append the current terminal contents to the buffer. */
1692 move_terminal_to_buffer(term);
1693
1694 set_terminal_mode(term, TRUE);
1695
1696 /* Move the window cursor to the position of the cursor in the
1697 * terminal. */
1698 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1699 + term->tl_cursor_pos.row + 1;
1700 check_cursor();
1701 coladvance(term->tl_cursor_pos.col);
1702
1703 /* Display the same lines as in the terminal. */
1704 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1705}
1706
1707/*
1708 * Returns TRUE if the current window contains a terminal and we are in
1709 * Terminal-Normal mode.
1710 */
1711 int
1712term_in_normal_mode(void)
1713{
1714 term_T *term = curbuf->b_term;
1715
1716 return term != NULL && term->tl_normal_mode;
1717}
1718
1719/*
1720 * Switch from Terminal-Normal mode to Terminal-Job mode.
1721 * Restores updating the terminal window.
1722 */
1723 void
1724term_enter_job_mode()
1725{
1726 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001727
1728 set_terminal_mode(term, FALSE);
1729
1730 if (term->tl_channel_closed)
1731 cleanup_vterm(term);
1732 redraw_buf_and_status_later(curbuf, NOT_VALID);
1733}
1734
1735/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001736 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001737 * Note: while waiting a terminal may be closed and freed if the channel is
1738 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001739 */
1740 static int
1741term_vgetc()
1742{
1743 int c;
1744 int save_State = State;
1745
1746 State = TERMINAL;
1747 got_int = FALSE;
1748#ifdef WIN3264
1749 ctrl_break_was_pressed = FALSE;
1750#endif
1751 c = vgetc();
1752 got_int = FALSE;
1753 State = save_State;
1754 return c;
1755}
1756
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001757static int mouse_was_outside = FALSE;
1758
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001759/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001760 * Send keys to terminal.
1761 * Return FAIL when the key needs to be handled in Normal mode.
1762 * Return OK when the key was dropped or sent to the terminal.
1763 */
1764 int
1765send_keys_to_term(term_T *term, int c, int typed)
1766{
1767 char msg[KEY_BUF_LEN];
1768 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001769 int dragging_outside = FALSE;
1770
1771 /* Catch keys that need to be handled as in Normal mode. */
1772 switch (c)
1773 {
1774 case NUL:
1775 case K_ZERO:
1776 if (typed)
1777 stuffcharReadbuff(c);
1778 return FAIL;
1779
Bram Moolenaar231a2db2018-05-06 13:53:50 +02001780 case K_TABLINE:
1781 stuffcharReadbuff(c);
1782 return FAIL;
1783
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001784 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001785 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001786 return FAIL;
1787
1788 case K_LEFTDRAG:
1789 case K_MIDDLEDRAG:
1790 case K_RIGHTDRAG:
1791 case K_X1DRAG:
1792 case K_X2DRAG:
1793 dragging_outside = mouse_was_outside;
1794 /* FALLTHROUGH */
1795 case K_LEFTMOUSE:
1796 case K_LEFTMOUSE_NM:
1797 case K_LEFTRELEASE:
1798 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001799 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001800 case K_MIDDLEMOUSE:
1801 case K_MIDDLERELEASE:
1802 case K_RIGHTMOUSE:
1803 case K_RIGHTRELEASE:
1804 case K_X1MOUSE:
1805 case K_X1RELEASE:
1806 case K_X2MOUSE:
1807 case K_X2RELEASE:
1808
1809 case K_MOUSEUP:
1810 case K_MOUSEDOWN:
1811 case K_MOUSELEFT:
1812 case K_MOUSERIGHT:
1813 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001814 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001815 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001816 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001817 || dragging_outside)
1818 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001819 /* click or scroll outside the current window or on status line
1820 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001821 if (typed)
1822 {
1823 stuffcharReadbuff(c);
1824 mouse_was_outside = TRUE;
1825 }
1826 return FAIL;
1827 }
1828 }
1829 if (typed)
1830 mouse_was_outside = FALSE;
1831
1832 /* Convert the typed key to a sequence of bytes for the job. */
1833 len = term_convert_key(term, c, msg);
1834 if (len > 0)
1835 /* TODO: if FAIL is returned, stop? */
1836 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1837 (char_u *)msg, (int)len, NULL);
1838
1839 return OK;
1840}
1841
1842 static void
1843position_cursor(win_T *wp, VTermPos *pos)
1844{
1845 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1846 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1847 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1848}
1849
1850/*
1851 * Handle CTRL-W "": send register contents to the job.
1852 */
1853 static void
1854term_paste_register(int prev_c UNUSED)
1855{
1856 int c;
1857 list_T *l;
1858 listitem_T *item;
1859 long reglen = 0;
1860 int type;
1861
1862#ifdef FEAT_CMDL_INFO
1863 if (add_to_showcmd(prev_c))
1864 if (add_to_showcmd('"'))
1865 out_flush();
1866#endif
1867 c = term_vgetc();
1868#ifdef FEAT_CMDL_INFO
1869 clear_showcmd();
1870#endif
1871 if (!term_use_loop())
1872 /* job finished while waiting for a character */
1873 return;
1874
1875 /* CTRL-W "= prompt for expression to evaluate. */
1876 if (c == '=' && get_expr_register() != '=')
1877 return;
1878 if (!term_use_loop())
1879 /* job finished while waiting for a character */
1880 return;
1881
1882 l = (list_T *)get_reg_contents(c, GREG_LIST);
1883 if (l != NULL)
1884 {
1885 type = get_reg_type(c, &reglen);
1886 for (item = l->lv_first; item != NULL; item = item->li_next)
1887 {
1888 char_u *s = get_tv_string(&item->li_tv);
1889#ifdef WIN3264
1890 char_u *tmp = s;
1891
1892 if (!enc_utf8 && enc_codepage > 0)
1893 {
1894 WCHAR *ret = NULL;
1895 int length = 0;
1896
1897 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1898 (int)STRLEN(s), &ret, &length);
1899 if (ret != NULL)
1900 {
1901 WideCharToMultiByte_alloc(CP_UTF8, 0,
1902 ret, length, (char **)&s, &length, 0, 0);
1903 vim_free(ret);
1904 }
1905 }
1906#endif
1907 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1908 s, (int)STRLEN(s), NULL);
1909#ifdef WIN3264
1910 if (tmp != s)
1911 vim_free(s);
1912#endif
1913
1914 if (item->li_next != NULL || type == MLINE)
1915 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1916 (char_u *)"\r", 1, NULL);
1917 }
1918 list_free(l);
1919 }
1920}
1921
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001922/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001923 * Return TRUE when waiting for a character in the terminal, the cursor of the
1924 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001925 */
1926 int
1927terminal_is_active()
1928{
1929 return in_terminal_loop != NULL;
1930}
1931
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001932#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001933 cursorentry_T *
1934term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1935{
1936 term_T *term = in_terminal_loop;
1937 static cursorentry_T entry;
1938
1939 vim_memset(&entry, 0, sizeof(entry));
1940 entry.shape = entry.mshape =
1941 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1942 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1943 SHAPE_BLOCK;
1944 entry.percentage = 20;
1945 if (term->tl_cursor_blink)
1946 {
1947 entry.blinkwait = 700;
1948 entry.blinkon = 400;
1949 entry.blinkoff = 250;
1950 }
1951 *fg = gui.back_pixel;
1952 if (term->tl_cursor_color == NULL)
1953 *bg = gui.norm_pixel;
1954 else
1955 *bg = color_name2handle(term->tl_cursor_color);
1956 entry.name = "n";
1957 entry.used_for = SHAPE_CURSOR;
1958
1959 return &entry;
1960}
1961#endif
1962
Bram Moolenaard317b382018-02-08 22:33:31 +01001963 static void
1964may_output_cursor_props(void)
1965{
1966 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1967 || last_set_cursor_shape != desired_cursor_shape
1968 || last_set_cursor_blink != desired_cursor_blink)
1969 {
1970 last_set_cursor_color = desired_cursor_color;
1971 last_set_cursor_shape = desired_cursor_shape;
1972 last_set_cursor_blink = desired_cursor_blink;
1973 term_cursor_color(desired_cursor_color);
1974 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1975 /* this will restore the initial cursor style, if possible */
1976 ui_cursor_shape_forced(TRUE);
1977 else
1978 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1979 }
1980}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001981
Bram Moolenaard317b382018-02-08 22:33:31 +01001982/*
1983 * Set the cursor color and shape, if not last set to these.
1984 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001985 static void
1986may_set_cursor_props(term_T *term)
1987{
1988#ifdef FEAT_GUI
1989 /* For the GUI the cursor properties are obtained with
1990 * term_get_cursor_shape(). */
1991 if (gui.in_use)
1992 return;
1993#endif
1994 if (in_terminal_loop == term)
1995 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001996 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001997 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001998 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001999 desired_cursor_color = (char_u *)"";
2000 desired_cursor_shape = term->tl_cursor_shape;
2001 desired_cursor_blink = term->tl_cursor_blink;
2002 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002003 }
2004}
2005
Bram Moolenaard317b382018-02-08 22:33:31 +01002006/*
2007 * Reset the desired cursor properties and restore them when needed.
2008 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002009 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01002010prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002011{
2012#ifdef FEAT_GUI
2013 if (gui.in_use)
2014 return;
2015#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01002016 desired_cursor_color = (char_u *)"";
2017 desired_cursor_shape = -1;
2018 desired_cursor_blink = -1;
2019 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002020}
2021
2022/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002023 * Returns TRUE if the current window contains a terminal and we are sending
2024 * keys to the job.
2025 * If "check_job_status" is TRUE update the job status.
2026 */
2027 static int
2028term_use_loop_check(int check_job_status)
2029{
2030 term_T *term = curbuf->b_term;
2031
2032 return term != NULL
2033 && !term->tl_normal_mode
2034 && term->tl_vterm != NULL
2035 && term_job_running_check(term, check_job_status);
2036}
2037
2038/*
2039 * Returns TRUE if the current window contains a terminal and we are sending
2040 * keys to the job.
2041 */
2042 int
2043term_use_loop(void)
2044{
2045 return term_use_loop_check(FALSE);
2046}
2047
2048/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002049 * Called when entering a window with the mouse. If this is a terminal window
2050 * we may want to change state.
2051 */
2052 void
2053term_win_entered()
2054{
2055 term_T *term = curbuf->b_term;
2056
2057 if (term != NULL)
2058 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002059 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002060 {
2061 reset_VIsual_and_resel();
2062 if (State & INSERT)
2063 stop_insert_mode = TRUE;
2064 }
2065 mouse_was_outside = FALSE;
2066 enter_mouse_col = mouse_col;
2067 enter_mouse_row = mouse_row;
2068 }
2069}
2070
2071/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002072 * Wait for input and send it to the job.
2073 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2074 * when there is no more typahead.
2075 * Return when the start of a CTRL-W command is typed or anything else that
2076 * should be handled as a Normal mode command.
2077 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2078 * the terminal was closed.
2079 */
2080 int
2081terminal_loop(int blocking)
2082{
2083 int c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002084 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002085 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002086#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002087 int tty_fd = curbuf->b_term->tl_job->jv_channel
2088 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002089#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01002090 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002091
2092 /* Remember the terminal we are sending keys to. However, the terminal
2093 * might be closed while waiting for a character, e.g. typing "exit" in a
2094 * shell and ++close was used. Therefore use curbuf->b_term instead of a
2095 * stored reference. */
2096 in_terminal_loop = curbuf->b_term;
2097
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002098 if (*curwin->w_p_twk != NUL)
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002099 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002100 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2101 may_set_cursor_props(curbuf->b_term);
2102
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002103 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002104 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002105#ifdef FEAT_GUI
2106 if (!curbuf->b_term->tl_system)
2107#endif
2108 /* TODO: skip screen update when handling a sequence of keys. */
2109 /* Repeat redrawing in case a message is received while redrawing.
2110 */
2111 while (must_redraw != 0)
2112 if (update_screen(0) == FAIL)
2113 break;
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02002114 if (!term_use_loop_check(TRUE))
2115 /* job finished while redrawing */
2116 break;
2117
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002118 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002119 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002120
2121 c = term_vgetc();
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002122 if (!term_use_loop_check(TRUE))
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002123 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002124 /* Job finished while waiting for a character. Push back the
2125 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002126 if (c != K_IGNORE)
2127 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002128 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002129 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002130 if (c == K_IGNORE)
2131 continue;
2132
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002133#ifdef UNIX
2134 /*
2135 * The shell or another program may change the tty settings. Getting
2136 * them for every typed character is a bit of overhead, but it's needed
2137 * for the first character typed, e.g. when Vim starts in a shell.
2138 */
2139 if (isatty(tty_fd))
2140 {
2141 ttyinfo_T info;
2142
2143 /* Get the current backspace character of the pty. */
2144 if (get_tty_info(tty_fd, &info) == OK)
2145 term_backspace_char = info.backspace;
2146 }
2147#endif
2148
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002149#ifdef WIN3264
2150 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2151 * Use CTRL-BREAK to kill the job. */
2152 if (ctrl_break_was_pressed)
2153 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2154#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002155 /* Was either CTRL-W (termwinkey) or CTRL-\ pressed?
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002156 * Not in a system terminal. */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002157 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002158#ifdef FEAT_GUI
2159 && !curbuf->b_term->tl_system
2160#endif
2161 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002162 {
2163 int prev_c = c;
2164
2165#ifdef FEAT_CMDL_INFO
2166 if (add_to_showcmd(c))
2167 out_flush();
2168#endif
2169 c = term_vgetc();
2170#ifdef FEAT_CMDL_INFO
2171 clear_showcmd();
2172#endif
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002173 if (!term_use_loop_check(TRUE))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002174 /* job finished while waiting for a character */
2175 break;
2176
2177 if (prev_c == Ctrl_BSL)
2178 {
2179 if (c == Ctrl_N)
2180 {
2181 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2182 term_enter_normal_mode();
2183 ret = FAIL;
2184 goto theend;
2185 }
2186 /* Send both keys to the terminal. */
2187 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2188 }
2189 else if (c == Ctrl_C)
2190 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002191 /* "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002192 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2193 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002194 else if (termwinkey == 0 && c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002195 {
2196 /* "CTRL-W .": send CTRL-W to the job */
2197 c = Ctrl_W;
2198 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002199 else if (termwinkey == 0 && c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002200 {
2201 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2202 c = Ctrl_BSL;
2203 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002204 else if (c == 'N')
2205 {
2206 /* CTRL-W N : go to Terminal-Normal mode. */
2207 term_enter_normal_mode();
2208 ret = FAIL;
2209 goto theend;
2210 }
2211 else if (c == '"')
2212 {
2213 term_paste_register(prev_c);
2214 continue;
2215 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002216 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002217 {
2218 stuffcharReadbuff(Ctrl_W);
2219 stuffcharReadbuff(c);
2220 ret = OK;
2221 goto theend;
2222 }
2223 }
2224# ifdef WIN3264
2225 if (!enc_utf8 && has_mbyte && c >= 0x80)
2226 {
2227 WCHAR wc;
2228 char_u mb[3];
2229
2230 mb[0] = (unsigned)c >> 8;
2231 mb[1] = c;
2232 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2233 c = wc;
2234 }
2235# endif
2236 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2237 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002238 if (c == K_MOUSEMOVE)
2239 /* We are sure to come back here, don't reset the cursor color
2240 * and shape to avoid flickering. */
2241 restore_cursor = FALSE;
2242
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002243 ret = OK;
2244 goto theend;
2245 }
2246 }
2247 ret = FAIL;
2248
2249theend:
2250 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002251 if (restore_cursor)
2252 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002253
2254 /* Move a snapshot of the screen contents to the buffer, so that completion
2255 * works in other buffers. */
2256 if (curbuf->b_term != NULL)
2257 move_terminal_to_buffer(curbuf->b_term);
2258
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002259 return ret;
2260}
2261
2262/*
2263 * Called when a job has finished.
2264 * This updates the title and status, but does not close the vterm, because
2265 * there might still be pending output in the channel.
2266 */
2267 void
2268term_job_ended(job_T *job)
2269{
2270 term_T *term;
2271 int did_one = FALSE;
2272
2273 for (term = first_term; term != NULL; term = term->tl_next)
2274 if (term->tl_job == job)
2275 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002276 VIM_CLEAR(term->tl_title);
2277 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002278 redraw_buf_and_status_later(term->tl_buffer, VALID);
2279 did_one = TRUE;
2280 }
2281 if (did_one)
2282 redraw_statuslines();
2283 if (curbuf->b_term != NULL)
2284 {
2285 if (curbuf->b_term->tl_job == job)
2286 maketitle();
2287 update_cursor(curbuf->b_term, TRUE);
2288 }
2289}
2290
2291 static void
2292may_toggle_cursor(term_T *term)
2293{
2294 if (in_terminal_loop == term)
2295 {
2296 if (term->tl_cursor_visible)
2297 cursor_on();
2298 else
2299 cursor_off();
2300 }
2301}
2302
2303/*
2304 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002305 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002306 */
2307 static int
2308color2index(VTermColor *color, int fg, int *boldp)
2309{
2310 int red = color->red;
2311 int blue = color->blue;
2312 int green = color->green;
2313
Bram Moolenaar46359e12017-11-29 22:33:38 +01002314 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002315 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002316 /* First 16 colors and default: use the ANSI index, because these
2317 * colors can be redefined. */
2318 if (t_colors >= 16)
2319 return color->ansi_index;
2320 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002321 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002322 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002323 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002324 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2325 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2326 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002327 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002328 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2329 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2330 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2331 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2332 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2333 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2334 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2335 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2336 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2337 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2338 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002339 }
2340 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002341
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002342 if (t_colors >= 256)
2343 {
2344 if (red == blue && red == green)
2345 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002346 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002347 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002348 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2349 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2350 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002351 int i;
2352
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002353 if (red < 5)
2354 return 17; /* 00/00/00 */
2355 if (red > 245) /* ff/ff/ff */
2356 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002357 for (i = 0; i < 23; ++i)
2358 if (red < cutoff[i])
2359 return i + 233;
2360 return 256;
2361 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002362 {
2363 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2364 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002365
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002366 /* 216-color cube */
2367 for (ri = 0; ri < 5; ++ri)
2368 if (red < cutoff[ri])
2369 break;
2370 for (gi = 0; gi < 5; ++gi)
2371 if (green < cutoff[gi])
2372 break;
2373 for (bi = 0; bi < 5; ++bi)
2374 if (blue < cutoff[bi])
2375 break;
2376 return 17 + ri * 36 + gi * 6 + bi;
2377 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002378 }
2379 return 0;
2380}
2381
2382/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002383 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002384 */
2385 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002386vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002387{
2388 int attr = 0;
2389
2390 if (cellattrs.bold)
2391 attr |= HL_BOLD;
2392 if (cellattrs.underline)
2393 attr |= HL_UNDERLINE;
2394 if (cellattrs.italic)
2395 attr |= HL_ITALIC;
2396 if (cellattrs.strike)
2397 attr |= HL_STRIKETHROUGH;
2398 if (cellattrs.reverse)
2399 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002400 return attr;
2401}
2402
2403/*
2404 * Store Vterm attributes in "cell" from highlight flags.
2405 */
2406 static void
2407hl2vtermAttr(int attr, cellattr_T *cell)
2408{
2409 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2410 if (attr & HL_BOLD)
2411 cell->attrs.bold = 1;
2412 if (attr & HL_UNDERLINE)
2413 cell->attrs.underline = 1;
2414 if (attr & HL_ITALIC)
2415 cell->attrs.italic = 1;
2416 if (attr & HL_STRIKETHROUGH)
2417 cell->attrs.strike = 1;
2418 if (attr & HL_INVERSE)
2419 cell->attrs.reverse = 1;
2420}
2421
2422/*
2423 * Convert the attributes of a vterm cell into an attribute index.
2424 */
2425 static int
2426cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2427{
2428 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002429
2430#ifdef FEAT_GUI
2431 if (gui.in_use)
2432 {
2433 guicolor_T fg, bg;
2434
2435 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2436 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2437 return get_gui_attr_idx(attr, fg, bg);
2438 }
2439 else
2440#endif
2441#ifdef FEAT_TERMGUICOLORS
2442 if (p_tgc)
2443 {
2444 guicolor_T fg, bg;
2445
2446 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2447 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2448
2449 return get_tgc_attr_idx(attr, fg, bg);
2450 }
2451 else
2452#endif
2453 {
2454 int bold = MAYBE;
2455 int fg = color2index(&cellfg, TRUE, &bold);
2456 int bg = color2index(&cellbg, FALSE, &bold);
2457
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002458 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002459 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002460 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002461 if (fg == 0 && term_default_cterm_fg >= 0)
2462 fg = term_default_cterm_fg + 1;
2463 if (bg == 0 && term_default_cterm_bg >= 0)
2464 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002465 }
2466
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002467 /* with 8 colors set the bold attribute to get a bright foreground */
2468 if (bold == TRUE)
2469 attr |= HL_BOLD;
2470 return get_cterm_attr_idx(attr, fg, bg);
2471 }
2472 return 0;
2473}
2474
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002475 static void
2476set_dirty_snapshot(term_T *term)
2477{
2478 term->tl_dirty_snapshot = TRUE;
2479#ifdef FEAT_TIMERS
2480 if (!term->tl_normal_mode)
2481 {
2482 /* Update the snapshot after 100 msec of not getting updates. */
2483 profile_setlimit(100L, &term->tl_timer_due);
2484 term->tl_timer_set = TRUE;
2485 }
2486#endif
2487}
2488
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002489 static int
2490handle_damage(VTermRect rect, void *user)
2491{
2492 term_T *term = (term_T *)user;
2493
2494 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2495 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002496 set_dirty_snapshot(term);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002497 redraw_buf_later(term->tl_buffer, SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002498 return 1;
2499}
2500
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002501 static void
2502term_scroll_up(term_T *term, int start_row, int count)
2503{
2504 win_T *wp;
2505 VTermColor fg, bg;
2506 VTermScreenCellAttrs attr;
2507 int clear_attr;
2508
2509 /* Set the color to clear lines with. */
2510 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2511 &fg, &bg);
2512 vim_memset(&attr, 0, sizeof(attr));
2513 clear_attr = cell2attr(attr, fg, bg);
2514
2515 FOR_ALL_WINDOWS(wp)
2516 {
2517 if (wp->w_buffer == term->tl_buffer)
2518 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
2519 }
2520}
2521
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002522 static int
2523handle_moverect(VTermRect dest, VTermRect src, void *user)
2524{
2525 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002526 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002527
2528 /* Scrolling up is done much more efficiently by deleting lines instead of
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002529 * redrawing the text. But avoid doing this multiple times, postpone until
2530 * the redraw happens. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002531 if (dest.start_col == src.start_col
2532 && dest.end_col == src.end_col
2533 && dest.start_row < src.start_row)
2534 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002535 if (dest.start_row == 0)
2536 term->tl_postponed_scroll += count;
2537 else
2538 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002539 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002540
2541 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2542 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002543 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002544
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002545 /* Note sure if the scrolling will work correctly, let's do a complete
2546 * redraw later. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002547 redraw_buf_later(term->tl_buffer, NOT_VALID);
2548 return 1;
2549}
2550
2551 static int
2552handle_movecursor(
2553 VTermPos pos,
2554 VTermPos oldpos UNUSED,
2555 int visible,
2556 void *user)
2557{
2558 term_T *term = (term_T *)user;
2559 win_T *wp;
2560
2561 term->tl_cursor_pos = pos;
2562 term->tl_cursor_visible = visible;
2563
2564 FOR_ALL_WINDOWS(wp)
2565 {
2566 if (wp->w_buffer == term->tl_buffer)
2567 position_cursor(wp, &pos);
2568 }
2569 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2570 {
2571 may_toggle_cursor(term);
2572 update_cursor(term, term->tl_cursor_visible);
2573 }
2574
2575 return 1;
2576}
2577
2578 static int
2579handle_settermprop(
2580 VTermProp prop,
2581 VTermValue *value,
2582 void *user)
2583{
2584 term_T *term = (term_T *)user;
2585
2586 switch (prop)
2587 {
2588 case VTERM_PROP_TITLE:
2589 vim_free(term->tl_title);
2590 /* a blank title isn't useful, make it empty, so that "running" is
2591 * displayed */
2592 if (*skipwhite((char_u *)value->string) == NUL)
2593 term->tl_title = NULL;
2594#ifdef WIN3264
2595 else if (!enc_utf8 && enc_codepage > 0)
2596 {
2597 WCHAR *ret = NULL;
2598 int length = 0;
2599
2600 MultiByteToWideChar_alloc(CP_UTF8, 0,
2601 (char*)value->string, (int)STRLEN(value->string),
2602 &ret, &length);
2603 if (ret != NULL)
2604 {
2605 WideCharToMultiByte_alloc(enc_codepage, 0,
2606 ret, length, (char**)&term->tl_title,
2607 &length, 0, 0);
2608 vim_free(ret);
2609 }
2610 }
2611#endif
2612 else
2613 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002614 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002615 if (term == curbuf->b_term)
2616 maketitle();
2617 break;
2618
2619 case VTERM_PROP_CURSORVISIBLE:
2620 term->tl_cursor_visible = value->boolean;
2621 may_toggle_cursor(term);
2622 out_flush();
2623 break;
2624
2625 case VTERM_PROP_CURSORBLINK:
2626 term->tl_cursor_blink = value->boolean;
2627 may_set_cursor_props(term);
2628 break;
2629
2630 case VTERM_PROP_CURSORSHAPE:
2631 term->tl_cursor_shape = value->number;
2632 may_set_cursor_props(term);
2633 break;
2634
2635 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002636 if (desired_cursor_color == term->tl_cursor_color)
2637 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002638 vim_free(term->tl_cursor_color);
2639 if (*value->string == NUL)
2640 term->tl_cursor_color = NULL;
2641 else
2642 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2643 may_set_cursor_props(term);
2644 break;
2645
2646 case VTERM_PROP_ALTSCREEN:
2647 /* TODO: do anything else? */
2648 term->tl_using_altscreen = value->boolean;
2649 break;
2650
2651 default:
2652 break;
2653 }
2654 /* Always return 1, otherwise vterm doesn't store the value internally. */
2655 return 1;
2656}
2657
2658/*
2659 * The job running in the terminal resized the terminal.
2660 */
2661 static int
2662handle_resize(int rows, int cols, void *user)
2663{
2664 term_T *term = (term_T *)user;
2665 win_T *wp;
2666
2667 term->tl_rows = rows;
2668 term->tl_cols = cols;
2669 if (term->tl_vterm_size_changed)
2670 /* Size was set by vterm_set_size(), don't set the window size. */
2671 term->tl_vterm_size_changed = FALSE;
2672 else
2673 {
2674 FOR_ALL_WINDOWS(wp)
2675 {
2676 if (wp->w_buffer == term->tl_buffer)
2677 {
2678 win_setheight_win(rows, wp);
2679 win_setwidth_win(cols, wp);
2680 }
2681 }
2682 redraw_buf_later(term->tl_buffer, NOT_VALID);
2683 }
2684 return 1;
2685}
2686
2687/*
2688 * Handle a line that is pushed off the top of the screen.
2689 */
2690 static int
2691handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2692{
2693 term_T *term = (term_T *)user;
2694
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002695 /* First remove the lines that were appended before, the pushed line goes
2696 * above it. */
2697 cleanup_scrollback(term);
2698
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002699 /* If the number of lines that are stored goes over 'termscrollback' then
2700 * delete the first 10%. */
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002701 if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002702 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002703 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002704 int i;
2705
2706 curbuf = term->tl_buffer;
2707 for (i = 0; i < todo; ++i)
2708 {
2709 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2710 ml_delete(1, FALSE);
2711 }
2712 curbuf = curwin->w_buffer;
2713
2714 term->tl_scrollback.ga_len -= todo;
2715 mch_memmove(term->tl_scrollback.ga_data,
2716 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2717 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
2718 }
2719
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002720 if (ga_grow(&term->tl_scrollback, 1) == OK)
2721 {
2722 cellattr_T *p = NULL;
2723 int len = 0;
2724 int i;
2725 int c;
2726 int col;
2727 sb_line_T *line;
2728 garray_T ga;
2729 cellattr_T fill_attr = term->tl_default_color;
2730
2731 /* do not store empty cells at the end */
2732 for (i = 0; i < cols; ++i)
2733 if (cells[i].chars[0] != 0)
2734 len = i + 1;
2735 else
2736 cell2cellattr(&cells[i], &fill_attr);
2737
2738 ga_init2(&ga, 1, 100);
2739 if (len > 0)
2740 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2741 if (p != NULL)
2742 {
2743 for (col = 0; col < len; col += cells[col].width)
2744 {
2745 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2746 {
2747 ga.ga_len = 0;
2748 break;
2749 }
2750 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2751 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2752 (char_u *)ga.ga_data + ga.ga_len);
2753 cell2cellattr(&cells[col], &p[col]);
2754 }
2755 }
2756 if (ga_grow(&ga, 1) == FAIL)
2757 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2758 else
2759 {
2760 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2761 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2762 }
2763 ga_clear(&ga);
2764
2765 line = (sb_line_T *)term->tl_scrollback.ga_data
2766 + term->tl_scrollback.ga_len;
2767 line->sb_cols = len;
2768 line->sb_cells = p;
2769 line->sb_fill_attr = fill_attr;
2770 ++term->tl_scrollback.ga_len;
2771 ++term->tl_scrollback_scrolled;
2772 }
2773 return 0; /* ignored */
2774}
2775
2776static VTermScreenCallbacks screen_callbacks = {
2777 handle_damage, /* damage */
2778 handle_moverect, /* moverect */
2779 handle_movecursor, /* movecursor */
2780 handle_settermprop, /* settermprop */
2781 NULL, /* bell */
2782 handle_resize, /* resize */
2783 handle_pushline, /* sb_pushline */
2784 NULL /* sb_popline */
2785};
2786
2787/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002788 * Do the work after the channel of a terminal was closed.
2789 * Must be called only when updating_screen is FALSE.
2790 * Returns TRUE when a buffer was closed (list of terminals may have changed).
2791 */
2792 static int
2793term_after_channel_closed(term_T *term)
2794{
2795 /* Unless in Terminal-Normal mode: clear the vterm. */
2796 if (!term->tl_normal_mode)
2797 {
2798 int fnum = term->tl_buffer->b_fnum;
2799
2800 cleanup_vterm(term);
2801
2802 if (term->tl_finish == TL_FINISH_CLOSE)
2803 {
2804 aco_save_T aco;
2805
2806 /* ++close or term_finish == "close" */
2807 ch_log(NULL, "terminal job finished, closing window");
2808 aucmd_prepbuf(&aco, term->tl_buffer);
2809 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
2810 aucmd_restbuf(&aco);
2811 return TRUE;
2812 }
2813 if (term->tl_finish == TL_FINISH_OPEN
2814 && term->tl_buffer->b_nwindows == 0)
2815 {
2816 char buf[50];
2817
2818 /* TODO: use term_opencmd */
2819 ch_log(NULL, "terminal job finished, opening window");
2820 vim_snprintf(buf, sizeof(buf),
2821 term->tl_opencmd == NULL
2822 ? "botright sbuf %d"
2823 : (char *)term->tl_opencmd, fnum);
2824 do_cmdline_cmd((char_u *)buf);
2825 }
2826 else
2827 ch_log(NULL, "terminal job finished");
2828 }
2829
2830 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2831 return FALSE;
2832}
2833
2834/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002835 * Called when a channel has been closed.
2836 * If this was a channel for a terminal window then finish it up.
2837 */
2838 void
2839term_channel_closed(channel_T *ch)
2840{
2841 term_T *term;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002842 term_T *next_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002843 int did_one = FALSE;
2844
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002845 for (term = first_term; term != NULL; term = next_term)
2846 {
2847 next_term = term->tl_next;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002848 if (term->tl_job == ch->ch_job)
2849 {
2850 term->tl_channel_closed = TRUE;
2851 did_one = TRUE;
2852
Bram Moolenaard23a8232018-02-10 18:45:26 +01002853 VIM_CLEAR(term->tl_title);
2854 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar402c8392018-05-06 22:01:42 +02002855#ifdef WIN3264
2856 if (term->tl_out_fd != NULL)
2857 {
2858 fclose(term->tl_out_fd);
2859 term->tl_out_fd = NULL;
2860 }
2861#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002862
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002863 if (updating_screen)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002864 {
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002865 /* Cannot open or close windows now. Can happen when
2866 * 'lazyredraw' is set. */
2867 term->tl_channel_recently_closed = TRUE;
2868 continue;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002869 }
2870
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002871 if (term_after_channel_closed(term))
2872 next_term = first_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002873 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002874 }
2875
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002876 if (did_one)
2877 {
2878 redraw_statuslines();
2879
2880 /* Need to break out of vgetc(). */
2881 ins_char_typebuf(K_IGNORE);
2882 typebuf_was_filled = TRUE;
2883
2884 term = curbuf->b_term;
2885 if (term != NULL)
2886 {
2887 if (term->tl_job == ch->ch_job)
2888 maketitle();
2889 update_cursor(term, term->tl_cursor_visible);
2890 }
2891 }
2892}
2893
2894/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002895 * To be called after resetting updating_screen: handle any terminal where the
2896 * channel was closed.
2897 */
2898 void
2899term_check_channel_closed_recently()
2900{
2901 term_T *term;
2902 term_T *next_term;
2903
2904 for (term = first_term; term != NULL; term = next_term)
2905 {
2906 next_term = term->tl_next;
2907 if (term->tl_channel_recently_closed)
2908 {
2909 term->tl_channel_recently_closed = FALSE;
2910 if (term_after_channel_closed(term))
2911 // start over, the list may have changed
2912 next_term = first_term;
2913 }
2914 }
2915}
2916
2917/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002918 * Fill one screen line from a line of the terminal.
2919 * Advances "pos" to past the last column.
2920 */
2921 static void
2922term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2923{
2924 int off = screen_get_current_line_off();
2925
2926 for (pos->col = 0; pos->col < max_col; )
2927 {
2928 VTermScreenCell cell;
2929 int c;
2930
2931 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2932 vim_memset(&cell, 0, sizeof(cell));
2933
2934 c = cell.chars[0];
2935 if (c == NUL)
2936 {
2937 ScreenLines[off] = ' ';
2938 if (enc_utf8)
2939 ScreenLinesUC[off] = NUL;
2940 }
2941 else
2942 {
2943 if (enc_utf8)
2944 {
2945 int i;
2946
2947 /* composing chars */
2948 for (i = 0; i < Screen_mco
2949 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2950 {
2951 ScreenLinesC[i][off] = cell.chars[i + 1];
2952 if (cell.chars[i + 1] == 0)
2953 break;
2954 }
2955 if (c >= 0x80 || (Screen_mco > 0
2956 && ScreenLinesC[0][off] != 0))
2957 {
2958 ScreenLines[off] = ' ';
2959 ScreenLinesUC[off] = c;
2960 }
2961 else
2962 {
2963 ScreenLines[off] = c;
2964 ScreenLinesUC[off] = NUL;
2965 }
2966 }
2967#ifdef WIN3264
2968 else if (has_mbyte && c >= 0x80)
2969 {
2970 char_u mb[MB_MAXBYTES+1];
2971 WCHAR wc = c;
2972
2973 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2974 (char*)mb, 2, 0, 0) > 1)
2975 {
2976 ScreenLines[off] = mb[0];
2977 ScreenLines[off + 1] = mb[1];
2978 cell.width = mb_ptr2cells(mb);
2979 }
2980 else
2981 ScreenLines[off] = c;
2982 }
2983#endif
2984 else
2985 ScreenLines[off] = c;
2986 }
2987 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2988
2989 ++pos->col;
2990 ++off;
2991 if (cell.width == 2)
2992 {
2993 if (enc_utf8)
2994 ScreenLinesUC[off] = NUL;
2995
2996 /* don't set the second byte to NUL for a DBCS encoding, it
2997 * has been set above */
2998 if (enc_utf8 || !has_mbyte)
2999 ScreenLines[off] = NUL;
3000
3001 ++pos->col;
3002 ++off;
3003 }
3004 }
3005}
3006
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003007#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01003008 static void
3009update_system_term(term_T *term)
3010{
3011 VTermPos pos;
3012 VTermScreen *screen;
3013
3014 if (term->tl_vterm == NULL)
3015 return;
3016 screen = vterm_obtain_screen(term->tl_vterm);
3017
3018 /* Scroll up to make more room for terminal lines if needed. */
3019 while (term->tl_toprow > 0
3020 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3021 {
3022 int save_p_more = p_more;
3023
3024 p_more = FALSE;
3025 msg_row = Rows - 1;
3026 msg_puts((char_u *)"\n");
3027 p_more = save_p_more;
3028 --term->tl_toprow;
3029 }
3030
3031 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3032 && pos.row < Rows; ++pos.row)
3033 {
3034 if (pos.row < term->tl_rows)
3035 {
3036 int max_col = MIN(Columns, term->tl_cols);
3037
3038 term_line2screenline(screen, &pos, max_col);
3039 }
3040 else
3041 pos.col = 0;
3042
3043 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
3044 }
3045
3046 term->tl_dirty_row_start = MAX_ROW;
3047 term->tl_dirty_row_end = 0;
3048 update_cursor(term, TRUE);
3049}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003050#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01003051
3052/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003053 * Return TRUE if window "wp" is to be redrawn with term_update_window().
3054 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003055 * Terminal-Normal mode.
3056 */
3057 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003058term_do_update_window(win_T *wp)
3059{
3060 term_T *term = wp->w_buffer->b_term;
3061
3062 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3063}
3064
3065/*
3066 * Called to update a window that contains an active terminal.
3067 */
3068 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003069term_update_window(win_T *wp)
3070{
3071 term_T *term = wp->w_buffer->b_term;
3072 VTerm *vterm;
3073 VTermScreen *screen;
3074 VTermState *state;
3075 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003076 int rows, cols;
3077 int newrows, newcols;
3078 int minsize;
3079 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003080
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003081 vterm = term->tl_vterm;
3082 screen = vterm_obtain_screen(vterm);
3083 state = vterm_obtain_state(vterm);
3084
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003085 /* We use NOT_VALID on a resize or scroll, redraw everything then. With
3086 * SOME_VALID only redraw what was marked dirty. */
3087 if (wp->w_redr_type > SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003088 {
3089 term->tl_dirty_row_start = 0;
3090 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003091
3092 if (term->tl_postponed_scroll > 0
3093 && term->tl_postponed_scroll < term->tl_rows / 3)
3094 /* Scrolling is usually faster than redrawing, when there are only
3095 * a few lines to scroll. */
3096 term_scroll_up(term, 0, term->tl_postponed_scroll);
3097 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003098 }
3099
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003100 /*
3101 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003102 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003103 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003104 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003105
Bram Moolenaar498c2562018-04-15 23:45:15 +02003106 newrows = 99999;
3107 newcols = 99999;
3108 FOR_ALL_WINDOWS(twp)
3109 {
3110 /* When more than one window shows the same terminal, use the
3111 * smallest size. */
3112 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003113 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02003114 newrows = MIN(newrows, twp->w_height);
3115 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003116 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02003117 }
3118 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3119 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3120
3121 if (term->tl_rows != newrows || term->tl_cols != newcols)
3122 {
3123
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003124
3125 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003126 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003127 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003128 newrows);
3129 term_report_winsize(term, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003130 }
3131
3132 /* The cursor may have been moved when resizing. */
3133 vterm_state_get_cursorpos(state, &pos);
3134 position_cursor(wp, &pos);
3135
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003136 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3137 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003138 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003139 if (pos.row < term->tl_rows)
3140 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003141 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003142
Bram Moolenaar13568252018-03-16 20:46:58 +01003143 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003144 }
3145 else
3146 pos.col = 0;
3147
Bram Moolenaarf118d482018-03-13 13:14:00 +01003148 screen_line(wp->w_winrow + pos.row
3149#ifdef FEAT_MENU
3150 + winbar_height(wp)
3151#endif
3152 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003153 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003154 term->tl_dirty_row_start = MAX_ROW;
3155 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003156}
3157
3158/*
3159 * Return TRUE if "wp" is a terminal window where the job has finished.
3160 */
3161 int
3162term_is_finished(buf_T *buf)
3163{
3164 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3165}
3166
3167/*
3168 * Return TRUE if "wp" is a terminal window where the job has finished or we
3169 * are in Terminal-Normal mode, thus we show the buffer contents.
3170 */
3171 int
3172term_show_buffer(buf_T *buf)
3173{
3174 term_T *term = buf->b_term;
3175
3176 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3177}
3178
3179/*
3180 * The current buffer is going to be changed. If there is terminal
3181 * highlighting remove it now.
3182 */
3183 void
3184term_change_in_curbuf(void)
3185{
3186 term_T *term = curbuf->b_term;
3187
3188 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3189 {
3190 free_scrollback(term);
3191 redraw_buf_later(term->tl_buffer, NOT_VALID);
3192
3193 /* The buffer is now like a normal buffer, it cannot be easily
3194 * abandoned when changed. */
3195 set_string_option_direct((char_u *)"buftype", -1,
3196 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3197 }
3198}
3199
3200/*
3201 * Get the screen attribute for a position in the buffer.
3202 * Use a negative "col" to get the filler background color.
3203 */
3204 int
3205term_get_attr(buf_T *buf, linenr_T lnum, int col)
3206{
3207 term_T *term = buf->b_term;
3208 sb_line_T *line;
3209 cellattr_T *cellattr;
3210
3211 if (lnum > term->tl_scrollback.ga_len)
3212 cellattr = &term->tl_default_color;
3213 else
3214 {
3215 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3216 if (col < 0 || col >= line->sb_cols)
3217 cellattr = &line->sb_fill_attr;
3218 else
3219 cellattr = line->sb_cells + col;
3220 }
3221 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3222}
3223
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003224/*
3225 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003226 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003227 */
3228 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003229cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003230{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003231 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003232}
3233
3234/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003235 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003236 */
3237 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003238init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003239{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003240 VTermColor *fg, *bg;
3241 int fgval, bgval;
3242 int id;
3243
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003244 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3245 term->tl_default_color.width = 1;
3246 fg = &term->tl_default_color.fg;
3247 bg = &term->tl_default_color.bg;
3248
3249 /* Vterm uses a default black background. Set it to white when
3250 * 'background' is "light". */
3251 if (*p_bg == 'l')
3252 {
3253 fgval = 0;
3254 bgval = 255;
3255 }
3256 else
3257 {
3258 fgval = 255;
3259 bgval = 0;
3260 }
3261 fg->red = fg->green = fg->blue = fgval;
3262 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003263 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003264
3265 /* The "Terminal" highlight group overrules the defaults. */
3266 id = syn_name2id((char_u *)"Terminal");
3267
Bram Moolenaar46359e12017-11-29 22:33:38 +01003268 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003269#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3270 if (0
3271# ifdef FEAT_GUI
3272 || gui.in_use
3273# endif
3274# ifdef FEAT_TERMGUICOLORS
3275 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003276# ifdef FEAT_VTP
3277 /* Finally get INVALCOLOR on this execution path */
3278 || (!p_tgc && t_colors >= 256)
3279# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003280# endif
3281 )
3282 {
3283 guicolor_T fg_rgb = INVALCOLOR;
3284 guicolor_T bg_rgb = INVALCOLOR;
3285
3286 if (id != 0)
3287 syn_id2colors(id, &fg_rgb, &bg_rgb);
3288
3289# ifdef FEAT_GUI
3290 if (gui.in_use)
3291 {
3292 if (fg_rgb == INVALCOLOR)
3293 fg_rgb = gui.norm_pixel;
3294 if (bg_rgb == INVALCOLOR)
3295 bg_rgb = gui.back_pixel;
3296 }
3297# ifdef FEAT_TERMGUICOLORS
3298 else
3299# endif
3300# endif
3301# ifdef FEAT_TERMGUICOLORS
3302 {
3303 if (fg_rgb == INVALCOLOR)
3304 fg_rgb = cterm_normal_fg_gui_color;
3305 if (bg_rgb == INVALCOLOR)
3306 bg_rgb = cterm_normal_bg_gui_color;
3307 }
3308# endif
3309 if (fg_rgb != INVALCOLOR)
3310 {
3311 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3312
3313 fg->red = (unsigned)(rgb >> 16);
3314 fg->green = (unsigned)(rgb >> 8) & 255;
3315 fg->blue = (unsigned)rgb & 255;
3316 }
3317 if (bg_rgb != INVALCOLOR)
3318 {
3319 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3320
3321 bg->red = (unsigned)(rgb >> 16);
3322 bg->green = (unsigned)(rgb >> 8) & 255;
3323 bg->blue = (unsigned)rgb & 255;
3324 }
3325 }
3326 else
3327#endif
3328 if (id != 0 && t_colors >= 16)
3329 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003330 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003331 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003332 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003333 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003334 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003335 else
3336 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003337#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003338 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003339#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003340
3341 /* In an MS-Windows console we know the normal colors. */
3342 if (cterm_normal_fg_color > 0)
3343 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003344 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003345# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003346 tmp = fg->red;
3347 fg->red = fg->blue;
3348 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003349# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003350 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003351# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003352 else
3353 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003354# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003355
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003356 if (cterm_normal_bg_color > 0)
3357 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003358 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003359# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003360 tmp = bg->red;
3361 bg->red = bg->blue;
3362 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003363# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003364 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003365# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003366 else
3367 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003368# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003369 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003370}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003371
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003372#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3373/*
3374 * Set the 16 ANSI colors from array of RGB values
3375 */
3376 static void
3377set_vterm_palette(VTerm *vterm, long_u *rgb)
3378{
3379 int index = 0;
3380 VTermState *state = vterm_obtain_state(vterm);
3381 for (; index < 16; index++)
3382 {
3383 VTermColor color;
3384 color.red = (unsigned)(rgb[index] >> 16);
3385 color.green = (unsigned)(rgb[index] >> 8) & 255;
3386 color.blue = (unsigned)rgb[index] & 255;
3387 vterm_state_set_palette_color(state, index, &color);
3388 }
3389}
3390
3391/*
3392 * Set the ANSI color palette from a list of colors
3393 */
3394 static int
3395set_ansi_colors_list(VTerm *vterm, list_T *list)
3396{
3397 int n = 0;
3398 long_u rgb[16];
3399 listitem_T *li = list->lv_first;
3400
3401 for (; li != NULL && n < 16; li = li->li_next, n++)
3402 {
3403 char_u *color_name;
3404 guicolor_T guicolor;
3405
3406 color_name = get_tv_string_chk(&li->li_tv);
3407 if (color_name == NULL)
3408 return FAIL;
3409
3410 guicolor = GUI_GET_COLOR(color_name);
3411 if (guicolor == INVALCOLOR)
3412 return FAIL;
3413
3414 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3415 }
3416
3417 if (n != 16 || li != NULL)
3418 return FAIL;
3419
3420 set_vterm_palette(vterm, rgb);
3421
3422 return OK;
3423}
3424
3425/*
3426 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3427 */
3428 static void
3429init_vterm_ansi_colors(VTerm *vterm)
3430{
3431 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3432
3433 if (var != NULL
3434 && (var->di_tv.v_type != VAR_LIST
3435 || var->di_tv.vval.v_list == NULL
3436 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
3437 EMSG2(_(e_invarg2), "g:terminal_ansi_colors");
3438}
3439#endif
3440
Bram Moolenaar52acb112018-03-18 19:20:22 +01003441/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003442 * Handles a "drop" command from the job in the terminal.
3443 * "item" is the file name, "item->li_next" may have options.
3444 */
3445 static void
3446handle_drop_command(listitem_T *item)
3447{
3448 char_u *fname = get_tv_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003449 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003450 int bufnr;
3451 win_T *wp;
3452 tabpage_T *tp;
3453 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003454 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003455
3456 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3457 FOR_ALL_TAB_WINDOWS(tp, wp)
3458 {
3459 if (wp->w_buffer->b_fnum == bufnr)
3460 {
3461 /* buffer is in a window already, go there */
3462 goto_tabpage_win(tp, wp);
3463 return;
3464 }
3465 }
3466
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003467 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003468
3469 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3470 && opt_item->li_tv.vval.v_dict != NULL)
3471 {
3472 dict_T *dict = opt_item->li_tv.vval.v_dict;
3473 char_u *p;
3474
3475 p = get_dict_string(dict, (char_u *)"ff", FALSE);
3476 if (p == NULL)
3477 p = get_dict_string(dict, (char_u *)"fileformat", FALSE);
3478 if (p != NULL)
3479 {
3480 if (check_ff_value(p) == FAIL)
3481 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3482 else
3483 ea.force_ff = *p;
3484 }
3485 p = get_dict_string(dict, (char_u *)"enc", FALSE);
3486 if (p == NULL)
3487 p = get_dict_string(dict, (char_u *)"encoding", FALSE);
3488 if (p != NULL)
3489 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003490 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003491 if (ea.cmd != NULL)
3492 {
3493 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3494 ea.force_enc = 11;
3495 tofree = ea.cmd;
3496 }
3497 }
3498
3499 p = get_dict_string(dict, (char_u *)"bad", FALSE);
3500 if (p != NULL)
3501 get_bad_opt(p, &ea);
3502
3503 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3504 ea.force_bin = FORCE_BIN;
3505 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3506 ea.force_bin = FORCE_BIN;
3507 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3508 ea.force_bin = FORCE_NOBIN;
3509 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3510 ea.force_bin = FORCE_NOBIN;
3511 }
3512
3513 /* open in new window, like ":split fname" */
3514 if (ea.cmd == NULL)
3515 ea.cmd = (char_u *)"split";
3516 ea.arg = fname;
3517 ea.cmdidx = CMD_split;
3518 ex_splitview(&ea);
3519
3520 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003521}
3522
3523/*
3524 * Handles a function call from the job running in a terminal.
3525 * "item" is the function name, "item->li_next" has the arguments.
3526 */
3527 static void
3528handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3529{
3530 char_u *func;
3531 typval_T argvars[2];
3532 typval_T rettv;
3533 int doesrange;
3534
3535 if (item->li_next == NULL)
3536 {
3537 ch_log(channel, "Missing function arguments for call");
3538 return;
3539 }
3540 func = get_tv_string(&item->li_tv);
3541
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003542 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003543 {
3544 ch_log(channel, "Invalid function name: %s", func);
3545 return;
3546 }
3547
3548 argvars[0].v_type = VAR_NUMBER;
3549 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3550 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003551 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003552 2, argvars, /* argv_func */ NULL,
3553 /* firstline */ 1, /* lastline */ 1,
3554 &doesrange, /* evaluate */ TRUE,
3555 /* partial */ NULL, /* selfdict */ NULL) == OK)
3556 {
3557 clear_tv(&rettv);
3558 ch_log(channel, "Function %s called", func);
3559 }
3560 else
3561 ch_log(channel, "Calling function %s failed", func);
3562}
3563
3564/*
3565 * Called by libvterm when it cannot recognize an OSC sequence.
3566 * We recognize a terminal API command.
3567 */
3568 static int
3569parse_osc(const char *command, size_t cmdlen, void *user)
3570{
3571 term_T *term = (term_T *)user;
3572 js_read_T reader;
3573 typval_T tv;
3574 channel_T *channel = term->tl_job == NULL ? NULL
3575 : term->tl_job->jv_channel;
3576
3577 /* We recognize only OSC 5 1 ; {command} */
3578 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3579 return 0; /* not handled */
3580
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003581 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003582 if (reader.js_buf == NULL)
3583 return 1;
3584 reader.js_fill = NULL;
3585 reader.js_used = 0;
3586 if (json_decode(&reader, &tv, 0) == OK
3587 && tv.v_type == VAR_LIST
3588 && tv.vval.v_list != NULL)
3589 {
3590 listitem_T *item = tv.vval.v_list->lv_first;
3591
3592 if (item == NULL)
3593 ch_log(channel, "Missing command");
3594 else
3595 {
3596 char_u *cmd = get_tv_string(&item->li_tv);
3597
Bram Moolenaara997b452018-04-17 23:24:06 +02003598 /* Make sure an invoked command doesn't delete the buffer (and the
3599 * terminal) under our fingers. */
3600 ++term->tl_buffer->b_locked;
3601
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003602 item = item->li_next;
3603 if (item == NULL)
3604 ch_log(channel, "Missing argument for %s", cmd);
3605 else if (STRCMP(cmd, "drop") == 0)
3606 handle_drop_command(item);
3607 else if (STRCMP(cmd, "call") == 0)
3608 handle_call_command(term, channel, item);
3609 else
3610 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003611 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003612 }
3613 }
3614 else
3615 ch_log(channel, "Invalid JSON received");
3616
3617 vim_free(reader.js_buf);
3618 clear_tv(&tv);
3619 return 1;
3620}
3621
3622static VTermParserCallbacks parser_fallbacks = {
3623 NULL, /* text */
3624 NULL, /* control */
3625 NULL, /* escape */
3626 NULL, /* csi */
3627 parse_osc, /* osc */
3628 NULL, /* dcs */
3629 NULL /* resize */
3630};
3631
3632/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003633 * Use Vim's allocation functions for vterm so profiling works.
3634 */
3635 static void *
3636vterm_malloc(size_t size, void *data UNUSED)
3637{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003638 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003639}
3640
3641 static void
3642vterm_memfree(void *ptr, void *data UNUSED)
3643{
3644 vim_free(ptr);
3645}
3646
3647static VTermAllocatorFunctions vterm_allocator = {
3648 &vterm_malloc,
3649 &vterm_memfree
3650};
3651
3652/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003653 * Create a new vterm and initialize it.
3654 */
3655 static void
3656create_vterm(term_T *term, int rows, int cols)
3657{
3658 VTerm *vterm;
3659 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003660 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003661 VTermValue value;
3662
Bram Moolenaar756ef112018-04-10 12:04:27 +02003663 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003664 term->tl_vterm = vterm;
3665 screen = vterm_obtain_screen(vterm);
3666 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3667 /* TODO: depends on 'encoding'. */
3668 vterm_set_utf8(vterm, 1);
3669
3670 init_default_colors(term);
3671
3672 vterm_state_set_default_colors(
3673 vterm_obtain_state(vterm),
3674 &term->tl_default_color.fg,
3675 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003676
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003677 if (t_colors >= 16)
3678 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3679
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003680 /* Required to initialize most things. */
3681 vterm_screen_reset(screen, 1 /* hard */);
3682
3683 /* Allow using alternate screen. */
3684 vterm_screen_enable_altscreen(screen, 1);
3685
3686 /* For unix do not use a blinking cursor. In an xterm this causes the
3687 * cursor to blink if it's blinking in the xterm.
3688 * For Windows we respect the system wide setting. */
3689#ifdef WIN3264
3690 if (GetCaretBlinkTime() == INFINITE)
3691 value.boolean = 0;
3692 else
3693 value.boolean = 1;
3694#else
3695 value.boolean = 0;
3696#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003697 state = vterm_obtain_state(vterm);
3698 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3699 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003700}
3701
3702/*
3703 * Return the text to show for the buffer name and status.
3704 */
3705 char_u *
3706term_get_status_text(term_T *term)
3707{
3708 if (term->tl_status_text == NULL)
3709 {
3710 char_u *txt;
3711 size_t len;
3712
3713 if (term->tl_normal_mode)
3714 {
3715 if (term_job_running(term))
3716 txt = (char_u *)_("Terminal");
3717 else
3718 txt = (char_u *)_("Terminal-finished");
3719 }
3720 else if (term->tl_title != NULL)
3721 txt = term->tl_title;
3722 else if (term_none_open(term))
3723 txt = (char_u *)_("active");
3724 else if (term_job_running(term))
3725 txt = (char_u *)_("running");
3726 else
3727 txt = (char_u *)_("finished");
3728 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3729 term->tl_status_text = alloc((int)len);
3730 if (term->tl_status_text != NULL)
3731 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3732 term->tl_buffer->b_fname, txt);
3733 }
3734 return term->tl_status_text;
3735}
3736
3737/*
3738 * Mark references in jobs of terminals.
3739 */
3740 int
3741set_ref_in_term(int copyID)
3742{
3743 int abort = FALSE;
3744 term_T *term;
3745 typval_T tv;
3746
3747 for (term = first_term; term != NULL; term = term->tl_next)
3748 if (term->tl_job != NULL)
3749 {
3750 tv.v_type = VAR_JOB;
3751 tv.vval.v_job = term->tl_job;
3752 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3753 }
3754 return abort;
3755}
3756
3757/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003758 * Cache "Terminal" highlight group colors.
3759 */
3760 void
3761set_terminal_default_colors(int cterm_fg, int cterm_bg)
3762{
3763 term_default_cterm_fg = cterm_fg - 1;
3764 term_default_cterm_bg = cterm_bg - 1;
3765}
3766
3767/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003768 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003769 * Returns NULL when the buffer is not for a terminal window and logs a message
3770 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003771 */
3772 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003773term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003774{
3775 buf_T *buf;
3776
3777 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3778 ++emsg_off;
3779 buf = get_buf_tv(&argvars[0], FALSE);
3780 --emsg_off;
3781 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003782 {
3783 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003784 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003785 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003786 return buf;
3787}
3788
Bram Moolenaard96ff162018-02-18 22:13:29 +01003789 static int
3790same_color(VTermColor *a, VTermColor *b)
3791{
3792 return a->red == b->red
3793 && a->green == b->green
3794 && a->blue == b->blue
3795 && a->ansi_index == b->ansi_index;
3796}
3797
3798 static void
3799dump_term_color(FILE *fd, VTermColor *color)
3800{
3801 fprintf(fd, "%02x%02x%02x%d",
3802 (int)color->red, (int)color->green, (int)color->blue,
3803 (int)color->ansi_index);
3804}
3805
3806/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003807 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003808 *
3809 * Each screen cell in full is:
3810 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3811 * {characters} is a space for an empty cell
3812 * For a double-width character "+" is changed to "*" and the next cell is
3813 * skipped.
3814 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3815 * when "&" use the same as the previous cell.
3816 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3817 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3818 * {color-idx} is a number from 0 to 255
3819 *
3820 * Screen cell with same width, attributes and color as the previous one:
3821 * |{characters}
3822 *
3823 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3824 *
3825 * Repeating the previous screen cell:
3826 * @{count}
3827 */
3828 void
3829f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3830{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003831 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003832 term_T *term;
3833 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003834 int max_height = 0;
3835 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003836 stat_T st;
3837 FILE *fd;
3838 VTermPos pos;
3839 VTermScreen *screen;
3840 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003841 VTermState *state;
3842 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003843
3844 if (check_restricted() || check_secure())
3845 return;
3846 if (buf == NULL)
3847 return;
3848 term = buf->b_term;
3849
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003850 if (argvars[2].v_type != VAR_UNKNOWN)
3851 {
3852 dict_T *d;
3853
3854 if (argvars[2].v_type != VAR_DICT)
3855 {
3856 EMSG(_(e_dictreq));
3857 return;
3858 }
3859 d = argvars[2].vval.v_dict;
3860 if (d != NULL)
3861 {
3862 max_height = get_dict_number(d, (char_u *)"rows");
3863 max_width = get_dict_number(d, (char_u *)"columns");
3864 }
3865 }
3866
Bram Moolenaard96ff162018-02-18 22:13:29 +01003867 fname = get_tv_string_chk(&argvars[1]);
3868 if (fname == NULL)
3869 return;
3870 if (mch_stat((char *)fname, &st) >= 0)
3871 {
3872 EMSG2(_("E953: File exists: %s"), fname);
3873 return;
3874 }
3875
Bram Moolenaard96ff162018-02-18 22:13:29 +01003876 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3877 {
3878 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3879 return;
3880 }
3881
3882 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3883
3884 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003885 state = vterm_obtain_state(term->tl_vterm);
3886 vterm_state_get_cursorpos(state, &cursor_pos);
3887
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003888 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3889 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003890 {
3891 int repeat = 0;
3892
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003893 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3894 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003895 {
3896 VTermScreenCell cell;
3897 int same_attr;
3898 int same_chars = TRUE;
3899 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003900 int is_cursor_pos = (pos.col == cursor_pos.col
3901 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003902
3903 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3904 vim_memset(&cell, 0, sizeof(cell));
3905
3906 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3907 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003908 int c = cell.chars[i];
3909 int pc = prev_cell.chars[i];
3910
3911 /* For the first character NUL is the same as space. */
3912 if (i == 0)
3913 {
3914 c = (c == NUL) ? ' ' : c;
3915 pc = (pc == NUL) ? ' ' : pc;
3916 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003917 if (cell.chars[i] != prev_cell.chars[i])
3918 same_chars = FALSE;
3919 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3920 break;
3921 }
3922 same_attr = vtermAttr2hl(cell.attrs)
3923 == vtermAttr2hl(prev_cell.attrs)
3924 && same_color(&cell.fg, &prev_cell.fg)
3925 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003926 if (same_chars && cell.width == prev_cell.width && same_attr
3927 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003928 {
3929 ++repeat;
3930 }
3931 else
3932 {
3933 if (repeat > 0)
3934 {
3935 fprintf(fd, "@%d", repeat);
3936 repeat = 0;
3937 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003938 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003939
3940 if (cell.chars[0] == NUL)
3941 fputs(" ", fd);
3942 else
3943 {
3944 char_u charbuf[10];
3945 int len;
3946
3947 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3948 && cell.chars[i] != NUL; ++i)
3949 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02003950 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003951 fwrite(charbuf, len, 1, fd);
3952 }
3953 }
3954
3955 /* When only the characters differ we don't write anything, the
3956 * following "|", "@" or NL will indicate using the same
3957 * attributes. */
3958 if (cell.width != prev_cell.width || !same_attr)
3959 {
3960 if (cell.width == 2)
3961 {
3962 fputs("*", fd);
3963 ++pos.col;
3964 }
3965 else
3966 fputs("+", fd);
3967
3968 if (same_attr)
3969 {
3970 fputs("&", fd);
3971 }
3972 else
3973 {
3974 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3975 if (same_color(&cell.fg, &prev_cell.fg))
3976 fputs("&", fd);
3977 else
3978 {
3979 fputs("#", fd);
3980 dump_term_color(fd, &cell.fg);
3981 }
3982 if (same_color(&cell.bg, &prev_cell.bg))
3983 fputs("&", fd);
3984 else
3985 {
3986 fputs("#", fd);
3987 dump_term_color(fd, &cell.bg);
3988 }
3989 }
3990 }
3991
3992 prev_cell = cell;
3993 }
3994 }
3995 if (repeat > 0)
3996 fprintf(fd, "@%d", repeat);
3997 fputs("\n", fd);
3998 }
3999
4000 fclose(fd);
4001}
4002
4003/*
4004 * Called when a dump is corrupted. Put a breakpoint here when debugging.
4005 */
4006 static void
4007dump_is_corrupt(garray_T *gap)
4008{
4009 ga_concat(gap, (char_u *)"CORRUPT");
4010}
4011
4012 static void
4013append_cell(garray_T *gap, cellattr_T *cell)
4014{
4015 if (ga_grow(gap, 1) == OK)
4016 {
4017 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
4018 ++gap->ga_len;
4019 }
4020}
4021
4022/*
4023 * Read the dump file from "fd" and append lines to the current buffer.
4024 * Return the cell width of the longest line.
4025 */
4026 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01004027read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004028{
4029 int c;
4030 garray_T ga_text;
4031 garray_T ga_cell;
4032 char_u *prev_char = NULL;
4033 int attr = 0;
4034 cellattr_T cell;
4035 term_T *term = curbuf->b_term;
4036 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004037 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004038
4039 ga_init2(&ga_text, 1, 90);
4040 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
4041 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01004042 cursor_pos->row = -1;
4043 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004044
4045 c = fgetc(fd);
4046 for (;;)
4047 {
4048 if (c == EOF)
4049 break;
4050 if (c == '\n')
4051 {
4052 /* End of a line: append it to the buffer. */
4053 if (ga_text.ga_data == NULL)
4054 dump_is_corrupt(&ga_text);
4055 if (ga_grow(&term->tl_scrollback, 1) == OK)
4056 {
4057 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
4058 + term->tl_scrollback.ga_len;
4059
4060 if (max_cells < ga_cell.ga_len)
4061 max_cells = ga_cell.ga_len;
4062 line->sb_cols = ga_cell.ga_len;
4063 line->sb_cells = ga_cell.ga_data;
4064 line->sb_fill_attr = term->tl_default_color;
4065 ++term->tl_scrollback.ga_len;
4066 ga_init(&ga_cell);
4067
4068 ga_append(&ga_text, NUL);
4069 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4070 ga_text.ga_len, FALSE);
4071 }
4072 else
4073 ga_clear(&ga_cell);
4074 ga_text.ga_len = 0;
4075
4076 c = fgetc(fd);
4077 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004078 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004079 {
4080 int prev_len = ga_text.ga_len;
4081
Bram Moolenaar9271d052018-02-25 21:39:46 +01004082 if (c == '>')
4083 {
4084 if (cursor_pos->row != -1)
4085 dump_is_corrupt(&ga_text); /* duplicate cursor */
4086 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
4087 cursor_pos->col = ga_cell.ga_len;
4088 }
4089
Bram Moolenaard96ff162018-02-18 22:13:29 +01004090 /* normal character(s) followed by "+", "*", "|", "@" or NL */
4091 c = fgetc(fd);
4092 if (c != EOF)
4093 ga_append(&ga_text, c);
4094 for (;;)
4095 {
4096 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004097 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01004098 || c == EOF || c == '\n')
4099 break;
4100 ga_append(&ga_text, c);
4101 }
4102
4103 /* save the character for repeating it */
4104 vim_free(prev_char);
4105 if (ga_text.ga_data != NULL)
4106 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
4107 ga_text.ga_len - prev_len);
4108
Bram Moolenaar9271d052018-02-25 21:39:46 +01004109 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004110 {
4111 /* use all attributes from previous cell */
4112 }
4113 else if (c == '+' || c == '*')
4114 {
4115 int is_bg;
4116
4117 cell.width = c == '+' ? 1 : 2;
4118
4119 c = fgetc(fd);
4120 if (c == '&')
4121 {
4122 /* use same attr as previous cell */
4123 c = fgetc(fd);
4124 }
4125 else if (isdigit(c))
4126 {
4127 /* get the decimal attribute */
4128 attr = 0;
4129 while (isdigit(c))
4130 {
4131 attr = attr * 10 + (c - '0');
4132 c = fgetc(fd);
4133 }
4134 hl2vtermAttr(attr, &cell);
4135 }
4136 else
4137 dump_is_corrupt(&ga_text);
4138
4139 /* is_bg == 0: fg, is_bg == 1: bg */
4140 for (is_bg = 0; is_bg <= 1; ++is_bg)
4141 {
4142 if (c == '&')
4143 {
4144 /* use same color as previous cell */
4145 c = fgetc(fd);
4146 }
4147 else if (c == '#')
4148 {
4149 int red, green, blue, index = 0;
4150
4151 c = fgetc(fd);
4152 red = hex2nr(c);
4153 c = fgetc(fd);
4154 red = (red << 4) + hex2nr(c);
4155 c = fgetc(fd);
4156 green = hex2nr(c);
4157 c = fgetc(fd);
4158 green = (green << 4) + hex2nr(c);
4159 c = fgetc(fd);
4160 blue = hex2nr(c);
4161 c = fgetc(fd);
4162 blue = (blue << 4) + hex2nr(c);
4163 c = fgetc(fd);
4164 if (!isdigit(c))
4165 dump_is_corrupt(&ga_text);
4166 while (isdigit(c))
4167 {
4168 index = index * 10 + (c - '0');
4169 c = fgetc(fd);
4170 }
4171
4172 if (is_bg)
4173 {
4174 cell.bg.red = red;
4175 cell.bg.green = green;
4176 cell.bg.blue = blue;
4177 cell.bg.ansi_index = index;
4178 }
4179 else
4180 {
4181 cell.fg.red = red;
4182 cell.fg.green = green;
4183 cell.fg.blue = blue;
4184 cell.fg.ansi_index = index;
4185 }
4186 }
4187 else
4188 dump_is_corrupt(&ga_text);
4189 }
4190 }
4191 else
4192 dump_is_corrupt(&ga_text);
4193
4194 append_cell(&ga_cell, &cell);
4195 }
4196 else if (c == '@')
4197 {
4198 if (prev_char == NULL)
4199 dump_is_corrupt(&ga_text);
4200 else
4201 {
4202 int count = 0;
4203
4204 /* repeat previous character, get the count */
4205 for (;;)
4206 {
4207 c = fgetc(fd);
4208 if (!isdigit(c))
4209 break;
4210 count = count * 10 + (c - '0');
4211 }
4212
4213 while (count-- > 0)
4214 {
4215 ga_concat(&ga_text, prev_char);
4216 append_cell(&ga_cell, &cell);
4217 }
4218 }
4219 }
4220 else
4221 {
4222 dump_is_corrupt(&ga_text);
4223 c = fgetc(fd);
4224 }
4225 }
4226
4227 if (ga_text.ga_len > 0)
4228 {
4229 /* trailing characters after last NL */
4230 dump_is_corrupt(&ga_text);
4231 ga_append(&ga_text, NUL);
4232 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4233 ga_text.ga_len, FALSE);
4234 }
4235
4236 ga_clear(&ga_text);
4237 vim_free(prev_char);
4238
4239 return max_cells;
4240}
4241
4242/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004243 * Return an allocated string with at least "text_width" "=" characters and
4244 * "fname" inserted in the middle.
4245 */
4246 static char_u *
4247get_separator(int text_width, char_u *fname)
4248{
4249 int width = MAX(text_width, curwin->w_width);
4250 char_u *textline;
4251 int fname_size;
4252 char_u *p = fname;
4253 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004254 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004255
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004256 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004257 if (textline == NULL)
4258 return NULL;
4259
4260 fname_size = vim_strsize(fname);
4261 if (fname_size < width - 8)
4262 {
4263 /* enough room, don't use the full window width */
4264 width = MAX(text_width, fname_size + 8);
4265 }
4266 else if (fname_size > width - 8)
4267 {
4268 /* full name doesn't fit, use only the tail */
4269 p = gettail(fname);
4270 fname_size = vim_strsize(p);
4271 }
4272 /* skip characters until the name fits */
4273 while (fname_size > width - 8)
4274 {
4275 p += (*mb_ptr2len)(p);
4276 fname_size = vim_strsize(p);
4277 }
4278
4279 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4280 textline[i] = '=';
4281 textline[i++] = ' ';
4282
4283 STRCPY(textline + i, p);
4284 off = STRLEN(textline);
4285 textline[off] = ' ';
4286 for (i = 1; i < (width - fname_size) / 2; ++i)
4287 textline[off + i] = '=';
4288 textline[off + i] = NUL;
4289
4290 return textline;
4291}
4292
4293/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004294 * Common for "term_dumpdiff()" and "term_dumpload()".
4295 */
4296 static void
4297term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4298{
4299 jobopt_T opt;
4300 buf_T *buf;
4301 char_u buf1[NUMBUFLEN];
4302 char_u buf2[NUMBUFLEN];
4303 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004304 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004305 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004306 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004307 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004308 char_u *textline = NULL;
4309
4310 /* First open the files. If this fails bail out. */
4311 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
4312 if (do_diff)
4313 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
4314 if (fname1 == NULL || (do_diff && fname2 == NULL))
4315 {
4316 EMSG(_(e_invarg));
4317 return;
4318 }
4319 fd1 = mch_fopen((char *)fname1, READBIN);
4320 if (fd1 == NULL)
4321 {
4322 EMSG2(_(e_notread), fname1);
4323 return;
4324 }
4325 if (do_diff)
4326 {
4327 fd2 = mch_fopen((char *)fname2, READBIN);
4328 if (fd2 == NULL)
4329 {
4330 fclose(fd1);
4331 EMSG2(_(e_notread), fname2);
4332 return;
4333 }
4334 }
4335
4336 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004337 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4338 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4339 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4340 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4341 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004342
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004343 if (opt.jo_term_name == NULL)
4344 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004345 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004346
Bram Moolenaarb571c632018-03-21 22:27:59 +01004347 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004348 if (fname_tofree != NULL)
4349 {
4350 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4351 opt.jo_term_name = fname_tofree;
4352 }
4353 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004354
Bram Moolenaar13568252018-03-16 20:46:58 +01004355 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004356 if (buf != NULL && buf->b_term != NULL)
4357 {
4358 int i;
4359 linenr_T bot_lnum;
4360 linenr_T lnum;
4361 term_T *term = buf->b_term;
4362 int width;
4363 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004364 VTermPos cursor_pos1;
4365 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004366
Bram Moolenaar52acb112018-03-18 19:20:22 +01004367 init_default_colors(term);
4368
Bram Moolenaard96ff162018-02-18 22:13:29 +01004369 rettv->vval.v_number = buf->b_fnum;
4370
4371 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004372 width = read_dump_file(fd1, &cursor_pos1);
4373
4374 /* position the cursor */
4375 if (cursor_pos1.row >= 0)
4376 {
4377 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4378 coladvance(cursor_pos1.col);
4379 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004380
4381 /* Delete the empty line that was in the empty buffer. */
4382 ml_delete(1, FALSE);
4383
4384 /* For term_dumpload() we are done here. */
4385 if (!do_diff)
4386 goto theend;
4387
4388 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4389
Bram Moolenaar4a696342018-04-05 18:45:26 +02004390 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004391 if (textline == NULL)
4392 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004393 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4394 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4395 vim_free(textline);
4396
4397 textline = get_separator(width, fname2);
4398 if (textline == NULL)
4399 goto theend;
4400 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4401 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004402 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004403
4404 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004405 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004406 if (width2 > width)
4407 {
4408 vim_free(textline);
4409 textline = alloc(width2 + 1);
4410 if (textline == NULL)
4411 goto theend;
4412 width = width2;
4413 textline[width] = NUL;
4414 }
4415 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4416
4417 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4418 {
4419 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4420 {
4421 /* bottom part has fewer rows, fill with "-" */
4422 for (i = 0; i < width; ++i)
4423 textline[i] = '-';
4424 }
4425 else
4426 {
4427 char_u *line1;
4428 char_u *line2;
4429 char_u *p1;
4430 char_u *p2;
4431 int col;
4432 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4433 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4434 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4435 ->sb_cells;
4436
4437 /* Make a copy, getting the second line will invalidate it. */
4438 line1 = vim_strsave(ml_get(lnum));
4439 if (line1 == NULL)
4440 break;
4441 p1 = line1;
4442
4443 line2 = ml_get(lnum + bot_lnum);
4444 p2 = line2;
4445 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4446 {
4447 int len1 = utfc_ptr2len(p1);
4448 int len2 = utfc_ptr2len(p2);
4449
4450 textline[col] = ' ';
4451 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004452 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004453 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004454 else if (lnum == cursor_pos1.row + 1
4455 && col == cursor_pos1.col
4456 && (cursor_pos1.row != cursor_pos2.row
4457 || cursor_pos1.col != cursor_pos2.col))
4458 /* cursor in first but not in second */
4459 textline[col] = '>';
4460 else if (lnum == cursor_pos2.row + 1
4461 && col == cursor_pos2.col
4462 && (cursor_pos1.row != cursor_pos2.row
4463 || cursor_pos1.col != cursor_pos2.col))
4464 /* cursor in second but not in first */
4465 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004466 else if (cellattr1 != NULL && cellattr2 != NULL)
4467 {
4468 if ((cellattr1 + col)->width
4469 != (cellattr2 + col)->width)
4470 textline[col] = 'w';
4471 else if (!same_color(&(cellattr1 + col)->fg,
4472 &(cellattr2 + col)->fg))
4473 textline[col] = 'f';
4474 else if (!same_color(&(cellattr1 + col)->bg,
4475 &(cellattr2 + col)->bg))
4476 textline[col] = 'b';
4477 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4478 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4479 textline[col] = 'a';
4480 }
4481 p1 += len1;
4482 p2 += len2;
4483 /* TODO: handle different width */
4484 }
4485 vim_free(line1);
4486
4487 while (col < width)
4488 {
4489 if (*p1 == NUL && *p2 == NUL)
4490 textline[col] = '?';
4491 else if (*p1 == NUL)
4492 {
4493 textline[col] = '+';
4494 p2 += utfc_ptr2len(p2);
4495 }
4496 else
4497 {
4498 textline[col] = '-';
4499 p1 += utfc_ptr2len(p1);
4500 }
4501 ++col;
4502 }
4503 }
4504 if (add_empty_scrollback(term, &term->tl_default_color,
4505 term->tl_top_diff_rows) == OK)
4506 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4507 ++bot_lnum;
4508 }
4509
4510 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4511 {
4512 /* bottom part has more rows, fill with "+" */
4513 for (i = 0; i < width; ++i)
4514 textline[i] = '+';
4515 if (add_empty_scrollback(term, &term->tl_default_color,
4516 term->tl_top_diff_rows) == OK)
4517 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4518 ++lnum;
4519 ++bot_lnum;
4520 }
4521
4522 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004523
4524 /* looks better without wrapping */
4525 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004526 }
4527
4528theend:
4529 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004530 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004531 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004532 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004533 fclose(fd2);
4534}
4535
4536/*
4537 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4538 * bottom files.
4539 * Return FAIL when this is not possible.
4540 */
4541 int
4542term_swap_diff()
4543{
4544 term_T *term = curbuf->b_term;
4545 linenr_T line_count;
4546 linenr_T top_rows;
4547 linenr_T bot_rows;
4548 linenr_T bot_start;
4549 linenr_T lnum;
4550 char_u *p;
4551 sb_line_T *sb_line;
4552
4553 if (term == NULL
4554 || !term_is_finished(curbuf)
4555 || term->tl_top_diff_rows == 0
4556 || term->tl_scrollback.ga_len == 0)
4557 return FAIL;
4558
4559 line_count = curbuf->b_ml.ml_line_count;
4560 top_rows = term->tl_top_diff_rows;
4561 bot_rows = term->tl_bot_diff_rows;
4562 bot_start = line_count - bot_rows;
4563 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4564
4565 /* move lines from top to above the bottom part */
4566 for (lnum = 1; lnum <= top_rows; ++lnum)
4567 {
4568 p = vim_strsave(ml_get(1));
4569 if (p == NULL)
4570 return OK;
4571 ml_append(bot_start, p, 0, FALSE);
4572 ml_delete(1, FALSE);
4573 vim_free(p);
4574 }
4575
4576 /* move lines from bottom to the top */
4577 for (lnum = 1; lnum <= bot_rows; ++lnum)
4578 {
4579 p = vim_strsave(ml_get(bot_start + lnum));
4580 if (p == NULL)
4581 return OK;
4582 ml_delete(bot_start + lnum, FALSE);
4583 ml_append(lnum - 1, p, 0, FALSE);
4584 vim_free(p);
4585 }
4586
4587 if (top_rows == bot_rows)
4588 {
4589 /* rows counts are equal, can swap cell properties */
4590 for (lnum = 0; lnum < top_rows; ++lnum)
4591 {
4592 sb_line_T temp;
4593
4594 temp = *(sb_line + lnum);
4595 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4596 *(sb_line + bot_start + lnum) = temp;
4597 }
4598 }
4599 else
4600 {
4601 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4602 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4603
4604 /* need to copy cell properties into temp memory */
4605 if (temp != NULL)
4606 {
4607 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4608 mch_memmove(term->tl_scrollback.ga_data,
4609 temp + bot_start,
4610 sizeof(sb_line_T) * bot_rows);
4611 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4612 temp + top_rows,
4613 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4614 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4615 + line_count - top_rows,
4616 temp,
4617 sizeof(sb_line_T) * top_rows);
4618 vim_free(temp);
4619 }
4620 }
4621
4622 term->tl_top_diff_rows = bot_rows;
4623 term->tl_bot_diff_rows = top_rows;
4624
4625 update_screen(NOT_VALID);
4626 return OK;
4627}
4628
4629/*
4630 * "term_dumpdiff(filename, filename, options)" function
4631 */
4632 void
4633f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4634{
4635 term_load_dump(argvars, rettv, TRUE);
4636}
4637
4638/*
4639 * "term_dumpload(filename, options)" function
4640 */
4641 void
4642f_term_dumpload(typval_T *argvars, typval_T *rettv)
4643{
4644 term_load_dump(argvars, rettv, FALSE);
4645}
4646
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004647/*
4648 * "term_getaltscreen(buf)" function
4649 */
4650 void
4651f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4652{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004653 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004654
4655 if (buf == NULL)
4656 return;
4657 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4658}
4659
4660/*
4661 * "term_getattr(attr, name)" function
4662 */
4663 void
4664f_term_getattr(typval_T *argvars, typval_T *rettv)
4665{
4666 int attr;
4667 size_t i;
4668 char_u *name;
4669
4670 static struct {
4671 char *name;
4672 int attr;
4673 } attrs[] = {
4674 {"bold", HL_BOLD},
4675 {"italic", HL_ITALIC},
4676 {"underline", HL_UNDERLINE},
4677 {"strike", HL_STRIKETHROUGH},
4678 {"reverse", HL_INVERSE},
4679 };
4680
4681 attr = get_tv_number(&argvars[0]);
4682 name = get_tv_string_chk(&argvars[1]);
4683 if (name == NULL)
4684 return;
4685
4686 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4687 if (STRCMP(name, attrs[i].name) == 0)
4688 {
4689 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4690 break;
4691 }
4692}
4693
4694/*
4695 * "term_getcursor(buf)" function
4696 */
4697 void
4698f_term_getcursor(typval_T *argvars, typval_T *rettv)
4699{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004700 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004701 term_T *term;
4702 list_T *l;
4703 dict_T *d;
4704
4705 if (rettv_list_alloc(rettv) == FAIL)
4706 return;
4707 if (buf == NULL)
4708 return;
4709 term = buf->b_term;
4710
4711 l = rettv->vval.v_list;
4712 list_append_number(l, term->tl_cursor_pos.row + 1);
4713 list_append_number(l, term->tl_cursor_pos.col + 1);
4714
4715 d = dict_alloc();
4716 if (d != NULL)
4717 {
4718 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4719 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4720 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4721 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4722 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4723 ? (char_u *)"" : term->tl_cursor_color);
4724 list_append_dict(l, d);
4725 }
4726}
4727
4728/*
4729 * "term_getjob(buf)" function
4730 */
4731 void
4732f_term_getjob(typval_T *argvars, typval_T *rettv)
4733{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004734 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004735
4736 rettv->v_type = VAR_JOB;
4737 rettv->vval.v_job = NULL;
4738 if (buf == NULL)
4739 return;
4740
4741 rettv->vval.v_job = buf->b_term->tl_job;
4742 if (rettv->vval.v_job != NULL)
4743 ++rettv->vval.v_job->jv_refcount;
4744}
4745
4746 static int
4747get_row_number(typval_T *tv, term_T *term)
4748{
4749 if (tv->v_type == VAR_STRING
4750 && tv->vval.v_string != NULL
4751 && STRCMP(tv->vval.v_string, ".") == 0)
4752 return term->tl_cursor_pos.row;
4753 return (int)get_tv_number(tv) - 1;
4754}
4755
4756/*
4757 * "term_getline(buf, row)" function
4758 */
4759 void
4760f_term_getline(typval_T *argvars, typval_T *rettv)
4761{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004762 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004763 term_T *term;
4764 int row;
4765
4766 rettv->v_type = VAR_STRING;
4767 if (buf == NULL)
4768 return;
4769 term = buf->b_term;
4770 row = get_row_number(&argvars[1], term);
4771
4772 if (term->tl_vterm == NULL)
4773 {
4774 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4775
4776 /* vterm is finished, get the text from the buffer */
4777 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4778 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4779 }
4780 else
4781 {
4782 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4783 VTermRect rect;
4784 int len;
4785 char_u *p;
4786
4787 if (row < 0 || row >= term->tl_rows)
4788 return;
4789 len = term->tl_cols * MB_MAXBYTES + 1;
4790 p = alloc(len);
4791 if (p == NULL)
4792 return;
4793 rettv->vval.v_string = p;
4794
4795 rect.start_col = 0;
4796 rect.end_col = term->tl_cols;
4797 rect.start_row = row;
4798 rect.end_row = row + 1;
4799 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4800 }
4801}
4802
4803/*
4804 * "term_getscrolled(buf)" function
4805 */
4806 void
4807f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4808{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004809 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004810
4811 if (buf == NULL)
4812 return;
4813 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4814}
4815
4816/*
4817 * "term_getsize(buf)" function
4818 */
4819 void
4820f_term_getsize(typval_T *argvars, typval_T *rettv)
4821{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004822 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004823 list_T *l;
4824
4825 if (rettv_list_alloc(rettv) == FAIL)
4826 return;
4827 if (buf == NULL)
4828 return;
4829
4830 l = rettv->vval.v_list;
4831 list_append_number(l, buf->b_term->tl_rows);
4832 list_append_number(l, buf->b_term->tl_cols);
4833}
4834
4835/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004836 * "term_setsize(buf, rows, cols)" function
4837 */
4838 void
4839f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4840{
4841 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4842 term_T *term;
4843 varnumber_T rows, cols;
4844
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004845 if (buf == NULL)
4846 {
4847 EMSG(_("E955: Not a terminal buffer"));
4848 return;
4849 }
4850 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004851 return;
4852 term = buf->b_term;
4853 rows = get_tv_number(&argvars[1]);
4854 rows = rows <= 0 ? term->tl_rows : rows;
4855 cols = get_tv_number(&argvars[2]);
4856 cols = cols <= 0 ? term->tl_cols : cols;
4857 vterm_set_size(term->tl_vterm, rows, cols);
4858 /* handle_resize() will resize the windows */
4859
4860 /* Get and remember the size we ended up with. Update the pty. */
4861 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
4862 term_report_winsize(term, term->tl_rows, term->tl_cols);
4863}
4864
4865/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004866 * "term_getstatus(buf)" function
4867 */
4868 void
4869f_term_getstatus(typval_T *argvars, typval_T *rettv)
4870{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004871 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004872 term_T *term;
4873 char_u val[100];
4874
4875 rettv->v_type = VAR_STRING;
4876 if (buf == NULL)
4877 return;
4878 term = buf->b_term;
4879
4880 if (term_job_running(term))
4881 STRCPY(val, "running");
4882 else
4883 STRCPY(val, "finished");
4884 if (term->tl_normal_mode)
4885 STRCAT(val, ",normal");
4886 rettv->vval.v_string = vim_strsave(val);
4887}
4888
4889/*
4890 * "term_gettitle(buf)" function
4891 */
4892 void
4893f_term_gettitle(typval_T *argvars, typval_T *rettv)
4894{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004895 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004896
4897 rettv->v_type = VAR_STRING;
4898 if (buf == NULL)
4899 return;
4900
4901 if (buf->b_term->tl_title != NULL)
4902 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4903}
4904
4905/*
4906 * "term_gettty(buf)" function
4907 */
4908 void
4909f_term_gettty(typval_T *argvars, typval_T *rettv)
4910{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004911 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar9b50f362018-05-07 20:10:17 +02004912 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004913 int num = 0;
4914
4915 rettv->v_type = VAR_STRING;
4916 if (buf == NULL)
4917 return;
4918 if (argvars[1].v_type != VAR_UNKNOWN)
4919 num = get_tv_number(&argvars[1]);
4920
4921 switch (num)
4922 {
4923 case 0:
4924 if (buf->b_term->tl_job != NULL)
4925 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004926 break;
4927 case 1:
4928 if (buf->b_term->tl_job != NULL)
4929 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004930 break;
4931 default:
4932 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4933 return;
4934 }
4935 if (p != NULL)
4936 rettv->vval.v_string = vim_strsave(p);
4937}
4938
4939/*
4940 * "term_list()" function
4941 */
4942 void
4943f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4944{
4945 term_T *tp;
4946 list_T *l;
4947
4948 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4949 return;
4950
4951 l = rettv->vval.v_list;
4952 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4953 if (tp != NULL && tp->tl_buffer != NULL)
4954 if (list_append_number(l,
4955 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4956 return;
4957}
4958
4959/*
4960 * "term_scrape(buf, row)" function
4961 */
4962 void
4963f_term_scrape(typval_T *argvars, typval_T *rettv)
4964{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004965 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004966 VTermScreen *screen = NULL;
4967 VTermPos pos;
4968 list_T *l;
4969 term_T *term;
4970 char_u *p;
4971 sb_line_T *line;
4972
4973 if (rettv_list_alloc(rettv) == FAIL)
4974 return;
4975 if (buf == NULL)
4976 return;
4977 term = buf->b_term;
4978
4979 l = rettv->vval.v_list;
4980 pos.row = get_row_number(&argvars[1], term);
4981
4982 if (term->tl_vterm != NULL)
4983 {
4984 screen = vterm_obtain_screen(term->tl_vterm);
4985 p = NULL;
4986 line = NULL;
4987 }
4988 else
4989 {
4990 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4991
4992 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4993 return;
4994 p = ml_get_buf(buf, lnum + 1, FALSE);
4995 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4996 }
4997
4998 for (pos.col = 0; pos.col < term->tl_cols; )
4999 {
5000 dict_T *dcell;
5001 int width;
5002 VTermScreenCellAttrs attrs;
5003 VTermColor fg, bg;
5004 char_u rgb[8];
5005 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
5006 int off = 0;
5007 int i;
5008
5009 if (screen == NULL)
5010 {
5011 cellattr_T *cellattr;
5012 int len;
5013
5014 /* vterm has finished, get the cell from scrollback */
5015 if (pos.col >= line->sb_cols)
5016 break;
5017 cellattr = line->sb_cells + pos.col;
5018 width = cellattr->width;
5019 attrs = cellattr->attrs;
5020 fg = cellattr->fg;
5021 bg = cellattr->bg;
5022 len = MB_PTR2LEN(p);
5023 mch_memmove(mbs, p, len);
5024 mbs[len] = NUL;
5025 p += len;
5026 }
5027 else
5028 {
5029 VTermScreenCell cell;
5030 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
5031 break;
5032 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5033 {
5034 if (cell.chars[i] == 0)
5035 break;
5036 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
5037 }
5038 mbs[off] = NUL;
5039 width = cell.width;
5040 attrs = cell.attrs;
5041 fg = cell.fg;
5042 bg = cell.bg;
5043 }
5044 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01005045 if (dcell == NULL)
5046 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005047 list_append_dict(l, dcell);
5048
5049 dict_add_nr_str(dcell, "chars", 0, mbs);
5050
5051 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5052 fg.red, fg.green, fg.blue);
5053 dict_add_nr_str(dcell, "fg", 0, rgb);
5054 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5055 bg.red, bg.green, bg.blue);
5056 dict_add_nr_str(dcell, "bg", 0, rgb);
5057
5058 dict_add_nr_str(dcell, "attr",
5059 cell2attr(attrs, fg, bg), NULL);
5060 dict_add_nr_str(dcell, "width", width, NULL);
5061
5062 ++pos.col;
5063 if (width == 2)
5064 ++pos.col;
5065 }
5066}
5067
5068/*
5069 * "term_sendkeys(buf, keys)" function
5070 */
5071 void
5072f_term_sendkeys(typval_T *argvars, typval_T *rettv)
5073{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005074 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005075 char_u *msg;
5076 term_T *term;
5077
5078 rettv->v_type = VAR_UNKNOWN;
5079 if (buf == NULL)
5080 return;
5081
5082 msg = get_tv_string_chk(&argvars[1]);
5083 if (msg == NULL)
5084 return;
5085 term = buf->b_term;
5086 if (term->tl_vterm == NULL)
5087 return;
5088
5089 while (*msg != NUL)
5090 {
5091 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02005092 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005093 }
5094}
5095
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005096#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
5097/*
5098 * "term_getansicolors(buf)" function
5099 */
5100 void
5101f_term_getansicolors(typval_T *argvars, typval_T *rettv)
5102{
5103 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
5104 term_T *term;
5105 VTermState *state;
5106 VTermColor color;
5107 char_u hexbuf[10];
5108 int index;
5109 list_T *list;
5110
5111 if (rettv_list_alloc(rettv) == FAIL)
5112 return;
5113
5114 if (buf == NULL)
5115 return;
5116 term = buf->b_term;
5117 if (term->tl_vterm == NULL)
5118 return;
5119
5120 list = rettv->vval.v_list;
5121 state = vterm_obtain_state(term->tl_vterm);
5122 for (index = 0; index < 16; index++)
5123 {
5124 vterm_state_get_palette_color(state, index, &color);
5125 sprintf((char *)hexbuf, "#%02x%02x%02x",
5126 color.red, color.green, color.blue);
5127 if (list_append_string(list, hexbuf, 7) == FAIL)
5128 return;
5129 }
5130}
5131
5132/*
5133 * "term_setansicolors(buf, list)" function
5134 */
5135 void
5136f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
5137{
5138 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
5139 term_T *term;
5140
5141 if (buf == NULL)
5142 return;
5143 term = buf->b_term;
5144 if (term->tl_vterm == NULL)
5145 return;
5146
5147 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
5148 {
5149 EMSG(_(e_listreq));
5150 return;
5151 }
5152
5153 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
5154 EMSG(_(e_invarg));
5155}
5156#endif
5157
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005158/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005159 * "term_setrestore(buf, command)" function
5160 */
5161 void
5162f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5163{
5164#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005165 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005166 term_T *term;
5167 char_u *cmd;
5168
5169 if (buf == NULL)
5170 return;
5171 term = buf->b_term;
5172 vim_free(term->tl_command);
5173 cmd = get_tv_string_chk(&argvars[1]);
5174 if (cmd != NULL)
5175 term->tl_command = vim_strsave(cmd);
5176 else
5177 term->tl_command = NULL;
5178#endif
5179}
5180
5181/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005182 * "term_setkill(buf, how)" function
5183 */
5184 void
5185f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5186{
5187 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5188 term_T *term;
5189 char_u *how;
5190
5191 if (buf == NULL)
5192 return;
5193 term = buf->b_term;
5194 vim_free(term->tl_kill);
5195 how = get_tv_string_chk(&argvars[1]);
5196 if (how != NULL)
5197 term->tl_kill = vim_strsave(how);
5198 else
5199 term->tl_kill = NULL;
5200}
5201
5202/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005203 * "term_start(command, options)" function
5204 */
5205 void
5206f_term_start(typval_T *argvars, typval_T *rettv)
5207{
5208 jobopt_T opt;
5209 buf_T *buf;
5210
5211 init_job_options(&opt);
5212 if (argvars[1].v_type != VAR_UNKNOWN
5213 && get_job_options(&argvars[1], &opt,
5214 JO_TIMEOUT_ALL + JO_STOPONEXIT
5215 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5216 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5217 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5218 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005219 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005220 + JO2_NORESTORE + JO2_TERM_KILL
5221 + JO2_ANSI_COLORS) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005222 return;
5223
Bram Moolenaar13568252018-03-16 20:46:58 +01005224 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005225
5226 if (buf != NULL && buf->b_term != NULL)
5227 rettv->vval.v_number = buf->b_fnum;
5228}
5229
5230/*
5231 * "term_wait" function
5232 */
5233 void
5234f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5235{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005236 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005237
5238 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005239 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005240 if (buf->b_term->tl_job == NULL)
5241 {
5242 ch_log(NULL, "term_wait(): no job to wait for");
5243 return;
5244 }
5245 if (buf->b_term->tl_job->jv_channel == NULL)
5246 /* channel is closed, nothing to do */
5247 return;
5248
5249 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005250 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005251 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5252 {
5253 /* The job is dead, keep reading channel I/O until the channel is
5254 * closed. buf->b_term may become NULL if the terminal was closed while
5255 * waiting. */
5256 ch_log(NULL, "term_wait(): waiting for channel to close");
5257 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5258 {
5259 mch_check_messages();
5260 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01005261 if (!buf_valid(buf))
5262 /* If the terminal is closed when the channel is closed the
5263 * buffer disappears. */
5264 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005265 ui_delay(10L, FALSE);
5266 }
5267 mch_check_messages();
5268 parse_queued_messages();
5269 }
5270 else
5271 {
5272 long wait = 10L;
5273
5274 mch_check_messages();
5275 parse_queued_messages();
5276
5277 /* Wait for some time for any channel I/O. */
5278 if (argvars[1].v_type != VAR_UNKNOWN)
5279 wait = get_tv_number(&argvars[1]);
5280 ui_delay(wait, TRUE);
5281 mch_check_messages();
5282
5283 /* Flushing messages on channels is hopefully sufficient.
5284 * TODO: is there a better way? */
5285 parse_queued_messages();
5286 }
5287}
5288
5289/*
5290 * Called when a channel has sent all the lines to a terminal.
5291 * Send a CTRL-D to mark the end of the text.
5292 */
5293 void
5294term_send_eof(channel_T *ch)
5295{
5296 term_T *term;
5297
5298 for (term = first_term; term != NULL; term = term->tl_next)
5299 if (term->tl_job == ch->ch_job)
5300 {
5301 if (term->tl_eof_chars != NULL)
5302 {
5303 channel_send(ch, PART_IN, term->tl_eof_chars,
5304 (int)STRLEN(term->tl_eof_chars), NULL);
5305 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5306 }
5307# ifdef WIN3264
5308 else
5309 /* Default: CTRL-D */
5310 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5311# endif
5312 }
5313}
5314
5315# if defined(WIN3264) || defined(PROTO)
5316
5317/**************************************
5318 * 2. MS-Windows implementation.
5319 */
5320
5321# ifndef PROTO
5322
5323#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5324#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005325#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005326
5327void* (*winpty_config_new)(UINT64, void*);
5328void* (*winpty_open)(void*, void*);
5329void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5330BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5331void (*winpty_config_set_mouse_mode)(void*, int);
5332void (*winpty_config_set_initial_size)(void*, int, int);
5333LPCWSTR (*winpty_conin_name)(void*);
5334LPCWSTR (*winpty_conout_name)(void*);
5335LPCWSTR (*winpty_conerr_name)(void*);
5336void (*winpty_free)(void*);
5337void (*winpty_config_free)(void*);
5338void (*winpty_spawn_config_free)(void*);
5339void (*winpty_error_free)(void*);
5340LPCWSTR (*winpty_error_msg)(void*);
5341BOOL (*winpty_set_size)(void*, int, int, void*);
5342HANDLE (*winpty_agent_process)(void*);
5343
5344#define WINPTY_DLL "winpty.dll"
5345
5346static HINSTANCE hWinPtyDLL = NULL;
5347# endif
5348
5349 static int
5350dyn_winpty_init(int verbose)
5351{
5352 int i;
5353 static struct
5354 {
5355 char *name;
5356 FARPROC *ptr;
5357 } winpty_entry[] =
5358 {
5359 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5360 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5361 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5362 {"winpty_config_set_mouse_mode",
5363 (FARPROC*)&winpty_config_set_mouse_mode},
5364 {"winpty_config_set_initial_size",
5365 (FARPROC*)&winpty_config_set_initial_size},
5366 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5367 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5368 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5369 {"winpty_free", (FARPROC*)&winpty_free},
5370 {"winpty_open", (FARPROC*)&winpty_open},
5371 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5372 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5373 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5374 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5375 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5376 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5377 {NULL, NULL}
5378 };
5379
5380 /* No need to initialize twice. */
5381 if (hWinPtyDLL)
5382 return OK;
5383 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5384 * winpty.dll. */
5385 if (*p_winptydll != NUL)
5386 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5387 if (!hWinPtyDLL)
5388 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5389 if (!hWinPtyDLL)
5390 {
5391 if (verbose)
5392 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
5393 : (char_u *)WINPTY_DLL);
5394 return FAIL;
5395 }
5396 for (i = 0; winpty_entry[i].name != NULL
5397 && winpty_entry[i].ptr != NULL; ++i)
5398 {
5399 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5400 winpty_entry[i].name)) == NULL)
5401 {
5402 if (verbose)
5403 EMSG2(_(e_loadfunc), winpty_entry[i].name);
5404 return FAIL;
5405 }
5406 }
5407
5408 return OK;
5409}
5410
5411/*
5412 * Create a new terminal of "rows" by "cols" cells.
5413 * Store a reference in "term".
5414 * Return OK or FAIL.
5415 */
5416 static int
5417term_and_job_init(
5418 term_T *term,
5419 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005420 char **argv UNUSED,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005421 jobopt_T *opt,
5422 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005423{
5424 WCHAR *cmd_wchar = NULL;
5425 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005426 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005427 channel_T *channel = NULL;
5428 job_T *job = NULL;
5429 DWORD error;
5430 HANDLE jo = NULL;
5431 HANDLE child_process_handle;
5432 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005433 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005434 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005435 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005436 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005437
5438 if (dyn_winpty_init(TRUE) == FAIL)
5439 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005440 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5441 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005442
5443 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005444 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005445 cmd = argvar->vval.v_string;
5446 }
5447 else if (argvar->v_type == VAR_LIST)
5448 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005449 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005450 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005451 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005452 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005453 if (cmd == NULL || *cmd == NUL)
5454 {
5455 EMSG(_(e_invarg));
5456 goto failed;
5457 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005458
5459 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005460 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005461 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005462 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005463 if (opt->jo_cwd != NULL)
5464 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005465
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005466 win32_build_env(opt->jo_env, &ga_env, TRUE);
5467 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005468
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005469 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5470 if (term->tl_winpty_config == NULL)
5471 goto failed;
5472
5473 winpty_config_set_mouse_mode(term->tl_winpty_config,
5474 WINPTY_MOUSE_MODE_FORCE);
5475 winpty_config_set_initial_size(term->tl_winpty_config,
5476 term->tl_cols, term->tl_rows);
5477 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5478 if (term->tl_winpty == NULL)
5479 goto failed;
5480
5481 spawn_config = winpty_spawn_config_new(
5482 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5483 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5484 NULL,
5485 cmd_wchar,
5486 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005487 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005488 &winpty_err);
5489 if (spawn_config == NULL)
5490 goto failed;
5491
5492 channel = add_channel();
5493 if (channel == NULL)
5494 goto failed;
5495
5496 job = job_alloc();
5497 if (job == NULL)
5498 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02005499 if (argvar->v_type == VAR_STRING)
5500 {
5501 int argc;
5502
5503 build_argv_from_string(cmd, &job->jv_argv, &argc);
5504 }
5505 else
5506 {
5507 int argc;
5508
5509 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5510 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005511
5512 if (opt->jo_set & JO_IN_BUF)
5513 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5514
5515 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5516 &child_thread_handle, &error, &winpty_err))
5517 goto failed;
5518
5519 channel_set_pipes(channel,
5520 (sock_T)CreateFileW(
5521 winpty_conin_name(term->tl_winpty),
5522 GENERIC_WRITE, 0, NULL,
5523 OPEN_EXISTING, 0, NULL),
5524 (sock_T)CreateFileW(
5525 winpty_conout_name(term->tl_winpty),
5526 GENERIC_READ, 0, NULL,
5527 OPEN_EXISTING, 0, NULL),
5528 (sock_T)CreateFileW(
5529 winpty_conerr_name(term->tl_winpty),
5530 GENERIC_READ, 0, NULL,
5531 OPEN_EXISTING, 0, NULL));
5532
5533 /* Write lines with CR instead of NL. */
5534 channel->ch_write_text_mode = TRUE;
5535
5536 jo = CreateJobObject(NULL, NULL);
5537 if (jo == NULL)
5538 goto failed;
5539
5540 if (!AssignProcessToJobObject(jo, child_process_handle))
5541 {
5542 /* Failed, switch the way to terminate process with TerminateProcess. */
5543 CloseHandle(jo);
5544 jo = NULL;
5545 }
5546
5547 winpty_spawn_config_free(spawn_config);
5548 vim_free(cmd_wchar);
5549 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005550 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005551
5552 create_vterm(term, term->tl_rows, term->tl_cols);
5553
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005554#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5555 if (opt->jo_set2 & JO2_ANSI_COLORS)
5556 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5557 else
5558 init_vterm_ansi_colors(term->tl_vterm);
5559#endif
5560
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005561 channel_set_job(channel, job, opt);
5562 job_set_options(job, opt);
5563
5564 job->jv_channel = channel;
5565 job->jv_proc_info.hProcess = child_process_handle;
5566 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5567 job->jv_job_object = jo;
5568 job->jv_status = JOB_STARTED;
5569 job->jv_tty_in = utf16_to_enc(
5570 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5571 job->jv_tty_out = utf16_to_enc(
5572 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5573 ++job->jv_refcount;
5574 term->tl_job = job;
5575
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005576 /* Redirecting stdout and stderr doesn't work at the job level. Instead
5577 * open the file here and handle it in. opt->jo_io was changed in
5578 * setup_job_options(), use the original flags here. */
5579 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
5580 {
5581 char_u *fname = opt->jo_io_name[PART_OUT];
5582
5583 ch_log(channel, "Opening output file %s", fname);
5584 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
5585 if (term->tl_out_fd == NULL)
5586 EMSG2(_(e_notopen), fname);
5587 }
5588
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005589 return OK;
5590
5591failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005592 ga_clear(&ga_cmd);
5593 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005594 vim_free(cmd_wchar);
5595 vim_free(cwd_wchar);
5596 if (spawn_config != NULL)
5597 winpty_spawn_config_free(spawn_config);
5598 if (channel != NULL)
5599 channel_clear(channel);
5600 if (job != NULL)
5601 {
5602 job->jv_channel = NULL;
5603 job_cleanup(job);
5604 }
5605 term->tl_job = NULL;
5606 if (jo != NULL)
5607 CloseHandle(jo);
5608 if (term->tl_winpty != NULL)
5609 winpty_free(term->tl_winpty);
5610 term->tl_winpty = NULL;
5611 if (term->tl_winpty_config != NULL)
5612 winpty_config_free(term->tl_winpty_config);
5613 term->tl_winpty_config = NULL;
5614 if (winpty_err != NULL)
5615 {
5616 char_u *msg = utf16_to_enc(
5617 (short_u *)winpty_error_msg(winpty_err), NULL);
5618
5619 EMSG(msg);
5620 winpty_error_free(winpty_err);
5621 }
5622 return FAIL;
5623}
5624
5625 static int
5626create_pty_only(term_T *term, jobopt_T *options)
5627{
5628 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5629 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5630 char in_name[80], out_name[80];
5631 channel_T *channel = NULL;
5632
5633 create_vterm(term, term->tl_rows, term->tl_cols);
5634
5635 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5636 GetCurrentProcessId(),
5637 curbuf->b_fnum);
5638 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5639 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5640 PIPE_UNLIMITED_INSTANCES,
5641 0, 0, NMPWAIT_NOWAIT, NULL);
5642 if (hPipeIn == INVALID_HANDLE_VALUE)
5643 goto failed;
5644
5645 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5646 GetCurrentProcessId(),
5647 curbuf->b_fnum);
5648 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5649 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5650 PIPE_UNLIMITED_INSTANCES,
5651 0, 0, 0, NULL);
5652 if (hPipeOut == INVALID_HANDLE_VALUE)
5653 goto failed;
5654
5655 ConnectNamedPipe(hPipeIn, NULL);
5656 ConnectNamedPipe(hPipeOut, NULL);
5657
5658 term->tl_job = job_alloc();
5659 if (term->tl_job == NULL)
5660 goto failed;
5661 ++term->tl_job->jv_refcount;
5662
5663 /* behave like the job is already finished */
5664 term->tl_job->jv_status = JOB_FINISHED;
5665
5666 channel = add_channel();
5667 if (channel == NULL)
5668 goto failed;
5669 term->tl_job->jv_channel = channel;
5670 channel->ch_keep_open = TRUE;
5671 channel->ch_named_pipe = TRUE;
5672
5673 channel_set_pipes(channel,
5674 (sock_T)hPipeIn,
5675 (sock_T)hPipeOut,
5676 (sock_T)hPipeOut);
5677 channel_set_job(channel, term->tl_job, options);
5678 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5679 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5680
5681 return OK;
5682
5683failed:
5684 if (hPipeIn != NULL)
5685 CloseHandle(hPipeIn);
5686 if (hPipeOut != NULL)
5687 CloseHandle(hPipeOut);
5688 return FAIL;
5689}
5690
5691/*
5692 * Free the terminal emulator part of "term".
5693 */
5694 static void
5695term_free_vterm(term_T *term)
5696{
5697 if (term->tl_winpty != NULL)
5698 winpty_free(term->tl_winpty);
5699 term->tl_winpty = NULL;
5700 if (term->tl_winpty_config != NULL)
5701 winpty_config_free(term->tl_winpty_config);
5702 term->tl_winpty_config = NULL;
5703 if (term->tl_vterm != NULL)
5704 vterm_free(term->tl_vterm);
5705 term->tl_vterm = NULL;
5706}
5707
5708/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005709 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005710 */
5711 static void
5712term_report_winsize(term_T *term, int rows, int cols)
5713{
5714 if (term->tl_winpty)
5715 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5716}
5717
5718 int
5719terminal_enabled(void)
5720{
5721 return dyn_winpty_init(FALSE) == OK;
5722}
5723
5724# else
5725
5726/**************************************
5727 * 3. Unix-like implementation.
5728 */
5729
5730/*
5731 * Create a new terminal of "rows" by "cols" cells.
5732 * Start job for "cmd".
5733 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005734 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005735 * Return OK or FAIL.
5736 */
5737 static int
5738term_and_job_init(
5739 term_T *term,
5740 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005741 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005742 jobopt_T *opt,
5743 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005744{
5745 create_vterm(term, term->tl_rows, term->tl_cols);
5746
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005747#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5748 if (opt->jo_set2 & JO2_ANSI_COLORS)
5749 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5750 else
5751 init_vterm_ansi_colors(term->tl_vterm);
5752#endif
5753
Bram Moolenaar13568252018-03-16 20:46:58 +01005754 /* This may change a string in "argvar". */
5755 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005756 if (term->tl_job != NULL)
5757 ++term->tl_job->jv_refcount;
5758
5759 return term->tl_job != NULL
5760 && term->tl_job->jv_channel != NULL
5761 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5762}
5763
5764 static int
5765create_pty_only(term_T *term, jobopt_T *opt)
5766{
5767 create_vterm(term, term->tl_rows, term->tl_cols);
5768
5769 term->tl_job = job_alloc();
5770 if (term->tl_job == NULL)
5771 return FAIL;
5772 ++term->tl_job->jv_refcount;
5773
5774 /* behave like the job is already finished */
5775 term->tl_job->jv_status = JOB_FINISHED;
5776
5777 return mch_create_pty_channel(term->tl_job, opt);
5778}
5779
5780/*
5781 * Free the terminal emulator part of "term".
5782 */
5783 static void
5784term_free_vterm(term_T *term)
5785{
5786 if (term->tl_vterm != NULL)
5787 vterm_free(term->tl_vterm);
5788 term->tl_vterm = NULL;
5789}
5790
5791/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005792 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005793 */
5794 static void
5795term_report_winsize(term_T *term, int rows, int cols)
5796{
5797 /* Use an ioctl() to report the new window size to the job. */
5798 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5799 {
5800 int fd = -1;
5801 int part;
5802
5803 for (part = PART_OUT; part < PART_COUNT; ++part)
5804 {
5805 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5806 if (isatty(fd))
5807 break;
5808 }
5809 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5810 mch_signal_job(term->tl_job, (char_u *)"winch");
5811 }
5812}
5813
5814# endif
5815
5816#endif /* FEAT_TERMINAL */