blob: 6e5b1002c2366f690a570cca8d78ba9ac3ee9113 [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 Moolenaarf25329c2018-05-06 21:49:32 +020041 * - Win32: Redirecting input does not work, half of Test_terminal_redir_file()
Bram Moolenaar802bfb12018-04-15 17:28:13 +020042 * is disabled.
Bram Moolenaarf25329c2018-05-06 21:49:32 +020043 * - Win32: Redirecting output works but includes escape sequences.
44 * - Win32: Make terminal used for :!cmd in the GUI work better. Allow for
45 * redirection.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +020046 * - When the job only outputs lines, we could handle resizing the terminal
47 * better: store lines separated by line breaks, instead of screen lines,
48 * then when the window is resized redraw those lines.
Bram Moolenaarf25329c2018-05-06 21:49:32 +020049 * - Redrawing is slow with Athena and Motif. (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020050 * - For the GUI fill termios with default values, perhaps like pangoterm:
51 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar802bfb12018-04-15 17:28:13 +020052 * - When 'encoding' is not utf-8, or the job is using another encoding, setup
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020053 * conversions.
Bram Moolenaar498c2562018-04-15 23:45:15 +020054 * - Termdebug does not work when Vim build with mzscheme: gdb hangs just after
55 * "run". Everything else works, including communication channel. Not
56 * initializing mzscheme avoid the problem, thus it's not some #ifdef.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020057 */
58
59#include "vim.h"
60
61#if defined(FEAT_TERMINAL) || defined(PROTO)
62
63#ifndef MIN
64# define MIN(x,y) ((x) < (y) ? (x) : (y))
65#endif
66#ifndef MAX
67# define MAX(x,y) ((x) > (y) ? (x) : (y))
68#endif
69
70#include "libvterm/include/vterm.h"
71
72/* This is VTermScreenCell without the characters, thus much smaller. */
73typedef struct {
74 VTermScreenCellAttrs attrs;
75 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010076 VTermColor fg;
77 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020078} cellattr_T;
79
80typedef struct sb_line_S {
81 int sb_cols; /* can differ per line */
82 cellattr_T *sb_cells; /* allocated */
83 cellattr_T sb_fill_attr; /* for short line */
84} sb_line_T;
85
86/* typedef term_T in structs.h */
87struct terminal_S {
88 term_T *tl_next;
89
90 VTerm *tl_vterm;
91 job_T *tl_job;
92 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +010093#if defined(FEAT_GUI)
94 int tl_system; /* when non-zero used for :!cmd output */
95 int tl_toprow; /* row with first line of system terminal */
96#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020097
98 /* Set when setting the size of a vterm, reset after redrawing. */
99 int tl_vterm_size_changed;
100
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200101 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
102 int tl_channel_closed;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100103 int tl_finish;
104#define TL_FINISH_UNSET NUL
105#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
106#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
107#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200108 char_u *tl_opencmd;
109 char_u *tl_eof_chars;
110
111#ifdef WIN3264
112 void *tl_winpty_config;
113 void *tl_winpty;
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200114
115 FILE *tl_out_fd;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200116#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100117#if defined(FEAT_SESSION)
118 char_u *tl_command;
119#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100120 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200121
122 /* last known vterm size */
123 int tl_rows;
124 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200125
126 char_u *tl_title; /* NULL or allocated */
127 char_u *tl_status_text; /* NULL or allocated */
128
129 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200130 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200131 int tl_dirty_row_end; /* row below last one to update */
Bram Moolenaar56bc8e22018-05-10 18:05:56 +0200132 int tl_dirty_snapshot; /* text updated after making snapshot */
133#ifdef FEAT_TIMERS
134 int tl_timer_set;
135 proftime_T tl_timer_due;
136#endif
Bram Moolenaar6eddadf2018-05-06 16:40:16 +0200137 int tl_postponed_scroll; /* to be scrolled up */
138
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200139 garray_T tl_scrollback;
140 int tl_scrollback_scrolled;
141 cellattr_T tl_default_color;
142
Bram Moolenaard96ff162018-02-18 22:13:29 +0100143 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
144 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
145
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200146 VTermPos tl_cursor_pos;
147 int tl_cursor_visible;
148 int tl_cursor_blink;
149 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
150 char_u *tl_cursor_color; /* NULL or allocated */
151
152 int tl_using_altscreen;
153};
154
155#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
156#define TMODE_LOOP 2 /* CTRL-W N used */
157
158/*
159 * List of all active terminals.
160 */
161static term_T *first_term = NULL;
162
163/* Terminal active in terminal_loop(). */
164static term_T *in_terminal_loop = NULL;
165
166#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
167#define KEY_BUF_LEN 200
168
169/*
170 * Functions with separate implementation for MS-Windows and Unix-like systems.
171 */
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200172static 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 +0200173static int create_pty_only(term_T *term, jobopt_T *opt);
174static void term_report_winsize(term_T *term, int rows, int cols);
175static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100176#ifdef FEAT_GUI
177static void update_system_term(term_T *term);
178#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200179
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100180/* The character that we know (or assume) that the terminal expects for the
181 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200182static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200183
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100184/* "Terminal" highlight group colors. */
185static int term_default_cterm_fg = -1;
186static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200187
Bram Moolenaard317b382018-02-08 22:33:31 +0100188/* Store the last set and the desired cursor properties, so that we only update
189 * them when needed. Doing it unnecessary may result in flicker. */
190static char_u *last_set_cursor_color = (char_u *)"";
191static char_u *desired_cursor_color = (char_u *)"";
192static int last_set_cursor_shape = -1;
193static int desired_cursor_shape = -1;
194static int last_set_cursor_blink = -1;
195static int desired_cursor_blink = -1;
196
197
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200198/**************************************
199 * 1. Generic code for all systems.
200 */
201
202/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200203 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
Bram Moolenaar498c2562018-04-15 23:45:15 +0200204 * current window.
205 * Sets "rows" and/or "cols" to zero when it should follow the window size.
206 * Return TRUE if the size is the minimum size: "24*80".
207 */
208 static int
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200209parse_termwinsize(win_T *wp, int *rows, int *cols)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200210{
211 int minsize = FALSE;
212
213 *rows = 0;
214 *cols = 0;
215
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200216 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200217 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200218 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200219
220 /* Syntax of value was already checked when it's set. */
221 if (p == NULL)
222 {
223 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200224 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200225 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200226 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200227 *cols = atoi((char *)p + 1);
228 }
229 return minsize;
230}
231
232/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200233 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200234 */
235 static void
236set_term_and_win_size(term_T *term)
237{
Bram Moolenaar13568252018-03-16 20:46:58 +0100238#ifdef FEAT_GUI
239 if (term->tl_system)
240 {
241 /* Use the whole screen for the system command. However, it will start
242 * at the command line and scroll up as needed, using tl_toprow. */
243 term->tl_rows = Rows;
244 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200245 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100246 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100247#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200248 if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200249 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200250 if (term->tl_rows != 0)
251 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
252 if (term->tl_cols != 0)
253 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200254 }
255 if (term->tl_rows == 0)
256 term->tl_rows = curwin->w_height;
257 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200258 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200259 if (term->tl_cols == 0)
260 term->tl_cols = curwin->w_width;
261 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200262 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200263}
264
265/*
266 * Initialize job options for a terminal job.
267 * Caller may overrule some of them.
268 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100269 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200270init_job_options(jobopt_T *opt)
271{
272 clear_job_options(opt);
273
274 opt->jo_mode = MODE_RAW;
275 opt->jo_out_mode = MODE_RAW;
276 opt->jo_err_mode = MODE_RAW;
277 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
278}
279
280/*
281 * Set job options mandatory for a terminal job.
282 */
283 static void
284setup_job_options(jobopt_T *opt, int rows, int cols)
285{
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200286#ifndef WIN3264
287 /* Win32: Redirecting the job output won't work, thus always connect stdout
288 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200289 if (!(opt->jo_set & JO_OUT_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200290#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200291 {
292 /* Connect stdout to the terminal. */
293 opt->jo_io[PART_OUT] = JIO_BUFFER;
294 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
295 opt->jo_modifiable[PART_OUT] = 0;
296 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
297 }
298
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200299#ifndef WIN3264
300 /* Win32: Redirecting the job output won't work, thus always connect stderr
301 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200302 if (!(opt->jo_set & JO_ERR_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200303#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200304 {
305 /* Connect stderr to the terminal. */
306 opt->jo_io[PART_ERR] = JIO_BUFFER;
307 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
308 opt->jo_modifiable[PART_ERR] = 0;
309 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
310 }
311
312 opt->jo_pty = TRUE;
313 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
314 opt->jo_term_rows = rows;
315 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
316 opt->jo_term_cols = cols;
317}
318
319/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100320 * Close a terminal buffer (and its window). Used when creating the terminal
321 * fails.
322 */
323 static void
324term_close_buffer(buf_T *buf, buf_T *old_curbuf)
325{
326 free_terminal(buf);
327 if (old_curbuf != NULL)
328 {
329 --curbuf->b_nwindows;
330 curbuf = old_curbuf;
331 curwin->w_buffer = curbuf;
332 ++curbuf->b_nwindows;
333 }
334
335 /* Wiping out the buffer will also close the window and call
336 * free_terminal(). */
337 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
338}
339
340/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200341 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100342 * Use either "argvar" or "argv", the other must be NULL.
343 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
344 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200345 * Returns NULL when failed.
346 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100347 buf_T *
348term_start(
349 typval_T *argvar,
350 char **argv,
351 jobopt_T *opt,
352 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200353{
354 exarg_T split_ea;
355 win_T *old_curwin = curwin;
356 term_T *term;
357 buf_T *old_curbuf = NULL;
358 int res;
359 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100360 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200361 jobopt_T orig_opt; // only partly filled
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200362
363 if (check_restricted() || check_secure())
364 return NULL;
365
366 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
367 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
368 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
369 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
370 {
371 EMSG(_(e_invarg));
372 return NULL;
373 }
374
375 term = (term_T *)alloc_clear(sizeof(term_T));
376 if (term == NULL)
377 return NULL;
378 term->tl_dirty_row_end = MAX_ROW;
379 term->tl_cursor_visible = TRUE;
380 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
381 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100382#ifdef FEAT_GUI
383 term->tl_system = (flags & TERM_START_SYSTEM);
384#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200385 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
386
387 vim_memset(&split_ea, 0, sizeof(split_ea));
388 if (opt->jo_curwin)
389 {
390 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100391 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200392 {
393 no_write_message();
394 vim_free(term);
395 return NULL;
396 }
397 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100398 ECMD_HIDE
399 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
400 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200401 {
402 vim_free(term);
403 return NULL;
404 }
405 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100406 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200407 {
408 buf_T *buf;
409
410 /* Create a new buffer without a window. Make it the current buffer for
411 * a moment to be able to do the initialisations. */
412 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
413 BLN_NEW | BLN_LISTED);
414 if (buf == NULL || ml_open(buf) == FAIL)
415 {
416 vim_free(term);
417 return NULL;
418 }
419 old_curbuf = curbuf;
420 --curbuf->b_nwindows;
421 curbuf = buf;
422 curwin->w_buffer = buf;
423 ++curbuf->b_nwindows;
424 }
425 else
426 {
427 /* Open a new window or tab. */
428 split_ea.cmdidx = CMD_new;
429 split_ea.cmd = (char_u *)"new";
430 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100431 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200432 {
433 split_ea.line2 = opt->jo_term_rows;
434 split_ea.addr_count = 1;
435 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100436 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200437 {
438 split_ea.line2 = opt->jo_term_cols;
439 split_ea.addr_count = 1;
440 }
441
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100442 if (vertical)
443 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200444 ex_splitview(&split_ea);
445 if (curwin == old_curwin)
446 {
447 /* split failed */
448 vim_free(term);
449 return NULL;
450 }
451 }
452 term->tl_buffer = curbuf;
453 curbuf->b_term = term;
454
455 if (!opt->jo_hidden)
456 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100457 /* Only one size was taken care of with :new, do the other one. With
458 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100459 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200460 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100461 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200462 win_setwidth(opt->jo_term_cols);
463 }
464
465 /* Link the new terminal in the list of active terminals. */
466 term->tl_next = first_term;
467 first_term = term;
468
469 if (opt->jo_term_name != NULL)
470 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100471 else if (argv != NULL)
472 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200473 else
474 {
475 int i;
476 size_t len;
477 char_u *cmd, *p;
478
479 if (argvar->v_type == VAR_STRING)
480 {
481 cmd = argvar->vval.v_string;
482 if (cmd == NULL)
483 cmd = (char_u *)"";
484 else if (STRCMP(cmd, "NONE") == 0)
485 cmd = (char_u *)"pty";
486 }
487 else if (argvar->v_type != VAR_LIST
488 || argvar->vval.v_list == NULL
489 || argvar->vval.v_list->lv_len < 1
490 || (cmd = get_tv_string_chk(
491 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
492 cmd = (char_u*)"";
493
494 len = STRLEN(cmd) + 10;
495 p = alloc((int)len);
496
497 for (i = 0; p != NULL; ++i)
498 {
499 /* Prepend a ! to the command name to avoid the buffer name equals
500 * the executable, otherwise ":w!" would overwrite it. */
501 if (i == 0)
502 vim_snprintf((char *)p, len, "!%s", cmd);
503 else
504 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
505 if (buflist_findname(p) == NULL)
506 {
507 vim_free(curbuf->b_ffname);
508 curbuf->b_ffname = p;
509 break;
510 }
511 }
512 }
513 curbuf->b_fname = curbuf->b_ffname;
514
515 if (opt->jo_term_opencmd != NULL)
516 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
517
518 if (opt->jo_eof_chars != NULL)
519 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
520
521 set_string_option_direct((char_u *)"buftype", -1,
522 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
523
524 /* Mark the buffer as not modifiable. It can only be made modifiable after
525 * the job finished. */
526 curbuf->b_p_ma = FALSE;
527
528 set_term_and_win_size(term);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200529#ifdef WIN3264
530 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
531#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200532 setup_job_options(opt, term->tl_rows, term->tl_cols);
533
Bram Moolenaar13568252018-03-16 20:46:58 +0100534 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100535 return curbuf;
536
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100537#if defined(FEAT_SESSION)
538 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100539 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100540 {
541 term->tl_command = vim_strsave((char_u *)"NONE");
542 }
543 else if (argvar->v_type == VAR_STRING)
544 {
545 char_u *cmd = argvar->vval.v_string;
546
547 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
548 term->tl_command = vim_strsave(cmd);
549 }
550 else if (argvar->v_type == VAR_LIST
551 && argvar->vval.v_list != NULL
552 && argvar->vval.v_list->lv_len > 0)
553 {
554 garray_T ga;
555 listitem_T *item;
556
557 ga_init2(&ga, 1, 100);
558 for (item = argvar->vval.v_list->lv_first;
559 item != NULL; item = item->li_next)
560 {
561 char_u *s = get_tv_string_chk(&item->li_tv);
562 char_u *p;
563
564 if (s == NULL)
565 break;
566 p = vim_strsave_fnameescape(s, FALSE);
567 if (p == NULL)
568 break;
569 ga_concat(&ga, p);
570 vim_free(p);
571 ga_append(&ga, ' ');
572 }
573 if (item == NULL)
574 {
575 ga_append(&ga, NUL);
576 term->tl_command = ga.ga_data;
577 }
578 else
579 ga_clear(&ga);
580 }
581#endif
582
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100583 if (opt->jo_term_kill != NULL)
584 {
585 char_u *p = skiptowhite(opt->jo_term_kill);
586
587 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
588 }
589
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200590 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100591 if (argv == NULL
592 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200593 && argvar->vval.v_string != NULL
594 && STRCMP(argvar->vval.v_string, "NONE") == 0)
595 res = create_pty_only(term, opt);
596 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200597 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200598
599 newbuf = curbuf;
600 if (res == OK)
601 {
602 /* Get and remember the size we ended up with. Update the pty. */
603 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
604 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100605#ifdef FEAT_GUI
606 if (term->tl_system)
607 {
608 /* display first line below typed command */
609 term->tl_toprow = msg_row + 1;
610 term->tl_dirty_row_end = 0;
611 }
612#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200613
614 /* Make sure we don't get stuck on sending keys to the job, it leads to
615 * a deadlock if the job is waiting for Vim to read. */
616 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
617
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200618 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200619 {
620 --curbuf->b_nwindows;
621 curbuf = old_curbuf;
622 curwin->w_buffer = curbuf;
623 ++curbuf->b_nwindows;
624 }
625 }
626 else
627 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100628 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200629 return NULL;
630 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100631
Bram Moolenaar13568252018-03-16 20:46:58 +0100632 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200633 return newbuf;
634}
635
636/*
637 * ":terminal": open a terminal window and execute a job in it.
638 */
639 void
640ex_terminal(exarg_T *eap)
641{
642 typval_T argvar[2];
643 jobopt_T opt;
644 char_u *cmd;
645 char_u *tofree = NULL;
646
647 init_job_options(&opt);
648
649 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100650 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200651 {
652 char_u *p, *ep;
653
654 cmd += 2;
655 p = skiptowhite(cmd);
656 ep = vim_strchr(cmd, '=');
657 if (ep != NULL && ep < p)
658 p = ep;
659
660 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
661 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100662 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
663 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200664 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
665 opt.jo_term_finish = 'o';
666 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
667 opt.jo_curwin = 1;
668 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
669 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100670 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
671 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100672 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
673 && ep != NULL)
674 {
675 opt.jo_set2 |= JO2_TERM_KILL;
676 opt.jo_term_kill = ep + 1;
677 p = skiptowhite(cmd);
678 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200679 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
680 && ep != NULL && isdigit(ep[1]))
681 {
682 opt.jo_set2 |= JO2_TERM_ROWS;
683 opt.jo_term_rows = atoi((char *)ep + 1);
684 p = skiptowhite(cmd);
685 }
686 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
687 && ep != NULL && isdigit(ep[1]))
688 {
689 opt.jo_set2 |= JO2_TERM_COLS;
690 opt.jo_term_cols = atoi((char *)ep + 1);
691 p = skiptowhite(cmd);
692 }
693 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
694 && ep != NULL)
695 {
696 char_u *buf = NULL;
697 char_u *keys;
698
699 p = skiptowhite(cmd);
700 *p = NUL;
701 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
702 opt.jo_set2 |= JO2_EOF_CHARS;
703 opt.jo_eof_chars = vim_strsave(keys);
704 vim_free(buf);
705 *p = ' ';
706 }
707 else
708 {
709 if (*p)
710 *p = NUL;
711 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100712 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200713 }
714 cmd = skipwhite(p);
715 }
716 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100717 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200718 /* Make a copy of 'shell', an autocommand may change the option. */
719 tofree = cmd = vim_strsave(p_sh);
720
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100721 /* default to close when the shell exits */
722 if (opt.jo_term_finish == NUL)
723 opt.jo_term_finish = 'c';
724 }
725
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200726 if (eap->addr_count > 0)
727 {
728 /* Write lines from current buffer to the job. */
729 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
730 opt.jo_io[PART_IN] = JIO_BUFFER;
731 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
732 opt.jo_in_top = eap->line1;
733 opt.jo_in_bot = eap->line2;
734 }
735
736 argvar[0].v_type = VAR_STRING;
737 argvar[0].vval.v_string = cmd;
738 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100739 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200740 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100741
742theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200743 vim_free(opt.jo_eof_chars);
744}
745
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100746#if defined(FEAT_SESSION) || defined(PROTO)
747/*
748 * Write a :terminal command to the session file to restore the terminal in
749 * window "wp".
750 * Return FAIL if writing fails.
751 */
752 int
753term_write_session(FILE *fd, win_T *wp)
754{
755 term_T *term = wp->w_buffer->b_term;
756
757 /* Create the terminal and run the command. This is not without
758 * risk, but let's assume the user only creates a session when this
759 * will be OK. */
760 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
761 term->tl_cols, term->tl_rows) < 0)
762 return FAIL;
763 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
764 return FAIL;
765
766 return put_eol(fd);
767}
768
769/*
770 * Return TRUE if "buf" has a terminal that should be restored.
771 */
772 int
773term_should_restore(buf_T *buf)
774{
775 term_T *term = buf->b_term;
776
777 return term != NULL && (term->tl_command == NULL
778 || STRCMP(term->tl_command, "NONE") != 0);
779}
780#endif
781
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200782/*
783 * Free the scrollback buffer for "term".
784 */
785 static void
786free_scrollback(term_T *term)
787{
788 int i;
789
790 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
791 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
792 ga_clear(&term->tl_scrollback);
793}
794
795/*
796 * Free a terminal and everything it refers to.
797 * Kills the job if there is one.
798 * Called when wiping out a buffer.
799 */
800 void
801free_terminal(buf_T *buf)
802{
803 term_T *term = buf->b_term;
804 term_T *tp;
805
806 if (term == NULL)
807 return;
808 if (first_term == term)
809 first_term = term->tl_next;
810 else
811 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
812 if (tp->tl_next == term)
813 {
814 tp->tl_next = term->tl_next;
815 break;
816 }
817
818 if (term->tl_job != NULL)
819 {
820 if (term->tl_job->jv_status != JOB_ENDED
821 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100822 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200823 job_stop(term->tl_job, NULL, "kill");
824 job_unref(term->tl_job);
825 }
826
827 free_scrollback(term);
828
829 term_free_vterm(term);
830 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100831#ifdef FEAT_SESSION
832 vim_free(term->tl_command);
833#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100834 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200835 vim_free(term->tl_status_text);
836 vim_free(term->tl_opencmd);
837 vim_free(term->tl_eof_chars);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200838#ifdef WIN3264
839 if (term->tl_out_fd != NULL)
840 fclose(term->tl_out_fd);
841#endif
Bram Moolenaard317b382018-02-08 22:33:31 +0100842 if (desired_cursor_color == term->tl_cursor_color)
843 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200844 vim_free(term->tl_cursor_color);
845 vim_free(term);
846 buf->b_term = NULL;
847 if (in_terminal_loop == term)
848 in_terminal_loop = NULL;
849}
850
851/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100852 * Get the part that is connected to the tty. Normally this is PART_IN, but
853 * when writing buffer lines to the job it can be another. This makes it
854 * possible to do "1,5term vim -".
855 */
856 static ch_part_T
857get_tty_part(term_T *term)
858{
859#ifdef UNIX
860 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
861 int i;
862
863 for (i = 0; i < 3; ++i)
864 {
865 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
866
867 if (isatty(fd))
868 return parts[i];
869 }
870#endif
871 return PART_IN;
872}
873
874/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200875 * Write job output "msg[len]" to the vterm.
876 */
877 static void
878term_write_job_output(term_T *term, char_u *msg, size_t len)
879{
880 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100881 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200882
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100883 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200884
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100885 /* flush vterm buffer when vterm responded to control sequence */
886 if (prevlen != vterm_output_get_buffer_current(vterm))
887 {
888 char buf[KEY_BUF_LEN];
889 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
890
891 if (curlen > 0)
892 channel_send(term->tl_job->jv_channel, get_tty_part(term),
893 (char_u *)buf, (int)curlen, NULL);
894 }
895
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200896 /* this invokes the damage callbacks */
897 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
898}
899
900 static void
901update_cursor(term_T *term, int redraw)
902{
903 if (term->tl_normal_mode)
904 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100905#ifdef FEAT_GUI
906 if (term->tl_system)
907 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
908 term->tl_cursor_pos.col);
909 else
910#endif
911 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200912 if (redraw)
913 {
914 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
915 cursor_on();
916 out_flush();
917#ifdef FEAT_GUI
918 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100919 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200920 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100921 gui_mch_flush();
922 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200923#endif
924 }
925}
926
927/*
928 * Invoked when "msg" output from a job was received. Write it to the terminal
929 * of "buffer".
930 */
931 void
932write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
933{
934 size_t len = STRLEN(msg);
935 term_T *term = buffer->b_term;
936
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200937#ifdef WIN3264
938 /* Win32: Cannot redirect output of the job, intercept it here and write to
939 * the file. */
940 if (term->tl_out_fd != NULL)
941 {
942 ch_log(channel, "Writing %d bytes to output file", (int)len);
943 fwrite(msg, len, 1, term->tl_out_fd);
944 return;
945 }
946#endif
947
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200948 if (term->tl_vterm == NULL)
949 {
950 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
951 return;
952 }
953 ch_log(channel, "writing %d bytes to terminal", (int)len);
954 term_write_job_output(term, msg, len);
955
Bram Moolenaar13568252018-03-16 20:46:58 +0100956#ifdef FEAT_GUI
957 if (term->tl_system)
958 {
959 /* show system output, scrolling up the screen as needed */
960 update_system_term(term);
961 update_cursor(term, TRUE);
962 }
963 else
964#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200965 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
966 * contents, thus no screen update is needed. */
967 if (!term->tl_normal_mode)
968 {
969 /* TODO: only update once in a while. */
970 ch_log(term->tl_job->jv_channel, "updating screen");
971 if (buffer == curbuf)
972 {
973 update_screen(0);
974 update_cursor(term, TRUE);
975 }
976 else
977 redraw_after_callback(TRUE);
978 }
979}
980
981/*
982 * Send a mouse position and click to the vterm
983 */
984 static int
985term_send_mouse(VTerm *vterm, int button, int pressed)
986{
987 VTermModifier mod = VTERM_MOD_NONE;
988
989 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200990 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100991 if (button != 0)
992 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200993 return TRUE;
994}
995
Bram Moolenaarc48369c2018-03-11 19:30:45 +0100996static int enter_mouse_col = -1;
997static int enter_mouse_row = -1;
998
999/*
1000 * Handle a mouse click, drag or release.
1001 * Return TRUE when a mouse event is sent to the terminal.
1002 */
1003 static int
1004term_mouse_click(VTerm *vterm, int key)
1005{
1006#if defined(FEAT_CLIPBOARD)
1007 /* For modeless selection mouse drag and release events are ignored, unless
1008 * they are preceded with a mouse down event */
1009 static int ignore_drag_release = TRUE;
1010 VTermMouseState mouse_state;
1011
1012 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1013 if (mouse_state.flags == 0)
1014 {
1015 /* Terminal is not using the mouse, use modeless selection. */
1016 switch (key)
1017 {
1018 case K_LEFTDRAG:
1019 case K_LEFTRELEASE:
1020 case K_RIGHTDRAG:
1021 case K_RIGHTRELEASE:
1022 /* Ignore drag and release events when the button-down wasn't
1023 * seen before. */
1024 if (ignore_drag_release)
1025 {
1026 int save_mouse_col, save_mouse_row;
1027
1028 if (enter_mouse_col < 0)
1029 break;
1030
1031 /* mouse click in the window gave us focus, handle that
1032 * click now */
1033 save_mouse_col = mouse_col;
1034 save_mouse_row = mouse_row;
1035 mouse_col = enter_mouse_col;
1036 mouse_row = enter_mouse_row;
1037 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1038 mouse_col = save_mouse_col;
1039 mouse_row = save_mouse_row;
1040 }
1041 /* FALLTHROUGH */
1042 case K_LEFTMOUSE:
1043 case K_RIGHTMOUSE:
1044 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1045 ignore_drag_release = TRUE;
1046 else
1047 ignore_drag_release = FALSE;
1048 /* Should we call mouse_has() here? */
1049 if (clip_star.available)
1050 {
1051 int button, is_click, is_drag;
1052
1053 button = get_mouse_button(KEY2TERMCAP1(key),
1054 &is_click, &is_drag);
1055 if (mouse_model_popup() && button == MOUSE_LEFT
1056 && (mod_mask & MOD_MASK_SHIFT))
1057 {
1058 /* Translate shift-left to right button. */
1059 button = MOUSE_RIGHT;
1060 mod_mask &= ~MOD_MASK_SHIFT;
1061 }
1062 clip_modeless(button, is_click, is_drag);
1063 }
1064 break;
1065
1066 case K_MIDDLEMOUSE:
1067 if (clip_star.available)
1068 insert_reg('*', TRUE);
1069 break;
1070 }
1071 enter_mouse_col = -1;
1072 return FALSE;
1073 }
1074#endif
1075 enter_mouse_col = -1;
1076
1077 switch (key)
1078 {
1079 case K_LEFTMOUSE:
1080 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1081 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1082 case K_LEFTRELEASE:
1083 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1084 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1085 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1086 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1087 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1088 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1089 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1090 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1091 }
1092 return TRUE;
1093}
1094
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001095/*
1096 * Convert typed key "c" into bytes to send to the job.
1097 * Return the number of bytes in "buf".
1098 */
1099 static int
1100term_convert_key(term_T *term, int c, char *buf)
1101{
1102 VTerm *vterm = term->tl_vterm;
1103 VTermKey key = VTERM_KEY_NONE;
1104 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001105 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001106
1107 switch (c)
1108 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001109 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1110
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001111 /* don't use VTERM_KEY_BACKSPACE, it always
1112 * becomes 0x7f DEL */
1113 case K_BS: c = term_backspace_char; break;
1114
1115 case ESC: key = VTERM_KEY_ESCAPE; break;
1116 case K_DEL: key = VTERM_KEY_DEL; break;
1117 case K_DOWN: key = VTERM_KEY_DOWN; break;
1118 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1119 key = VTERM_KEY_DOWN; break;
1120 case K_END: key = VTERM_KEY_END; break;
1121 case K_S_END: mod = VTERM_MOD_SHIFT;
1122 key = VTERM_KEY_END; break;
1123 case K_C_END: mod = VTERM_MOD_CTRL;
1124 key = VTERM_KEY_END; break;
1125 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1126 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1127 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1128 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1129 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1130 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1131 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1132 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1133 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1134 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1135 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1136 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1137 case K_HOME: key = VTERM_KEY_HOME; break;
1138 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1139 key = VTERM_KEY_HOME; break;
1140 case K_C_HOME: mod = VTERM_MOD_CTRL;
1141 key = VTERM_KEY_HOME; break;
1142 case K_INS: key = VTERM_KEY_INS; break;
1143 case K_K0: key = VTERM_KEY_KP_0; break;
1144 case K_K1: key = VTERM_KEY_KP_1; break;
1145 case K_K2: key = VTERM_KEY_KP_2; break;
1146 case K_K3: key = VTERM_KEY_KP_3; break;
1147 case K_K4: key = VTERM_KEY_KP_4; break;
1148 case K_K5: key = VTERM_KEY_KP_5; break;
1149 case K_K6: key = VTERM_KEY_KP_6; break;
1150 case K_K7: key = VTERM_KEY_KP_7; break;
1151 case K_K8: key = VTERM_KEY_KP_8; break;
1152 case K_K9: key = VTERM_KEY_KP_9; break;
1153 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1154 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1155 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1156 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1157 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1158 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1159 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1160 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1161 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1162 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1163 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1164 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1165 case K_LEFT: key = VTERM_KEY_LEFT; break;
1166 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1167 key = VTERM_KEY_LEFT; break;
1168 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1169 key = VTERM_KEY_LEFT; break;
1170 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1171 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1172 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1173 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1174 key = VTERM_KEY_RIGHT; break;
1175 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1176 key = VTERM_KEY_RIGHT; break;
1177 case K_UP: key = VTERM_KEY_UP; break;
1178 case K_S_UP: mod = VTERM_MOD_SHIFT;
1179 key = VTERM_KEY_UP; break;
1180 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001181 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1182 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001183
Bram Moolenaara42ad572017-11-16 13:08:04 +01001184 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1185 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001186 case K_MOUSELEFT: /* TODO */ return 0;
1187 case K_MOUSERIGHT: /* TODO */ return 0;
1188
1189 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001190 case K_LEFTMOUSE_NM:
1191 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001192 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001193 case K_LEFTRELEASE_NM:
1194 case K_MOUSEMOVE:
1195 case K_MIDDLEMOUSE:
1196 case K_MIDDLEDRAG:
1197 case K_MIDDLERELEASE:
1198 case K_RIGHTMOUSE:
1199 case K_RIGHTDRAG:
1200 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1201 return 0;
1202 other = TRUE;
1203 break;
1204
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001205 case K_X1MOUSE: /* TODO */ return 0;
1206 case K_X1DRAG: /* TODO */ return 0;
1207 case K_X1RELEASE: /* TODO */ return 0;
1208 case K_X2MOUSE: /* TODO */ return 0;
1209 case K_X2DRAG: /* TODO */ return 0;
1210 case K_X2RELEASE: /* TODO */ return 0;
1211
1212 case K_IGNORE: return 0;
1213 case K_NOP: return 0;
1214 case K_UNDO: return 0;
1215 case K_HELP: return 0;
1216 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1217 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1218 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1219 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1220 case K_SELECT: return 0;
1221#ifdef FEAT_GUI
1222 case K_VER_SCROLLBAR: return 0;
1223 case K_HOR_SCROLLBAR: return 0;
1224#endif
1225#ifdef FEAT_GUI_TABLINE
1226 case K_TABLINE: return 0;
1227 case K_TABMENU: return 0;
1228#endif
1229#ifdef FEAT_NETBEANS_INTG
1230 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1231#endif
1232#ifdef FEAT_DND
1233 case K_DROP: return 0;
1234#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001235 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001236 case K_PS: vterm_keyboard_start_paste(vterm);
1237 other = TRUE;
1238 break;
1239 case K_PE: vterm_keyboard_end_paste(vterm);
1240 other = TRUE;
1241 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001242 }
1243
1244 /*
1245 * Convert special keys to vterm keys:
1246 * - Write keys to vterm: vterm_keyboard_key()
1247 * - Write output to channel.
1248 * TODO: use mod_mask
1249 */
1250 if (key != VTERM_KEY_NONE)
1251 /* Special key, let vterm convert it. */
1252 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001253 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001254 /* Normal character, let vterm convert it. */
1255 vterm_keyboard_unichar(vterm, c, mod);
1256
1257 /* Read back the converted escape sequence. */
1258 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1259}
1260
1261/*
1262 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001263 * If "check_job_status" is TRUE update the job status.
1264 */
1265 static int
1266term_job_running_check(term_T *term, int check_job_status)
1267{
1268 /* Also consider the job finished when the channel is closed, to avoid a
1269 * race condition when updating the title. */
1270 if (term != NULL
1271 && term->tl_job != NULL
1272 && channel_is_open(term->tl_job->jv_channel))
1273 {
1274 if (check_job_status)
1275 job_status(term->tl_job);
1276 return (term->tl_job->jv_status == JOB_STARTED
1277 || term->tl_job->jv_channel->ch_keep_open);
1278 }
1279 return FALSE;
1280}
1281
1282/*
1283 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001284 */
1285 int
1286term_job_running(term_T *term)
1287{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001288 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001289}
1290
1291/*
1292 * Return TRUE if "term" has an active channel and used ":term NONE".
1293 */
1294 int
1295term_none_open(term_T *term)
1296{
1297 /* Also consider the job finished when the channel is closed, to avoid a
1298 * race condition when updating the title. */
1299 return term != NULL
1300 && term->tl_job != NULL
1301 && channel_is_open(term->tl_job->jv_channel)
1302 && term->tl_job->jv_channel->ch_keep_open;
1303}
1304
1305/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001306 * Used when exiting: kill the job in "buf" if so desired.
1307 * Return OK when the job finished.
1308 * Return FAIL when the job is still running.
1309 */
1310 int
1311term_try_stop_job(buf_T *buf)
1312{
1313 int count;
1314 char *how = (char *)buf->b_term->tl_kill;
1315
1316#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1317 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1318 {
1319 char_u buff[DIALOG_MSG_SIZE];
1320 int ret;
1321
1322 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1323 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1324 if (ret == VIM_YES)
1325 how = "kill";
1326 else if (ret == VIM_CANCEL)
1327 return FAIL;
1328 }
1329#endif
1330 if (how == NULL || *how == NUL)
1331 return FAIL;
1332
1333 job_stop(buf->b_term->tl_job, NULL, how);
1334
1335 /* wait for up to a second for the job to die */
1336 for (count = 0; count < 100; ++count)
1337 {
1338 /* buffer, terminal and job may be cleaned up while waiting */
1339 if (!buf_valid(buf)
1340 || buf->b_term == NULL
1341 || buf->b_term->tl_job == NULL)
1342 return OK;
1343
1344 /* call job_status() to update jv_status */
1345 job_status(buf->b_term->tl_job);
1346 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1347 return OK;
1348 ui_delay(10L, FALSE);
1349 mch_check_messages();
1350 parse_queued_messages();
1351 }
1352 return FAIL;
1353}
1354
1355/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001356 * Add the last line of the scrollback buffer to the buffer in the window.
1357 */
1358 static void
1359add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1360{
1361 buf_T *buf = term->tl_buffer;
1362 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1363 linenr_T lnum = buf->b_ml.ml_line_count;
1364
1365#ifdef WIN3264
1366 if (!enc_utf8 && enc_codepage > 0)
1367 {
1368 WCHAR *ret = NULL;
1369 int length = 0;
1370
1371 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1372 &ret, &length);
1373 if (ret != NULL)
1374 {
1375 WideCharToMultiByte_alloc(enc_codepage, 0,
1376 ret, length, (char **)&text, &len, 0, 0);
1377 vim_free(ret);
1378 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1379 vim_free(text);
1380 }
1381 }
1382 else
1383#endif
1384 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1385 if (empty)
1386 {
1387 /* Delete the empty line that was in the empty buffer. */
1388 curbuf = buf;
1389 ml_delete(1, FALSE);
1390 curbuf = curwin->w_buffer;
1391 }
1392}
1393
1394 static void
1395cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1396{
1397 attr->width = cell->width;
1398 attr->attrs = cell->attrs;
1399 attr->fg = cell->fg;
1400 attr->bg = cell->bg;
1401}
1402
1403 static int
1404equal_celattr(cellattr_T *a, cellattr_T *b)
1405{
1406 /* Comparing the colors should be sufficient. */
1407 return a->fg.red == b->fg.red
1408 && a->fg.green == b->fg.green
1409 && a->fg.blue == b->fg.blue
1410 && a->bg.red == b->bg.red
1411 && a->bg.green == b->bg.green
1412 && a->bg.blue == b->bg.blue;
1413}
1414
Bram Moolenaard96ff162018-02-18 22:13:29 +01001415/*
1416 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1417 * line at this position. Otherwise at the end.
1418 */
1419 static int
1420add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1421{
1422 if (ga_grow(&term->tl_scrollback, 1) == OK)
1423 {
1424 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1425 + term->tl_scrollback.ga_len;
1426
1427 if (lnum > 0)
1428 {
1429 int i;
1430
1431 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1432 {
1433 *line = *(line - 1);
1434 --line;
1435 }
1436 }
1437 line->sb_cols = 0;
1438 line->sb_cells = NULL;
1439 line->sb_fill_attr = *fill_attr;
1440 ++term->tl_scrollback.ga_len;
1441 return OK;
1442 }
1443 return FALSE;
1444}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001445
1446/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001447 * Remove the terminal contents from the scrollback and the buffer.
1448 * Used before adding a new scrollback line or updating the buffer for lines
1449 * displayed in the terminal.
1450 */
1451 static void
1452cleanup_scrollback(term_T *term)
1453{
1454 sb_line_T *line;
1455 garray_T *gap;
1456
1457 gap = &term->tl_scrollback;
1458 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1459 && gap->ga_len > 0)
1460 {
1461 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1462 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1463 vim_free(line->sb_cells);
1464 --gap->ga_len;
1465 }
1466 check_cursor();
1467}
1468
1469/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001470 * Add the current lines of the terminal to scrollback and to the buffer.
1471 * Called after the job has ended and when switching to Terminal-Normal mode.
1472 */
1473 static void
1474move_terminal_to_buffer(term_T *term)
1475{
1476 win_T *wp;
1477 int len;
1478 int lines_skipped = 0;
1479 VTermPos pos;
1480 VTermScreenCell cell;
1481 cellattr_T fill_attr, new_fill_attr;
1482 cellattr_T *p;
1483 VTermScreen *screen;
1484
1485 if (term->tl_vterm == NULL)
1486 return;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001487
1488 /* Nothing to do if the buffer already has the lines and nothing was
1489 * changed. */
1490 if (!term->tl_dirty_snapshot
1491 && curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled)
1492 return;
1493
1494 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1495 "Adding terminal window snapshot to buffer");
1496
1497 /* First remove the lines that were appended before, they might be
1498 * outdated. */
1499 cleanup_scrollback(term);
1500
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001501 screen = vterm_obtain_screen(term->tl_vterm);
1502 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001503 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1504 {
1505 len = 0;
1506 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1507 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1508 && cell.chars[0] != NUL)
1509 {
1510 len = pos.col + 1;
1511 new_fill_attr = term->tl_default_color;
1512 }
1513 else
1514 /* Assume the last attr is the filler attr. */
1515 cell2cellattr(&cell, &new_fill_attr);
1516
1517 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1518 ++lines_skipped;
1519 else
1520 {
1521 while (lines_skipped > 0)
1522 {
1523 /* Line was skipped, add an empty line. */
1524 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001525 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001526 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001527 }
1528
1529 if (len == 0)
1530 p = NULL;
1531 else
1532 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1533 if ((p != NULL || len == 0)
1534 && ga_grow(&term->tl_scrollback, 1) == OK)
1535 {
1536 garray_T ga;
1537 int width;
1538 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1539 + term->tl_scrollback.ga_len;
1540
1541 ga_init2(&ga, 1, 100);
1542 for (pos.col = 0; pos.col < len; pos.col += width)
1543 {
1544 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1545 {
1546 width = 1;
1547 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1548 if (ga_grow(&ga, 1) == OK)
1549 ga.ga_len += utf_char2bytes(' ',
1550 (char_u *)ga.ga_data + ga.ga_len);
1551 }
1552 else
1553 {
1554 width = cell.width;
1555
1556 cell2cellattr(&cell, &p[pos.col]);
1557
1558 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1559 {
1560 int i;
1561 int c;
1562
1563 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1564 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1565 (char_u *)ga.ga_data + ga.ga_len);
1566 }
1567 }
1568 }
1569 line->sb_cols = len;
1570 line->sb_cells = p;
1571 line->sb_fill_attr = new_fill_attr;
1572 fill_attr = new_fill_attr;
1573 ++term->tl_scrollback.ga_len;
1574
1575 if (ga_grow(&ga, 1) == FAIL)
1576 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1577 else
1578 {
1579 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1580 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1581 }
1582 ga_clear(&ga);
1583 }
1584 else
1585 vim_free(p);
1586 }
1587 }
1588
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001589 term->tl_dirty_snapshot = FALSE;
1590#ifdef FEAT_TIMERS
1591 term->tl_timer_set = FALSE;
1592#endif
1593
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001594 /* Obtain the current background color. */
1595 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1596 &term->tl_default_color.fg, &term->tl_default_color.bg);
1597
1598 FOR_ALL_WINDOWS(wp)
1599 {
1600 if (wp->w_buffer == term->tl_buffer)
1601 {
1602 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1603 wp->w_cursor.col = 0;
1604 wp->w_valid = 0;
1605 if (wp->w_cursor.lnum >= wp->w_height)
1606 {
1607 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1608
1609 if (wp->w_topline < min_topline)
1610 wp->w_topline = min_topline;
1611 }
1612 redraw_win_later(wp, NOT_VALID);
1613 }
1614 }
1615}
1616
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001617#if defined(FEAT_TIMERS) || defined(PROTO)
1618/*
1619 * Check if any terminal timer expired. If so, copy text from the terminal to
1620 * the buffer.
1621 * Return the time until the next timer will expire.
1622 */
1623 int
1624term_check_timers(int next_due_arg, proftime_T *now)
1625{
1626 term_T *term;
1627 int next_due = next_due_arg;
1628
1629 for (term = first_term; term != NULL; term = term->tl_next)
1630 {
1631 if (term->tl_timer_set && !term->tl_normal_mode)
1632 {
1633 long this_due = proftime_time_left(&term->tl_timer_due, now);
1634
1635 if (this_due <= 1)
1636 {
1637 term->tl_timer_set = FALSE;
1638 move_terminal_to_buffer(term);
1639 }
1640 else if (next_due == -1 || next_due > this_due)
1641 next_due = this_due;
1642 }
1643 }
1644
1645 return next_due;
1646}
1647#endif
1648
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001649 static void
1650set_terminal_mode(term_T *term, int normal_mode)
1651{
1652 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001653 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001654 if (term->tl_buffer == curbuf)
1655 maketitle();
1656}
1657
1658/*
1659 * Called after the job if finished and Terminal mode is not active:
1660 * Move the vterm contents into the scrollback buffer and free the vterm.
1661 */
1662 static void
1663cleanup_vterm(term_T *term)
1664{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001665 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001666 move_terminal_to_buffer(term);
1667 term_free_vterm(term);
1668 set_terminal_mode(term, FALSE);
1669}
1670
1671/*
1672 * Switch from Terminal-Job mode to Terminal-Normal mode.
1673 * Suspends updating the terminal window.
1674 */
1675 static void
1676term_enter_normal_mode(void)
1677{
1678 term_T *term = curbuf->b_term;
1679
1680 /* Append the current terminal contents to the buffer. */
1681 move_terminal_to_buffer(term);
1682
1683 set_terminal_mode(term, TRUE);
1684
1685 /* Move the window cursor to the position of the cursor in the
1686 * terminal. */
1687 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1688 + term->tl_cursor_pos.row + 1;
1689 check_cursor();
1690 coladvance(term->tl_cursor_pos.col);
1691
1692 /* Display the same lines as in the terminal. */
1693 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1694}
1695
1696/*
1697 * Returns TRUE if the current window contains a terminal and we are in
1698 * Terminal-Normal mode.
1699 */
1700 int
1701term_in_normal_mode(void)
1702{
1703 term_T *term = curbuf->b_term;
1704
1705 return term != NULL && term->tl_normal_mode;
1706}
1707
1708/*
1709 * Switch from Terminal-Normal mode to Terminal-Job mode.
1710 * Restores updating the terminal window.
1711 */
1712 void
1713term_enter_job_mode()
1714{
1715 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001716
1717 set_terminal_mode(term, FALSE);
1718
1719 if (term->tl_channel_closed)
1720 cleanup_vterm(term);
1721 redraw_buf_and_status_later(curbuf, NOT_VALID);
1722}
1723
1724/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001725 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001726 * Note: while waiting a terminal may be closed and freed if the channel is
1727 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001728 */
1729 static int
1730term_vgetc()
1731{
1732 int c;
1733 int save_State = State;
1734
1735 State = TERMINAL;
1736 got_int = FALSE;
1737#ifdef WIN3264
1738 ctrl_break_was_pressed = FALSE;
1739#endif
1740 c = vgetc();
1741 got_int = FALSE;
1742 State = save_State;
1743 return c;
1744}
1745
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001746static int mouse_was_outside = FALSE;
1747
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001748/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001749 * Send keys to terminal.
1750 * Return FAIL when the key needs to be handled in Normal mode.
1751 * Return OK when the key was dropped or sent to the terminal.
1752 */
1753 int
1754send_keys_to_term(term_T *term, int c, int typed)
1755{
1756 char msg[KEY_BUF_LEN];
1757 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001758 int dragging_outside = FALSE;
1759
1760 /* Catch keys that need to be handled as in Normal mode. */
1761 switch (c)
1762 {
1763 case NUL:
1764 case K_ZERO:
1765 if (typed)
1766 stuffcharReadbuff(c);
1767 return FAIL;
1768
Bram Moolenaar231a2db2018-05-06 13:53:50 +02001769 case K_TABLINE:
1770 stuffcharReadbuff(c);
1771 return FAIL;
1772
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001773 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001774 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001775 return FAIL;
1776
1777 case K_LEFTDRAG:
1778 case K_MIDDLEDRAG:
1779 case K_RIGHTDRAG:
1780 case K_X1DRAG:
1781 case K_X2DRAG:
1782 dragging_outside = mouse_was_outside;
1783 /* FALLTHROUGH */
1784 case K_LEFTMOUSE:
1785 case K_LEFTMOUSE_NM:
1786 case K_LEFTRELEASE:
1787 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001788 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001789 case K_MIDDLEMOUSE:
1790 case K_MIDDLERELEASE:
1791 case K_RIGHTMOUSE:
1792 case K_RIGHTRELEASE:
1793 case K_X1MOUSE:
1794 case K_X1RELEASE:
1795 case K_X2MOUSE:
1796 case K_X2RELEASE:
1797
1798 case K_MOUSEUP:
1799 case K_MOUSEDOWN:
1800 case K_MOUSELEFT:
1801 case K_MOUSERIGHT:
1802 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001803 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001804 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001805 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001806 || dragging_outside)
1807 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001808 /* click or scroll outside the current window or on status line
1809 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001810 if (typed)
1811 {
1812 stuffcharReadbuff(c);
1813 mouse_was_outside = TRUE;
1814 }
1815 return FAIL;
1816 }
1817 }
1818 if (typed)
1819 mouse_was_outside = FALSE;
1820
1821 /* Convert the typed key to a sequence of bytes for the job. */
1822 len = term_convert_key(term, c, msg);
1823 if (len > 0)
1824 /* TODO: if FAIL is returned, stop? */
1825 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1826 (char_u *)msg, (int)len, NULL);
1827
1828 return OK;
1829}
1830
1831 static void
1832position_cursor(win_T *wp, VTermPos *pos)
1833{
1834 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1835 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1836 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1837}
1838
1839/*
1840 * Handle CTRL-W "": send register contents to the job.
1841 */
1842 static void
1843term_paste_register(int prev_c UNUSED)
1844{
1845 int c;
1846 list_T *l;
1847 listitem_T *item;
1848 long reglen = 0;
1849 int type;
1850
1851#ifdef FEAT_CMDL_INFO
1852 if (add_to_showcmd(prev_c))
1853 if (add_to_showcmd('"'))
1854 out_flush();
1855#endif
1856 c = term_vgetc();
1857#ifdef FEAT_CMDL_INFO
1858 clear_showcmd();
1859#endif
1860 if (!term_use_loop())
1861 /* job finished while waiting for a character */
1862 return;
1863
1864 /* CTRL-W "= prompt for expression to evaluate. */
1865 if (c == '=' && get_expr_register() != '=')
1866 return;
1867 if (!term_use_loop())
1868 /* job finished while waiting for a character */
1869 return;
1870
1871 l = (list_T *)get_reg_contents(c, GREG_LIST);
1872 if (l != NULL)
1873 {
1874 type = get_reg_type(c, &reglen);
1875 for (item = l->lv_first; item != NULL; item = item->li_next)
1876 {
1877 char_u *s = get_tv_string(&item->li_tv);
1878#ifdef WIN3264
1879 char_u *tmp = s;
1880
1881 if (!enc_utf8 && enc_codepage > 0)
1882 {
1883 WCHAR *ret = NULL;
1884 int length = 0;
1885
1886 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1887 (int)STRLEN(s), &ret, &length);
1888 if (ret != NULL)
1889 {
1890 WideCharToMultiByte_alloc(CP_UTF8, 0,
1891 ret, length, (char **)&s, &length, 0, 0);
1892 vim_free(ret);
1893 }
1894 }
1895#endif
1896 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1897 s, (int)STRLEN(s), NULL);
1898#ifdef WIN3264
1899 if (tmp != s)
1900 vim_free(s);
1901#endif
1902
1903 if (item->li_next != NULL || type == MLINE)
1904 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1905 (char_u *)"\r", 1, NULL);
1906 }
1907 list_free(l);
1908 }
1909}
1910
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001911/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001912 * Return TRUE when waiting for a character in the terminal, the cursor of the
1913 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001914 */
1915 int
1916terminal_is_active()
1917{
1918 return in_terminal_loop != NULL;
1919}
1920
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001921#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001922 cursorentry_T *
1923term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1924{
1925 term_T *term = in_terminal_loop;
1926 static cursorentry_T entry;
1927
1928 vim_memset(&entry, 0, sizeof(entry));
1929 entry.shape = entry.mshape =
1930 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1931 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1932 SHAPE_BLOCK;
1933 entry.percentage = 20;
1934 if (term->tl_cursor_blink)
1935 {
1936 entry.blinkwait = 700;
1937 entry.blinkon = 400;
1938 entry.blinkoff = 250;
1939 }
1940 *fg = gui.back_pixel;
1941 if (term->tl_cursor_color == NULL)
1942 *bg = gui.norm_pixel;
1943 else
1944 *bg = color_name2handle(term->tl_cursor_color);
1945 entry.name = "n";
1946 entry.used_for = SHAPE_CURSOR;
1947
1948 return &entry;
1949}
1950#endif
1951
Bram Moolenaard317b382018-02-08 22:33:31 +01001952 static void
1953may_output_cursor_props(void)
1954{
1955 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1956 || last_set_cursor_shape != desired_cursor_shape
1957 || last_set_cursor_blink != desired_cursor_blink)
1958 {
1959 last_set_cursor_color = desired_cursor_color;
1960 last_set_cursor_shape = desired_cursor_shape;
1961 last_set_cursor_blink = desired_cursor_blink;
1962 term_cursor_color(desired_cursor_color);
1963 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1964 /* this will restore the initial cursor style, if possible */
1965 ui_cursor_shape_forced(TRUE);
1966 else
1967 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1968 }
1969}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001970
Bram Moolenaard317b382018-02-08 22:33:31 +01001971/*
1972 * Set the cursor color and shape, if not last set to these.
1973 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001974 static void
1975may_set_cursor_props(term_T *term)
1976{
1977#ifdef FEAT_GUI
1978 /* For the GUI the cursor properties are obtained with
1979 * term_get_cursor_shape(). */
1980 if (gui.in_use)
1981 return;
1982#endif
1983 if (in_terminal_loop == term)
1984 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001985 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001986 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001987 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001988 desired_cursor_color = (char_u *)"";
1989 desired_cursor_shape = term->tl_cursor_shape;
1990 desired_cursor_blink = term->tl_cursor_blink;
1991 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001992 }
1993}
1994
Bram Moolenaard317b382018-02-08 22:33:31 +01001995/*
1996 * Reset the desired cursor properties and restore them when needed.
1997 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001998 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001999prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002000{
2001#ifdef FEAT_GUI
2002 if (gui.in_use)
2003 return;
2004#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01002005 desired_cursor_color = (char_u *)"";
2006 desired_cursor_shape = -1;
2007 desired_cursor_blink = -1;
2008 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002009}
2010
2011/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002012 * Returns TRUE if the current window contains a terminal and we are sending
2013 * keys to the job.
2014 * If "check_job_status" is TRUE update the job status.
2015 */
2016 static int
2017term_use_loop_check(int check_job_status)
2018{
2019 term_T *term = curbuf->b_term;
2020
2021 return term != NULL
2022 && !term->tl_normal_mode
2023 && term->tl_vterm != NULL
2024 && term_job_running_check(term, check_job_status);
2025}
2026
2027/*
2028 * Returns TRUE if the current window contains a terminal and we are sending
2029 * keys to the job.
2030 */
2031 int
2032term_use_loop(void)
2033{
2034 return term_use_loop_check(FALSE);
2035}
2036
2037/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002038 * Called when entering a window with the mouse. If this is a terminal window
2039 * we may want to change state.
2040 */
2041 void
2042term_win_entered()
2043{
2044 term_T *term = curbuf->b_term;
2045
2046 if (term != NULL)
2047 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002048 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002049 {
2050 reset_VIsual_and_resel();
2051 if (State & INSERT)
2052 stop_insert_mode = TRUE;
2053 }
2054 mouse_was_outside = FALSE;
2055 enter_mouse_col = mouse_col;
2056 enter_mouse_row = mouse_row;
2057 }
2058}
2059
2060/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002061 * Wait for input and send it to the job.
2062 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2063 * when there is no more typahead.
2064 * Return when the start of a CTRL-W command is typed or anything else that
2065 * should be handled as a Normal mode command.
2066 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2067 * the terminal was closed.
2068 */
2069 int
2070terminal_loop(int blocking)
2071{
2072 int c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002073 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002074 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002075#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002076 int tty_fd = curbuf->b_term->tl_job->jv_channel
2077 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002078#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01002079 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002080
2081 /* Remember the terminal we are sending keys to. However, the terminal
2082 * might be closed while waiting for a character, e.g. typing "exit" in a
2083 * shell and ++close was used. Therefore use curbuf->b_term instead of a
2084 * stored reference. */
2085 in_terminal_loop = curbuf->b_term;
2086
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002087 if (*curwin->w_p_twk != NUL)
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002088 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002089 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2090 may_set_cursor_props(curbuf->b_term);
2091
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002092 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002093 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002094#ifdef FEAT_GUI
2095 if (!curbuf->b_term->tl_system)
2096#endif
2097 /* TODO: skip screen update when handling a sequence of keys. */
2098 /* Repeat redrawing in case a message is received while redrawing.
2099 */
2100 while (must_redraw != 0)
2101 if (update_screen(0) == FAIL)
2102 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002103 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002104 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002105
2106 c = term_vgetc();
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002107 if (!term_use_loop_check(TRUE))
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002108 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002109 /* Job finished while waiting for a character. Push back the
2110 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002111 if (c != K_IGNORE)
2112 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002113 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002114 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002115 if (c == K_IGNORE)
2116 continue;
2117
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002118#ifdef UNIX
2119 /*
2120 * The shell or another program may change the tty settings. Getting
2121 * them for every typed character is a bit of overhead, but it's needed
2122 * for the first character typed, e.g. when Vim starts in a shell.
2123 */
2124 if (isatty(tty_fd))
2125 {
2126 ttyinfo_T info;
2127
2128 /* Get the current backspace character of the pty. */
2129 if (get_tty_info(tty_fd, &info) == OK)
2130 term_backspace_char = info.backspace;
2131 }
2132#endif
2133
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002134#ifdef WIN3264
2135 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2136 * Use CTRL-BREAK to kill the job. */
2137 if (ctrl_break_was_pressed)
2138 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2139#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002140 /* Was either CTRL-W (termwinkey) or CTRL-\ pressed?
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002141 * Not in a system terminal. */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002142 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002143#ifdef FEAT_GUI
2144 && !curbuf->b_term->tl_system
2145#endif
2146 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002147 {
2148 int prev_c = c;
2149
2150#ifdef FEAT_CMDL_INFO
2151 if (add_to_showcmd(c))
2152 out_flush();
2153#endif
2154 c = term_vgetc();
2155#ifdef FEAT_CMDL_INFO
2156 clear_showcmd();
2157#endif
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002158 if (!term_use_loop_check(TRUE))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002159 /* job finished while waiting for a character */
2160 break;
2161
2162 if (prev_c == Ctrl_BSL)
2163 {
2164 if (c == Ctrl_N)
2165 {
2166 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2167 term_enter_normal_mode();
2168 ret = FAIL;
2169 goto theend;
2170 }
2171 /* Send both keys to the terminal. */
2172 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2173 }
2174 else if (c == Ctrl_C)
2175 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002176 /* "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002177 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2178 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002179 else if (termwinkey == 0 && c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002180 {
2181 /* "CTRL-W .": send CTRL-W to the job */
2182 c = Ctrl_W;
2183 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002184 else if (termwinkey == 0 && c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002185 {
2186 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2187 c = Ctrl_BSL;
2188 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002189 else if (c == 'N')
2190 {
2191 /* CTRL-W N : go to Terminal-Normal mode. */
2192 term_enter_normal_mode();
2193 ret = FAIL;
2194 goto theend;
2195 }
2196 else if (c == '"')
2197 {
2198 term_paste_register(prev_c);
2199 continue;
2200 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002201 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002202 {
2203 stuffcharReadbuff(Ctrl_W);
2204 stuffcharReadbuff(c);
2205 ret = OK;
2206 goto theend;
2207 }
2208 }
2209# ifdef WIN3264
2210 if (!enc_utf8 && has_mbyte && c >= 0x80)
2211 {
2212 WCHAR wc;
2213 char_u mb[3];
2214
2215 mb[0] = (unsigned)c >> 8;
2216 mb[1] = c;
2217 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2218 c = wc;
2219 }
2220# endif
2221 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2222 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002223 if (c == K_MOUSEMOVE)
2224 /* We are sure to come back here, don't reset the cursor color
2225 * and shape to avoid flickering. */
2226 restore_cursor = FALSE;
2227
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002228 ret = OK;
2229 goto theend;
2230 }
2231 }
2232 ret = FAIL;
2233
2234theend:
2235 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002236 if (restore_cursor)
2237 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002238
2239 /* Move a snapshot of the screen contents to the buffer, so that completion
2240 * works in other buffers. */
2241 if (curbuf->b_term != NULL)
2242 move_terminal_to_buffer(curbuf->b_term);
2243
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002244 return ret;
2245}
2246
2247/*
2248 * Called when a job has finished.
2249 * This updates the title and status, but does not close the vterm, because
2250 * there might still be pending output in the channel.
2251 */
2252 void
2253term_job_ended(job_T *job)
2254{
2255 term_T *term;
2256 int did_one = FALSE;
2257
2258 for (term = first_term; term != NULL; term = term->tl_next)
2259 if (term->tl_job == job)
2260 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002261 VIM_CLEAR(term->tl_title);
2262 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002263 redraw_buf_and_status_later(term->tl_buffer, VALID);
2264 did_one = TRUE;
2265 }
2266 if (did_one)
2267 redraw_statuslines();
2268 if (curbuf->b_term != NULL)
2269 {
2270 if (curbuf->b_term->tl_job == job)
2271 maketitle();
2272 update_cursor(curbuf->b_term, TRUE);
2273 }
2274}
2275
2276 static void
2277may_toggle_cursor(term_T *term)
2278{
2279 if (in_terminal_loop == term)
2280 {
2281 if (term->tl_cursor_visible)
2282 cursor_on();
2283 else
2284 cursor_off();
2285 }
2286}
2287
2288/*
2289 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002290 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002291 */
2292 static int
2293color2index(VTermColor *color, int fg, int *boldp)
2294{
2295 int red = color->red;
2296 int blue = color->blue;
2297 int green = color->green;
2298
Bram Moolenaar46359e12017-11-29 22:33:38 +01002299 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002300 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002301 /* First 16 colors and default: use the ANSI index, because these
2302 * colors can be redefined. */
2303 if (t_colors >= 16)
2304 return color->ansi_index;
2305 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002306 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002307 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002308 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002309 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2310 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2311 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002312 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002313 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2314 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2315 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2316 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2317 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2318 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2319 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2320 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2321 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2322 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2323 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002324 }
2325 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002326
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002327 if (t_colors >= 256)
2328 {
2329 if (red == blue && red == green)
2330 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002331 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002332 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002333 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2334 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2335 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002336 int i;
2337
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002338 if (red < 5)
2339 return 17; /* 00/00/00 */
2340 if (red > 245) /* ff/ff/ff */
2341 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002342 for (i = 0; i < 23; ++i)
2343 if (red < cutoff[i])
2344 return i + 233;
2345 return 256;
2346 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002347 {
2348 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2349 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002350
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002351 /* 216-color cube */
2352 for (ri = 0; ri < 5; ++ri)
2353 if (red < cutoff[ri])
2354 break;
2355 for (gi = 0; gi < 5; ++gi)
2356 if (green < cutoff[gi])
2357 break;
2358 for (bi = 0; bi < 5; ++bi)
2359 if (blue < cutoff[bi])
2360 break;
2361 return 17 + ri * 36 + gi * 6 + bi;
2362 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002363 }
2364 return 0;
2365}
2366
2367/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002368 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002369 */
2370 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002371vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002372{
2373 int attr = 0;
2374
2375 if (cellattrs.bold)
2376 attr |= HL_BOLD;
2377 if (cellattrs.underline)
2378 attr |= HL_UNDERLINE;
2379 if (cellattrs.italic)
2380 attr |= HL_ITALIC;
2381 if (cellattrs.strike)
2382 attr |= HL_STRIKETHROUGH;
2383 if (cellattrs.reverse)
2384 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002385 return attr;
2386}
2387
2388/*
2389 * Store Vterm attributes in "cell" from highlight flags.
2390 */
2391 static void
2392hl2vtermAttr(int attr, cellattr_T *cell)
2393{
2394 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2395 if (attr & HL_BOLD)
2396 cell->attrs.bold = 1;
2397 if (attr & HL_UNDERLINE)
2398 cell->attrs.underline = 1;
2399 if (attr & HL_ITALIC)
2400 cell->attrs.italic = 1;
2401 if (attr & HL_STRIKETHROUGH)
2402 cell->attrs.strike = 1;
2403 if (attr & HL_INVERSE)
2404 cell->attrs.reverse = 1;
2405}
2406
2407/*
2408 * Convert the attributes of a vterm cell into an attribute index.
2409 */
2410 static int
2411cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2412{
2413 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002414
2415#ifdef FEAT_GUI
2416 if (gui.in_use)
2417 {
2418 guicolor_T fg, bg;
2419
2420 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2421 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2422 return get_gui_attr_idx(attr, fg, bg);
2423 }
2424 else
2425#endif
2426#ifdef FEAT_TERMGUICOLORS
2427 if (p_tgc)
2428 {
2429 guicolor_T fg, bg;
2430
2431 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2432 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2433
2434 return get_tgc_attr_idx(attr, fg, bg);
2435 }
2436 else
2437#endif
2438 {
2439 int bold = MAYBE;
2440 int fg = color2index(&cellfg, TRUE, &bold);
2441 int bg = color2index(&cellbg, FALSE, &bold);
2442
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002443 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002444 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002445 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002446 if (fg == 0 && term_default_cterm_fg >= 0)
2447 fg = term_default_cterm_fg + 1;
2448 if (bg == 0 && term_default_cterm_bg >= 0)
2449 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002450 }
2451
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002452 /* with 8 colors set the bold attribute to get a bright foreground */
2453 if (bold == TRUE)
2454 attr |= HL_BOLD;
2455 return get_cterm_attr_idx(attr, fg, bg);
2456 }
2457 return 0;
2458}
2459
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002460 static void
2461set_dirty_snapshot(term_T *term)
2462{
2463 term->tl_dirty_snapshot = TRUE;
2464#ifdef FEAT_TIMERS
2465 if (!term->tl_normal_mode)
2466 {
2467 /* Update the snapshot after 100 msec of not getting updates. */
2468 profile_setlimit(100L, &term->tl_timer_due);
2469 term->tl_timer_set = TRUE;
2470 }
2471#endif
2472}
2473
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002474 static int
2475handle_damage(VTermRect rect, void *user)
2476{
2477 term_T *term = (term_T *)user;
2478
2479 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2480 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002481 set_dirty_snapshot(term);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002482 redraw_buf_later(term->tl_buffer, SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002483 return 1;
2484}
2485
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002486 static void
2487term_scroll_up(term_T *term, int start_row, int count)
2488{
2489 win_T *wp;
2490 VTermColor fg, bg;
2491 VTermScreenCellAttrs attr;
2492 int clear_attr;
2493
2494 /* Set the color to clear lines with. */
2495 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2496 &fg, &bg);
2497 vim_memset(&attr, 0, sizeof(attr));
2498 clear_attr = cell2attr(attr, fg, bg);
2499
2500 FOR_ALL_WINDOWS(wp)
2501 {
2502 if (wp->w_buffer == term->tl_buffer)
2503 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
2504 }
2505}
2506
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002507 static int
2508handle_moverect(VTermRect dest, VTermRect src, void *user)
2509{
2510 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002511 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002512
2513 /* Scrolling up is done much more efficiently by deleting lines instead of
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002514 * redrawing the text. But avoid doing this multiple times, postpone until
2515 * the redraw happens. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002516 if (dest.start_col == src.start_col
2517 && dest.end_col == src.end_col
2518 && dest.start_row < src.start_row)
2519 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002520 if (dest.start_row == 0)
2521 term->tl_postponed_scroll += count;
2522 else
2523 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002524 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002525
2526 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2527 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002528 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002529
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002530 /* Note sure if the scrolling will work correctly, let's do a complete
2531 * redraw later. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002532 redraw_buf_later(term->tl_buffer, NOT_VALID);
2533 return 1;
2534}
2535
2536 static int
2537handle_movecursor(
2538 VTermPos pos,
2539 VTermPos oldpos UNUSED,
2540 int visible,
2541 void *user)
2542{
2543 term_T *term = (term_T *)user;
2544 win_T *wp;
2545
2546 term->tl_cursor_pos = pos;
2547 term->tl_cursor_visible = visible;
2548
2549 FOR_ALL_WINDOWS(wp)
2550 {
2551 if (wp->w_buffer == term->tl_buffer)
2552 position_cursor(wp, &pos);
2553 }
2554 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2555 {
2556 may_toggle_cursor(term);
2557 update_cursor(term, term->tl_cursor_visible);
2558 }
2559
2560 return 1;
2561}
2562
2563 static int
2564handle_settermprop(
2565 VTermProp prop,
2566 VTermValue *value,
2567 void *user)
2568{
2569 term_T *term = (term_T *)user;
2570
2571 switch (prop)
2572 {
2573 case VTERM_PROP_TITLE:
2574 vim_free(term->tl_title);
2575 /* a blank title isn't useful, make it empty, so that "running" is
2576 * displayed */
2577 if (*skipwhite((char_u *)value->string) == NUL)
2578 term->tl_title = NULL;
2579#ifdef WIN3264
2580 else if (!enc_utf8 && enc_codepage > 0)
2581 {
2582 WCHAR *ret = NULL;
2583 int length = 0;
2584
2585 MultiByteToWideChar_alloc(CP_UTF8, 0,
2586 (char*)value->string, (int)STRLEN(value->string),
2587 &ret, &length);
2588 if (ret != NULL)
2589 {
2590 WideCharToMultiByte_alloc(enc_codepage, 0,
2591 ret, length, (char**)&term->tl_title,
2592 &length, 0, 0);
2593 vim_free(ret);
2594 }
2595 }
2596#endif
2597 else
2598 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002599 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002600 if (term == curbuf->b_term)
2601 maketitle();
2602 break;
2603
2604 case VTERM_PROP_CURSORVISIBLE:
2605 term->tl_cursor_visible = value->boolean;
2606 may_toggle_cursor(term);
2607 out_flush();
2608 break;
2609
2610 case VTERM_PROP_CURSORBLINK:
2611 term->tl_cursor_blink = value->boolean;
2612 may_set_cursor_props(term);
2613 break;
2614
2615 case VTERM_PROP_CURSORSHAPE:
2616 term->tl_cursor_shape = value->number;
2617 may_set_cursor_props(term);
2618 break;
2619
2620 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002621 if (desired_cursor_color == term->tl_cursor_color)
2622 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002623 vim_free(term->tl_cursor_color);
2624 if (*value->string == NUL)
2625 term->tl_cursor_color = NULL;
2626 else
2627 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2628 may_set_cursor_props(term);
2629 break;
2630
2631 case VTERM_PROP_ALTSCREEN:
2632 /* TODO: do anything else? */
2633 term->tl_using_altscreen = value->boolean;
2634 break;
2635
2636 default:
2637 break;
2638 }
2639 /* Always return 1, otherwise vterm doesn't store the value internally. */
2640 return 1;
2641}
2642
2643/*
2644 * The job running in the terminal resized the terminal.
2645 */
2646 static int
2647handle_resize(int rows, int cols, void *user)
2648{
2649 term_T *term = (term_T *)user;
2650 win_T *wp;
2651
2652 term->tl_rows = rows;
2653 term->tl_cols = cols;
2654 if (term->tl_vterm_size_changed)
2655 /* Size was set by vterm_set_size(), don't set the window size. */
2656 term->tl_vterm_size_changed = FALSE;
2657 else
2658 {
2659 FOR_ALL_WINDOWS(wp)
2660 {
2661 if (wp->w_buffer == term->tl_buffer)
2662 {
2663 win_setheight_win(rows, wp);
2664 win_setwidth_win(cols, wp);
2665 }
2666 }
2667 redraw_buf_later(term->tl_buffer, NOT_VALID);
2668 }
2669 return 1;
2670}
2671
2672/*
2673 * Handle a line that is pushed off the top of the screen.
2674 */
2675 static int
2676handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2677{
2678 term_T *term = (term_T *)user;
2679
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002680 /* First remove the lines that were appended before, the pushed line goes
2681 * above it. */
2682 cleanup_scrollback(term);
2683
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002684 /* If the number of lines that are stored goes over 'termscrollback' then
2685 * delete the first 10%. */
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002686 if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002687 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002688 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002689 int i;
2690
2691 curbuf = term->tl_buffer;
2692 for (i = 0; i < todo; ++i)
2693 {
2694 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2695 ml_delete(1, FALSE);
2696 }
2697 curbuf = curwin->w_buffer;
2698
2699 term->tl_scrollback.ga_len -= todo;
2700 mch_memmove(term->tl_scrollback.ga_data,
2701 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2702 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
2703 }
2704
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002705 if (ga_grow(&term->tl_scrollback, 1) == OK)
2706 {
2707 cellattr_T *p = NULL;
2708 int len = 0;
2709 int i;
2710 int c;
2711 int col;
2712 sb_line_T *line;
2713 garray_T ga;
2714 cellattr_T fill_attr = term->tl_default_color;
2715
2716 /* do not store empty cells at the end */
2717 for (i = 0; i < cols; ++i)
2718 if (cells[i].chars[0] != 0)
2719 len = i + 1;
2720 else
2721 cell2cellattr(&cells[i], &fill_attr);
2722
2723 ga_init2(&ga, 1, 100);
2724 if (len > 0)
2725 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2726 if (p != NULL)
2727 {
2728 for (col = 0; col < len; col += cells[col].width)
2729 {
2730 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2731 {
2732 ga.ga_len = 0;
2733 break;
2734 }
2735 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2736 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2737 (char_u *)ga.ga_data + ga.ga_len);
2738 cell2cellattr(&cells[col], &p[col]);
2739 }
2740 }
2741 if (ga_grow(&ga, 1) == FAIL)
2742 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2743 else
2744 {
2745 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2746 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2747 }
2748 ga_clear(&ga);
2749
2750 line = (sb_line_T *)term->tl_scrollback.ga_data
2751 + term->tl_scrollback.ga_len;
2752 line->sb_cols = len;
2753 line->sb_cells = p;
2754 line->sb_fill_attr = fill_attr;
2755 ++term->tl_scrollback.ga_len;
2756 ++term->tl_scrollback_scrolled;
2757 }
2758 return 0; /* ignored */
2759}
2760
2761static VTermScreenCallbacks screen_callbacks = {
2762 handle_damage, /* damage */
2763 handle_moverect, /* moverect */
2764 handle_movecursor, /* movecursor */
2765 handle_settermprop, /* settermprop */
2766 NULL, /* bell */
2767 handle_resize, /* resize */
2768 handle_pushline, /* sb_pushline */
2769 NULL /* sb_popline */
2770};
2771
2772/*
2773 * Called when a channel has been closed.
2774 * If this was a channel for a terminal window then finish it up.
2775 */
2776 void
2777term_channel_closed(channel_T *ch)
2778{
2779 term_T *term;
2780 int did_one = FALSE;
2781
2782 for (term = first_term; term != NULL; term = term->tl_next)
2783 if (term->tl_job == ch->ch_job)
2784 {
2785 term->tl_channel_closed = TRUE;
2786 did_one = TRUE;
2787
Bram Moolenaard23a8232018-02-10 18:45:26 +01002788 VIM_CLEAR(term->tl_title);
2789 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar402c8392018-05-06 22:01:42 +02002790#ifdef WIN3264
2791 if (term->tl_out_fd != NULL)
2792 {
2793 fclose(term->tl_out_fd);
2794 term->tl_out_fd = NULL;
2795 }
2796#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002797
2798 /* Unless in Terminal-Normal mode: clear the vterm. */
2799 if (!term->tl_normal_mode)
2800 {
2801 int fnum = term->tl_buffer->b_fnum;
2802
2803 cleanup_vterm(term);
2804
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002805 if (term->tl_finish == TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002806 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002807 aco_save_T aco;
2808
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002809 /* ++close or term_finish == "close" */
2810 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002811 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002812 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002813 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002814 break;
2815 }
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002816 if (term->tl_finish == TL_FINISH_OPEN
2817 && term->tl_buffer->b_nwindows == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002818 {
2819 char buf[50];
2820
2821 /* TODO: use term_opencmd */
2822 ch_log(NULL, "terminal job finished, opening window");
2823 vim_snprintf(buf, sizeof(buf),
2824 term->tl_opencmd == NULL
2825 ? "botright sbuf %d"
2826 : (char *)term->tl_opencmd, fnum);
2827 do_cmdline_cmd((char_u *)buf);
2828 }
2829 else
2830 ch_log(NULL, "terminal job finished");
2831 }
2832
2833 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2834 }
2835 if (did_one)
2836 {
2837 redraw_statuslines();
2838
2839 /* Need to break out of vgetc(). */
2840 ins_char_typebuf(K_IGNORE);
2841 typebuf_was_filled = TRUE;
2842
2843 term = curbuf->b_term;
2844 if (term != NULL)
2845 {
2846 if (term->tl_job == ch->ch_job)
2847 maketitle();
2848 update_cursor(term, term->tl_cursor_visible);
2849 }
2850 }
2851}
2852
2853/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002854 * Fill one screen line from a line of the terminal.
2855 * Advances "pos" to past the last column.
2856 */
2857 static void
2858term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2859{
2860 int off = screen_get_current_line_off();
2861
2862 for (pos->col = 0; pos->col < max_col; )
2863 {
2864 VTermScreenCell cell;
2865 int c;
2866
2867 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2868 vim_memset(&cell, 0, sizeof(cell));
2869
2870 c = cell.chars[0];
2871 if (c == NUL)
2872 {
2873 ScreenLines[off] = ' ';
2874 if (enc_utf8)
2875 ScreenLinesUC[off] = NUL;
2876 }
2877 else
2878 {
2879 if (enc_utf8)
2880 {
2881 int i;
2882
2883 /* composing chars */
2884 for (i = 0; i < Screen_mco
2885 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2886 {
2887 ScreenLinesC[i][off] = cell.chars[i + 1];
2888 if (cell.chars[i + 1] == 0)
2889 break;
2890 }
2891 if (c >= 0x80 || (Screen_mco > 0
2892 && ScreenLinesC[0][off] != 0))
2893 {
2894 ScreenLines[off] = ' ';
2895 ScreenLinesUC[off] = c;
2896 }
2897 else
2898 {
2899 ScreenLines[off] = c;
2900 ScreenLinesUC[off] = NUL;
2901 }
2902 }
2903#ifdef WIN3264
2904 else if (has_mbyte && c >= 0x80)
2905 {
2906 char_u mb[MB_MAXBYTES+1];
2907 WCHAR wc = c;
2908
2909 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2910 (char*)mb, 2, 0, 0) > 1)
2911 {
2912 ScreenLines[off] = mb[0];
2913 ScreenLines[off + 1] = mb[1];
2914 cell.width = mb_ptr2cells(mb);
2915 }
2916 else
2917 ScreenLines[off] = c;
2918 }
2919#endif
2920 else
2921 ScreenLines[off] = c;
2922 }
2923 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2924
2925 ++pos->col;
2926 ++off;
2927 if (cell.width == 2)
2928 {
2929 if (enc_utf8)
2930 ScreenLinesUC[off] = NUL;
2931
2932 /* don't set the second byte to NUL for a DBCS encoding, it
2933 * has been set above */
2934 if (enc_utf8 || !has_mbyte)
2935 ScreenLines[off] = NUL;
2936
2937 ++pos->col;
2938 ++off;
2939 }
2940 }
2941}
2942
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002943#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002944 static void
2945update_system_term(term_T *term)
2946{
2947 VTermPos pos;
2948 VTermScreen *screen;
2949
2950 if (term->tl_vterm == NULL)
2951 return;
2952 screen = vterm_obtain_screen(term->tl_vterm);
2953
2954 /* Scroll up to make more room for terminal lines if needed. */
2955 while (term->tl_toprow > 0
2956 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2957 {
2958 int save_p_more = p_more;
2959
2960 p_more = FALSE;
2961 msg_row = Rows - 1;
2962 msg_puts((char_u *)"\n");
2963 p_more = save_p_more;
2964 --term->tl_toprow;
2965 }
2966
2967 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2968 && pos.row < Rows; ++pos.row)
2969 {
2970 if (pos.row < term->tl_rows)
2971 {
2972 int max_col = MIN(Columns, term->tl_cols);
2973
2974 term_line2screenline(screen, &pos, max_col);
2975 }
2976 else
2977 pos.col = 0;
2978
2979 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2980 }
2981
2982 term->tl_dirty_row_start = MAX_ROW;
2983 term->tl_dirty_row_end = 0;
2984 update_cursor(term, TRUE);
2985}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002986#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002987
2988/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002989 * Return TRUE if window "wp" is to be redrawn with term_update_window().
2990 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002991 * Terminal-Normal mode.
2992 */
2993 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002994term_do_update_window(win_T *wp)
2995{
2996 term_T *term = wp->w_buffer->b_term;
2997
2998 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
2999}
3000
3001/*
3002 * Called to update a window that contains an active terminal.
3003 */
3004 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003005term_update_window(win_T *wp)
3006{
3007 term_T *term = wp->w_buffer->b_term;
3008 VTerm *vterm;
3009 VTermScreen *screen;
3010 VTermState *state;
3011 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003012 int rows, cols;
3013 int newrows, newcols;
3014 int minsize;
3015 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003016
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003017 vterm = term->tl_vterm;
3018 screen = vterm_obtain_screen(vterm);
3019 state = vterm_obtain_state(vterm);
3020
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003021 /* We use NOT_VALID on a resize or scroll, redraw everything then. With
3022 * SOME_VALID only redraw what was marked dirty. */
3023 if (wp->w_redr_type > SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003024 {
3025 term->tl_dirty_row_start = 0;
3026 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003027
3028 if (term->tl_postponed_scroll > 0
3029 && term->tl_postponed_scroll < term->tl_rows / 3)
3030 /* Scrolling is usually faster than redrawing, when there are only
3031 * a few lines to scroll. */
3032 term_scroll_up(term, 0, term->tl_postponed_scroll);
3033 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003034 }
3035
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003036 /*
3037 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003038 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003039 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003040 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003041
Bram Moolenaar498c2562018-04-15 23:45:15 +02003042 newrows = 99999;
3043 newcols = 99999;
3044 FOR_ALL_WINDOWS(twp)
3045 {
3046 /* When more than one window shows the same terminal, use the
3047 * smallest size. */
3048 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003049 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02003050 newrows = MIN(newrows, twp->w_height);
3051 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003052 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02003053 }
3054 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3055 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3056
3057 if (term->tl_rows != newrows || term->tl_cols != newcols)
3058 {
3059
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003060
3061 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003062 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003063 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003064 newrows);
3065 term_report_winsize(term, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003066 }
3067
3068 /* The cursor may have been moved when resizing. */
3069 vterm_state_get_cursorpos(state, &pos);
3070 position_cursor(wp, &pos);
3071
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003072 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3073 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003074 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003075 if (pos.row < term->tl_rows)
3076 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003077 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003078
Bram Moolenaar13568252018-03-16 20:46:58 +01003079 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003080 }
3081 else
3082 pos.col = 0;
3083
Bram Moolenaarf118d482018-03-13 13:14:00 +01003084 screen_line(wp->w_winrow + pos.row
3085#ifdef FEAT_MENU
3086 + winbar_height(wp)
3087#endif
3088 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003089 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003090 term->tl_dirty_row_start = MAX_ROW;
3091 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003092}
3093
3094/*
3095 * Return TRUE if "wp" is a terminal window where the job has finished.
3096 */
3097 int
3098term_is_finished(buf_T *buf)
3099{
3100 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3101}
3102
3103/*
3104 * Return TRUE if "wp" is a terminal window where the job has finished or we
3105 * are in Terminal-Normal mode, thus we show the buffer contents.
3106 */
3107 int
3108term_show_buffer(buf_T *buf)
3109{
3110 term_T *term = buf->b_term;
3111
3112 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3113}
3114
3115/*
3116 * The current buffer is going to be changed. If there is terminal
3117 * highlighting remove it now.
3118 */
3119 void
3120term_change_in_curbuf(void)
3121{
3122 term_T *term = curbuf->b_term;
3123
3124 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3125 {
3126 free_scrollback(term);
3127 redraw_buf_later(term->tl_buffer, NOT_VALID);
3128
3129 /* The buffer is now like a normal buffer, it cannot be easily
3130 * abandoned when changed. */
3131 set_string_option_direct((char_u *)"buftype", -1,
3132 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3133 }
3134}
3135
3136/*
3137 * Get the screen attribute for a position in the buffer.
3138 * Use a negative "col" to get the filler background color.
3139 */
3140 int
3141term_get_attr(buf_T *buf, linenr_T lnum, int col)
3142{
3143 term_T *term = buf->b_term;
3144 sb_line_T *line;
3145 cellattr_T *cellattr;
3146
3147 if (lnum > term->tl_scrollback.ga_len)
3148 cellattr = &term->tl_default_color;
3149 else
3150 {
3151 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3152 if (col < 0 || col >= line->sb_cols)
3153 cellattr = &line->sb_fill_attr;
3154 else
3155 cellattr = line->sb_cells + col;
3156 }
3157 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3158}
3159
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003160/*
3161 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003162 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003163 */
3164 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003165cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003166{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003167 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003168}
3169
3170/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003171 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003172 */
3173 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003174init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003175{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003176 VTermColor *fg, *bg;
3177 int fgval, bgval;
3178 int id;
3179
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003180 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3181 term->tl_default_color.width = 1;
3182 fg = &term->tl_default_color.fg;
3183 bg = &term->tl_default_color.bg;
3184
3185 /* Vterm uses a default black background. Set it to white when
3186 * 'background' is "light". */
3187 if (*p_bg == 'l')
3188 {
3189 fgval = 0;
3190 bgval = 255;
3191 }
3192 else
3193 {
3194 fgval = 255;
3195 bgval = 0;
3196 }
3197 fg->red = fg->green = fg->blue = fgval;
3198 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003199 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003200
3201 /* The "Terminal" highlight group overrules the defaults. */
3202 id = syn_name2id((char_u *)"Terminal");
3203
Bram Moolenaar46359e12017-11-29 22:33:38 +01003204 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003205#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3206 if (0
3207# ifdef FEAT_GUI
3208 || gui.in_use
3209# endif
3210# ifdef FEAT_TERMGUICOLORS
3211 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003212# ifdef FEAT_VTP
3213 /* Finally get INVALCOLOR on this execution path */
3214 || (!p_tgc && t_colors >= 256)
3215# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003216# endif
3217 )
3218 {
3219 guicolor_T fg_rgb = INVALCOLOR;
3220 guicolor_T bg_rgb = INVALCOLOR;
3221
3222 if (id != 0)
3223 syn_id2colors(id, &fg_rgb, &bg_rgb);
3224
3225# ifdef FEAT_GUI
3226 if (gui.in_use)
3227 {
3228 if (fg_rgb == INVALCOLOR)
3229 fg_rgb = gui.norm_pixel;
3230 if (bg_rgb == INVALCOLOR)
3231 bg_rgb = gui.back_pixel;
3232 }
3233# ifdef FEAT_TERMGUICOLORS
3234 else
3235# endif
3236# endif
3237# ifdef FEAT_TERMGUICOLORS
3238 {
3239 if (fg_rgb == INVALCOLOR)
3240 fg_rgb = cterm_normal_fg_gui_color;
3241 if (bg_rgb == INVALCOLOR)
3242 bg_rgb = cterm_normal_bg_gui_color;
3243 }
3244# endif
3245 if (fg_rgb != INVALCOLOR)
3246 {
3247 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3248
3249 fg->red = (unsigned)(rgb >> 16);
3250 fg->green = (unsigned)(rgb >> 8) & 255;
3251 fg->blue = (unsigned)rgb & 255;
3252 }
3253 if (bg_rgb != INVALCOLOR)
3254 {
3255 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3256
3257 bg->red = (unsigned)(rgb >> 16);
3258 bg->green = (unsigned)(rgb >> 8) & 255;
3259 bg->blue = (unsigned)rgb & 255;
3260 }
3261 }
3262 else
3263#endif
3264 if (id != 0 && t_colors >= 16)
3265 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003266 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003267 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003268 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003269 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003270 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003271 else
3272 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003273#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003274 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003275#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003276
3277 /* In an MS-Windows console we know the normal colors. */
3278 if (cterm_normal_fg_color > 0)
3279 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003280 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003281# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003282 tmp = fg->red;
3283 fg->red = fg->blue;
3284 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003285# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003286 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003287# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003288 else
3289 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003290# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003291
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003292 if (cterm_normal_bg_color > 0)
3293 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003294 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003295# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003296 tmp = bg->red;
3297 bg->red = bg->blue;
3298 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003299# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003300 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003301# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003302 else
3303 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003304# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003305 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003306}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003307
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003308#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3309/*
3310 * Set the 16 ANSI colors from array of RGB values
3311 */
3312 static void
3313set_vterm_palette(VTerm *vterm, long_u *rgb)
3314{
3315 int index = 0;
3316 VTermState *state = vterm_obtain_state(vterm);
3317 for (; index < 16; index++)
3318 {
3319 VTermColor color;
3320 color.red = (unsigned)(rgb[index] >> 16);
3321 color.green = (unsigned)(rgb[index] >> 8) & 255;
3322 color.blue = (unsigned)rgb[index] & 255;
3323 vterm_state_set_palette_color(state, index, &color);
3324 }
3325}
3326
3327/*
3328 * Set the ANSI color palette from a list of colors
3329 */
3330 static int
3331set_ansi_colors_list(VTerm *vterm, list_T *list)
3332{
3333 int n = 0;
3334 long_u rgb[16];
3335 listitem_T *li = list->lv_first;
3336
3337 for (; li != NULL && n < 16; li = li->li_next, n++)
3338 {
3339 char_u *color_name;
3340 guicolor_T guicolor;
3341
3342 color_name = get_tv_string_chk(&li->li_tv);
3343 if (color_name == NULL)
3344 return FAIL;
3345
3346 guicolor = GUI_GET_COLOR(color_name);
3347 if (guicolor == INVALCOLOR)
3348 return FAIL;
3349
3350 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3351 }
3352
3353 if (n != 16 || li != NULL)
3354 return FAIL;
3355
3356 set_vterm_palette(vterm, rgb);
3357
3358 return OK;
3359}
3360
3361/*
3362 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3363 */
3364 static void
3365init_vterm_ansi_colors(VTerm *vterm)
3366{
3367 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3368
3369 if (var != NULL
3370 && (var->di_tv.v_type != VAR_LIST
3371 || var->di_tv.vval.v_list == NULL
3372 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
3373 EMSG2(_(e_invarg2), "g:terminal_ansi_colors");
3374}
3375#endif
3376
Bram Moolenaar52acb112018-03-18 19:20:22 +01003377/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003378 * Handles a "drop" command from the job in the terminal.
3379 * "item" is the file name, "item->li_next" may have options.
3380 */
3381 static void
3382handle_drop_command(listitem_T *item)
3383{
3384 char_u *fname = get_tv_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003385 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003386 int bufnr;
3387 win_T *wp;
3388 tabpage_T *tp;
3389 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003390 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003391
3392 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3393 FOR_ALL_TAB_WINDOWS(tp, wp)
3394 {
3395 if (wp->w_buffer->b_fnum == bufnr)
3396 {
3397 /* buffer is in a window already, go there */
3398 goto_tabpage_win(tp, wp);
3399 return;
3400 }
3401 }
3402
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003403 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003404
3405 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3406 && opt_item->li_tv.vval.v_dict != NULL)
3407 {
3408 dict_T *dict = opt_item->li_tv.vval.v_dict;
3409 char_u *p;
3410
3411 p = get_dict_string(dict, (char_u *)"ff", FALSE);
3412 if (p == NULL)
3413 p = get_dict_string(dict, (char_u *)"fileformat", FALSE);
3414 if (p != NULL)
3415 {
3416 if (check_ff_value(p) == FAIL)
3417 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3418 else
3419 ea.force_ff = *p;
3420 }
3421 p = get_dict_string(dict, (char_u *)"enc", FALSE);
3422 if (p == NULL)
3423 p = get_dict_string(dict, (char_u *)"encoding", FALSE);
3424 if (p != NULL)
3425 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003426 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003427 if (ea.cmd != NULL)
3428 {
3429 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3430 ea.force_enc = 11;
3431 tofree = ea.cmd;
3432 }
3433 }
3434
3435 p = get_dict_string(dict, (char_u *)"bad", FALSE);
3436 if (p != NULL)
3437 get_bad_opt(p, &ea);
3438
3439 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3440 ea.force_bin = FORCE_BIN;
3441 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3442 ea.force_bin = FORCE_BIN;
3443 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3444 ea.force_bin = FORCE_NOBIN;
3445 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3446 ea.force_bin = FORCE_NOBIN;
3447 }
3448
3449 /* open in new window, like ":split fname" */
3450 if (ea.cmd == NULL)
3451 ea.cmd = (char_u *)"split";
3452 ea.arg = fname;
3453 ea.cmdidx = CMD_split;
3454 ex_splitview(&ea);
3455
3456 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003457}
3458
3459/*
3460 * Handles a function call from the job running in a terminal.
3461 * "item" is the function name, "item->li_next" has the arguments.
3462 */
3463 static void
3464handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3465{
3466 char_u *func;
3467 typval_T argvars[2];
3468 typval_T rettv;
3469 int doesrange;
3470
3471 if (item->li_next == NULL)
3472 {
3473 ch_log(channel, "Missing function arguments for call");
3474 return;
3475 }
3476 func = get_tv_string(&item->li_tv);
3477
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003478 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003479 {
3480 ch_log(channel, "Invalid function name: %s", func);
3481 return;
3482 }
3483
3484 argvars[0].v_type = VAR_NUMBER;
3485 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3486 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003487 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003488 2, argvars, /* argv_func */ NULL,
3489 /* firstline */ 1, /* lastline */ 1,
3490 &doesrange, /* evaluate */ TRUE,
3491 /* partial */ NULL, /* selfdict */ NULL) == OK)
3492 {
3493 clear_tv(&rettv);
3494 ch_log(channel, "Function %s called", func);
3495 }
3496 else
3497 ch_log(channel, "Calling function %s failed", func);
3498}
3499
3500/*
3501 * Called by libvterm when it cannot recognize an OSC sequence.
3502 * We recognize a terminal API command.
3503 */
3504 static int
3505parse_osc(const char *command, size_t cmdlen, void *user)
3506{
3507 term_T *term = (term_T *)user;
3508 js_read_T reader;
3509 typval_T tv;
3510 channel_T *channel = term->tl_job == NULL ? NULL
3511 : term->tl_job->jv_channel;
3512
3513 /* We recognize only OSC 5 1 ; {command} */
3514 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3515 return 0; /* not handled */
3516
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003517 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003518 if (reader.js_buf == NULL)
3519 return 1;
3520 reader.js_fill = NULL;
3521 reader.js_used = 0;
3522 if (json_decode(&reader, &tv, 0) == OK
3523 && tv.v_type == VAR_LIST
3524 && tv.vval.v_list != NULL)
3525 {
3526 listitem_T *item = tv.vval.v_list->lv_first;
3527
3528 if (item == NULL)
3529 ch_log(channel, "Missing command");
3530 else
3531 {
3532 char_u *cmd = get_tv_string(&item->li_tv);
3533
Bram Moolenaara997b452018-04-17 23:24:06 +02003534 /* Make sure an invoked command doesn't delete the buffer (and the
3535 * terminal) under our fingers. */
3536 ++term->tl_buffer->b_locked;
3537
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003538 item = item->li_next;
3539 if (item == NULL)
3540 ch_log(channel, "Missing argument for %s", cmd);
3541 else if (STRCMP(cmd, "drop") == 0)
3542 handle_drop_command(item);
3543 else if (STRCMP(cmd, "call") == 0)
3544 handle_call_command(term, channel, item);
3545 else
3546 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003547 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003548 }
3549 }
3550 else
3551 ch_log(channel, "Invalid JSON received");
3552
3553 vim_free(reader.js_buf);
3554 clear_tv(&tv);
3555 return 1;
3556}
3557
3558static VTermParserCallbacks parser_fallbacks = {
3559 NULL, /* text */
3560 NULL, /* control */
3561 NULL, /* escape */
3562 NULL, /* csi */
3563 parse_osc, /* osc */
3564 NULL, /* dcs */
3565 NULL /* resize */
3566};
3567
3568/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003569 * Use Vim's allocation functions for vterm so profiling works.
3570 */
3571 static void *
3572vterm_malloc(size_t size, void *data UNUSED)
3573{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003574 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003575}
3576
3577 static void
3578vterm_memfree(void *ptr, void *data UNUSED)
3579{
3580 vim_free(ptr);
3581}
3582
3583static VTermAllocatorFunctions vterm_allocator = {
3584 &vterm_malloc,
3585 &vterm_memfree
3586};
3587
3588/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003589 * Create a new vterm and initialize it.
3590 */
3591 static void
3592create_vterm(term_T *term, int rows, int cols)
3593{
3594 VTerm *vterm;
3595 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003596 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003597 VTermValue value;
3598
Bram Moolenaar756ef112018-04-10 12:04:27 +02003599 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003600 term->tl_vterm = vterm;
3601 screen = vterm_obtain_screen(vterm);
3602 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3603 /* TODO: depends on 'encoding'. */
3604 vterm_set_utf8(vterm, 1);
3605
3606 init_default_colors(term);
3607
3608 vterm_state_set_default_colors(
3609 vterm_obtain_state(vterm),
3610 &term->tl_default_color.fg,
3611 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003612
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003613 if (t_colors >= 16)
3614 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3615
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003616 /* Required to initialize most things. */
3617 vterm_screen_reset(screen, 1 /* hard */);
3618
3619 /* Allow using alternate screen. */
3620 vterm_screen_enable_altscreen(screen, 1);
3621
3622 /* For unix do not use a blinking cursor. In an xterm this causes the
3623 * cursor to blink if it's blinking in the xterm.
3624 * For Windows we respect the system wide setting. */
3625#ifdef WIN3264
3626 if (GetCaretBlinkTime() == INFINITE)
3627 value.boolean = 0;
3628 else
3629 value.boolean = 1;
3630#else
3631 value.boolean = 0;
3632#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003633 state = vterm_obtain_state(vterm);
3634 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3635 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003636}
3637
3638/*
3639 * Return the text to show for the buffer name and status.
3640 */
3641 char_u *
3642term_get_status_text(term_T *term)
3643{
3644 if (term->tl_status_text == NULL)
3645 {
3646 char_u *txt;
3647 size_t len;
3648
3649 if (term->tl_normal_mode)
3650 {
3651 if (term_job_running(term))
3652 txt = (char_u *)_("Terminal");
3653 else
3654 txt = (char_u *)_("Terminal-finished");
3655 }
3656 else if (term->tl_title != NULL)
3657 txt = term->tl_title;
3658 else if (term_none_open(term))
3659 txt = (char_u *)_("active");
3660 else if (term_job_running(term))
3661 txt = (char_u *)_("running");
3662 else
3663 txt = (char_u *)_("finished");
3664 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3665 term->tl_status_text = alloc((int)len);
3666 if (term->tl_status_text != NULL)
3667 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3668 term->tl_buffer->b_fname, txt);
3669 }
3670 return term->tl_status_text;
3671}
3672
3673/*
3674 * Mark references in jobs of terminals.
3675 */
3676 int
3677set_ref_in_term(int copyID)
3678{
3679 int abort = FALSE;
3680 term_T *term;
3681 typval_T tv;
3682
3683 for (term = first_term; term != NULL; term = term->tl_next)
3684 if (term->tl_job != NULL)
3685 {
3686 tv.v_type = VAR_JOB;
3687 tv.vval.v_job = term->tl_job;
3688 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3689 }
3690 return abort;
3691}
3692
3693/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003694 * Cache "Terminal" highlight group colors.
3695 */
3696 void
3697set_terminal_default_colors(int cterm_fg, int cterm_bg)
3698{
3699 term_default_cterm_fg = cterm_fg - 1;
3700 term_default_cterm_bg = cterm_bg - 1;
3701}
3702
3703/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003704 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003705 * Returns NULL when the buffer is not for a terminal window and logs a message
3706 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003707 */
3708 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003709term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003710{
3711 buf_T *buf;
3712
3713 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3714 ++emsg_off;
3715 buf = get_buf_tv(&argvars[0], FALSE);
3716 --emsg_off;
3717 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003718 {
3719 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003720 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003721 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003722 return buf;
3723}
3724
Bram Moolenaard96ff162018-02-18 22:13:29 +01003725 static int
3726same_color(VTermColor *a, VTermColor *b)
3727{
3728 return a->red == b->red
3729 && a->green == b->green
3730 && a->blue == b->blue
3731 && a->ansi_index == b->ansi_index;
3732}
3733
3734 static void
3735dump_term_color(FILE *fd, VTermColor *color)
3736{
3737 fprintf(fd, "%02x%02x%02x%d",
3738 (int)color->red, (int)color->green, (int)color->blue,
3739 (int)color->ansi_index);
3740}
3741
3742/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003743 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003744 *
3745 * Each screen cell in full is:
3746 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3747 * {characters} is a space for an empty cell
3748 * For a double-width character "+" is changed to "*" and the next cell is
3749 * skipped.
3750 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3751 * when "&" use the same as the previous cell.
3752 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3753 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3754 * {color-idx} is a number from 0 to 255
3755 *
3756 * Screen cell with same width, attributes and color as the previous one:
3757 * |{characters}
3758 *
3759 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3760 *
3761 * Repeating the previous screen cell:
3762 * @{count}
3763 */
3764 void
3765f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3766{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003767 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003768 term_T *term;
3769 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003770 int max_height = 0;
3771 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003772 stat_T st;
3773 FILE *fd;
3774 VTermPos pos;
3775 VTermScreen *screen;
3776 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003777 VTermState *state;
3778 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003779
3780 if (check_restricted() || check_secure())
3781 return;
3782 if (buf == NULL)
3783 return;
3784 term = buf->b_term;
3785
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003786 if (argvars[2].v_type != VAR_UNKNOWN)
3787 {
3788 dict_T *d;
3789
3790 if (argvars[2].v_type != VAR_DICT)
3791 {
3792 EMSG(_(e_dictreq));
3793 return;
3794 }
3795 d = argvars[2].vval.v_dict;
3796 if (d != NULL)
3797 {
3798 max_height = get_dict_number(d, (char_u *)"rows");
3799 max_width = get_dict_number(d, (char_u *)"columns");
3800 }
3801 }
3802
Bram Moolenaard96ff162018-02-18 22:13:29 +01003803 fname = get_tv_string_chk(&argvars[1]);
3804 if (fname == NULL)
3805 return;
3806 if (mch_stat((char *)fname, &st) >= 0)
3807 {
3808 EMSG2(_("E953: File exists: %s"), fname);
3809 return;
3810 }
3811
Bram Moolenaard96ff162018-02-18 22:13:29 +01003812 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3813 {
3814 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3815 return;
3816 }
3817
3818 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3819
3820 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003821 state = vterm_obtain_state(term->tl_vterm);
3822 vterm_state_get_cursorpos(state, &cursor_pos);
3823
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003824 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3825 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003826 {
3827 int repeat = 0;
3828
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003829 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3830 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003831 {
3832 VTermScreenCell cell;
3833 int same_attr;
3834 int same_chars = TRUE;
3835 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003836 int is_cursor_pos = (pos.col == cursor_pos.col
3837 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003838
3839 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3840 vim_memset(&cell, 0, sizeof(cell));
3841
3842 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3843 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003844 int c = cell.chars[i];
3845 int pc = prev_cell.chars[i];
3846
3847 /* For the first character NUL is the same as space. */
3848 if (i == 0)
3849 {
3850 c = (c == NUL) ? ' ' : c;
3851 pc = (pc == NUL) ? ' ' : pc;
3852 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003853 if (cell.chars[i] != prev_cell.chars[i])
3854 same_chars = FALSE;
3855 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3856 break;
3857 }
3858 same_attr = vtermAttr2hl(cell.attrs)
3859 == vtermAttr2hl(prev_cell.attrs)
3860 && same_color(&cell.fg, &prev_cell.fg)
3861 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003862 if (same_chars && cell.width == prev_cell.width && same_attr
3863 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003864 {
3865 ++repeat;
3866 }
3867 else
3868 {
3869 if (repeat > 0)
3870 {
3871 fprintf(fd, "@%d", repeat);
3872 repeat = 0;
3873 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003874 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003875
3876 if (cell.chars[0] == NUL)
3877 fputs(" ", fd);
3878 else
3879 {
3880 char_u charbuf[10];
3881 int len;
3882
3883 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3884 && cell.chars[i] != NUL; ++i)
3885 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02003886 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003887 fwrite(charbuf, len, 1, fd);
3888 }
3889 }
3890
3891 /* When only the characters differ we don't write anything, the
3892 * following "|", "@" or NL will indicate using the same
3893 * attributes. */
3894 if (cell.width != prev_cell.width || !same_attr)
3895 {
3896 if (cell.width == 2)
3897 {
3898 fputs("*", fd);
3899 ++pos.col;
3900 }
3901 else
3902 fputs("+", fd);
3903
3904 if (same_attr)
3905 {
3906 fputs("&", fd);
3907 }
3908 else
3909 {
3910 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3911 if (same_color(&cell.fg, &prev_cell.fg))
3912 fputs("&", fd);
3913 else
3914 {
3915 fputs("#", fd);
3916 dump_term_color(fd, &cell.fg);
3917 }
3918 if (same_color(&cell.bg, &prev_cell.bg))
3919 fputs("&", fd);
3920 else
3921 {
3922 fputs("#", fd);
3923 dump_term_color(fd, &cell.bg);
3924 }
3925 }
3926 }
3927
3928 prev_cell = cell;
3929 }
3930 }
3931 if (repeat > 0)
3932 fprintf(fd, "@%d", repeat);
3933 fputs("\n", fd);
3934 }
3935
3936 fclose(fd);
3937}
3938
3939/*
3940 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3941 */
3942 static void
3943dump_is_corrupt(garray_T *gap)
3944{
3945 ga_concat(gap, (char_u *)"CORRUPT");
3946}
3947
3948 static void
3949append_cell(garray_T *gap, cellattr_T *cell)
3950{
3951 if (ga_grow(gap, 1) == OK)
3952 {
3953 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3954 ++gap->ga_len;
3955 }
3956}
3957
3958/*
3959 * Read the dump file from "fd" and append lines to the current buffer.
3960 * Return the cell width of the longest line.
3961 */
3962 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003963read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003964{
3965 int c;
3966 garray_T ga_text;
3967 garray_T ga_cell;
3968 char_u *prev_char = NULL;
3969 int attr = 0;
3970 cellattr_T cell;
3971 term_T *term = curbuf->b_term;
3972 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003973 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003974
3975 ga_init2(&ga_text, 1, 90);
3976 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3977 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003978 cursor_pos->row = -1;
3979 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003980
3981 c = fgetc(fd);
3982 for (;;)
3983 {
3984 if (c == EOF)
3985 break;
3986 if (c == '\n')
3987 {
3988 /* End of a line: append it to the buffer. */
3989 if (ga_text.ga_data == NULL)
3990 dump_is_corrupt(&ga_text);
3991 if (ga_grow(&term->tl_scrollback, 1) == OK)
3992 {
3993 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3994 + term->tl_scrollback.ga_len;
3995
3996 if (max_cells < ga_cell.ga_len)
3997 max_cells = ga_cell.ga_len;
3998 line->sb_cols = ga_cell.ga_len;
3999 line->sb_cells = ga_cell.ga_data;
4000 line->sb_fill_attr = term->tl_default_color;
4001 ++term->tl_scrollback.ga_len;
4002 ga_init(&ga_cell);
4003
4004 ga_append(&ga_text, NUL);
4005 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4006 ga_text.ga_len, FALSE);
4007 }
4008 else
4009 ga_clear(&ga_cell);
4010 ga_text.ga_len = 0;
4011
4012 c = fgetc(fd);
4013 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004014 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004015 {
4016 int prev_len = ga_text.ga_len;
4017
Bram Moolenaar9271d052018-02-25 21:39:46 +01004018 if (c == '>')
4019 {
4020 if (cursor_pos->row != -1)
4021 dump_is_corrupt(&ga_text); /* duplicate cursor */
4022 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
4023 cursor_pos->col = ga_cell.ga_len;
4024 }
4025
Bram Moolenaard96ff162018-02-18 22:13:29 +01004026 /* normal character(s) followed by "+", "*", "|", "@" or NL */
4027 c = fgetc(fd);
4028 if (c != EOF)
4029 ga_append(&ga_text, c);
4030 for (;;)
4031 {
4032 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004033 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01004034 || c == EOF || c == '\n')
4035 break;
4036 ga_append(&ga_text, c);
4037 }
4038
4039 /* save the character for repeating it */
4040 vim_free(prev_char);
4041 if (ga_text.ga_data != NULL)
4042 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
4043 ga_text.ga_len - prev_len);
4044
Bram Moolenaar9271d052018-02-25 21:39:46 +01004045 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004046 {
4047 /* use all attributes from previous cell */
4048 }
4049 else if (c == '+' || c == '*')
4050 {
4051 int is_bg;
4052
4053 cell.width = c == '+' ? 1 : 2;
4054
4055 c = fgetc(fd);
4056 if (c == '&')
4057 {
4058 /* use same attr as previous cell */
4059 c = fgetc(fd);
4060 }
4061 else if (isdigit(c))
4062 {
4063 /* get the decimal attribute */
4064 attr = 0;
4065 while (isdigit(c))
4066 {
4067 attr = attr * 10 + (c - '0');
4068 c = fgetc(fd);
4069 }
4070 hl2vtermAttr(attr, &cell);
4071 }
4072 else
4073 dump_is_corrupt(&ga_text);
4074
4075 /* is_bg == 0: fg, is_bg == 1: bg */
4076 for (is_bg = 0; is_bg <= 1; ++is_bg)
4077 {
4078 if (c == '&')
4079 {
4080 /* use same color as previous cell */
4081 c = fgetc(fd);
4082 }
4083 else if (c == '#')
4084 {
4085 int red, green, blue, index = 0;
4086
4087 c = fgetc(fd);
4088 red = hex2nr(c);
4089 c = fgetc(fd);
4090 red = (red << 4) + hex2nr(c);
4091 c = fgetc(fd);
4092 green = hex2nr(c);
4093 c = fgetc(fd);
4094 green = (green << 4) + hex2nr(c);
4095 c = fgetc(fd);
4096 blue = hex2nr(c);
4097 c = fgetc(fd);
4098 blue = (blue << 4) + hex2nr(c);
4099 c = fgetc(fd);
4100 if (!isdigit(c))
4101 dump_is_corrupt(&ga_text);
4102 while (isdigit(c))
4103 {
4104 index = index * 10 + (c - '0');
4105 c = fgetc(fd);
4106 }
4107
4108 if (is_bg)
4109 {
4110 cell.bg.red = red;
4111 cell.bg.green = green;
4112 cell.bg.blue = blue;
4113 cell.bg.ansi_index = index;
4114 }
4115 else
4116 {
4117 cell.fg.red = red;
4118 cell.fg.green = green;
4119 cell.fg.blue = blue;
4120 cell.fg.ansi_index = index;
4121 }
4122 }
4123 else
4124 dump_is_corrupt(&ga_text);
4125 }
4126 }
4127 else
4128 dump_is_corrupt(&ga_text);
4129
4130 append_cell(&ga_cell, &cell);
4131 }
4132 else if (c == '@')
4133 {
4134 if (prev_char == NULL)
4135 dump_is_corrupt(&ga_text);
4136 else
4137 {
4138 int count = 0;
4139
4140 /* repeat previous character, get the count */
4141 for (;;)
4142 {
4143 c = fgetc(fd);
4144 if (!isdigit(c))
4145 break;
4146 count = count * 10 + (c - '0');
4147 }
4148
4149 while (count-- > 0)
4150 {
4151 ga_concat(&ga_text, prev_char);
4152 append_cell(&ga_cell, &cell);
4153 }
4154 }
4155 }
4156 else
4157 {
4158 dump_is_corrupt(&ga_text);
4159 c = fgetc(fd);
4160 }
4161 }
4162
4163 if (ga_text.ga_len > 0)
4164 {
4165 /* trailing characters after last NL */
4166 dump_is_corrupt(&ga_text);
4167 ga_append(&ga_text, NUL);
4168 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4169 ga_text.ga_len, FALSE);
4170 }
4171
4172 ga_clear(&ga_text);
4173 vim_free(prev_char);
4174
4175 return max_cells;
4176}
4177
4178/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004179 * Return an allocated string with at least "text_width" "=" characters and
4180 * "fname" inserted in the middle.
4181 */
4182 static char_u *
4183get_separator(int text_width, char_u *fname)
4184{
4185 int width = MAX(text_width, curwin->w_width);
4186 char_u *textline;
4187 int fname_size;
4188 char_u *p = fname;
4189 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004190 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004191
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004192 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004193 if (textline == NULL)
4194 return NULL;
4195
4196 fname_size = vim_strsize(fname);
4197 if (fname_size < width - 8)
4198 {
4199 /* enough room, don't use the full window width */
4200 width = MAX(text_width, fname_size + 8);
4201 }
4202 else if (fname_size > width - 8)
4203 {
4204 /* full name doesn't fit, use only the tail */
4205 p = gettail(fname);
4206 fname_size = vim_strsize(p);
4207 }
4208 /* skip characters until the name fits */
4209 while (fname_size > width - 8)
4210 {
4211 p += (*mb_ptr2len)(p);
4212 fname_size = vim_strsize(p);
4213 }
4214
4215 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4216 textline[i] = '=';
4217 textline[i++] = ' ';
4218
4219 STRCPY(textline + i, p);
4220 off = STRLEN(textline);
4221 textline[off] = ' ';
4222 for (i = 1; i < (width - fname_size) / 2; ++i)
4223 textline[off + i] = '=';
4224 textline[off + i] = NUL;
4225
4226 return textline;
4227}
4228
4229/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004230 * Common for "term_dumpdiff()" and "term_dumpload()".
4231 */
4232 static void
4233term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4234{
4235 jobopt_T opt;
4236 buf_T *buf;
4237 char_u buf1[NUMBUFLEN];
4238 char_u buf2[NUMBUFLEN];
4239 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004240 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004241 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004242 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004243 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004244 char_u *textline = NULL;
4245
4246 /* First open the files. If this fails bail out. */
4247 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
4248 if (do_diff)
4249 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
4250 if (fname1 == NULL || (do_diff && fname2 == NULL))
4251 {
4252 EMSG(_(e_invarg));
4253 return;
4254 }
4255 fd1 = mch_fopen((char *)fname1, READBIN);
4256 if (fd1 == NULL)
4257 {
4258 EMSG2(_(e_notread), fname1);
4259 return;
4260 }
4261 if (do_diff)
4262 {
4263 fd2 = mch_fopen((char *)fname2, READBIN);
4264 if (fd2 == NULL)
4265 {
4266 fclose(fd1);
4267 EMSG2(_(e_notread), fname2);
4268 return;
4269 }
4270 }
4271
4272 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004273 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4274 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4275 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4276 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4277 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004278
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004279 if (opt.jo_term_name == NULL)
4280 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004281 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004282
Bram Moolenaarb571c632018-03-21 22:27:59 +01004283 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004284 if (fname_tofree != NULL)
4285 {
4286 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4287 opt.jo_term_name = fname_tofree;
4288 }
4289 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004290
Bram Moolenaar13568252018-03-16 20:46:58 +01004291 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004292 if (buf != NULL && buf->b_term != NULL)
4293 {
4294 int i;
4295 linenr_T bot_lnum;
4296 linenr_T lnum;
4297 term_T *term = buf->b_term;
4298 int width;
4299 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004300 VTermPos cursor_pos1;
4301 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004302
Bram Moolenaar52acb112018-03-18 19:20:22 +01004303 init_default_colors(term);
4304
Bram Moolenaard96ff162018-02-18 22:13:29 +01004305 rettv->vval.v_number = buf->b_fnum;
4306
4307 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004308 width = read_dump_file(fd1, &cursor_pos1);
4309
4310 /* position the cursor */
4311 if (cursor_pos1.row >= 0)
4312 {
4313 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4314 coladvance(cursor_pos1.col);
4315 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004316
4317 /* Delete the empty line that was in the empty buffer. */
4318 ml_delete(1, FALSE);
4319
4320 /* For term_dumpload() we are done here. */
4321 if (!do_diff)
4322 goto theend;
4323
4324 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4325
Bram Moolenaar4a696342018-04-05 18:45:26 +02004326 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004327 if (textline == NULL)
4328 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004329 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4330 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4331 vim_free(textline);
4332
4333 textline = get_separator(width, fname2);
4334 if (textline == NULL)
4335 goto theend;
4336 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4337 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004338 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004339
4340 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004341 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004342 if (width2 > width)
4343 {
4344 vim_free(textline);
4345 textline = alloc(width2 + 1);
4346 if (textline == NULL)
4347 goto theend;
4348 width = width2;
4349 textline[width] = NUL;
4350 }
4351 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4352
4353 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4354 {
4355 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4356 {
4357 /* bottom part has fewer rows, fill with "-" */
4358 for (i = 0; i < width; ++i)
4359 textline[i] = '-';
4360 }
4361 else
4362 {
4363 char_u *line1;
4364 char_u *line2;
4365 char_u *p1;
4366 char_u *p2;
4367 int col;
4368 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4369 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4370 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4371 ->sb_cells;
4372
4373 /* Make a copy, getting the second line will invalidate it. */
4374 line1 = vim_strsave(ml_get(lnum));
4375 if (line1 == NULL)
4376 break;
4377 p1 = line1;
4378
4379 line2 = ml_get(lnum + bot_lnum);
4380 p2 = line2;
4381 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4382 {
4383 int len1 = utfc_ptr2len(p1);
4384 int len2 = utfc_ptr2len(p2);
4385
4386 textline[col] = ' ';
4387 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004388 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004389 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004390 else if (lnum == cursor_pos1.row + 1
4391 && col == cursor_pos1.col
4392 && (cursor_pos1.row != cursor_pos2.row
4393 || cursor_pos1.col != cursor_pos2.col))
4394 /* cursor in first but not in second */
4395 textline[col] = '>';
4396 else if (lnum == cursor_pos2.row + 1
4397 && col == cursor_pos2.col
4398 && (cursor_pos1.row != cursor_pos2.row
4399 || cursor_pos1.col != cursor_pos2.col))
4400 /* cursor in second but not in first */
4401 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004402 else if (cellattr1 != NULL && cellattr2 != NULL)
4403 {
4404 if ((cellattr1 + col)->width
4405 != (cellattr2 + col)->width)
4406 textline[col] = 'w';
4407 else if (!same_color(&(cellattr1 + col)->fg,
4408 &(cellattr2 + col)->fg))
4409 textline[col] = 'f';
4410 else if (!same_color(&(cellattr1 + col)->bg,
4411 &(cellattr2 + col)->bg))
4412 textline[col] = 'b';
4413 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4414 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4415 textline[col] = 'a';
4416 }
4417 p1 += len1;
4418 p2 += len2;
4419 /* TODO: handle different width */
4420 }
4421 vim_free(line1);
4422
4423 while (col < width)
4424 {
4425 if (*p1 == NUL && *p2 == NUL)
4426 textline[col] = '?';
4427 else if (*p1 == NUL)
4428 {
4429 textline[col] = '+';
4430 p2 += utfc_ptr2len(p2);
4431 }
4432 else
4433 {
4434 textline[col] = '-';
4435 p1 += utfc_ptr2len(p1);
4436 }
4437 ++col;
4438 }
4439 }
4440 if (add_empty_scrollback(term, &term->tl_default_color,
4441 term->tl_top_diff_rows) == OK)
4442 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4443 ++bot_lnum;
4444 }
4445
4446 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4447 {
4448 /* bottom part has more rows, fill with "+" */
4449 for (i = 0; i < width; ++i)
4450 textline[i] = '+';
4451 if (add_empty_scrollback(term, &term->tl_default_color,
4452 term->tl_top_diff_rows) == OK)
4453 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4454 ++lnum;
4455 ++bot_lnum;
4456 }
4457
4458 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004459
4460 /* looks better without wrapping */
4461 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004462 }
4463
4464theend:
4465 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004466 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004467 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004468 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004469 fclose(fd2);
4470}
4471
4472/*
4473 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4474 * bottom files.
4475 * Return FAIL when this is not possible.
4476 */
4477 int
4478term_swap_diff()
4479{
4480 term_T *term = curbuf->b_term;
4481 linenr_T line_count;
4482 linenr_T top_rows;
4483 linenr_T bot_rows;
4484 linenr_T bot_start;
4485 linenr_T lnum;
4486 char_u *p;
4487 sb_line_T *sb_line;
4488
4489 if (term == NULL
4490 || !term_is_finished(curbuf)
4491 || term->tl_top_diff_rows == 0
4492 || term->tl_scrollback.ga_len == 0)
4493 return FAIL;
4494
4495 line_count = curbuf->b_ml.ml_line_count;
4496 top_rows = term->tl_top_diff_rows;
4497 bot_rows = term->tl_bot_diff_rows;
4498 bot_start = line_count - bot_rows;
4499 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4500
4501 /* move lines from top to above the bottom part */
4502 for (lnum = 1; lnum <= top_rows; ++lnum)
4503 {
4504 p = vim_strsave(ml_get(1));
4505 if (p == NULL)
4506 return OK;
4507 ml_append(bot_start, p, 0, FALSE);
4508 ml_delete(1, FALSE);
4509 vim_free(p);
4510 }
4511
4512 /* move lines from bottom to the top */
4513 for (lnum = 1; lnum <= bot_rows; ++lnum)
4514 {
4515 p = vim_strsave(ml_get(bot_start + lnum));
4516 if (p == NULL)
4517 return OK;
4518 ml_delete(bot_start + lnum, FALSE);
4519 ml_append(lnum - 1, p, 0, FALSE);
4520 vim_free(p);
4521 }
4522
4523 if (top_rows == bot_rows)
4524 {
4525 /* rows counts are equal, can swap cell properties */
4526 for (lnum = 0; lnum < top_rows; ++lnum)
4527 {
4528 sb_line_T temp;
4529
4530 temp = *(sb_line + lnum);
4531 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4532 *(sb_line + bot_start + lnum) = temp;
4533 }
4534 }
4535 else
4536 {
4537 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4538 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4539
4540 /* need to copy cell properties into temp memory */
4541 if (temp != NULL)
4542 {
4543 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4544 mch_memmove(term->tl_scrollback.ga_data,
4545 temp + bot_start,
4546 sizeof(sb_line_T) * bot_rows);
4547 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4548 temp + top_rows,
4549 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4550 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4551 + line_count - top_rows,
4552 temp,
4553 sizeof(sb_line_T) * top_rows);
4554 vim_free(temp);
4555 }
4556 }
4557
4558 term->tl_top_diff_rows = bot_rows;
4559 term->tl_bot_diff_rows = top_rows;
4560
4561 update_screen(NOT_VALID);
4562 return OK;
4563}
4564
4565/*
4566 * "term_dumpdiff(filename, filename, options)" function
4567 */
4568 void
4569f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4570{
4571 term_load_dump(argvars, rettv, TRUE);
4572}
4573
4574/*
4575 * "term_dumpload(filename, options)" function
4576 */
4577 void
4578f_term_dumpload(typval_T *argvars, typval_T *rettv)
4579{
4580 term_load_dump(argvars, rettv, FALSE);
4581}
4582
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004583/*
4584 * "term_getaltscreen(buf)" function
4585 */
4586 void
4587f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4588{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004589 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004590
4591 if (buf == NULL)
4592 return;
4593 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4594}
4595
4596/*
4597 * "term_getattr(attr, name)" function
4598 */
4599 void
4600f_term_getattr(typval_T *argvars, typval_T *rettv)
4601{
4602 int attr;
4603 size_t i;
4604 char_u *name;
4605
4606 static struct {
4607 char *name;
4608 int attr;
4609 } attrs[] = {
4610 {"bold", HL_BOLD},
4611 {"italic", HL_ITALIC},
4612 {"underline", HL_UNDERLINE},
4613 {"strike", HL_STRIKETHROUGH},
4614 {"reverse", HL_INVERSE},
4615 };
4616
4617 attr = get_tv_number(&argvars[0]);
4618 name = get_tv_string_chk(&argvars[1]);
4619 if (name == NULL)
4620 return;
4621
4622 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4623 if (STRCMP(name, attrs[i].name) == 0)
4624 {
4625 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4626 break;
4627 }
4628}
4629
4630/*
4631 * "term_getcursor(buf)" function
4632 */
4633 void
4634f_term_getcursor(typval_T *argvars, typval_T *rettv)
4635{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004636 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004637 term_T *term;
4638 list_T *l;
4639 dict_T *d;
4640
4641 if (rettv_list_alloc(rettv) == FAIL)
4642 return;
4643 if (buf == NULL)
4644 return;
4645 term = buf->b_term;
4646
4647 l = rettv->vval.v_list;
4648 list_append_number(l, term->tl_cursor_pos.row + 1);
4649 list_append_number(l, term->tl_cursor_pos.col + 1);
4650
4651 d = dict_alloc();
4652 if (d != NULL)
4653 {
4654 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4655 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4656 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4657 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4658 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4659 ? (char_u *)"" : term->tl_cursor_color);
4660 list_append_dict(l, d);
4661 }
4662}
4663
4664/*
4665 * "term_getjob(buf)" function
4666 */
4667 void
4668f_term_getjob(typval_T *argvars, typval_T *rettv)
4669{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004670 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004671
4672 rettv->v_type = VAR_JOB;
4673 rettv->vval.v_job = NULL;
4674 if (buf == NULL)
4675 return;
4676
4677 rettv->vval.v_job = buf->b_term->tl_job;
4678 if (rettv->vval.v_job != NULL)
4679 ++rettv->vval.v_job->jv_refcount;
4680}
4681
4682 static int
4683get_row_number(typval_T *tv, term_T *term)
4684{
4685 if (tv->v_type == VAR_STRING
4686 && tv->vval.v_string != NULL
4687 && STRCMP(tv->vval.v_string, ".") == 0)
4688 return term->tl_cursor_pos.row;
4689 return (int)get_tv_number(tv) - 1;
4690}
4691
4692/*
4693 * "term_getline(buf, row)" function
4694 */
4695 void
4696f_term_getline(typval_T *argvars, typval_T *rettv)
4697{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004698 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004699 term_T *term;
4700 int row;
4701
4702 rettv->v_type = VAR_STRING;
4703 if (buf == NULL)
4704 return;
4705 term = buf->b_term;
4706 row = get_row_number(&argvars[1], term);
4707
4708 if (term->tl_vterm == NULL)
4709 {
4710 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4711
4712 /* vterm is finished, get the text from the buffer */
4713 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4714 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4715 }
4716 else
4717 {
4718 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4719 VTermRect rect;
4720 int len;
4721 char_u *p;
4722
4723 if (row < 0 || row >= term->tl_rows)
4724 return;
4725 len = term->tl_cols * MB_MAXBYTES + 1;
4726 p = alloc(len);
4727 if (p == NULL)
4728 return;
4729 rettv->vval.v_string = p;
4730
4731 rect.start_col = 0;
4732 rect.end_col = term->tl_cols;
4733 rect.start_row = row;
4734 rect.end_row = row + 1;
4735 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4736 }
4737}
4738
4739/*
4740 * "term_getscrolled(buf)" function
4741 */
4742 void
4743f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4744{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004745 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004746
4747 if (buf == NULL)
4748 return;
4749 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4750}
4751
4752/*
4753 * "term_getsize(buf)" function
4754 */
4755 void
4756f_term_getsize(typval_T *argvars, typval_T *rettv)
4757{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004758 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004759 list_T *l;
4760
4761 if (rettv_list_alloc(rettv) == FAIL)
4762 return;
4763 if (buf == NULL)
4764 return;
4765
4766 l = rettv->vval.v_list;
4767 list_append_number(l, buf->b_term->tl_rows);
4768 list_append_number(l, buf->b_term->tl_cols);
4769}
4770
4771/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004772 * "term_setsize(buf, rows, cols)" function
4773 */
4774 void
4775f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4776{
4777 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4778 term_T *term;
4779 varnumber_T rows, cols;
4780
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004781 if (buf == NULL)
4782 {
4783 EMSG(_("E955: Not a terminal buffer"));
4784 return;
4785 }
4786 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004787 return;
4788 term = buf->b_term;
4789 rows = get_tv_number(&argvars[1]);
4790 rows = rows <= 0 ? term->tl_rows : rows;
4791 cols = get_tv_number(&argvars[2]);
4792 cols = cols <= 0 ? term->tl_cols : cols;
4793 vterm_set_size(term->tl_vterm, rows, cols);
4794 /* handle_resize() will resize the windows */
4795
4796 /* Get and remember the size we ended up with. Update the pty. */
4797 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
4798 term_report_winsize(term, term->tl_rows, term->tl_cols);
4799}
4800
4801/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004802 * "term_getstatus(buf)" function
4803 */
4804 void
4805f_term_getstatus(typval_T *argvars, typval_T *rettv)
4806{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004807 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004808 term_T *term;
4809 char_u val[100];
4810
4811 rettv->v_type = VAR_STRING;
4812 if (buf == NULL)
4813 return;
4814 term = buf->b_term;
4815
4816 if (term_job_running(term))
4817 STRCPY(val, "running");
4818 else
4819 STRCPY(val, "finished");
4820 if (term->tl_normal_mode)
4821 STRCAT(val, ",normal");
4822 rettv->vval.v_string = vim_strsave(val);
4823}
4824
4825/*
4826 * "term_gettitle(buf)" function
4827 */
4828 void
4829f_term_gettitle(typval_T *argvars, typval_T *rettv)
4830{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004831 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004832
4833 rettv->v_type = VAR_STRING;
4834 if (buf == NULL)
4835 return;
4836
4837 if (buf->b_term->tl_title != NULL)
4838 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4839}
4840
4841/*
4842 * "term_gettty(buf)" function
4843 */
4844 void
4845f_term_gettty(typval_T *argvars, typval_T *rettv)
4846{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004847 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar9b50f362018-05-07 20:10:17 +02004848 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004849 int num = 0;
4850
4851 rettv->v_type = VAR_STRING;
4852 if (buf == NULL)
4853 return;
4854 if (argvars[1].v_type != VAR_UNKNOWN)
4855 num = get_tv_number(&argvars[1]);
4856
4857 switch (num)
4858 {
4859 case 0:
4860 if (buf->b_term->tl_job != NULL)
4861 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004862 break;
4863 case 1:
4864 if (buf->b_term->tl_job != NULL)
4865 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004866 break;
4867 default:
4868 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4869 return;
4870 }
4871 if (p != NULL)
4872 rettv->vval.v_string = vim_strsave(p);
4873}
4874
4875/*
4876 * "term_list()" function
4877 */
4878 void
4879f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4880{
4881 term_T *tp;
4882 list_T *l;
4883
4884 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4885 return;
4886
4887 l = rettv->vval.v_list;
4888 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4889 if (tp != NULL && tp->tl_buffer != NULL)
4890 if (list_append_number(l,
4891 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4892 return;
4893}
4894
4895/*
4896 * "term_scrape(buf, row)" function
4897 */
4898 void
4899f_term_scrape(typval_T *argvars, typval_T *rettv)
4900{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004901 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004902 VTermScreen *screen = NULL;
4903 VTermPos pos;
4904 list_T *l;
4905 term_T *term;
4906 char_u *p;
4907 sb_line_T *line;
4908
4909 if (rettv_list_alloc(rettv) == FAIL)
4910 return;
4911 if (buf == NULL)
4912 return;
4913 term = buf->b_term;
4914
4915 l = rettv->vval.v_list;
4916 pos.row = get_row_number(&argvars[1], term);
4917
4918 if (term->tl_vterm != NULL)
4919 {
4920 screen = vterm_obtain_screen(term->tl_vterm);
4921 p = NULL;
4922 line = NULL;
4923 }
4924 else
4925 {
4926 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4927
4928 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4929 return;
4930 p = ml_get_buf(buf, lnum + 1, FALSE);
4931 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4932 }
4933
4934 for (pos.col = 0; pos.col < term->tl_cols; )
4935 {
4936 dict_T *dcell;
4937 int width;
4938 VTermScreenCellAttrs attrs;
4939 VTermColor fg, bg;
4940 char_u rgb[8];
4941 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4942 int off = 0;
4943 int i;
4944
4945 if (screen == NULL)
4946 {
4947 cellattr_T *cellattr;
4948 int len;
4949
4950 /* vterm has finished, get the cell from scrollback */
4951 if (pos.col >= line->sb_cols)
4952 break;
4953 cellattr = line->sb_cells + pos.col;
4954 width = cellattr->width;
4955 attrs = cellattr->attrs;
4956 fg = cellattr->fg;
4957 bg = cellattr->bg;
4958 len = MB_PTR2LEN(p);
4959 mch_memmove(mbs, p, len);
4960 mbs[len] = NUL;
4961 p += len;
4962 }
4963 else
4964 {
4965 VTermScreenCell cell;
4966 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4967 break;
4968 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4969 {
4970 if (cell.chars[i] == 0)
4971 break;
4972 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4973 }
4974 mbs[off] = NUL;
4975 width = cell.width;
4976 attrs = cell.attrs;
4977 fg = cell.fg;
4978 bg = cell.bg;
4979 }
4980 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004981 if (dcell == NULL)
4982 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004983 list_append_dict(l, dcell);
4984
4985 dict_add_nr_str(dcell, "chars", 0, mbs);
4986
4987 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4988 fg.red, fg.green, fg.blue);
4989 dict_add_nr_str(dcell, "fg", 0, rgb);
4990 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4991 bg.red, bg.green, bg.blue);
4992 dict_add_nr_str(dcell, "bg", 0, rgb);
4993
4994 dict_add_nr_str(dcell, "attr",
4995 cell2attr(attrs, fg, bg), NULL);
4996 dict_add_nr_str(dcell, "width", width, NULL);
4997
4998 ++pos.col;
4999 if (width == 2)
5000 ++pos.col;
5001 }
5002}
5003
5004/*
5005 * "term_sendkeys(buf, keys)" function
5006 */
5007 void
5008f_term_sendkeys(typval_T *argvars, typval_T *rettv)
5009{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005010 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005011 char_u *msg;
5012 term_T *term;
5013
5014 rettv->v_type = VAR_UNKNOWN;
5015 if (buf == NULL)
5016 return;
5017
5018 msg = get_tv_string_chk(&argvars[1]);
5019 if (msg == NULL)
5020 return;
5021 term = buf->b_term;
5022 if (term->tl_vterm == NULL)
5023 return;
5024
5025 while (*msg != NUL)
5026 {
5027 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02005028 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005029 }
5030}
5031
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005032#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
5033/*
5034 * "term_getansicolors(buf)" function
5035 */
5036 void
5037f_term_getansicolors(typval_T *argvars, typval_T *rettv)
5038{
5039 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
5040 term_T *term;
5041 VTermState *state;
5042 VTermColor color;
5043 char_u hexbuf[10];
5044 int index;
5045 list_T *list;
5046
5047 if (rettv_list_alloc(rettv) == FAIL)
5048 return;
5049
5050 if (buf == NULL)
5051 return;
5052 term = buf->b_term;
5053 if (term->tl_vterm == NULL)
5054 return;
5055
5056 list = rettv->vval.v_list;
5057 state = vterm_obtain_state(term->tl_vterm);
5058 for (index = 0; index < 16; index++)
5059 {
5060 vterm_state_get_palette_color(state, index, &color);
5061 sprintf((char *)hexbuf, "#%02x%02x%02x",
5062 color.red, color.green, color.blue);
5063 if (list_append_string(list, hexbuf, 7) == FAIL)
5064 return;
5065 }
5066}
5067
5068/*
5069 * "term_setansicolors(buf, list)" function
5070 */
5071 void
5072f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
5073{
5074 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
5075 term_T *term;
5076
5077 if (buf == NULL)
5078 return;
5079 term = buf->b_term;
5080 if (term->tl_vterm == NULL)
5081 return;
5082
5083 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
5084 {
5085 EMSG(_(e_listreq));
5086 return;
5087 }
5088
5089 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
5090 EMSG(_(e_invarg));
5091}
5092#endif
5093
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005094/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005095 * "term_setrestore(buf, command)" function
5096 */
5097 void
5098f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5099{
5100#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005101 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005102 term_T *term;
5103 char_u *cmd;
5104
5105 if (buf == NULL)
5106 return;
5107 term = buf->b_term;
5108 vim_free(term->tl_command);
5109 cmd = get_tv_string_chk(&argvars[1]);
5110 if (cmd != NULL)
5111 term->tl_command = vim_strsave(cmd);
5112 else
5113 term->tl_command = NULL;
5114#endif
5115}
5116
5117/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005118 * "term_setkill(buf, how)" function
5119 */
5120 void
5121f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5122{
5123 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5124 term_T *term;
5125 char_u *how;
5126
5127 if (buf == NULL)
5128 return;
5129 term = buf->b_term;
5130 vim_free(term->tl_kill);
5131 how = get_tv_string_chk(&argvars[1]);
5132 if (how != NULL)
5133 term->tl_kill = vim_strsave(how);
5134 else
5135 term->tl_kill = NULL;
5136}
5137
5138/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005139 * "term_start(command, options)" function
5140 */
5141 void
5142f_term_start(typval_T *argvars, typval_T *rettv)
5143{
5144 jobopt_T opt;
5145 buf_T *buf;
5146
5147 init_job_options(&opt);
5148 if (argvars[1].v_type != VAR_UNKNOWN
5149 && get_job_options(&argvars[1], &opt,
5150 JO_TIMEOUT_ALL + JO_STOPONEXIT
5151 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5152 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5153 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5154 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005155 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005156 + JO2_NORESTORE + JO2_TERM_KILL
5157 + JO2_ANSI_COLORS) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005158 return;
5159
Bram Moolenaar13568252018-03-16 20:46:58 +01005160 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005161
5162 if (buf != NULL && buf->b_term != NULL)
5163 rettv->vval.v_number = buf->b_fnum;
5164}
5165
5166/*
5167 * "term_wait" function
5168 */
5169 void
5170f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5171{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005172 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005173
5174 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005175 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005176 if (buf->b_term->tl_job == NULL)
5177 {
5178 ch_log(NULL, "term_wait(): no job to wait for");
5179 return;
5180 }
5181 if (buf->b_term->tl_job->jv_channel == NULL)
5182 /* channel is closed, nothing to do */
5183 return;
5184
5185 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005186 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005187 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5188 {
5189 /* The job is dead, keep reading channel I/O until the channel is
5190 * closed. buf->b_term may become NULL if the terminal was closed while
5191 * waiting. */
5192 ch_log(NULL, "term_wait(): waiting for channel to close");
5193 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5194 {
5195 mch_check_messages();
5196 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01005197 if (!buf_valid(buf))
5198 /* If the terminal is closed when the channel is closed the
5199 * buffer disappears. */
5200 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005201 ui_delay(10L, FALSE);
5202 }
5203 mch_check_messages();
5204 parse_queued_messages();
5205 }
5206 else
5207 {
5208 long wait = 10L;
5209
5210 mch_check_messages();
5211 parse_queued_messages();
5212
5213 /* Wait for some time for any channel I/O. */
5214 if (argvars[1].v_type != VAR_UNKNOWN)
5215 wait = get_tv_number(&argvars[1]);
5216 ui_delay(wait, TRUE);
5217 mch_check_messages();
5218
5219 /* Flushing messages on channels is hopefully sufficient.
5220 * TODO: is there a better way? */
5221 parse_queued_messages();
5222 }
5223}
5224
5225/*
5226 * Called when a channel has sent all the lines to a terminal.
5227 * Send a CTRL-D to mark the end of the text.
5228 */
5229 void
5230term_send_eof(channel_T *ch)
5231{
5232 term_T *term;
5233
5234 for (term = first_term; term != NULL; term = term->tl_next)
5235 if (term->tl_job == ch->ch_job)
5236 {
5237 if (term->tl_eof_chars != NULL)
5238 {
5239 channel_send(ch, PART_IN, term->tl_eof_chars,
5240 (int)STRLEN(term->tl_eof_chars), NULL);
5241 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5242 }
5243# ifdef WIN3264
5244 else
5245 /* Default: CTRL-D */
5246 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5247# endif
5248 }
5249}
5250
5251# if defined(WIN3264) || defined(PROTO)
5252
5253/**************************************
5254 * 2. MS-Windows implementation.
5255 */
5256
5257# ifndef PROTO
5258
5259#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5260#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005261#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005262
5263void* (*winpty_config_new)(UINT64, void*);
5264void* (*winpty_open)(void*, void*);
5265void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5266BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5267void (*winpty_config_set_mouse_mode)(void*, int);
5268void (*winpty_config_set_initial_size)(void*, int, int);
5269LPCWSTR (*winpty_conin_name)(void*);
5270LPCWSTR (*winpty_conout_name)(void*);
5271LPCWSTR (*winpty_conerr_name)(void*);
5272void (*winpty_free)(void*);
5273void (*winpty_config_free)(void*);
5274void (*winpty_spawn_config_free)(void*);
5275void (*winpty_error_free)(void*);
5276LPCWSTR (*winpty_error_msg)(void*);
5277BOOL (*winpty_set_size)(void*, int, int, void*);
5278HANDLE (*winpty_agent_process)(void*);
5279
5280#define WINPTY_DLL "winpty.dll"
5281
5282static HINSTANCE hWinPtyDLL = NULL;
5283# endif
5284
5285 static int
5286dyn_winpty_init(int verbose)
5287{
5288 int i;
5289 static struct
5290 {
5291 char *name;
5292 FARPROC *ptr;
5293 } winpty_entry[] =
5294 {
5295 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5296 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5297 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5298 {"winpty_config_set_mouse_mode",
5299 (FARPROC*)&winpty_config_set_mouse_mode},
5300 {"winpty_config_set_initial_size",
5301 (FARPROC*)&winpty_config_set_initial_size},
5302 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5303 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5304 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5305 {"winpty_free", (FARPROC*)&winpty_free},
5306 {"winpty_open", (FARPROC*)&winpty_open},
5307 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5308 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5309 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5310 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5311 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5312 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5313 {NULL, NULL}
5314 };
5315
5316 /* No need to initialize twice. */
5317 if (hWinPtyDLL)
5318 return OK;
5319 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5320 * winpty.dll. */
5321 if (*p_winptydll != NUL)
5322 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5323 if (!hWinPtyDLL)
5324 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5325 if (!hWinPtyDLL)
5326 {
5327 if (verbose)
5328 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
5329 : (char_u *)WINPTY_DLL);
5330 return FAIL;
5331 }
5332 for (i = 0; winpty_entry[i].name != NULL
5333 && winpty_entry[i].ptr != NULL; ++i)
5334 {
5335 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5336 winpty_entry[i].name)) == NULL)
5337 {
5338 if (verbose)
5339 EMSG2(_(e_loadfunc), winpty_entry[i].name);
5340 return FAIL;
5341 }
5342 }
5343
5344 return OK;
5345}
5346
5347/*
5348 * Create a new terminal of "rows" by "cols" cells.
5349 * Store a reference in "term".
5350 * Return OK or FAIL.
5351 */
5352 static int
5353term_and_job_init(
5354 term_T *term,
5355 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005356 char **argv UNUSED,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005357 jobopt_T *opt,
5358 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005359{
5360 WCHAR *cmd_wchar = NULL;
5361 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005362 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005363 channel_T *channel = NULL;
5364 job_T *job = NULL;
5365 DWORD error;
5366 HANDLE jo = NULL;
5367 HANDLE child_process_handle;
5368 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005369 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005370 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005371 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005372 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005373
5374 if (dyn_winpty_init(TRUE) == FAIL)
5375 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005376 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5377 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005378
5379 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005380 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005381 cmd = argvar->vval.v_string;
5382 }
5383 else if (argvar->v_type == VAR_LIST)
5384 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005385 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005386 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005387 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005388 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005389 if (cmd == NULL || *cmd == NUL)
5390 {
5391 EMSG(_(e_invarg));
5392 goto failed;
5393 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005394
5395 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005396 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005397 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005398 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005399 if (opt->jo_cwd != NULL)
5400 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005401
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005402 win32_build_env(opt->jo_env, &ga_env, TRUE);
5403 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005404
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005405 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5406 if (term->tl_winpty_config == NULL)
5407 goto failed;
5408
5409 winpty_config_set_mouse_mode(term->tl_winpty_config,
5410 WINPTY_MOUSE_MODE_FORCE);
5411 winpty_config_set_initial_size(term->tl_winpty_config,
5412 term->tl_cols, term->tl_rows);
5413 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5414 if (term->tl_winpty == NULL)
5415 goto failed;
5416
5417 spawn_config = winpty_spawn_config_new(
5418 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5419 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5420 NULL,
5421 cmd_wchar,
5422 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005423 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005424 &winpty_err);
5425 if (spawn_config == NULL)
5426 goto failed;
5427
5428 channel = add_channel();
5429 if (channel == NULL)
5430 goto failed;
5431
5432 job = job_alloc();
5433 if (job == NULL)
5434 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02005435 if (argvar->v_type == VAR_STRING)
5436 {
5437 int argc;
5438
5439 build_argv_from_string(cmd, &job->jv_argv, &argc);
5440 }
5441 else
5442 {
5443 int argc;
5444
5445 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5446 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005447
5448 if (opt->jo_set & JO_IN_BUF)
5449 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5450
5451 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5452 &child_thread_handle, &error, &winpty_err))
5453 goto failed;
5454
5455 channel_set_pipes(channel,
5456 (sock_T)CreateFileW(
5457 winpty_conin_name(term->tl_winpty),
5458 GENERIC_WRITE, 0, NULL,
5459 OPEN_EXISTING, 0, NULL),
5460 (sock_T)CreateFileW(
5461 winpty_conout_name(term->tl_winpty),
5462 GENERIC_READ, 0, NULL,
5463 OPEN_EXISTING, 0, NULL),
5464 (sock_T)CreateFileW(
5465 winpty_conerr_name(term->tl_winpty),
5466 GENERIC_READ, 0, NULL,
5467 OPEN_EXISTING, 0, NULL));
5468
5469 /* Write lines with CR instead of NL. */
5470 channel->ch_write_text_mode = TRUE;
5471
5472 jo = CreateJobObject(NULL, NULL);
5473 if (jo == NULL)
5474 goto failed;
5475
5476 if (!AssignProcessToJobObject(jo, child_process_handle))
5477 {
5478 /* Failed, switch the way to terminate process with TerminateProcess. */
5479 CloseHandle(jo);
5480 jo = NULL;
5481 }
5482
5483 winpty_spawn_config_free(spawn_config);
5484 vim_free(cmd_wchar);
5485 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005486 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005487
5488 create_vterm(term, term->tl_rows, term->tl_cols);
5489
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005490#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5491 if (opt->jo_set2 & JO2_ANSI_COLORS)
5492 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5493 else
5494 init_vterm_ansi_colors(term->tl_vterm);
5495#endif
5496
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005497 channel_set_job(channel, job, opt);
5498 job_set_options(job, opt);
5499
5500 job->jv_channel = channel;
5501 job->jv_proc_info.hProcess = child_process_handle;
5502 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5503 job->jv_job_object = jo;
5504 job->jv_status = JOB_STARTED;
5505 job->jv_tty_in = utf16_to_enc(
5506 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5507 job->jv_tty_out = utf16_to_enc(
5508 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5509 ++job->jv_refcount;
5510 term->tl_job = job;
5511
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005512 /* Redirecting stdout and stderr doesn't work at the job level. Instead
5513 * open the file here and handle it in. opt->jo_io was changed in
5514 * setup_job_options(), use the original flags here. */
5515 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
5516 {
5517 char_u *fname = opt->jo_io_name[PART_OUT];
5518
5519 ch_log(channel, "Opening output file %s", fname);
5520 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
5521 if (term->tl_out_fd == NULL)
5522 EMSG2(_(e_notopen), fname);
5523 }
5524
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005525 return OK;
5526
5527failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005528 ga_clear(&ga_cmd);
5529 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005530 vim_free(cmd_wchar);
5531 vim_free(cwd_wchar);
5532 if (spawn_config != NULL)
5533 winpty_spawn_config_free(spawn_config);
5534 if (channel != NULL)
5535 channel_clear(channel);
5536 if (job != NULL)
5537 {
5538 job->jv_channel = NULL;
5539 job_cleanup(job);
5540 }
5541 term->tl_job = NULL;
5542 if (jo != NULL)
5543 CloseHandle(jo);
5544 if (term->tl_winpty != NULL)
5545 winpty_free(term->tl_winpty);
5546 term->tl_winpty = NULL;
5547 if (term->tl_winpty_config != NULL)
5548 winpty_config_free(term->tl_winpty_config);
5549 term->tl_winpty_config = NULL;
5550 if (winpty_err != NULL)
5551 {
5552 char_u *msg = utf16_to_enc(
5553 (short_u *)winpty_error_msg(winpty_err), NULL);
5554
5555 EMSG(msg);
5556 winpty_error_free(winpty_err);
5557 }
5558 return FAIL;
5559}
5560
5561 static int
5562create_pty_only(term_T *term, jobopt_T *options)
5563{
5564 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5565 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5566 char in_name[80], out_name[80];
5567 channel_T *channel = NULL;
5568
5569 create_vterm(term, term->tl_rows, term->tl_cols);
5570
5571 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5572 GetCurrentProcessId(),
5573 curbuf->b_fnum);
5574 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5575 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5576 PIPE_UNLIMITED_INSTANCES,
5577 0, 0, NMPWAIT_NOWAIT, NULL);
5578 if (hPipeIn == INVALID_HANDLE_VALUE)
5579 goto failed;
5580
5581 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5582 GetCurrentProcessId(),
5583 curbuf->b_fnum);
5584 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5585 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5586 PIPE_UNLIMITED_INSTANCES,
5587 0, 0, 0, NULL);
5588 if (hPipeOut == INVALID_HANDLE_VALUE)
5589 goto failed;
5590
5591 ConnectNamedPipe(hPipeIn, NULL);
5592 ConnectNamedPipe(hPipeOut, NULL);
5593
5594 term->tl_job = job_alloc();
5595 if (term->tl_job == NULL)
5596 goto failed;
5597 ++term->tl_job->jv_refcount;
5598
5599 /* behave like the job is already finished */
5600 term->tl_job->jv_status = JOB_FINISHED;
5601
5602 channel = add_channel();
5603 if (channel == NULL)
5604 goto failed;
5605 term->tl_job->jv_channel = channel;
5606 channel->ch_keep_open = TRUE;
5607 channel->ch_named_pipe = TRUE;
5608
5609 channel_set_pipes(channel,
5610 (sock_T)hPipeIn,
5611 (sock_T)hPipeOut,
5612 (sock_T)hPipeOut);
5613 channel_set_job(channel, term->tl_job, options);
5614 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5615 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5616
5617 return OK;
5618
5619failed:
5620 if (hPipeIn != NULL)
5621 CloseHandle(hPipeIn);
5622 if (hPipeOut != NULL)
5623 CloseHandle(hPipeOut);
5624 return FAIL;
5625}
5626
5627/*
5628 * Free the terminal emulator part of "term".
5629 */
5630 static void
5631term_free_vterm(term_T *term)
5632{
5633 if (term->tl_winpty != NULL)
5634 winpty_free(term->tl_winpty);
5635 term->tl_winpty = NULL;
5636 if (term->tl_winpty_config != NULL)
5637 winpty_config_free(term->tl_winpty_config);
5638 term->tl_winpty_config = NULL;
5639 if (term->tl_vterm != NULL)
5640 vterm_free(term->tl_vterm);
5641 term->tl_vterm = NULL;
5642}
5643
5644/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005645 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005646 */
5647 static void
5648term_report_winsize(term_T *term, int rows, int cols)
5649{
5650 if (term->tl_winpty)
5651 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5652}
5653
5654 int
5655terminal_enabled(void)
5656{
5657 return dyn_winpty_init(FALSE) == OK;
5658}
5659
5660# else
5661
5662/**************************************
5663 * 3. Unix-like implementation.
5664 */
5665
5666/*
5667 * Create a new terminal of "rows" by "cols" cells.
5668 * Start job for "cmd".
5669 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005670 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005671 * Return OK or FAIL.
5672 */
5673 static int
5674term_and_job_init(
5675 term_T *term,
5676 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005677 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005678 jobopt_T *opt,
5679 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005680{
5681 create_vterm(term, term->tl_rows, term->tl_cols);
5682
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005683#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5684 if (opt->jo_set2 & JO2_ANSI_COLORS)
5685 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5686 else
5687 init_vterm_ansi_colors(term->tl_vterm);
5688#endif
5689
Bram Moolenaar13568252018-03-16 20:46:58 +01005690 /* This may change a string in "argvar". */
5691 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005692 if (term->tl_job != NULL)
5693 ++term->tl_job->jv_refcount;
5694
5695 return term->tl_job != NULL
5696 && term->tl_job->jv_channel != NULL
5697 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5698}
5699
5700 static int
5701create_pty_only(term_T *term, jobopt_T *opt)
5702{
5703 create_vterm(term, term->tl_rows, term->tl_cols);
5704
5705 term->tl_job = job_alloc();
5706 if (term->tl_job == NULL)
5707 return FAIL;
5708 ++term->tl_job->jv_refcount;
5709
5710 /* behave like the job is already finished */
5711 term->tl_job->jv_status = JOB_FINISHED;
5712
5713 return mch_create_pty_channel(term->tl_job, opt);
5714}
5715
5716/*
5717 * Free the terminal emulator part of "term".
5718 */
5719 static void
5720term_free_vterm(term_T *term)
5721{
5722 if (term->tl_vterm != NULL)
5723 vterm_free(term->tl_vterm);
5724 term->tl_vterm = NULL;
5725}
5726
5727/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005728 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005729 */
5730 static void
5731term_report_winsize(term_T *term, int rows, int cols)
5732{
5733 /* Use an ioctl() to report the new window size to the job. */
5734 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5735 {
5736 int fd = -1;
5737 int part;
5738
5739 for (part = PART_OUT; part < PART_COUNT; ++part)
5740 {
5741 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5742 if (isatty(fd))
5743 break;
5744 }
5745 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5746 mch_signal_job(term->tl_job, (char_u *)"winch");
5747 }
5748}
5749
5750# endif
5751
5752#endif /* FEAT_TERMINAL */