blob: 760591443842c196712643907339addc5aeb6b1a [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.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020039 */
40
41#include "vim.h"
42
43#if defined(FEAT_TERMINAL) || defined(PROTO)
44
45#ifndef MIN
46# define MIN(x,y) ((x) < (y) ? (x) : (y))
47#endif
48#ifndef MAX
49# define MAX(x,y) ((x) > (y) ? (x) : (y))
50#endif
51
52#include "libvterm/include/vterm.h"
53
54/* This is VTermScreenCell without the characters, thus much smaller. */
55typedef struct {
56 VTermScreenCellAttrs attrs;
57 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010058 VTermColor fg;
59 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020060} cellattr_T;
61
62typedef struct sb_line_S {
63 int sb_cols; /* can differ per line */
64 cellattr_T *sb_cells; /* allocated */
65 cellattr_T sb_fill_attr; /* for short line */
66} sb_line_T;
67
68/* typedef term_T in structs.h */
69struct terminal_S {
70 term_T *tl_next;
71
72 VTerm *tl_vterm;
73 job_T *tl_job;
74 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +010075#if defined(FEAT_GUI)
76 int tl_system; /* when non-zero used for :!cmd output */
77 int tl_toprow; /* row with first line of system terminal */
78#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020079
80 /* Set when setting the size of a vterm, reset after redrawing. */
81 int tl_vterm_size_changed;
82
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020083 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
84 int tl_channel_closed;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +020085 int tl_channel_recently_closed; // still need to handle tl_finish
86
Bram Moolenaar1dd98332018-03-16 22:54:53 +010087 int tl_finish;
88#define TL_FINISH_UNSET NUL
89#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
90#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
91#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020092 char_u *tl_opencmd;
93 char_u *tl_eof_chars;
94
95#ifdef WIN3264
96 void *tl_winpty_config;
97 void *tl_winpty;
Bram Moolenaarf25329c2018-05-06 21:49:32 +020098
99 FILE *tl_out_fd;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200100#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100101#if defined(FEAT_SESSION)
102 char_u *tl_command;
103#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100104 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200105
106 /* last known vterm size */
107 int tl_rows;
108 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200109
110 char_u *tl_title; /* NULL or allocated */
111 char_u *tl_status_text; /* NULL or allocated */
112
113 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200114 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200115 int tl_dirty_row_end; /* row below last one to update */
Bram Moolenaar56bc8e22018-05-10 18:05:56 +0200116 int tl_dirty_snapshot; /* text updated after making snapshot */
117#ifdef FEAT_TIMERS
118 int tl_timer_set;
119 proftime_T tl_timer_due;
120#endif
Bram Moolenaar6eddadf2018-05-06 16:40:16 +0200121 int tl_postponed_scroll; /* to be scrolled up */
122
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200123 garray_T tl_scrollback;
124 int tl_scrollback_scrolled;
125 cellattr_T tl_default_color;
126
Bram Moolenaard96ff162018-02-18 22:13:29 +0100127 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
128 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
129
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200130 VTermPos tl_cursor_pos;
131 int tl_cursor_visible;
132 int tl_cursor_blink;
133 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
134 char_u *tl_cursor_color; /* NULL or allocated */
135
136 int tl_using_altscreen;
137};
138
139#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
140#define TMODE_LOOP 2 /* CTRL-W N used */
141
142/*
143 * List of all active terminals.
144 */
145static term_T *first_term = NULL;
146
147/* Terminal active in terminal_loop(). */
148static term_T *in_terminal_loop = NULL;
149
150#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
151#define KEY_BUF_LEN 200
152
153/*
154 * Functions with separate implementation for MS-Windows and Unix-like systems.
155 */
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200156static 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 +0200157static int create_pty_only(term_T *term, jobopt_T *opt);
158static void term_report_winsize(term_T *term, int rows, int cols);
159static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100160#ifdef FEAT_GUI
161static void update_system_term(term_T *term);
162#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200163
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100164/* The character that we know (or assume) that the terminal expects for the
165 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200166static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200167
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100168/* "Terminal" highlight group colors. */
169static int term_default_cterm_fg = -1;
170static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200171
Bram Moolenaard317b382018-02-08 22:33:31 +0100172/* Store the last set and the desired cursor properties, so that we only update
173 * them when needed. Doing it unnecessary may result in flicker. */
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200174static char_u *last_set_cursor_color = NULL;
175static char_u *desired_cursor_color = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +0100176static int last_set_cursor_shape = -1;
177static int desired_cursor_shape = -1;
178static int last_set_cursor_blink = -1;
179static int desired_cursor_blink = -1;
180
181
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200182/**************************************
183 * 1. Generic code for all systems.
184 */
185
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200186 static int
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200187cursor_color_equal(char_u *lhs_color, char_u *rhs_color)
188{
189 if (lhs_color != NULL && rhs_color != NULL)
190 return STRCMP(lhs_color, rhs_color) == 0;
191 return lhs_color == NULL && rhs_color == NULL;
192}
193
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200194 static void
195cursor_color_copy(char_u **to_color, char_u *from_color)
196{
197 // Avoid a free & alloc if the value is already right.
198 if (cursor_color_equal(*to_color, from_color))
199 return;
200 vim_free(*to_color);
201 *to_color = (from_color == NULL) ? NULL : vim_strsave(from_color);
202}
203
204 static char_u *
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200205cursor_color_get(char_u *color)
206{
207 return (color == NULL) ? (char_u *)"" : color;
208}
209
210
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200211/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200212 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
Bram Moolenaar498c2562018-04-15 23:45:15 +0200213 * current window.
214 * Sets "rows" and/or "cols" to zero when it should follow the window size.
215 * Return TRUE if the size is the minimum size: "24*80".
216 */
217 static int
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200218parse_termwinsize(win_T *wp, int *rows, int *cols)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200219{
220 int minsize = FALSE;
221
222 *rows = 0;
223 *cols = 0;
224
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200225 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200226 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200227 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200228
229 /* Syntax of value was already checked when it's set. */
230 if (p == NULL)
231 {
232 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200233 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200234 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200235 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200236 *cols = atoi((char *)p + 1);
237 }
238 return minsize;
239}
240
241/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200242 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200243 */
244 static void
245set_term_and_win_size(term_T *term)
246{
Bram Moolenaar13568252018-03-16 20:46:58 +0100247#ifdef FEAT_GUI
248 if (term->tl_system)
249 {
250 /* Use the whole screen for the system command. However, it will start
251 * at the command line and scroll up as needed, using tl_toprow. */
252 term->tl_rows = Rows;
253 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200254 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100255 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100256#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200257 if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200258 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200259 if (term->tl_rows != 0)
260 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
261 if (term->tl_cols != 0)
262 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200263 }
264 if (term->tl_rows == 0)
265 term->tl_rows = curwin->w_height;
266 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200267 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200268 if (term->tl_cols == 0)
269 term->tl_cols = curwin->w_width;
270 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200271 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200272}
273
274/*
275 * Initialize job options for a terminal job.
276 * Caller may overrule some of them.
277 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100278 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200279init_job_options(jobopt_T *opt)
280{
281 clear_job_options(opt);
282
283 opt->jo_mode = MODE_RAW;
284 opt->jo_out_mode = MODE_RAW;
285 opt->jo_err_mode = MODE_RAW;
286 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
287}
288
289/*
290 * Set job options mandatory for a terminal job.
291 */
292 static void
293setup_job_options(jobopt_T *opt, int rows, int cols)
294{
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200295#ifndef WIN3264
296 /* Win32: Redirecting the job output won't work, thus always connect stdout
297 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200298 if (!(opt->jo_set & JO_OUT_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200299#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200300 {
301 /* Connect stdout to the terminal. */
302 opt->jo_io[PART_OUT] = JIO_BUFFER;
303 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
304 opt->jo_modifiable[PART_OUT] = 0;
305 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
306 }
307
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200308#ifndef WIN3264
309 /* Win32: Redirecting the job output won't work, thus always connect stderr
310 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200311 if (!(opt->jo_set & JO_ERR_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200312#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200313 {
314 /* Connect stderr to the terminal. */
315 opt->jo_io[PART_ERR] = JIO_BUFFER;
316 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
317 opt->jo_modifiable[PART_ERR] = 0;
318 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
319 }
320
321 opt->jo_pty = TRUE;
322 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
323 opt->jo_term_rows = rows;
324 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
325 opt->jo_term_cols = cols;
326}
327
328/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100329 * Close a terminal buffer (and its window). Used when creating the terminal
330 * fails.
331 */
332 static void
333term_close_buffer(buf_T *buf, buf_T *old_curbuf)
334{
335 free_terminal(buf);
336 if (old_curbuf != NULL)
337 {
338 --curbuf->b_nwindows;
339 curbuf = old_curbuf;
340 curwin->w_buffer = curbuf;
341 ++curbuf->b_nwindows;
342 }
343
344 /* Wiping out the buffer will also close the window and call
345 * free_terminal(). */
346 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
347}
348
349/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200350 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100351 * Use either "argvar" or "argv", the other must be NULL.
352 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
353 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200354 * Returns NULL when failed.
355 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100356 buf_T *
357term_start(
358 typval_T *argvar,
359 char **argv,
360 jobopt_T *opt,
361 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200362{
363 exarg_T split_ea;
364 win_T *old_curwin = curwin;
365 term_T *term;
366 buf_T *old_curbuf = NULL;
367 int res;
368 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100369 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200370 jobopt_T orig_opt; // only partly filled
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200371
372 if (check_restricted() || check_secure())
373 return NULL;
374
375 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
376 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
377 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
378 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
379 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100380 emsg(_(e_invarg));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200381 return NULL;
382 }
383
384 term = (term_T *)alloc_clear(sizeof(term_T));
385 if (term == NULL)
386 return NULL;
387 term->tl_dirty_row_end = MAX_ROW;
388 term->tl_cursor_visible = TRUE;
389 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
390 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100391#ifdef FEAT_GUI
392 term->tl_system = (flags & TERM_START_SYSTEM);
393#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200394 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
395
396 vim_memset(&split_ea, 0, sizeof(split_ea));
397 if (opt->jo_curwin)
398 {
399 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100400 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200401 {
402 no_write_message();
403 vim_free(term);
404 return NULL;
405 }
406 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100407 ECMD_HIDE
408 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
409 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200410 {
411 vim_free(term);
412 return NULL;
413 }
414 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100415 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200416 {
417 buf_T *buf;
418
419 /* Create a new buffer without a window. Make it the current buffer for
420 * a moment to be able to do the initialisations. */
421 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
422 BLN_NEW | BLN_LISTED);
423 if (buf == NULL || ml_open(buf) == FAIL)
424 {
425 vim_free(term);
426 return NULL;
427 }
428 old_curbuf = curbuf;
429 --curbuf->b_nwindows;
430 curbuf = buf;
431 curwin->w_buffer = buf;
432 ++curbuf->b_nwindows;
433 }
434 else
435 {
436 /* Open a new window or tab. */
437 split_ea.cmdidx = CMD_new;
438 split_ea.cmd = (char_u *)"new";
439 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100440 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200441 {
442 split_ea.line2 = opt->jo_term_rows;
443 split_ea.addr_count = 1;
444 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100445 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200446 {
447 split_ea.line2 = opt->jo_term_cols;
448 split_ea.addr_count = 1;
449 }
450
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100451 if (vertical)
452 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200453 ex_splitview(&split_ea);
454 if (curwin == old_curwin)
455 {
456 /* split failed */
457 vim_free(term);
458 return NULL;
459 }
460 }
461 term->tl_buffer = curbuf;
462 curbuf->b_term = term;
463
464 if (!opt->jo_hidden)
465 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100466 /* Only one size was taken care of with :new, do the other one. With
467 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100468 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200469 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100470 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200471 win_setwidth(opt->jo_term_cols);
472 }
473
474 /* Link the new terminal in the list of active terminals. */
475 term->tl_next = first_term;
476 first_term = term;
477
478 if (opt->jo_term_name != NULL)
479 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100480 else if (argv != NULL)
481 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200482 else
483 {
484 int i;
485 size_t len;
486 char_u *cmd, *p;
487
488 if (argvar->v_type == VAR_STRING)
489 {
490 cmd = argvar->vval.v_string;
491 if (cmd == NULL)
492 cmd = (char_u *)"";
493 else if (STRCMP(cmd, "NONE") == 0)
494 cmd = (char_u *)"pty";
495 }
496 else if (argvar->v_type != VAR_LIST
497 || argvar->vval.v_list == NULL
498 || argvar->vval.v_list->lv_len < 1
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100499 || (cmd = tv_get_string_chk(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200500 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
501 cmd = (char_u*)"";
502
503 len = STRLEN(cmd) + 10;
504 p = alloc((int)len);
505
506 for (i = 0; p != NULL; ++i)
507 {
508 /* Prepend a ! to the command name to avoid the buffer name equals
509 * the executable, otherwise ":w!" would overwrite it. */
510 if (i == 0)
511 vim_snprintf((char *)p, len, "!%s", cmd);
512 else
513 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
514 if (buflist_findname(p) == NULL)
515 {
516 vim_free(curbuf->b_ffname);
517 curbuf->b_ffname = p;
518 break;
519 }
520 }
521 }
522 curbuf->b_fname = curbuf->b_ffname;
523
524 if (opt->jo_term_opencmd != NULL)
525 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
526
527 if (opt->jo_eof_chars != NULL)
528 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
529
530 set_string_option_direct((char_u *)"buftype", -1,
531 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar7da1fb52018-08-04 16:54:11 +0200532 // Avoid that 'buftype' is reset when this buffer is entered.
533 curbuf->b_p_initialized = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200534
535 /* Mark the buffer as not modifiable. It can only be made modifiable after
536 * the job finished. */
537 curbuf->b_p_ma = FALSE;
538
539 set_term_and_win_size(term);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200540#ifdef WIN3264
541 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
542#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200543 setup_job_options(opt, term->tl_rows, term->tl_cols);
544
Bram Moolenaar13568252018-03-16 20:46:58 +0100545 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100546 return curbuf;
547
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100548#if defined(FEAT_SESSION)
549 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100550 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100551 {
552 term->tl_command = vim_strsave((char_u *)"NONE");
553 }
554 else if (argvar->v_type == VAR_STRING)
555 {
556 char_u *cmd = argvar->vval.v_string;
557
558 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
559 term->tl_command = vim_strsave(cmd);
560 }
561 else if (argvar->v_type == VAR_LIST
562 && argvar->vval.v_list != NULL
563 && argvar->vval.v_list->lv_len > 0)
564 {
565 garray_T ga;
566 listitem_T *item;
567
568 ga_init2(&ga, 1, 100);
569 for (item = argvar->vval.v_list->lv_first;
570 item != NULL; item = item->li_next)
571 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100572 char_u *s = tv_get_string_chk(&item->li_tv);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100573 char_u *p;
574
575 if (s == NULL)
576 break;
577 p = vim_strsave_fnameescape(s, FALSE);
578 if (p == NULL)
579 break;
580 ga_concat(&ga, p);
581 vim_free(p);
582 ga_append(&ga, ' ');
583 }
584 if (item == NULL)
585 {
586 ga_append(&ga, NUL);
587 term->tl_command = ga.ga_data;
588 }
589 else
590 ga_clear(&ga);
591 }
592#endif
593
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100594 if (opt->jo_term_kill != NULL)
595 {
596 char_u *p = skiptowhite(opt->jo_term_kill);
597
598 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
599 }
600
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200601 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100602 if (argv == NULL
603 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200604 && argvar->vval.v_string != NULL
605 && STRCMP(argvar->vval.v_string, "NONE") == 0)
606 res = create_pty_only(term, opt);
607 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200608 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200609
610 newbuf = curbuf;
611 if (res == OK)
612 {
613 /* Get and remember the size we ended up with. Update the pty. */
614 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
615 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100616#ifdef FEAT_GUI
617 if (term->tl_system)
618 {
619 /* display first line below typed command */
620 term->tl_toprow = msg_row + 1;
621 term->tl_dirty_row_end = 0;
622 }
623#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200624
625 /* Make sure we don't get stuck on sending keys to the job, it leads to
626 * a deadlock if the job is waiting for Vim to read. */
627 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
628
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200629 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200630 {
631 --curbuf->b_nwindows;
632 curbuf = old_curbuf;
633 curwin->w_buffer = curbuf;
634 ++curbuf->b_nwindows;
635 }
636 }
637 else
638 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100639 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200640 return NULL;
641 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100642
Bram Moolenaar13568252018-03-16 20:46:58 +0100643 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200644 return newbuf;
645}
646
647/*
648 * ":terminal": open a terminal window and execute a job in it.
649 */
650 void
651ex_terminal(exarg_T *eap)
652{
653 typval_T argvar[2];
654 jobopt_T opt;
655 char_u *cmd;
656 char_u *tofree = NULL;
657
658 init_job_options(&opt);
659
660 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100661 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200662 {
663 char_u *p, *ep;
664
665 cmd += 2;
666 p = skiptowhite(cmd);
667 ep = vim_strchr(cmd, '=');
668 if (ep != NULL && ep < p)
669 p = ep;
670
671 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
672 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100673 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
674 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200675 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
676 opt.jo_term_finish = 'o';
677 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
678 opt.jo_curwin = 1;
679 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
680 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100681 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
682 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100683 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
684 && ep != NULL)
685 {
686 opt.jo_set2 |= JO2_TERM_KILL;
687 opt.jo_term_kill = ep + 1;
688 p = skiptowhite(cmd);
689 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200690 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
691 && ep != NULL && isdigit(ep[1]))
692 {
693 opt.jo_set2 |= JO2_TERM_ROWS;
694 opt.jo_term_rows = atoi((char *)ep + 1);
695 p = skiptowhite(cmd);
696 }
697 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
698 && ep != NULL && isdigit(ep[1]))
699 {
700 opt.jo_set2 |= JO2_TERM_COLS;
701 opt.jo_term_cols = atoi((char *)ep + 1);
702 p = skiptowhite(cmd);
703 }
704 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
705 && ep != NULL)
706 {
707 char_u *buf = NULL;
708 char_u *keys;
709
710 p = skiptowhite(cmd);
711 *p = NUL;
712 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
713 opt.jo_set2 |= JO2_EOF_CHARS;
714 opt.jo_eof_chars = vim_strsave(keys);
715 vim_free(buf);
716 *p = ' ';
717 }
718 else
719 {
720 if (*p)
721 *p = NUL;
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100722 semsg(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100723 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200724 }
725 cmd = skipwhite(p);
726 }
727 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100728 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200729 /* Make a copy of 'shell', an autocommand may change the option. */
730 tofree = cmd = vim_strsave(p_sh);
731
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100732 /* default to close when the shell exits */
733 if (opt.jo_term_finish == NUL)
734 opt.jo_term_finish = 'c';
735 }
736
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200737 if (eap->addr_count > 0)
738 {
739 /* Write lines from current buffer to the job. */
740 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
741 opt.jo_io[PART_IN] = JIO_BUFFER;
742 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
743 opt.jo_in_top = eap->line1;
744 opt.jo_in_bot = eap->line2;
745 }
746
747 argvar[0].v_type = VAR_STRING;
748 argvar[0].vval.v_string = cmd;
749 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100750 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200751 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100752
753theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200754 vim_free(opt.jo_eof_chars);
755}
756
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100757#if defined(FEAT_SESSION) || defined(PROTO)
758/*
759 * Write a :terminal command to the session file to restore the terminal in
760 * window "wp".
761 * Return FAIL if writing fails.
762 */
763 int
764term_write_session(FILE *fd, win_T *wp)
765{
766 term_T *term = wp->w_buffer->b_term;
767
768 /* Create the terminal and run the command. This is not without
769 * risk, but let's assume the user only creates a session when this
770 * will be OK. */
771 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
772 term->tl_cols, term->tl_rows) < 0)
773 return FAIL;
774 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
775 return FAIL;
776
777 return put_eol(fd);
778}
779
780/*
781 * Return TRUE if "buf" has a terminal that should be restored.
782 */
783 int
784term_should_restore(buf_T *buf)
785{
786 term_T *term = buf->b_term;
787
788 return term != NULL && (term->tl_command == NULL
789 || STRCMP(term->tl_command, "NONE") != 0);
790}
791#endif
792
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200793/*
794 * Free the scrollback buffer for "term".
795 */
796 static void
797free_scrollback(term_T *term)
798{
799 int i;
800
801 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
802 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
803 ga_clear(&term->tl_scrollback);
804}
805
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100806
807// Terminals that need to be freed soon.
808term_T *terminals_to_free = NULL;
809
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200810/*
811 * Free a terminal and everything it refers to.
812 * Kills the job if there is one.
813 * Called when wiping out a buffer.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100814 * The actual terminal structure is freed later in free_unused_terminals(),
815 * because callbacks may wipe out a buffer while the terminal is still
816 * referenced.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200817 */
818 void
819free_terminal(buf_T *buf)
820{
821 term_T *term = buf->b_term;
822 term_T *tp;
823
824 if (term == NULL)
825 return;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100826
827 // Unlink the terminal form the list of terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200828 if (first_term == term)
829 first_term = term->tl_next;
830 else
831 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
832 if (tp->tl_next == term)
833 {
834 tp->tl_next = term->tl_next;
835 break;
836 }
837
838 if (term->tl_job != NULL)
839 {
840 if (term->tl_job->jv_status != JOB_ENDED
841 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100842 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200843 job_stop(term->tl_job, NULL, "kill");
844 job_unref(term->tl_job);
845 }
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100846 term->tl_next = terminals_to_free;
847 terminals_to_free = term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200848
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200849 buf->b_term = NULL;
850 if (in_terminal_loop == term)
851 in_terminal_loop = NULL;
852}
853
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100854 void
855free_unused_terminals()
856{
857 while (terminals_to_free != NULL)
858 {
859 term_T *term = terminals_to_free;
860
861 terminals_to_free = term->tl_next;
862
863 free_scrollback(term);
864
865 term_free_vterm(term);
866 vim_free(term->tl_title);
867#ifdef FEAT_SESSION
868 vim_free(term->tl_command);
869#endif
870 vim_free(term->tl_kill);
871 vim_free(term->tl_status_text);
872 vim_free(term->tl_opencmd);
873 vim_free(term->tl_eof_chars);
874#ifdef WIN3264
875 if (term->tl_out_fd != NULL)
876 fclose(term->tl_out_fd);
877#endif
878 vim_free(term->tl_cursor_color);
879 vim_free(term);
880 }
881}
882
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200883/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100884 * Get the part that is connected to the tty. Normally this is PART_IN, but
885 * when writing buffer lines to the job it can be another. This makes it
886 * possible to do "1,5term vim -".
887 */
888 static ch_part_T
889get_tty_part(term_T *term)
890{
891#ifdef UNIX
892 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
893 int i;
894
895 for (i = 0; i < 3; ++i)
896 {
897 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
898
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +0100899 if (mch_isatty(fd))
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100900 return parts[i];
901 }
902#endif
903 return PART_IN;
904}
905
906/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200907 * Write job output "msg[len]" to the vterm.
908 */
909 static void
910term_write_job_output(term_T *term, char_u *msg, size_t len)
911{
912 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100913 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200914
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100915 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200916
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100917 /* flush vterm buffer when vterm responded to control sequence */
918 if (prevlen != vterm_output_get_buffer_current(vterm))
919 {
920 char buf[KEY_BUF_LEN];
921 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
922
923 if (curlen > 0)
924 channel_send(term->tl_job->jv_channel, get_tty_part(term),
925 (char_u *)buf, (int)curlen, NULL);
926 }
927
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200928 /* this invokes the damage callbacks */
929 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
930}
931
932 static void
933update_cursor(term_T *term, int redraw)
934{
935 if (term->tl_normal_mode)
936 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100937#ifdef FEAT_GUI
938 if (term->tl_system)
939 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
940 term->tl_cursor_pos.col);
941 else
942#endif
943 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200944 if (redraw)
945 {
946 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
947 cursor_on();
948 out_flush();
949#ifdef FEAT_GUI
950 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100951 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200952 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100953 gui_mch_flush();
954 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200955#endif
956 }
957}
958
959/*
960 * Invoked when "msg" output from a job was received. Write it to the terminal
961 * of "buffer".
962 */
963 void
964write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
965{
966 size_t len = STRLEN(msg);
967 term_T *term = buffer->b_term;
968
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200969#ifdef WIN3264
970 /* Win32: Cannot redirect output of the job, intercept it here and write to
971 * the file. */
972 if (term->tl_out_fd != NULL)
973 {
974 ch_log(channel, "Writing %d bytes to output file", (int)len);
975 fwrite(msg, len, 1, term->tl_out_fd);
976 return;
977 }
978#endif
979
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200980 if (term->tl_vterm == NULL)
981 {
982 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
983 return;
984 }
985 ch_log(channel, "writing %d bytes to terminal", (int)len);
986 term_write_job_output(term, msg, len);
987
Bram Moolenaar13568252018-03-16 20:46:58 +0100988#ifdef FEAT_GUI
989 if (term->tl_system)
990 {
991 /* show system output, scrolling up the screen as needed */
992 update_system_term(term);
993 update_cursor(term, TRUE);
994 }
995 else
996#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200997 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
998 * contents, thus no screen update is needed. */
999 if (!term->tl_normal_mode)
1000 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001001 // Don't use update_screen() when editing the command line, it gets
1002 // cleared.
1003 // TODO: only update once in a while.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001004 ch_log(term->tl_job->jv_channel, "updating screen");
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001005 if (buffer == curbuf && (State & CMDLINE) == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001006 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001007 update_screen(VALID_NO_UPDATE);
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02001008 /* update_screen() can be slow, check the terminal wasn't closed
1009 * already */
1010 if (buffer == curbuf && curbuf->b_term != NULL)
1011 update_cursor(curbuf->b_term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001012 }
1013 else
1014 redraw_after_callback(TRUE);
1015 }
1016}
1017
1018/*
1019 * Send a mouse position and click to the vterm
1020 */
1021 static int
1022term_send_mouse(VTerm *vterm, int button, int pressed)
1023{
1024 VTermModifier mod = VTERM_MOD_NONE;
1025
1026 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +02001027 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001028 if (button != 0)
1029 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001030 return TRUE;
1031}
1032
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001033static int enter_mouse_col = -1;
1034static int enter_mouse_row = -1;
1035
1036/*
1037 * Handle a mouse click, drag or release.
1038 * Return TRUE when a mouse event is sent to the terminal.
1039 */
1040 static int
1041term_mouse_click(VTerm *vterm, int key)
1042{
1043#if defined(FEAT_CLIPBOARD)
1044 /* For modeless selection mouse drag and release events are ignored, unless
1045 * they are preceded with a mouse down event */
1046 static int ignore_drag_release = TRUE;
1047 VTermMouseState mouse_state;
1048
1049 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1050 if (mouse_state.flags == 0)
1051 {
1052 /* Terminal is not using the mouse, use modeless selection. */
1053 switch (key)
1054 {
1055 case K_LEFTDRAG:
1056 case K_LEFTRELEASE:
1057 case K_RIGHTDRAG:
1058 case K_RIGHTRELEASE:
1059 /* Ignore drag and release events when the button-down wasn't
1060 * seen before. */
1061 if (ignore_drag_release)
1062 {
1063 int save_mouse_col, save_mouse_row;
1064
1065 if (enter_mouse_col < 0)
1066 break;
1067
1068 /* mouse click in the window gave us focus, handle that
1069 * click now */
1070 save_mouse_col = mouse_col;
1071 save_mouse_row = mouse_row;
1072 mouse_col = enter_mouse_col;
1073 mouse_row = enter_mouse_row;
1074 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1075 mouse_col = save_mouse_col;
1076 mouse_row = save_mouse_row;
1077 }
1078 /* FALLTHROUGH */
1079 case K_LEFTMOUSE:
1080 case K_RIGHTMOUSE:
1081 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1082 ignore_drag_release = TRUE;
1083 else
1084 ignore_drag_release = FALSE;
1085 /* Should we call mouse_has() here? */
1086 if (clip_star.available)
1087 {
1088 int button, is_click, is_drag;
1089
1090 button = get_mouse_button(KEY2TERMCAP1(key),
1091 &is_click, &is_drag);
1092 if (mouse_model_popup() && button == MOUSE_LEFT
1093 && (mod_mask & MOD_MASK_SHIFT))
1094 {
1095 /* Translate shift-left to right button. */
1096 button = MOUSE_RIGHT;
1097 mod_mask &= ~MOD_MASK_SHIFT;
1098 }
1099 clip_modeless(button, is_click, is_drag);
1100 }
1101 break;
1102
1103 case K_MIDDLEMOUSE:
1104 if (clip_star.available)
1105 insert_reg('*', TRUE);
1106 break;
1107 }
1108 enter_mouse_col = -1;
1109 return FALSE;
1110 }
1111#endif
1112 enter_mouse_col = -1;
1113
1114 switch (key)
1115 {
1116 case K_LEFTMOUSE:
1117 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1118 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1119 case K_LEFTRELEASE:
1120 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1121 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1122 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1123 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1124 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1125 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1126 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1127 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1128 }
1129 return TRUE;
1130}
1131
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001132/*
1133 * Convert typed key "c" into bytes to send to the job.
1134 * Return the number of bytes in "buf".
1135 */
1136 static int
1137term_convert_key(term_T *term, int c, char *buf)
1138{
1139 VTerm *vterm = term->tl_vterm;
1140 VTermKey key = VTERM_KEY_NONE;
1141 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001142 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001143
1144 switch (c)
1145 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001146 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1147
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001148 /* don't use VTERM_KEY_BACKSPACE, it always
1149 * becomes 0x7f DEL */
1150 case K_BS: c = term_backspace_char; break;
1151
1152 case ESC: key = VTERM_KEY_ESCAPE; break;
1153 case K_DEL: key = VTERM_KEY_DEL; break;
1154 case K_DOWN: key = VTERM_KEY_DOWN; break;
1155 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1156 key = VTERM_KEY_DOWN; break;
1157 case K_END: key = VTERM_KEY_END; break;
1158 case K_S_END: mod = VTERM_MOD_SHIFT;
1159 key = VTERM_KEY_END; break;
1160 case K_C_END: mod = VTERM_MOD_CTRL;
1161 key = VTERM_KEY_END; break;
1162 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1163 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1164 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1165 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1166 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1167 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1168 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1169 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1170 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1171 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1172 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1173 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1174 case K_HOME: key = VTERM_KEY_HOME; break;
1175 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1176 key = VTERM_KEY_HOME; break;
1177 case K_C_HOME: mod = VTERM_MOD_CTRL;
1178 key = VTERM_KEY_HOME; break;
1179 case K_INS: key = VTERM_KEY_INS; break;
1180 case K_K0: key = VTERM_KEY_KP_0; break;
1181 case K_K1: key = VTERM_KEY_KP_1; break;
1182 case K_K2: key = VTERM_KEY_KP_2; break;
1183 case K_K3: key = VTERM_KEY_KP_3; break;
1184 case K_K4: key = VTERM_KEY_KP_4; break;
1185 case K_K5: key = VTERM_KEY_KP_5; break;
1186 case K_K6: key = VTERM_KEY_KP_6; break;
1187 case K_K7: key = VTERM_KEY_KP_7; break;
1188 case K_K8: key = VTERM_KEY_KP_8; break;
1189 case K_K9: key = VTERM_KEY_KP_9; break;
1190 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1191 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1192 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1193 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1194 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1195 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1196 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1197 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1198 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1199 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1200 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1201 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1202 case K_LEFT: key = VTERM_KEY_LEFT; break;
1203 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1204 key = VTERM_KEY_LEFT; break;
1205 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1206 key = VTERM_KEY_LEFT; break;
1207 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1208 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1209 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1210 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1211 key = VTERM_KEY_RIGHT; break;
1212 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1213 key = VTERM_KEY_RIGHT; break;
1214 case K_UP: key = VTERM_KEY_UP; break;
1215 case K_S_UP: mod = VTERM_MOD_SHIFT;
1216 key = VTERM_KEY_UP; break;
1217 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001218 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1219 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001220
Bram Moolenaara42ad572017-11-16 13:08:04 +01001221 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1222 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001223 case K_MOUSELEFT: /* TODO */ return 0;
1224 case K_MOUSERIGHT: /* TODO */ return 0;
1225
1226 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001227 case K_LEFTMOUSE_NM:
1228 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001229 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001230 case K_LEFTRELEASE_NM:
1231 case K_MOUSEMOVE:
1232 case K_MIDDLEMOUSE:
1233 case K_MIDDLEDRAG:
1234 case K_MIDDLERELEASE:
1235 case K_RIGHTMOUSE:
1236 case K_RIGHTDRAG:
1237 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1238 return 0;
1239 other = TRUE;
1240 break;
1241
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001242 case K_X1MOUSE: /* TODO */ return 0;
1243 case K_X1DRAG: /* TODO */ return 0;
1244 case K_X1RELEASE: /* TODO */ return 0;
1245 case K_X2MOUSE: /* TODO */ return 0;
1246 case K_X2DRAG: /* TODO */ return 0;
1247 case K_X2RELEASE: /* TODO */ return 0;
1248
1249 case K_IGNORE: return 0;
1250 case K_NOP: return 0;
1251 case K_UNDO: return 0;
1252 case K_HELP: return 0;
1253 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1254 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1255 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1256 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1257 case K_SELECT: return 0;
1258#ifdef FEAT_GUI
1259 case K_VER_SCROLLBAR: return 0;
1260 case K_HOR_SCROLLBAR: return 0;
1261#endif
1262#ifdef FEAT_GUI_TABLINE
1263 case K_TABLINE: return 0;
1264 case K_TABMENU: return 0;
1265#endif
1266#ifdef FEAT_NETBEANS_INTG
1267 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1268#endif
1269#ifdef FEAT_DND
1270 case K_DROP: return 0;
1271#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001272 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001273 case K_PS: vterm_keyboard_start_paste(vterm);
1274 other = TRUE;
1275 break;
1276 case K_PE: vterm_keyboard_end_paste(vterm);
1277 other = TRUE;
1278 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001279 }
1280
1281 /*
1282 * Convert special keys to vterm keys:
1283 * - Write keys to vterm: vterm_keyboard_key()
1284 * - Write output to channel.
1285 * TODO: use mod_mask
1286 */
1287 if (key != VTERM_KEY_NONE)
1288 /* Special key, let vterm convert it. */
1289 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001290 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001291 /* Normal character, let vterm convert it. */
1292 vterm_keyboard_unichar(vterm, c, mod);
1293
1294 /* Read back the converted escape sequence. */
1295 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1296}
1297
1298/*
1299 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001300 * If "check_job_status" is TRUE update the job status.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001301 * NOTE: "term" may be freed by callbacks.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001302 */
1303 static int
1304term_job_running_check(term_T *term, int check_job_status)
1305{
1306 /* Also consider the job finished when the channel is closed, to avoid a
1307 * race condition when updating the title. */
1308 if (term != NULL
1309 && term->tl_job != NULL
1310 && channel_is_open(term->tl_job->jv_channel))
1311 {
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001312 job_T *job = term->tl_job;
1313
1314 // Careful: Checking the job status may invoked callbacks, which close
1315 // the buffer and terminate "term". However, "job" will not be freed
1316 // yet.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001317 if (check_job_status)
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001318 job_status(job);
1319 return (job->jv_status == JOB_STARTED
1320 || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001321 }
1322 return FALSE;
1323}
1324
1325/*
1326 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001327 */
1328 int
1329term_job_running(term_T *term)
1330{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001331 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001332}
1333
1334/*
1335 * Return TRUE if "term" has an active channel and used ":term NONE".
1336 */
1337 int
1338term_none_open(term_T *term)
1339{
1340 /* Also consider the job finished when the channel is closed, to avoid a
1341 * race condition when updating the title. */
1342 return term != NULL
1343 && term->tl_job != NULL
1344 && channel_is_open(term->tl_job->jv_channel)
1345 && term->tl_job->jv_channel->ch_keep_open;
1346}
1347
1348/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001349 * Used when exiting: kill the job in "buf" if so desired.
1350 * Return OK when the job finished.
1351 * Return FAIL when the job is still running.
1352 */
1353 int
1354term_try_stop_job(buf_T *buf)
1355{
1356 int count;
1357 char *how = (char *)buf->b_term->tl_kill;
1358
1359#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1360 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1361 {
1362 char_u buff[DIALOG_MSG_SIZE];
1363 int ret;
1364
1365 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1366 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1367 if (ret == VIM_YES)
1368 how = "kill";
1369 else if (ret == VIM_CANCEL)
1370 return FAIL;
1371 }
1372#endif
1373 if (how == NULL || *how == NUL)
1374 return FAIL;
1375
1376 job_stop(buf->b_term->tl_job, NULL, how);
1377
Bram Moolenaar9172d232019-01-29 23:06:54 +01001378 // wait for up to a second for the job to die
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001379 for (count = 0; count < 100; ++count)
1380 {
Bram Moolenaar9172d232019-01-29 23:06:54 +01001381 job_T *job;
1382
1383 // buffer, terminal and job may be cleaned up while waiting
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001384 if (!buf_valid(buf)
1385 || buf->b_term == NULL
1386 || buf->b_term->tl_job == NULL)
1387 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001388 job = buf->b_term->tl_job;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001389
Bram Moolenaar9172d232019-01-29 23:06:54 +01001390 // Call job_status() to update jv_status. It may cause the job to be
1391 // cleaned up but it won't be freed.
1392 job_status(job);
1393 if (job->jv_status >= JOB_ENDED)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001394 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001395
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001396 ui_delay(10L, FALSE);
1397 mch_check_messages();
1398 parse_queued_messages();
1399 }
1400 return FAIL;
1401}
1402
1403/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001404 * Add the last line of the scrollback buffer to the buffer in the window.
1405 */
1406 static void
1407add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1408{
1409 buf_T *buf = term->tl_buffer;
1410 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1411 linenr_T lnum = buf->b_ml.ml_line_count;
1412
1413#ifdef WIN3264
1414 if (!enc_utf8 && enc_codepage > 0)
1415 {
1416 WCHAR *ret = NULL;
1417 int length = 0;
1418
1419 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1420 &ret, &length);
1421 if (ret != NULL)
1422 {
1423 WideCharToMultiByte_alloc(enc_codepage, 0,
1424 ret, length, (char **)&text, &len, 0, 0);
1425 vim_free(ret);
1426 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1427 vim_free(text);
1428 }
1429 }
1430 else
1431#endif
1432 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1433 if (empty)
1434 {
1435 /* Delete the empty line that was in the empty buffer. */
1436 curbuf = buf;
1437 ml_delete(1, FALSE);
1438 curbuf = curwin->w_buffer;
1439 }
1440}
1441
1442 static void
1443cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1444{
1445 attr->width = cell->width;
1446 attr->attrs = cell->attrs;
1447 attr->fg = cell->fg;
1448 attr->bg = cell->bg;
1449}
1450
1451 static int
1452equal_celattr(cellattr_T *a, cellattr_T *b)
1453{
1454 /* Comparing the colors should be sufficient. */
1455 return a->fg.red == b->fg.red
1456 && a->fg.green == b->fg.green
1457 && a->fg.blue == b->fg.blue
1458 && a->bg.red == b->bg.red
1459 && a->bg.green == b->bg.green
1460 && a->bg.blue == b->bg.blue;
1461}
1462
Bram Moolenaard96ff162018-02-18 22:13:29 +01001463/*
1464 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1465 * line at this position. Otherwise at the end.
1466 */
1467 static int
1468add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1469{
1470 if (ga_grow(&term->tl_scrollback, 1) == OK)
1471 {
1472 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1473 + term->tl_scrollback.ga_len;
1474
1475 if (lnum > 0)
1476 {
1477 int i;
1478
1479 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1480 {
1481 *line = *(line - 1);
1482 --line;
1483 }
1484 }
1485 line->sb_cols = 0;
1486 line->sb_cells = NULL;
1487 line->sb_fill_attr = *fill_attr;
1488 ++term->tl_scrollback.ga_len;
1489 return OK;
1490 }
1491 return FALSE;
1492}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001493
1494/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001495 * Remove the terminal contents from the scrollback and the buffer.
1496 * Used before adding a new scrollback line or updating the buffer for lines
1497 * displayed in the terminal.
1498 */
1499 static void
1500cleanup_scrollback(term_T *term)
1501{
1502 sb_line_T *line;
1503 garray_T *gap;
1504
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001505 curbuf = term->tl_buffer;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001506 gap = &term->tl_scrollback;
1507 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1508 && gap->ga_len > 0)
1509 {
1510 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1511 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1512 vim_free(line->sb_cells);
1513 --gap->ga_len;
1514 }
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001515 curbuf = curwin->w_buffer;
1516 if (curbuf == term->tl_buffer)
1517 check_cursor();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001518}
1519
1520/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001521 * Add the current lines of the terminal to scrollback and to the buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001522 */
1523 static void
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001524update_snapshot(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001525{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001526 VTermScreen *screen;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001527 int len;
1528 int lines_skipped = 0;
1529 VTermPos pos;
1530 VTermScreenCell cell;
1531 cellattr_T fill_attr, new_fill_attr;
1532 cellattr_T *p;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001533
1534 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1535 "Adding terminal window snapshot to buffer");
1536
1537 /* First remove the lines that were appended before, they might be
1538 * outdated. */
1539 cleanup_scrollback(term);
1540
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001541 screen = vterm_obtain_screen(term->tl_vterm);
1542 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001543 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1544 {
1545 len = 0;
1546 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1547 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1548 && cell.chars[0] != NUL)
1549 {
1550 len = pos.col + 1;
1551 new_fill_attr = term->tl_default_color;
1552 }
1553 else
1554 /* Assume the last attr is the filler attr. */
1555 cell2cellattr(&cell, &new_fill_attr);
1556
1557 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1558 ++lines_skipped;
1559 else
1560 {
1561 while (lines_skipped > 0)
1562 {
1563 /* Line was skipped, add an empty line. */
1564 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001565 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001566 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001567 }
1568
1569 if (len == 0)
1570 p = NULL;
1571 else
1572 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1573 if ((p != NULL || len == 0)
1574 && ga_grow(&term->tl_scrollback, 1) == OK)
1575 {
1576 garray_T ga;
1577 int width;
1578 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1579 + term->tl_scrollback.ga_len;
1580
1581 ga_init2(&ga, 1, 100);
1582 for (pos.col = 0; pos.col < len; pos.col += width)
1583 {
1584 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1585 {
1586 width = 1;
1587 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1588 if (ga_grow(&ga, 1) == OK)
1589 ga.ga_len += utf_char2bytes(' ',
1590 (char_u *)ga.ga_data + ga.ga_len);
1591 }
1592 else
1593 {
1594 width = cell.width;
1595
1596 cell2cellattr(&cell, &p[pos.col]);
1597
Bram Moolenaara79fd562018-12-20 20:47:32 +01001598 // Each character can be up to 6 bytes.
1599 if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001600 {
1601 int i;
1602 int c;
1603
1604 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1605 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1606 (char_u *)ga.ga_data + ga.ga_len);
1607 }
1608 }
1609 }
1610 line->sb_cols = len;
1611 line->sb_cells = p;
1612 line->sb_fill_attr = new_fill_attr;
1613 fill_attr = new_fill_attr;
1614 ++term->tl_scrollback.ga_len;
1615
1616 if (ga_grow(&ga, 1) == FAIL)
1617 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1618 else
1619 {
1620 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1621 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1622 }
1623 ga_clear(&ga);
1624 }
1625 else
1626 vim_free(p);
1627 }
1628 }
1629
Bram Moolenaarf3aea592018-11-11 22:18:21 +01001630 // Add trailing empty lines.
1631 for (pos.row = term->tl_scrollback.ga_len;
1632 pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row;
1633 ++pos.row)
1634 {
1635 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
1636 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1637 }
1638
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001639 term->tl_dirty_snapshot = FALSE;
1640#ifdef FEAT_TIMERS
1641 term->tl_timer_set = FALSE;
1642#endif
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001643}
1644
1645/*
1646 * If needed, add the current lines of the terminal to scrollback and to the
1647 * buffer. Called after the job has ended and when switching to
1648 * Terminal-Normal mode.
1649 * When "redraw" is TRUE redraw the windows that show the terminal.
1650 */
1651 static void
1652may_move_terminal_to_buffer(term_T *term, int redraw)
1653{
1654 win_T *wp;
1655
1656 if (term->tl_vterm == NULL)
1657 return;
1658
1659 /* Update the snapshot only if something changes or the buffer does not
1660 * have all the lines. */
1661 if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
1662 <= term->tl_scrollback_scrolled)
1663 update_snapshot(term);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001664
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001665 /* Obtain the current background color. */
1666 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1667 &term->tl_default_color.fg, &term->tl_default_color.bg);
1668
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001669 if (redraw)
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001670 FOR_ALL_WINDOWS(wp)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001671 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001672 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001673 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001674 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1675 wp->w_cursor.col = 0;
1676 wp->w_valid = 0;
1677 if (wp->w_cursor.lnum >= wp->w_height)
1678 {
1679 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001680
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001681 if (wp->w_topline < min_topline)
1682 wp->w_topline = min_topline;
1683 }
1684 redraw_win_later(wp, NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001685 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001686 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001687}
1688
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001689#if defined(FEAT_TIMERS) || defined(PROTO)
1690/*
1691 * Check if any terminal timer expired. If so, copy text from the terminal to
1692 * the buffer.
1693 * Return the time until the next timer will expire.
1694 */
1695 int
1696term_check_timers(int next_due_arg, proftime_T *now)
1697{
1698 term_T *term;
1699 int next_due = next_due_arg;
1700
1701 for (term = first_term; term != NULL; term = term->tl_next)
1702 {
1703 if (term->tl_timer_set && !term->tl_normal_mode)
1704 {
1705 long this_due = proftime_time_left(&term->tl_timer_due, now);
1706
1707 if (this_due <= 1)
1708 {
1709 term->tl_timer_set = FALSE;
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001710 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001711 }
1712 else if (next_due == -1 || next_due > this_due)
1713 next_due = this_due;
1714 }
1715 }
1716
1717 return next_due;
1718}
1719#endif
1720
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001721 static void
1722set_terminal_mode(term_T *term, int normal_mode)
1723{
1724 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001725 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001726 if (term->tl_buffer == curbuf)
1727 maketitle();
1728}
1729
1730/*
1731 * Called after the job if finished and Terminal mode is not active:
1732 * Move the vterm contents into the scrollback buffer and free the vterm.
1733 */
1734 static void
1735cleanup_vterm(term_T *term)
1736{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001737 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001738 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001739 term_free_vterm(term);
1740 set_terminal_mode(term, FALSE);
1741}
1742
1743/*
1744 * Switch from Terminal-Job mode to Terminal-Normal mode.
1745 * Suspends updating the terminal window.
1746 */
1747 static void
1748term_enter_normal_mode(void)
1749{
1750 term_T *term = curbuf->b_term;
1751
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001752 set_terminal_mode(term, TRUE);
1753
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001754 /* Append the current terminal contents to the buffer. */
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001755 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001756
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001757 /* Move the window cursor to the position of the cursor in the
1758 * terminal. */
1759 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1760 + term->tl_cursor_pos.row + 1;
1761 check_cursor();
Bram Moolenaar620020e2018-05-13 19:06:12 +02001762 if (coladvance(term->tl_cursor_pos.col) == FAIL)
1763 coladvance(MAXCOL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001764
1765 /* Display the same lines as in the terminal. */
1766 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1767}
1768
1769/*
1770 * Returns TRUE if the current window contains a terminal and we are in
1771 * Terminal-Normal mode.
1772 */
1773 int
1774term_in_normal_mode(void)
1775{
1776 term_T *term = curbuf->b_term;
1777
1778 return term != NULL && term->tl_normal_mode;
1779}
1780
1781/*
1782 * Switch from Terminal-Normal mode to Terminal-Job mode.
1783 * Restores updating the terminal window.
1784 */
1785 void
1786term_enter_job_mode()
1787{
1788 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001789
1790 set_terminal_mode(term, FALSE);
1791
1792 if (term->tl_channel_closed)
1793 cleanup_vterm(term);
1794 redraw_buf_and_status_later(curbuf, NOT_VALID);
1795}
1796
1797/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001798 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001799 * Note: while waiting a terminal may be closed and freed if the channel is
1800 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001801 */
1802 static int
1803term_vgetc()
1804{
1805 int c;
1806 int save_State = State;
1807
1808 State = TERMINAL;
1809 got_int = FALSE;
1810#ifdef WIN3264
1811 ctrl_break_was_pressed = FALSE;
1812#endif
1813 c = vgetc();
1814 got_int = FALSE;
1815 State = save_State;
1816 return c;
1817}
1818
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001819static int mouse_was_outside = FALSE;
1820
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001821/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001822 * Send keys to terminal.
1823 * Return FAIL when the key needs to be handled in Normal mode.
1824 * Return OK when the key was dropped or sent to the terminal.
1825 */
1826 int
1827send_keys_to_term(term_T *term, int c, int typed)
1828{
1829 char msg[KEY_BUF_LEN];
1830 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001831 int dragging_outside = FALSE;
1832
1833 /* Catch keys that need to be handled as in Normal mode. */
1834 switch (c)
1835 {
1836 case NUL:
1837 case K_ZERO:
1838 if (typed)
1839 stuffcharReadbuff(c);
1840 return FAIL;
1841
Bram Moolenaar231a2db2018-05-06 13:53:50 +02001842 case K_TABLINE:
1843 stuffcharReadbuff(c);
1844 return FAIL;
1845
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001846 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001847 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001848 return FAIL;
1849
1850 case K_LEFTDRAG:
1851 case K_MIDDLEDRAG:
1852 case K_RIGHTDRAG:
1853 case K_X1DRAG:
1854 case K_X2DRAG:
1855 dragging_outside = mouse_was_outside;
1856 /* FALLTHROUGH */
1857 case K_LEFTMOUSE:
1858 case K_LEFTMOUSE_NM:
1859 case K_LEFTRELEASE:
1860 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001861 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001862 case K_MIDDLEMOUSE:
1863 case K_MIDDLERELEASE:
1864 case K_RIGHTMOUSE:
1865 case K_RIGHTRELEASE:
1866 case K_X1MOUSE:
1867 case K_X1RELEASE:
1868 case K_X2MOUSE:
1869 case K_X2RELEASE:
1870
1871 case K_MOUSEUP:
1872 case K_MOUSEDOWN:
1873 case K_MOUSELEFT:
1874 case K_MOUSERIGHT:
1875 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001876 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001877 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001878 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001879 || dragging_outside)
1880 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001881 /* click or scroll outside the current window or on status line
1882 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001883 if (typed)
1884 {
1885 stuffcharReadbuff(c);
1886 mouse_was_outside = TRUE;
1887 }
1888 return FAIL;
1889 }
1890 }
1891 if (typed)
1892 mouse_was_outside = FALSE;
1893
1894 /* Convert the typed key to a sequence of bytes for the job. */
1895 len = term_convert_key(term, c, msg);
1896 if (len > 0)
1897 /* TODO: if FAIL is returned, stop? */
1898 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1899 (char_u *)msg, (int)len, NULL);
1900
1901 return OK;
1902}
1903
1904 static void
1905position_cursor(win_T *wp, VTermPos *pos)
1906{
1907 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1908 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1909 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1910}
1911
1912/*
1913 * Handle CTRL-W "": send register contents to the job.
1914 */
1915 static void
1916term_paste_register(int prev_c UNUSED)
1917{
1918 int c;
1919 list_T *l;
1920 listitem_T *item;
1921 long reglen = 0;
1922 int type;
1923
1924#ifdef FEAT_CMDL_INFO
1925 if (add_to_showcmd(prev_c))
1926 if (add_to_showcmd('"'))
1927 out_flush();
1928#endif
1929 c = term_vgetc();
1930#ifdef FEAT_CMDL_INFO
1931 clear_showcmd();
1932#endif
1933 if (!term_use_loop())
1934 /* job finished while waiting for a character */
1935 return;
1936
1937 /* CTRL-W "= prompt for expression to evaluate. */
1938 if (c == '=' && get_expr_register() != '=')
1939 return;
1940 if (!term_use_loop())
1941 /* job finished while waiting for a character */
1942 return;
1943
1944 l = (list_T *)get_reg_contents(c, GREG_LIST);
1945 if (l != NULL)
1946 {
1947 type = get_reg_type(c, &reglen);
1948 for (item = l->lv_first; item != NULL; item = item->li_next)
1949 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01001950 char_u *s = tv_get_string(&item->li_tv);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001951#ifdef WIN3264
1952 char_u *tmp = s;
1953
1954 if (!enc_utf8 && enc_codepage > 0)
1955 {
1956 WCHAR *ret = NULL;
1957 int length = 0;
1958
1959 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1960 (int)STRLEN(s), &ret, &length);
1961 if (ret != NULL)
1962 {
1963 WideCharToMultiByte_alloc(CP_UTF8, 0,
1964 ret, length, (char **)&s, &length, 0, 0);
1965 vim_free(ret);
1966 }
1967 }
1968#endif
1969 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1970 s, (int)STRLEN(s), NULL);
1971#ifdef WIN3264
1972 if (tmp != s)
1973 vim_free(s);
1974#endif
1975
1976 if (item->li_next != NULL || type == MLINE)
1977 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1978 (char_u *)"\r", 1, NULL);
1979 }
1980 list_free(l);
1981 }
1982}
1983
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001984/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001985 * Return TRUE when waiting for a character in the terminal, the cursor of the
1986 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001987 */
1988 int
1989terminal_is_active()
1990{
1991 return in_terminal_loop != NULL;
1992}
1993
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001994#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001995 cursorentry_T *
1996term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1997{
1998 term_T *term = in_terminal_loop;
1999 static cursorentry_T entry;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002000 int id;
2001 guicolor_T term_fg, term_bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002002
2003 vim_memset(&entry, 0, sizeof(entry));
2004 entry.shape = entry.mshape =
2005 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
2006 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
2007 SHAPE_BLOCK;
2008 entry.percentage = 20;
2009 if (term->tl_cursor_blink)
2010 {
2011 entry.blinkwait = 700;
2012 entry.blinkon = 400;
2013 entry.blinkoff = 250;
2014 }
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002015
2016 /* The "Terminal" highlight group overrules the defaults. */
2017 id = syn_name2id((char_u *)"Terminal");
2018 if (id != 0)
2019 {
2020 syn_id2colors(id, &term_fg, &term_bg);
2021 *fg = term_bg;
2022 }
2023 else
2024 *fg = gui.back_pixel;
2025
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002026 if (term->tl_cursor_color == NULL)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002027 {
2028 if (id != 0)
2029 *bg = term_fg;
2030 else
2031 *bg = gui.norm_pixel;
2032 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002033 else
2034 *bg = color_name2handle(term->tl_cursor_color);
2035 entry.name = "n";
2036 entry.used_for = SHAPE_CURSOR;
2037
2038 return &entry;
2039}
2040#endif
2041
Bram Moolenaard317b382018-02-08 22:33:31 +01002042 static void
2043may_output_cursor_props(void)
2044{
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002045 if (!cursor_color_equal(last_set_cursor_color, desired_cursor_color)
Bram Moolenaard317b382018-02-08 22:33:31 +01002046 || last_set_cursor_shape != desired_cursor_shape
2047 || last_set_cursor_blink != desired_cursor_blink)
2048 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002049 cursor_color_copy(&last_set_cursor_color, desired_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002050 last_set_cursor_shape = desired_cursor_shape;
2051 last_set_cursor_blink = desired_cursor_blink;
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002052 term_cursor_color(cursor_color_get(desired_cursor_color));
Bram Moolenaard317b382018-02-08 22:33:31 +01002053 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
2054 /* this will restore the initial cursor style, if possible */
2055 ui_cursor_shape_forced(TRUE);
2056 else
2057 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
2058 }
2059}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002060
Bram Moolenaard317b382018-02-08 22:33:31 +01002061/*
2062 * Set the cursor color and shape, if not last set to these.
2063 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002064 static void
2065may_set_cursor_props(term_T *term)
2066{
2067#ifdef FEAT_GUI
2068 /* For the GUI the cursor properties are obtained with
2069 * term_get_cursor_shape(). */
2070 if (gui.in_use)
2071 return;
2072#endif
2073 if (in_terminal_loop == term)
2074 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002075 cursor_color_copy(&desired_cursor_color, term->tl_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002076 desired_cursor_shape = term->tl_cursor_shape;
2077 desired_cursor_blink = term->tl_cursor_blink;
2078 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002079 }
2080}
2081
Bram Moolenaard317b382018-02-08 22:33:31 +01002082/*
2083 * Reset the desired cursor properties and restore them when needed.
2084 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002085 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01002086prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002087{
2088#ifdef FEAT_GUI
2089 if (gui.in_use)
2090 return;
2091#endif
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002092 cursor_color_copy(&desired_cursor_color, NULL);
Bram Moolenaard317b382018-02-08 22:33:31 +01002093 desired_cursor_shape = -1;
2094 desired_cursor_blink = -1;
2095 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002096}
2097
2098/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002099 * Returns TRUE if the current window contains a terminal and we are sending
2100 * keys to the job.
2101 * If "check_job_status" is TRUE update the job status.
2102 */
2103 static int
2104term_use_loop_check(int check_job_status)
2105{
2106 term_T *term = curbuf->b_term;
2107
2108 return term != NULL
2109 && !term->tl_normal_mode
2110 && term->tl_vterm != NULL
2111 && term_job_running_check(term, check_job_status);
2112}
2113
2114/*
2115 * Returns TRUE if the current window contains a terminal and we are sending
2116 * keys to the job.
2117 */
2118 int
2119term_use_loop(void)
2120{
2121 return term_use_loop_check(FALSE);
2122}
2123
2124/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002125 * Called when entering a window with the mouse. If this is a terminal window
2126 * we may want to change state.
2127 */
2128 void
2129term_win_entered()
2130{
2131 term_T *term = curbuf->b_term;
2132
2133 if (term != NULL)
2134 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002135 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002136 {
2137 reset_VIsual_and_resel();
2138 if (State & INSERT)
2139 stop_insert_mode = TRUE;
2140 }
2141 mouse_was_outside = FALSE;
2142 enter_mouse_col = mouse_col;
2143 enter_mouse_row = mouse_row;
2144 }
2145}
2146
2147/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002148 * Wait for input and send it to the job.
2149 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2150 * when there is no more typahead.
2151 * Return when the start of a CTRL-W command is typed or anything else that
2152 * should be handled as a Normal mode command.
2153 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2154 * the terminal was closed.
2155 */
2156 int
2157terminal_loop(int blocking)
2158{
2159 int c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002160 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002161 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002162#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002163 int tty_fd = curbuf->b_term->tl_job->jv_channel
2164 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002165#endif
Bram Moolenaar73dd1bd2018-05-12 21:16:25 +02002166 int restore_cursor = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002167
2168 /* Remember the terminal we are sending keys to. However, the terminal
2169 * might be closed while waiting for a character, e.g. typing "exit" in a
2170 * shell and ++close was used. Therefore use curbuf->b_term instead of a
2171 * stored reference. */
2172 in_terminal_loop = curbuf->b_term;
2173
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002174 if (*curwin->w_p_twk != NUL)
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002175 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002176 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002177 if (termwinkey == Ctrl_W)
2178 termwinkey = 0;
2179 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002180 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2181 may_set_cursor_props(curbuf->b_term);
2182
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002183 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002184 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002185#ifdef FEAT_GUI
2186 if (!curbuf->b_term->tl_system)
2187#endif
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01002188 // TODO: skip screen update when handling a sequence of keys.
2189 // Repeat redrawing in case a message is received while redrawing.
Bram Moolenaar13568252018-03-16 20:46:58 +01002190 while (must_redraw != 0)
2191 if (update_screen(0) == FAIL)
2192 break;
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002193 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02002194 /* job finished while redrawing */
2195 break;
2196
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002197 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002198 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002199
2200 c = term_vgetc();
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002201 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002202 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002203 /* Job finished while waiting for a character. Push back the
2204 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002205 if (c != K_IGNORE)
2206 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002207 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002208 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002209 if (c == K_IGNORE)
2210 continue;
2211
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002212#ifdef UNIX
2213 /*
2214 * The shell or another program may change the tty settings. Getting
2215 * them for every typed character is a bit of overhead, but it's needed
2216 * for the first character typed, e.g. when Vim starts in a shell.
2217 */
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01002218 if (mch_isatty(tty_fd))
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002219 {
2220 ttyinfo_T info;
2221
2222 /* Get the current backspace character of the pty. */
2223 if (get_tty_info(tty_fd, &info) == OK)
2224 term_backspace_char = info.backspace;
2225 }
2226#endif
2227
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002228#ifdef WIN3264
2229 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2230 * Use CTRL-BREAK to kill the job. */
2231 if (ctrl_break_was_pressed)
2232 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2233#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002234 /* Was either CTRL-W (termwinkey) or CTRL-\ pressed?
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002235 * Not in a system terminal. */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002236 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002237#ifdef FEAT_GUI
2238 && !curbuf->b_term->tl_system
2239#endif
2240 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002241 {
2242 int prev_c = c;
2243
2244#ifdef FEAT_CMDL_INFO
2245 if (add_to_showcmd(c))
2246 out_flush();
2247#endif
2248 c = term_vgetc();
2249#ifdef FEAT_CMDL_INFO
2250 clear_showcmd();
2251#endif
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002252 if (!term_use_loop_check(TRUE)
2253 || in_terminal_loop != curbuf->b_term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002254 /* job finished while waiting for a character */
2255 break;
2256
2257 if (prev_c == Ctrl_BSL)
2258 {
2259 if (c == Ctrl_N)
2260 {
2261 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2262 term_enter_normal_mode();
2263 ret = FAIL;
2264 goto theend;
2265 }
2266 /* Send both keys to the terminal. */
2267 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2268 }
2269 else if (c == Ctrl_C)
2270 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002271 /* "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002272 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2273 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002274 else if (c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002275 {
2276 /* "CTRL-W .": send CTRL-W to the job */
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002277 /* "'termwinkey' .": send 'termwinkey' to the job */
2278 c = termwinkey == 0 ? Ctrl_W : termwinkey;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002279 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002280 else if (c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002281 {
2282 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2283 c = Ctrl_BSL;
2284 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002285 else if (c == 'N')
2286 {
2287 /* CTRL-W N : go to Terminal-Normal mode. */
2288 term_enter_normal_mode();
2289 ret = FAIL;
2290 goto theend;
2291 }
2292 else if (c == '"')
2293 {
2294 term_paste_register(prev_c);
2295 continue;
2296 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002297 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002298 {
2299 stuffcharReadbuff(Ctrl_W);
2300 stuffcharReadbuff(c);
2301 ret = OK;
2302 goto theend;
2303 }
2304 }
2305# ifdef WIN3264
2306 if (!enc_utf8 && has_mbyte && c >= 0x80)
2307 {
2308 WCHAR wc;
2309 char_u mb[3];
2310
2311 mb[0] = (unsigned)c >> 8;
2312 mb[1] = c;
2313 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2314 c = wc;
2315 }
2316# endif
2317 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2318 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002319 if (c == K_MOUSEMOVE)
2320 /* We are sure to come back here, don't reset the cursor color
2321 * and shape to avoid flickering. */
2322 restore_cursor = FALSE;
2323
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002324 ret = OK;
2325 goto theend;
2326 }
2327 }
2328 ret = FAIL;
2329
2330theend:
2331 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002332 if (restore_cursor)
2333 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002334
2335 /* Move a snapshot of the screen contents to the buffer, so that completion
2336 * works in other buffers. */
Bram Moolenaar620020e2018-05-13 19:06:12 +02002337 if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode)
2338 may_move_terminal_to_buffer(curbuf->b_term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002339
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002340 return ret;
2341}
2342
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002343 static void
2344may_toggle_cursor(term_T *term)
2345{
2346 if (in_terminal_loop == term)
2347 {
2348 if (term->tl_cursor_visible)
2349 cursor_on();
2350 else
2351 cursor_off();
2352 }
2353}
2354
2355/*
2356 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002357 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002358 */
2359 static int
2360color2index(VTermColor *color, int fg, int *boldp)
2361{
2362 int red = color->red;
2363 int blue = color->blue;
2364 int green = color->green;
2365
Bram Moolenaar46359e12017-11-29 22:33:38 +01002366 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002367 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002368 /* First 16 colors and default: use the ANSI index, because these
2369 * colors can be redefined. */
2370 if (t_colors >= 16)
2371 return color->ansi_index;
2372 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002373 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002374 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002375 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002376 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2377 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2378 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002379 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002380 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2381 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2382 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2383 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2384 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2385 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2386 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2387 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2388 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2389 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2390 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002391 }
2392 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002393
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002394 if (t_colors >= 256)
2395 {
2396 if (red == blue && red == green)
2397 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002398 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002399 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002400 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2401 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2402 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002403 int i;
2404
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002405 if (red < 5)
2406 return 17; /* 00/00/00 */
2407 if (red > 245) /* ff/ff/ff */
2408 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002409 for (i = 0; i < 23; ++i)
2410 if (red < cutoff[i])
2411 return i + 233;
2412 return 256;
2413 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002414 {
2415 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2416 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002417
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002418 /* 216-color cube */
2419 for (ri = 0; ri < 5; ++ri)
2420 if (red < cutoff[ri])
2421 break;
2422 for (gi = 0; gi < 5; ++gi)
2423 if (green < cutoff[gi])
2424 break;
2425 for (bi = 0; bi < 5; ++bi)
2426 if (blue < cutoff[bi])
2427 break;
2428 return 17 + ri * 36 + gi * 6 + bi;
2429 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002430 }
2431 return 0;
2432}
2433
2434/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002435 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002436 */
2437 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002438vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002439{
2440 int attr = 0;
2441
2442 if (cellattrs.bold)
2443 attr |= HL_BOLD;
2444 if (cellattrs.underline)
2445 attr |= HL_UNDERLINE;
2446 if (cellattrs.italic)
2447 attr |= HL_ITALIC;
2448 if (cellattrs.strike)
2449 attr |= HL_STRIKETHROUGH;
2450 if (cellattrs.reverse)
2451 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002452 return attr;
2453}
2454
2455/*
2456 * Store Vterm attributes in "cell" from highlight flags.
2457 */
2458 static void
2459hl2vtermAttr(int attr, cellattr_T *cell)
2460{
2461 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2462 if (attr & HL_BOLD)
2463 cell->attrs.bold = 1;
2464 if (attr & HL_UNDERLINE)
2465 cell->attrs.underline = 1;
2466 if (attr & HL_ITALIC)
2467 cell->attrs.italic = 1;
2468 if (attr & HL_STRIKETHROUGH)
2469 cell->attrs.strike = 1;
2470 if (attr & HL_INVERSE)
2471 cell->attrs.reverse = 1;
2472}
2473
2474/*
2475 * Convert the attributes of a vterm cell into an attribute index.
2476 */
2477 static int
2478cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2479{
2480 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002481
2482#ifdef FEAT_GUI
2483 if (gui.in_use)
2484 {
2485 guicolor_T fg, bg;
2486
2487 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2488 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2489 return get_gui_attr_idx(attr, fg, bg);
2490 }
2491 else
2492#endif
2493#ifdef FEAT_TERMGUICOLORS
2494 if (p_tgc)
2495 {
2496 guicolor_T fg, bg;
2497
2498 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2499 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2500
2501 return get_tgc_attr_idx(attr, fg, bg);
2502 }
2503 else
2504#endif
2505 {
2506 int bold = MAYBE;
2507 int fg = color2index(&cellfg, TRUE, &bold);
2508 int bg = color2index(&cellbg, FALSE, &bold);
2509
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002510 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002511 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002512 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002513 if (fg == 0 && term_default_cterm_fg >= 0)
2514 fg = term_default_cterm_fg + 1;
2515 if (bg == 0 && term_default_cterm_bg >= 0)
2516 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002517 }
2518
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002519 /* with 8 colors set the bold attribute to get a bright foreground */
2520 if (bold == TRUE)
2521 attr |= HL_BOLD;
2522 return get_cterm_attr_idx(attr, fg, bg);
2523 }
2524 return 0;
2525}
2526
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002527 static void
2528set_dirty_snapshot(term_T *term)
2529{
2530 term->tl_dirty_snapshot = TRUE;
2531#ifdef FEAT_TIMERS
2532 if (!term->tl_normal_mode)
2533 {
2534 /* Update the snapshot after 100 msec of not getting updates. */
2535 profile_setlimit(100L, &term->tl_timer_due);
2536 term->tl_timer_set = TRUE;
2537 }
2538#endif
2539}
2540
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002541 static int
2542handle_damage(VTermRect rect, void *user)
2543{
2544 term_T *term = (term_T *)user;
2545
2546 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2547 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002548 set_dirty_snapshot(term);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002549 redraw_buf_later(term->tl_buffer, SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002550 return 1;
2551}
2552
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002553 static void
2554term_scroll_up(term_T *term, int start_row, int count)
2555{
2556 win_T *wp;
2557 VTermColor fg, bg;
2558 VTermScreenCellAttrs attr;
2559 int clear_attr;
2560
2561 /* Set the color to clear lines with. */
2562 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2563 &fg, &bg);
2564 vim_memset(&attr, 0, sizeof(attr));
2565 clear_attr = cell2attr(attr, fg, bg);
2566
2567 FOR_ALL_WINDOWS(wp)
2568 {
2569 if (wp->w_buffer == term->tl_buffer)
2570 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
2571 }
2572}
2573
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002574 static int
2575handle_moverect(VTermRect dest, VTermRect src, void *user)
2576{
2577 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002578 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002579
2580 /* Scrolling up is done much more efficiently by deleting lines instead of
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002581 * redrawing the text. But avoid doing this multiple times, postpone until
2582 * the redraw happens. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002583 if (dest.start_col == src.start_col
2584 && dest.end_col == src.end_col
2585 && dest.start_row < src.start_row)
2586 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002587 if (dest.start_row == 0)
2588 term->tl_postponed_scroll += count;
2589 else
2590 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002591 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002592
2593 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2594 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002595 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002596
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002597 /* Note sure if the scrolling will work correctly, let's do a complete
2598 * redraw later. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002599 redraw_buf_later(term->tl_buffer, NOT_VALID);
2600 return 1;
2601}
2602
2603 static int
2604handle_movecursor(
2605 VTermPos pos,
2606 VTermPos oldpos UNUSED,
2607 int visible,
2608 void *user)
2609{
2610 term_T *term = (term_T *)user;
2611 win_T *wp;
2612
2613 term->tl_cursor_pos = pos;
2614 term->tl_cursor_visible = visible;
2615
2616 FOR_ALL_WINDOWS(wp)
2617 {
2618 if (wp->w_buffer == term->tl_buffer)
2619 position_cursor(wp, &pos);
2620 }
2621 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2622 {
2623 may_toggle_cursor(term);
2624 update_cursor(term, term->tl_cursor_visible);
2625 }
2626
2627 return 1;
2628}
2629
2630 static int
2631handle_settermprop(
2632 VTermProp prop,
2633 VTermValue *value,
2634 void *user)
2635{
2636 term_T *term = (term_T *)user;
2637
2638 switch (prop)
2639 {
2640 case VTERM_PROP_TITLE:
2641 vim_free(term->tl_title);
2642 /* a blank title isn't useful, make it empty, so that "running" is
2643 * displayed */
2644 if (*skipwhite((char_u *)value->string) == NUL)
2645 term->tl_title = NULL;
2646#ifdef WIN3264
2647 else if (!enc_utf8 && enc_codepage > 0)
2648 {
2649 WCHAR *ret = NULL;
2650 int length = 0;
2651
2652 MultiByteToWideChar_alloc(CP_UTF8, 0,
2653 (char*)value->string, (int)STRLEN(value->string),
2654 &ret, &length);
2655 if (ret != NULL)
2656 {
2657 WideCharToMultiByte_alloc(enc_codepage, 0,
2658 ret, length, (char**)&term->tl_title,
2659 &length, 0, 0);
2660 vim_free(ret);
2661 }
2662 }
2663#endif
2664 else
2665 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002666 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002667 if (term == curbuf->b_term)
2668 maketitle();
2669 break;
2670
2671 case VTERM_PROP_CURSORVISIBLE:
2672 term->tl_cursor_visible = value->boolean;
2673 may_toggle_cursor(term);
2674 out_flush();
2675 break;
2676
2677 case VTERM_PROP_CURSORBLINK:
2678 term->tl_cursor_blink = value->boolean;
2679 may_set_cursor_props(term);
2680 break;
2681
2682 case VTERM_PROP_CURSORSHAPE:
2683 term->tl_cursor_shape = value->number;
2684 may_set_cursor_props(term);
2685 break;
2686
2687 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002688 cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002689 may_set_cursor_props(term);
2690 break;
2691
2692 case VTERM_PROP_ALTSCREEN:
2693 /* TODO: do anything else? */
2694 term->tl_using_altscreen = value->boolean;
2695 break;
2696
2697 default:
2698 break;
2699 }
2700 /* Always return 1, otherwise vterm doesn't store the value internally. */
2701 return 1;
2702}
2703
2704/*
2705 * The job running in the terminal resized the terminal.
2706 */
2707 static int
2708handle_resize(int rows, int cols, void *user)
2709{
2710 term_T *term = (term_T *)user;
2711 win_T *wp;
2712
2713 term->tl_rows = rows;
2714 term->tl_cols = cols;
2715 if (term->tl_vterm_size_changed)
2716 /* Size was set by vterm_set_size(), don't set the window size. */
2717 term->tl_vterm_size_changed = FALSE;
2718 else
2719 {
2720 FOR_ALL_WINDOWS(wp)
2721 {
2722 if (wp->w_buffer == term->tl_buffer)
2723 {
2724 win_setheight_win(rows, wp);
2725 win_setwidth_win(cols, wp);
2726 }
2727 }
2728 redraw_buf_later(term->tl_buffer, NOT_VALID);
2729 }
2730 return 1;
2731}
2732
2733/*
2734 * Handle a line that is pushed off the top of the screen.
2735 */
2736 static int
2737handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2738{
2739 term_T *term = (term_T *)user;
2740
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002741 /* First remove the lines that were appended before, the pushed line goes
2742 * above it. */
2743 cleanup_scrollback(term);
2744
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002745 /* If the number of lines that are stored goes over 'termscrollback' then
2746 * delete the first 10%. */
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002747 if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002748 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002749 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002750 int i;
2751
2752 curbuf = term->tl_buffer;
2753 for (i = 0; i < todo; ++i)
2754 {
2755 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2756 ml_delete(1, FALSE);
2757 }
2758 curbuf = curwin->w_buffer;
2759
2760 term->tl_scrollback.ga_len -= todo;
2761 mch_memmove(term->tl_scrollback.ga_data,
2762 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2763 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
Bram Moolenaar4d6cd292018-05-15 23:53:26 +02002764 term->tl_scrollback_scrolled -= todo;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002765 }
2766
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002767 if (ga_grow(&term->tl_scrollback, 1) == OK)
2768 {
2769 cellattr_T *p = NULL;
2770 int len = 0;
2771 int i;
2772 int c;
2773 int col;
2774 sb_line_T *line;
2775 garray_T ga;
2776 cellattr_T fill_attr = term->tl_default_color;
2777
2778 /* do not store empty cells at the end */
2779 for (i = 0; i < cols; ++i)
2780 if (cells[i].chars[0] != 0)
2781 len = i + 1;
2782 else
2783 cell2cellattr(&cells[i], &fill_attr);
2784
2785 ga_init2(&ga, 1, 100);
2786 if (len > 0)
2787 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2788 if (p != NULL)
2789 {
2790 for (col = 0; col < len; col += cells[col].width)
2791 {
2792 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2793 {
2794 ga.ga_len = 0;
2795 break;
2796 }
2797 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2798 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2799 (char_u *)ga.ga_data + ga.ga_len);
2800 cell2cellattr(&cells[col], &p[col]);
2801 }
2802 }
2803 if (ga_grow(&ga, 1) == FAIL)
2804 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2805 else
2806 {
2807 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2808 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2809 }
2810 ga_clear(&ga);
2811
2812 line = (sb_line_T *)term->tl_scrollback.ga_data
2813 + term->tl_scrollback.ga_len;
2814 line->sb_cols = len;
2815 line->sb_cells = p;
2816 line->sb_fill_attr = fill_attr;
2817 ++term->tl_scrollback.ga_len;
2818 ++term->tl_scrollback_scrolled;
2819 }
2820 return 0; /* ignored */
2821}
2822
2823static VTermScreenCallbacks screen_callbacks = {
2824 handle_damage, /* damage */
2825 handle_moverect, /* moverect */
2826 handle_movecursor, /* movecursor */
2827 handle_settermprop, /* settermprop */
2828 NULL, /* bell */
2829 handle_resize, /* resize */
2830 handle_pushline, /* sb_pushline */
2831 NULL /* sb_popline */
2832};
2833
2834/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002835 * Do the work after the channel of a terminal was closed.
2836 * Must be called only when updating_screen is FALSE.
2837 * Returns TRUE when a buffer was closed (list of terminals may have changed).
2838 */
2839 static int
2840term_after_channel_closed(term_T *term)
2841{
2842 /* Unless in Terminal-Normal mode: clear the vterm. */
2843 if (!term->tl_normal_mode)
2844 {
2845 int fnum = term->tl_buffer->b_fnum;
2846
2847 cleanup_vterm(term);
2848
2849 if (term->tl_finish == TL_FINISH_CLOSE)
2850 {
2851 aco_save_T aco;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02002852 int do_set_w_closing = term->tl_buffer->b_nwindows == 0;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002853
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02002854 // ++close or term_finish == "close"
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002855 ch_log(NULL, "terminal job finished, closing window");
2856 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02002857 // Avoid closing the window if we temporarily use it.
2858 if (do_set_w_closing)
2859 curwin->w_closing = TRUE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002860 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02002861 if (do_set_w_closing)
2862 curwin->w_closing = FALSE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002863 aucmd_restbuf(&aco);
2864 return TRUE;
2865 }
2866 if (term->tl_finish == TL_FINISH_OPEN
2867 && term->tl_buffer->b_nwindows == 0)
2868 {
2869 char buf[50];
2870
2871 /* TODO: use term_opencmd */
2872 ch_log(NULL, "terminal job finished, opening window");
2873 vim_snprintf(buf, sizeof(buf),
2874 term->tl_opencmd == NULL
2875 ? "botright sbuf %d"
2876 : (char *)term->tl_opencmd, fnum);
2877 do_cmdline_cmd((char_u *)buf);
2878 }
2879 else
2880 ch_log(NULL, "terminal job finished");
2881 }
2882
2883 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2884 return FALSE;
2885}
2886
2887/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002888 * Called when a channel has been closed.
2889 * If this was a channel for a terminal window then finish it up.
2890 */
2891 void
2892term_channel_closed(channel_T *ch)
2893{
2894 term_T *term;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002895 term_T *next_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002896 int did_one = FALSE;
2897
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002898 for (term = first_term; term != NULL; term = next_term)
2899 {
2900 next_term = term->tl_next;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002901 if (term->tl_job == ch->ch_job)
2902 {
2903 term->tl_channel_closed = TRUE;
2904 did_one = TRUE;
2905
Bram Moolenaard23a8232018-02-10 18:45:26 +01002906 VIM_CLEAR(term->tl_title);
2907 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar402c8392018-05-06 22:01:42 +02002908#ifdef WIN3264
2909 if (term->tl_out_fd != NULL)
2910 {
2911 fclose(term->tl_out_fd);
2912 term->tl_out_fd = NULL;
2913 }
2914#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002915
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002916 if (updating_screen)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002917 {
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002918 /* Cannot open or close windows now. Can happen when
2919 * 'lazyredraw' is set. */
2920 term->tl_channel_recently_closed = TRUE;
2921 continue;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002922 }
2923
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002924 if (term_after_channel_closed(term))
2925 next_term = first_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002926 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002927 }
2928
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002929 if (did_one)
2930 {
2931 redraw_statuslines();
2932
2933 /* Need to break out of vgetc(). */
2934 ins_char_typebuf(K_IGNORE);
2935 typebuf_was_filled = TRUE;
2936
2937 term = curbuf->b_term;
2938 if (term != NULL)
2939 {
2940 if (term->tl_job == ch->ch_job)
2941 maketitle();
2942 update_cursor(term, term->tl_cursor_visible);
2943 }
2944 }
2945}
2946
2947/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002948 * To be called after resetting updating_screen: handle any terminal where the
2949 * channel was closed.
2950 */
2951 void
2952term_check_channel_closed_recently()
2953{
2954 term_T *term;
2955 term_T *next_term;
2956
2957 for (term = first_term; term != NULL; term = next_term)
2958 {
2959 next_term = term->tl_next;
2960 if (term->tl_channel_recently_closed)
2961 {
2962 term->tl_channel_recently_closed = FALSE;
2963 if (term_after_channel_closed(term))
2964 // start over, the list may have changed
2965 next_term = first_term;
2966 }
2967 }
2968}
2969
2970/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002971 * Fill one screen line from a line of the terminal.
2972 * Advances "pos" to past the last column.
2973 */
2974 static void
2975term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2976{
2977 int off = screen_get_current_line_off();
2978
2979 for (pos->col = 0; pos->col < max_col; )
2980 {
2981 VTermScreenCell cell;
2982 int c;
2983
2984 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2985 vim_memset(&cell, 0, sizeof(cell));
2986
2987 c = cell.chars[0];
2988 if (c == NUL)
2989 {
2990 ScreenLines[off] = ' ';
2991 if (enc_utf8)
2992 ScreenLinesUC[off] = NUL;
2993 }
2994 else
2995 {
2996 if (enc_utf8)
2997 {
2998 int i;
2999
3000 /* composing chars */
3001 for (i = 0; i < Screen_mco
3002 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
3003 {
3004 ScreenLinesC[i][off] = cell.chars[i + 1];
3005 if (cell.chars[i + 1] == 0)
3006 break;
3007 }
3008 if (c >= 0x80 || (Screen_mco > 0
3009 && ScreenLinesC[0][off] != 0))
3010 {
3011 ScreenLines[off] = ' ';
3012 ScreenLinesUC[off] = c;
3013 }
3014 else
3015 {
3016 ScreenLines[off] = c;
3017 ScreenLinesUC[off] = NUL;
3018 }
3019 }
3020#ifdef WIN3264
3021 else if (has_mbyte && c >= 0x80)
3022 {
3023 char_u mb[MB_MAXBYTES+1];
3024 WCHAR wc = c;
3025
3026 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
3027 (char*)mb, 2, 0, 0) > 1)
3028 {
3029 ScreenLines[off] = mb[0];
3030 ScreenLines[off + 1] = mb[1];
3031 cell.width = mb_ptr2cells(mb);
3032 }
3033 else
3034 ScreenLines[off] = c;
3035 }
3036#endif
3037 else
3038 ScreenLines[off] = c;
3039 }
3040 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
3041
3042 ++pos->col;
3043 ++off;
3044 if (cell.width == 2)
3045 {
3046 if (enc_utf8)
3047 ScreenLinesUC[off] = NUL;
3048
3049 /* don't set the second byte to NUL for a DBCS encoding, it
3050 * has been set above */
3051 if (enc_utf8 || !has_mbyte)
3052 ScreenLines[off] = NUL;
3053
3054 ++pos->col;
3055 ++off;
3056 }
3057 }
3058}
3059
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003060#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01003061 static void
3062update_system_term(term_T *term)
3063{
3064 VTermPos pos;
3065 VTermScreen *screen;
3066
3067 if (term->tl_vterm == NULL)
3068 return;
3069 screen = vterm_obtain_screen(term->tl_vterm);
3070
3071 /* Scroll up to make more room for terminal lines if needed. */
3072 while (term->tl_toprow > 0
3073 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3074 {
3075 int save_p_more = p_more;
3076
3077 p_more = FALSE;
3078 msg_row = Rows - 1;
Bram Moolenaar113e1072019-01-20 15:30:40 +01003079 msg_puts("\n");
Bram Moolenaar13568252018-03-16 20:46:58 +01003080 p_more = save_p_more;
3081 --term->tl_toprow;
3082 }
3083
3084 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3085 && pos.row < Rows; ++pos.row)
3086 {
3087 if (pos.row < term->tl_rows)
3088 {
3089 int max_col = MIN(Columns, term->tl_cols);
3090
3091 term_line2screenline(screen, &pos, max_col);
3092 }
3093 else
3094 pos.col = 0;
3095
3096 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
3097 }
3098
3099 term->tl_dirty_row_start = MAX_ROW;
3100 term->tl_dirty_row_end = 0;
3101 update_cursor(term, TRUE);
3102}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003103#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01003104
3105/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003106 * Return TRUE if window "wp" is to be redrawn with term_update_window().
3107 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003108 * Terminal-Normal mode.
3109 */
3110 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003111term_do_update_window(win_T *wp)
3112{
3113 term_T *term = wp->w_buffer->b_term;
3114
3115 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3116}
3117
3118/*
3119 * Called to update a window that contains an active terminal.
3120 */
3121 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003122term_update_window(win_T *wp)
3123{
3124 term_T *term = wp->w_buffer->b_term;
3125 VTerm *vterm;
3126 VTermScreen *screen;
3127 VTermState *state;
3128 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003129 int rows, cols;
3130 int newrows, newcols;
3131 int minsize;
3132 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003133
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003134 vterm = term->tl_vterm;
3135 screen = vterm_obtain_screen(vterm);
3136 state = vterm_obtain_state(vterm);
3137
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003138 /* We use NOT_VALID on a resize or scroll, redraw everything then. With
3139 * SOME_VALID only redraw what was marked dirty. */
3140 if (wp->w_redr_type > SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003141 {
3142 term->tl_dirty_row_start = 0;
3143 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003144
3145 if (term->tl_postponed_scroll > 0
3146 && term->tl_postponed_scroll < term->tl_rows / 3)
3147 /* Scrolling is usually faster than redrawing, when there are only
3148 * a few lines to scroll. */
3149 term_scroll_up(term, 0, term->tl_postponed_scroll);
3150 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003151 }
3152
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003153 /*
3154 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003155 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003156 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003157 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003158
Bram Moolenaar498c2562018-04-15 23:45:15 +02003159 newrows = 99999;
3160 newcols = 99999;
3161 FOR_ALL_WINDOWS(twp)
3162 {
3163 /* When more than one window shows the same terminal, use the
3164 * smallest size. */
3165 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003166 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02003167 newrows = MIN(newrows, twp->w_height);
3168 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003169 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02003170 }
3171 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3172 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3173
3174 if (term->tl_rows != newrows || term->tl_cols != newcols)
3175 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003176 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003177 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003178 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003179 newrows);
3180 term_report_winsize(term, newrows, newcols);
Bram Moolenaar875cf872018-07-08 20:49:07 +02003181
3182 // Updating the terminal size will cause the snapshot to be cleared.
3183 // When not in terminal_loop() we need to restore it.
3184 if (term != in_terminal_loop)
3185 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003186 }
3187
3188 /* The cursor may have been moved when resizing. */
3189 vterm_state_get_cursorpos(state, &pos);
3190 position_cursor(wp, &pos);
3191
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003192 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3193 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003194 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003195 if (pos.row < term->tl_rows)
3196 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003197 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003198
Bram Moolenaar13568252018-03-16 20:46:58 +01003199 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003200 }
3201 else
3202 pos.col = 0;
3203
Bram Moolenaarf118d482018-03-13 13:14:00 +01003204 screen_line(wp->w_winrow + pos.row
3205#ifdef FEAT_MENU
3206 + winbar_height(wp)
3207#endif
3208 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003209 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003210 term->tl_dirty_row_start = MAX_ROW;
3211 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003212}
3213
3214/*
3215 * Return TRUE if "wp" is a terminal window where the job has finished.
3216 */
3217 int
3218term_is_finished(buf_T *buf)
3219{
3220 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3221}
3222
3223/*
3224 * Return TRUE if "wp" is a terminal window where the job has finished or we
3225 * are in Terminal-Normal mode, thus we show the buffer contents.
3226 */
3227 int
3228term_show_buffer(buf_T *buf)
3229{
3230 term_T *term = buf->b_term;
3231
3232 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3233}
3234
3235/*
3236 * The current buffer is going to be changed. If there is terminal
3237 * highlighting remove it now.
3238 */
3239 void
3240term_change_in_curbuf(void)
3241{
3242 term_T *term = curbuf->b_term;
3243
3244 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3245 {
3246 free_scrollback(term);
3247 redraw_buf_later(term->tl_buffer, NOT_VALID);
3248
3249 /* The buffer is now like a normal buffer, it cannot be easily
3250 * abandoned when changed. */
3251 set_string_option_direct((char_u *)"buftype", -1,
3252 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3253 }
3254}
3255
3256/*
3257 * Get the screen attribute for a position in the buffer.
3258 * Use a negative "col" to get the filler background color.
3259 */
3260 int
3261term_get_attr(buf_T *buf, linenr_T lnum, int col)
3262{
3263 term_T *term = buf->b_term;
3264 sb_line_T *line;
3265 cellattr_T *cellattr;
3266
3267 if (lnum > term->tl_scrollback.ga_len)
3268 cellattr = &term->tl_default_color;
3269 else
3270 {
3271 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3272 if (col < 0 || col >= line->sb_cols)
3273 cellattr = &line->sb_fill_attr;
3274 else
3275 cellattr = line->sb_cells + col;
3276 }
3277 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3278}
3279
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003280/*
3281 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003282 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003283 */
3284 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003285cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003286{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003287 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003288}
3289
3290/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003291 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003292 */
3293 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003294init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003295{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003296 VTermColor *fg, *bg;
3297 int fgval, bgval;
3298 int id;
3299
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003300 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3301 term->tl_default_color.width = 1;
3302 fg = &term->tl_default_color.fg;
3303 bg = &term->tl_default_color.bg;
3304
3305 /* Vterm uses a default black background. Set it to white when
3306 * 'background' is "light". */
3307 if (*p_bg == 'l')
3308 {
3309 fgval = 0;
3310 bgval = 255;
3311 }
3312 else
3313 {
3314 fgval = 255;
3315 bgval = 0;
3316 }
3317 fg->red = fg->green = fg->blue = fgval;
3318 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003319 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003320
3321 /* The "Terminal" highlight group overrules the defaults. */
3322 id = syn_name2id((char_u *)"Terminal");
3323
Bram Moolenaar46359e12017-11-29 22:33:38 +01003324 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003325#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3326 if (0
3327# ifdef FEAT_GUI
3328 || gui.in_use
3329# endif
3330# ifdef FEAT_TERMGUICOLORS
3331 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003332# ifdef FEAT_VTP
3333 /* Finally get INVALCOLOR on this execution path */
3334 || (!p_tgc && t_colors >= 256)
3335# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003336# endif
3337 )
3338 {
3339 guicolor_T fg_rgb = INVALCOLOR;
3340 guicolor_T bg_rgb = INVALCOLOR;
3341
3342 if (id != 0)
3343 syn_id2colors(id, &fg_rgb, &bg_rgb);
3344
3345# ifdef FEAT_GUI
3346 if (gui.in_use)
3347 {
3348 if (fg_rgb == INVALCOLOR)
3349 fg_rgb = gui.norm_pixel;
3350 if (bg_rgb == INVALCOLOR)
3351 bg_rgb = gui.back_pixel;
3352 }
3353# ifdef FEAT_TERMGUICOLORS
3354 else
3355# endif
3356# endif
3357# ifdef FEAT_TERMGUICOLORS
3358 {
3359 if (fg_rgb == INVALCOLOR)
3360 fg_rgb = cterm_normal_fg_gui_color;
3361 if (bg_rgb == INVALCOLOR)
3362 bg_rgb = cterm_normal_bg_gui_color;
3363 }
3364# endif
3365 if (fg_rgb != INVALCOLOR)
3366 {
3367 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3368
3369 fg->red = (unsigned)(rgb >> 16);
3370 fg->green = (unsigned)(rgb >> 8) & 255;
3371 fg->blue = (unsigned)rgb & 255;
3372 }
3373 if (bg_rgb != INVALCOLOR)
3374 {
3375 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3376
3377 bg->red = (unsigned)(rgb >> 16);
3378 bg->green = (unsigned)(rgb >> 8) & 255;
3379 bg->blue = (unsigned)rgb & 255;
3380 }
3381 }
3382 else
3383#endif
3384 if (id != 0 && t_colors >= 16)
3385 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003386 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003387 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003388 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003389 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003390 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003391 else
3392 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003393#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003394 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003395#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003396
3397 /* In an MS-Windows console we know the normal colors. */
3398 if (cterm_normal_fg_color > 0)
3399 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003400 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003401# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003402 tmp = fg->red;
3403 fg->red = fg->blue;
3404 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003405# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003406 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003407# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003408 else
3409 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003410# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003411
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003412 if (cterm_normal_bg_color > 0)
3413 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003414 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003415# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003416 tmp = bg->red;
3417 bg->red = bg->blue;
3418 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003419# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003420 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003421# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003422 else
3423 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003424# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003425 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003426}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003427
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003428#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3429/*
3430 * Set the 16 ANSI colors from array of RGB values
3431 */
3432 static void
3433set_vterm_palette(VTerm *vterm, long_u *rgb)
3434{
3435 int index = 0;
3436 VTermState *state = vterm_obtain_state(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003437
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003438 for (; index < 16; index++)
3439 {
3440 VTermColor color;
3441 color.red = (unsigned)(rgb[index] >> 16);
3442 color.green = (unsigned)(rgb[index] >> 8) & 255;
3443 color.blue = (unsigned)rgb[index] & 255;
3444 vterm_state_set_palette_color(state, index, &color);
3445 }
3446}
3447
3448/*
3449 * Set the ANSI color palette from a list of colors
3450 */
3451 static int
3452set_ansi_colors_list(VTerm *vterm, list_T *list)
3453{
3454 int n = 0;
3455 long_u rgb[16];
3456 listitem_T *li = list->lv_first;
3457
3458 for (; li != NULL && n < 16; li = li->li_next, n++)
3459 {
3460 char_u *color_name;
3461 guicolor_T guicolor;
3462
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003463 color_name = tv_get_string_chk(&li->li_tv);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003464 if (color_name == NULL)
3465 return FAIL;
3466
3467 guicolor = GUI_GET_COLOR(color_name);
3468 if (guicolor == INVALCOLOR)
3469 return FAIL;
3470
3471 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3472 }
3473
3474 if (n != 16 || li != NULL)
3475 return FAIL;
3476
3477 set_vterm_palette(vterm, rgb);
3478
3479 return OK;
3480}
3481
3482/*
3483 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3484 */
3485 static void
3486init_vterm_ansi_colors(VTerm *vterm)
3487{
3488 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3489
3490 if (var != NULL
3491 && (var->di_tv.v_type != VAR_LIST
3492 || var->di_tv.vval.v_list == NULL
3493 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003494 semsg(_(e_invarg2), "g:terminal_ansi_colors");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003495}
3496#endif
3497
Bram Moolenaar52acb112018-03-18 19:20:22 +01003498/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003499 * Handles a "drop" command from the job in the terminal.
3500 * "item" is the file name, "item->li_next" may have options.
3501 */
3502 static void
3503handle_drop_command(listitem_T *item)
3504{
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003505 char_u *fname = tv_get_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003506 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003507 int bufnr;
3508 win_T *wp;
3509 tabpage_T *tp;
3510 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003511 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003512
3513 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3514 FOR_ALL_TAB_WINDOWS(tp, wp)
3515 {
3516 if (wp->w_buffer->b_fnum == bufnr)
3517 {
3518 /* buffer is in a window already, go there */
3519 goto_tabpage_win(tp, wp);
3520 return;
3521 }
3522 }
3523
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003524 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003525
3526 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3527 && opt_item->li_tv.vval.v_dict != NULL)
3528 {
3529 dict_T *dict = opt_item->li_tv.vval.v_dict;
3530 char_u *p;
3531
Bram Moolenaar8f667172018-12-14 15:38:31 +01003532 p = dict_get_string(dict, (char_u *)"ff", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003533 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003534 p = dict_get_string(dict, (char_u *)"fileformat", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003535 if (p != NULL)
3536 {
3537 if (check_ff_value(p) == FAIL)
3538 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3539 else
3540 ea.force_ff = *p;
3541 }
Bram Moolenaar8f667172018-12-14 15:38:31 +01003542 p = dict_get_string(dict, (char_u *)"enc", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003543 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003544 p = dict_get_string(dict, (char_u *)"encoding", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003545 if (p != NULL)
3546 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003547 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003548 if (ea.cmd != NULL)
3549 {
3550 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3551 ea.force_enc = 11;
3552 tofree = ea.cmd;
3553 }
3554 }
3555
Bram Moolenaar8f667172018-12-14 15:38:31 +01003556 p = dict_get_string(dict, (char_u *)"bad", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003557 if (p != NULL)
3558 get_bad_opt(p, &ea);
3559
3560 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3561 ea.force_bin = FORCE_BIN;
3562 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3563 ea.force_bin = FORCE_BIN;
3564 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3565 ea.force_bin = FORCE_NOBIN;
3566 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3567 ea.force_bin = FORCE_NOBIN;
3568 }
3569
3570 /* open in new window, like ":split fname" */
3571 if (ea.cmd == NULL)
3572 ea.cmd = (char_u *)"split";
3573 ea.arg = fname;
3574 ea.cmdidx = CMD_split;
3575 ex_splitview(&ea);
3576
3577 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003578}
3579
3580/*
3581 * Handles a function call from the job running in a terminal.
3582 * "item" is the function name, "item->li_next" has the arguments.
3583 */
3584 static void
3585handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3586{
3587 char_u *func;
3588 typval_T argvars[2];
3589 typval_T rettv;
3590 int doesrange;
3591
3592 if (item->li_next == NULL)
3593 {
3594 ch_log(channel, "Missing function arguments for call");
3595 return;
3596 }
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003597 func = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003598
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003599 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003600 {
3601 ch_log(channel, "Invalid function name: %s", func);
3602 return;
3603 }
3604
3605 argvars[0].v_type = VAR_NUMBER;
3606 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3607 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003608 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003609 2, argvars, /* argv_func */ NULL,
3610 /* firstline */ 1, /* lastline */ 1,
3611 &doesrange, /* evaluate */ TRUE,
3612 /* partial */ NULL, /* selfdict */ NULL) == OK)
3613 {
3614 clear_tv(&rettv);
3615 ch_log(channel, "Function %s called", func);
3616 }
3617 else
3618 ch_log(channel, "Calling function %s failed", func);
3619}
3620
3621/*
3622 * Called by libvterm when it cannot recognize an OSC sequence.
3623 * We recognize a terminal API command.
3624 */
3625 static int
3626parse_osc(const char *command, size_t cmdlen, void *user)
3627{
3628 term_T *term = (term_T *)user;
3629 js_read_T reader;
3630 typval_T tv;
3631 channel_T *channel = term->tl_job == NULL ? NULL
3632 : term->tl_job->jv_channel;
3633
3634 /* We recognize only OSC 5 1 ; {command} */
3635 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3636 return 0; /* not handled */
3637
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003638 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003639 if (reader.js_buf == NULL)
3640 return 1;
3641 reader.js_fill = NULL;
3642 reader.js_used = 0;
3643 if (json_decode(&reader, &tv, 0) == OK
3644 && tv.v_type == VAR_LIST
3645 && tv.vval.v_list != NULL)
3646 {
3647 listitem_T *item = tv.vval.v_list->lv_first;
3648
3649 if (item == NULL)
3650 ch_log(channel, "Missing command");
3651 else
3652 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003653 char_u *cmd = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003654
Bram Moolenaara997b452018-04-17 23:24:06 +02003655 /* Make sure an invoked command doesn't delete the buffer (and the
3656 * terminal) under our fingers. */
3657 ++term->tl_buffer->b_locked;
3658
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003659 item = item->li_next;
3660 if (item == NULL)
3661 ch_log(channel, "Missing argument for %s", cmd);
3662 else if (STRCMP(cmd, "drop") == 0)
3663 handle_drop_command(item);
3664 else if (STRCMP(cmd, "call") == 0)
3665 handle_call_command(term, channel, item);
3666 else
3667 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003668 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003669 }
3670 }
3671 else
3672 ch_log(channel, "Invalid JSON received");
3673
3674 vim_free(reader.js_buf);
3675 clear_tv(&tv);
3676 return 1;
3677}
3678
3679static VTermParserCallbacks parser_fallbacks = {
3680 NULL, /* text */
3681 NULL, /* control */
3682 NULL, /* escape */
3683 NULL, /* csi */
3684 parse_osc, /* osc */
3685 NULL, /* dcs */
3686 NULL /* resize */
3687};
3688
3689/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003690 * Use Vim's allocation functions for vterm so profiling works.
3691 */
3692 static void *
3693vterm_malloc(size_t size, void *data UNUSED)
3694{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003695 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003696}
3697
3698 static void
3699vterm_memfree(void *ptr, void *data UNUSED)
3700{
3701 vim_free(ptr);
3702}
3703
3704static VTermAllocatorFunctions vterm_allocator = {
3705 &vterm_malloc,
3706 &vterm_memfree
3707};
3708
3709/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003710 * Create a new vterm and initialize it.
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003711 * Return FAIL when out of memory.
Bram Moolenaar52acb112018-03-18 19:20:22 +01003712 */
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003713 static int
Bram Moolenaar52acb112018-03-18 19:20:22 +01003714create_vterm(term_T *term, int rows, int cols)
3715{
3716 VTerm *vterm;
3717 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003718 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003719 VTermValue value;
3720
Bram Moolenaar756ef112018-04-10 12:04:27 +02003721 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003722 term->tl_vterm = vterm;
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003723 if (vterm == NULL)
3724 return FAIL;
3725
3726 // Allocate screen and state here, so we can bail out if that fails.
3727 state = vterm_obtain_state(vterm);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003728 screen = vterm_obtain_screen(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003729 if (state == NULL || screen == NULL)
3730 {
3731 vterm_free(vterm);
3732 return FAIL;
3733 }
3734
Bram Moolenaar52acb112018-03-18 19:20:22 +01003735 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3736 /* TODO: depends on 'encoding'. */
3737 vterm_set_utf8(vterm, 1);
3738
3739 init_default_colors(term);
3740
3741 vterm_state_set_default_colors(
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003742 state,
Bram Moolenaar52acb112018-03-18 19:20:22 +01003743 &term->tl_default_color.fg,
3744 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003745
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003746 if (t_colors >= 16)
3747 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3748
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003749 /* Required to initialize most things. */
3750 vterm_screen_reset(screen, 1 /* hard */);
3751
3752 /* Allow using alternate screen. */
3753 vterm_screen_enable_altscreen(screen, 1);
3754
3755 /* For unix do not use a blinking cursor. In an xterm this causes the
3756 * cursor to blink if it's blinking in the xterm.
3757 * For Windows we respect the system wide setting. */
3758#ifdef WIN3264
3759 if (GetCaretBlinkTime() == INFINITE)
3760 value.boolean = 0;
3761 else
3762 value.boolean = 1;
3763#else
3764 value.boolean = 0;
3765#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003766 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3767 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003768
3769 return OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003770}
3771
3772/*
3773 * Return the text to show for the buffer name and status.
3774 */
3775 char_u *
3776term_get_status_text(term_T *term)
3777{
3778 if (term->tl_status_text == NULL)
3779 {
3780 char_u *txt;
3781 size_t len;
3782
3783 if (term->tl_normal_mode)
3784 {
3785 if (term_job_running(term))
3786 txt = (char_u *)_("Terminal");
3787 else
3788 txt = (char_u *)_("Terminal-finished");
3789 }
3790 else if (term->tl_title != NULL)
3791 txt = term->tl_title;
3792 else if (term_none_open(term))
3793 txt = (char_u *)_("active");
3794 else if (term_job_running(term))
3795 txt = (char_u *)_("running");
3796 else
3797 txt = (char_u *)_("finished");
3798 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3799 term->tl_status_text = alloc((int)len);
3800 if (term->tl_status_text != NULL)
3801 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3802 term->tl_buffer->b_fname, txt);
3803 }
3804 return term->tl_status_text;
3805}
3806
3807/*
3808 * Mark references in jobs of terminals.
3809 */
3810 int
3811set_ref_in_term(int copyID)
3812{
3813 int abort = FALSE;
3814 term_T *term;
3815 typval_T tv;
3816
3817 for (term = first_term; term != NULL; term = term->tl_next)
3818 if (term->tl_job != NULL)
3819 {
3820 tv.v_type = VAR_JOB;
3821 tv.vval.v_job = term->tl_job;
3822 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3823 }
3824 return abort;
3825}
3826
3827/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003828 * Cache "Terminal" highlight group colors.
3829 */
3830 void
3831set_terminal_default_colors(int cterm_fg, int cterm_bg)
3832{
3833 term_default_cterm_fg = cterm_fg - 1;
3834 term_default_cterm_bg = cterm_bg - 1;
3835}
3836
3837/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003838 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003839 * Returns NULL when the buffer is not for a terminal window and logs a message
3840 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003841 */
3842 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003843term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003844{
3845 buf_T *buf;
3846
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003847 (void)tv_get_number(&argvars[0]); /* issue errmsg if type error */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003848 ++emsg_off;
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +01003849 buf = tv_get_buf(&argvars[0], FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003850 --emsg_off;
3851 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003852 {
3853 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003854 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003855 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003856 return buf;
3857}
3858
Bram Moolenaard96ff162018-02-18 22:13:29 +01003859 static int
3860same_color(VTermColor *a, VTermColor *b)
3861{
3862 return a->red == b->red
3863 && a->green == b->green
3864 && a->blue == b->blue
3865 && a->ansi_index == b->ansi_index;
3866}
3867
3868 static void
3869dump_term_color(FILE *fd, VTermColor *color)
3870{
3871 fprintf(fd, "%02x%02x%02x%d",
3872 (int)color->red, (int)color->green, (int)color->blue,
3873 (int)color->ansi_index);
3874}
3875
3876/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003877 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003878 *
3879 * Each screen cell in full is:
3880 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3881 * {characters} is a space for an empty cell
3882 * For a double-width character "+" is changed to "*" and the next cell is
3883 * skipped.
3884 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3885 * when "&" use the same as the previous cell.
3886 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3887 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3888 * {color-idx} is a number from 0 to 255
3889 *
3890 * Screen cell with same width, attributes and color as the previous one:
3891 * |{characters}
3892 *
3893 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3894 *
3895 * Repeating the previous screen cell:
3896 * @{count}
3897 */
3898 void
3899f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3900{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003901 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003902 term_T *term;
3903 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003904 int max_height = 0;
3905 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003906 stat_T st;
3907 FILE *fd;
3908 VTermPos pos;
3909 VTermScreen *screen;
3910 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003911 VTermState *state;
3912 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003913
3914 if (check_restricted() || check_secure())
3915 return;
3916 if (buf == NULL)
3917 return;
3918 term = buf->b_term;
Bram Moolenaara5c48c22018-09-09 19:56:07 +02003919 if (term->tl_vterm == NULL)
3920 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003921 emsg(_("E958: Job already finished"));
Bram Moolenaara5c48c22018-09-09 19:56:07 +02003922 return;
3923 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003924
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003925 if (argvars[2].v_type != VAR_UNKNOWN)
3926 {
3927 dict_T *d;
3928
3929 if (argvars[2].v_type != VAR_DICT)
3930 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003931 emsg(_(e_dictreq));
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003932 return;
3933 }
3934 d = argvars[2].vval.v_dict;
3935 if (d != NULL)
3936 {
Bram Moolenaar8f667172018-12-14 15:38:31 +01003937 max_height = dict_get_number(d, (char_u *)"rows");
3938 max_width = dict_get_number(d, (char_u *)"columns");
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003939 }
3940 }
3941
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003942 fname = tv_get_string_chk(&argvars[1]);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003943 if (fname == NULL)
3944 return;
3945 if (mch_stat((char *)fname, &st) >= 0)
3946 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003947 semsg(_("E953: File exists: %s"), fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003948 return;
3949 }
3950
Bram Moolenaard96ff162018-02-18 22:13:29 +01003951 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3952 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003953 semsg(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003954 return;
3955 }
3956
3957 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3958
3959 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003960 state = vterm_obtain_state(term->tl_vterm);
3961 vterm_state_get_cursorpos(state, &cursor_pos);
3962
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003963 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3964 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003965 {
3966 int repeat = 0;
3967
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003968 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3969 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003970 {
3971 VTermScreenCell cell;
3972 int same_attr;
3973 int same_chars = TRUE;
3974 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003975 int is_cursor_pos = (pos.col == cursor_pos.col
3976 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003977
3978 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3979 vim_memset(&cell, 0, sizeof(cell));
3980
3981 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3982 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003983 int c = cell.chars[i];
3984 int pc = prev_cell.chars[i];
3985
3986 /* For the first character NUL is the same as space. */
3987 if (i == 0)
3988 {
3989 c = (c == NUL) ? ' ' : c;
3990 pc = (pc == NUL) ? ' ' : pc;
3991 }
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02003992 if (c != pc)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003993 same_chars = FALSE;
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02003994 if (c == NUL || pc == NUL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003995 break;
3996 }
3997 same_attr = vtermAttr2hl(cell.attrs)
3998 == vtermAttr2hl(prev_cell.attrs)
3999 && same_color(&cell.fg, &prev_cell.fg)
4000 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004001 if (same_chars && cell.width == prev_cell.width && same_attr
4002 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004003 {
4004 ++repeat;
4005 }
4006 else
4007 {
4008 if (repeat > 0)
4009 {
4010 fprintf(fd, "@%d", repeat);
4011 repeat = 0;
4012 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004013 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004014
4015 if (cell.chars[0] == NUL)
4016 fputs(" ", fd);
4017 else
4018 {
4019 char_u charbuf[10];
4020 int len;
4021
4022 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
4023 && cell.chars[i] != NUL; ++i)
4024 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02004025 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004026 fwrite(charbuf, len, 1, fd);
4027 }
4028 }
4029
4030 /* When only the characters differ we don't write anything, the
4031 * following "|", "@" or NL will indicate using the same
4032 * attributes. */
4033 if (cell.width != prev_cell.width || !same_attr)
4034 {
4035 if (cell.width == 2)
4036 {
4037 fputs("*", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004038 }
4039 else
4040 fputs("+", fd);
4041
4042 if (same_attr)
4043 {
4044 fputs("&", fd);
4045 }
4046 else
4047 {
4048 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
4049 if (same_color(&cell.fg, &prev_cell.fg))
4050 fputs("&", fd);
4051 else
4052 {
4053 fputs("#", fd);
4054 dump_term_color(fd, &cell.fg);
4055 }
4056 if (same_color(&cell.bg, &prev_cell.bg))
4057 fputs("&", fd);
4058 else
4059 {
4060 fputs("#", fd);
4061 dump_term_color(fd, &cell.bg);
4062 }
4063 }
4064 }
4065
4066 prev_cell = cell;
4067 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004068
4069 if (cell.width == 2)
4070 ++pos.col;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004071 }
4072 if (repeat > 0)
4073 fprintf(fd, "@%d", repeat);
4074 fputs("\n", fd);
4075 }
4076
4077 fclose(fd);
4078}
4079
4080/*
4081 * Called when a dump is corrupted. Put a breakpoint here when debugging.
4082 */
4083 static void
4084dump_is_corrupt(garray_T *gap)
4085{
4086 ga_concat(gap, (char_u *)"CORRUPT");
4087}
4088
4089 static void
4090append_cell(garray_T *gap, cellattr_T *cell)
4091{
4092 if (ga_grow(gap, 1) == OK)
4093 {
4094 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
4095 ++gap->ga_len;
4096 }
4097}
4098
4099/*
4100 * Read the dump file from "fd" and append lines to the current buffer.
4101 * Return the cell width of the longest line.
4102 */
4103 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01004104read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004105{
4106 int c;
4107 garray_T ga_text;
4108 garray_T ga_cell;
4109 char_u *prev_char = NULL;
4110 int attr = 0;
4111 cellattr_T cell;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004112 cellattr_T empty_cell;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004113 term_T *term = curbuf->b_term;
4114 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004115 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004116
4117 ga_init2(&ga_text, 1, 90);
4118 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
4119 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004120 vim_memset(&empty_cell, 0, sizeof(empty_cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01004121 cursor_pos->row = -1;
4122 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004123
4124 c = fgetc(fd);
4125 for (;;)
4126 {
4127 if (c == EOF)
4128 break;
Bram Moolenaar0fd6be72018-10-23 21:42:59 +02004129 if (c == '\r')
4130 {
4131 // DOS line endings? Ignore.
4132 c = fgetc(fd);
4133 }
4134 else if (c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004135 {
4136 /* End of a line: append it to the buffer. */
4137 if (ga_text.ga_data == NULL)
4138 dump_is_corrupt(&ga_text);
4139 if (ga_grow(&term->tl_scrollback, 1) == OK)
4140 {
4141 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
4142 + term->tl_scrollback.ga_len;
4143
4144 if (max_cells < ga_cell.ga_len)
4145 max_cells = ga_cell.ga_len;
4146 line->sb_cols = ga_cell.ga_len;
4147 line->sb_cells = ga_cell.ga_data;
4148 line->sb_fill_attr = term->tl_default_color;
4149 ++term->tl_scrollback.ga_len;
4150 ga_init(&ga_cell);
4151
4152 ga_append(&ga_text, NUL);
4153 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4154 ga_text.ga_len, FALSE);
4155 }
4156 else
4157 ga_clear(&ga_cell);
4158 ga_text.ga_len = 0;
4159
4160 c = fgetc(fd);
4161 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004162 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004163 {
4164 int prev_len = ga_text.ga_len;
4165
Bram Moolenaar9271d052018-02-25 21:39:46 +01004166 if (c == '>')
4167 {
4168 if (cursor_pos->row != -1)
4169 dump_is_corrupt(&ga_text); /* duplicate cursor */
4170 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
4171 cursor_pos->col = ga_cell.ga_len;
4172 }
4173
Bram Moolenaard96ff162018-02-18 22:13:29 +01004174 /* normal character(s) followed by "+", "*", "|", "@" or NL */
4175 c = fgetc(fd);
4176 if (c != EOF)
4177 ga_append(&ga_text, c);
4178 for (;;)
4179 {
4180 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004181 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01004182 || c == EOF || c == '\n')
4183 break;
4184 ga_append(&ga_text, c);
4185 }
4186
4187 /* save the character for repeating it */
4188 vim_free(prev_char);
4189 if (ga_text.ga_data != NULL)
4190 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
4191 ga_text.ga_len - prev_len);
4192
Bram Moolenaar9271d052018-02-25 21:39:46 +01004193 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004194 {
4195 /* use all attributes from previous cell */
4196 }
4197 else if (c == '+' || c == '*')
4198 {
4199 int is_bg;
4200
4201 cell.width = c == '+' ? 1 : 2;
4202
4203 c = fgetc(fd);
4204 if (c == '&')
4205 {
4206 /* use same attr as previous cell */
4207 c = fgetc(fd);
4208 }
4209 else if (isdigit(c))
4210 {
4211 /* get the decimal attribute */
4212 attr = 0;
4213 while (isdigit(c))
4214 {
4215 attr = attr * 10 + (c - '0');
4216 c = fgetc(fd);
4217 }
4218 hl2vtermAttr(attr, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004219
4220 /* is_bg == 0: fg, is_bg == 1: bg */
4221 for (is_bg = 0; is_bg <= 1; ++is_bg)
4222 {
4223 if (c == '&')
4224 {
4225 /* use same color as previous cell */
4226 c = fgetc(fd);
4227 }
4228 else if (c == '#')
4229 {
4230 int red, green, blue, index = 0;
4231
4232 c = fgetc(fd);
4233 red = hex2nr(c);
4234 c = fgetc(fd);
4235 red = (red << 4) + hex2nr(c);
4236 c = fgetc(fd);
4237 green = hex2nr(c);
4238 c = fgetc(fd);
4239 green = (green << 4) + hex2nr(c);
4240 c = fgetc(fd);
4241 blue = hex2nr(c);
4242 c = fgetc(fd);
4243 blue = (blue << 4) + hex2nr(c);
4244 c = fgetc(fd);
4245 if (!isdigit(c))
4246 dump_is_corrupt(&ga_text);
4247 while (isdigit(c))
4248 {
4249 index = index * 10 + (c - '0');
4250 c = fgetc(fd);
4251 }
4252
4253 if (is_bg)
4254 {
4255 cell.bg.red = red;
4256 cell.bg.green = green;
4257 cell.bg.blue = blue;
4258 cell.bg.ansi_index = index;
4259 }
4260 else
4261 {
4262 cell.fg.red = red;
4263 cell.fg.green = green;
4264 cell.fg.blue = blue;
4265 cell.fg.ansi_index = index;
4266 }
4267 }
4268 else
4269 dump_is_corrupt(&ga_text);
4270 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004271 }
4272 else
4273 dump_is_corrupt(&ga_text);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004274 }
4275 else
4276 dump_is_corrupt(&ga_text);
4277
4278 append_cell(&ga_cell, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004279 if (cell.width == 2)
4280 append_cell(&ga_cell, &empty_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004281 }
4282 else if (c == '@')
4283 {
4284 if (prev_char == NULL)
4285 dump_is_corrupt(&ga_text);
4286 else
4287 {
4288 int count = 0;
4289
4290 /* repeat previous character, get the count */
4291 for (;;)
4292 {
4293 c = fgetc(fd);
4294 if (!isdigit(c))
4295 break;
4296 count = count * 10 + (c - '0');
4297 }
4298
4299 while (count-- > 0)
4300 {
4301 ga_concat(&ga_text, prev_char);
4302 append_cell(&ga_cell, &cell);
4303 }
4304 }
4305 }
4306 else
4307 {
4308 dump_is_corrupt(&ga_text);
4309 c = fgetc(fd);
4310 }
4311 }
4312
4313 if (ga_text.ga_len > 0)
4314 {
4315 /* trailing characters after last NL */
4316 dump_is_corrupt(&ga_text);
4317 ga_append(&ga_text, NUL);
4318 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4319 ga_text.ga_len, FALSE);
4320 }
4321
4322 ga_clear(&ga_text);
4323 vim_free(prev_char);
4324
4325 return max_cells;
4326}
4327
4328/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004329 * Return an allocated string with at least "text_width" "=" characters and
4330 * "fname" inserted in the middle.
4331 */
4332 static char_u *
4333get_separator(int text_width, char_u *fname)
4334{
4335 int width = MAX(text_width, curwin->w_width);
4336 char_u *textline;
4337 int fname_size;
4338 char_u *p = fname;
4339 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004340 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004341
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004342 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004343 if (textline == NULL)
4344 return NULL;
4345
4346 fname_size = vim_strsize(fname);
4347 if (fname_size < width - 8)
4348 {
4349 /* enough room, don't use the full window width */
4350 width = MAX(text_width, fname_size + 8);
4351 }
4352 else if (fname_size > width - 8)
4353 {
4354 /* full name doesn't fit, use only the tail */
4355 p = gettail(fname);
4356 fname_size = vim_strsize(p);
4357 }
4358 /* skip characters until the name fits */
4359 while (fname_size > width - 8)
4360 {
4361 p += (*mb_ptr2len)(p);
4362 fname_size = vim_strsize(p);
4363 }
4364
4365 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4366 textline[i] = '=';
4367 textline[i++] = ' ';
4368
4369 STRCPY(textline + i, p);
4370 off = STRLEN(textline);
4371 textline[off] = ' ';
4372 for (i = 1; i < (width - fname_size) / 2; ++i)
4373 textline[off + i] = '=';
4374 textline[off + i] = NUL;
4375
4376 return textline;
4377}
4378
4379/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004380 * Common for "term_dumpdiff()" and "term_dumpload()".
4381 */
4382 static void
4383term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4384{
4385 jobopt_T opt;
4386 buf_T *buf;
4387 char_u buf1[NUMBUFLEN];
4388 char_u buf2[NUMBUFLEN];
4389 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004390 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004391 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004392 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004393 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004394 char_u *textline = NULL;
4395
4396 /* First open the files. If this fails bail out. */
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004397 fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004398 if (do_diff)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004399 fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004400 if (fname1 == NULL || (do_diff && fname2 == NULL))
4401 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004402 emsg(_(e_invarg));
Bram Moolenaard96ff162018-02-18 22:13:29 +01004403 return;
4404 }
4405 fd1 = mch_fopen((char *)fname1, READBIN);
4406 if (fd1 == NULL)
4407 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004408 semsg(_(e_notread), fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004409 return;
4410 }
4411 if (do_diff)
4412 {
4413 fd2 = mch_fopen((char *)fname2, READBIN);
4414 if (fd2 == NULL)
4415 {
4416 fclose(fd1);
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004417 semsg(_(e_notread), fname2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004418 return;
4419 }
4420 }
4421
4422 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004423 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4424 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4425 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4426 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4427 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004428
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004429 if (opt.jo_term_name == NULL)
4430 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004431 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004432
Bram Moolenaarb571c632018-03-21 22:27:59 +01004433 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004434 if (fname_tofree != NULL)
4435 {
4436 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4437 opt.jo_term_name = fname_tofree;
4438 }
4439 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004440
Bram Moolenaar13568252018-03-16 20:46:58 +01004441 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004442 if (buf != NULL && buf->b_term != NULL)
4443 {
4444 int i;
4445 linenr_T bot_lnum;
4446 linenr_T lnum;
4447 term_T *term = buf->b_term;
4448 int width;
4449 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004450 VTermPos cursor_pos1;
4451 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004452
Bram Moolenaar52acb112018-03-18 19:20:22 +01004453 init_default_colors(term);
4454
Bram Moolenaard96ff162018-02-18 22:13:29 +01004455 rettv->vval.v_number = buf->b_fnum;
4456
4457 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004458 width = read_dump_file(fd1, &cursor_pos1);
4459
4460 /* position the cursor */
4461 if (cursor_pos1.row >= 0)
4462 {
4463 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4464 coladvance(cursor_pos1.col);
4465 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004466
4467 /* Delete the empty line that was in the empty buffer. */
4468 ml_delete(1, FALSE);
4469
4470 /* For term_dumpload() we are done here. */
4471 if (!do_diff)
4472 goto theend;
4473
4474 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4475
Bram Moolenaar4a696342018-04-05 18:45:26 +02004476 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004477 if (textline == NULL)
4478 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004479 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4480 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4481 vim_free(textline);
4482
4483 textline = get_separator(width, fname2);
4484 if (textline == NULL)
4485 goto theend;
4486 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4487 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004488 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004489
4490 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004491 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004492 if (width2 > width)
4493 {
4494 vim_free(textline);
4495 textline = alloc(width2 + 1);
4496 if (textline == NULL)
4497 goto theend;
4498 width = width2;
4499 textline[width] = NUL;
4500 }
4501 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4502
4503 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4504 {
4505 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4506 {
4507 /* bottom part has fewer rows, fill with "-" */
4508 for (i = 0; i < width; ++i)
4509 textline[i] = '-';
4510 }
4511 else
4512 {
4513 char_u *line1;
4514 char_u *line2;
4515 char_u *p1;
4516 char_u *p2;
4517 int col;
4518 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4519 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4520 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4521 ->sb_cells;
4522
4523 /* Make a copy, getting the second line will invalidate it. */
4524 line1 = vim_strsave(ml_get(lnum));
4525 if (line1 == NULL)
4526 break;
4527 p1 = line1;
4528
4529 line2 = ml_get(lnum + bot_lnum);
4530 p2 = line2;
4531 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4532 {
4533 int len1 = utfc_ptr2len(p1);
4534 int len2 = utfc_ptr2len(p2);
4535
4536 textline[col] = ' ';
4537 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004538 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004539 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004540 else if (lnum == cursor_pos1.row + 1
4541 && col == cursor_pos1.col
4542 && (cursor_pos1.row != cursor_pos2.row
4543 || cursor_pos1.col != cursor_pos2.col))
4544 /* cursor in first but not in second */
4545 textline[col] = '>';
4546 else if (lnum == cursor_pos2.row + 1
4547 && col == cursor_pos2.col
4548 && (cursor_pos1.row != cursor_pos2.row
4549 || cursor_pos1.col != cursor_pos2.col))
4550 /* cursor in second but not in first */
4551 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004552 else if (cellattr1 != NULL && cellattr2 != NULL)
4553 {
4554 if ((cellattr1 + col)->width
4555 != (cellattr2 + col)->width)
4556 textline[col] = 'w';
4557 else if (!same_color(&(cellattr1 + col)->fg,
4558 &(cellattr2 + col)->fg))
4559 textline[col] = 'f';
4560 else if (!same_color(&(cellattr1 + col)->bg,
4561 &(cellattr2 + col)->bg))
4562 textline[col] = 'b';
4563 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4564 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4565 textline[col] = 'a';
4566 }
4567 p1 += len1;
4568 p2 += len2;
4569 /* TODO: handle different width */
4570 }
4571 vim_free(line1);
4572
4573 while (col < width)
4574 {
4575 if (*p1 == NUL && *p2 == NUL)
4576 textline[col] = '?';
4577 else if (*p1 == NUL)
4578 {
4579 textline[col] = '+';
4580 p2 += utfc_ptr2len(p2);
4581 }
4582 else
4583 {
4584 textline[col] = '-';
4585 p1 += utfc_ptr2len(p1);
4586 }
4587 ++col;
4588 }
4589 }
4590 if (add_empty_scrollback(term, &term->tl_default_color,
4591 term->tl_top_diff_rows) == OK)
4592 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4593 ++bot_lnum;
4594 }
4595
4596 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4597 {
4598 /* bottom part has more rows, fill with "+" */
4599 for (i = 0; i < width; ++i)
4600 textline[i] = '+';
4601 if (add_empty_scrollback(term, &term->tl_default_color,
4602 term->tl_top_diff_rows) == OK)
4603 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4604 ++lnum;
4605 ++bot_lnum;
4606 }
4607
4608 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004609
4610 /* looks better without wrapping */
4611 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004612 }
4613
4614theend:
4615 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004616 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004617 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004618 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004619 fclose(fd2);
4620}
4621
4622/*
4623 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4624 * bottom files.
4625 * Return FAIL when this is not possible.
4626 */
4627 int
4628term_swap_diff()
4629{
4630 term_T *term = curbuf->b_term;
4631 linenr_T line_count;
4632 linenr_T top_rows;
4633 linenr_T bot_rows;
4634 linenr_T bot_start;
4635 linenr_T lnum;
4636 char_u *p;
4637 sb_line_T *sb_line;
4638
4639 if (term == NULL
4640 || !term_is_finished(curbuf)
4641 || term->tl_top_diff_rows == 0
4642 || term->tl_scrollback.ga_len == 0)
4643 return FAIL;
4644
4645 line_count = curbuf->b_ml.ml_line_count;
4646 top_rows = term->tl_top_diff_rows;
4647 bot_rows = term->tl_bot_diff_rows;
4648 bot_start = line_count - bot_rows;
4649 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4650
4651 /* move lines from top to above the bottom part */
4652 for (lnum = 1; lnum <= top_rows; ++lnum)
4653 {
4654 p = vim_strsave(ml_get(1));
4655 if (p == NULL)
4656 return OK;
4657 ml_append(bot_start, p, 0, FALSE);
4658 ml_delete(1, FALSE);
4659 vim_free(p);
4660 }
4661
4662 /* move lines from bottom to the top */
4663 for (lnum = 1; lnum <= bot_rows; ++lnum)
4664 {
4665 p = vim_strsave(ml_get(bot_start + lnum));
4666 if (p == NULL)
4667 return OK;
4668 ml_delete(bot_start + lnum, FALSE);
4669 ml_append(lnum - 1, p, 0, FALSE);
4670 vim_free(p);
4671 }
4672
4673 if (top_rows == bot_rows)
4674 {
4675 /* rows counts are equal, can swap cell properties */
4676 for (lnum = 0; lnum < top_rows; ++lnum)
4677 {
4678 sb_line_T temp;
4679
4680 temp = *(sb_line + lnum);
4681 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4682 *(sb_line + bot_start + lnum) = temp;
4683 }
4684 }
4685 else
4686 {
4687 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4688 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4689
4690 /* need to copy cell properties into temp memory */
4691 if (temp != NULL)
4692 {
4693 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4694 mch_memmove(term->tl_scrollback.ga_data,
4695 temp + bot_start,
4696 sizeof(sb_line_T) * bot_rows);
4697 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4698 temp + top_rows,
4699 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4700 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4701 + line_count - top_rows,
4702 temp,
4703 sizeof(sb_line_T) * top_rows);
4704 vim_free(temp);
4705 }
4706 }
4707
4708 term->tl_top_diff_rows = bot_rows;
4709 term->tl_bot_diff_rows = top_rows;
4710
4711 update_screen(NOT_VALID);
4712 return OK;
4713}
4714
4715/*
4716 * "term_dumpdiff(filename, filename, options)" function
4717 */
4718 void
4719f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4720{
4721 term_load_dump(argvars, rettv, TRUE);
4722}
4723
4724/*
4725 * "term_dumpload(filename, options)" function
4726 */
4727 void
4728f_term_dumpload(typval_T *argvars, typval_T *rettv)
4729{
4730 term_load_dump(argvars, rettv, FALSE);
4731}
4732
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004733/*
4734 * "term_getaltscreen(buf)" function
4735 */
4736 void
4737f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4738{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004739 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004740
4741 if (buf == NULL)
4742 return;
4743 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4744}
4745
4746/*
4747 * "term_getattr(attr, name)" function
4748 */
4749 void
4750f_term_getattr(typval_T *argvars, typval_T *rettv)
4751{
4752 int attr;
4753 size_t i;
4754 char_u *name;
4755
4756 static struct {
4757 char *name;
4758 int attr;
4759 } attrs[] = {
4760 {"bold", HL_BOLD},
4761 {"italic", HL_ITALIC},
4762 {"underline", HL_UNDERLINE},
4763 {"strike", HL_STRIKETHROUGH},
4764 {"reverse", HL_INVERSE},
4765 };
4766
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004767 attr = tv_get_number(&argvars[0]);
4768 name = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004769 if (name == NULL)
4770 return;
4771
4772 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4773 if (STRCMP(name, attrs[i].name) == 0)
4774 {
4775 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4776 break;
4777 }
4778}
4779
4780/*
4781 * "term_getcursor(buf)" function
4782 */
4783 void
4784f_term_getcursor(typval_T *argvars, typval_T *rettv)
4785{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004786 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004787 term_T *term;
4788 list_T *l;
4789 dict_T *d;
4790
4791 if (rettv_list_alloc(rettv) == FAIL)
4792 return;
4793 if (buf == NULL)
4794 return;
4795 term = buf->b_term;
4796
4797 l = rettv->vval.v_list;
4798 list_append_number(l, term->tl_cursor_pos.row + 1);
4799 list_append_number(l, term->tl_cursor_pos.col + 1);
4800
4801 d = dict_alloc();
4802 if (d != NULL)
4803 {
Bram Moolenaare0be1672018-07-08 16:50:37 +02004804 dict_add_number(d, "visible", term->tl_cursor_visible);
4805 dict_add_number(d, "blink", blink_state_is_inverted()
4806 ? !term->tl_cursor_blink : term->tl_cursor_blink);
4807 dict_add_number(d, "shape", term->tl_cursor_shape);
4808 dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004809 list_append_dict(l, d);
4810 }
4811}
4812
4813/*
4814 * "term_getjob(buf)" function
4815 */
4816 void
4817f_term_getjob(typval_T *argvars, typval_T *rettv)
4818{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004819 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004820
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004821 if (buf == NULL)
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01004822 {
4823 rettv->v_type = VAR_SPECIAL;
4824 rettv->vval.v_number = VVAL_NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004825 return;
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01004826 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004827
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01004828 rettv->v_type = VAR_JOB;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004829 rettv->vval.v_job = buf->b_term->tl_job;
4830 if (rettv->vval.v_job != NULL)
4831 ++rettv->vval.v_job->jv_refcount;
4832}
4833
4834 static int
4835get_row_number(typval_T *tv, term_T *term)
4836{
4837 if (tv->v_type == VAR_STRING
4838 && tv->vval.v_string != NULL
4839 && STRCMP(tv->vval.v_string, ".") == 0)
4840 return term->tl_cursor_pos.row;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004841 return (int)tv_get_number(tv) - 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004842}
4843
4844/*
4845 * "term_getline(buf, row)" function
4846 */
4847 void
4848f_term_getline(typval_T *argvars, typval_T *rettv)
4849{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004850 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004851 term_T *term;
4852 int row;
4853
4854 rettv->v_type = VAR_STRING;
4855 if (buf == NULL)
4856 return;
4857 term = buf->b_term;
4858 row = get_row_number(&argvars[1], term);
4859
4860 if (term->tl_vterm == NULL)
4861 {
4862 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4863
4864 /* vterm is finished, get the text from the buffer */
4865 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4866 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4867 }
4868 else
4869 {
4870 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4871 VTermRect rect;
4872 int len;
4873 char_u *p;
4874
4875 if (row < 0 || row >= term->tl_rows)
4876 return;
4877 len = term->tl_cols * MB_MAXBYTES + 1;
4878 p = alloc(len);
4879 if (p == NULL)
4880 return;
4881 rettv->vval.v_string = p;
4882
4883 rect.start_col = 0;
4884 rect.end_col = term->tl_cols;
4885 rect.start_row = row;
4886 rect.end_row = row + 1;
4887 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4888 }
4889}
4890
4891/*
4892 * "term_getscrolled(buf)" function
4893 */
4894 void
4895f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4896{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004897 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004898
4899 if (buf == NULL)
4900 return;
4901 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4902}
4903
4904/*
4905 * "term_getsize(buf)" function
4906 */
4907 void
4908f_term_getsize(typval_T *argvars, typval_T *rettv)
4909{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004910 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004911 list_T *l;
4912
4913 if (rettv_list_alloc(rettv) == FAIL)
4914 return;
4915 if (buf == NULL)
4916 return;
4917
4918 l = rettv->vval.v_list;
4919 list_append_number(l, buf->b_term->tl_rows);
4920 list_append_number(l, buf->b_term->tl_cols);
4921}
4922
4923/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004924 * "term_setsize(buf, rows, cols)" function
4925 */
4926 void
4927f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4928{
4929 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4930 term_T *term;
4931 varnumber_T rows, cols;
4932
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004933 if (buf == NULL)
4934 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004935 emsg(_("E955: Not a terminal buffer"));
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004936 return;
4937 }
4938 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004939 return;
4940 term = buf->b_term;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004941 rows = tv_get_number(&argvars[1]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02004942 rows = rows <= 0 ? term->tl_rows : rows;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004943 cols = tv_get_number(&argvars[2]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02004944 cols = cols <= 0 ? term->tl_cols : cols;
4945 vterm_set_size(term->tl_vterm, rows, cols);
4946 /* handle_resize() will resize the windows */
4947
4948 /* Get and remember the size we ended up with. Update the pty. */
4949 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
4950 term_report_winsize(term, term->tl_rows, term->tl_cols);
4951}
4952
4953/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004954 * "term_getstatus(buf)" function
4955 */
4956 void
4957f_term_getstatus(typval_T *argvars, typval_T *rettv)
4958{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004959 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004960 term_T *term;
4961 char_u val[100];
4962
4963 rettv->v_type = VAR_STRING;
4964 if (buf == NULL)
4965 return;
4966 term = buf->b_term;
4967
4968 if (term_job_running(term))
4969 STRCPY(val, "running");
4970 else
4971 STRCPY(val, "finished");
4972 if (term->tl_normal_mode)
4973 STRCAT(val, ",normal");
4974 rettv->vval.v_string = vim_strsave(val);
4975}
4976
4977/*
4978 * "term_gettitle(buf)" function
4979 */
4980 void
4981f_term_gettitle(typval_T *argvars, typval_T *rettv)
4982{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004983 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004984
4985 rettv->v_type = VAR_STRING;
4986 if (buf == NULL)
4987 return;
4988
4989 if (buf->b_term->tl_title != NULL)
4990 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4991}
4992
4993/*
4994 * "term_gettty(buf)" function
4995 */
4996 void
4997f_term_gettty(typval_T *argvars, typval_T *rettv)
4998{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004999 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar9b50f362018-05-07 20:10:17 +02005000 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005001 int num = 0;
5002
5003 rettv->v_type = VAR_STRING;
5004 if (buf == NULL)
5005 return;
5006 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005007 num = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005008
5009 switch (num)
5010 {
5011 case 0:
5012 if (buf->b_term->tl_job != NULL)
5013 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005014 break;
5015 case 1:
5016 if (buf->b_term->tl_job != NULL)
5017 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005018 break;
5019 default:
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005020 semsg(_(e_invarg2), tv_get_string(&argvars[1]));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005021 return;
5022 }
5023 if (p != NULL)
5024 rettv->vval.v_string = vim_strsave(p);
5025}
5026
5027/*
5028 * "term_list()" function
5029 */
5030 void
5031f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
5032{
5033 term_T *tp;
5034 list_T *l;
5035
5036 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
5037 return;
5038
5039 l = rettv->vval.v_list;
5040 for (tp = first_term; tp != NULL; tp = tp->tl_next)
5041 if (tp != NULL && tp->tl_buffer != NULL)
5042 if (list_append_number(l,
5043 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
5044 return;
5045}
5046
5047/*
5048 * "term_scrape(buf, row)" function
5049 */
5050 void
5051f_term_scrape(typval_T *argvars, typval_T *rettv)
5052{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005053 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005054 VTermScreen *screen = NULL;
5055 VTermPos pos;
5056 list_T *l;
5057 term_T *term;
5058 char_u *p;
5059 sb_line_T *line;
5060
5061 if (rettv_list_alloc(rettv) == FAIL)
5062 return;
5063 if (buf == NULL)
5064 return;
5065 term = buf->b_term;
5066
5067 l = rettv->vval.v_list;
5068 pos.row = get_row_number(&argvars[1], term);
5069
5070 if (term->tl_vterm != NULL)
5071 {
5072 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar06d62602018-12-27 21:27:03 +01005073 if (screen == NULL) // can't really happen
5074 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005075 p = NULL;
5076 line = NULL;
5077 }
5078 else
5079 {
5080 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
5081
5082 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
5083 return;
5084 p = ml_get_buf(buf, lnum + 1, FALSE);
5085 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
5086 }
5087
5088 for (pos.col = 0; pos.col < term->tl_cols; )
5089 {
5090 dict_T *dcell;
5091 int width;
5092 VTermScreenCellAttrs attrs;
5093 VTermColor fg, bg;
5094 char_u rgb[8];
5095 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
5096 int off = 0;
5097 int i;
5098
5099 if (screen == NULL)
5100 {
5101 cellattr_T *cellattr;
5102 int len;
5103
5104 /* vterm has finished, get the cell from scrollback */
5105 if (pos.col >= line->sb_cols)
5106 break;
5107 cellattr = line->sb_cells + pos.col;
5108 width = cellattr->width;
5109 attrs = cellattr->attrs;
5110 fg = cellattr->fg;
5111 bg = cellattr->bg;
5112 len = MB_PTR2LEN(p);
5113 mch_memmove(mbs, p, len);
5114 mbs[len] = NUL;
5115 p += len;
5116 }
5117 else
5118 {
5119 VTermScreenCell cell;
5120 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
5121 break;
5122 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5123 {
5124 if (cell.chars[i] == 0)
5125 break;
5126 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
5127 }
5128 mbs[off] = NUL;
5129 width = cell.width;
5130 attrs = cell.attrs;
5131 fg = cell.fg;
5132 bg = cell.bg;
5133 }
5134 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01005135 if (dcell == NULL)
5136 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005137 list_append_dict(l, dcell);
5138
Bram Moolenaare0be1672018-07-08 16:50:37 +02005139 dict_add_string(dcell, "chars", mbs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005140
5141 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5142 fg.red, fg.green, fg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005143 dict_add_string(dcell, "fg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005144 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5145 bg.red, bg.green, bg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005146 dict_add_string(dcell, "bg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005147
Bram Moolenaare0be1672018-07-08 16:50:37 +02005148 dict_add_number(dcell, "attr", cell2attr(attrs, fg, bg));
5149 dict_add_number(dcell, "width", width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005150
5151 ++pos.col;
5152 if (width == 2)
5153 ++pos.col;
5154 }
5155}
5156
5157/*
5158 * "term_sendkeys(buf, keys)" function
5159 */
5160 void
5161f_term_sendkeys(typval_T *argvars, typval_T *rettv)
5162{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005163 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005164 char_u *msg;
5165 term_T *term;
5166
5167 rettv->v_type = VAR_UNKNOWN;
5168 if (buf == NULL)
5169 return;
5170
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005171 msg = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005172 if (msg == NULL)
5173 return;
5174 term = buf->b_term;
5175 if (term->tl_vterm == NULL)
5176 return;
5177
5178 while (*msg != NUL)
5179 {
Bram Moolenaar6b810d92018-06-04 17:28:44 +02005180 int c;
5181
5182 if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL)
5183 {
5184 c = TO_SPECIAL(msg[1], msg[2]);
5185 msg += 3;
5186 }
5187 else
5188 {
5189 c = PTR2CHAR(msg);
5190 msg += MB_CPTR2LEN(msg);
5191 }
5192 send_keys_to_term(term, c, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005193 }
5194}
5195
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005196#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
5197/*
5198 * "term_getansicolors(buf)" function
5199 */
5200 void
5201f_term_getansicolors(typval_T *argvars, typval_T *rettv)
5202{
5203 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
5204 term_T *term;
5205 VTermState *state;
5206 VTermColor color;
5207 char_u hexbuf[10];
5208 int index;
5209 list_T *list;
5210
5211 if (rettv_list_alloc(rettv) == FAIL)
5212 return;
5213
5214 if (buf == NULL)
5215 return;
5216 term = buf->b_term;
5217 if (term->tl_vterm == NULL)
5218 return;
5219
5220 list = rettv->vval.v_list;
5221 state = vterm_obtain_state(term->tl_vterm);
5222 for (index = 0; index < 16; index++)
5223 {
5224 vterm_state_get_palette_color(state, index, &color);
5225 sprintf((char *)hexbuf, "#%02x%02x%02x",
5226 color.red, color.green, color.blue);
5227 if (list_append_string(list, hexbuf, 7) == FAIL)
5228 return;
5229 }
5230}
5231
5232/*
5233 * "term_setansicolors(buf, list)" function
5234 */
5235 void
5236f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
5237{
5238 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
5239 term_T *term;
5240
5241 if (buf == NULL)
5242 return;
5243 term = buf->b_term;
5244 if (term->tl_vterm == NULL)
5245 return;
5246
5247 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
5248 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005249 emsg(_(e_listreq));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005250 return;
5251 }
5252
5253 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005254 emsg(_(e_invarg));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005255}
5256#endif
5257
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005258/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005259 * "term_setrestore(buf, command)" function
5260 */
5261 void
5262f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5263{
5264#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005265 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005266 term_T *term;
5267 char_u *cmd;
5268
5269 if (buf == NULL)
5270 return;
5271 term = buf->b_term;
5272 vim_free(term->tl_command);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005273 cmd = tv_get_string_chk(&argvars[1]);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005274 if (cmd != NULL)
5275 term->tl_command = vim_strsave(cmd);
5276 else
5277 term->tl_command = NULL;
5278#endif
5279}
5280
5281/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005282 * "term_setkill(buf, how)" function
5283 */
5284 void
5285f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5286{
5287 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5288 term_T *term;
5289 char_u *how;
5290
5291 if (buf == NULL)
5292 return;
5293 term = buf->b_term;
5294 vim_free(term->tl_kill);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005295 how = tv_get_string_chk(&argvars[1]);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005296 if (how != NULL)
5297 term->tl_kill = vim_strsave(how);
5298 else
5299 term->tl_kill = NULL;
5300}
5301
5302/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005303 * "term_start(command, options)" function
5304 */
5305 void
5306f_term_start(typval_T *argvars, typval_T *rettv)
5307{
5308 jobopt_T opt;
5309 buf_T *buf;
5310
5311 init_job_options(&opt);
5312 if (argvars[1].v_type != VAR_UNKNOWN
5313 && get_job_options(&argvars[1], &opt,
5314 JO_TIMEOUT_ALL + JO_STOPONEXIT
5315 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5316 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5317 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5318 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005319 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005320 + JO2_NORESTORE + JO2_TERM_KILL
5321 + JO2_ANSI_COLORS) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005322 return;
5323
Bram Moolenaar13568252018-03-16 20:46:58 +01005324 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005325
5326 if (buf != NULL && buf->b_term != NULL)
5327 rettv->vval.v_number = buf->b_fnum;
5328}
5329
5330/*
5331 * "term_wait" function
5332 */
5333 void
5334f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5335{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005336 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005337
5338 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005339 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005340 if (buf->b_term->tl_job == NULL)
5341 {
5342 ch_log(NULL, "term_wait(): no job to wait for");
5343 return;
5344 }
5345 if (buf->b_term->tl_job->jv_channel == NULL)
5346 /* channel is closed, nothing to do */
5347 return;
5348
5349 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005350 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005351 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5352 {
5353 /* The job is dead, keep reading channel I/O until the channel is
5354 * closed. buf->b_term may become NULL if the terminal was closed while
5355 * waiting. */
5356 ch_log(NULL, "term_wait(): waiting for channel to close");
5357 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5358 {
5359 mch_check_messages();
5360 parse_queued_messages();
Bram Moolenaard45aa552018-05-21 22:50:29 +02005361 ui_delay(10L, FALSE);
Bram Moolenaare5182262017-11-19 15:05:44 +01005362 if (!buf_valid(buf))
5363 /* If the terminal is closed when the channel is closed the
5364 * buffer disappears. */
5365 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005366 }
5367 mch_check_messages();
5368 parse_queued_messages();
5369 }
5370 else
5371 {
5372 long wait = 10L;
5373
5374 mch_check_messages();
5375 parse_queued_messages();
5376
5377 /* Wait for some time for any channel I/O. */
5378 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005379 wait = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005380 ui_delay(wait, TRUE);
5381 mch_check_messages();
5382
5383 /* Flushing messages on channels is hopefully sufficient.
5384 * TODO: is there a better way? */
5385 parse_queued_messages();
5386 }
5387}
5388
5389/*
5390 * Called when a channel has sent all the lines to a terminal.
5391 * Send a CTRL-D to mark the end of the text.
5392 */
5393 void
5394term_send_eof(channel_T *ch)
5395{
5396 term_T *term;
5397
5398 for (term = first_term; term != NULL; term = term->tl_next)
5399 if (term->tl_job == ch->ch_job)
5400 {
5401 if (term->tl_eof_chars != NULL)
5402 {
5403 channel_send(ch, PART_IN, term->tl_eof_chars,
5404 (int)STRLEN(term->tl_eof_chars), NULL);
5405 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5406 }
5407# ifdef WIN3264
5408 else
5409 /* Default: CTRL-D */
5410 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5411# endif
5412 }
5413}
5414
Bram Moolenaar113e1072019-01-20 15:30:40 +01005415#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005416 job_T *
5417term_getjob(term_T *term)
5418{
5419 return term != NULL ? term->tl_job : NULL;
5420}
Bram Moolenaar113e1072019-01-20 15:30:40 +01005421#endif
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005422
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005423# if defined(WIN3264) || defined(PROTO)
5424
5425/**************************************
5426 * 2. MS-Windows implementation.
5427 */
5428
5429# ifndef PROTO
5430
5431#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5432#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005433#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005434
5435void* (*winpty_config_new)(UINT64, void*);
5436void* (*winpty_open)(void*, void*);
5437void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5438BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5439void (*winpty_config_set_mouse_mode)(void*, int);
5440void (*winpty_config_set_initial_size)(void*, int, int);
5441LPCWSTR (*winpty_conin_name)(void*);
5442LPCWSTR (*winpty_conout_name)(void*);
5443LPCWSTR (*winpty_conerr_name)(void*);
5444void (*winpty_free)(void*);
5445void (*winpty_config_free)(void*);
5446void (*winpty_spawn_config_free)(void*);
5447void (*winpty_error_free)(void*);
5448LPCWSTR (*winpty_error_msg)(void*);
5449BOOL (*winpty_set_size)(void*, int, int, void*);
5450HANDLE (*winpty_agent_process)(void*);
5451
5452#define WINPTY_DLL "winpty.dll"
5453
5454static HINSTANCE hWinPtyDLL = NULL;
5455# endif
5456
5457 static int
5458dyn_winpty_init(int verbose)
5459{
5460 int i;
5461 static struct
5462 {
5463 char *name;
5464 FARPROC *ptr;
5465 } winpty_entry[] =
5466 {
5467 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5468 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5469 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5470 {"winpty_config_set_mouse_mode",
5471 (FARPROC*)&winpty_config_set_mouse_mode},
5472 {"winpty_config_set_initial_size",
5473 (FARPROC*)&winpty_config_set_initial_size},
5474 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5475 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5476 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5477 {"winpty_free", (FARPROC*)&winpty_free},
5478 {"winpty_open", (FARPROC*)&winpty_open},
5479 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5480 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5481 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5482 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5483 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5484 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5485 {NULL, NULL}
5486 };
5487
5488 /* No need to initialize twice. */
5489 if (hWinPtyDLL)
5490 return OK;
5491 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5492 * winpty.dll. */
5493 if (*p_winptydll != NUL)
5494 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5495 if (!hWinPtyDLL)
5496 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5497 if (!hWinPtyDLL)
5498 {
5499 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005500 semsg(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005501 : (char_u *)WINPTY_DLL);
5502 return FAIL;
5503 }
5504 for (i = 0; winpty_entry[i].name != NULL
5505 && winpty_entry[i].ptr != NULL; ++i)
5506 {
5507 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5508 winpty_entry[i].name)) == NULL)
5509 {
5510 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005511 semsg(_(e_loadfunc), winpty_entry[i].name);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005512 return FAIL;
5513 }
5514 }
5515
5516 return OK;
5517}
5518
5519/*
5520 * Create a new terminal of "rows" by "cols" cells.
5521 * Store a reference in "term".
5522 * Return OK or FAIL.
5523 */
5524 static int
5525term_and_job_init(
5526 term_T *term,
5527 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005528 char **argv UNUSED,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005529 jobopt_T *opt,
5530 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005531{
5532 WCHAR *cmd_wchar = NULL;
5533 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005534 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005535 channel_T *channel = NULL;
5536 job_T *job = NULL;
5537 DWORD error;
5538 HANDLE jo = NULL;
5539 HANDLE child_process_handle;
5540 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005541 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005542 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005543 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005544 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005545
5546 if (dyn_winpty_init(TRUE) == FAIL)
5547 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005548 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5549 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005550
5551 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005552 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005553 cmd = argvar->vval.v_string;
5554 }
5555 else if (argvar->v_type == VAR_LIST)
5556 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005557 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005558 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005559 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005560 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005561 if (cmd == NULL || *cmd == NUL)
5562 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005563 emsg(_(e_invarg));
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005564 goto failed;
5565 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005566
5567 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005568 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005569 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005570 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005571 if (opt->jo_cwd != NULL)
5572 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005573
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005574 win32_build_env(opt->jo_env, &ga_env, TRUE);
5575 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005576
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005577 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5578 if (term->tl_winpty_config == NULL)
5579 goto failed;
5580
5581 winpty_config_set_mouse_mode(term->tl_winpty_config,
5582 WINPTY_MOUSE_MODE_FORCE);
5583 winpty_config_set_initial_size(term->tl_winpty_config,
5584 term->tl_cols, term->tl_rows);
5585 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5586 if (term->tl_winpty == NULL)
5587 goto failed;
5588
5589 spawn_config = winpty_spawn_config_new(
5590 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5591 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5592 NULL,
5593 cmd_wchar,
5594 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005595 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005596 &winpty_err);
5597 if (spawn_config == NULL)
5598 goto failed;
5599
5600 channel = add_channel();
5601 if (channel == NULL)
5602 goto failed;
5603
5604 job = job_alloc();
5605 if (job == NULL)
5606 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02005607 if (argvar->v_type == VAR_STRING)
5608 {
5609 int argc;
5610
5611 build_argv_from_string(cmd, &job->jv_argv, &argc);
5612 }
5613 else
5614 {
5615 int argc;
5616
5617 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5618 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005619
5620 if (opt->jo_set & JO_IN_BUF)
5621 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5622
5623 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5624 &child_thread_handle, &error, &winpty_err))
5625 goto failed;
5626
5627 channel_set_pipes(channel,
5628 (sock_T)CreateFileW(
5629 winpty_conin_name(term->tl_winpty),
5630 GENERIC_WRITE, 0, NULL,
5631 OPEN_EXISTING, 0, NULL),
5632 (sock_T)CreateFileW(
5633 winpty_conout_name(term->tl_winpty),
5634 GENERIC_READ, 0, NULL,
5635 OPEN_EXISTING, 0, NULL),
5636 (sock_T)CreateFileW(
5637 winpty_conerr_name(term->tl_winpty),
5638 GENERIC_READ, 0, NULL,
5639 OPEN_EXISTING, 0, NULL));
5640
5641 /* Write lines with CR instead of NL. */
5642 channel->ch_write_text_mode = TRUE;
5643
5644 jo = CreateJobObject(NULL, NULL);
5645 if (jo == NULL)
5646 goto failed;
5647
5648 if (!AssignProcessToJobObject(jo, child_process_handle))
5649 {
5650 /* Failed, switch the way to terminate process with TerminateProcess. */
5651 CloseHandle(jo);
5652 jo = NULL;
5653 }
5654
5655 winpty_spawn_config_free(spawn_config);
5656 vim_free(cmd_wchar);
5657 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005658 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005659
Bram Moolenaarcd929f72018-12-24 21:38:45 +01005660 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
5661 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005662
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005663#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5664 if (opt->jo_set2 & JO2_ANSI_COLORS)
5665 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5666 else
5667 init_vterm_ansi_colors(term->tl_vterm);
5668#endif
5669
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005670 channel_set_job(channel, job, opt);
5671 job_set_options(job, opt);
5672
5673 job->jv_channel = channel;
5674 job->jv_proc_info.hProcess = child_process_handle;
5675 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5676 job->jv_job_object = jo;
5677 job->jv_status = JOB_STARTED;
5678 job->jv_tty_in = utf16_to_enc(
5679 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5680 job->jv_tty_out = utf16_to_enc(
5681 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5682 ++job->jv_refcount;
5683 term->tl_job = job;
5684
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005685 /* Redirecting stdout and stderr doesn't work at the job level. Instead
5686 * open the file here and handle it in. opt->jo_io was changed in
5687 * setup_job_options(), use the original flags here. */
5688 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
5689 {
5690 char_u *fname = opt->jo_io_name[PART_OUT];
5691
5692 ch_log(channel, "Opening output file %s", fname);
5693 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
5694 if (term->tl_out_fd == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005695 semsg(_(e_notopen), fname);
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005696 }
5697
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005698 return OK;
5699
5700failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005701 ga_clear(&ga_cmd);
5702 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005703 vim_free(cmd_wchar);
5704 vim_free(cwd_wchar);
5705 if (spawn_config != NULL)
5706 winpty_spawn_config_free(spawn_config);
5707 if (channel != NULL)
5708 channel_clear(channel);
5709 if (job != NULL)
5710 {
5711 job->jv_channel = NULL;
5712 job_cleanup(job);
5713 }
5714 term->tl_job = NULL;
5715 if (jo != NULL)
5716 CloseHandle(jo);
5717 if (term->tl_winpty != NULL)
5718 winpty_free(term->tl_winpty);
5719 term->tl_winpty = NULL;
5720 if (term->tl_winpty_config != NULL)
5721 winpty_config_free(term->tl_winpty_config);
5722 term->tl_winpty_config = NULL;
5723 if (winpty_err != NULL)
5724 {
5725 char_u *msg = utf16_to_enc(
5726 (short_u *)winpty_error_msg(winpty_err), NULL);
5727
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005728 emsg(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005729 winpty_error_free(winpty_err);
5730 }
5731 return FAIL;
5732}
5733
5734 static int
5735create_pty_only(term_T *term, jobopt_T *options)
5736{
5737 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5738 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5739 char in_name[80], out_name[80];
5740 channel_T *channel = NULL;
5741
Bram Moolenaarcd929f72018-12-24 21:38:45 +01005742 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
5743 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005744
5745 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5746 GetCurrentProcessId(),
5747 curbuf->b_fnum);
5748 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5749 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5750 PIPE_UNLIMITED_INSTANCES,
5751 0, 0, NMPWAIT_NOWAIT, NULL);
5752 if (hPipeIn == INVALID_HANDLE_VALUE)
5753 goto failed;
5754
5755 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5756 GetCurrentProcessId(),
5757 curbuf->b_fnum);
5758 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5759 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5760 PIPE_UNLIMITED_INSTANCES,
5761 0, 0, 0, NULL);
5762 if (hPipeOut == INVALID_HANDLE_VALUE)
5763 goto failed;
5764
5765 ConnectNamedPipe(hPipeIn, NULL);
5766 ConnectNamedPipe(hPipeOut, NULL);
5767
5768 term->tl_job = job_alloc();
5769 if (term->tl_job == NULL)
5770 goto failed;
5771 ++term->tl_job->jv_refcount;
5772
5773 /* behave like the job is already finished */
5774 term->tl_job->jv_status = JOB_FINISHED;
5775
5776 channel = add_channel();
5777 if (channel == NULL)
5778 goto failed;
5779 term->tl_job->jv_channel = channel;
5780 channel->ch_keep_open = TRUE;
5781 channel->ch_named_pipe = TRUE;
5782
5783 channel_set_pipes(channel,
5784 (sock_T)hPipeIn,
5785 (sock_T)hPipeOut,
5786 (sock_T)hPipeOut);
5787 channel_set_job(channel, term->tl_job, options);
5788 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5789 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5790
5791 return OK;
5792
5793failed:
5794 if (hPipeIn != NULL)
5795 CloseHandle(hPipeIn);
5796 if (hPipeOut != NULL)
5797 CloseHandle(hPipeOut);
5798 return FAIL;
5799}
5800
5801/*
5802 * Free the terminal emulator part of "term".
5803 */
5804 static void
5805term_free_vterm(term_T *term)
5806{
5807 if (term->tl_winpty != NULL)
5808 winpty_free(term->tl_winpty);
5809 term->tl_winpty = NULL;
5810 if (term->tl_winpty_config != NULL)
5811 winpty_config_free(term->tl_winpty_config);
5812 term->tl_winpty_config = NULL;
5813 if (term->tl_vterm != NULL)
5814 vterm_free(term->tl_vterm);
5815 term->tl_vterm = NULL;
5816}
5817
5818/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005819 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005820 */
5821 static void
5822term_report_winsize(term_T *term, int rows, int cols)
5823{
5824 if (term->tl_winpty)
5825 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5826}
5827
5828 int
5829terminal_enabled(void)
5830{
5831 return dyn_winpty_init(FALSE) == OK;
5832}
5833
5834# else
5835
5836/**************************************
5837 * 3. Unix-like implementation.
5838 */
5839
5840/*
5841 * Create a new terminal of "rows" by "cols" cells.
5842 * Start job for "cmd".
5843 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005844 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005845 * Return OK or FAIL.
5846 */
5847 static int
5848term_and_job_init(
5849 term_T *term,
5850 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005851 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005852 jobopt_T *opt,
5853 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005854{
Bram Moolenaarcd929f72018-12-24 21:38:45 +01005855 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
5856 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005857
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005858#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5859 if (opt->jo_set2 & JO2_ANSI_COLORS)
5860 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5861 else
5862 init_vterm_ansi_colors(term->tl_vterm);
5863#endif
5864
Bram Moolenaar13568252018-03-16 20:46:58 +01005865 /* This may change a string in "argvar". */
Bram Moolenaar493359e2018-06-12 20:25:52 +02005866 term->tl_job = job_start(argvar, argv, opt, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005867 if (term->tl_job != NULL)
5868 ++term->tl_job->jv_refcount;
5869
5870 return term->tl_job != NULL
5871 && term->tl_job->jv_channel != NULL
5872 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5873}
5874
5875 static int
5876create_pty_only(term_T *term, jobopt_T *opt)
5877{
Bram Moolenaarcd929f72018-12-24 21:38:45 +01005878 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
5879 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005880
5881 term->tl_job = job_alloc();
5882 if (term->tl_job == NULL)
5883 return FAIL;
5884 ++term->tl_job->jv_refcount;
5885
5886 /* behave like the job is already finished */
5887 term->tl_job->jv_status = JOB_FINISHED;
5888
5889 return mch_create_pty_channel(term->tl_job, opt);
5890}
5891
5892/*
5893 * Free the terminal emulator part of "term".
5894 */
5895 static void
5896term_free_vterm(term_T *term)
5897{
5898 if (term->tl_vterm != NULL)
5899 vterm_free(term->tl_vterm);
5900 term->tl_vterm = NULL;
5901}
5902
5903/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005904 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005905 */
5906 static void
5907term_report_winsize(term_T *term, int rows, int cols)
5908{
5909 /* Use an ioctl() to report the new window size to the job. */
5910 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5911 {
5912 int fd = -1;
5913 int part;
5914
5915 for (part = PART_OUT; part < PART_COUNT; ++part)
5916 {
5917 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01005918 if (mch_isatty(fd))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005919 break;
5920 }
5921 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5922 mch_signal_job(term->tl_job, (char_u *)"winch");
5923 }
5924}
5925
5926# endif
5927
5928#endif /* FEAT_TERMINAL */